summaryrefslogtreecommitdiff
path: root/QSDeliciousPlugIn_Source.m
diff options
context:
space:
mode:
authorQiushi (Billy) He <qiushihe@me.com>2010-08-02 23:32:53 -0700
committerpjrobertson <robertson.patrick@gmail.com>2010-08-14 11:41:44 +1000
commit8c5345cda99f7ff178fed3d51a59f5602372613d (patch)
tree77e4a3c0060cdce36060c3ffbe17aeaa7d620970 /QSDeliciousPlugIn_Source.m
parentf53401b2fbc498fe529214b8c0b365390830efe2 (diff)
Implemented support for Pinboard.in
Diffstat (limited to 'QSDeliciousPlugIn_Source.m')
-rw-r--r--QSDeliciousPlugIn_Source.m351
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