4

I have a kQueue observer on the Documents directory in my app. I am using the kQueue that triggers a callback when the Documents directory contents changes.

here are two of the settings

eventToAdd.flags  = EV_ADD | EV_CLEAR;
eventToAdd.fflags = NOTE_WRITE;

The problem is that I get notified when the contents changed when a new file is added, but the actual file is not completely copied yet, so when I try process the new file I get a SIGABRT crash.

How can I delay the notification until the file is completed?

some_id
  • 29,466
  • 62
  • 182
  • 304

2 Answers2

2

I solved this by creating 2 listeners... one on the app's Documents directory to watch for new files appearing, and a File proxy object that is created for each file that appears. The File object has a fileBusy flag. The File object sets a 2 second timer when a chunk of data is written to the file. I assume the file is completely written if no updates before the timer expires.

File change listener code here: https://gist.github.com/nielsbot/5155671

My (partial) delegate for the above listener below. (The "File" object that represents a file on disk)

@implementation File<FileChangeObserverDelegate>

    -(void)scheduleFileBusyTimeout
    {
        self.fileBusyTimeoutTimer = [ NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector( fileBusyTimeoutTimerFired: ) userInfo:nil repeats:NO ] ;
    }

    -(void)setFileChangeObserver:(FileChangeObserver *)observer
    {
        [_fileChangeObserver invalidate ] ;
        _fileChangeObserver = observer ;
    }

    -(void)fileChanged:(FileChangeObserver *)asset typeMask:(enum FileChangeNotificationType)type
    {
        @synchronized( self )
        {
            if ( ( type & kFileChangeType_Delete ) != 0 )
            {
                // we're going away soon...
                self.fileChangeObserver = nil ;
            }
            else
            {

                self.fileBusy = YES ;
                [ self scheduleFileBusyTimeout ] ;
            }
        }
    }

    -(void)fileBusyTimeoutTimerFired:(NSTimer*)timer
    {
        @autoreleasepool {
            self.fileBusy = NO ;
        }
    }

    -(void)setFileBusyTimeoutTimer:(NSTimer *)timer
    {
        [ _fileBusyTimeoutTimer invalidate ] ;
        _fileBusyTimeoutTimer = timer ;
    }
@end
nielsbot
  • 15,922
  • 4
  • 48
  • 73
0

First, see:

The short answer is that there's no great way to do this. Ideally you should write the file elsewhere, then move it into Documents. That makes it an atomic action. Or write it as a special filename (".partial", ".download", etc) and rename it at the end (again, an atomic action that will fire a second kqueue event).

Community
  • 1
  • 1
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • I like the `.partial` concept--although if you drop files in via iTunes, it won't obey this rule. My workaround was to set a timer after each file write, then assume the file is completely written after 1s or so. – nielsbot Mar 13 '13 at 20:16
  • @nielsbot - Is 1 second or so enough? Is the event not triggered when the filename is added, and depending on the filesize, the write operation is dynamic and the timing cannot be known. – some_id Mar 13 '13 at 21:02
  • you will receive a kqueue notification each time a _new chunk_ is written to the file--reset your timer to 1s after _each chunk_. The 1s timer isn't for the entire file write. If no chunks are written for a duration of 1s, assume the file is completely written. Not ideal, I know, but I could not think of a better way. – nielsbot Mar 13 '13 at 21:30
  • If you have control of the file format, you could also give your file a header that specifies the total file length--then after each chunk written you could determine if the file is complete... – nielsbot Mar 13 '13 at 21:32
  • Thanks. Who owns the timer and where is it started, reset if the callback is the same? – some_id Mar 13 '13 at 21:35
  • I posted in the form of an answer... HTH – nielsbot Mar 13 '13 at 21:38