Downloads - Perform write on background thread, add cancel and clear, indicate complete/cancel in progress cell

This commit is contained in:
anthony 2020-12-18 20:43:26 +00:00
parent 38b61f46e7
commit 74e3fe059f
9 changed files with 141 additions and 20 deletions

View File

@ -2,9 +2,11 @@
@class DownloadManager;
@class DownloadItem;
struct download_context;
@protocol DownloadManagerDelegate
-(void)downloadManagerDidAddDownload: (DownloadManager*)aDownloadManager;
-(void)downloadManager: (DownloadManager*)aDownloadManager didRemoveItems: (NSArray*)downloadItems;
-(void)downloadManager: (DownloadManager*)aDownloadManager didUpdateItem: (DownloadItem*)aDownloadItem;
@end
@ -19,11 +21,14 @@
NSOutputStream *outputStream;
NSString *error;
DownloadManager *manager;
NSTimeInterval lastWrite;
struct download_context *ctx;
}
-(BOOL)appendToDownload: (NSData*)data;
-(void)cancel;
-(void)complete;
-(BOOL)isComplete;
-(BOOL)isCancelled;
-(void)failWithMessage: (NSString*)message;
-(NSURL*)destination;
-(NSString*)detailsText;
@ -38,8 +43,10 @@
id<DownloadManagerDelegate> delegate;
}
+(DownloadManager*)defaultDownloadManager;
-(DownloadItem*)createDownloadForDestination: (NSURL*)path withSizeInBytes: (NSUInteger)size;
-(DownloadItem*)createDownloadForDestination: (NSURL*)path withContext: (struct download_context*)ctx;
-(NSArray*)downloads;
-(void)removeDownloadsAtIndexes: (NSIndexSet*)anIndexSet;
-(void)cancelDownloadsAtIndexes: (NSIndexSet*)anIndexSet;
-(id)delegate;
-(void)setDelegate: (id<DownloadManagerDelegate>)aDelegate;
@end

View File

