From ebdf8b9395ac2f7c59e7526c71b8262713214709 Mon Sep 17 00:00:00 2001 From: Kelvin Sherlock Date: Thu, 3 Sep 2020 22:37:27 -0400 Subject: [PATCH] improved download manager a few rough edges left but it displays the list of ROMs and allows downloading individual items and viewing them in Finder. --- Ample/Ample.h | 1 + Ample/Ample.m | 11 + Ample/Base.lproj/DownloadWindow.xib | 139 +++++++++++- Ample/DownloadWindowController.h | 18 +- Ample/DownloadWindowController.m | 327 ++++++++++++++++++++++++++-- 5 files changed, 468 insertions(+), 28 deletions(-) diff --git a/Ample/Ample.h b/Ample/Ample.h index 2c62288..2060116 100644 --- a/Ample/Ample.h +++ b/Ample/Ample.h @@ -12,6 +12,7 @@ #import NSURL *SupportDirectory(void); +NSString *SupportDirectoryPath(void); /* NSUserDefaults keys */ extern NSString *kUseCustomMame; diff --git a/Ample/Ample.m b/Ample/Ample.m index db766b0..007dbaa 100644 --- a/Ample/Ample.m +++ b/Ample/Ample.m @@ -21,6 +21,17 @@ NSURL *SupportDirectory(void) { [fm createDirectoryAtURL: cached withIntermediateDirectories: YES attributes: nil error: &error]; } return cached; + +} + +NSString *SupportDirectoryPath(void) { + static NSString *cached = nil; + + if (!cached) { + NSURL *url = SupportDirectory(); + cached = [NSString stringWithCString: [url fileSystemRepresentation] encoding: NSUTF8StringEncoding]; + } + return cached; } diff --git a/Ample/Base.lproj/DownloadWindow.xib b/Ample/Base.lproj/DownloadWindow.xib index 997fa8e..d7687b4 100644 --- a/Ample/Base.lproj/DownloadWindow.xib +++ b/Ample/Base.lproj/DownloadWindow.xib @@ -8,6 +8,7 @@ + @@ -16,10 +17,10 @@ - + - + - + @@ -51,8 +52,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -64,7 +165,33 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ample/DownloadWindowController.h b/Ample/DownloadWindowController.h index 2fd21d6..80c1ec3 100644 --- a/Ample/DownloadWindowController.h +++ b/Ample/DownloadWindowController.h @@ -10,7 +10,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface DownloadWindowController : NSWindowController +@interface DownloadWindowController : NSWindowController @property NSString *currentROM; @property NSInteger currentCount; @@ -20,4 +20,20 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface DownloadWindowController (URL) +@end + +@interface DownloadWindowController (Table) +@end + +@interface DownloadWindowController (Menu) + +@end + + +@interface DownloadTableCellView : NSTableCellView +@property (weak) IBOutlet NSTextField *statusTextField; +@property (weak) IBOutlet NSProgressIndicator *activity; +@end + NS_ASSUME_NONNULL_END diff --git a/Ample/DownloadWindowController.m b/Ample/DownloadWindowController.m index ba4fc34..9fa17fc 100644 --- a/Ample/DownloadWindowController.m +++ b/Ample/DownloadWindowController.m @@ -9,17 +9,47 @@ #import "Ample.h" #import "DownloadWindowController.h" +enum { + ItemMissing = 0, + ItemFound, + ItemDownloading, + ItemDownloaded, + ItemCanceled, + ItemError +}; + +@interface DownloadItem : NSObject + +@property NSString *name; +@property NSError *error; +@property NSString *pathName; +@property NSURLSessionDownloadTask *task; +@property NSURL *localURL; + +@property NSUInteger status; +@property NSUInteger index; + + +-(void)cancelDownload; +-(void)beginDownloadWithTask:(NSURLSessionDownloadTask *)task; +-(void)completeWithError: (NSError *)error; +-(NSString *)statusDescription; +@end + + + @interface DownloadWindowController () +@property (weak) IBOutlet NSTableView *tableView; @end @implementation DownloadWindowController { - NSArray *_roms; + NSArray *_items; NSURL *_romFolder; NSURL *_sourceURL; NSURLSession *_session; - NSMutableSet *_tasks; + NSMutableDictionary *_taskIndex; } -(NSString *)windowNibName { @@ -40,40 +70,98 @@ NSDictionary *d = [NSDictionary dictionaryWithContentsOfURL: url]; NSURL *sd = SupportDirectory(); + NSString *romdir = [SupportDirectoryPath() stringByAppendingPathComponent: @"roms"]; + _romFolder = [sd URLByAppendingPathComponent: @"roms"]; [fm createDirectoryAtURL: _romFolder withIntermediateDirectories: YES attributes: nil error: &error]; - _roms = [d objectForKey: @"roms"]; + NSArray *roms = [d objectForKey: @"roms"]; [self setCurrentROM: @""]; [self setCurrentCount: 0]; - [self setTotalCount: [_roms count]]; + [self setTotalCount: [roms count]]; [self setErrorCount: 0]; _sourceURL = [NSURL URLWithString: @"https://archive.org/download/mame0224_rom"]; // hardcoded.... - [self download]; + NSMutableArray *tmp = [NSMutableArray arrayWithCapacity: [roms count]]; + unsigned ix = 0; + for (NSString *name in roms) { + + DownloadItem *item = [DownloadItem new]; + [item setName: name]; + [item setIndex: ix++]; + + [tmp addObject: item]; + + // check if the file exists. + NSString *s = [romdir stringByAppendingPathComponent: name]; + NSString *path; + + path = [s stringByAppendingPathExtension: @"zip"]; + if ([fm fileExistsAtPath: path]) { + [item setStatus: ItemFound]; + [item setLocalURL: [NSURL fileURLWithPath: path]]; + continue; + } + path = [s stringByAppendingPathExtension: @"7z"]; + if ([fm fileExistsAtPath: path]) { + [item setStatus: ItemFound]; + [item setLocalURL: [NSURL fileURLWithPath: path]]; + continue; + } + } + _items = tmp; + + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + _session = [NSURLSession sessionWithConfiguration: config delegate: self delegateQueue: nil]; + _taskIndex = [NSMutableDictionary dictionaryWithCapacity: [_items count]]; + + //[self download]; +} + +-(void)downloadItem: (DownloadItem *)item { + + if (!_session) { + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + _session = [NSURLSession sessionWithConfiguration: config delegate: self delegateQueue: nil]; + } + + NSURLSessionDownloadTask *task; + NSString *s = [item name]; + NSString *path = [s stringByAppendingString: @".7z"]; // hardcoded. + NSURL *url = [_sourceURL URLByAppendingPathComponent: path]; + + task = [_session downloadTaskWithURL: url]; + + [item beginDownloadWithTask: task]; + [_taskIndex setObject: item forKey: task]; + + [task resume]; + } -(void)download { - NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; - _session = [NSURLSession sessionWithConfiguration: config delegate: self delegateQueue: nil]; - _tasks = [NSMutableSet setWithCapacity: [_roms count]]; + // run in thread? //unsigned count = 0; - for (NSString *s in _roms) { + for (DownloadItem *item in _items) { NSURLSessionDownloadTask *task; + NSString *s = [item name]; NSString *path = [s stringByAppendingString: @".7z"]; // hardcoded. NSURL *url = [_sourceURL URLByAppendingPathComponent: path]; task = [_session downloadTaskWithURL: url]; - [_tasks addObject: task]; - [task resume]; + [_taskIndex setObject: item forKey: task]; + [item setTask: task]; + + [task resume]; + //++count; //if (count >= 2) break; } @@ -81,17 +169,62 @@ } --(IBAction)cancel:(id)sender { +-(DownloadItem *)clickedItem { + NSInteger row = [_tableView clickedRow]; + if (row < 0 || row >= [_items count]) return nil; + return [_items objectAtIndex: row]; +} +-(void)redrawRow: (NSUInteger)row { - for (NSURLSessionTask *task in _tasks) { - [task cancel]; + //NSRect r = [_tableView rectOfRow: row]; + //[_tableView setNeedsDisplayInRect: r]; + + NSIndexSet *rIx = [NSIndexSet indexSetWithIndex: row]; + NSIndexSet *cIx = [NSIndexSet indexSetWithIndex: 0]; + + [_tableView reloadDataForRowIndexes: rIx columnIndexes: cIx]; +} +#pragma mark - IBActions + +-(IBAction)cancelAll:(id)sender { + + for (DownloadItem *item in _items) { + [item cancelDownload]; } + [_session invalidateAndCancel]; _session = nil; - _tasks = nil; + [_taskIndex removeAllObjects]; [self setCurrentCount: 0]; [self setActive: NO]; - + + [_tableView reloadData]; + //[_tableView setNeedsDisplay: YES]; // doesn't work... +} + +- (IBAction)showInFinder:(id)sender { + DownloadItem *item = [self clickedItem]; + if (!item) return; + NSURL *url = [item localURL]; + if (!url) return; + + NSWorkspace *ws = [NSWorkspace sharedWorkspace]; + [ws activateFileViewerSelectingURLs: @[url]]; +} + +- (IBAction)download:(id)sender { + DownloadItem *item = [self clickedItem]; + if (!item) return; + + [self downloadItem: item]; + [self redrawRow: [item index]]; +} +- (IBAction)cancel:(id)sender { + DownloadItem *item = [self clickedItem]; + if (!item) return; + + [item cancelDownload]; + [self redrawRow: [item index]]; } @@ -100,33 +233,185 @@ -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { - + // not sure if strictly necessary but this happens in a background thread + // and these are used in KVO binding. Also, main thread only + // means no race conditions. dispatch_async(dispatch_get_main_queue(), ^(void){ if (error) [self setErrorCount: self->_errorCount + 1]; else [self setCurrentCount: self->_currentCount + 1]; - [self->_tasks removeObject: task]; - if (![self->_tasks anyObject]) { + + NSMutableDictionary *taskIndex = self->_taskIndex; + DownloadItem *item = [taskIndex objectForKey: task]; + [taskIndex removeObjectForKey: task]; + + if ([taskIndex count] == 0) { [self setActive: NO]; } + + if (item) { + [item completeWithError: error]; + NSUInteger row = [item index]; + + [self redrawRow: row]; + } }); } -- (void)URLSession:(NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location { +- (void)URLSession:(NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)task didFinishDownloadingToURL:(nonnull NSURL *)location { // need to move to the destination directory... // file deleted after this function returns, so can't move asynchronously. NSFileManager *fm = [NSFileManager defaultManager]; - NSURL *src = [[downloadTask originalRequest] URL]; + NSURL *src = [[task originalRequest] URL]; NSURL *dest = [_romFolder URLByAppendingPathComponent: [src lastPathComponent]]; NSError *error = nil; [fm moveItemAtURL: location toURL: dest error: &error]; + DownloadItem *item = [_taskIndex objectForKey: task]; + [item setLocalURL: dest]; + + /* + dispatch_async(dispatch_get_main_queue(), ^(void){ + + + [item setLocalURL: dest]; + } + */ NSLog(@"%@", src); } @end +@implementation DownloadWindowController (Table) + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { + return [_items count]; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { + + return [_items objectAtIndex: row]; +} + + +- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { + + if (row == 51) { NSLog(@"viewForRow 51");} + + DownloadItem *item = [_items objectAtIndex: row]; + DownloadTableCellView *v = [tableView makeViewWithIdentifier: @"DownloadCell" owner: self]; + + [[v textField] setObjectValue: [item name]]; + + NSTextField *tf = [v statusTextField]; + + [tf setObjectValue: [item statusDescription]]; + if ([item error]) { + [tf setTextColor: [NSColor redColor]]; + } else { + [tf setTextColor: [NSColor blackColor]]; + //if ([tableView isRowSelected: row]){ + //[tf setTextColor: [NSColor whiteColor]]; + //} + } + + if ([item task]) { + [[v activity] startAnimation: nil]; + } else { + [[v activity] stopAnimation: nil]; + } + + + return v; +} + + +@end + + +@implementation DownloadTableCellView + +@end + +@implementation DownloadItem + +-(void)beginDownloadWithTask:(NSURLSessionDownloadTask *)task { + _task = task; + _error = nil; + if (task) _status = ItemDownloading; +} + +-(void)cancelDownload { + if (!_task) return; + [_task cancel]; + _task = nil; + _status = ItemCanceled; +} + +-(void)completeWithError: (NSError *)error { + _task = nil; + if (error) { + _error = error; + _status = ItemError; + } else { + // what if there was an error moving it? + _error = nil; + _status = ItemDownloaded; + } +} + +-(NSString *)statusDescription { + + static NSString *Names[] = { + @"ROM missing", + @"ROM found", + @"Downloading…", + @"Downloaded", + @"Canceled", + @"Error" + }; + if (_error) return [_error description]; + + if (_status > sizeof(Names)/sizeof(Names[0])) return @"Unknown"; + return Names[_status]; +} + +@end + + + +@implementation DownloadWindowController (Menu) + +enum { + kOpenInFinder = 1, + kDownload, + kCancel, +}; + +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem { + NSInteger row = [_tableView clickedRow]; + if (row < 0) return NO; + DownloadItem *item = [_items objectAtIndex: row]; + + NSUInteger status = [item status]; + switch([menuItem tag]) { + case kOpenInFinder: + return status == ItemFound || status == ItemDownloaded; + break; + case kDownload: + return YES; + //return status == ItemMissing || status == ItemError || status == ItemCanceled; + break; + case kCancel: + return status == ItemDownloading; + break; + + } + return NO; +} + +@end