diff options
Diffstat (limited to 'frontends/cocoa/DownloadWindowController.m')
-rw-r--r-- | frontends/cocoa/DownloadWindowController.m | 414 |
1 files changed, 414 insertions, 0 deletions
diff --git a/frontends/cocoa/DownloadWindowController.m b/frontends/cocoa/DownloadWindowController.m new file mode 100644 index 000000000..8850327b9 --- /dev/null +++ b/frontends/cocoa/DownloadWindowController.m @@ -0,0 +1,414 @@ +/* + * 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 "netsurf/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); +} + +- (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 == NSModalResponseCancel) { + [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, (__bridge CFDictionaryRef)attributes); + NSLOG(netsurf, INFO, "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 == NSModalResponseOK) { + [[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 = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)mimeType, NULL); + if ([type hasPrefix:@"dyn."] || [type isEqualToString:(NSString *)kUTTypeData]) { + NSString *pathExt = [[url path] pathExtension]; + type = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)pathExt, NULL); + } + 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]; + + return (__bridge_retained 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 = (__bridge 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 = (__bridge DownloadWindowController *)dw; + [window showError:[NSString stringWithUTF8String:error_msg]]; +} + +static void +gui_download_window_done(struct gui_download_window *dw) +{ + DownloadWindowController *const window = (__bridge 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; |