diff --git a/Application/AppController.h b/Application/AppController.h index f88e676..6fd33c5 100644 --- a/Application/AppController.h +++ b/Application/AppController.h @@ -24,6 +24,8 @@ #import "SFHFRatingCell.h" #import "SFHFCircularCounterCell.h" #import "SFHFCornerView.h" +#import "EBIconAndTextCell.h" +#import "EBFavIconUtils.h" #import "defines.h" #import "DCTypes.h" @@ -74,6 +76,8 @@ NSMutableDictionary *posts; NSArray *filteredTags; NSArray *filteredPosts; + + NSMutableDictionary *favIcons; NSString *currentSearch; DCAPITag *currentTagFilter; @@ -107,6 +111,7 @@ - (void) refreshAll; - (void) refreshTags; - (void) refreshPostsWithDownload: (BOOL) download; +- (void) refreshFavIconsWithDownload: (BOOL) download; /* Search/Tag Filtering */ - (IBAction) doSearch: (id) sender; @@ -142,6 +147,8 @@ - (NSArray *) tagsArray; - (void) resortTags; - (void) renameTag: (NSString *) originalName to: (NSString *) newName withUpload: (BOOL) upload; +- (void) setFavIcons: (NSDictionary *) newFavIcons; +- (NSMutableDictionary *) favIcons; /* UI setup */ - (void) setupTaglist; diff --git a/Application/AppController.m b/Application/AppController.m index 8790c8f..ff1acfa 100644 --- a/Application/AppController.m +++ b/Application/AppController.m @@ -98,6 +98,7 @@ - (void) applicationDidFinishLaunching: (NSNotification *) aNotification { [self setPosts: [NSMutableDictionary dictionaryWithCapacity: 0]]; [self setTags: [NSMutableDictionary dictionaryWithCapacity: 0]]; + [self setFavIcons: [NSMutableDictionary dictionaryWithCapacity: 0]]; [self setupTaglist]; [self setupPostlist]; @@ -223,8 +224,12 @@ - (void) setupPostlist { [starCell release]; NSTableColumn *descriptionColumn = [postList tableColumnWithIdentifier: @"description"]; - NSCell *descriptionColumnCell = [descriptionColumn dataCell]; + EBIconAndTextCell * descriptionColumnCell = [[EBIconAndTextCell alloc] initWithDefaultIcon:[NSImage imageNamed: @"default_favicon.tif"]]; + [descriptionColumnCell setIconSize: NSMakeSize(13.0,13.0)]; [descriptionColumnCell setWraps: YES]; + [descriptionColumnCell setFont: [[descriptionColumn dataCell] font]]; // Works, but there must be a better way to set default font + [descriptionColumn setDataCell: descriptionColumnCell]; + [descriptionColumnCell release]; NSTableColumn *dateColumn = [postList tableColumnWithIdentifier: @"date"]; NSCell *dateColumnCell = [dateColumn dataCell]; @@ -266,6 +271,7 @@ - (void) refreshAll { [spinnyThing startAnimation: self]; [self refreshPostsWithDownload: YES]; [self refreshTags]; + [self refreshFavIconsWithDownload: NO]; [spinnyThing stopAnimation: self]; [pool release]; @@ -335,6 +341,32 @@ - (void) refreshPostsWithDownload: (BOOL) download { [pool release]; } +- (void) refreshFavIconsWithDownload: (BOOL) download { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSArray * unfilteredURLs = [self urlsArray]; + NSString * favIconPath; + + NSEnumerator * urlEnum = [unfilteredURLs objectEnumerator]; + NSString * currURLString; + NSURL * currURL; + + while((currURLString = (NSString *)[urlEnum nextObject]) != nil) { + currURL = [NSURL URLWithString:currURLString]; + + if(currURL != nil) { + favIconPath = [EBFavIconUtils downloadFavIconForURL:currURL forceDownload: download]; + + if (favIconPath) { + [[self favIcons] setObject:favIconPath forKey:currURL]; + } + } + } + + [postList performSelectorOnMainThread: @selector(reloadData) withObject:nil waitUntilDone:NO]; + [pool release]; +} + - (NSArray *) selectedTags { if ([tagList isRowSelected: 0]) { return nil; @@ -445,6 +477,19 @@ - (DCAPIClient *) client { return [[client retain] autorelease]; } +- (void) setFavIcons: (NSDictionary *) newFavIcons { + @synchronized(favIcons) { + if (favIcons != newFavIcons) { + [favIcons release]; + favIcons = [newFavIcons mutableCopy]; + } + } +} + +- (NSMutableDictionary *) favIcons { + return [[favIcons retain] autorelease]; +} + - (void) setTags: (NSDictionary *) newTags { @synchronized(tags) { if (tags != newTags) { @@ -916,7 +961,12 @@ - (BOOL) tableView: (NSTableView *) tableView writeRows: (NSArray *) rows toPast } if (tableView == postList) { - [pboard declareTypes: [NSArray arrayWithObjects: kDCAPIPostPboardType, NSURLPboardType, NSStringPboardType, nil] owner: self]; + [pboard declareTypes: [NSArray arrayWithObjects: kDCAPIPostPboardType, + kWebURLsWithTitlesPboardType, + NSURLPboardType, + kWebURLPboardType, + kWebURLNamePboardType, + NSStringPboardType, nil] owner: self]; NSNumber *currentPostIndex = [rows objectAtIndex: 0]; DCAPIPost *currentPost = [[self filteredPosts] objectAtIndex: [currentPostIndex unsignedIntValue]]; @@ -925,7 +975,24 @@ - (BOOL) tableView: (NSTableView *) tableView writeRows: (NSArray *) rows toPast NSURL *currentURL = [currentPost URL]; [pboard setString: [currentURL absoluteString] forType: NSStringPboardType]; [currentURL writeToPasteboard: pboard]; - + + NSString *currentTitle = [currentPost description]; + + id plist = [NSArray arrayWithObjects:[NSArray arrayWithObject:[currentURL absoluteString]], + [NSArray arrayWithObject:[currentPost description]], + nil]; + NSData * data = [NSPropertyListSerialization dataFromPropertyList:plist + format:NSPropertyListXMLFormat_v1_0 + errorDescription:NULL]; + NSString * string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]; + + [pboard setPropertyList:plist forType:kWebURLsWithTitlesPboardType]; + [pboard setString:string forType:kWebURLsWithTitlesPboardType]; + [pboard setData:data forType:kWebURLsWithTitlesPboardType]; + + [pboard setString:[currentURL absoluteString] forType:kWebURLPboardType]; + [pboard setString:currentTitle forType:kWebURLNamePboardType]; + return YES; } @@ -969,6 +1036,15 @@ - (void) tableView: (NSTableView *) tableView didClickTableColumn: (NSTableColum } } +- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex { + if(aTableView == postList) { + if([[aTableColumn identifier] isEqualToString:@"description"]) { + DCAPIPost * currentPost = [[self filteredPosts] objectAtIndex: rowIndex]; + [(EBIconAndTextCell *) aCell setFavIconPath: [[self favIcons] objectForKey: [currentPost URL]]]; + } + } +} + // ----- beg implementation for postList tooltips ----- - (NSString *)tableView:(SFHFTableView *)tableView tooltipForItem:(id)item { if (tableView == postList) { @@ -1349,6 +1425,9 @@ - (IBAction) postNewLink: (id) sender { DCAPIPost *newPost = [[DCAPIPost alloc] initWithURL: postURL description: postDescription extended: postExtended date: postDate tags: nil urlHash: nil]; [newPost setTagsFromString: postTags]; + + NSString * favIconPath = [EBFavIconUtils downloadFavIconForURL: [newPost valueForKey: @"URL"] forceDownload: NO]; + [[self favIcons] setObject: favIconPath forKey: [newPost valueForKey: @"URL"]]; [NSThread detachNewThreadSelector: @selector(addPost:) toTarget: [self client] withObject: newPost]; @@ -1620,6 +1699,7 @@ - (void) dealloc { [currentPostProperties release]; [loginProperties release]; [currentSearch release]; + [favIcons release]; #ifdef AWOOSTER_CHANGES [textIndex release]; #endif diff --git a/Art/default_favicon.tif b/Art/default_favicon.tif new file mode 100644 index 0000000..7702634 Binary files /dev/null and b/Art/default_favicon.tif differ diff --git a/Delicious Client.xcode/buzz.pbxuser b/Delicious Client.xcode/buzz.pbxuser index a441d6b..e57ab93 100644 --- a/Delicious Client.xcode/buzz.pbxuser +++ b/Delicious Client.xcode/buzz.pbxuser @@ -1018,6 +1018,54 @@ }; PBXWorkspaceStateSaveDate = 140776626; }; + perUserProjectItems = { + 6B64759E0867DB4A00CD0EC9 = 6B64759E0867DB4A00CD0EC9; + 6B64759F0867DB4A00CD0EC9 = 6B64759F0867DB4A00CD0EC9; + 6B6475B00867DD0C00CD0EC9 = 6B6475B00867DD0C00CD0EC9; + 6B6475B50867DD6F00CD0EC9 = 6B6475B50867DD6F00CD0EC9; + 6B6475B80867DDA500CD0EC9 = 6B6475B80867DDA500CD0EC9; + 6B6475BB0867DDC600CD0EC9 = 6B6475BB0867DDC600CD0EC9; + 6B6475BE0867DE0500CD0EC9 = 6B6475BE0867DE0500CD0EC9; + 6B6475C10867DE5E00CD0EC9 = 6B6475C10867DE5E00CD0EC9; + 6B6475C40867DEAD00CD0EC9 = 6B6475C40867DEAD00CD0EC9; + 6B6475C70867DF2C00CD0EC9 = 6B6475C70867DF2C00CD0EC9; + 6B6475CA0867DFB200CD0EC9 = 6B6475CA0867DFB200CD0EC9; + 6B6475CD0867DFF100CD0EC9 = 6B6475CD0867DFF100CD0EC9; + 6B6475D00867E09000CD0EC9 = 6B6475D00867E09000CD0EC9; + 6B6475EE0867E2B100CD0EC9 = 6B6475EE0867E2B100CD0EC9; + 6B6475EF0867E2B100CD0EC9 = 6B6475EF0867E2B100CD0EC9; + 6B6475F00867E2B100CD0EC9 = 6B6475F00867E2B100CD0EC9; + 6B6475F10867E2B100CD0EC9 = 6B6475F10867E2B100CD0EC9; + 6B6475F20867E2F000CD0EC9 = 6B6475F20867E2F000CD0EC9; + 6B6475F90867E78C00CD0EC9 = 6B6475F90867E78C00CD0EC9; + 6B6475FC0867E79600CD0EC9 = 6B6475FC0867E79600CD0EC9; + 6B6475FE0867E79600CD0EC9 = 6B6475FE0867E79600CD0EC9; + 6B6476010867EA2100CD0EC9 = 6B6476010867EA2100CD0EC9; + 6B6476050867EB7E00CD0EC9 = 6B6476050867EB7E00CD0EC9; + 6B6476060867EB7E00CD0EC9 = 6B6476060867EB7E00CD0EC9; + 6B64760B0867EFB500CD0EC9 = 6B64760B0867EFB500CD0EC9; + 6B64760C0867EFB500CD0EC9 = 6B64760C0867EFB500CD0EC9; + 6B64760D0867EFB500CD0EC9 = 6B64760D0867EFB500CD0EC9; + 6B64760E0867EFB500CD0EC9 = 6B64760E0867EFB500CD0EC9; + 6B64760F0867EFB500CD0EC9 = 6B64760F0867EFB500CD0EC9; + 6B6476140867EFFF00CD0EC9 = 6B6476140867EFFF00CD0EC9; + 6B6476150867EFFF00CD0EC9 = 6B6476150867EFFF00CD0EC9; + 6B64761A0867F07E00CD0EC9 = 6B64761A0867F07E00CD0EC9; + 6B64761B0867F07E00CD0EC9 = 6B64761B0867F07E00CD0EC9; + 6B64761E0867F09900CD0EC9 = 6B64761E0867F09900CD0EC9; + 6B64761F0867F09900CD0EC9 = 6B64761F0867F09900CD0EC9; + 6B6476220867F09900CD0EC9 = 6B6476220867F09900CD0EC9; + 6B6476270867F18D00CD0EC9 = 6B6476270867F18D00CD0EC9; + 6B6476280867F18D00CD0EC9 = 6B6476280867F18D00CD0EC9; + 6B64762C0867F1D800CD0EC9 = 6B64762C0867F1D800CD0EC9; + 6B64762D0867F1D800CD0EC9 = 6B64762D0867F1D800CD0EC9; + 6B6476320867F23E00CD0EC9 = 6B6476320867F23E00CD0EC9; + 6B6476330867F23E00CD0EC9 = 6B6476330867F23E00CD0EC9; + 6B6476390867F2EC00CD0EC9 = 6B6476390867F2EC00CD0EC9; + 6B64763C0867F31F00CD0EC9 = 6B64763C0867F31F00CD0EC9; + 6B64763D0867F31F00CD0EC9 = 6B64763D0867F31F00CD0EC9; + 6B6476440867F33D00CD0EC9 = 6B6476440867F33D00CD0EC9; + }; sourceControlManager = 6BF88AE705C4D57100D7EDBB; userBuildSettings = { }; @@ -1198,7 +1246,7 @@ uiCtxt = { sepNavIntBoundsRect = "{{0, 0}, {1154, 826}}"; sepNavSelRange = "{187, 0}"; - sepNavVisRect = "{{0, 0}, {711, 429}}"; + sepNavVisRect = "{{0, 0}, {705, 429}}"; sepNavWindowFrame = "{{262, 122}, {750, 558}}"; }; }; @@ -1277,11 +1325,449 @@ sepNavWindowFrame = "{{61, 222}, {793, 563}}"; }; }; + 6B64759E0867DB4A00CD0EC9 = { + comments = "warning: assignment makes integer from pointer without a cast"; + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + rLen = 1; + rLoc = 860; + rType = 1; + }; + 6B64759F0867DB4A00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "openURLSpec.launchFlags = NULL;"; + rLen = 34; + rLoc = 29993; + rType = 0; + vrLen = 604; + vrLoc = 28281; + }; + 6B6475B00867DD0C00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXBookmark; + }; + 6B6475B50867DD6F00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 228"; + rLen = 0; + rLoc = 10076; + rType = 0; + vrLen = 2201; + vrLoc = 9500; + }; + 6B6475B80867DDA500CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 231"; + rLen = 0; + rLoc = 10305; + rType = 0; + vrLen = 2365; + vrLoc = 9500; + }; + 6B6475BB0867DDC600CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 274"; + rLen = 0; + rLoc = 12285; + rType = 0; + vrLen = 1093; + vrLoc = 12062; + }; + 6B6475BE0867DE0500CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: refreshPostsWithDownload:"; + rLen = 0; + rLoc = 14165; + rType = 0; + vrLen = 1101; + vrLoc = 13932; + }; + 6B6475C10867DE5E00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 477"; + rLen = 0; + rLoc = 18343; + rType = 0; + vrLen = 786; + vrLoc = 17905; + }; + 6B6475C40867DEAD00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 965"; + rLen = 0; + rLoc = 33625; + rType = 0; + vrLen = 1381; + vrLoc = 32738; + }; + 6B6475C70867DF2C00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 983"; + rLen = 0; + rLoc = 34504; + rType = 0; + vrLen = 1781; + vrLoc = 34040; + }; + 6B6475CA0867DFB200CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: tableView:didClickTableColumn:"; + rLen = 0; + rLoc = 36675; + rType = 0; + vrLen = 1379; + vrLoc = 36001; + }; + 6B6475CD0867DFF100CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 1695"; + rLen = 0; + rLoc = 59971; + rType = 0; + vrLen = 1045; + vrLoc = 58839; + }; + 6B6475D00867E09000CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 1425"; + rLen = 0; + rLoc = 50351; + rType = 0; + vrLen = 1563; + vrLoc = 49431; + }; + 6B6475DA0867E18100CD0EC9 = { + uiCtxt = { + sepNavIntBoundsRect = "{{0, 0}, {962, 1302}}"; + sepNavSelRange = "{432, 50}"; + sepNavVisRect = "{{0, 351}, {919, 526}}"; + sepNavWindowFrame = "{{288, 103}, {964, 655}}"; + }; + }; + 6B6475EE0867E2B100CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "openURLSpec.launchFlags = NULL;"; + rLen = 34; + rLoc = 29993; + rType = 0; + vrLen = 579; + vrLoc = 28331; + }; + 6B6475EF0867E2B100CD0EC9 = { + comments = "error: NSFileManager-ESBExtensions.h: No such file or directory"; + fRef = 6B6475DA0867E18100CD0EC9; + isa = PBXTextBookmark; + rLen = 1; + rLoc = 10; + rType = 1; + }; + 6B6475F00867E2B100CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "openURLSpec.launchFlags = NULL;"; + rLen = 34; + rLoc = 29993; + rType = 0; + vrLen = 579; + vrLoc = 28331; + }; + 6B6475F10867E2B100CD0EC9 = { + fRef = 6B6475DA0867E18100CD0EC9; + isa = PBXTextBookmark; + name = "#import \"NSFileManager+ESBExtensions.h\""; + rLen = 40; + rLoc = 201; + rType = 0; + vrLen = 345; + vrLoc = 48; + }; + 6B6475F20867E2F000CD0EC9 = { + fRef = 6B6475DA0867E18100CD0EC9; + isa = PBXTextBookmark; + name = "#import \"NSFileManager+ESBExtensions.h\""; + rLen = 40; + rLoc = 201; + rType = 0; + vrLen = 345; + vrLoc = 48; + }; + 6B6475F90867E78C00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + rLen = 15; + rLoc = 10015; + rType = 0; + }; + 6B6475FC0867E79600CD0EC9 = { + fRef = 6B6475DA0867E18100CD0EC9; + isa = PBXTextBookmark; + name = "#import \"NSFileManager+ESBExtensions.h\""; + rLen = 40; + rLoc = 201; + rType = 0; + vrLen = 345; + vrLoc = 48; + }; + 6B6475FE0867E79600CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 227"; + rLen = 0; + rLoc = 10024; + rType = 0; + vrLen = 1134; + vrLoc = 9388; + }; + 6B6476010867EA2100CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXBookmark; + }; + 6B6476050867EB7E00CD0EC9 = { + fRef = 6B6475DA0867E18100CD0EC9; + isa = PBXTextBookmark; + name = "#import \"NSFileManager+ESBExtensions.h\""; + rLen = 40; + rLoc = 201; + rType = 0; + vrLen = 345; + vrLoc = 48; + }; + 6B6476060867EB7E00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 227"; + rLen = 0; + rLoc = 10024; + rType = 0; + vrLen = 1134; + vrLoc = 9388; + }; + 6B64760B0867EFB500CD0EC9 = { + fRef = 6B6475DA0867E18100CD0EC9; + isa = PBXTextBookmark; + name = "#import \"NSFileManager+ESBExtensions.h\""; + rLen = 40; + rLoc = 201; + rType = 0; + vrLen = 345; + vrLoc = 48; + }; + 6B64760C0867EFB500CD0EC9 = { + comments = "error: 'unfilteredURLs' undeclared (first use in this function)"; + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + rLen = 1; + rLoc = 349; + rType = 1; + }; + 6B64760D0867EFB500CD0EC9 = { + fRef = 6B6475DA0867E18100CD0EC9; + isa = PBXTextBookmark; + name = "#import \"NSFileManager+ESBExtensions.h\""; + rLen = 40; + rLoc = 201; + rType = 0; + vrLen = 345; + vrLoc = 48; + }; + 6B64760E0867EFB500CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "NSEnumerator * urlEnum = [unfilteredURLs objectEnumerator];"; + rLen = 62; + rLoc = 14306; + rType = 0; + vrLen = 449; + vrLoc = 14167; + }; + 6B64760F0867EFB500CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 227"; + rLen = 0; + rLoc = 10024; + rType = 0; + vrLen = 1134; + vrLoc = 9388; + }; + 6B6476140867EFFF00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "NSEnumerator * urlEnum = [unfilteredURLs objectEnumerator];"; + rLen = 62; + rLoc = 14306; + rType = 0; + vrLen = 449; + vrLoc = 14167; + }; + 6B6476150867EFFF00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 227"; + rLen = 0; + rLoc = 10024; + rType = 0; + vrLen = 1134; + vrLoc = 9388; + }; + 6B64761A0867F07E00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "NSEnumerator * urlEnum = [unfilteredURLs objectEnumerator];"; + rLen = 62; + rLoc = 14306; + rType = 0; + vrLen = 449; + vrLoc = 14167; + }; + 6B64761B0867F07E00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 227"; + rLen = 0; + rLoc = 10024; + rType = 0; + vrLen = 1134; + vrLoc = 9388; + }; + 6B64761E0867F09900CD0EC9 = { + fRef = 6B6475DA0867E18100CD0EC9; + isa = PBXTextBookmark; + rLen = 1; + rLoc = 22; + rType = 1; + }; + 6B64761F0867F09900CD0EC9 = { + fRef = 6B6475DA0867E18100CD0EC9; + isa = PBXTextBookmark; + name = "+ (NSString *)downloadFavIconForURL:(NSURL *)aURL"; + rLen = 50; + rLoc = 432; + rType = 0; + vrLen = 1710; + vrLoc = 522; + }; + 6B6476220867F09900CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = downloadFavIconForURL; + rLen = 21; + rLoc = 14597; + rType = 0; + vrLen = 1249; + vrLoc = 13837; + }; + 6B6476270867F18D00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "NSEnumerator * urlEnum = [unfilteredURLs objectEnumerator];"; + rLen = 62; + rLoc = 14306; + rType = 0; + vrLen = 449; + vrLoc = 14167; + }; + 6B6476280867F18D00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 227"; + rLen = 0; + rLoc = 10024; + rType = 0; + vrLen = 1134; + vrLoc = 9388; + }; + 6B64762C0867F1D800CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "NSEnumerator * urlEnum = [unfilteredURLs objectEnumerator];"; + rLen = 62; + rLoc = 14306; + rType = 0; + vrLen = 449; + vrLoc = 14167; + }; + 6B64762D0867F1D800CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 227"; + rLen = 0; + rLoc = 10024; + rType = 0; + vrLen = 1134; + vrLoc = 9388; + }; + 6B6476320867F23E00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "NSEnumerator * urlEnum = [unfilteredURLs objectEnumerator];"; + rLen = 62; + rLoc = 14306; + rType = 0; + vrLen = 449; + vrLoc = 14167; + }; + 6B6476330867F23E00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 227"; + rLen = 0; + rLoc = 10024; + rType = 0; + vrLen = 1134; + vrLoc = 9388; + }; + 6B6476390867F2EC00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXBookmark; + }; + 6B64763C0867F31F00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "NSEnumerator * urlEnum = [unfilteredURLs objectEnumerator];"; + rLen = 62; + rLoc = 14306; + rType = 0; + vrLen = 449; + vrLoc = 14167; + }; + 6B64763D0867F31F00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 227"; + rLen = 0; + rLoc = 10024; + rType = 0; + vrLen = 1134; + vrLoc = 9388; + }; + 6B6476440867F33D00CD0EC9 = { + fRef = 6BF88AE405C4D56000D7EDBB; + isa = PBXTextBookmark; + name = "AppController.m: 352"; + rLen = 0; + rLoc = 14414; + rType = 0; + vrLen = 1075; + vrLoc = 13991; + }; 6B698143065346D6000649B0 = { uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {711, 1022}}"; - sepNavSelRange = "{449, 0}"; - sepNavVisRect = "{{0, 0}, {711, 429}}"; + sepNavIntBoundsRect = "{{0, 0}, {711, 1078}}"; + sepNavSelRange = "{2862, 0}"; + sepNavVisRect = "{{0, 649}, {711, 429}}"; sepNavWindowFrame = "{{84, 206}, {750, 558}}"; }; }; @@ -1974,18 +2460,18 @@ }; 6BF88AE305C4D56000D7EDBB = { uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {711, 2492}}"; - sepNavSelRange = "{1705, 10}"; - sepNavVisRect = "{{0, 647}, {711, 429}}"; + sepNavIntBoundsRect = "{{0, 0}, {818, 2618}}"; + sepNavSelRange = "{4597, 0}"; + sepNavVisRect = "{{0, 1794}, {705, 429}}"; sepNavWindowFrame = "{{2, 264}, {750, 558}}"; }; }; 6BF88AE405C4D56000D7EDBB = { uiCtxt = { - sepNavIntBoundsRect = "{{0, 0}, {1736, 22848}}"; - sepNavSelRange = "{7762, 133}"; - sepNavVisRect = "{{0, 2498}, {603, 171}}"; - sepNavWindowFrame = "{{38, 151}, {964, 655}}"; + sepNavIntBoundsRect = "{{0, 0}, {1736, 23968}}"; + sepNavSelRange = "{14414, 0}"; + sepNavVisRect = "{{0, 4733}, {919, 526}}"; + sepNavWindowFrame = "{{197, 153}, {964, 655}}"; }; }; 6BF88AE705C4D57100D7EDBB = { diff --git a/Delicious Client.xcode/project.pbxproj b/Delicious Client.xcode/project.pbxproj index 0c5bf5b..44480db 100644 --- a/Delicious Client.xcode/project.pbxproj +++ b/Delicious Client.xcode/project.pbxproj @@ -164,6 +164,7 @@ }; 29B97317FDCFA39411CA2CEA = { children = ( + 6B6475D30867E11800CD0EC9, 6B0896C60838FCEF0057A654, 6B0896C70838FCEF0057A654, 6BF009AD077B05B30036723E, @@ -1045,6 +1046,7 @@ files = ( 6B47BF5E086345C500636CB2, 6B47BF61086345CF00636CB2, + 6B6475D40867E11800CD0EC9, ); isa = PBXResourcesBuildPhase; runOnlyForDeploymentPostprocessing = 0; @@ -1052,6 +1054,9 @@ 6B47BF58086345A400636CB2 = { buildActionMask = 2147483647; files = ( + 6B6475D80867E14A00CD0EC9, + 6B6475DC0867E18100CD0EC9, + 6B6475E10867E1EC00CD0EC9, ); isa = PBXSourcesBuildPhase; runOnlyForDeploymentPostprocessing = 0; @@ -1189,6 +1194,9 @@ dstSubfolderSpec = 1; files = ( 6B47BF6F0863468D00636CB2, + 6B6475D70867E14A00CD0EC9, + 6B6475DB0867E18100CD0EC9, + 6B6475E00867E1EC00CD0EC9, ); isa = PBXCopyFilesBuildPhase; runOnlyForDeploymentPostprocessing = 0; @@ -1306,6 +1314,152 @@ settings = { }; }; + 6B6475D30867E11800CD0EC9 = { + isa = PBXFileReference; + lastKnownFileType = image.tiff; + name = default_favicon.tif; + path = Art/default_favicon.tif; + refType = 4; + sourceTree = ""; + }; + 6B6475D40867E11800CD0EC9 = { + fileRef = 6B6475D30867E11800CD0EC9; + isa = PBXBuildFile; + settings = { + }; + }; + 6B6475D50867E14A00CD0EC9 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = EBIconAndTextCell.h; + path = UI/EBIconAndTextCell.h; + refType = 4; + sourceTree = ""; + }; + 6B6475D60867E14A00CD0EC9 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + name = EBIconAndTextCell.m; + path = UI/EBIconAndTextCell.m; + refType = 4; + sourceTree = ""; + }; + 6B6475D70867E14A00CD0EC9 = { + fileRef = 6B6475D50867E14A00CD0EC9; + isa = PBXBuildFile; + settings = { + }; + }; + 6B6475D80867E14A00CD0EC9 = { + fileRef = 6B6475D60867E14A00CD0EC9; + isa = PBXBuildFile; + settings = { + }; + }; + 6B6475D90867E18100CD0EC9 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = EBFavIconUtils.h; + path = Utilities/EBFavIconUtils.h; + refType = 4; + sourceTree = ""; + }; + 6B6475DA0867E18100CD0EC9 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + name = EBFavIconUtils.m; + path = Utilities/EBFavIconUtils.m; + refType = 4; + sourceTree = ""; + }; + 6B6475DB0867E18100CD0EC9 = { + fileRef = 6B6475D90867E18100CD0EC9; + isa = PBXBuildFile; + settings = { + }; + }; + 6B6475DC0867E18100CD0EC9 = { + fileRef = 6B6475DA0867E18100CD0EC9; + isa = PBXBuildFile; + settings = { + }; + }; + 6B6475DE0867E1EC00CD0EC9 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = "NSFileManager+ESBExtensions.h"; + path = "Utilities/NSFileManager+ESBExtensions.h"; + refType = 4; + sourceTree = ""; + }; + 6B6475DF0867E1EC00CD0EC9 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.objc; + name = "NSFileManager+ESBExtensions.m"; + path = "Utilities/NSFileManager+ESBExtensions.m"; + refType = 4; + sourceTree = ""; + }; + 6B6475E00867E1EC00CD0EC9 = { + fileRef = 6B6475DE0867E1EC00CD0EC9; + isa = PBXBuildFile; + settings = { + }; + }; + 6B6475E10867E1EC00CD0EC9 = { + fileRef = 6B6475DF0867E1EC00CD0EC9; + isa = PBXBuildFile; + settings = { + }; + }; + 6B6475E20867E26500CD0EC9 = { + fileRef = 6B6475D30867E11800CD0EC9; + isa = PBXBuildFile; + settings = { + }; + }; + 6B6475F30867E2F800CD0EC9 = { + fileRef = 6B6475D50867E14A00CD0EC9; + isa = PBXBuildFile; + settings = { + }; + }; + 6B6475F40867E2FB00CD0EC9 = { + fileRef = 6B6475D60867E14A00CD0EC9; + isa = PBXBuildFile; + settings = { + }; + }; + 6B6475F50867E31500CD0EC9 = { + fileRef = 6B6475DE0867E1EC00CD0EC9; + isa = PBXBuildFile; + settings = { + }; + }; + 6B6475F60867E31600CD0EC9 = { + fileRef = 6B6475DF0867E1EC00CD0EC9; + isa = PBXBuildFile; + settings = { + }; + }; + 6B6475F70867E31700CD0EC9 = { + fileRef = 6B6475D90867E18100CD0EC9; + isa = PBXBuildFile; + settings = { + }; + }; + 6B6475F80867E31700CD0EC9 = { + fileRef = 6B6475DA0867E18100CD0EC9; + isa = PBXBuildFile; + settings = { + }; + }; 6B698143065346D6000649B0 = { fileEncoding = 4; isa = PBXFileReference; @@ -1642,6 +1796,10 @@ 6B6B69DF082B3C97003DFA07, 6BA936C20655EEC400AE7890, 6BA936C30655EEC400AE7890, + 6B6475DE0867E1EC00CD0EC9, + 6B6475DF0867E1EC00CD0EC9, + 6B6475D90867E18100CD0EC9, + 6B6475DA0867E18100CD0EC9, 6B212599071D935F0044339D, 6B21259A071D935F0044339D, ); @@ -1652,6 +1810,8 @@ }; 6BD8D4B607205221008CA80A = { children = ( + 6B6475D50867E14A00CD0EC9, + 6B6475D60867E14A00CD0EC9, 6B61ACDB0846382500A27692, 6B61ACDA0846382500A27692, 6B13FAE406E10A6F007B4E5F, @@ -1902,6 +2062,9 @@ 6B0896D70838FD090057A654, 6B61ACDD0846382500A27692, 6B6AA443084A7B0F00FDB683, + 6B6475F30867E2F800CD0EC9, + 6B6475F50867E31500CD0EC9, + 6B6475F70867E31700CD0EC9, ); isa = PBXHeadersBuildPhase; runOnlyForDeploymentPostprocessing = 0; @@ -1919,6 +2082,7 @@ 8D11072B0486CEB800E47090, 6BA939AE065692DC00AE7890, 6B0D0363072EC7B6009A7725, + 6B6475E20867E26500CD0EC9, 3B3F0229073EF1A3003AB1C7, 6BB0A8E9074FF64B008FDDCC, 6B3AC8840759162F00F20C1F, @@ -1973,6 +2137,9 @@ 6B0896D80838FD090057A654, 6B61ACDC0846382500A27692, 6B6AA444084A7B0F00FDB683, + 6B6475F40867E2FB00CD0EC9, + 6B6475F60867E31600CD0EC9, + 6B6475F80867E31700CD0EC9, ); isa = PBXSourcesBuildPhase; runOnlyForDeploymentPostprocessing = 0; diff --git a/UI/EBIconAndTextCell.h b/UI/EBIconAndTextCell.h new file mode 100644 index 0000000..a88cc29 --- /dev/null +++ b/UI/EBIconAndTextCell.h @@ -0,0 +1,41 @@ +// +// EBIconAndTextCell.h +// Delicious Client +// +// Created by Eric Blair on 6/2/05. +// Copyright 2005 __MyCompanyName__. All rights reserved. +// + +#import + + +@interface EBIconAndTextCell : NSTextFieldCell { + NSImage *favIcon; + NSString *favIconPath; + NSString *description; + NSSize iconSize; + NSImage *defaultIcon; + BOOL regenFavIcon; +} + +- (id)initWithDefaultIcon: (NSImage *)newDefaultIcon; + +- (id)initWithFavIconPath: (NSString *)newFavIconPath + description: (NSString *)newDescription + iconSize: (NSSize)newIconSize + defaultIcon: (NSImage *)newDefaultIcon; + +- (void)setFavIcon: (NSImage *)newFavIcon; +- (NSImage* )favIcon; +- (void)setFavIconPath: (NSString *)newFavIconPath; +- (NSString *)favIconPath; +- (void)setDescription: (NSString *)newDescription; +- (NSString *)description; +- (void)setIconSize:(NSSize)newIconSize; +- (NSSize)iconSize; +- (void)setDefaultIcon: (NSImage *)newDefaultIcon; +- (NSImage *)defaultIcon; +- (void)setRegenFavIcon: (BOOL)newRegenFavIcon; +- (BOOL)regenFavIcon; + +@end diff --git a/UI/EBIconAndTextCell.m b/UI/EBIconAndTextCell.m new file mode 100644 index 0000000..9488f65 --- /dev/null +++ b/UI/EBIconAndTextCell.m @@ -0,0 +1,239 @@ +// +// EBIconAndTextCell.m +// Delicious Client +// +// Created by Eric Blair on 6/2/05. +// Copyright 2005 __MyCompanyName__. All rights reserved. +// + +#import "EBIconAndTextCell.h" + +#define kImageMargin 3.0 +#define kFavIconPathCoderKey (@"FavIconPath") +#define kDescriptionCoderKey (@"Description") +#define kIconSizeKey (@"IconSize") +#define kDefaultIconCoderKey (@"DefaultIconPath") + +@interface EBIconAndTextCell (Private) +- (void)generateFavIcon; +@end + +@implementation EBIconAndTextCell + +- (id)copyWithZone: (NSZone *)zone { + EBIconAndTextCell * cellCopy = [super copyWithZone: zone]; + + cellCopy->favIcon = nil; + [cellCopy setFavIcon: [self favIcon]]; + cellCopy->favIconPath = nil; + [cellCopy setFavIconPath: [self favIconPath]]; + cellCopy->description = nil; + [cellCopy setDescription: [self description]]; + [cellCopy setIconSize:[self iconSize]]; + cellCopy->defaultIcon = nil; + [cellCopy setDefaultIcon: [self defaultIcon]]; + cellCopy->regenFavIcon = YES; + [cellCopy setRegenFavIcon: [self regenFavIcon]]; + + return cellCopy; +} + +- (id)initWithCoder: (NSCoder *)decoder { + self = [super initWithCoder: decoder]; + + favIcon = nil; + favIconPath = [[decoder decodeObjectForKey: kFavIconPathCoderKey] retain]; + description = [[decoder decodeObjectForKey: kDescriptionCoderKey] retain]; + iconSize = [decoder decodeSizeForKey: kIconSizeKey]; + defaultIcon = [[decoder decodeObjectForKey: kDefaultIconCoderKey] retain]; + regenFavIcon = YES; + + return self; +} + +- (void)encodeWithCoder: (NSCoder*)encoder { + [super encodeWithCoder: encoder]; + + [encoder encodeObject: [self favIconPath] forKey: kFavIconPathCoderKey]; + [encoder encodeObject: [self description] forKey: kDescriptionCoderKey]; + [encoder encodeSize: [self iconSize] forKey: kIconSizeKey]; + [encoder encodeObject: [self defaultIcon] forKey: kDefaultIconCoderKey]; +} + +- (id)init { + return [self initWithFavIconPath:@"" description:@"" iconSize:NSZeroSize defaultIcon:nil]; +} + +- (id)initTextCell: (NSString *)aText { + return [self initWithFavIconPath:@"" description:aText iconSize:NSZeroSize defaultIcon:nil]; +} + +- (id)initImageCell: (NSImage *)anImage { + return [self initWithFavIconPath:@"" description:@"" iconSize:NSZeroSize defaultIcon:nil]; +} + +- (id)initWithDefaultIcon: (NSImage *)newDefaultIcon { + return [self initWithFavIconPath:@"" description:@"" iconSize:NSZeroSize defaultIcon:newDefaultIcon]; +} + +- (id)initWithFavIconPath: (NSString *)newFavIconPath + description: (NSString *)newDescription + iconSize: (NSSize)newIconSize + defaultIcon: (NSImage *)newDefaultIcon { + + self = [super initTextCell:newDescription]; + + if(self) { + [self setFavIconPath: newFavIconPath]; + [self setIconSize: newIconSize]; + [self setDefaultIcon: newDefaultIcon]; + } + + return self; +} + +- (void)dealloc +{ + [favIcon release]; + [favIconPath release]; + [description release]; + [defaultIcon release]; + + [super dealloc]; +} + +- (NSSize)cellSize +{ + NSSize cellSize = [super cellSize]; + NSSize imageSize = NSZeroSize; + NSSize result = NSZeroSize; + + [self generateFavIcon]; + + imageSize = [[self favIcon] size]; + imageSize.width += kImageMargin; + imageSize.height += kImageMargin; + + result.width = cellSize.width + imageSize.width; + result.height = MAX(imageSize.height, result.height); + + return result; +} + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { + [self generateFavIcon]; + + NSSize imageSize; + NSRect imageFrame; + + imageSize = [[self favIcon] size]; + NSDivideRect(cellFrame, &imageFrame, &cellFrame, 2*kImageMargin + imageSize.width, NSMinXEdge); + if([self drawsBackground]) { + [[self backgroundColor] set]; + NSRectFill(imageFrame); + } + imageFrame.origin.x += kImageMargin; + imageFrame.size = imageSize; + + if([controlView isFlipped]) + imageFrame.origin.y += ceil((cellFrame.size.height + imageFrame.size.height) / 2); + else + imageFrame.origin.y += ceil((cellFrame.size.height - imageFrame.size.height) / 2); + + [[self favIcon] compositeToPoint:imageFrame.origin operation:NSCompositeSourceOver]; + + [super drawWithFrame:cellFrame inView:controlView]; +} + +- (void)setFavIcon: (NSImage *)newFavIcon { + if(favIcon != newFavIcon) { + [favIcon release]; + favIcon = [newFavIcon copy]; + } +} + +- (NSImage* )favIcon { + return [[favIcon retain] autorelease]; +} + +- (void)setFavIconPath: (NSString *)newFavIconPath { + if(favIconPath != newFavIconPath) { + [favIconPath release]; + favIconPath = [newFavIconPath copy]; + + // When we update the path, we should flag the favIcon for regen. + [self setRegenFavIcon: YES]; + } +} + +- (NSString *)favIconPath { + return [[favIconPath retain] autorelease]; +} + +- (void)setDescription: (NSString *)newDescription { + if(description != newDescription) { + [description release]; + description = [newDescription copy]; + } +} + +- (NSString *)description { + return [[description retain] autorelease]; +} + + +- (void)setIconSize:(NSSize)newIconSize +{ + iconSize = newIconSize; +} + +- (NSSize)iconSize +{ + return iconSize; +} + +- (void)setDefaultIcon: (NSImage *)newDefaultIcon { + if(defaultIcon != newDefaultIcon) { + [defaultIcon release]; + defaultIcon = [newDefaultIcon copy]; + } +} + +- (NSImage *)defaultIcon { + return [[defaultIcon retain] autorelease]; +} + +- (void)setRegenFavIcon:(BOOL)newRegenFavIcon { + regenFavIcon = newRegenFavIcon; +} + +- (BOOL)regenFavIcon { + return regenFavIcon; +} + +@end + +@implementation EBIconAndTextCell (Private) + +- (void)generateFavIcon { + // Kick out if we have a fav icon and we don't need to regen + if([self favIcon] != nil && ![self regenFavIcon]) + return; + // Try to get a stored fav icon for the cell + BOOL gotFavIcon = NO; + if([[NSFileManager defaultManager] fileExistsAtPath:[self favIconPath]]) { + [self setFavIcon: [[NSImage alloc] initWithContentsOfFile:[self favIconPath]]]; + // Check if the data at the given path could be converted to an NSImage + gotFavIcon = ([self favIcon] != nil); + } + // Fall back to the default icon + if(!gotFavIcon) + [self setFavIcon: [self defaultIcon]]; + + if(NSEqualSizes([self iconSize], NSZeroSize) == NO) { + [[self favIcon] setScalesWhenResized:YES]; + [[self favIcon] setSize: iconSize]; + } +} + +@end diff --git a/Utilities/EBFavIconUtils.h b/Utilities/EBFavIconUtils.h new file mode 100644 index 0000000..869f102 --- /dev/null +++ b/Utilities/EBFavIconUtils.h @@ -0,0 +1,18 @@ +// +// EBFavIconUtils.h +// Delicious Client +// +// Created by Eric Blair on 6/4/05. +// Copyright 2005 __MyCompanyName__. All rights reserved. +// + +#import + +@interface EBFavIconUtils : NSObject { + +} + ++ (NSString *)downloadFavIconForURL:(NSURL *)aURL + forceDownload:(BOOL)aForceDownload; + +@end diff --git a/Utilities/EBFavIconUtils.m b/Utilities/EBFavIconUtils.m new file mode 100644 index 0000000..1faee7c --- /dev/null +++ b/Utilities/EBFavIconUtils.m @@ -0,0 +1,101 @@ +// +// EBFavIconUtils.m +// Delicious Client +// +// Created by Eric Blair on 6/4/05. +// Copyright 2005 __MyCompanyName__. All rights reserved. +// + +#import "EBFavIconUtils.h" +#import +#import "NSFileManager+ESBExtensions.h" +#import "defines.h" + +@interface EBFavIconUtils (Private) + ++ (NSURL *)favIconURLForURL:(NSURL *)aURL; ++ (NSString *)createFileNameForURL: (NSURL *)aURL; + +@end + +@implementation EBFavIconUtils + ++ (NSString *)downloadFavIconForURL:(NSURL *)aURL + forceDownload:(BOOL)aForceDownload { + + NSString * favIconFolder = [[NSFileManager defaultManager] getApplicationSupportSubpath: @"FavIcons"]; + NSString * favIconName = [EBFavIconUtils createFileNameForURL: aURL]; + + if (favIconName == nil) { + return nil; + } + + NSString * favIconPath = [favIconFolder stringByAppendingPathComponent:favIconName]; + + // If we're purging the cache or the file doesn't exist, pull it down from the web + // Should set favIconPath to "" if the file doesn't exist on the server. + if(aForceDownload || ![[NSFileManager defaultManager] fileExistsAtPath:favIconPath]) { + BOOL proceed = YES; + + // If the file exists, delete it - cache purce + // If we can't delete, stop processing since we don't have write access to the required path. + if([[NSFileManager defaultManager] fileExistsAtPath:favIconPath]) + proceed = [[NSFileManager defaultManager] removeFileAtPath:favIconPath handler:nil]; + + if(proceed) { + // This somehow needs to be augmented to look at the contents of any html + // file located at the URL - we should give precendence to the LINK tags. + NSURL * faviconURL = [EBFavIconUtils favIconURLForURL:aURL]; + NSMutableURLRequest * req = [NSMutableURLRequest requestWithURL:faviconURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.0]; + [req setValue:kUSER_AGENT forHTTPHeaderField:@"User-Agent"]; + + NSURLResponse * resp; + NSError * error; + + NSData *returnData = [NSURLConnection sendSynchronousRequest: req returningResponse: &resp error: &error]; + + if (!error) { + NSImage * favicon = [[NSImage alloc] initWithData: returnData]; + + //save to disk + if(favicon != nil) + [returnData writeToFile: favIconPath atomically:YES]; + + [favicon release]; + } + else + NSLog(@"%@", error); + } + } + + return favIconPath; +} + +@end + +@implementation EBFavIconUtils (Private) + ++ (NSString *)createFileNameForURL: (NSURL *)aURL { + if ([aURL host] == nil) { + return nil; + } + + NSMutableString * hostName = [NSMutableString stringWithString: [aURL host]]; + + // Convert '.' to '_' + [hostName replaceOccurrencesOfString:@"." + withString:@"_" + options:nil + range:NSMakeRange(0, [hostName length])]; + + [hostName appendString:@".ico"]; + + return [[hostName copy] autorelease]; +} + ++ (NSURL *)favIconURLForURL:(NSURL *)aURL { + NSURL * faviconURL = [NSURL URLWithString: [NSString stringWithFormat:@"http://%@/favicon.ico", [aURL host]]]; + return [[faviconURL copy] autorelease]; +} + +@end diff --git a/Utilities/NSFileManager+ESBExtensions.h b/Utilities/NSFileManager+ESBExtensions.h new file mode 100644 index 0000000..83d539b --- /dev/null +++ b/Utilities/NSFileManager+ESBExtensions.h @@ -0,0 +1,35 @@ +// +// NSFoundation+ESBExtension.h +// ESBFoundation +// +// Created by Eric Blair on Wed Sep 17 2003. +// Copyright (c) 2003 __MyCompanyName__. All rights reserved. +// + +#import + + +@interface NSFileManager (ESBExtensions) + +// Directory manipulations +- (BOOL)directoryExistsAtPath:(NSString *)path; +- (BOOL)directoryExistsAtPath:(NSString *)path + traverseLink:(BOOL)traverseLink; + +// Creates any directories needed to be able to create a file at the specified +// path. Raises an exception on failure. +- (void)createPathToFile:(NSString *)path + attributes:(NSDictionary *)attributes; +- (void)createPath:(NSString *)path + attributes:(NSDictionary *)attributes; + +// Returns the ~/Library/Application Support/ folder, +// creating any necessary folders along the path hierarchy. +- (NSString *)getApplicationSupportFolder; + +// Returns a folder path within the +// ~/Library/Application Support// folder, creating +// the any necessary folders along the path hierarchy. +- (NSString *)getApplicationSupportSubpath:(NSString *)subPath; + +@end diff --git a/Utilities/NSFileManager+ESBExtensions.m b/Utilities/NSFileManager+ESBExtensions.m new file mode 100644 index 0000000..10642a0 --- /dev/null +++ b/Utilities/NSFileManager+ESBExtensions.m @@ -0,0 +1,121 @@ +// +// NSFoundation+ESBExtension.m +// ESBFoundation +// +// Created by Eric Blair on Wed Sep 17 2003. +// Copyright (c) 2003 __MyCompanyName__. All rights reserved. +// + +#import "NSFileManager+ESBExtensions.h" + + +@implementation NSFileManager (ESBExtensions) + +- (BOOL)directoryExistsAtPath:(NSString *)path +{ + return [self directoryExistsAtPath:path traverseLink:NO]; +} + +- (BOOL)directoryExistsAtPath:(NSString *)path + traverseLink:(BOOL)traverseLink +{ + NSDictionary * attributes; + + attributes = [self fileAttributesAtPath:path traverseLink:traverseLink]; + + return [[attributes objectForKey:NSFileType] isEqualToString:NSFileTypeDirectory]; +} + +- (void)createPathToFile:(NSString *)path + attributes:(NSDictionary *)attributes +{ + NSArray * pathComponents; + unsigned int dirCount; + NSString * finalDirectory; + + pathComponents = [path pathComponents]; + dirCount = [pathComponents count] - 1; + + // Short-circuit if the final directory already exists + finalDirectory = + [NSString pathWithComponents: + [pathComponents subarrayWithRange:NSMakeRange(0, dirCount)]]; + [self createPath:finalDirectory attributes:attributes]; +} + +- (void)createPath:(NSString *)path + attributes:(NSDictionary *)attributes +{ + NSArray * pathComponents; + unsigned int dirIndex, dirCount; + unsigned int startingIndex; + + pathComponents = [path pathComponents]; + dirCount = [pathComponents count]; + // Short-circuit if the final directory already exists + path = + [NSString pathWithComponents: + [pathComponents subarrayWithRange:NSMakeRange(0, dirCount)]]; + + if ([self directoryExistsAtPath:path traverseLink:YES]) + return; + + startingIndex = 0; + + for (dirIndex = startingIndex; dirIndex < dirCount; dirIndex++) { + NSString *partialPath; + BOOL fileExists; + + partialPath = + [NSString pathWithComponents: + [pathComponents subarrayWithRange:NSMakeRange(0, dirIndex + 1)]]; + + // Don't use the 'fileExistsAtPath:isDirectory:' version since it doesn't traverse symlinks + fileExists = [self fileExistsAtPath:partialPath]; + if (!fileExists) { + if (![self createDirectoryAtPath:partialPath attributes:attributes]) + [NSException raise:NSGenericException + format:@"Unable to create a directory at path: %@", partialPath]; + } else { + NSDictionary *attributes; + + attributes = [self fileAttributesAtPath:partialPath traverseLink:YES]; + if (![[attributes objectForKey:NSFileType] isEqualToString: NSFileTypeDirectory]) { + [NSException raise:NSGenericException + format:@"Unable to write to path \"%@\" because \"%@\" is not a directory", + path, partialPath]; + } + } + } +} + +- (NSString *)getApplicationSupportFolder +{ + NSArray * domains = + NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, + NSUserDomainMask, + YES); + NSString * baseDir = [domains objectAtIndex:0]; + NSString * allSupportDir = + [baseDir stringByAppendingPathComponent:@"Application Support"]; + NSString * appSupportDir = + [allSupportDir stringByAppendingPathComponent: + [self displayNameAtPath:[[NSBundle mainBundle] bundlePath]]]; + + [self createPath:appSupportDir attributes:nil]; + + return [[appSupportDir copy] autorelease]; +} + +- (NSString *)getApplicationSupportSubpath:(NSString *)subPath +{ + NSString * appSupportDir = [self getApplicationSupportFolder]; + NSString * appSupportSubdir = + [appSupportDir stringByAppendingPathComponent:subPath]; + + [self createPath:appSupportSubdir attributes:nil]; + + return [[appSupportSubdir copy] autorelease]; +} + +@end diff --git a/defines.h b/defines.h index e9bedcf..2d1bc1c 100644 --- a/defines.h +++ b/defines.h @@ -70,4 +70,8 @@ #define kRATING_TAG_CHARACTER @"*" #define kTAGLIST_TAG_COLUMN_IDENTIFIER @"tags" -#define kSAFARI_HISTORY_PATH @"~/Library/Safari/History.plist" \ No newline at end of file +#define kSAFARI_HISTORY_PATH @"~/Library/Safari/History.plist" + +#define kWebURLPboardType @"CorePasteboardFlavorType 0x75726C20" +#define kWebURLNamePboardType @"CorePasteboardFlavorType 0x75726C6E" +#define kWebURLsWithTitlesPboardType @"WebURLsWithTitlesPboardType" \ No newline at end of file