Fixing bug 318001 - Fatal hang on downloads to the desktop with Quicksilver running, implement kqueue notifications. r=smorgan, sr=mento. Camino head only.

This commit is contained in:
nick.kreeger%park.edu 2007-02-17 02:41:38 +00:00
Родитель 206d4adf61
Коммит 567abc1c9e
8 изменённых файлов: 392 добавлений и 56 удалений

Просмотреть файл

@ -4122,6 +4122,7 @@
DE74F74E0AB25EBC00FD1D5B,
DEE9EBA50AF5C379002BC511,
DEB968190B0D8E0B0023F8B1,
DEE34A550B84F5C600BCD687,
);
isa = PBXHeadersBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
@ -5253,6 +5254,7 @@
DE74F7550AB25EDA00FD1D5B,
DEE9EBA80AF5C390002BC511,
DEB968160B0D8DF70023F8B1,
DEE34A580B84F5E100BCD687,
);
isa = PBXSourcesBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
@ -8013,6 +8015,7 @@
DE74F74F0AB25EBC00FD1D5B,
DEE9EBA40AF5C379002BC511,
DEB968180B0D8E0B0023F8B1,
DEE34A540B84F5C600BCD687,
);
isa = PBXHeadersBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
@ -9145,6 +9148,7 @@
DE74F7560AB25EDA00FD1D5B,
DEE9EBA70AF5C390002BC511,
DEB968150B0D8DF70023F8B1,
DEE34A570B84F5E100BCD687,
);
isa = PBXSourcesBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
@ -14191,6 +14195,50 @@
settings = {
};
};
DEE34A530B84F5C600BCD687 = {
fileEncoding = 30;
isa = PBXFileReference;
lastKnownFileType = sourcecode.c.h;
name = FileChangeWatcher.h;
path = src/download/FileChangeWatcher.h;
refType = 2;
sourceTree = SOURCE_ROOT;
};
DEE34A540B84F5C600BCD687 = {
fileRef = DEE34A530B84F5C600BCD687;
isa = PBXBuildFile;
settings = {
};
};
DEE34A550B84F5C600BCD687 = {
fileRef = DEE34A530B84F5C600BCD687;
isa = PBXBuildFile;
settings = {
};
};
DEE34A560B84F5E100BCD687 = {
fileEncoding = 30;
isa = PBXFileReference;
lastKnownFileType = sourcecode.c.objc;
name = FileChangeWatcher.m;
path = src/download/FileChangeWatcher.m;
refType = 2;
sourceTree = SOURCE_ROOT;
};
DEE34A570B84F5E100BCD687 = {
fileRef = DEE34A560B84F5E100BCD687;
isa = PBXBuildFile;
settings = {
COMPILER_FLAGS = "-fobjc-exceptions";
};
};
DEE34A580B84F5E100BCD687 = {
fileRef = DEE34A560B84F5E100BCD687;
isa = PBXBuildFile;
settings = {
COMPILER_FLAGS = "-fobjc-exceptions";
};
};
DEE9EBA30AF5C379002BC511 = {
fileEncoding = 30;
isa = PBXFileReference;
@ -14975,6 +15023,7 @@
F517395B020CE3740189DA0C,
3FC0FB7F05A0D2DC002F47DE,
3FC0FB8005A0D2DC002F47DE,
DEE34A560B84F5E100BCD687,
);
isa = PBXGroup;
name = Camino;
@ -15340,6 +15389,7 @@
F566BD1202EFA9AD01A967F3,
F59E9F3D0237E28401A967DF,
DE74F74D0AB25EBC00FD1D5B,
DEE34A530B84F5C600BCD687,
F5137A1102676B9101026D05,
F528E218020FD8400168DE43,
F5607CB5023944AD01A967DF,

Просмотреть файл

