diff --git a/camino/Camino.xcode/project.pbxproj b/camino/Camino.xcode/project.pbxproj index 5533283c8c1..6e7d2997e56 100644 --- a/camino/Camino.xcode/project.pbxproj +++ b/camino/Camino.xcode/project.pbxproj @@ -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, diff --git a/camino/src/download/FileChangeWatcher.h b/camino/src/download/FileChangeWatcher.h new file mode 100644 index 00000000000..4b23c60eb0b --- /dev/null +++ b/camino/src/download/FileChangeWatcher.h @@ -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 +* +* 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 + +// +// 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)aWatchedFileDelegate; + +// +// Remove an object implementing the |WatchedFileDelegate| from the watch queue. +// +-(void)removeWatchedFileDelegate:(id)aWatchedFileDelegate; + +@end diff --git a/camino/src/download/FileChangeWatcher.m b/camino/src/download/FileChangeWatcher.m new file mode 100644 index 00000000000..3ad54ca0c74 --- /dev/null +++ b/camino/src/download/FileChangeWatcher.m @@ -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 +* +* 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 +#include +#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)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)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)event.udata fileDidChange]; + } + NS_HANDLER + NSLog(@"Error in watcherThread: %@", localException); + NS_ENDHANDLER + + [pool release]; + } +} + +@end diff --git a/camino/src/download/ProgressDlgController.h b/camino/src/download/ProgressDlgController.h index d8a57b5a2e0..d6b12783148 100644 --- a/camino/src/download/ProgressDlgController.h +++ b/camino/src/download/ProgressDlgController.h @@ -23,6 +23,7 @@ * Simon Fraser * Calum Robinson * Josh Aas + * Nick Kreeger * * 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 { - 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 )progressDisplay; +-(void)didStartDownload:(ProgressViewController*)progressDisplay; -(void)didEndDownload:(id )progressDisplay withSuccess:(BOOL)completedOK statusCode:(nsresult)status; -(void)removeDownload:(id )progressDisplay suppressRedraw:(BOOL)suppressRedraw; -(NSApplicationTerminateReply)allowTerminate; @@ -112,4 +116,7 @@ -(void)saveProgressViewControllers; -(void)loadProgressViewControllers; +-(void)addFileDelegateToWatchList:(id)aWatchedFileDelegate; +-(void)removeFileDelegateFromWatchList:(id)aWatchedFileDelegate; + @end diff --git a/camino/src/download/ProgressDlgController.mm b/camino/src/download/ProgressDlgController.mm index e0140cd8a03..8f229235690 100644 --- a/camino/src/download/ProgressDlgController.mm +++ b/camino/src/download/ProgressDlgController.mm @@ -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 )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 )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)aWatchedFileDelegate +{ + [mFileChangeWatcher addWatchedFileDelegate:aWatchedFileDelegate]; +} + +-(void)removeFileDelegateFromWatchList:(id)aWatchedFileDelegate +{ + [mFileChangeWatcher removeWatchedFileDelegate:aWatchedFileDelegate]; +} + // Remove the successful downloads from the downloads list -(void)removeSuccessfulDownloads { @@ -1201,8 +1216,7 @@ static id gSharedProgressController = nil; */ -(id )createProgressDisplay { - ProgressViewController *newController = [[[ProgressViewController alloc] init] autorelease]; - [newController setProgressWindowController:self]; + ProgressViewController* newController = [[[ProgressViewController alloc] initWithWindowController:self] autorelease]; [mProgressViewControllers addObject:newController]; return newController; diff --git a/camino/src/download/ProgressViewController.h b/camino/src/download/ProgressViewController.h index bf7a0399dca..15fe8209867 100644 --- a/camino/src/download/ProgressViewController.h +++ b/camino/src/download/ProgressViewController.h @@ -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 +@interface ProgressViewController : NSObject { 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 diff --git a/camino/src/download/ProgressViewController.mm b/camino/src/download/ProgressViewController.mm index f2890e10e14..128f2d82d29 100644 --- a/camino/src/download/ProgressViewController.mm +++ b/camino/src/download/ProgressViewController.mm @@ -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 diff --git a/camino/src/embedding/CHDownloadProgressDisplay.h b/camino/src/embedding/CHDownloadProgressDisplay.h index 4b68fa6298f..bed062a3bf1 100644 --- a/camino/src/embedding/CHDownloadProgressDisplay.h +++ b/camino/src/embedding/CHDownloadProgressDisplay.h @@ -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.