Revision: 29539 http://sourceforge.net/p/bibdesk/svn/29539 Author: hofman Date: 2025-09-07 17:09:12 +0000 (Sun, 07 Sep 2025) Log Message: ----------- separate objects for various download delegates forwarding to shared download manager, to avoid overlapping delegate protocols. Also avoids declaration of webkit in manager header.
Modified Paths: -------------- trunk/bibdesk/BDSKDownloadManager.h trunk/bibdesk/BDSKDownloadManager.m trunk/bibdesk/BDSKTextImportController.m trunk/bibdesk/BDSKWebView.m Modified: trunk/bibdesk/BDSKDownloadManager.h =================================================================== --- trunk/bibdesk/BDSKDownloadManager.h 2025-09-07 09:27:09 UTC (rev 29538) +++ trunk/bibdesk/BDSKDownloadManager.h 2025-09-07 17:09:12 UTC (rev 29539) @@ -37,32 +37,18 @@ */ #import <Cocoa/Cocoa.h> -#import <WebKit/WebKit.h> -#import "BDSKDownloader.h" NS_ASSUME_NONNULL_BEGIN -#if MAC_OS_X_VERSION_MAX_ALLOWED < 110300 -@protocol WKDownloadDelegate <NSObject> -@end -@interface WKDownload : NSObject <NSProgressReporting> -@property (nonatomic, readonly, nullable) NSURLRequest *originalRequest; -@property (nonatomic, readonly, weak) WKWebView *webView; -@property (nonatomic, weak) id <WKDownloadDelegate> delegate; -- (void)cancel:(void(^)(NSData * resumeData))completionHandler; -@end -#endif - extern NSString *BDSKDownloadsDidChangeNotification; -typedef NS_ENUM(NSInteger, BDSKDownloadStatus) { - BDSKDownloadStatusDownloading, - BDSKDownloadStatusFinished, - BDSKDownloadStatusFailed -}; +@protocol WebDownloadDelegate, WKDownloadDelegate, BDSKDownloadDelegate; -@interface BDSKDownloadManager : NSObject <WebDownloadDelegate, WKDownloadDelegate, BDSKDownloadDelegate> { +@interface BDSKDownloadManager : NSObject { NSMutableArray *downloads; + id<WebDownloadDelegate> webDownloadDelegate; + id<WKDownloadDelegate> wkDownloadDelegate; + id<BDSKDownloadDelegate> bdskDownloadDelegate; } @property (class, nonatomic, readonly) BDSKDownloadManager *sharedManager; @@ -69,6 +55,10 @@ @property (nonatomic) NSArray *downloads; +@property (nonatomic, readonly) id<WebDownloadDelegate> webDownloadDelegate; +@property (nonatomic, readonly) id<WKDownloadDelegate> wkDownloadDelegate; +@property (nonatomic, readonly) id<BDSKDownloadDelegate> bdskDownloadDelegate; + @property (nonatomic) BOOL removeFinishedDownloads, removeFailedDownloads; - (void)clear; @@ -81,6 +71,12 @@ #pragma mark - +typedef NS_ENUM(NSInteger, BDSKDownloadStatus) { + BDSKDownloadStatusDownloading, + BDSKDownloadStatusFinished, + BDSKDownloadStatusFailed +}; + @interface BDSKWebDownload : NSObject { NSInteger uniqueID; NSURL *URL; Modified: trunk/bibdesk/BDSKDownloadManager.m =================================================================== --- trunk/bibdesk/BDSKDownloadManager.m 2025-09-07 09:27:09 UTC (rev 29538) +++ trunk/bibdesk/BDSKDownloadManager.m 2025-09-07 17:09:12 UTC (rev 29539) @@ -37,6 +37,8 @@ */ #import "BDSKDownloadManager.h" +#import <WebKit/WebKit.h> +#import "BDSKDownloader.h" #import "BDSKStringConstants.h" #import "NSURL_BDSKExtensions.h" #import "NSFileManager_BDSKExtensions.h" @@ -46,10 +48,40 @@ NSString *BDSKDownloadsDidChangeNotification = @"BDSKDownloadsDidChangeNotification"; +#if MAC_OS_X_VERSION_MAX_ALLOWED < 110300 +@protocol WKDownloadDelegate <NSObject> +@end +@interface WKDownload : NSObject <NSProgressReporting> +@property (nonatomic, readonly, nullable) NSURLRequest *originalRequest; +@property (nonatomic, readonly, weak) WKWebView *webView; +@property (nonatomic, weak) id <WKDownloadDelegate> delegate; +- (void)cancel:(void(^)(NSData * resumeData))completionHandler; +@end +#endif + +#pragma mark - + +@interface BDSKDownloadDelegate : NSObject { + BDSKDownloadManager *downloadManager; +} +- (instancetype)initWithDownloadManager:(BDSKDownloadManager *)aDownloadManager; +@end + +@interface BDSKWebDownloadDelegate : BDSKDownloadDelegate <WebDownloadDelegate> +@end + +@interface BDSKWkDownloadDelegate : BDSKDownloadDelegate <WKDownloadDelegate> +@end + +@interface BDSKBdskDownloadDelegate : BDSKDownloadDelegate <BDSKDownloadDelegate> +@end + +#pragma mark - + @implementation BDSKDownloadManager @synthesize downloads; -@dynamic removeFinishedDownloads, removeFailedDownloads; +@dynamic removeFinishedDownloads, removeFailedDownloads, webDownloadDelegate, wkDownloadDelegate, bdskDownloadDelegate; + (BDSKDownloadManager *)sharedManager { static BDSKDownloadManager *sharedManager = nil; @@ -83,7 +115,7 @@ } - (void)notifyUpdate { - [[NSNotificationCenter defaultCenter] postNotificationName:BDSKDownloadsDidChangeNotification object:self]; + [[NSNotificationCenter defaultCenter] postNotificationName:BDSKDownloadsDidChangeNotification object:self]; } - (void)addDownload:(id)download { @@ -91,35 +123,6 @@ [self notifyUpdate]; } -- (void)setStatus:(BDSKDownloadStatus)status forDownload:(BDSKWebDownload *)webDownload error:(NSError *)error { - if (webDownload == nil) - return; - - [webDownload setStatus:status]; - - if ((status == BDSKDownloadStatusFailed && [[NSUserDefaults standardUserDefaults] boolForKey:BDSKRemoveFailedDownloadsKey]) || - (status == BDSKDownloadStatusFinished && [[NSUserDefaults standardUserDefaults] boolForKey:BDSKRemoveFinishedDownloadsKey])) - [downloads removeObject:webDownload]; - - [self notifyUpdate]; - - if (status == BDSKDownloadStatusFailed && error && ([[error domain] isEqualToString:NSURLErrorDomain] == NO || [error code] != NSURLErrorCancelled)) { - NSString *errorDescription = [error localizedDescription] ?: NSLocalizedString(@"An error occured during download.", @"Informative text in alert dialog"); - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:NSLocalizedString(@"Download Failed", @"Message in alert dialog when download failed")]; - [alert setInformativeText:errorDescription]; - [alert runModal]; - } -} - -- (BDSKWebDownload *)webDownloadForDownload:(id)download { - for (BDSKWebDownload *webDownload in downloads) { - if ([webDownload download] == download) - return webDownload; - } - return nil; -} - - (BDSKWebDownload *)downloadWithUniqueID:(NSInteger)uniqueID { for (BDSKWebDownload *webDownload in downloads) { if ([webDownload uniqueID] == uniqueID) @@ -177,8 +180,56 @@ - (NSString *)fontFamilyName { return [[NSFont systemFontOfSize:0.0] familyName]; } -#pragma mark Download delegate protocols +#pragma mark Download delegate support +- (id<WebDownloadDelegate>)webDownloadDelegate { + if (webDownloadDelegate == nil) + webDownloadDelegate = [[BDSKWebDownloadDelegate alloc] initWithDownloadManager:self]; + return webDownloadDelegate;; +} + +- (id<WKDownloadDelegate>)wkDownloadDelegate { + if (wkDownloadDelegate == nil) + wkDownloadDelegate = [[BDSKWkDownloadDelegate alloc] initWithDownloadManager:self]; + return wkDownloadDelegate;; +} + +- (id<BDSKDownloadDelegate>)bdskDownloadDelegate { + if (bdskDownloadDelegate == nil) + bdskDownloadDelegate = [[BDSKBdskDownloadDelegate alloc] initWithDownloadManager:self]; + return bdskDownloadDelegate;; +} + +- (BDSKWebDownload *)webDownloadForDownload:(id)download { + for (BDSKWebDownload *webDownload in downloads) { + if ([webDownload download] == download) + return webDownload; + } + return nil; +} + +- (void)download:(id)download didFinishWithError:(NSError *)error { + BDSKWebDownload *webDownload = [self webDownloadForDownload:download]; + if (webDownload == nil) + return; + + [webDownload setStatus:error ? BDSKDownloadStatusFailed : BDSKDownloadStatusFinished]; + + if ((error && [[NSUserDefaults standardUserDefaults] boolForKey:BDSKRemoveFailedDownloadsKey]) || + (error == nil && [[NSUserDefaults standardUserDefaults] boolForKey:BDSKRemoveFinishedDownloadsKey])) + [downloads removeObject:webDownload]; + + [self notifyUpdate]; + + if (error && ([[error domain] isEqualToString:NSURLErrorDomain] == NO || [error code] != NSURLErrorCancelled)) { + NSString *errorDescription = [error localizedDescription] ?: NSLocalizedString(@"An error occured during download.", @"Informative text in alert dialog"); + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:NSLocalizedString(@"Download Failed", @"Message in alert dialog when download failed")]; + [alert setInformativeText:errorDescription]; + [alert runModal]; + } +} + - (void)download:(id)download decideDestinationWithSuggestedFilename:(NSString *)filename window:(NSWindow *)window completionHandler:(void (^)(NSURL * destination))completionHandler { NSString *extension = [filename pathExtension]; @@ -206,100 +257,6 @@ [sPanel beginWithCompletionHandler:handler]; } -#pragma mark NSURLDownload delegate protocol - -- (void)downloadDidBegin:(NSURLDownload *)download { - [self addDownload:download]; -} - -// both in NSURLDownloadDelegate and WKDownloadDelegate -- (void)downloadDidFinish:(id)download { - BDSKWebDownload *webDownload = [self webDownloadForDownload:download]; - - if ([webDownload response] && [[webDownload fileURL] checkResourceIsReachableAndReturnError:NULL]) { - // this must be an NSURLDownload, otherwise there won't be a response - // move the file when finished, as the delegate method to choose beforehand does not work - NSString *filename = [[webDownload response] suggestedFilename]; - - [self download:download decideDestinationWithSuggestedFilename:filename window:nil completionHandler:^(NSURL *destination){ - if (destination) { - NSError *error = nil; - if ([destination checkResourceIsReachableAndReturnError:NULL]) - [[NSFileManager defaultManager] removeItemAtURL:destination error:NULL]; - if ([[NSFileManager defaultManager] moveItemAtURL:[webDownload fileURL] toURL:destination error:&error]) { - [webDownload setFileURL:destination]; - [self setStatus:BDSKDownloadStatusFinished forDownload:webDownload error:nil]; - } else { - [self setStatus:BDSKDownloadStatusFailed forDownload:webDownload error:error]; - } - } else { - NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:nil]; - [self setStatus:BDSKDownloadStatusFailed forDownload:webDownload error:error]; - } - }]; - } else { - [self setStatus:BDSKDownloadStatusFinished forDownload:webDownload error:nil]; - } -} - -- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error { - BDSKWebDownload *webDownload = [self webDownloadForDownload:download]; - [self setStatus:BDSKDownloadStatusFailed forDownload:webDownload error:error]; -} - -- (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response { - [[self webDownloadForDownload:download] setResponse:response]; -} - -// -download:decideDestinationWithSuggestedFilename: is not functional anymore -// it returns after the destination is already returned and downloading to disk has already started - -// both in NSURLDownloadDelegate and BDSKDownloadDelegate -- (void)download:(id)download didCreateDestination:(id)location { - if ([location isKindOfClass:[NSURL class]]) - [[self webDownloadForDownload:download] setFileURL:location]; - else if ([location isKindOfClass:[NSString class]]) - [[self webDownloadForDownload:download] setFileURL:[NSURL fileURLWithPath:location isDirectory:NO]]; -} - -- (BOOL)download:(NSURLDownload *)download shouldDecodeSourceDataOfMIMEType:(NSString *)encodingType { - return YES; -} - -#pragma mark BDSKDownload delegate protocol - -- (void)download:(BDSKDownload *)download didCompleteWithError:(NSError *)error { - BDSKWebDownload *webDownload = [self webDownloadForDownload:download]; - if (error) - [self setStatus:BDSKDownloadStatusFailed forDownload:webDownload error:error]; - else - [self setStatus:BDSKDownloadStatusFinished forDownload:webDownload error:nil]; -} - -- (void)download:(BDSKDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename completionHandler:(void (^)(NSURL *destinationURL, BOOL allowOverwrite))completionHandler { - [self download:download decideDestinationWithSuggestedFilename:filename window:nil completionHandler:^(NSURL *destination){ - completionHandler(destination, YES); - }]; -} - -#pragma mark WKDownload delegate protocol - -- (void)download:(WKDownload *)download didFailWithError:(NSError *)error resumeData:(NSData *)resumeData API_AVAILABLE(macos(11.3)) { - BDSKWebDownload *webDownload = [self webDownloadForDownload:download]; - [self setStatus:BDSKDownloadStatusFailed forDownload:webDownload error:error]; -} - -- (void)download:(WKDownload *)download decideDestinationUsingResponse:(NSURLResponse *)response suggestedFilename:(NSString *)filename completionHandler:(void (^)(NSURL * destination))completionHandler API_AVAILABLE(macos(11.3)) { - [self download:download decideDestinationWithSuggestedFilename:filename window:[[download webView] window] completionHandler:^(NSURL *destination){ - if (destination) { - if ([destination checkResourceIsReachableAndReturnError:NULL]) - [[NSFileManager defaultManager] removeItemAtURL:destination error:NULL]; - [[self webDownloadForDownload:download] setFileURL:destination]; - } - completionHandler(destination); - }]; -} - @end #pragma mark - @@ -358,3 +315,115 @@ } @end + +#pragma mark - + +@implementation BDSKDownloadDelegate + +- (instancetype)initWithDownloadManager:(BDSKDownloadManager *)aDownloadManager { + self = [super init]; + if (self) { + downloadManager = aDownloadManager; + } + return self; +} + +@end + +#pragma mark - + +@implementation BDSKWebDownloadDelegate + +- (void)downloadDidBegin:(NSURLDownload *)download { + [downloadManager addDownload:download]; +} + +// both in NSURLDownloadDelegate and WKDownloadDelegate +- (void)downloadDidFinish:(NSURLDownload *)download { + BDSKWebDownload *webDownload = [downloadManager webDownloadForDownload:download]; + + NSString *filename = [[webDownload response] suggestedFilename]; + + [downloadManager download:download decideDestinationWithSuggestedFilename:filename window:nil completionHandler:^(NSURL *destination){ + if (destination) { + NSError *error = nil; + if ([destination checkResourceIsReachableAndReturnError:NULL]) + [[NSFileManager defaultManager] removeItemAtURL:destination error:NULL]; + if ([[NSFileManager defaultManager] moveItemAtURL:[webDownload fileURL] toURL:destination error:&error]) { + [webDownload setFileURL:destination]; + [downloadManager download:download didFinishWithError:nil]; + } else { + [downloadManager download:download didFinishWithError:error]; + } + } else { + NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:nil]; + [downloadManager download:download didFinishWithError:error]; + } + }]; +} + +- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error { + [downloadManager download:download didFinishWithError:error]; +} + +- (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response { + [[downloadManager webDownloadForDownload:download] setResponse:response]; +} + +// -download:decideDestinationWithSuggestedFilename: is not functional anymore +// it returns after the destination is already returned and downloading to disk has already started + +- (void)download:(NSURLDownload *)download didCreateDestination:(NSString *)path { + [[downloadManager webDownloadForDownload:download] setFileURL:[NSURL fileURLWithPath:path isDirectory:NO]]; +} + +- (BOOL)download:(NSURLDownload *)download shouldDecodeSourceDataOfMIMEType:(NSString *)encodingType { + return YES; +} + +@end + +#pragma mark - + +@implementation BDSKWkDownloadDelegate + +- (void)downloadDidFinish:(WKDownload *)download API_AVAILABLE(macos(11.3)) { + [downloadManager download:download didFinishWithError:nil]; +} + +- (void)download:(WKDownload *)download didFailWithError:(NSError *)error resumeData:(NSData *)resumeData API_AVAILABLE(macos(11.3)) { + [downloadManager download:download didFinishWithError:error]; +} + +- (void)download:(WKDownload *)download decideDestinationUsingResponse:(NSURLResponse *)response suggestedFilename:(NSString *)filename completionHandler:(void (^)(NSURL * destination))completionHandler API_AVAILABLE(macos(11.3)) { + [downloadManager download:download decideDestinationWithSuggestedFilename:filename window:[[download webView] window] completionHandler:^(NSURL *destination){ + if (destination) { + if ([destination checkResourceIsReachableAndReturnError:NULL]) + [[NSFileManager defaultManager] removeItemAtURL:destination error:NULL]; + [[downloadManager webDownloadForDownload:download] setFileURL:destination]; + } + completionHandler(destination); + }]; +} + +@end + +#pragma mark - + +@implementation BDSKBdskDownloadDelegate + +- (void)download:(BDSKDownload *)download didCompleteWithError:(NSError *)error { + [downloadManager download:download didFinishWithError:error]; +} + +- (void)download:(BDSKDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename completionHandler:(void (^)(NSURL *destinationURL, BOOL allowOverwrite))completionHandler { + [downloadManager download:download decideDestinationWithSuggestedFilename:filename window:nil completionHandler:^(NSURL *destination){ + completionHandler(destination, YES); + }]; +} + +- (void)download:(BDSKDownload *)download didCreateDestination:(NSURL *)locationURL { + [[downloadManager webDownloadForDownload:download] setFileURL:locationURL]; +} + +@end Modified: trunk/bibdesk/BDSKTextImportController.m =================================================================== --- trunk/bibdesk/BDSKTextImportController.m 2025-09-07 09:27:09 UTC (rev 29538) +++ trunk/bibdesk/BDSKTextImportController.m 2025-09-07 17:09:12 UTC (rev 29539) @@ -101,6 +101,14 @@ @class WKDownload; #define WKNavigationActionPolicyDownload 2 #define WKNavigationResponsePolicyDownload 2 +@protocol WKDownloadDelegate <NSObject> +@end +@interface WKDownload : NSObject <NSProgressReporting> +@property (nonatomic, readonly, nullable) NSURLRequest *originalRequest; +@property (nonatomic, readonly, weak) WKWebView *webView; +@property (nonatomic, weak) id <WKDownloadDelegate> delegate; +- (void)cancel:(void(^)(NSData * resumeData))completionHandler; +@end @interface WKWebView (BDSKBigSurDeclarations) - (void)startDownloadUsingRequest:(NSURLRequest *)request completionHandler:(void(^)(WKDownload *))completionHandler; @end @@ -949,7 +957,7 @@ } else if (@available(macOS 11.3, *)) { decisionHandler(WKNavigationResponsePolicyDownload); } else { - BDSKDownload *aDownload = [[BDSKDownloader sharedDownloader] startFileDownloadWithRequest:[NSURLRequest requestWithURL:[[navigationResponse response] URL]] delegate:[BDSKDownloadManager sharedManager]]; + BDSKDownload *aDownload = [[BDSKDownloader sharedDownloader] startFileDownloadWithRequest:[NSURLRequest requestWithURL:[[navigationResponse response] URL]] delegate:[[BDSKDownloadManager sharedManager] bdskDownloadDelegate]]; [[BDSKDownloadManager sharedManager] addDownload:aDownload]; decisionHandler(WKNavigationResponsePolicyCancel); } @@ -962,7 +970,7 @@ else decisionHandler(WKNavigationActionPolicyAllow, preferences); } else if ([navigationAction navigationType] == WKNavigationTypeFormSubmitted || [[[navigationAction request] valueForHTTPHeaderField:@"Content-Disposition"] hasPrefix:@"attachment"]) { - BDSKDownload *aDownload = [[BDSKDownloader sharedDownloader] startFileDownloadWithRequest:[navigationAction request] delegate:[BDSKDownloadManager sharedManager]]; + BDSKDownload *aDownload = [[BDSKDownloader sharedDownloader] startFileDownloadWithRequest:[navigationAction request] delegate:[[BDSKDownloadManager sharedManager] bdskDownloadDelegate]]; [[BDSKDownloadManager sharedManager] addDownload:aDownload]; decisionHandler(WKNavigationActionPolicyCancel, preferences); } else { @@ -971,12 +979,12 @@ } - (void)webView:(WKWebView *)aWebView navigationAction:(WKNavigationAction *)navigationAction didBecomeDownload:(WKDownload *)aDownload API_AVAILABLE(macos(11.3)) { - [aDownload setDelegate:[BDSKDownloadManager sharedManager]]; + [aDownload setDelegate:[[BDSKDownloadManager sharedManager] wkDownloadDelegate]]; [[BDSKDownloadManager sharedManager] addDownload:aDownload]; } - (void)webView:(WKWebView *)aWebView navigationResponse:(WKNavigationResponse *)navigationResponse didBecomeDownload:(WKDownload *)aDownload API_AVAILABLE(macos(11.3)) { - [aDownload setDelegate:[BDSKDownloadManager sharedManager]]; + [aDownload setDelegate:[[BDSKDownloadManager sharedManager] wkDownloadDelegate]]; [[BDSKDownloadManager sharedManager] addDownload:aDownload]; } @@ -1077,11 +1085,11 @@ } else if (customMenuAction == BDSKWebViewCustomMenuActionDownloadLink) { if (@available(macOS 11.3, *)) { [webView startDownloadUsingRequest:request completionHandler:^(WKDownload *aDownload){ - [aDownload setDelegate:[BDSKDownloadManager sharedManager]]; + [aDownload setDelegate:[[BDSKDownloadManager sharedManager] wkDownloadDelegate]]; [[BDSKDownloadManager sharedManager] addDownload:aDownload]; }]; } else { - BDSKDownload *aDownload = [[BDSKDownloader sharedDownloader] startFileDownloadWithRequest:request delegate:[BDSKDownloadManager sharedManager]]; + BDSKDownload *aDownload = [[BDSKDownloader sharedDownloader] startFileDownloadWithRequest:request delegate:[[BDSKDownloadManager sharedManager] bdskDownloadDelegate]]; [[BDSKDownloadManager sharedManager] addDownload:aDownload]; } } else { Modified: trunk/bibdesk/BDSKWebView.m =================================================================== --- trunk/bibdesk/BDSKWebView.m 2025-09-07 09:27:09 UTC (rev 29538) +++ trunk/bibdesk/BDSKWebView.m 2025-09-07 17:09:12 UTC (rev 29539) @@ -95,7 +95,7 @@ [self setPolicyDelegate:webDelegate]; [self setUIDelegate:webDelegate]; [self setEditingDelegate:webDelegate]; - [self setDownloadDelegate:[BDSKDownloadManager sharedManager]]; + [self setDownloadDelegate:[[BDSKDownloadManager sharedManager] webDownloadDelegate]]; } return self; } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. _______________________________________________ Bibdesk-commit mailing list Bibdesk-commit@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/bibdesk-commit