@ -0,0 +1,90 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Nick Kreeger
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Nick Kreeger <nick.kreeger@park.edu>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#import <AppKit/AppKit.h>
//
// Any object that needs a file to be polled can implement this protocol and
// register itself with the watch queue.
//
@protocol WatchedFileDelegate
//
// This method will need to return the full path of the file/folder
// that is being watched.
//
-(const char*)representedFilePath;
//
// This method gets called when the watcher recieves news that the
// watched path has changed.
// Note: This method will be called on a background thread.
//
-(void)fileDidChange;
@end
//
// This class provides a file polling service to any object
// that implements the |WatchedFileDelegate| protocol.
//
@interface FileChangeWatcher : NSObject
{
@private
NSMutableArray* mWatchedFileDelegates; // strong ref
NSMutableArray* mWatchedFileDescriptors; // strong ref
int mQueueFileDesc;
BOOL mShouldRunThread;
BOOL mThreadIsRunning;
}
//
// Add an object implementing the |WatchedFileDelegate| to the
// watch queue.
// Note: An object will only be added once to the queue.
//
-(void)addWatchedFileDelegate:(id<WatchedFileDelegate>)aWatchedFileDelegate;
//
// Remove an object implementing the |WatchedFileDelegate| from the watch queue.
//
-(void)removeWatchedFileDelegate:(id<WatchedFileDelegate>)aWatchedFileDelegate;
@end

Просмотреть файл

@ -0,0 +1,162 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Nick Kreeger
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Nick Kreeger <nick.kreeger@park.edu>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#import "FileChangeWatcher.h"
#include <sys/types.h>
#include <sys/event.h>
#import "unistd.h"
#import "fcntl.h"
const int kSecondPollingInterval = 60;
@interface FileChangeWatcher(Private)
-(void)startPolling;
-(void)stopPolling;
-(void)pollWatchedDirectories;
@end
@implementation FileChangeWatcher
-(id)init
{
if ((self = [super init])) {
mWatchedFileDelegates = [[NSMutableArray alloc] init];
mWatchedFileDescriptors = [[NSMutableArray alloc] init];
mQueueFileDesc = kqueue();
}
return self;
}
-(void)dealloc
{
close(mQueueFileDesc);
[mWatchedFileDelegates release];
[mWatchedFileDescriptors release];
[super dealloc];
}
-(void)addWatchedFileDelegate:(id<WatchedFileDelegate>)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];
}
}
-(void)removeWatchedFileDelegate:(id<WatchedFileDelegate>)aWatchedFileDelegate
{
int index = [mWatchedFileDelegates indexOfObject:aWatchedFileDelegate];
if (index == NSNotFound)
return;
int fileDesc = [[mWatchedFileDescriptors objectAtIndex:index] intValue];
[mWatchedFileDelegates removeObjectAtIndex:index];
[mWatchedFileDescriptors removeObjectAtIndex:index];
close(fileDesc);
if (mShouldRunThread && [mWatchedFileDelegates count] == 0)
[self stopPolling];
}
-(void)startPolling
{
@synchronized(self) {
mShouldRunThread = YES;
if (!mThreadIsRunning) {
mThreadIsRunning = YES;
[NSThread detachNewThreadSelector:@selector(pollWatchedDirectories)
toTarget:self
withObject:nil];
}
}
}
-(void)stopPolling
{
@synchronized(self) {
mShouldRunThread = NO;
}
}
//
// Portions of this method were originally written by M. Uli Kusterer.
//
-(void)pollWatchedDirectories
{
const struct timespec timeInterval = { kSecondPollingInterval, 0 };
while (1) {
@synchronized(self) {
if (!mShouldRunThread) {
mThreadIsRunning = NO;
break;
}
}
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NS_DURING
struct kevent event;
int n = kevent(mQueueFileDesc, NULL, 0, &event, 1, &timeInterval);
if (n > 0 && event.filter == EVFILT_VNODE && event.fflags) {
[(id<WatchedFileDelegate>)event.udata fileDidChange];
}
NS_HANDLER
NSLog(@"Error in watcherThread: %@", localException);
NS_ENDHANDLER
[pool release];
}
}
@end

Просмотреть файл