@ -1,9 +1,12 @@
#import <Cocoa/Cocoa.h>
#import "DownloadManager.h"
#import "desktop/download.h"
// TODO: - Verify behavior of performOnBackgroundThread on a multiprocessor system!!
@implementation DownloadItem
-(id)initWithManager: (DownloadManager*)aManager destination: (NSURL*)aDestination size: (NSInteger)aSize index: (NSUInteger)anIndex {
-(id)initWithManager: (DownloadManager*)aManager destination: (NSURL*)aDestination size: (NSInteger)aSize index: (NSUInteger)anIndex ctx: (struct download_context*)aCtx {
if (self = [super init]) {
error = nil;
index = anIndex;
@ -18,6 +21,7 @@
append: NO];
[outputStream retain];
[outputStream open];
ctx = aCtx;
}
return self;
}
@ -30,23 +34,41 @@
if (error) {
[error release];
}
download_context_destroy(ctx);
[super dealloc];
}
-(BOOL)appendToDownload: (NSData*)data {
// NOTE: - Not sure if this really works on mp systems...
// Does this get queued sequentially? If not need to upkeep our own thread...
[self performSelectorInBackground: @selector(reallyWriteData:) withObject: data];
return YES;
}
-(void)reallyWriteData: (NSData*)data {
NSUInteger len = [data length];
NSUInteger writtenNow = [outputStream write: [data bytes] maxLength: len];
written += writtenNow;
// Unless im misunderstanding download_context_get_total_length appears to return
// a too-small non-zero value for download size when called in
// gnustep_download_create...
// a too-small non-zero value for download size when called so...
size = MAX(written, size);
NSTimeInterval time = [startDate timeIntervalSinceNow];
// Only notify the manager at most once in each second.
if ((int)time != (int)lastWrite) {
[self performSelectorOnMainThread: @selector(notifyManager) withObject: nil
waitUntilDone: NO];
}
lastWrite = time;
}
-(void)notifyManager {
[[manager delegate] downloadManager: manager didUpdateItem: self];
return writtenNow == len;
}
-(void)cancel {
NSLog(@"cancel!!");
if (!completed) {
cancelled = YES;
download_context_abort(ctx);
[self complete];
}
}
@ -69,6 +91,9 @@
return completed;
}
-(BOOL)isCancelled {
return cancelled;
}
-(NSURL*)destination {
return destination;
@ -84,7 +109,12 @@
}
NSUInteger bytesLeft = size - written;
double kibLeft = (double)bytesLeft / 1024.0;
return [NSString stringWithFormat: @"%.2f KiB", kibLeft];
if (kibLeft < 1024.0) {
return [NSString stringWithFormat: @"%.2f KiB", kibLeft];
} else {
double mibLeft = (double)kibLeft / 1024.0;
return [NSString stringWithFormat: @"%.2f MiB", mibLeft];
}
}
-(NSString*)speedText {
@ -93,7 +123,12 @@
}
NSTimeInterval secondsPassed = -[startDate timeIntervalSinceNow];
double kibPerSecond = (double)written / (secondsPassed * 1024.0);
return [NSString stringWithFormat: @"%.2f KiB/s", kibPerSecond];
if (kibPerSecond < 1024.0) {
return [NSString stringWithFormat: @"%.2f KiB/s", kibPerSecond];
} else {
double mibPerSecond = kibPerSecond / 1024.0;
return [NSString stringWithFormat: @"%.2f MiB/s", mibPerSecond];
}
}
-(double)completionProgress {
@ -149,9 +184,11 @@
[super dealloc];
}
-(DownloadItem*)createDownloadForDestination: (NSURL*)path withSizeInBytes: (NSUInteger)size {
-(DownloadItem*)createDownloadForDestination: (NSURL*)path withContext: (struct download_context*)ctx {
// TODO: - dataSize is smaller than the actual size in some cases. Why?
NSUInteger dataSize = download_context_get_total_length(ctx);
DownloadItem *item = [[DownloadItem alloc] initWithManager: self destination: path
size: size index: [downloads count]];
size: dataSize index: [downloads count] ctx: ctx];
[downloads addObject: item];
[item release];
[delegate downloadManagerDidAddDownload: self];
@ -170,4 +207,19 @@
return delegate;
}
-(void)removeDownloadsAtIndexes: (NSIndexSet*)anIndexSet {
NSUInteger dlCount = [downloads count];
if ([anIndexSet indexGreaterThanOrEqualToIndex: dlCount] == NSNotFound) {
NSArray *items = [downloads objectsAtIndexes: anIndexSet];
[downloads removeObjectsAtIndexes: anIndexSet];
[delegate downloadManager: self didRemoveItems: items];
}
}
-(void)cancelDownloadsAtIndexes: (NSIndexSet*)anIndexSet {
NSArray *items = [downloads objectsAtIndexes: anIndexSet];
[items makeObjectsPerformSelector: @selector(cancel)];
}
@end

View File

@ -1,9 +1,13 @@
#import <Cocoa/Cocoa.h>
#import "DownloadManager.h"
@interface DownloadsWindowController: NSWindowController<NSTableViewDataSource, DownloadManagerDelegate> {
id tableView;
}
-(void)remove: (id)sender;
-(void)cancel: (id)sender;
@end

View File

