From 8635a448bf27eca18ea6808df7a1627eae52a28d Mon Sep 17 00:00:00 2001 From: "stuart.morgan%alumni.case.edu" Date: Wed, 19 Sep 2007 16:55:07 +0000 Subject: [PATCH] Camino only - Bug 393562: kqueue download folders, not individual downloads. r=kreeger sr=pink git-svn-id: svn://10.0.0.236/trunk@236307 18797224-902f-48f8-a5cc-f745e15eee43 --- .../camino/src/download/FileChangeWatcher.h | 13 +-- .../camino/src/download/FileChangeWatcher.m | 109 ++++++++++++------ .../src/download/ProgressDlgController.h | 4 +- .../src/download/ProgressDlgController.mm | 13 ++- .../src/download/ProgressViewController.h | 4 +- .../src/download/ProgressViewController.mm | 29 +++-- 6 files changed, 116 insertions(+), 56 deletions(-) diff --git a/mozilla/camino/src/download/FileChangeWatcher.h b/mozilla/camino/src/download/FileChangeWatcher.h index 4b23c60eb0b..4eb4131b671 100644 --- a/mozilla/camino/src/download/FileChangeWatcher.h +++ b/mozilla/camino/src/download/FileChangeWatcher.h @@ -48,14 +48,14 @@ // This method will need to return the full path of the file/folder // that is being watched. // --(const char*)representedFilePath; +-(NSString*)representedFilePath; // -// This method gets called when the watcher recieves news that the -// watched path has changed. +// This method gets called when the watcher recieves news that a file has +// been removed at the watched path. // Note: This method will be called on a background thread. // --(void)fileDidChange; +-(void)fileRemoved; @end @@ -66,9 +66,8 @@ // @interface FileChangeWatcher : NSObject { -@private - NSMutableArray* mWatchedFileDelegates; // strong ref - NSMutableArray* mWatchedFileDescriptors; // strong ref + @private + NSMutableDictionary* mWatchedDirectories; // strong ref int mQueueFileDesc; BOOL mShouldRunThread; diff --git a/mozilla/camino/src/download/FileChangeWatcher.m b/mozilla/camino/src/download/FileChangeWatcher.m index 215d6d66b9d..c6b01fecfad 100644 --- a/mozilla/camino/src/download/FileChangeWatcher.m +++ b/mozilla/camino/src/download/FileChangeWatcher.m @@ -42,13 +42,20 @@ #import "unistd.h" #import "fcntl.h" -const int kSecondPollingInterval = 60; +static const int kSecondPollingInterval = 60; + +static const unsigned int kMaxWatchedDirectories = 25; + +// Watched directory dictionary keys +static NSString* const kDelegatesKey = @"delegates"; +static NSString* const kFileDescriptorKey = @"fdes"; @interface FileChangeWatcher(Private) -(void)startPolling; -(void)stopPolling; -(void)pollWatchedDirectories; +-(void)directoryChanged:(NSString*)directory; @end @@ -57,8 +64,7 @@ const int kSecondPollingInterval = 60; -(id)init { if ((self = [super init])) { - mWatchedFileDelegates = [[NSMutableArray alloc] init]; - mWatchedFileDescriptors = [[NSMutableArray alloc] init]; + mWatchedDirectories = [[NSMutableDictionary alloc] init]; mQueueFileDesc = kqueue(); } @@ -68,45 +74,66 @@ const int kSecondPollingInterval = 60; -(void)dealloc { close(mQueueFileDesc); - [mWatchedFileDelegates release]; - [mWatchedFileDescriptors release]; + [mWatchedDirectories release]; [super dealloc]; } -(void)addWatchedFileDelegate:(id)aWatchedFileDelegate -{ - if ([mWatchedFileDelegates containsObject:aWatchedFileDelegate]) - return; - - int fileDesc = open([aWatchedFileDelegate representedFilePath], O_EVTONLY, 0); - if (fileDesc >= 0) { - struct timespec nullts = { 0, 0 }; - struct kevent ev; - u_int fflags = NOTE_RENAME | NOTE_WRITE | NOTE_DELETE; - - EV_SET(&ev, fileDesc, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, fflags, 0, (void*)aWatchedFileDelegate); - - [mWatchedFileDelegates addObject:aWatchedFileDelegate]; - [mWatchedFileDescriptors addObject:[NSNumber numberWithInt:fileDesc]]; - kevent(mQueueFileDesc, &ev, 1, NULL, 0, &nullts); - if (!mShouldRunThread && [mWatchedFileDescriptors count] > 0) - [self startPolling]; +{ + NSString* parentDirectory = + [[aWatchedFileDelegate representedFilePath] stringByDeletingLastPathComponent]; + NSMutableDictionary* directoryInfo = [mWatchedDirectories objectForKey:parentDirectory]; + if (directoryInfo) { + NSMutableArray* directoryDelegates = [directoryInfo objectForKey:kDelegatesKey]; + if (![directoryDelegates containsObject:aWatchedFileDelegate]) + [directoryDelegates addObject:aWatchedFileDelegate]; + } + else { + // We cap the number of kqueues so we don't end up sucking down all the + // available file descriptors. + if ([mWatchedDirectories count] >= kMaxWatchedDirectories) + return; + + int fileDesc = open([parentDirectory fileSystemRepresentation], O_EVTONLY, 0); + if (fileDesc >= 0) { + struct timespec nullts = { 0, 0 }; + struct kevent ev; + u_int fflags = NOTE_RENAME | NOTE_WRITE | NOTE_DELETE; + + EV_SET(&ev, fileDesc, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, fflags, + 0, (void*)parentDirectory); + + kevent(mQueueFileDesc, &ev, 1, NULL, 0, &nullts); + if (!mShouldRunThread) + [self startPolling]; + + directoryInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + [NSMutableArray arrayWithObject:aWatchedFileDelegate], kDelegatesKey, + [NSNumber numberWithInt:fileDesc], kFileDescriptorKey, + nil]; + [mWatchedDirectories setObject:directoryInfo forKey:parentDirectory]; + } } } -(void)removeWatchedFileDelegate:(id)aWatchedFileDelegate { - int index = [mWatchedFileDelegates indexOfObject:aWatchedFileDelegate]; - if (index == NSNotFound) + NSString* parentDirectory = + [[aWatchedFileDelegate representedFilePath] stringByDeletingLastPathComponent]; + NSMutableDictionary* directoryInfo = [mWatchedDirectories objectForKey:parentDirectory]; + NSMutableArray* directoryDelegates = [directoryInfo objectForKey:kDelegatesKey]; + if (![directoryDelegates containsObject:aWatchedFileDelegate]) return; - - int fileDesc = [[mWatchedFileDescriptors objectAtIndex:index] intValue]; - [mWatchedFileDelegates removeObjectAtIndex:index]; - [mWatchedFileDescriptors removeObjectAtIndex:index]; - close(fileDesc); - - if (mShouldRunThread && [mWatchedFileDelegates count] == 0) - [self stopPolling]; + + int fileDesc = [[directoryInfo objectForKey:kFileDescriptorKey] intValue]; + [directoryDelegates removeObject:aWatchedFileDelegate]; + + if ([directoryDelegates count] == 0) { + close(fileDesc); + [mWatchedDirectories removeObjectForKey:parentDirectory]; + if (mShouldRunThread && [mWatchedDirectories count] == 0) + [self stopPolling]; + } } -(void)startPolling @@ -149,7 +176,7 @@ const int kSecondPollingInterval = 60; struct kevent event; int n = kevent(mQueueFileDesc, NULL, 0, &event, 1, &timeInterval); if (n > 0 && event.filter == EVFILT_VNODE && event.fflags) { - [(id)event.udata fileDidChange]; + [self directoryChanged:(NSString*)event.udata]; } } @catch (id exception) { @@ -160,4 +187,20 @@ const int kSecondPollingInterval = 60; } } +-(void)directoryChanged:(NSString*)directory +{ + NSSet* existingFiles = + [NSSet setWithArray:[[NSFileManager defaultManager] directoryContentsAtPath:directory]]; + + NSMutableDictionary* directoryInfo = [mWatchedDirectories objectForKey:directory]; + NSMutableArray* directoryDelegates = [directoryInfo objectForKey:kDelegatesKey]; + NSEnumerator* delegateEnumerator = [directoryDelegates objectEnumerator]; + id fileDelegate; + while ((fileDelegate = [delegateEnumerator nextObject])) { + NSString* filename = [[fileDelegate representedFilePath] lastPathComponent]; + if (![existingFiles member:filename]) + [fileDelegate fileRemoved]; + } +} + @end diff --git a/mozilla/camino/src/download/ProgressDlgController.h b/mozilla/camino/src/download/ProgressDlgController.h index 437a1ad5023..ce8841fcd29 100644 --- a/mozilla/camino/src/download/ProgressDlgController.h +++ b/mozilla/camino/src/download/ProgressDlgController.h @@ -110,8 +110,8 @@ -(int)numDownloadsInProgress; -(void)clearAllDownloads; -(void)didStartDownload:(ProgressViewController*)progressDisplay; --(void)didEndDownload:(id )progressDisplay withSuccess:(BOOL)completedOK statusCode:(nsresult)status; --(void)removeDownload:(id )progressDisplay suppressRedraw:(BOOL)suppressRedraw; +-(void)didEndDownload:(ProgressViewController*)progressDisplay withSuccess:(BOOL)completedOK statusCode:(nsresult)status; +-(void)removeDownload:(ProgressViewController*)progressDisplay suppressRedraw:(BOOL)suppressRedraw; -(NSApplicationTerminateReply)allowTerminate; -(void)applicationWillTerminate; -(void)saveProgressViewControllers; diff --git a/mozilla/camino/src/download/ProgressDlgController.mm b/mozilla/camino/src/download/ProgressDlgController.mm index 3db51b724e2..d48592cf3ae 100644 --- a/mozilla/camino/src/download/ProgressDlgController.mm +++ b/mozilla/camino/src/download/ProgressDlgController.mm @@ -240,8 +240,13 @@ static id gSharedProgressController = nil; // now remove stuff, don't need to check if active, the toolbar/menu validates for (unsigned int i = 0; i < selectedCount; i++) { - [[selected objectAtIndex:i] deleteFile:sender]; - [self removeDownload:[selected objectAtIndex:i] suppressRedraw:YES]; + ProgressViewController* progressController = [selected objectAtIndex:i]; + // if the file was moved without the controller noticing, the move to trash + // will fail, but cause it to notice that the file is missing. Leave it in + // the list (now showing missing) so the user doesn't think it was + // successfully trashed. + if ([progressController deleteFile]) + [self removeDownload:progressController suppressRedraw:YES]; } [self rebuildViews]; @@ -558,7 +563,7 @@ static id gSharedProgressController = nil; [self scrollIntoView:progressDisplay]; } --(void)didEndDownload:(id )progressDisplay withSuccess:(BOOL)completedOK statusCode:(nsresult)status +-(void)didEndDownload:(ProgressViewController*)progressDisplay withSuccess:(BOOL)completedOK statusCode:(nsresult)status { [self rebuildViews]; // to swap in the completed view [[[self window] toolbar] validateVisibleItems]; // force update which doesn't always happen @@ -649,7 +654,7 @@ static id gSharedProgressController = nil; { } --(void)removeDownload:(id )progressDisplay suppressRedraw:(BOOL)suppressRedraw +-(void)removeDownload:(ProgressViewController*)progressDisplay suppressRedraw:(BOOL)suppressRedraw { [progressDisplay displayWillBeRemoved]; // This is sometimes called by code that thinks it can continue diff --git a/mozilla/camino/src/download/ProgressViewController.h b/mozilla/camino/src/download/ProgressViewController.h index 15fe8209867..d26ad739f95 100644 --- a/mozilla/camino/src/download/ProgressViewController.h +++ b/mozilla/camino/src/download/ProgressViewController.h @@ -115,10 +115,11 @@ const int kRemoveUponSuccessfulDownloadPrefValue = 2; -(IBAction)remove:(id)sender; -(IBAction)reveal:(id)sender; -(IBAction)open:(id)sender; --(IBAction)deleteFile:(id)sender; -(IBAction)pause:(id)sender; -(IBAction)resume:(id)sender; +-(BOOL)deleteFile; + -(BOOL)isActive; -(BOOL)isCanceled; -(BOOL)isSelected; @@ -127,7 +128,6 @@ const int kRemoveUponSuccessfulDownloadPrefValue = 2; -(BOOL)hasSucceeded; --(void)checkFileExists; -(void)setSelected:(BOOL)inSelected; -(NSDictionary*)downloadInfoDictionary; diff --git a/mozilla/camino/src/download/ProgressViewController.mm b/mozilla/camino/src/download/ProgressViewController.mm index 2055e305cb5..7dd583020f9 100644 --- a/mozilla/camino/src/download/ProgressViewController.mm +++ b/mozilla/camino/src/download/ProgressViewController.mm @@ -64,6 +64,7 @@ enum { -(void)viewDidLoad; -(void)setupFileSystemNotification; -(void)unsubscribeFileSystemNotification; +-(void)checkFileExists; -(void)refreshDownloadInfo; -(void)launchFileIfAppropriate; -(void)setProgressViewFromDictionary:(NSDictionary*)aDict; @@ -293,15 +294,21 @@ enum { -(IBAction)reveal:(id)sender { - if (![[NSWorkspace sharedWorkspace] selectFile:mDestPath - inFileViewerRootedAtPath:[mDestPath stringByDeletingLastPathComponent]]) { + [self checkFileExists]; + if (!mFileExists || + ![[NSWorkspace sharedWorkspace] selectFile:mDestPath + inFileViewerRootedAtPath:[mDestPath stringByDeletingLastPathComponent]]) + { NSBeep(); } } -(IBAction)open:(id)sender { - if (![[NSWorkspace sharedWorkspace] openFile:mDestPath]) { + [self checkFileExists]; + if (!mFileExists || + ![[NSWorkspace sharedWorkspace] openFile:mDestPath]) + { NSBeep(); } return; @@ -339,9 +346,14 @@ enum { [self refreshDownloadInfo]; } -// Delete the file we downloaded and remove this item from the list --(IBAction)deleteFile:(id)sender +// Move the downloaded file to the trash. +-(BOOL)deleteFile { + [self checkFileExists]; + if (!mFileExists) { + NSBeep(); + return NO; + } NSString* fileName = [mDestPath lastPathComponent]; NSString* path = [mDestPath stringByDeletingLastPathComponent]; @@ -350,6 +362,7 @@ enum { destination:@"" files:[NSArray arrayWithObject:fileName] tag:nil]; + return YES; } -(void)downloadDidEnd @@ -590,12 +603,12 @@ enum { return dict; } --(const char*)representedFilePath +-(NSString*)representedFilePath { - return [mDestPath fileSystemRepresentation]; + return mDestPath; } --(void)fileDidChange +-(void)fileRemoved { // This method gets called on a background thread, so switch the |checkFileExists| call to the main thread. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];