@ -23,6 +23,7 @@
* Simon Fraser <sfraser@netscape.com>
* Calum Robinson <calumr@mac.com>
* Josh Aas <josh@mozilla.com>
* Nick Kreeger <nick.kreeger@park.edu>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -68,6 +69,7 @@
#import "CHDownloadProgressDisplay.h"
#import "ProgressViewController.h"
#import "FileChangeWatcher.h"
//
// interface ProgressDlgController
@ -79,16 +81,18 @@
@interface ProgressDlgController : NSWindowController<CHDownloadDisplayFactory, CHStackViewDataSource>
{
IBOutlet CHStackView *mStackView;
IBOutlet NSScrollView *mScrollView;
IBOutlet CHStackView* mStackView;
IBOutlet NSScrollView* mScrollView;
NSSize mDefaultWindowSize;
NSTimer *mDownloadTimer; // used for updating the status, STRONG ref
NSMutableArray *mProgressViewControllers; // the downloads we manage, STRONG ref
NSTimer* mDownloadTimer; // used for updating the status, STRONG ref
NSMutableArray* mProgressViewControllers; // the downloads we manage, STRONG ref
int mNumActiveDownloads;
int mSelectionPivotIndex;
BOOL mShouldCloseWindow; // true when a download completes when termination modal sheet is up
BOOL mAwaitingTermination; // true when we are waiting for users termination modal sheet
FileChangeWatcher* mFileChangeWatcher; // strong ref.
}
+(ProgressDlgController *)sharedDownloadController; // creates if necessary
@ -104,7 +108,7 @@
-(int)numDownloadsInProgress;
-(void)clearAllDownloads;
-(void)didStartDownload:(id <CHDownloadProgressDisplay>)progressDisplay;
-(void)didStartDownload:(ProgressViewController*)progressDisplay;
-(void)didEndDownload:(id <CHDownloadProgressDisplay>)progressDisplay withSuccess:(BOOL)completedOK statusCode:(nsresult)status;
-(void)removeDownload:(id <CHDownloadProgressDisplay>)progressDisplay suppressRedraw:(BOOL)suppressRedraw;
-(NSApplicationTerminateReply)allowTerminate;
@ -112,4 +116,7 @@
-(void)saveProgressViewControllers;
-(void)loadProgressViewControllers;
-(void)addFileDelegateToWatchList:(id<WatchedFileDelegate>)aWatchedFileDelegate;
-(void)removeFileDelegateFromWatchList:(id<WatchedFileDelegate>)aWatchedFileDelegate;
@end

Просмотреть файл