@ -3,6 +3,9 @@
#import "DownloadManager.h"
#import "ProgressBarCell.h"
#define TAG_MENU_REMOVE 5
#define TAG_MENU_CANCEL 3
@implementation DownloadsWindowController
-(id)init {
@ -35,7 +38,13 @@
objectAtIndex: rowIndex];
NSString *identifier = [aTableColumn identifier];
if ([identifier isEqual: @"progress"]) {
return [NSNumber numberWithDouble: [item completionProgress]];
if ([item isCancelled]) {
return @"Cancelled";
} else if ([item isComplete]) {
return @"Complete";
} else {
return [NSNumber numberWithDouble: [item completionProgress]];
}
} else if ([identifier isEqual: @"details"]) {
return [item detailsText];
} else if ([identifier isEqual: @"remaining"]) {
@ -67,11 +76,45 @@
[tableView reloadData];
}
-(void)downloadManager: (DownloadManager*)aDownloadManager didRemoveItems: (NSArray*)downloadItems {
[tableView reloadData];
}
-(void)downloadManager: (DownloadManager*)aDownloadManager didUpdateItem: (DownloadItem*)aDownloadItem {
NSIndexSet *rows = [NSIndexSet indexSetWithIndex: [aDownloadItem index]];
NSIndexSet *columns = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, 4)];
[tableView reloadDataForRowIndexes: rows columnIndexes: columns];
}
// MARK: - Menu Stuff
-(BOOL)validateMenuItem: (NSMenuItem*)aMenuItem {
NSInteger tag = [aMenuItem tag];
if (tag == TAG_MENU_REMOVE || tag == TAG_MENU_CANCEL) {
return [tableView numberOfSelectedRows] > 0;
}
return YES;
}
-(void)remove: (id)sender {
id row;
NSMutableIndexSet *indices = [NSMutableIndexSet indexSet];
NSEnumerator *selectedRows = [tableView selectedRowEnumerator];
while ((row = [selectedRows nextObject]) != nil) {
[indices addIndex: [row integerValue]];
}
[[DownloadManager defaultDownloadManager] removeDownloadsAtIndexes: indices];
}
-(void)cancel: (id)sender {
id row;
NSMutableIndexSet *indices = [NSMutableIndexSet indexSet];
NSEnumerator *selectedRows = [tableView selectedRowEnumerator];
while ((row = [selectedRows nextObject]) != nil) {
[indices addIndex: [row integerValue]];
}
[[DownloadManager defaultDownloadManager] cancelDownloadsAtIndexes: indices];
}
@end

View File

@ -4,16 +4,31 @@
@implementation ProgressBarCell
-(void)drawInteriorWithFrame: (NSRect)cellFrame inView: (NSView*)controlView {
double progress = [[self objectValue] doubleValue];
double progress = 0.0;
NSString *text = nil;
NSColor *color = nil;
if ([[self objectValue] isKindOfClass: [NSNumber class]]) {
progress = [[self objectValue] doubleValue];
} else if ([[self objectValue] isKindOfClass: [NSString class]]) {
text = [self objectValue];
}
if ([self isHighlighted]) {
color = [NSColor whiteColor];
} else {
color = [NSColor colorWithDeviceRed: 0.2 green: 0.5 blue: 0.9 alpha: 1.0];
}
cellFrame.size.width *= progress;
[NSGraphicsContext saveGraphicsState];
if ([self isHighlighted]) {
[[NSColor whiteColor] set];
} else {
[[NSColor colorWithDeviceRed: 0.2 green: 0.5 blue: 0.9 alpha: 1.0] set];
}
[color set];
[NSBezierPath fillRect: cellFrame];
[NSGraphicsContext restoreGraphicsState];
if (text != nil) {
NSAttributedString *str = [[NSAttributedString alloc] initWithString: text
attributes: [NSDictionary dictionaryWithObjectsAndKeys:
color, NSForegroundColorAttributeName, nil]];
[str drawAtPoint: cellFrame.origin];
[str release];
}
[NSGraphicsContext restoreGraphicsState];;
}
-(void)highlight: (BOOL)lit withFrame: (NSRect)cellFrame inView: (NSView*)controlView {

View File

@ -15,6 +15,8 @@
"didTapNewWindow:",
"findNext:",
"findPrevious:",
"removeAll:",
"remove:",
"showAll:",
"showDownloadsWindow:",
"showFindPanel:"

View File

@ -13,10 +13,8 @@
static struct gui_download_window *gnustep_download_create(struct download_context *ctx, struct gui_window *parent) {
NSLog(@"gnustep_download_create");
NSURL *url = [[NSApp delegate] requestDownloadDestination];
// TODO: - dataSize is smaller than the actual size in some cases. Why?
NSUInteger dataSize = download_context_get_total_length(ctx);
DownloadItem *download = [[DownloadManager defaultDownloadManager]
createDownloadForDestination: url withSizeInBytes: dataSize];
createDownloadForDestination: url withContext: ctx];
[[NSApp delegate] showDownloadsWindow: nil];
return (struct gui_download_window*)download;
}