diff options
Diffstat (limited to 'frontends/cocoa/DownloadWindowController.m')
-rw-r--r-- | frontends/cocoa/DownloadWindowController.m | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/frontends/cocoa/DownloadWindowController.m b/frontends/cocoa/DownloadWindowController.m new file mode 100644 index 000000000..0c9d869f8 --- /dev/null +++ b/frontends/cocoa/DownloadWindowController.m @@ -0,0 +1,415 @@ +/* + * Copyright 2011 Sven Weidauer <sven.weidauer@gmail.com> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "utils/log.h" +#import "utils/nsurl.h" +#import "desktop/download.h" +#import "desktop/gui_download.h" + +#import "cocoa/DownloadWindowController.h" +#import "cocoa/gui.h" + +@interface DownloadWindowController () + +@property (readwrite, retain, nonatomic) NSFileHandle *outputFile; +@property (readwrite, retain, nonatomic) NSMutableData *savedData; +@property (readwrite, copy, nonatomic) NSDate *startDate; + +- (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo; +- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo; +- (void)askCancelDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo; + +- (BOOL) receivedData: (NSData *)data; + +- (void) showError: (NSString *)error; +- (void) downloadDone; +- (void) removeIfPossible; + +@end + +static void cocoa_unregister_download( DownloadWindowController *download ); +static void cocoa_register_download( DownloadWindowController *download ); + + +@implementation DownloadWindowController + +- (id) initWithContext: (struct download_context *)ctx +{ + if ((self = [super initWithWindowNibName: @"DownloadWindow"]) == nil) { + return nil; + } + + context = ctx; + totalSize = download_context_get_total_length( context ); + [self setURL: [NSURL URLWithString: [NSString stringWithUTF8String: nsurl_access(download_context_get_url( context ))]]]; + [self setMIMEType: [NSString stringWithUTF8String: download_context_get_mime_type( context )]]; + [self setStartDate: [NSDate date]]; + + return self; +} + +- (void) dealloc +{ + download_context_destroy( context ); + + [self setURL: nil]; + [self setMIMEType: nil]; + [self setSaveURL: nil]; + [self setOutputFile: nil]; + [self setSavedData: nil]; + [self setStartDate: nil]; + + [super dealloc]; +} + +- (void) abort +{ + download_context_abort( context ); + [self removeIfPossible]; +} + +- (void) askForSave +{ + canClose = NO; + [[NSSavePanel savePanel] + beginSheetForDirectory: nil + file: [NSString stringWithUTF8String: download_context_get_filename( context )] + modalForWindow: [self window] + modalDelegate: self + didEndSelector: @selector(savePanelDidEnd:returnCode:contextInfo:) + contextInfo: NULL]; +} + +- (void) savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo +{ + canClose = YES; + + if (returnCode == NSCancelButton) { + [self abort]; + return; + } + + NSURL *targetURL = [sheet URL]; + NSString *path = [targetURL path]; + + [[NSFileManager defaultManager] createFileAtPath: path contents: nil attributes: nil]; + + FSRef ref; + if (CFURLGetFSRef( (CFURLRef)targetURL, &ref )) { + NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys: + url, (NSString *)kLSQuarantineDataURLKey, + (NSString *)kLSQuarantineTypeWebDownload, (NSString *)kLSQuarantineTypeKey, + nil]; + LSSetItemAttribute( &ref, kLSRolesAll, kLSItemQuarantineProperties, (CFDictionaryRef)attributes ); + LOG("Set quarantine attributes on file %s", [path UTF8String]); + } + + [self setOutputFile: [NSFileHandle fileHandleForWritingAtPath: path]]; + [self setSaveURL: targetURL]; + + NSWindow *win = [self window]; + [win setRepresentedURL: targetURL]; + [win setTitle: [self fileName]]; + + if (nil == outputFile) { + [self performSelector: @selector(showError:) withObject: @"Cannot create file" afterDelay: 0]; + return; + } + + if (nil != savedData) { + [outputFile writeData: savedData]; + [self setSavedData: nil]; + } + + [self removeIfPossible]; +} + +- (BOOL) receivedData: (NSData *)data +{ + if (outputFile) { + [outputFile writeData: data]; + } else { + if (nil == savedData) { + [self setSavedData: [NSMutableData data]]; + } + [savedData appendData: data]; + } + + [self setReceivedSize: receivedSize + [data length]]; + + return YES; +} + +- (void) showError: (NSString *)error +{ + canClose = NO; + NSAlert *alert = [NSAlert alertWithMessageText: NSLocalizedString( @"Error", @"show error" ) + defaultButton: NSLocalizedString( @"OK", @"'OK' button" ) + alternateButton: nil otherButton: nil + informativeTextWithFormat: @"%@", error]; + + [alert beginSheetModalForWindow: [self window] modalDelegate: self + didEndSelector: @selector(alertDidEnd:returnCode:contextInfo:) + contextInfo: NULL]; +} + +- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo +{ + [self abort]; +} + +- (void) removeIfPossible +{ + if (canClose && shouldClose) { + cocoa_unregister_download( self ); + } +} +- (void) downloadDone +{ + shouldClose = YES; + [self removeIfPossible]; +} + +- (BOOL) windowShouldClose: (id)sender +{ + if ([[NSUserDefaults standardUserDefaults] boolForKey: kAlwaysCancelDownload]) { + return YES; + } + + NSAlert *ask = [NSAlert alertWithMessageText: NSLocalizedString( @"Cancel download?", @"Download" ) + defaultButton: NSLocalizedString( @"Yes", @"" ) + alternateButton: NSLocalizedString( @"No", @"" ) + otherButton: nil + informativeTextWithFormat: NSLocalizedString( @"Should the download of '%@' really be cancelled?", @"Download" ), + [self fileName]]; + [ask setShowsSuppressionButton: YES]; + [ask beginSheetModalForWindow: [self window] modalDelegate: self + didEndSelector: @selector(askCancelDidEnd:returnCode:contextInfo:) contextInfo: NULL]; + return NO; +} + +- (void) windowWillClose: (NSNotification *)notification +{ + [self abort]; +} + +- (void) askCancelDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo +{ + if (returnCode == NSOKButton) { + [[NSUserDefaults standardUserDefaults] + setBool: [[alert suppressionButton] state] == NSOnState + forKey: kAlwaysCancelDownload]; + [self close]; + } +} + +#pragma mark - +#pragma mark Properties + +@synthesize URL = url; +@synthesize MIMEType = mimeType; +@synthesize totalSize; +@synthesize saveURL; +@synthesize outputFile; +@synthesize savedData; +@synthesize receivedSize; +@synthesize startDate; + ++ (NSSet *) keyPathsForValuesAffectingStatusText +{ + return [NSSet setWithObjects: @"totalSize", @"receivedSize", nil]; +} + +#ifndef NSAppKitVersionNumber10_5 +#define NSAppKitVersionNumber10_5 949 +#endif + +static NSString *cocoa_file_size_string( float size ) +{ + static unsigned factor = 0; + if (factor == 0) { + if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5) { + factor = 1000; + } else { + factor = 1024; + } + } + + if (size == 0) return @"nothing"; + if (size <= 1.0) return @"1 byte"; + + if (size < factor - 1) return [NSString stringWithFormat:@"%1.0f bytes",size]; + + size /= factor; + if (size < factor - 1) return [NSString stringWithFormat:@"%1.1f KB", size]; + + size /= factor; + if (size < factor - 1) return [NSString stringWithFormat:@"%1.1f MB", size]; + + size /= factor; + if (size < factor - 1) return [NSString stringWithFormat:@"%1.1f GB", size]; + + size /= factor; + return [NSString stringWithFormat:@"%1.1f TB", size]; +} + +static NSString *cocoa_time_string( unsigned seconds ) +{ + if (seconds <= 10) { + return NSLocalizedString(@"less than 10 seconds", + @"time remaining" ); + } + + if (seconds < 60) { + return [NSString stringWithFormat: NSLocalizedString( @"%u seconds", + @"time remaining" ), seconds]; + } + + unsigned minutes = seconds / 60; + seconds = seconds % 60; + + if (minutes < 60) { + return [NSString stringWithFormat: NSLocalizedString( @"%u:%02u minutes", + @"time remaining: minutes, seconds" ) , minutes, seconds]; + } + + unsigned hours = minutes / 60; + minutes = minutes % 60; + + return [NSString stringWithFormat: NSLocalizedString( @"%2:%02u hours", @"time remaining: hours, minutes" ), hours, minutes]; +} + +- (NSString *) statusText +{ + NSString *speedString = @""; + + float speed = 0.0; + NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate: startDate]; + if (elapsedTime >= 0.1) { + speed = (float)receivedSize / elapsedTime; + speedString = [NSString stringWithFormat: @" (%@/s)", cocoa_file_size_string( speed )]; + } + + NSString *timeRemainingString = @""; + NSString *totalSizeString = @""; + if (totalSize != 0) { + if (speed > 0.0) { + float timeRemaining = (float)(totalSize - receivedSize) / speed; + timeRemainingString = [NSString stringWithFormat: @": %@", cocoa_time_string( timeRemaining )]; + } + totalSizeString = [NSString stringWithFormat: NSLocalizedString( @" of %@", @"... of (total size)" ), cocoa_file_size_string( totalSize )]; + } + + return [NSString stringWithFormat: @"%@%@%@%@", cocoa_file_size_string( receivedSize ), + totalSizeString, speedString, timeRemainingString]; +} + ++ (NSSet *) keyPathsForValuesAffectingFileName +{ + return [NSSet setWithObject: @"saveURL"]; +} + +- (NSString *) fileName +{ + return [[saveURL path] lastPathComponent]; +} + ++ (NSSet *) keyPathsForValuesAffectingIcon +{ + return [NSSet setWithObjects: @"mimeType", @"URL", nil]; +} + +- (NSImage *) icon; +{ + NSString *type = [(NSString *)UTTypeCreatePreferredIdentifierForTag( kUTTagClassMIMEType, (CFStringRef)mimeType, NULL ) autorelease]; + if ([type hasPrefix: @"dyn."] || [type isEqualToString: (NSString *)kUTTypeData]) { + NSString *pathExt = [[url path] pathExtension]; + type = [(NSString *)UTTypeCreatePreferredIdentifierForTag( kUTTagClassFilenameExtension, (CFStringRef)pathExt, NULL ) autorelease]; + } + return [[NSWorkspace sharedWorkspace] iconForFileType: type]; +} + + +#pragma mark - +#pragma mark NetSurf interface functions + +static struct gui_download_window * +gui_download_window_create(download_context *ctx, + struct gui_window *parent) +{ + DownloadWindowController * const window = [[DownloadWindowController alloc] initWithContext: ctx]; + cocoa_register_download( window ); + [window askForSave]; + [window release]; + + return (struct gui_download_window *)window; +} + +static nserror +gui_download_window_data(struct gui_download_window *dw, + const char *data, + unsigned int size) +{ + DownloadWindowController * const window = (DownloadWindowController *)dw; + return [window receivedData: [NSData dataWithBytes: data length: size]] ? NSERROR_OK : NSERROR_SAVE_FAILED; +} + +static void +gui_download_window_error(struct gui_download_window *dw, + const char *error_msg) +{ + DownloadWindowController * const window = (DownloadWindowController *)dw; + [window showError: [NSString stringWithUTF8String: error_msg]]; +} + +static void +gui_download_window_done(struct gui_download_window *dw) +{ + DownloadWindowController * const window = (DownloadWindowController *)dw; + [window downloadDone]; +} + +@end + +#pragma mark - +static NSMutableSet *cocoa_all_downloads = nil; + +static void +cocoa_register_download( DownloadWindowController *download ) +{ + if (cocoa_all_downloads == nil) { + cocoa_all_downloads = [[NSMutableSet alloc] init]; + } + [cocoa_all_downloads addObject: download]; +} + +static void +cocoa_unregister_download( DownloadWindowController *download ) +{ + [cocoa_all_downloads removeObject: download]; +} + + +static struct gui_download_table download_table = { + .create = gui_download_window_create, + .data = gui_download_window_data, + .error = gui_download_window_error, + .done = gui_download_window_done, +}; + +struct gui_download_table *cocoa_download_table = &download_table; |