/* * Copyright 2011 Sven Weidauer * * 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 . */ #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); 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 == 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;