diff options
| author | Qiushi (Billy) He <qiushihe@me.com> | 2010-08-02 23:32:53 -0700 |
|---|---|---|
| committer | pjrobertson <robertson.patrick@gmail.com> | 2010-08-14 11:41:44 +1000 |
| commit | 8c5345cda99f7ff178fed3d51a59f5602372613d (patch) | |
| tree | 77e4a3c0060cdce36060c3ffbe17aeaa7d620970 /QSDeliciousPlugIn_Source.m | |
| parent | f53401b2fbc498fe529214b8c0b365390830efe2 (diff) | |
Implemented support for Pinboard.in
Diffstat (limited to 'QSDeliciousPlugIn_Source.m')
| -rw-r--r-- | QSDeliciousPlugIn_Source.m | 351 |
1 files changed, 234 insertions, 117 deletions
diff --git a/QSDeliciousPlugIn_Source.m b/QSDeliciousPlugIn_Source.m index 1267654..a2d6494 100644 --- a/QSDeliciousPlugIn_Source.m +++ b/QSDeliciousPlugIn_Source.m @@ -9,75 +9,237 @@ #import "QSDeliciousPlugIn_Source.h" #import <QSCore/QSCore.h> -#define kQSDeliciousTagType @"us.icio.del.tag" -#define DELICIOUS_API_URL @"api.del.icio.us/v1" -#define MAGNOLIA_API_URL @"ma.gnolia.com/api/mirrord/v1" +#import <Security/Security.h> @implementation QSDeliciousPlugIn_Source -+ (void)initialize{ + ++ (void)initialize { [self setKeys:[NSArray arrayWithObject:@"selection"] triggerChangeNotificationsForDependentKey:@"currentPassword"]; } -- (BOOL)indexIsValidFromDate:(NSDate *)indexDate forEntry:(NSDictionary *)theEntry{ - return -[indexDate timeIntervalSinceNow] < 24*60*60; + +- (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{ +- (NSImage *) iconForEntry:(NSDictionary *)dict { return [[NSBundle bundleForClass:[self class]]imageNamed:@"bookmark_icon"]; } -- (NSString *)identifierForObject:(id <QSObject>)object{ - return nil; +- (NSString *) mainNibName { + return @"QSDeliciousPrefPane"; } -- (NSView *) settingsView{ - if (![super settingsView]){ +- (void)populateFields { + NSLog(@"populating: %@/%@", [[selection info] objectForKey:@"username"], [[selection info] objectForKey:@"site"]); +} + +- (NSView *) settingsView { + if (![super settingsView]) [NSBundle loadNibNamed:@"QSDeliciousPlugInSource" owner:self]; - } return [super settingsView]; } -- (NSData *)cachedBookmarkDataForUser:(NSString *)username{ - NSString *cachePath=[QSApplicationSupportSubPath(@"Caches/del.icio.us/",NO) stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.xml",username]]; - return [NSData dataWithContentsOfFile:cachePath]; +// Keychain Access -- The QS Built-in ones seems to be broken + +- (SecProtocolType)protocolTypeForString:(NSString *)protocol { + if ([protocol isEqualToString:@"ftp"]) return kSecProtocolTypeFTP; + else if ([protocol isEqualToString:@"http"]) return kSecProtocolTypeHTTP; + else if ([protocol isEqualToString:@"sftp"]) return kSecProtocolTypeFTPS; + else if ([protocol isEqualToString:@"eppc"]) return kSecProtocolTypeEPPC; + else if ([protocol isEqualToString:@"afp"]) return kSecProtocolTypeAFP; + else if ([protocol isEqualToString:@"smb"]) return kSecProtocolTypeSMB; + else if ([protocol isEqualToString:@"ssh"]) return kSecProtocolTypeSSH; + else if ([protocol isEqualToString:@"telnet"]) return kSecProtocolTypeTelnet; + return 0; } -- (NSString *)passwordForUser:(NSString *)username{ - NSURL *url=[NSURL URLWithString:[NSString stringWithFormat:@"http://%@@del.icio.us/",username]]; - return [url keychainPassword]; + +- (NSString *)passwordForHost:(NSString *)host user:(NSString *)user andType:(SecProtocolType)type { + const char *buffer; + UInt32 length = 0; + OSErr err; + + err = SecKeychainFindInternetPassword(NULL, + [host length], [host UTF8String], + 0, + NULL, + [user length], [user UTF8String], + 0, NULL, + 0, + type, + 0, + &length, (void**)&buffer, + NULL); + + if (err == noErr) { + NSString *password = [[[NSString alloc] initWithCString:buffer length:length] autorelease]; + SecKeychainItemFreeContent(NULL,(void *)buffer); + return password; + } + return nil; } -- (NSData *)bookmarkDataForUser:(NSString *)username onSite:(int)isMagnolia{ - NSString *password=[self passwordForUser:username]; - if (VERBOSE)NSLog(@"Downloading del.icio.us bookmarks for %@ %d",username, isMagnolia); - //NSString *count=[[self currentEntry] objectForKey:@"QSDeliciousRecentCount"]; +- (NSString *)passwordForHost:(NSString *)host user:(NSString *)user andScheme:(NSString *)scheme { + NSString *password = nil; + + SecProtocolType type = [self protocolTypeForString:scheme]; + + password = [self passwordForHost:host user:user andType:type]; - if (!username || !password) return nil; - //if (!count)count=@"50"; + if (!password && type == kSecProtocolTypeFTP) + password = [self passwordForHost:host user:user andType:kSecProtocolTypeFTPAccount]; // Workaround for Transmit's old type usage + if ( !password ) + password = [self passwordForHost:host user:user andType:0]; + if ( !password ) + NSLog(@"Couldn't find password. URL:%@ %@ %@", host, user,scheme ); + return password; +} + +- (NSString *)keychainPasswordForURL:(NSURL *)url { + return [self passwordForHost:[url host] user:[url user] andScheme:[url scheme]]; +} - - NSString *apiurl=isMagnolia?MAGNOLIA_API_URL:DELICIOUS_API_URL; +- (OSErr)addURLPasswordToKeychain:(NSURL *)url { + OSErr err; - NSError *error; - NSURL *url=[NSURL URLWithString: - [NSString stringWithFormat:@"https://%@:%@@%@/posts/all?",username,password,apiurl]]; + NSString *host = [url host]; + NSString *user = [url user]; + NSString *pass = [url password]; - NSMutableURLRequest *theRequest=[NSMutableURLRequest requestWithURL:url - cachePolicy:NSURLRequestUseProtocolCachePolicy - timeoutInterval:60.0]; + SecProtocolType type = [self protocolTypeForString:[url scheme]]; - [theRequest setValue:@"Quicksilver (Blacktree,MacOSX)" forHTTPHeaderField:@"User-Agent"]; + SecKeychainItemRef existing = NULL; + + err = SecKeychainFindInternetPassword(NULL, + [host length], [host UTF8String], + 0, NULL, + [user length], [user UTF8String], + 0, NULL, + 0, + type, + 0, + NULL,NULL, + &existing); + + if ( !err ) { + err = SecKeychainItemModifyContent( existing, NULL, [pass length], [pass UTF8String] ); + CFRelease( existing ); + } else { + err = SecKeychainAddInternetPassword(NULL, + [host length], [host UTF8String], + 0, NULL, + [user length], [user UTF8String], + 0, NULL, + 0, + type, + 0, + [pass length], [pass UTF8String], + NULL); + } + + return err; +} + +// Site Index/API/URL + +- (int)siteIndex { + return [[selection info] objectForKey:@"site"] != nil ? [[[selection info] objectForKey:@"site"] integerValue] : 0; +} + +- (NSString *)siteURLForIndex:(int)siteIndex { + if (siteIndex == 0) return @"del.icio.us"; + else if (siteIndex == 1) return @"ma.gnolia.com"; + else if (siteIndex == 2) return @"pinboard.in"; + else return nil; +} + +- (NSString *)reversedSiteURLForIndex:(int)siteIndex { + if (siteIndex == 0) return @"us.icio.del"; + else if (siteIndex == 1) return @"com.gnolia.ma"; + else if (siteIndex == 2) return @"in.pinboard"; + else return nil; +} + +- (NSString *)tagURLForIndex:(int)siteIndex { + return [NSString stringWithFormat:@"%2.%2", [self reversedSiteURLForIndex:[self siteIndex]], @"tag"]; +} + +- (NSString *)apiURLForIndex:(int)siteIndex { + if (siteIndex == 0) return @"api.del.icio.us/v1"; + else if (siteIndex == 1) return @"ma.gnolia.com/api/mirrord/v1"; + else if (siteIndex == 2) return @"api.pinboard.in/v1"; + else return nil; +} + +// Current Site/API URL + +- (NSString *)currentSiteURL { + return [self siteURLForIndex:[self siteIndex]]; +} + +- (NSString *)currentAPIURL { + return [self apiURLForIndex:[self siteIndex]]; +} + +// Useranme + +- (NSString *)currentUsername { + return [[selection info] objectForKey:@"username"]; +} + +// Password Related + +- (NSString *)currentPassword { + NSString *account = [self currentUsername]; + if (!account) return nil; + NSURL *keychainURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@@%@/", account, [self currentSiteURL]]]; + NSString *password = [self keychainPasswordForURL:keychainURL]; + + return password; +} + +- (void)setCurrentPassword:(NSString *)newPassword { + NSString *account = [self currentUsername]; + if (!account) return; + if ([newPassword length] <= 0) return; - // NSURLConnection *theConnection=[[[NSURLConnection alloc] initWithRequest:theRequest delegate:nil]; + NSURL *keychainURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%@@%@/", account, newPassword, [self currentSiteURL]]]; - //NSLog(@"url %@",url); - NSData *data=[NSURLConnection sendSynchronousRequest:theRequest returningResponse:nil error:&error]; - NSString *cachePath=[QSApplicationSupportSubPath(@"Caches/del.icio.us/",YES) stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.xml",username]]; + [self addURLPasswordToKeychain:keychainURL]; +} + +// Bookarmk/Caching + +- (NSData *)cachedBookmarkData { + NSString *cachePath=[QSApplicationSupportSubPath([NSString stringWithFormat:@"Caches/%@/", [self currentSiteURL]], NO) stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.xml", [self currentUsername]]]; + return [NSData dataWithContentsOfFile:cachePath]; +} + +- (NSData *)bookmarkData { + if (![self currentUsername] || ![self currentPassword]) return nil; + + NSString *urlString = [NSString stringWithFormat:@"https://%@:%@@%@/posts/all?", [self currentUsername], [self currentPassword], [self currentAPIURL]]; + NSURL *requestURL = [NSURL URLWithString:urlString]; + + NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:requestURL + cachePolicy:NSURLRequestUseProtocolCachePolicy + timeoutInterval:60.0]; + [theRequest setHTTPMethod:@"POST"]; + [theRequest setValue:@"text/xml" forHTTPHeaderField:@"Content-type"]; + [theRequest setValue:@"Quicksilver (Blacktree,MacOSX)" forHTTPHeaderField:@"User-Agent"]; + + NSError *error; + NSData *data = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:nil error:&error]; + + NSString *cachePath = [QSApplicationSupportSubPath([NSString stringWithFormat:@"Caches/%@/", [self currentSiteURL]], YES) stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.xml", [self currentUsername]]]; [data writeToFile:cachePath atomically:NO]; + return data; } -- (QSObject *)objectForPost:(NSDictionary *)post{ + +- (QSObject *)objectForPost:(NSDictionary *)post { QSObject *newObject=[QSObject makeObjectWithIdentifier:[post objectForKey:@"hash"]]; [newObject setObject:[post objectForKey:@"href"] forType:QSURLType]; [newObject setName:[post objectForKey:@"description"]]; @@ -90,18 +252,15 @@ return newObject; } -- (NSArray *) objectsForEntry:(NSDictionary *)theEntry{ - NSString *username=[theEntry objectForKey:@"username"]; - NSData *data=nil;//[self cachedBookmarkDataForUser:username]; - if (![data length]) data=[self bookmarkDataForUser:username onSite:[[theEntry objectForKey:@"site"]intValue]]; +- (NSArray *)objectsForEntry:(NSDictionary *)theEntry { + NSData *data = nil;//[self cachedBookmarkData]; + if (![data length]) data = [self bookmarkData]; - //NSString *string=[[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]autorelease]; - //NSLog(@"data %@",string); + NSXMLParser *postParser = [[NSXMLParser alloc]initWithData:data]; + [postParser setDelegate:self]; - NSXMLParser *postParser=[[NSXMLParser alloc]initWithData:data]; + posts = [NSMutableArray arrayWithCapacity:1]; - [postParser setDelegate:self]; - posts=[NSMutableArray arrayWithCapacity:1]; [postParser parse]; NSMutableArray *objects=[NSMutableArray arrayWithCapacity:1]; @@ -120,10 +279,10 @@ if ([[theEntry objectForKey:@"includeTags"]boolValue]){ while(tag=[e nextObject]){ newObject=[QSObject makeObjectWithIdentifier:[NSString stringWithFormat:@"[del.icio.us tag]:%@",tag]]; - [newObject setObject:tag forType:kQSDeliciousTagType]; - [newObject setObject:username forMeta:@"us.icio.del.username"]; + [newObject setObject:tag forType:[self tagURLForIndex:[self siteIndex]]]; + [newObject setObject:[self currentUsername] forMeta:@"us.icio.del.username"]; [newObject setName:tag]; - [newObject setPrimaryType:kQSDeliciousTagType]; + [newObject setPrimaryType:[self tagURLForIndex:[self siteIndex]]]; [objects addObject:newObject]; } } @@ -134,7 +293,7 @@ } - (NSArray *)objectsForTag:(NSString *)tag username:(NSString *)username{ - NSData *data=[self cachedBookmarkDataForUser:username]; + NSData *data=[self cachedBookmarkData]; NSXMLParser *postParser=[[NSXMLParser alloc]initWithData:data]; [postParser setDelegate:self]; @@ -145,7 +304,7 @@ QSObject *newObject; NSEnumerator *e=[posts objectEnumerator]; NSDictionary *post; - NSMutableSet *tagSet=[NSMutableSet set]; + // NSMutableSet *tagSet=[NSMutableSet set]; while(post=[e nextObject]){ if ([[post objectForKey:@"tag"]rangeOfString:tag].location==NSNotFound)continue; newObject=[self objectForPost:post]; @@ -154,83 +313,41 @@ return objects; } -// XML Stuff -- (void)parser:(NSXMLParser*)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { - // NSLog(@"started %@ %@ %@ %@",elementName,namespaceURI,qName,attributeDict); - - if ([elementName isEqualToString:@"post"] && attributeDict) - [posts addObject:attributeDict]; -} - -- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { - // NSLog(@"ended %@ %@ %@ %@",elementName,namespaceURI,qName); -} - - - - - - - // Object Handler Methods /* - - (void)setQuickIconForObject:(QSObject *)object{ - [object setIcon:nil]; // An icon that is either already in memory or easy to load - } - - (BOOL)loadIconForObject:(QSObject *)object{ - return NO; - id data=[object objectForType:QSDeliciousPlugInType]; - [object setIcon:nil]; - return YES; - } - */ - - - - - -- (NSString *) mainNibName{ - return @"QSDeliciousPrefPane"; +- (void)setQuickIconForObject:(QSObject *)object { + [object setIcon:nil]; // An icon that is either already in memory or easy to load } -- (void)populateFields{ -} -- (NSString *)currentPassword{ - NSString *account=[[self currentEntry] objectForKey:@"username"]; - if (!account)return nil; - NSURL *url=[NSURL URLWithString:[NSString stringWithFormat:@"http://%@@del.icio.us/",account]]; - NSString *password=[url keychainPassword]; - return password; -} -- (void)setCurrentPassword:(NSString *)newPassword{ - NSString *account=[[self currentEntry] objectForKey:@"username"]; - NSURL *url=[NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%@@del.icio.us/",account,newPassword]]; - if ([newPassword length]) - [url addPasswordToKeychain]; - +- (BOOL)loadIconForObject:(QSObject *)object { + return NO; + id data=[object objectForType:QSDeliciousPlugInType]; + [object setIcon:nil]; + return YES; } +*/ - - - - - - - - -- (void)setQuickIconForObject:(QSObject *)object{ +- (void)setQuickIconForObject:(QSObject *)object { [object setIcon:[[NSBundle bundleForClass:[self class]]imageNamed:@"bookmark_icon"]]; } -- (BOOL)loadChildrenForObject:(QSObject *)object{ - [object setChildren: - [self objectsForTag:[object objectForType:kQSDeliciousTagType] - username:[object objectForMeta:@"us.icio.del.username"]]]; + +- (BOOL)loadChildrenForObject:(QSObject *)object { + [object setChildren:[self objectsForTag:[object objectForType:[self tagURLForIndex:[self siteIndex]]] + username:[object objectForMeta:@"us.icio.del.username"]]]; return YES; } +// NSXMLParserDelegate Functions +- (void)parser:(NSXMLParser*)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { + NSLog(@"XML Parser started %@ %@ %@ %@",elementName,namespaceURI,qName,attributeDict); + if ([elementName isEqualToString:@"post"] && attributeDict) + [posts addObject:attributeDict]; +} - +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + NSLog(@"XML Parser ended %@ %@ %@", elementName, namespaceURI, qName); +} @end |