@ -142,6 +142,8 @@ static id gSharedProgressController = nil;
[toolbar setAutosavesConfiguration:YES];
[[self window] setToolbar:toolbar];
mFileChangeWatcher = [[FileChangeWatcher alloc] init];
// load the saved instances to mProgressViewControllers array
[self loadProgressViewControllers];
}
@ -153,6 +155,7 @@ static id gSharedProgressController = nil;
gSharedProgressController = nil;
}
[mProgressViewControllers release];
[mFileChangeWatcher release];
[self killDownloadTimer];
[super dealloc];
}
@ -526,7 +529,7 @@ static id gSharedProgressController = nil;
}
}
-(void)didStartDownload:(id <CHDownloadProgressDisplay>)progressDisplay
-(void)didStartDownload:(ProgressViewController*)progressDisplay
{
if (![[self window] isVisible]) {
[self showWindow:nil]; // make sure the window is visible
@ -645,6 +648,7 @@ static id gSharedProgressController = nil;
-(void)removeDownload:(id <CHDownloadProgressDisplay>)progressDisplay suppressRedraw:(BOOL)suppressRedraw
{
[progressDisplay displayWillBeRemoved];
[mProgressViewControllers removeObject:progressDisplay];
if ([mProgressViewControllers count] == 0) {
@ -808,7 +812,8 @@ static id gSharedProgressController = nil;
NSDictionary* downloadsDictionary;
while((downloadsDictionary = [downloadsEnum nextObject]))
{
ProgressViewController* curController = [[ProgressViewController alloc] initWithDictionary:downloadsDictionary];
ProgressViewController* curController = [[ProgressViewController alloc] initWithDictionary:downloadsDictionary
andWindowController:self];
[mProgressViewControllers addObject:curController];
[curController release];
}
@ -817,6 +822,16 @@ static id gSharedProgressController = nil;
}
}
-(void)addFileDelegateToWatchList:(id<WatchedFileDelegate>)aWatchedFileDelegate
{
[mFileChangeWatcher addWatchedFileDelegate:aWatchedFileDelegate];
}
-(void)removeFileDelegateFromWatchList:(id<WatchedFileDelegate>)aWatchedFileDelegate
{
[mFileChangeWatcher removeWatchedFileDelegate:aWatchedFileDelegate];
}
// Remove the successful downloads from the downloads list
-(void)removeSuccessfulDownloads
{
@ -1201,8 +1216,7 @@ static id gSharedProgressController = nil;
*/
-(id <CHDownloadProgressDisplay>)createProgressDisplay
{
ProgressViewController *newController = [[[ProgressViewController alloc] init] autorelease];
[newController setProgressWindowController:self];
ProgressViewController* newController = [[[ProgressViewController alloc] initWithWindowController:self] autorelease];
[mProgressViewControllers addObject:newController];
return newController;

Просмотреть файл

@ -42,6 +42,7 @@
#import "CHDownloadProgressDisplay.h"
#import "FileChangeWatcher.h"
class CHDownloader;
@class ProgressDlgController;
@ -70,7 +71,7 @@ const int kRemoveUponSuccessfulDownloadPrefValue = 2;
// well as managing the download. It holds onto two views, one for while
// the item is downloading, the other for after it has completed.
//
@interface ProgressViewController : NSObject<CHDownloadProgressDisplay>
@interface ProgressViewController : NSObject<CHDownloadProgressDisplay, WatchedFileDelegate>
{
IBOutlet NSProgressIndicator *mProgressBar;
@ -83,10 +84,8 @@ const int kRemoveUponSuccessfulDownloadPrefValue = 2;
BOOL mDownloadDone;
BOOL mRefreshIcon;
BOOL mFileExists;
BOOL mFileIsWatched;
BOOL mIsSelected;
FNSubscriptionRef mSubRef;
FNSubscriptionUPP mSubUPP;
NSTimeInterval mDownloadTime; // only set when done
@ -106,7 +105,10 @@ const int kRemoveUponSuccessfulDownloadPrefValue = 2;
+(NSString *)formatFuzzyTime:(int)aSeconds;
+(NSString *)formatBytes:(float)aBytes;
-(id)initWithDictionary:(NSDictionary*)aDict;
-(id)initWithWindowController:(ProgressDlgController*)aWindowController;
-(id)initWithDictionary:(NSDictionary*)aDict
andWindowController:(ProgressDlgController*)aWindowController;
-(ProgressView *)view;
-(IBAction)cancel:(id)sender;
@ -132,6 +134,4 @@ const int kRemoveUponSuccessfulDownloadPrefValue = 2;
-(NSMenu*)contextualMenu;
-(void)setProgressWindowController:(ProgressDlgController*)progressWindowController;
@end

Просмотреть файл

@ -59,13 +59,6 @@ enum {
kLabelTagIcon
};
// Method helps set up a subscription to the download dir to listen for changes
static void FileSystemNotificationProc(FNMessage message, OptionBits flags, void* refcon, FNSubscriptionRef subscription)
{
[(ProgressViewController*)refcon checkFileExists];
}
@interface ProgressViewController(ProgressViewControllerPrivate)
-(void)viewDidLoad;
@ -166,10 +159,20 @@ static void FileSystemNotificationProc(FNMessage message, OptionBits flags, void
return self;
}
-(id)initWithDictionary:(NSDictionary*)aDict
-(id)initWithWindowController:(ProgressDlgController*)aWindowController
{
if ((self = [self init]))
mProgressWindowController = aWindowController;
return self;
}
-(id)initWithDictionary:(NSDictionary*)aDict
andWindowController:(ProgressDlgController*)aWindowController
{
if ((self = [self init]))
{
mProgressWindowController = aWindowController;
[self setProgressViewFromDictionary:aDict];
}
@ -197,8 +200,6 @@ static void FileSystemNotificationProc(FNMessage message, OptionBits flags, void
[mCompletedView setController:nil];
[mProgressView setController:nil];
[self unsubscribeFileSystemNotification];
[mStartTime release];
[mSourceURL release];
[mDestPath release];
@ -250,35 +251,20 @@ static void FileSystemNotificationProc(FNMessage message, OptionBits flags, void
-(void)setupFileSystemNotification
{
// there is some update, that is why we are being called again
// unsubscribe the old stuff and create a fresh subscription
[self unsubscribeFileSystemNotification];
[self checkFileExists];
NSString *dir = [mDestPath stringByDeletingLastPathComponent];
if (dir)
{
mSubUPP = NewFNSubscriptionUPP(FileSystemNotificationProc);
OSStatus err = FNSubscribeByPath(((const UInt8*)[dir fileSystemRepresentation]), mSubUPP, (void*)self, nil, &mSubRef);
if (err != noErr)
NSLog(@"Failed to subscribe to file system notification for %@", dir);
if (mFileExists && !mFileIsWatched) {
// Adding ourselves to the watch kqueue creates an extra ref-count to our instance.
// We will remove ourselves from the watch kqueue on |displayWillBeRemoved|, which
// will remove the extra ref count from our instance.
[mProgressWindowController addFileDelegateToWatchList:self];
mFileIsWatched = YES;
}
}
-(void)unsubscribeFileSystemNotification
{
if (mSubRef)
{
FNUnsubscribe(mSubRef);
mSubRef = nil;
}
if (mSubUPP)
{
DisposeFNSubscriptionUPP(mSubUPP);
mSubUPP = nil;
}
[mProgressWindowController removeFileDelegateFromWatchList:self];
mFileIsWatched = NO;
}
-(IBAction)copySourceURL:(id)sender
@ -505,11 +491,6 @@ static void FileSystemNotificationProc(FNMessage message, OptionBits flags, void
}
}
-(void)setProgressWindowController:(ProgressDlgController*)progressWindowController
{
mProgressWindowController = progressWindowController;
}
-(BOOL)isActive
{
return !mDownloadDone;
@ -602,6 +583,21 @@ static void FileSystemNotificationProc(FNMessage message, OptionBits flags, void
return dict;
}
-(const char*)representedFilePath
{
return [mDestPath fileSystemRepresentation];
}
-(void)fileDidChange
{
// This method gets called on a background thread, so switch the |checkFileExists| call to the main thread.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self performSelectorOnMainThread:@selector(checkFileExists)
withObject:nil
waitUntilDone:NO];
[pool release];
}
-(NSMenu*)contextualMenu
{
NSMenu *menu = [[NSMenu alloc] init];
@ -691,6 +687,8 @@ static void FileSystemNotificationProc(FNMessage message, OptionBits flags, void
[self downloadDidEnd];
[mProgressWindowController didEndDownload:self withSuccess:completedOK statusCode:aStatus];
if (completedOK)
[self setupFileSystemNotification];
}
-(void)setProgressTo:(long long)aCurProgress ofMax:(long long)aMaxProgress
@ -775,13 +773,26 @@ static void FileSystemNotificationProc(FNMessage message, OptionBits flags, void
{
[mDestPath autorelease];
mDestPath = [aDestPath copy];
[self setupFileSystemNotification];
if (mDownloadDone)
[self setupFileSystemNotification];
//[self tryToSetFinderComments];
}
- (NSString*)destinationPath
-(NSString*)destinationPath
{
return mDestPath;
}
//
// This method exists because of an extra ref-count to ourselves in
// |FileChangeWatcher|. To remove this extra refcount before |dealloc|,
// this class gets notified when the view is about to be removed.
//
-(void)displayWillBeRemoved
{
// The file can only be watched if the download compeleted sucessfully
if (mFileIsWatched)
[self unsubscribeFileSystemNotification];
}
@end

Просмотреть файл

@ -121,6 +121,8 @@ class CHDownloader;
- (void)setDestinationPath:(NSString*)aDestPath;
- (NSString*)destinationPath;
- (void)displayWillBeRemoved;
@end
// A formal protocol which is implemented by a factory of progress UI.