diff --git a/Base.lproj/UI.xib b/Base.lproj/UI.xib index bed945a..1f3f822 100644 --- a/Base.lproj/UI.xib +++ b/Base.lproj/UI.xib @@ -1,9 +1,9 @@ - + - - + + @@ -451,25 +451,26 @@ DQ + - + - + - + - + - + - + @@ -480,94 +481,66 @@ DQ - - + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -575,7 +548,7 @@ DQ - + diff --git a/CTCFeedParser.h b/CTCFeedParser.h index 53b4006..419961b 100644 --- a/CTCFeedParser.h +++ b/CTCFeedParser.h @@ -3,6 +3,7 @@ @interface CTCFeedParser : NSObject -+ (NSArray*)parseFiles:(NSXMLDocument*)feed; ++ (NSArray*)parseFiles:(NSXMLDocument*)feed + error:(NSError * __autoreleasing *)error; @end diff --git a/CTCFeedParser.m b/CTCFeedParser.m index b5dceac..f32fd1a 100644 --- a/CTCFeedParser.m +++ b/CTCFeedParser.m @@ -3,7 +3,8 @@ @implementation CTCFeedParser -+ (NSArray*)parseFiles:(NSXMLDocument*)feed { ++ (NSArray*)parseFiles:(NSXMLDocument*)feed + error:(NSError * __autoreleasing *)outError { NSLog(@"Parsing feed"); NSError *error = nil; @@ -12,7 +13,7 @@ + (NSArray*)parseFiles:(NSXMLDocument*)feed { NSArray *fileNodes = [feed nodesForXPath:@"//rss/channel/item" error:&error]; if (!fileNodes) { - NSLog(@"Parsing for URLs failed: %@", error); + *outError = error; return nil; } diff --git a/CTCFileUtils.h b/CTCFileUtils.h index a64fd31..f833416 100644 --- a/CTCFileUtils.h +++ b/CTCFileUtils.h @@ -3,6 +3,12 @@ @interface CTCFileUtils : NSObject ++ (NSData *)bookmarkForURL:(NSURL *)url + error:(NSError * __autoreleasing *)error; + ++ (NSURL *)URLFromBookmark:(NSData *)bookmark + error:(NSError * __autoreleasing *)error; + + (NSString *)computeFilenameFromURL:(NSURL*)fileURL; + (NSString *)addTorrentExtensionTo:(NSString*)filename; diff --git a/CTCFileUtils.m b/CTCFileUtils.m index 9c93676..c8f35f7 100644 --- a/CTCFileUtils.m +++ b/CTCFileUtils.m @@ -6,6 +6,42 @@ @implementation CTCFileUtils ++ (NSData *)bookmarkForURL:(NSURL *)url + error:(NSError * __autoreleasing *)outError { + // Create a bookmark so we can transfer access to the downloads path + // to the feed checker service + NSError *error = nil; + NSData *downloadFolderBookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationMinimalBookmark + includingResourceValuesForKeys:@[] + relativeToURL:nil + error:&error]; + if (!downloadFolderBookmark || error) { + *outError = error; + return nil; + } + + return downloadFolderBookmark; +} + ++ (NSURL *)URLFromBookmark:(NSData *)bookmark + error:(NSError * __autoreleasing *)outError { + NSError *error = nil; + BOOL isStale = NO; + NSURL *URL = [NSURL URLByResolvingBookmarkData:bookmark + options:kNilOptions + relativeToURL:nil + bookmarkDataIsStale:&isStale + error:&error]; + + if (!URL || error) { + NSLog(@"Could not get URL from bookmark: %@", error); + *outError = error; + return nil; + } + + return URL; +} + + (NSString *)computeFilenameFromURL:(NSURL*)fileURL { // Compute destination filename NSString *filename = fileURL.path.pathComponents.lastObject; diff --git a/CTCMenuController.m b/CTCMenuController.m index f4a2317..a928677 100644 --- a/CTCMenuController.m +++ b/CTCMenuController.m @@ -12,12 +12,18 @@ @interface CTCMenuController () @property (strong, nonatomic) NSStatusItem *menuBarItem; +@property (strong, nonatomic) NSDateFormatter *lastUpdateDateFormatter; + @end @implementation CTCMenuController - (void)awakeFromNib { + // Create a date formatter for "last update" dates + self.lastUpdateDateFormatter = NSDateFormatter.new; + self.lastUpdateDateFormatter.timeStyle = NSDateFormatterShortStyle; + [self setupMenuItem]; // Update UI with initial values @@ -95,27 +101,16 @@ - (void)refreshSchedulerStatus { - (void)setLastUpdateStatus:(BOOL)lastUpdateWasSuccessful time:(NSDate *)time { // Create something like "Last update: 3:45 AM" and place it in the menu - NSString *baseLastUpdateString = nil; - NSString *lastUpdateString = nil; + NSString *lastUpdateStatusFormat = lastUpdateWasSuccessful ? + NSLocalizedString(@"lastupdate", @"Title for the last update time") : + NSLocalizedString(@"lastupdatefailed", @"Title for the last update time if it fails"); - if (lastUpdateWasSuccessful) { - baseLastUpdateString = NSLocalizedString(@"lastupdate", @"Title for the last update time"); - } - else { - baseLastUpdateString = NSLocalizedString(@"lastupdatefailed", @"Title for the last update time if it fails"); - } - - if (time) { - NSDateFormatter *dateFormatter = NSDateFormatter.new; - dateFormatter.timeStyle = NSDateFormatterShortStyle; - NSString *lastUpdateTime = [dateFormatter stringFromDate:time]; - lastUpdateString = [NSString stringWithFormat:baseLastUpdateString, lastUpdateTime]; - } - else { - lastUpdateString = [NSString stringWithFormat:baseLastUpdateString, NSLocalizedString(@"never", @"Never happened")]; - } + NSString *lastUpdateStatus = time ? + [NSString stringWithFormat:lastUpdateStatusFormat, + [self.lastUpdateDateFormatter stringFromDate:time]] : + [NSString stringWithFormat:lastUpdateStatusFormat, NSLocalizedString(@"never", @"Never happened")]; - [self.menuLastUpdate setTitle:lastUpdateString]; + [self.menuLastUpdate setTitle:lastUpdateStatus]; } - (void)setIdle { diff --git a/CTCRecentsCellView.h b/CTCRecentsCellView.h new file mode 100644 index 0000000..bc50c22 --- /dev/null +++ b/CTCRecentsCellView.h @@ -0,0 +1,9 @@ +#import + + +@interface CTCRecentsCellView : NSTableCellView + +@property (weak, nonatomic) IBOutlet NSTextField *downloadDateTextField; +@property (weak, nonatomic) IBOutlet NSButton *downloadAgainButton; + +@end diff --git a/CTCRecentsCellView.m b/CTCRecentsCellView.m new file mode 100644 index 0000000..2820ebc --- /dev/null +++ b/CTCRecentsCellView.m @@ -0,0 +1,4 @@ +#import "CTCRecentsCellView.h" + + +@implementation CTCRecentsCellView @end diff --git a/CTCRecentsController.m b/CTCRecentsController.m index 1eb9033..e8e3740 100644 --- a/CTCRecentsController.m +++ b/CTCRecentsController.m @@ -1,6 +1,7 @@ #import "CTCRecentsController.h" #import "CTCDefaults.h" #import "CTCScheduler.h" +#import "CTCRecentsCellView.h" @interface CTCRecentsController () @@ -20,8 +21,6 @@ - (void)awakeFromNib { self.downloadDateFormatter.dateStyle = NSDateFormatterShortStyle; self.downloadDateFormatter.doesRelativeDateFormatting = YES; - self.table.selectionHighlightStyle = NSTableViewSelectionHighlightStyleSourceList; - [self setupObservers]; } @@ -46,66 +45,42 @@ - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { return CTCDefaults.downloadHistory.count; } -- (IBAction)downloadRecentItemAgain:(id)sender { - NSLog(@"download again"); +- (IBAction)downloadRecentItemAgain:(NSButton *)senderButton { + NSUInteger clickedRow = [self.table rowForView:senderButton]; + NSDictionary *recentToDownload = CTCDefaults.downloadHistory[clickedRow]; + if (!recentToDownload) return; + + BOOL isMagnetLink = [recentToDownload[@"isMagnetLink"] boolValue]; + if (isMagnetLink) { + [NSWorkspace.sharedWorkspace openURL:[NSURL URLWithString:recentToDownload[@"url"]]]; + } + else { + [CTCScheduler.sharedScheduler downloadFile:recentToDownload + completion:^(NSDictionary *downloadedFile, NSError *error) { + if (downloadedFile && CTCDefaults.shouldOpenTorrentsAutomatically) { + [NSWorkspace.sharedWorkspace openFile:downloadedFile[@"torrentFilePath"]]; + } + }]; + } +} + +- (NSView *)tableView:(NSTableView *)tableView + viewForTableColumn:(NSTableColumn *)tableColumn + row:(NSInteger)row { + NSDictionary *recent = CTCDefaults.downloadHistory[row]; -// // Also refresh the list of recently downloaded torrents -// // Get the full list -// NSArray *downloadHistory = CTCDefaults.downloadHistory; -// -// // Get last 9 elements (changed from 10 so everything aligns nicer in the menu.. small tweak) -// NSUInteger recentsCount = MIN(downloadHistory.count, 9U); -// NSArray *recents = [downloadHistory subarrayWithRange:NSMakeRange(0U, recentsCount)]; -// -// // Clear menu -// [self.menuRecentTorrents.submenu removeAllItems]; -// -// // Add new items -// [recents enumerateObjectsUsingBlock:^(NSDictionary *recent, NSUInteger index, BOOL *stop) { -// NSString *menuTitle = [NSString stringWithFormat:@"%lu %@", index + 1, recent[@"title"]]; -// NSMenuItem *recentMenuItem = [[NSMenuItem alloc] initWithTitle:menuTitle -// action:NULL -// keyEquivalent:@""]; -// -// recentMenuItem.submenu = [self submenuForRecentItem:recent atIndex:index]; -// [self.menuRecentTorrents.submenu addItem:recentMenuItem]; -// }]; -// -// // Put the Show in finder menu back -// [self.menuRecentTorrents.submenu addItem:self.menuShowInFinder]; + CTCRecentsCellView *cell = [tableView makeViewWithIdentifier:@"RecentCell" owner:self]; -// // Create a "download again" item -// NSMenuItem *downloadAgainItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"redownload", @"Button to download a downloaded torrent file again") -// action:@selector(downloadRecentItemAgain:) -// keyEquivalent:@""]; -// downloadAgainItem.target = self; -// downloadAgainItem.tag = index; -// [submenu addItem:downloadAgainItem]; -// -// // Create a disabled item with the download date, if available -// NSDate *downloadDate = (NSDate *)recent[@"date"]; -// if (downloadDate) { -// // it may be interesting to have a bit more structure or intelligence to showing the dates for recent -// // items (just stuff this week based on preference?), or show the date in the list.. this solves -// // the problem of "how recent was recent?" tho with the tooltip. -// NSString *relativeDownloadDateDescription = [self.downloadDateFormatter stringFromDate:downloadDate]; -// NSMenuItem *downloadDateItem = [[NSMenuItem alloc] initWithTitle:relativeDownloadDateDescription -// action:NULL -// keyEquivalent:@""]; -// downloadAgainItem.enabled = NO; -// [submenu addItem:downloadDateItem]; -// } + cell.textField.stringValue = recent[@"title"]; -// NSDictionary *recentToDownload = CTCDefaults.downloadHistory[senderMenuItem.tag]; -// if (!recentToDownload) return; -// -// BOOL isMagnetLink = [recentToDownload[@"isMagnetLink"] boolValue]; -// if (isMagnetLink) { -// [NSWorkspace.sharedWorkspace openURL:[NSURL URLWithString:recentToDownload[@"url"]]]; -// } -// else { -// [CTCScheduler.sharedScheduler downloadFile:recentToDownload]; -// } + NSDate *downloadDate = (NSDate *)recent[@"date"]; + cell.downloadDateTextField.stringValue = downloadDate ? [self.downloadDateFormatter stringFromDate:downloadDate] : @""; + + return cell; +} + +- (BOOL)selectionShouldChangeInTableView:(NSTableView *)tableView { + return NO; } - (void)dealloc { diff --git a/CTCScheduler.h b/CTCScheduler.h index d938a2b..1512b0c 100644 --- a/CTCScheduler.h +++ b/CTCScheduler.h @@ -16,6 +16,7 @@ extern NSString * const kCTCSchedulerLastUpdateStatusNotificationName; - (void)forceCheck; -- (void)downloadFile:(NSDictionary *)file; +- (void)downloadFile:(NSDictionary *)file + completion:(void (^)(NSDictionary *downloadedFile, NSError *error))completion; @end diff --git a/CTCScheduler.m b/CTCScheduler.m index 49c6de2..cea6461 100644 --- a/CTCScheduler.m +++ b/CTCScheduler.m @@ -1,6 +1,7 @@ #import "CTCScheduler.h" #import "CTCFeedChecker.h" #import "CTCDefaults.h" +#import "CTCFileUtils.h" #import "NSDate+TimeOfDayMath.h" @@ -81,6 +82,7 @@ - (void)setChecking:(BOOL)checking { - (void)setPolling:(BOOL)polling { _polling = polling; + [self reportStatus]; } @@ -108,23 +110,17 @@ - (void)checkFeed { } - (NSData *)downloadFolderBookmark { - NSString *downloadPath = CTCDefaults.torrentsSavePath; + NSError *error; + NSURL *url = [NSURL fileURLWithPath:CTCDefaults.torrentsSavePath]; + NSData *bookmark = [CTCFileUtils bookmarkForURL:url error:&error]; - // Create a bookmark so we can transfer access to the downloads path - // to the feed checker service - NSURL *downloadFolderURL = [NSURL fileURLWithPath:downloadPath]; - NSError *error = nil; - NSData *downloadFolderBookmark = [downloadFolderURL bookmarkDataWithOptions:NSURLBookmarkCreationMinimalBookmark - includingResourceValuesForKeys:@[] - relativeToURL:nil - error:&error]; - if (!downloadFolderBookmark || error) { - // Not really handling this error + if (!bookmark) { + // Not really handling this at all [NSException raise:@"Couldn't create bookmark for downloads folder" format:@"Error: %@", error]; } - return downloadFolderBookmark; + return bookmark; } - (void)callFeedCheckerWithReplyHandler:(CTCFeedCheckCompletionHandler)replyHandler { @@ -143,6 +139,9 @@ - (void)callFeedCheckerWithReplyHandler:(CTCFeedCheckCompletionHandler)replyHand organizingByFolder:CTCDefaults.shouldOrganizeTorrentsInFolders skippingURLs:previouslyDownloadedURLs withReply:^(NSArray *downloadedFeedFiles, NSError *error) { + if (error) { + NSLog(@"Feed Checker error (checking feed): %@", error); + } dispatch_async(dispatch_get_main_queue(), ^{ replyHandler(downloadedFeedFiles, error); }); @@ -181,14 +180,20 @@ - (void)forceCheck { [self checkFeed]; } -- (void)downloadFile:(NSDictionary *)file { +- (void)downloadFile:(NSDictionary *)file + completion:(void (^)(NSDictionary *downloadedFile, NSError *error))completion { // Call feed checker service CTCFeedChecker *feedChecker = [self.feedCheckerConnection remoteObjectProxy]; [feedChecker downloadFile:file toBookmark:[self downloadFolderBookmark] organizingByFolder:CTCDefaults.shouldOrganizeTorrentsInFolders - withReply:^(NSError *error) { - // TODO + withReply:^(NSDictionary *downloadedFile, NSError *error) { + if (error) { + NSLog(@"Feed Checker error (downloading file): %@", error); + } + dispatch_async(dispatch_get_main_queue(), ^{ + completion(downloadedFile, error); + }); }]; } diff --git a/Catch.xcodeproj/project.pbxproj b/Catch.xcodeproj/project.pbxproj index 15d9c9f..917b499 100644 --- a/Catch.xcodeproj/project.pbxproj +++ b/Catch.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 38F9B09111C98E9E0021C540 /* Automator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 38F9B09011C98E9E0021C540 /* Automator.framework */; }; 4403ACF41914041C002F286C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4403ACF31914041C002F286C /* Assets.xcassets */; }; 441297D417FE285500E9E21E /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 448CC7E617FE1F840063D3FD /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 4439DFDA19B5E48100408851 /* CTCRecentsCellView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4439DFD919B5E48100408851 /* CTCRecentsCellView.m */; }; 4456EE611916D20000B3FF1A /* NSDate+TimeOfDayMath.m in Sources */ = {isa = PBXBuildFile; fileRef = 4456EE601916D20000B3FF1A /* NSDate+TimeOfDayMath.m */; }; 4456EE651916F17F00B3FF1A /* CTCBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = 4456EE641916F17F00B3FF1A /* CTCBrowser.m */; }; 445D97A6191579C7006CD77C /* UI.strings in Resources */ = {isa = PBXBuildFile; fileRef = 445D97A4191579C7006CD77C /* UI.strings */; }; @@ -110,6 +111,8 @@ 4403ACF31914041C002F286C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 4412E8161957F09200701124 /* com.giorgiocalderolla.Catch.CatchFeedHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = com.giorgiocalderolla.Catch.CatchFeedHelper.entitlements; sourceTree = ""; }; 4419CF37180627A900C77432 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + 4439DFD819B5E48100408851 /* CTCRecentsCellView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTCRecentsCellView.h; sourceTree = ""; }; + 4439DFD919B5E48100408851 /* CTCRecentsCellView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTCRecentsCellView.m; sourceTree = ""; }; 4456EE5F1916D20000B3FF1A /* NSDate+TimeOfDayMath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+TimeOfDayMath.h"; sourceTree = ""; }; 4456EE601916D20000B3FF1A /* NSDate+TimeOfDayMath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+TimeOfDayMath.m"; sourceTree = ""; }; 4456EE631916F17F00B3FF1A /* CTCBrowser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTCBrowser.h; sourceTree = ""; }; @@ -195,6 +198,7 @@ children = ( 4456EE681916F1BB00B3FF1A /* Application */, 4456EE661916F1A600B3FF1A /* Controllers */, + 4439DFD719B5E45700408851 /* Views */, 4456EE671916F1B100B3FF1A /* Libraries */, 4456EE621916D2E700B3FF1A /* Categories */, 29B97317FDCFA39411CA2CEA /* Resources */, @@ -262,6 +266,15 @@ name = Frameworks; sourceTree = ""; }; + 4439DFD719B5E45700408851 /* Views */ = { + isa = PBXGroup; + children = ( + 4439DFD819B5E48100408851 /* CTCRecentsCellView.h */, + 4439DFD919B5E48100408851 /* CTCRecentsCellView.m */, + ); + name = Views; + sourceTree = ""; + }; 4456EE621916D2E700B3FF1A /* Categories */ = { isa = PBXGroup; children = ( @@ -547,6 +560,7 @@ 44FBC67E196316E900434B01 /* NSWindow+ShakeAnimation.m in Sources */, 44717AA71913072200580054 /* CTCLoginItems.m in Sources */, 3803C7EC11C3E7AD00FC08DB /* CTCScheduler.m in Sources */, + 4439DFDA19B5E48100408851 /* CTCRecentsCellView.m in Sources */, 44717AAA1913173600580054 /* CTCFileUtils.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/CatchFeedHelper/CTCFeedChecker.h b/CatchFeedHelper/CTCFeedChecker.h index 69cb68b..55bca52 100644 --- a/CatchFeedHelper/CTCFeedChecker.h +++ b/CatchFeedHelper/CTCFeedChecker.h @@ -4,7 +4,8 @@ extern NSString *kCTCFeedCheckerErrorDomain; typedef void (^CTCFeedCheckCompletionHandler)(NSArray *downloadedFeedFiles, NSError *error); -typedef void (^CTCFeedCheckDownloadCompletionHandler)(NSError *error); +typedef void (^CTCFeedCheckDownloadCompletionHandler)(NSDictionary *downloadedFile, + NSError *error); @protocol CTCFeedCheck @@ -15,7 +16,7 @@ typedef void (^CTCFeedCheckDownloadCompletionHandler)(NSError *error); skippingURLs:(NSArray *)previouslyDownloadedURLs withReply:(CTCFeedCheckCompletionHandler)reply; -- (void)downloadFile:(NSDictionary *)fileURL +- (void)downloadFile:(NSDictionary *)file toBookmark:(NSData *)downloadFolderBookmark organizingByFolder:(BOOL)shouldOrganizeByFolder withReply:(CTCFeedCheckDownloadCompletionHandler)reply; diff --git a/CatchFeedHelper/CTCFeedChecker.m b/CatchFeedHelper/CTCFeedChecker.m index ea76285..33972eb 100644 --- a/CatchFeedHelper/CTCFeedChecker.m +++ b/CatchFeedHelper/CTCFeedChecker.m @@ -33,11 +33,12 @@ - (void)checkShowRSSFeed:(NSURL *)feedURL withReply:(CTCFeedCheckCompletionHandler)reply { NSLog(@"Checking feed"); - NSError *error; + NSError *error = nil; // Resolve the bookmark (that the main app gives us to transfer access to // the download folder) to a URL - NSURL *downloadFolderURL = [self URLFromBookmark:downloadFolderBookmark error:&error]; + NSURL *downloadFolderURL = [CTCFileUtils URLFromBookmark:downloadFolderBookmark + error:&error]; if (!downloadFolderURL) { reply(@[], error); return; @@ -46,20 +47,22 @@ - (void)checkShowRSSFeed:(NSURL *)feedURL NSString *downloadFolderPath = downloadFolderURL.path; // Download the feed - NSXMLDocument *feed = [self downloadFeed:feedURL]; + NSXMLDocument *feed = [self downloadFeed:feedURL error:&error]; if (!feed) { reply(@[], [NSError errorWithDomain:kCTCFeedCheckerErrorDomain - code:-1 - userInfo:nil]); + code:-5 + userInfo:@{NSLocalizedDescriptionKey: @"Could not download feed", + NSUnderlyingErrorKey: error}]); return; } // Parse the feed for files - NSArray *feedFiles = [CTCFeedParser parseFiles:feed]; + NSArray *feedFiles = [CTCFeedParser parseFiles:feed error:&error]; if (!feedFiles) { reply(@[], [NSError errorWithDomain:kCTCFeedCheckerErrorDomain - code:-2 - userInfo:nil]); + code:-6 + userInfo:@{NSLocalizedDescriptionKey: @"Could not parse feed", + NSUnderlyingErrorKey: error}]); return; } @@ -81,20 +84,21 @@ - (void)downloadFile:(NSDictionary *)file withReply:(CTCFeedCheckDownloadCompletionHandler)reply { NSLog(@"Downloading single file: %@", file[@"url"]); - NSError *error; + NSError *error = nil; // Resolve the bookmark (that the main app gives us to transfer access to // the download folder) to a URL - NSURL *downloadFolderURL = [self URLFromBookmark:downloadFolderBookmark error:&error]; + NSURL *downloadFolderURL = [CTCFileUtils URLFromBookmark:downloadFolderBookmark + error:&error]; if (!downloadFolderURL) { - reply(error); + reply(nil, error); return; } NSString *downloadFolderPath = downloadFolderURL.path; // Download the file - [self downloadFiles:@[file] + NSArray *downloadedFiles = [self downloadFiles:@[file] toPath:downloadFolderPath organizingByFolder:shouldOrganizeByFolder skippingURLs:@[] @@ -102,10 +106,11 @@ - (void)downloadFile:(NSDictionary *)file NSLog(@"All done"); - reply(error); + reply(downloadedFiles.firstObject, error); } -- (NSXMLDocument*)downloadFeed:(NSURL*)feedURL { +- (NSXMLDocument*)downloadFeed:(NSURL*)feedURL + error:(NSError * __autoreleasing *)outError { NSLog(@"Downloading feed %@", feedURL); // Flush the cache, we want fresh results @@ -119,7 +124,7 @@ - (NSXMLDocument*)downloadFeed:(NSURL*)feedURL { error:&error]; if (!document) { - NSLog(@"Feed download failed: %@", error); + *outError = error; return nil; } @@ -128,29 +133,13 @@ - (NSXMLDocument*)downloadFeed:(NSURL*)feedURL { return document; } -- (NSURL *)URLFromBookmark:(NSData *)bookmark error:(NSError * __autoreleasing *)outError { - NSError *error = nil; - BOOL isStale = NO; - NSURL *URL = [NSURL URLByResolvingBookmarkData:bookmark - options:kNilOptions - relativeToURL:nil - bookmarkDataIsStale:&isStale - error:&error]; - - if (!URL || error) { - NSLog(@"Could not get URL from bookmark: %@", error); - *outError = error; - return nil; - } - - return URL; -} - - (NSArray *)downloadFiles:(NSArray *)feedFiles toPath:(NSString *)downloadPath organizingByFolder:(BOOL)shouldOrganizeByFolder skippingURLs:(NSArray *)previouslyDownloadedURLs - error:(NSError * __autoreleasing *)error { + error:(NSError * __autoreleasing *)outError { + NSError *error = nil; + NSLog(@"Downloading files (if needed)"); NSMutableArray *successfullyDownloadedFeedFiles = NSMutableArray.array; @@ -179,18 +168,17 @@ - (NSArray *)downloadFiles:(NSArray *)feedFiles NSString *downloadedTorrentFile = [self downloadFile:[NSURL URLWithString:url] toPath:downloadPath - withShowName:showName]; + withShowName:showName + error:&error]; if (downloadedTorrentFile) { - [successfullyDownloadedFeedFiles addObject:@{@"url": file[@"url"], + [successfullyDownloadedFeedFiles addObject:@{@"url": url, @"title": file[@"title"], @"isMagnetLink": @NO, @"torrentFilePath": downloadedTorrentFile}]; } else { - NSLog(@"Could not download %@", url); - *error = [NSError errorWithDomain:kCTCFeedCheckerErrorDomain - code:-3 - userInfo:nil]; + NSLog(@"Could not download %@: %@", url, error); + *outError = error; } } } @@ -200,7 +188,8 @@ - (NSArray *)downloadFiles:(NSArray *)feedFiles - (NSString *)downloadFile:(NSURL *)fileURL toPath:(NSString *)downloadPath - withShowName:(NSString *)showName { + withShowName:(NSString *)showName + error:(NSError * __autoreleasing *)outError { NSString *folder = [CTCFileUtils folderNameForShowWithName:showName]; if (folder) NSLog(@"Downloading file %@ in folder %@",fileURL,folder); @@ -211,12 +200,17 @@ - (NSString *)downloadFile:(NSURL *)fileURL // Download! NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:fileURL]; NSURLResponse *urlResponse = NSURLResponse.new; - NSError *downloadError = NSError.new; NSData *downloadedFile = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&urlResponse - error:&downloadError]; + error:&error]; - if (!downloadedFile) return nil; + if (!downloadedFile) { + *outError = [NSError errorWithDomain:kCTCFeedCheckerErrorDomain + code:-1 + userInfo:@{NSLocalizedDescriptionKey: @"Could not download file", + NSUnderlyingErrorKey: error}]; + return nil; + } NSLog(@"Download complete, filesize: %lu", (unsigned long)downloadedFile.length); @@ -238,6 +232,10 @@ - (NSString *)downloadFile:(NSURL *)fileURL isDirectory:&pathAndFolderIsDirectory]) { if (!pathAndFolderIsDirectory) { // Exists but isn't a directory! Aaargh! Abort! + NSString *errorDescription = [NSString stringWithFormat:@"Download path is not a directory: %@", pathAndFolder]; + *outError = [NSError errorWithDomain:kCTCFeedCheckerErrorDomain + code:-2 + userInfo:@{NSLocalizedDescriptionKey: errorDescription}]; return nil; } } @@ -248,7 +246,11 @@ - (NSString *)downloadFile:(NSURL *)fileURL attributes:nil error:&error]) { // Folder creation failed :( Abort - NSLog(@"Couldn't create folder %@", pathAndFolder); + NSString *errorDescription = [NSString stringWithFormat:@"Couldn't create folder: %@", pathAndFolder]; + *outError = [NSError errorWithDomain:kCTCFeedCheckerErrorDomain + code:-3 + userInfo:@{NSLocalizedDescriptionKey: errorDescription, + NSUnderlyingErrorKey: error}]; return nil; } else { @@ -258,9 +260,14 @@ - (NSString *)downloadFile:(NSURL *)fileURL // Write! BOOL wasWrittenSuccessfully = [downloadedFile writeToFile:pathAndFilename - atomically:YES]; + options:NSDataWritingAtomic + error:&error]; if (!wasWrittenSuccessfully) { - NSLog(@"Couldn't save file %@ to disk", pathAndFilename); + NSString *errorDescription = [NSString stringWithFormat:@"Couldn't save file to disk: %@", pathAndFolder]; + *outError = [NSError errorWithDomain:kCTCFeedCheckerErrorDomain + code:-4 + userInfo:@{NSLocalizedDescriptionKey: errorDescription, + NSUnderlyingErrorKey: error}]; return nil; }