diff options
| -rw-r--r-- | Factories/QSBookmarkProviderFactory.h | 10 | ||||
| -rw-r--r-- | Factories/QSBookmarkProviderFactory.m | 70 | ||||
| -rw-r--r-- | Protocols/QSBookmarkProvider.h | 20 | ||||
| -rw-r--r-- | Providers/QSDeliciousAPIProvider.h | 21 | ||||
| -rw-r--r-- | Providers/QSDeliciousAPIProvider.m | 360 | ||||
| -rw-r--r-- | Providers/QSLinkdingProvider.h | 9 | ||||
| -rw-r--r-- | Providers/QSLinkdingProvider.m | 348 | ||||
| -rw-r--r-- | QSDeliciousPlugIn_Source.h | 8 | ||||
| -rw-r--r-- | QSDeliciousPlugIn_Source.m | 349 | ||||
| -rw-r--r-- | Types/SocialSite.h | 10 | ||||
| -rw-r--r-- | Types/SocialSite.m | 70 |
11 files changed, 732 insertions, 543 deletions
diff --git a/Factories/QSBookmarkProviderFactory.h b/Factories/QSBookmarkProviderFactory.h index e2e694b..bc0ae79 100644 --- a/Factories/QSBookmarkProviderFactory.h +++ b/Factories/QSBookmarkProviderFactory.h @@ -5,20 +5,24 @@ // Factory for managing bookmark providers // -#import <Foundation/Foundation.h> #import "QSBookmarkProvider.h" #import "SocialSite.h" +#import <Foundation/Foundation.h> @interface QSBookmarkProviderFactory : NSObject -@property (nonatomic, strong, readonly) NSArray<id<QSBookmarkProvider>> *providers; +@property(nonatomic, strong, readonly) + NSArray<id<QSBookmarkProvider>> *providers; + (instancetype)sharedFactory; /** * Get the appropriate provider for the given site configuration */ -- (id<QSBookmarkProvider>)providerForSite:(SocialSite)site username:(NSString *)username password:(NSString *)password host:(NSString *)host; +- (id<QSBookmarkProvider>)providerForSite:(SocialSite)site + username:(NSString *)username + password:(NSString *)password + host:(NSString *)host; /** * Get all available providers diff --git a/Factories/QSBookmarkProviderFactory.m b/Factories/QSBookmarkProviderFactory.m index 75fd0e2..56b4146 100644 --- a/Factories/QSBookmarkProviderFactory.m +++ b/Factories/QSBookmarkProviderFactory.m @@ -9,54 +9,62 @@ #import "SocialSite.h" @interface QSBookmarkProviderFactory () -@property (nonatomic, strong, readwrite) NSArray<id<QSBookmarkProvider>> *providers; +@property(nonatomic, strong, readwrite) + NSArray<id<QSBookmarkProvider>> *providers; @end @implementation QSBookmarkProviderFactory + (instancetype)sharedFactory { - static QSBookmarkProviderFactory *sharedInstance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedInstance = [[self alloc] init]; - }); - return sharedInstance; + static QSBookmarkProviderFactory *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return sharedInstance; } - (instancetype)init { - self = [super init]; - if (self) { - [self setupProviders]; - } - return self; + self = [super init]; + if (self) { + [self setupProviders]; + } + return self; } - (void)setupProviders { - NSMutableArray *mutableProviders = [NSMutableArray array]; - - // Create Delicious / Pinboard API providers for each supported site - QSDeliciousAPIProvider *deliciousAPIProvider = [[QSDeliciousAPIProvider alloc] init]; - - // Create Linkding provider - QSLinkdingProvider *linkdingProvider = [[QSLinkdingProvider alloc] init]; - - [mutableProviders addObject:deliciousAPIProvider]; - [mutableProviders addObject:linkdingProvider]; - - self.providers = [mutableProviders copy]; + NSMutableArray *mutableProviders = [NSMutableArray array]; + + // Create Delicious / Pinboard API providers for each supported site + QSDeliciousAPIProvider *deliciousAPIProvider = + [[QSDeliciousAPIProvider alloc] init]; + + // Create Linkding provider + QSLinkdingProvider *linkdingProvider = [[QSLinkdingProvider alloc] init]; + + [mutableProviders addObject:deliciousAPIProvider]; + [mutableProviders addObject:linkdingProvider]; + + self.providers = [mutableProviders copy]; } -- (id<QSBookmarkProvider>)providerForSite:(SocialSite)site username:(NSString *)username password:(NSString *)password host:(NSString *)host { - for (id<QSBookmarkProvider> provider in self.providers) { - if ([provider canHandleSite:site username:username password:password host:host]) { - return provider; - } +- (id<QSBookmarkProvider>)providerForSite:(SocialSite)site + username:(NSString *)username + password:(NSString *)password + host:(NSString *)host { + for (id<QSBookmarkProvider> provider in self.providers) { + if ([provider canHandleSite:site + username:username + password:password + host:host]) { + return provider; } - return nil; + } + return nil; } - (NSArray<id<QSBookmarkProvider>> *)allProviders { - return self.providers; + return self.providers; } @end diff --git a/Protocols/QSBookmarkProvider.h b/Protocols/QSBookmarkProvider.h index 9131e3c..48e1417 100644 --- a/Protocols/QSBookmarkProvider.h +++ b/Protocols/QSBookmarkProvider.h @@ -5,8 +5,8 @@ // Protocol for social bookmark providers // -#import <Foundation/Foundation.h> #import "SocialSite.h" +#import <Foundation/Foundation.h> @class QSObject; @@ -16,18 +16,30 @@ /** * Check if this provider can handle the given site configuration */ -- (BOOL)canHandleSite:(SocialSite)site username:(NSString *)username password:(NSString *)password host:(NSString *)host; +- (BOOL)canHandleSite:(SocialSite)site + username:(NSString *)username + password:(NSString *)password + host:(NSString *)host; /** * Fetch bookmarks for the given configuration * Returns an NSArray of QSObject instances */ -- (NSArray *)fetchBookmarksForSite:(SocialSite)site username:(NSString *)username password:(NSString *)password identifier:(NSString *)identifier host:(NSString *)host includeTags:(BOOL)includeTags; +- (NSArray *)fetchBookmarksForSite:(SocialSite)site + username:(NSString *)username + password:(NSString *)password + identifier:(NSString *)identifier + host:(NSString *)host + includeTags:(BOOL)includeTags; @optional /** * Get bookmarks for a specific tag (used for child loading) */ -- (NSArray *)fetchBookmarksForTag:(NSString *)tag site:(SocialSite)site username:(NSString *)username password:(NSString *)password host:(NSString *)host; +- (NSArray *)fetchBookmarksForTag:(NSString *)tag + site:(SocialSite)site + username:(NSString *)username + password:(NSString *)password + host:(NSString *)host; @end diff --git a/Providers/QSDeliciousAPIProvider.h b/Providers/QSDeliciousAPIProvider.h index 7979812..310b00b 100644 --- a/Providers/QSDeliciousAPIProvider.h +++ b/Providers/QSDeliciousAPIProvider.h @@ -6,17 +6,26 @@ // Used by Delicious, Magnolia, and Pinboard // -#import <Foundation/Foundation.h> #import "QSBookmarkProvider.h" +#import <Foundation/Foundation.h> -@interface QSDeliciousAPIProvider : NSObject <QSBookmarkProvider, NSXMLParserDelegate> +@interface QSDeliciousAPIProvider + : NSObject <QSBookmarkProvider, NSXMLParserDelegate> -@property (nonatomic, strong) NSMutableArray *posts; +@property(nonatomic, strong) NSMutableArray *posts; // Subclasses can override these methods - (NSString *)apiURLForSite:(SocialSite)site andHost:(NSString *)host; -- (NSURL *)requestURLForSite:(SocialSite)site username:(NSString *)username password:(NSString *)password host:(NSString *)host; -- (NSData *)cachedBookmarkDataForSite:(SocialSite)site username:(NSString *)username host:(NSString *)host; -- (void)cacheBookmarkData:(NSData *)data forSite:(SocialSite)site username:(NSString *)username host:(NSString *)host; +- (NSURL *)requestURLForSite:(SocialSite)site + username:(NSString *)username + password:(NSString *)password + host:(NSString *)host; +- (NSData *)cachedBookmarkDataForSite:(SocialSite)site + username:(NSString *)username + host:(NSString *)host; +- (void)cacheBookmarkData:(NSData *)data + forSite:(SocialSite)site + username:(NSString *)username + host:(NSString *)host; @end diff --git a/Providers/QSDeliciousAPIProvider.m b/Providers/QSDeliciousAPIProvider.m index 0c37a43..03991c1 100644 --- a/Providers/QSDeliciousAPIProvider.m +++ b/Providers/QSDeliciousAPIProvider.m @@ -4,202 +4,264 @@ // #import "QSDeliciousAPIProvider.h" -#import "SocialSite.h" #import "Constants.h" +#import "SocialSite.h" #import <QSCore/QSCore.h> @implementation QSDeliciousAPIProvider -- (BOOL)canHandleSite:(SocialSite)site username:(NSString *)username password:(NSString *)password host:(NSString *)host { - return ( - site == SocialSiteDelicious || - site == SocialSiteMagnolia || - site == SocialSitePinboard || - site == SocialSiteSelfHostedDeliciousCompatible - ) && - username.length > 0 && - password.length > 0 && - (site != SocialSiteSelfHostedDeliciousCompatible || host.length > 0); +- (BOOL)canHandleSite:(SocialSite)site + username:(NSString *)username + password:(NSString *)password + host:(NSString *)host { + return (site == SocialSiteDelicious || site == SocialSiteMagnolia || + site == SocialSitePinboard || + site == SocialSiteSelfHostedDeliciousCompatible) && + username.length > 0 && password.length > 0 && + (site != SocialSiteSelfHostedDeliciousCompatible || host.length > 0); } - (NSString *)apiURLForSite:(SocialSite)site andHost:(NSString *)host { - switch (site) { - case SocialSiteDelicious: - return @"api.del.icio.us/v1"; - case SocialSiteMagnolia: - return @"ma.gnolia.com/api/mirrord/v1"; - case SocialSitePinboard: - return @"https://api.pinboard.in/v1"; - case SocialSiteSelfHostedDeliciousCompatible: - return [NSString stringWithFormat:@"%@/v1", host]; - default: - return nil; - } + switch (site) { + case SocialSiteDelicious: + return @"api.del.icio.us/v1"; + case SocialSiteMagnolia: + return @"ma.gnolia.com/api/mirrord/v1"; + case SocialSitePinboard: + return @"https://api.pinboard.in/v1"; + case SocialSiteSelfHostedDeliciousCompatible: + return [NSString stringWithFormat:@"%@/v1", host]; + default: + return nil; + } } - (BOOL)usesAuthToken:(SocialSite)site { - switch (site) { - case SocialSitePinboard: - case SocialSiteSelfHostedDeliciousCompatible: - return YES; - default: - return NO; - } + switch (site) { + case SocialSitePinboard: + case SocialSiteSelfHostedDeliciousCompatible: + return YES; + default: + return NO; + } } -- (NSURL *)requestURLForSite:(SocialSite)site username:(NSString *)username password:(NSString *)password host:(NSString *)host { - NSString *apiURL = [self apiURLForSite:site andHost: host]; - if (!apiURL) return nil; - +- (NSURL *)requestURLForSite:(SocialSite)site + username:(NSString *)username + password:(NSString *)password + host:(NSString *)host { + NSString *apiURL = [self apiURLForSite:site andHost:host]; + if (!apiURL) + return nil; + NSString *urlString; if ([self usesAuthToken:site]) { // Pinboard and pinboard compatible sites require an // auth token rather than a password. - urlString = [NSString stringWithFormat:@"%@/posts/all?auth_token=%@", apiURL, password]; + urlString = [NSString + stringWithFormat:@"%@/posts/all?auth_token=%@", apiURL, password]; } else { - urlString = [NSString stringWithFormat:@"https://%@:%@@%@/posts/all?", username, password, apiURL]; + urlString = [NSString stringWithFormat:@"https://%@:%@@%@/posts/all?", + username, password, apiURL]; } - return [NSURL URLWithString:urlString]; + return [NSURL URLWithString:urlString]; } -# pragma mark - Cache +#pragma mark - Cache + +- (NSString *)cachePathForSite:(SocialSite)site + username:(NSString *)username + host:(NSString *)host + create:(BOOL)create { -- (NSString *)cachePathForSite:(SocialSite)site username:(NSString *)username host:(NSString *)host create:(BOOL) create { - NSString *siteURL = [SocialSiteHelper cacheKeyForSite:site]; - NSString *safeHost = [[host componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]] componentsJoinedByString:@"-"]; - return [QSApplicationSupportSubPath([NSString stringWithFormat:@"Caches/%@/", siteURL], create) stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-%@.xml", safeHost, username]]; + NSString *safeHost = + [[host componentsSeparatedByCharactersInSet:[[NSCharacterSet + alphanumericCharacterSet] + invertedSet]] + componentsJoinedByString:@"-"]; + return [QSApplicationSupportSubPath( + [NSString stringWithFormat:@"Caches/%@/", siteURL], create) + stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-%@.xml", + safeHost, + username]]; } - -- (NSData *)cachedBookmarkDataForSite:(SocialSite)site username:(NSString *)username host:(NSString *)host { - NSString *cachePath = [self cachePathForSite: site username: username host: host create: NO]; +- (NSData *)cachedBookmarkDataForSite:(SocialSite)site + username:(NSString *)username + host:(NSString *)host { + NSString *cachePath = [self cachePathForSite:site + username:username + host:host + create:NO]; return [NSData dataWithContentsOfFile:cachePath]; } -- (void)cacheBookmarkData:(NSData *)data forSite:(SocialSite)site username:(NSString *)username host:(NSString *)host { - NSString *cachePath = [self cachePathForSite: site username: username host: host create: YES]; - [data writeToFile:cachePath atomically:NO]; +- (void)cacheBookmarkData:(NSData *)data + forSite:(SocialSite)site + username:(NSString *)username + host:(NSString *)host { + NSString *cachePath = [self cachePathForSite:site + username:username + host:host + create:YES]; + [data writeToFile:cachePath atomically:NO]; } -- (NSArray *)fetchBookmarksForSite:(SocialSite)site username:(NSString *)username password:(NSString *)password identifier:(NSString *)identifier host:(NSString *)host includeTags:(BOOL)includeTags { - - // Try cached data first - NSData *data = [self cachedBookmarkDataForSite:site username:username host:host]; +- (NSArray *)fetchBookmarksForSite:(SocialSite)site + username:(NSString *)username + password:(NSString *)password + identifier:(NSString *)identifier + host:(NSString *)host + includeTags:(BOOL)includeTags { - // If no cached data, fetch from API - if (![data length]) { - NSURL *requestURL = [self requestURLForSite:site username:username password:password host:host]; - if (!requestURL) return @[]; - - NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:requestURL - cachePolicy:NSURLRequestUseProtocolCachePolicy - timeoutInterval:60.0]; - [theRequest setHTTPMethod:@"GET"]; - [theRequest setValue:@"text/xml" forHTTPHeaderField:@"Content-type"]; - [theRequest setValue:@"Quicksilver (Blacktree,MacOSX)" forHTTPHeaderField:@"User-Agent"]; - - NSError *error = nil; - data = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:nil error:&error]; - - if (error) { - NSLog(@"Error fetching bookmarks: %@", error.localizedDescription); - return @[]; - } - - // Cache the data - [self cacheBookmarkData:data forSite:site username:username host:host]; + // Try cached data first + NSData *data = [self cachedBookmarkDataForSite:site + username:username + host:host]; + + // If no cached data, fetch from API + if (![data length]) { + NSURL *requestURL = [self requestURLForSite:site + username:username + password:password + host:host]; + if (!requestURL) + return @[]; + + NSMutableURLRequest *theRequest = + [NSMutableURLRequest requestWithURL:requestURL + cachePolicy:NSURLRequestUseProtocolCachePolicy + timeoutInterval:60.0]; + [theRequest setHTTPMethod:@"GET"]; + [theRequest setValue:@"text/xml" forHTTPHeaderField:@"Content-type"]; + [theRequest setValue:@"Quicksilver (Blacktree,MacOSX)" + forHTTPHeaderField:@"User-Agent"]; + + NSError *error = nil; + data = [NSURLConnection sendSynchronousRequest:theRequest + returningResponse:nil + error:&error]; + + if (error) { + NSLog(@"Error fetching bookmarks: %@", error.localizedDescription); + return @[]; } - - // Parse XML data - NSXMLParser *postParser = [[NSXMLParser alloc] initWithData:data]; - [postParser setDelegate:self]; - - self.posts = [NSMutableArray arrayWithCapacity:1]; - [postParser parse]; - - NSMutableArray *objects = [NSMutableArray arrayWithCapacity:1]; - NSMutableSet *tagSet = [NSMutableSet set]; - - // Create bookmark objects - for (NSDictionary *post in self.posts) { - QSObject *newObject = [self objectForPost:post]; - if (newObject) { - [objects addObject:newObject]; - - // Collect tags if requested - if (includeTags) { - NSString *tagString = [post objectForKey:@"tag"]; - if (tagString.length > 0) { - [tagSet addObjectsFromArray:[tagString componentsSeparatedByString:@" "]]; - } - } + + // Cache the data + [self cacheBookmarkData:data forSite:site username:username host:host]; + } + + // Parse XML data + NSXMLParser *postParser = [[NSXMLParser alloc] initWithData:data]; + [postParser setDelegate:self]; + + self.posts = [NSMutableArray arrayWithCapacity:1]; + [postParser parse]; + + NSMutableArray *objects = [NSMutableArray arrayWithCapacity:1]; + NSMutableSet *tagSet = [NSMutableSet set]; + + // Create bookmark objects + for (NSDictionary *post in self.posts) { + QSObject *newObject = [self objectForPost:post]; + if (newObject) { + [objects addObject:newObject]; + + // Collect tags if requested + if (includeTags) { + NSString *tagString = [post objectForKey:@"tag"]; + if (tagString.length > 0) { + [tagSet + addObjectsFromArray:[tagString componentsSeparatedByString:@" "]]; } + } } - - // Create tag objects if requested - if (includeTags) { - for (NSString *tag in tagSet) { - if (tag.length > 0) { - QSObject *tagObject = [QSObject makeObjectWithIdentifier:[NSString stringWithFormat:@"[%@ tag]:%@", [SocialSiteHelper displayNameForSite:site], tag]]; + } - [tagObject setObject:tag forType:kTagType]; - [tagObject setObject:@(site) forMeta:kTagSiteField]; - [tagObject setObject:username forMeta:kTagUsernameField]; - [tagObject setObject:host forMeta:kTagHostField]; - [tagObject setObject:identifier forMeta:kTagIdentifierField]; - [tagObject setName:tag]; - [tagObject setPrimaryType:kTagType]; - [objects addObject:tagObject]; - } - } + // Create tag objects if requested + if (includeTags) { + for (NSString *tag in tagSet) { + if (tag.length > 0) { + QSObject *tagObject = [QSObject + makeObjectWithIdentifier: + [NSString + stringWithFormat:@"[%@ tag]:%@", + [SocialSiteHelper displayNameForSite:site], + tag]]; + + [tagObject setObject:tag forType:kTagType]; + [tagObject setObject:@(site) forMeta:kTagSiteField]; + [tagObject setObject:username forMeta:kTagUsernameField]; + [tagObject setObject:host forMeta:kTagHostField]; + [tagObject setObject:identifier forMeta:kTagIdentifierField]; + [tagObject setName:tag]; + [tagObject setPrimaryType:kTagType]; + [objects addObject:tagObject]; + } } - - return objects; + } + + return objects; } -- (NSArray *)fetchBookmarksForTag:(NSString *)tag site:(SocialSite)site username:(NSString *)username password:(NSString *)password host:(NSString *)host { - NSData *data = [self cachedBookmarkDataForSite:site username:username host:host]; - if (!data) return @[]; - - NSXMLParser *postParser = [[NSXMLParser alloc] initWithData:data]; - [postParser setDelegate:self]; - self.posts = [NSMutableArray arrayWithCapacity:1]; - [postParser parse]; - - NSMutableArray *objects = [NSMutableArray arrayWithCapacity:1]; - - for (NSDictionary *post in self.posts) { - NSString *postTags = [post objectForKey:@"tag"]; - if ([postTags rangeOfString:tag].location != NSNotFound) { - QSObject *newObject = [self objectForPost:post]; - if (newObject) { - [objects addObject:newObject]; - } - } +- (NSArray *)fetchBookmarksForTag:(NSString *)tag + site:(SocialSite)site + username:(NSString *)username + password:(NSString *)password + host:(NSString *)host { + NSData *data = [self cachedBookmarkDataForSite:site + username:username + host:host]; + if (!data) + return @[]; + + NSXMLParser *postParser = [[NSXMLParser alloc] initWithData:data]; + [postParser setDelegate:self]; + self.posts = [NSMutableArray arrayWithCapacity:1]; + [postParser parse]; + + NSMutableArray *objects = [NSMutableArray arrayWithCapacity:1]; + + for (NSDictionary *post in self.posts) { + NSString *postTags = [post objectForKey:@"tag"]; + if ([postTags rangeOfString:tag].location != NSNotFound) { + QSObject *newObject = [self objectForPost:post]; + if (newObject) { + [objects addObject:newObject]; + } } - - return objects; + } + + return objects; } - (QSObject *)objectForPost:(NSDictionary *)post { - QSObject *newObject = [QSObject makeObjectWithIdentifier:[post objectForKey:@"hash"]]; - [newObject setObject:[post objectForKey:@"href"] forType:QSURLType]; - [newObject setName:[post objectForKey:@"description"]]; - [newObject setDetails:[post objectForKey:@"extended"]]; - [newObject setPrimaryType:QSURLType]; - return newObject; + QSObject *newObject = + [QSObject makeObjectWithIdentifier:[post objectForKey:@"hash"]]; + [newObject setObject:[post objectForKey:@"href"] forType:QSURLType]; + [newObject setName:[post objectForKey:@"description"]]; + [newObject setDetails:[post objectForKey:@"extended"]]; + [newObject setPrimaryType:QSURLType]; + return newObject; } #pragma mark - NSXMLParserDelegate -- (void)parser:(NSXMLParser*)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { - if ([elementName isEqualToString:@"post"] && attributeDict) { - [self.posts addObject:attributeDict]; - } +- (void)parser:(NSXMLParser *)parser + didStartElement:(NSString *)elementName + namespaceURI:(NSString *)namespaceURI + qualifiedName:(NSString *)qName + attributes:(NSDictionary *)attributeDict { + if ([elementName isEqualToString:@"post"] && attributeDict) { + [self.posts addObject:attributeDict]; + } } -- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { - // Implementation if needed +- (void)parser:(NSXMLParser *)parser + didEndElement:(NSString *)elementName + namespaceURI:(NSString *)namespaceURI + qualifiedName:(NSString *)qName { + // Implementation if needed } @end diff --git a/Providers/QSLinkdingProvider.h b/Providers/QSLinkdingProvider.h index 28807e8..0537a0c 100644 --- a/Providers/QSLinkdingProvider.h +++ b/Providers/QSLinkdingProvider.h @@ -5,12 +5,15 @@ // Provider for Linkding API (JSON-based with API key) // -#import <Foundation/Foundation.h> #import "QSBookmarkProvider.h" +#import <Foundation/Foundation.h> @interface QSLinkdingProvider : NSObject <QSBookmarkProvider> -- (NSData *)cachedBookmarkDataForHost:(NSString *)host username:(NSString *)username; -- (void)cacheBookmarkData:(NSData *)data forHost:(NSString *)host username:(NSString *)username; +- (NSData *)cachedBookmarkDataForHost:(NSString *)host + username:(NSString *)username; +- (void)cacheBookmarkData:(NSData *)data + forHost:(NSString *)host + username:(NSString *)username; @end
\ No newline at end of file diff --git a/Providers/QSLinkdingProvider.m b/Providers/QSLinkdingProvider.m index c7bf33f..378ae6f 100644 --- a/Providers/QSLinkdingProvider.m +++ b/Providers/QSLinkdingProvider.m @@ -4,178 +4,230 @@ // #import "QSLinkdingProvider.h" -#import "SocialSite.h" #import "Constants.h" +#import "SocialSite.h" #import <QSCore/QSCore.h> @implementation QSLinkdingProvider -- (BOOL)canHandleSite:(SocialSite)site username:(NSString *)username password:(NSString *)password host:(NSString *)host { - return (site == SocialSiteLinkding) && - username.length > 0 && - password.length > 0 && // password is API token for Linkding - host.length > 0; +- (BOOL)canHandleSite:(SocialSite)site + username:(NSString *)username + password:(NSString *)password + host:(NSString *)host { + return (site == SocialSiteLinkding) && username.length > 0 && + password.length > 0 && // password is API token for Linkding + host.length > 0; } -- (NSData *)cachedBookmarkDataForHost:(NSString *)host username:(NSString *)username { - // Create a safe filename from host - NSString *safeHost = [[host componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]] componentsJoinedByString:@"-"]; - NSString *cachePath = [QSApplicationSupportSubPath([NSString stringWithFormat:@"Caches/linkding/"], NO) stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-%@.json", safeHost, username]]; - return [NSData dataWithContentsOfFile:cachePath]; +- (NSData *)cachedBookmarkDataForHost:(NSString *)host + username:(NSString *)username { + // Create a safe filename from host + NSString *safeHost = + [[host componentsSeparatedByCharactersInSet:[[NSCharacterSet + alphanumericCharacterSet] + invertedSet]] + componentsJoinedByString:@"-"]; + NSString *cachePath = [QSApplicationSupportSubPath( + [NSString stringWithFormat:@"Caches/linkding/"], NO) + stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-%@.json", + safeHost, + username]]; + return [NSData dataWithContentsOfFile:cachePath]; } -- (void)cacheBookmarkData:(NSData *)data forHost:(NSString *)host username:(NSString *)username { - // Create a safe filename from host - NSString *safeHost = [[host componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]] componentsJoinedByString:@"-"]; - NSString *cachePath = [QSApplicationSupportSubPath([NSString stringWithFormat:@"Caches/linkding/"], YES) stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-%@.json", safeHost, username]]; - [data writeToFile:cachePath atomically:NO]; +- (void)cacheBookmarkData:(NSData *)data + forHost:(NSString *)host + username:(NSString *)username { + // Create a safe filename from host + NSString *safeHost = + [[host componentsSeparatedByCharactersInSet:[[NSCharacterSet + alphanumericCharacterSet] + invertedSet]] + componentsJoinedByString:@"-"]; + NSString *cachePath = [QSApplicationSupportSubPath( + [NSString stringWithFormat:@"Caches/linkding/"], YES) + stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-%@.json", + safeHost, + username]]; + [data writeToFile:cachePath atomically:NO]; } -- (NSArray *)fetchBookmarksForSite:(SocialSite)site username:(NSString *)username password:(NSString *)password identifier:(NSString *)identifier host:(NSString *)host includeTags:(BOOL)includeTags { - - if (![self canHandleSite:site username:username password:password host:host]) { - return @[]; +- (NSArray *)fetchBookmarksForSite:(SocialSite)site + username:(NSString *)username + password:(NSString *)password + identifier:(NSString *)identifier + host:(NSString *)host + includeTags:(BOOL)includeTags { + + if (![self canHandleSite:site + username:username + password:password + host:host]) { + return @[]; + } + + // Try cached data first + NSData *data = [self cachedBookmarkDataForHost:host username:username]; + + // If no cached data, fetch from API + if (![data length]) { + // Construct Linkding API URL + NSString *baseURL = host; + if (![baseURL hasPrefix:@"http://"] && ![baseURL hasPrefix:@"https://"]) { + baseURL = [NSString stringWithFormat:@"https://%@", baseURL]; } - - // Try cached data first - NSData *data = [self cachedBookmarkDataForHost:host username:username]; - - // If no cached data, fetch from API - if (![data length]) { - // Construct Linkding API URL - NSString *baseURL = host; - if (![baseURL hasPrefix:@"http://"] && ![baseURL hasPrefix:@"https://"]) { - baseURL = [NSString stringWithFormat:@"https://%@", baseURL]; - } - if ([baseURL hasSuffix:@"/"]) { - baseURL = [baseURL substringToIndex:[baseURL length] - 1]; - } - - NSString *urlString = [NSString stringWithFormat:@"%@/api/bookmarks/", baseURL]; - NSURL *requestURL = [NSURL URLWithString:urlString]; - - if (!requestURL) { - NSLog(@"Invalid Linkding host URL: %@", host); - return @[]; - } - - NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:requestURL - cachePolicy:NSURLRequestUseProtocolCachePolicy - timeoutInterval:60.0]; - [theRequest setHTTPMethod:@"GET"]; - [theRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"]; - [theRequest setValue:[NSString stringWithFormat:@"Token %@", password] forHTTPHeaderField:@"Authorization"]; - [theRequest setValue:@"Quicksilver (Blacktree,MacOSX)" forHTTPHeaderField:@"User-Agent"]; - - NSError *error = nil; - data = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:nil error:&error]; - - if (error) { - NSLog(@"Error fetching Linkding bookmarks: %@", error.localizedDescription); - return @[]; - } - - // Cache the data - [self cacheBookmarkData:data forHost:host username:username]; + if ([baseURL hasSuffix:@"/"]) { + baseURL = [baseURL substringToIndex:[baseURL length] - 1]; } - - // Parse JSON data - NSError *jsonError = nil; - NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; - - if (jsonError) { - NSLog(@"Error parsing Linkding JSON: %@", jsonError.localizedDescription); - return @[]; + + NSString *urlString = + [NSString stringWithFormat:@"%@/api/bookmarks/", baseURL]; + NSURL *requestURL = [NSURL URLWithString:urlString]; + + if (!requestURL) { + NSLog(@"Invalid Linkding host URL: %@", host); + return @[]; } - - NSArray *results = [jsonResponse objectForKey:@"results"]; - if (!results || ![results isKindOfClass:[NSArray class]]) { - NSLog(@"Invalid Linkding response format"); - return @[]; + + NSMutableURLRequest *theRequest = + [NSMutableURLRequest requestWithURL:requestURL + cachePolicy:NSURLRequestUseProtocolCachePolicy + timeoutInterval:60.0]; + [theRequest setHTTPMethod:@"GET"]; + [theRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"]; + [theRequest setValue:[NSString stringWithFormat:@"Token %@", password] + forHTTPHeaderField:@"Authorization"]; + [theRequest setValue:@"Quicksilver (Blacktree,MacOSX)" + forHTTPHeaderField:@"User-Agent"]; + + NSError *error = nil; + data = [NSURLConnection sendSynchronousRequest:theRequest + returningResponse:nil + error:&error]; + + if (error) { + NSLog(@"Error fetching Linkding bookmarks: %@", + error.localizedDescription); + return @[]; } - - NSMutableArray *objects = [NSMutableArray arrayWithCapacity:1]; - NSMutableSet *tagSet = [NSMutableSet set]; - - // Create bookmark objects - for (NSDictionary *bookmark in results) { - QSObject *newObject = [self objectForLinkdingBookmark:bookmark]; - if (newObject) { - [objects addObject:newObject]; - - // Collect tags if requested - if (includeTags) { - NSArray *tags = [bookmark objectForKey:@"tag_names"]; - if (tags && [tags isKindOfClass:[NSArray class]]) { - [tagSet addObjectsFromArray:tags]; - } - } + + // Cache the data + [self cacheBookmarkData:data forHost:host username:username]; + } + + // Parse JSON data + NSError *jsonError = nil; + NSDictionary *jsonResponse = + [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + + if (jsonError) { + NSLog(@"Error parsing Linkding JSON: %@", jsonError.localizedDescription); + return @[]; + } + + NSArray *results = [jsonResponse objectForKey:@"results"]; + if (!results || ![results isKindOfClass:[NSArray class]]) { + NSLog(@"Invalid Linkding response format"); + return @[]; + } + + NSMutableArray *objects = [NSMutableArray arrayWithCapacity:1]; + NSMutableSet *tagSet = [NSMutableSet set]; + + // Create bookmark objects + for (NSDictionary *bookmark in results) { + QSObject *newObject = [self objectForLinkdingBookmark:bookmark]; + if (newObject) { + [objects addObject:newObject]; + + // Collect tags if requested + if (includeTags) { + NSArray *tags = [bookmark objectForKey:@"tag_names"]; + if (tags && [tags isKindOfClass:[NSArray class]]) { + [tagSet addObjectsFromArray:tags]; } + } } - - // Create tag objects if requested - if (includeTags) { - for (NSString *tag in tagSet) { - if (tag.length > 0) { - QSObject *tagObject = [QSObject makeObjectWithIdentifier:[NSString stringWithFormat:@"[Linkding tag]:%@", tag]]; - [tagObject setObject:tag forType:kTagType]; - [tagObject setObject:@(site) forMeta:kTagSiteField]; - [tagObject setObject:username forMeta:kTagUsernameField]; - [tagObject setObject:host forMeta:kTagHostField]; - // We need the identifier to be able to fetch the keychain password - [tagObject setObject:identifier forMeta:kTagIdentifierField]; - [tagObject setName:tag]; - [tagObject setPrimaryType:kTagType]; - [objects addObject:tagObject]; - } - } + } + + // Create tag objects if requested + if (includeTags) { + for (NSString *tag in tagSet) { + if (tag.length > 0) { + QSObject *tagObject = [QSObject + makeObjectWithIdentifier:[NSString + stringWithFormat:@"[Linkding tag]:%@", + tag]]; + [tagObject setObject:tag forType:kTagType]; + [tagObject setObject:@(site) forMeta:kTagSiteField]; + [tagObject setObject:username forMeta:kTagUsernameField]; + [tagObject setObject:host forMeta:kTagHostField]; + // We need the identifier to be able to fetch the keychain password + [tagObject setObject:identifier forMeta:kTagIdentifierField]; + [tagObject setName:tag]; + [tagObject setPrimaryType:kTagType]; + [objects addObject:tagObject]; + } } - - return objects; + } + + return objects; } -- (NSArray *)fetchBookmarksForTag:(NSString *)tag site:(SocialSite)site username:(NSString *)username password:(NSString *)password host:(NSString *)host { - NSData *data = [self cachedBookmarkDataForHost:host username:username]; - if (!data) return @[]; - - NSError *jsonError; - NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; - - if (jsonError) return @[]; - - NSArray *results = [jsonResponse objectForKey:@"results"]; - if (!results || ![results isKindOfClass:[NSArray class]]) return @[]; - - NSMutableArray *objects = [NSMutableArray arrayWithCapacity:1]; - - for (NSDictionary *bookmark in results) { - NSArray *tags = [bookmark objectForKey:@"tag_names"]; - if (tags && [tags isKindOfClass:[NSArray class]] && [tags containsObject:tag]) { - QSObject *newObject = [self objectForLinkdingBookmark:bookmark]; - if (newObject) { - [objects addObject:newObject]; - } - } +- (NSArray *)fetchBookmarksForTag:(NSString *)tag + site:(SocialSite)site + username:(NSString *)username + password:(NSString *)password + host:(NSString *)host { + NSData *data = [self cachedBookmarkDataForHost:host username:username]; + if (!data) + return @[]; + + NSError *jsonError; + NSDictionary *jsonResponse = + [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + + if (jsonError) + return @[]; + + NSArray *results = [jsonResponse objectForKey:@"results"]; + if (!results || ![results isKindOfClass:[NSArray class]]) + return @[]; + + NSMutableArray *objects = [NSMutableArray arrayWithCapacity:1]; + + for (NSDictionary *bookmark in results) { + NSArray *tags = [bookmark objectForKey:@"tag_names"]; + if (tags && [tags isKindOfClass:[NSArray class]] && + [tags containsObject:tag]) { + QSObject *newObject = [self objectForLinkdingBookmark:bookmark]; + if (newObject) { + [objects addObject:newObject]; + } } - - return objects; + } + + return objects; } - (QSObject *)objectForLinkdingBookmark:(NSDictionary *)bookmark { - NSNumber *bookmarkId = [bookmark objectForKey:@"id"]; - NSString *url = [bookmark objectForKey:@"url"]; - NSString *title = [bookmark objectForKey:@"title"]; - NSString *description = [bookmark objectForKey:@"description"]; - - if (!bookmarkId || !url) return nil; - - QSObject *newObject = [QSObject makeObjectWithIdentifier:[NSString stringWithFormat:@"linkding-%@", bookmarkId]]; - [newObject setObject:url forType:QSURLType]; - [newObject setName:title.length > 0 ? title : url]; - [newObject setDetails:description.length > 0 ? description : @""]; - [newObject setPrimaryType:QSURLType]; - - return newObject; + NSNumber *bookmarkId = [bookmark objectForKey:@"id"]; + NSString *url = [bookmark objectForKey:@"url"]; + NSString *title = [bookmark objectForKey:@"title"]; + NSString *description = [bookmark objectForKey:@"description"]; + + if (!bookmarkId || !url) + return nil; + + QSObject *newObject = [QSObject + makeObjectWithIdentifier:[NSString stringWithFormat:@"linkding-%@", + bookmarkId]]; + [newObject setObject:url forType:QSURLType]; + [newObject setName:title.length > 0 ? title : url]; + [newObject setDetails:description.length > 0 ? description : @""]; + [newObject setPrimaryType:QSURLType]; + + return newObject; } @end diff --git a/QSDeliciousPlugIn_Source.h b/QSDeliciousPlugIn_Source.h index 56456c5..26d613c 100644 --- a/QSDeliciousPlugIn_Source.h +++ b/QSDeliciousPlugIn_Source.h @@ -6,18 +6,18 @@ // Copyright __MyCompanyName__ 2004. All rights reserved. // -#import <Foundation/Foundation.h> -#import <QSCore/QSCore.h> -#import "SocialSite.h" #import "QSBookmarkProvider.h" #import "QSBookmarkProviderFactory.h" +#import "SocialSite.h" +#import <Foundation/Foundation.h> +#import <QSCore/QSCore.h> @interface QSDeliciousPlugIn_Source : QSObjectSource { IBOutlet NSTextField *userField; IBOutlet NSTextField *passField; IBOutlet NSTextField *hostField; } -@property (nonatomic, strong) NSString *internalPassword; +@property(nonatomic, strong) NSString *internalPassword; - (IBAction)settingsChanged:(id)sender; @end diff --git a/QSDeliciousPlugIn_Source.m b/QSDeliciousPlugIn_Source.m index 4d841a1..49526e1 100644 --- a/QSDeliciousPlugIn_Source.m +++ b/QSDeliciousPlugIn_Source.m @@ -18,28 +18,31 @@ // This method will get called whenever we change which // active entry is selected. - (void)setSelectedEntry:(id)selectedEntry { - [super setSelectedEntry:selectedEntry]; - [self loadPasswordFromKeychain]; + [super setSelectedEntry:selectedEntry]; + [self loadPasswordFromKeychain]; } #pragma mark - Quicksilver Source Methods -- (BOOL)indexIsValidFromDate:(NSDate *)indexDate forEntry:(NSDictionary *)theEntry { +- (BOOL)indexIsValidFromDate:(NSDate *)indexDate + forEntry:(NSDictionary *)theEntry { return -[indexDate timeIntervalSinceNow] < 24 * 60 * 60; } -- (BOOL)isVisibleSource{ - return YES; +- (BOOL)isVisibleSource { + return YES; } -- (NSImage *) iconForEntry:(NSDictionary *)dict { - return [[NSBundle bundleForClass:[self class]]imageNamed:@"bookmark_icon"]; +- (NSImage *)iconForEntry:(NSDictionary *)dict { + return [[NSBundle bundleForClass:[self class]] imageNamed:@"bookmark_icon"]; } -- (NSView *)settingsView -{ +- (NSView *)settingsView { if (![super settingsView]) { - [[NSBundle bundleForClass:[self class]] loadNibNamed:NSStringFromClass([self class]) owner:self topLevelObjects:NULL]; + [[NSBundle bundleForClass:[self class]] + loadNibNamed:NSStringFromClass([self class]) + owner:self + topLevelObjects:NULL]; } return [super settingsView]; } @@ -48,7 +51,9 @@ - (SocialSite)siteIndex { NSDictionary *settings = self.selectedEntry.sourceSettings; - return [settings objectForKey:@"site"] != nil ? [[settings objectForKey:@"site"] integerValue] : SocialSiteDelicious; + return [settings objectForKey:@"site"] != nil + ? [[settings objectForKey:@"site"] integerValue] + : SocialSiteDelicious; } - (NSString *)currentUsername { @@ -64,221 +69,251 @@ } - (BOOL)includeTags { - return [[self.selectedEntry.sourceSettings objectForKey:@"includeTags"] boolValue]; + return [[self.selectedEntry.sourceSettings objectForKey:@"includeTags"] + boolValue]; } - // This method is called on action from all the NIB methods // to force the catalog to save the current values. - (IBAction)settingsChanged:(id)sender { - [[NSNotificationCenter defaultCenter] postNotificationName:QSCatalogEntryChangedNotification object:self.selectedEntry]; + [[NSNotificationCenter defaultCenter] + postNotificationName:QSCatalogEntryChangedNotification + object:self.selectedEntry]; [self willChangeValueForKey:@"isHostVisible"]; [self didChangeValueForKey:@"isHostVisible"]; } - #pragma mark - Keychain Helper Methods - (NSString *)keychainKeyForIdentifier:(NSString *)identifier { - return [NSString stringWithFormat:@"QSSocialBookmarks-%@", identifier]; + return [NSString stringWithFormat:@"QSSocialBookmarks-%@", identifier]; } - (NSString *)passwordFromKeychainForKey:(NSString *)key { - const char *service = "QSSocialBookmarks"; - const char *account = [key UTF8String]; - - UInt32 passwordLength = 0; - void *passwordData = NULL; - - OSStatus status = SecKeychainFindGenericPassword(NULL, - (UInt32)strlen(service), service, - (UInt32)strlen(account), account, - &passwordLength, &passwordData, - NULL); - - if (status == errSecSuccess && passwordData != NULL) { - NSString *password = [[NSString alloc] initWithBytes:passwordData - length:passwordLength - encoding:NSUTF8StringEncoding]; - SecKeychainItemFreeContent(NULL, passwordData); - return password; - } - - return nil; + const char *service = "QSSocialBookmarks"; + const char *account = [key UTF8String]; + + UInt32 passwordLength = 0; + void *passwordData = NULL; + + OSStatus status = SecKeychainFindGenericPassword( + NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), account, + &passwordLength, &passwordData, NULL); + + if (status == errSecSuccess && passwordData != NULL) { + NSString *password = [[NSString alloc] initWithBytes:passwordData + length:passwordLength + encoding:NSUTF8StringEncoding]; + SecKeychainItemFreeContent(NULL, passwordData); + return password; + } + + return nil; } -- (OSStatus)savePasswordToKeychainForKey:(NSString *)key password:(NSString *)password { - const char *service = "QSSocialBookmarks"; - const char *account = [key UTF8String]; - const char *passwordCString = [password UTF8String]; - - // First try to find existing item - SecKeychainItemRef item = NULL; - OSStatus findStatus = SecKeychainFindGenericPassword(NULL, - (UInt32)strlen(service), service, - (UInt32)strlen(account), account, - NULL, NULL, - &item); - - OSStatus status; - if (findStatus == errSecSuccess) { - // Update existing item - status = SecKeychainItemModifyAttributesAndData(item, - NULL, - (UInt32)strlen(passwordCString), - passwordCString); - CFRelease(item); - } else { - // Create new item - status = SecKeychainAddGenericPassword(NULL, - (UInt32)strlen(service), service, - (UInt32)strlen(account), account, - (UInt32)strlen(passwordCString), passwordCString, - NULL); - } - - return status; +- (OSStatus)savePasswordToKeychainForKey:(NSString *)key + password:(NSString *)password { + const char *service = "QSSocialBookmarks"; + const char *account = [key UTF8String]; + const char *passwordCString = [password UTF8String]; + + // First try to find existing item + SecKeychainItemRef item = NULL; + OSStatus findStatus = SecKeychainFindGenericPassword( + NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), account, + NULL, NULL, &item); + + OSStatus status; + if (findStatus == errSecSuccess) { + // Update existing item + status = SecKeychainItemModifyAttributesAndData( + item, NULL, (UInt32)strlen(passwordCString), passwordCString); + CFRelease(item); + } else { + // Create new item + status = SecKeychainAddGenericPassword( + NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), + account, (UInt32)strlen(passwordCString), passwordCString, NULL); + } + + return status; } - (OSStatus)deletePasswordFromKeychainForKey:(NSString *)key { - const char *service = "QSSocialBookmarks"; - const char *account = [key UTF8String]; - - SecKeychainItemRef item = NULL; - OSStatus findStatus = SecKeychainFindGenericPassword(NULL, - (UInt32)strlen(service), service, - (UInt32)strlen(account), account, - NULL, NULL, - &item); - - if (findStatus == errSecSuccess) { - OSStatus deleteStatus = SecKeychainItemDelete(item); - CFRelease(item); - return deleteStatus; - } - - return findStatus; + const char *service = "QSSocialBookmarks"; + const char *account = [key UTF8String]; + + SecKeychainItemRef item = NULL; + OSStatus findStatus = SecKeychainFindGenericPassword( + NULL, (UInt32)strlen(service), service, (UInt32)strlen(account), account, + NULL, NULL, &item); + + if (findStatus == errSecSuccess) { + OSStatus deleteStatus = SecKeychainItemDelete(item); + CFRelease(item); + return deleteStatus; + } + + return findStatus; } #pragma mark - Password Keychain Methods - (void)loadPasswordFromKeychain { - if (!self.selectedEntry || !self.selectedEntry.identifier) { - self.internalPassword = nil; - return; - } - - NSString *keychainKey = [self keychainKeyForIdentifier:self.selectedEntry.identifier]; - [self setPassword: [self passwordFromKeychainForKey:keychainKey]]; + if (!self.selectedEntry || !self.selectedEntry.identifier) { + self.internalPassword = nil; + return; + } + + NSString *keychainKey = + [self keychainKeyForIdentifier:self.selectedEntry.identifier]; + [self setPassword:[self passwordFromKeychainForKey:keychainKey]]; } - (void)savePasswordToKeychain { - if (!self.selectedEntry || !self.selectedEntry.identifier || !self.internalPassword) { - return; - } - - NSString *keychainKey = [self keychainKeyForIdentifier:self.selectedEntry.identifier]; - OSStatus status = [self savePasswordToKeychainForKey:keychainKey password:self.internalPassword]; - - if (status != errSecSuccess) { - NSLog(@"Failed to save password to keychain for key: %@, status: %d", keychainKey, (int)status); - } + if (!self.selectedEntry || !self.selectedEntry.identifier || + !self.internalPassword) { + return; + } + + NSString *keychainKey = + [self keychainKeyForIdentifier:self.selectedEntry.identifier]; + OSStatus status = [self savePasswordToKeychainForKey:keychainKey + password:self.internalPassword]; + + if (status != errSecSuccess) { + NSLog(@"Failed to save password to keychain for key: %@, status: %d", + keychainKey, (int)status); + } } #pragma mark - Password Property Accessors - (NSString *)password { - return self.internalPassword; + return self.internalPassword; } - (void)setPassword:(NSString *)password { - self.internalPassword = password; - - // Save to keychain asynchronously - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self savePasswordToKeychain]; - }); + self.internalPassword = password; + + // Save to keychain asynchronously + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + ^{ + [self savePasswordToKeychain]; + }); } #pragma mark - Host Visibility Control -- (BOOL) isHostVisible { - return [SocialSiteHelper hasVariableHost:[self siteIndex]]; +- (BOOL)isHostVisible { + return [SocialSiteHelper hasVariableHost:[self siteIndex]]; +} +- (void)setIsHostVisible:(BOOL)isVisible { } -- (void)setIsHostVisible:(BOOL)isVisible { } #pragma mark - Objects For Entry - (NSArray *)objectsForEntry:(QSCatalogEntry *)theEntry { - - NSDictionary *settings = theEntry.sourceSettings; - - SocialSite site = [settings objectForKey:@"site"] != nil ? [[settings objectForKey:@"site"] integerValue] : SocialSiteDelicious; - NSString *username = [settings objectForKey:@"username"]; - NSString *identifier = theEntry.identifier; - NSString *keychainKey = [self keychainKeyForIdentifier:identifier]; - NSString *password = [self passwordFromKeychainForKey:keychainKey]; - NSString *host = [settings objectForKey:@"host"]; - BOOL includeTags = [settings objectForKey:@"includeTags"]; - - QSBookmarkProviderFactory *factory = [QSBookmarkProviderFactory sharedFactory]; - id<QSBookmarkProvider> provider = [factory providerForSite:site username:username password:password host:host]; - - if (!provider) { - NSLog(@"No provider available for site %ld with username %@", (long)site, username); - return @[]; - } - - return [provider fetchBookmarksForSite:site username:username password:password identifier:identifier host:host includeTags:includeTags]; -} -- (NSArray *)objectsForTag:(NSString *)tag site:(SocialSite)site username:(NSString *)username identifier:(NSString *)identifier host:(NSString *)host { - + NSDictionary *settings = theEntry.sourceSettings; + + SocialSite site = [settings objectForKey:@"site"] != nil + ? [[settings objectForKey:@"site"] integerValue] + : SocialSiteDelicious; + NSString *username = [settings objectForKey:@"username"]; + NSString *identifier = theEntry.identifier; NSString *keychainKey = [self keychainKeyForIdentifier:identifier]; NSString *password = [self passwordFromKeychainForKey:keychainKey]; + NSString *host = [settings objectForKey:@"host"]; + BOOL includeTags = [settings objectForKey:@"includeTags"]; + + QSBookmarkProviderFactory *factory = + [QSBookmarkProviderFactory sharedFactory]; + id<QSBookmarkProvider> provider = [factory providerForSite:site + username:username + password:password + host:host]; - QSBookmarkProviderFactory *factory = [QSBookmarkProviderFactory sharedFactory]; - id<QSBookmarkProvider> provider = [factory providerForSite:site username:username password:password host:host]; - if (!provider) { - NSLog(@"No provider available for site %ld with username %@", (long)site, username); + NSLog(@"No provider available for site %ld with username %@", (long)site, + username); return @[]; } - - // Check if provider supports tag-based fetching - if ([provider respondsToSelector:@selector(fetchBookmarksForTag:site:username:password:host:)]) { - return [provider fetchBookmarksForTag:tag site:site username:username password:password host:host]; - } - + + return [provider fetchBookmarksForSite:site + username:username + password:password + identifier:identifier + host:host + includeTags:includeTags]; +} + +- (NSArray *)objectsForTag:(NSString *)tag + site:(SocialSite)site + username:(NSString *)username + identifier:(NSString *)identifier + host:(NSString *)host { + + NSString *keychainKey = [self keychainKeyForIdentifier:identifier]; + NSString *password = [self passwordFromKeychainForKey:keychainKey]; + + QSBookmarkProviderFactory *factory = + [QSBookmarkProviderFactory sharedFactory]; + id<QSBookmarkProvider> provider = [factory providerForSite:site + username:username + password:password + host:host]; + + if (!provider) { + NSLog(@"No provider available for site %ld with username %@", (long)site, + username); return @[]; + } + + // Check if provider supports tag-based fetching + if ([provider respondsToSelector:@selector + (fetchBookmarksForTag:site:username:password:host:)]) { + return [provider fetchBookmarksForTag:tag + site:site + username:username + password:password + host:host]; + } + + return @[]; } #pragma mark - Object Handler Methods - (void)setQuickIconForObject:(QSObject *)object { if (@available(macOS 11.0, *)) { - NSImage *image = [NSImage imageWithSystemSymbolName:@"tag" accessibilityDescription:@"Bookmark"]; + NSImage *image = [NSImage imageWithSystemSymbolName:@"tag" + accessibilityDescription:@"Bookmark"]; [object setIcon:image]; } else { - [object setIcon:[[NSBundle bundleForClass:[self class]]imageNamed:@"bookmark_icon"]]; + [object setIcon:[[NSBundle bundleForClass:[self class]] + imageNamed:@"bookmark_icon"]]; } } -// All our objects will have children. URLs will have tags, and tags will have URLs. -- (BOOL)objectHasChildren:(QSObject *) object { return YES; } +// All our objects will have children. URLs will have tags, and tags will have +// URLs. +- (BOOL)objectHasChildren:(QSObject *)object { + return YES; +} // This will receive a tag object. Tag objects will have the // source configuration in the meta: source.username, // source.site, source.host and source.identifier. - (BOOL)loadChildrenForObject:(QSObject *)object { - + NSNumber *siteNumber = [object objectForMeta:@"source.site"]; if (!siteNumber) { NSLog(@"The tag didn't have a valid site."); return NO; } - + SocialSite site = [siteNumber integerValue]; - + NSString *username = [object objectForMeta:@"source.username"]; if (!username) { NSLog(@"The tag didn't have a valid username."); @@ -302,9 +337,13 @@ return NO; } - NSArray *children = [self objectsForTag:tag site:site username:username identifier:identifier host:host]; - [object setChildren:children]; - return YES; + NSArray *children = [self objectsForTag:tag + site:site + username:username + identifier:identifier + host:host]; + [object setChildren:children]; + return YES; } @end diff --git a/Types/SocialSite.h b/Types/SocialSite.h index 43155c7..2510594 100644 --- a/Types/SocialSite.h +++ b/Types/SocialSite.h @@ -8,11 +8,11 @@ #import <Foundation/Foundation.h> typedef NS_ENUM(NSInteger, SocialSite) { - SocialSiteDelicious = 0, - SocialSiteMagnolia = 1, - SocialSitePinboard = 2, - SocialSiteLinkding = 3, - SocialSiteSelfHostedDeliciousCompatible = 4 + SocialSiteDelicious = 0, + SocialSiteMagnolia = 1, + SocialSitePinboard = 2, + SocialSiteLinkding = 3, + SocialSiteSelfHostedDeliciousCompatible = 4 }; @interface SocialSiteHelper : NSObject diff --git a/Types/SocialSite.m b/Types/SocialSite.m index 369f86d..258386d 100644 --- a/Types/SocialSite.m +++ b/Types/SocialSite.m @@ -8,48 +8,48 @@ @implementation SocialSiteHelper + (NSString *)displayNameForSite:(SocialSite)site { - switch (site) { - case SocialSiteDelicious: - return @"del.icio.us"; - case SocialSiteMagnolia: - return @"ma.gnolia.com"; - case SocialSitePinboard: - return @"Pinboard"; - case SocialSiteLinkding: - return @"Linkding"; - case SocialSiteSelfHostedDeliciousCompatible: - return @"Self-Hosted (Delicious Compatible)"; - default: - return @"Unknown"; - } + switch (site) { + case SocialSiteDelicious: + return @"del.icio.us"; + case SocialSiteMagnolia: + return @"ma.gnolia.com"; + case SocialSitePinboard: + return @"Pinboard"; + case SocialSiteLinkding: + return @"Linkding"; + case SocialSiteSelfHostedDeliciousCompatible: + return @"Self-Hosted (Delicious Compatible)"; + default: + return @"Unknown"; + } } // This is used for caching key + (NSString *)cacheKeyForSite:(SocialSite)site { - switch (site) { - case SocialSiteDelicious: - return @"del.icio.us"; - case SocialSiteMagnolia: - return @"ma.gnolia.com"; - case SocialSitePinboard: - return @"pinboard.in"; - case SocialSiteSelfHostedDeliciousCompatible: - return @"self-hosted-delicious"; - case SocialSiteLinkding: - return @"linkding"; - default: - return nil; - } + switch (site) { + case SocialSiteDelicious: + return @"del.icio.us"; + case SocialSiteMagnolia: + return @"ma.gnolia.com"; + case SocialSitePinboard: + return @"pinboard.in"; + case SocialSiteSelfHostedDeliciousCompatible: + return @"self-hosted-delicious"; + case SocialSiteLinkding: + return @"linkding"; + default: + return nil; + } } + (BOOL)hasVariableHost:(SocialSite)site { - switch (site) { - case SocialSiteSelfHostedDeliciousCompatible: - case SocialSiteLinkding: - return YES; - default: - return NO; - } + switch (site) { + case SocialSiteSelfHostedDeliciousCompatible: + case SocialSiteLinkding: + return YES; + default: + return NO; + } } @end |