2 * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #import <WebKit/WebIconDatabase.h>
31 #import <WebKit/WebIconDatabasePrivate.h>
32 #import <WebKit/WebFileDatabase.h>
33 #import <WebKit/WebKitLogging.h>
34 #import <WebKit/WebNSURLExtras.h>
35 #import <WebKit/WebKitNSStringExtras.h>
37 NSString * const WebIconDatabaseVersionKey = @"WebIconDatabaseVersion";
38 NSString * const WebURLToIconURLKey = @"WebSiteURLToIconURLKey";
40 NSString * const ObsoleteIconsOnDiskKey = @"WebIconsOnDisk";
41 NSString * const ObsoleteIconURLToURLsKey = @"WebIconURLToSiteURLs";
43 static const int WebIconDatabaseCurrentVersion = 2;
45 NSString *WebIconDatabaseDidAddIconNotification = @"WebIconDatabaseDidAddIconNotification";
46 NSString *WebIconNotificationUserInfoURLKey = @"WebIconNotificationUserInfoURLKey";
47 NSString *WebIconDatabaseDidRemoveAllIconsNotification = @"WebIconDatabaseDidRemoveAllIconsNotification";
49 NSString *WebIconDatabaseDirectoryDefaultsKey = @"WebIconDatabaseDirectoryDefaultsKey";
50 NSString *WebIconDatabaseEnabledDefaultsKey = @"WebIconDatabaseEnabled";
52 NSSize WebIconSmallSize = {16, 16};
53 NSSize WebIconMediumSize = {32, 32};
54 NSSize WebIconLargeSize = {128, 128};
56 @interface NSMutableDictionary (WebIconDatabase)
57 - (void)_web_setObjectUsingSetIfNecessary:(id)object forKey:(id)key;
60 @interface WebIconDatabase (WebInternal)
61 - (void)_clearDictionaries;
62 - (void)_createFileDatabase;
63 - (void)_loadIconDictionaries;
64 - (void)_updateFileDatabase;
65 - (void)_forgetIconForIconURLString:(NSString *)iconURLString;
66 - (NSMutableDictionary *)_iconsForIconURLString:(NSString *)iconURL;
67 - (NSImage *)_iconForFileURL:(NSString *)fileURL withSize:(NSSize)size;
68 - (void)_retainIconForIconURLString:(NSString *)iconURL;
69 - (void)_releaseIconForIconURLString:(NSString *)iconURL;
70 - (void)_retainOriginalIconsOnDisk;
71 - (void)_releaseOriginalIconsOnDisk;
72 - (void)_sendNotificationForURL:(NSString *)URL;
73 - (int)_totalRetainCountForIconURLString:(NSString *)iconURLString;
74 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons;
75 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon;
76 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache;
77 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size;
81 @implementation WebIconDatabase
83 + (WebIconDatabase *)sharedIconDatabase
85 static WebIconDatabase *database = nil;
89 double start = CFAbsoluteTimeGetCurrent();
91 database = [[WebIconDatabase alloc] init];
93 LOG(Timing, "initializing icon database with %d sites and %d icons took %f",
94 [database->_private->pageURLToIconURL count], [database->_private->iconURLToPageURLs count], (CFAbsoluteTimeGetCurrent() - start));
104 _private = [[WebIconDatabasePrivate alloc] init];
106 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
107 NSDictionary *initialDefaults = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:YES], WebIconDatabaseEnabledDefaultsKey, nil];
108 [defaults registerDefaults:initialDefaults];
109 [initialDefaults release];
110 if (![defaults boolForKey:WebIconDatabaseEnabledDefaultsKey]) {
114 [self _createFileDatabase];
115 [self _loadIconDictionaries];
117 _private->iconURLToIcons = [[NSMutableDictionary alloc] init];
118 _private->iconURLToExtraRetainCount = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
119 _private->pageURLToRetainCount = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
120 _private->iconsToEraseWithURLs = [[NSMutableSet alloc] init];
121 _private->iconsToSaveWithURLs = [[NSMutableSet alloc] init];
122 _private->iconURLsWithNoIcons = [[NSMutableSet alloc] init];
124 [[NSNotificationCenter defaultCenter] addObserver:self
125 selector:@selector(_applicationWillTerminate:)
126 name:NSApplicationWillTerminateNotification
129 // Retain icons on disk then release them once clean-up has begun.
130 // This gives the client the opportunity to retain them before they are erased.
131 [self _retainOriginalIconsOnDisk];
132 [self performSelector:@selector(_releaseOriginalIconsOnDisk) withObject:nil afterDelay:0];
137 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size cache:(BOOL)cache
142 if (!URL || ![self _isEnabled]) {
143 return [self defaultIconWithSize:size];
146 if ([URL _webkit_isFileURL]) {
147 return [self _iconForFileURL:URL withSize:size];
150 NSString *iconURLString = [_private->pageURLToIconURL objectForKey:URL];
151 if (!iconURLString) {
153 return [self defaultIconWithSize:size];
156 NSMutableDictionary *icons = [self _iconsForIconURLString:iconURLString];
158 if (![_private->iconURLsWithNoIcons containsObject:iconURLString]) {
159 ERROR("WebIconDatabase said it had %@, but it doesn't.", iconURLString);
161 return [self defaultIconWithSize:size];
164 return [self _iconFromDictionary:icons forSize:size cache:cache];
167 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size
169 return [self iconForURL:URL withSize:size cache:YES];
172 - (NSString *)iconURLForURL:(NSString *)URL
174 if (![self _isEnabled]) {
177 return URL ? [_private->pageURLToIconURL objectForKey:URL] : nil;
180 - (NSImage *)defaultIconWithSize:(NSSize)size
185 if (!_private->defaultIcons) {
186 NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"url_icon" ofType:@"tiff"];
188 NSImage *icon = [[NSImage alloc] initByReferencingFile:path];
189 _private->defaultIcons = [[NSMutableDictionary dictionaryWithObject:icon
190 forKey:[NSValue valueWithSize:[icon size]]] retain];
195 return [self _iconFromDictionary:_private->defaultIcons forSize:size cache:YES];
198 - (void)retainIconForURL:(NSString *)URL
202 if (![self _isEnabled]) {
206 int retainCount = (int)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, URL);
207 CFDictionarySetValue(_private->pageURLToRetainCount, URL, (void *)(retainCount + 1));
210 - (void)releaseIconForURL:(NSString *)pageURL
214 if (![self _isEnabled]) {
218 int retainCount = (int)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, pageURL);
220 if (retainCount <= 0) {
221 ERROR("The icon for %@ was released more times than it was retained.", pageURL);
225 int newRetainCount = retainCount - 1;
227 if (newRetainCount == 0) {
228 // Forget association between this page URL and a retain count
229 CFDictionaryRemoveValue(_private->pageURLToRetainCount, pageURL);
231 // If there's a known iconURL for this page URL, we need to do more cleanup
232 NSString *iconURL = [_private->pageURLToIconURL objectForKey:pageURL];
233 if (iconURL != nil) {
234 // If there are no other retainers of this icon, forget it entirely
235 if ([self _totalRetainCountForIconURLString:iconURL] == 0) {
236 [self _forgetIconForIconURLString:iconURL];
238 // There's at least one other retainer of this icon, so we need to forget the
239 // two-way links between this page URL and the icon URL, without blowing away
240 // the icon entirely.
241 [_private->pageURLToIconURL removeObjectForKey:pageURL];
243 id pageURLs = [_private->iconURLToPageURLs objectForKey:iconURL];
244 if ([pageURLs isKindOfClass:[NSMutableSet class]]) {
245 ASSERT([pageURLs containsObject:pageURL]);
246 [pageURLs removeObject:pageURL];
248 // Maybe this was the last page URL mapped to this icon URL
249 if ([pageURLs count] == 0) {
250 [_private->iconURLToPageURLs removeObjectForKey:iconURL];
253 // Only one page URL was associated with this icon URL; it must have been us
254 ASSERT([pageURLs isKindOfClass:[NSString class]]);
255 ASSERT([pageURLs isEqualToString:pageURL]);
256 [_private->iconURLToPageURLs removeObjectForKey:pageURL];
261 CFDictionarySetValue(_private->pageURLToRetainCount, pageURL, (void *)newRetainCount);
265 - (void)delayDatabaseCleanup
267 if (![self _isEnabled]) {
271 if(_private->didCleanup){
272 ERROR("delayDatabaseCleanup cannot be called after cleanup has begun");
276 _private->cleanupCount++;
279 - (void)allowDatabaseCleanup
281 if (![self _isEnabled]) {
285 if(_private->didCleanup){
286 ERROR("allowDatabaseCleanup cannot be called after cleanup has begun");
290 _private->cleanupCount--;
292 if(_private->cleanupCount == 0 && _private->waitingToCleanup){
293 [self _releaseOriginalIconsOnDisk];
300 @implementation WebIconDatabase (WebPendingPublic)
302 - (void)removeAllIcons
304 NSEnumerator *keyEnumerator = [(NSDictionary *)_private->iconURLToPageURLs keyEnumerator];
305 NSString *iconURLString;
306 while ((iconURLString = [keyEnumerator nextObject]) != nil) {
307 // Note that _forgetIconForIconURLString does not affect retain counts, so the current clients
308 // need not do anything about retaining/releasing icons here. (However, the current clients should
309 // respond to WebIconDatabaseDidRemoveAllIconsNotification by refetching any icons that are
310 // displayed in the UI.)
311 [self _forgetIconForIconURLString:iconURLString];
314 // Delete entire file database immediately. This has at least three advantages over waiting for
315 // _updateFileDatabase to execute:
316 // (1) _updateFileDatabase won't execute until an icon has been added
317 // (2) this is faster
318 // (3) this deletes all the on-disk hierarchy (especially useful if due to past problems there are
319 // some stale files in that hierarchy)
320 [_private->fileDatabase removeAllObjects];
321 [_private->iconsToEraseWithURLs removeAllObjects];
322 [_private->iconsToSaveWithURLs removeAllObjects];
323 [self _clearDictionaries];
324 [[NSNotificationCenter defaultCenter] postNotificationName:WebIconDatabaseDidRemoveAllIconsNotification
330 @implementation WebIconDatabase (WebPrivate)
334 return (_private->fileDatabase != nil);
337 - (void)_setIcon:(NSImage *)icon forIconURL:(NSString *)iconURL
341 ASSERT([self _isEnabled]);
343 NSMutableDictionary *icons = [self _iconsBySplittingRepresentationsOfIcon:icon];
349 [_private->iconURLToIcons setObject:icons forKey:iconURL];
351 [self _retainIconForIconURLString:iconURL];
353 // Release the newly created icon much like an autorelease.
354 // This gives the client enough time to retain it.
355 // FIXME: Should use an actual autorelease here using a proxy object instead.
356 [self performSelector:@selector(_releaseIconForIconURLString:) withObject:iconURL afterDelay:0];
359 - (void)_setHaveNoIconForIconURL:(NSString *)iconURL
362 ASSERT([self _isEnabled]);
364 [_private->iconURLsWithNoIcons addObject:iconURL];
366 [self _retainIconForIconURLString:iconURL];
368 // Release the newly created icon much like an autorelease.
369 // This gives the client enough time to retain it.
370 // FIXME: Should use an actual autorelease here using a proxy object instead.
371 [self performSelector:@selector(_releaseIconForIconURLString:) withObject:iconURL afterDelay:0];
375 - (void)_setIconURL:(NSString *)iconURL forURL:(NSString *)URL
379 ASSERT([self _isEnabled]);
380 ASSERT([self _hasIconForIconURL:iconURL]);
382 if ([[_private->pageURLToIconURL objectForKey:URL] isEqualToString:iconURL] &&
383 [_private->iconsOnDiskWithURLs containsObject:iconURL]) {
384 // Don't do any work if the icon URL is already bound to the site URL
388 [_private->pageURLToIconURL setObject:iconURL forKey:URL];
389 [_private->iconURLToPageURLs _web_setObjectUsingSetIfNecessary:URL forKey:iconURL];
391 [self _sendNotificationForURL:URL];
392 [self _updateFileDatabase];
395 - (BOOL)_hasIconForIconURL:(NSString *)iconURL;
397 ASSERT([self _isEnabled]);
399 return (([_private->iconURLToIcons objectForKey:iconURL] ||
400 [_private->iconURLsWithNoIcons containsObject:iconURL] ||
401 [_private->iconsOnDiskWithURLs containsObject:iconURL]) &&
402 [self _totalRetainCountForIconURLString:iconURL] > 0);
407 @implementation WebIconDatabase (WebInternal)
409 - (void)_createFileDatabase
411 // FIXME: Make defaults key public somehow
412 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
413 NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
415 if (!databaseDirectory) {
416 databaseDirectory = @"~/Library/Icons";
417 [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
419 databaseDirectory = [databaseDirectory stringByExpandingTildeInPath];
421 _private->fileDatabase = [[WebFileDatabase alloc] initWithPath:databaseDirectory];
422 [_private->fileDatabase setSizeLimit:20000000];
423 [_private->fileDatabase open];
426 - (void)_clearDictionaries
428 [_private->pageURLToIconURL release];
429 [_private->iconURLToPageURLs release];
430 [_private->iconsOnDiskWithURLs release];
431 [_private->originalIconsOnDiskWithURLs release];
432 _private->pageURLToIconURL = [[NSMutableDictionary alloc] init];
433 _private->iconURLToPageURLs = [[NSMutableDictionary alloc] init];
434 _private->iconsOnDiskWithURLs = [[NSMutableSet alloc] init];
435 _private->originalIconsOnDiskWithURLs = [[NSMutableSet alloc] init];
438 - (void)_loadIconDictionaries
440 WebFileDatabase *fileDB = _private->fileDatabase;
445 NSNumber *version = [fileDB objectForKey:WebIconDatabaseVersionKey];
447 // no version means first version
448 if (version == nil) {
450 } else if ([version isKindOfClass:[NSNumber class]]) {
451 v = [version intValue];
454 // Get the site URL to icon URL dictionary from the file DB.
455 NSMutableDictionary *pageURLToIconURL = nil;
456 if (v <= WebIconDatabaseCurrentVersion) {
457 pageURLToIconURL = [[fileDB objectForKey:WebURLToIconURLKey] retain];
458 // Remove the old unnecessary mapping files.
459 if (v < WebIconDatabaseCurrentVersion) {
460 [fileDB removeObjectForKey:ObsoleteIconsOnDiskKey];
461 [fileDB removeObjectForKey:ObsoleteIconURLToURLsKey];
465 if (![pageURLToIconURL isKindOfClass:[NSMutableDictionary class]]) {
466 [self _clearDictionaries];
470 // Keep a set of icon URLs on disk so we know what we need to write out or remove.
471 NSMutableSet *iconsOnDiskWithURLs = [[NSMutableSet alloc] initWithArray:[pageURLToIconURL allValues]];
473 // Reverse pageURLToIconURL so we have an icon URL to page URLs dictionary.
474 NSMutableDictionary *iconURLToPageURLs = [[NSMutableDictionary alloc] initWithCapacity:[_private->iconsOnDiskWithURLs count]];
475 NSEnumerator *enumerator = [pageURLToIconURL keyEnumerator];
477 while ((URL = [enumerator nextObject])) {
478 NSString *iconURL = (NSString *)[pageURLToIconURL objectForKey:URL];
479 if (![URL isKindOfClass:[NSString class]] || ![iconURL isKindOfClass:[NSString class]]) {
480 [self _clearDictionaries];
483 [iconURLToPageURLs _web_setObjectUsingSetIfNecessary:URL forKey:iconURL];
486 _private->pageURLToIconURL = pageURLToIconURL;
487 _private->iconURLToPageURLs = iconURLToPageURLs;
488 _private->iconsOnDiskWithURLs = iconsOnDiskWithURLs;
489 _private->originalIconsOnDiskWithURLs = [iconsOnDiskWithURLs copy];
492 // Only called by _setIconURL:forKey:
493 - (void)_updateFileDatabase
495 if(_private->cleanupCount != 0){
499 WebFileDatabase *fileDB = _private->fileDatabase;
504 [fileDB setObject:[NSNumber numberWithInt:WebIconDatabaseCurrentVersion] forKey:WebIconDatabaseVersionKey];
506 // Erase icons that have been released that are on disk.
507 // Must remove icons before writing them to disk or else we could potentially remove the newly written ones.
508 NSEnumerator *enumerator = [_private->iconsToEraseWithURLs objectEnumerator];
509 NSString *iconURLString;
511 while ((iconURLString = [enumerator nextObject]) != nil) {
512 [fileDB removeObjectForKey:iconURLString];
513 [_private->iconsOnDiskWithURLs removeObject:iconURLString];
516 // Save icons that have been retained that are not already on disk
517 enumerator = [_private->iconsToSaveWithURLs objectEnumerator];
519 while ((iconURLString = [enumerator nextObject]) != nil) {
520 NSMutableDictionary *icons = [_private->iconURLToIcons objectForKey:iconURLString];
522 // Save the 16 x 16 size icons as this is the only size that Safari uses.
523 // If we ever use larger sizes, we should save the largest size so icons look better when scaling up.
524 // This also works around the problem with cnet's blank 32x32 icon (3105486).
525 NSImage *icon = [icons objectForKey:[NSValue valueWithSize:NSMakeSize(16,16)]];
527 // In case there is no 16 x 16 size.
528 icon = [self _largestIconFromDictionary:icons];
530 NSData *iconData = [icon TIFFRepresentation];
532 //NSLog(@"Writing icon: %@", iconURLString);
533 [fileDB setObject:iconData forKey:iconURLString];
534 [_private->iconsOnDiskWithURLs addObject:iconURLString];
536 } else if ([_private->iconURLsWithNoIcons containsObject:iconURLString]) {
537 [fileDB setObject:[NSNull null] forKey:iconURLString];
538 [_private->iconsOnDiskWithURLs addObject:iconURLString];
542 [_private->iconsToEraseWithURLs removeAllObjects];
543 [_private->iconsToSaveWithURLs removeAllObjects];
545 // Save the icon dictionaries to disk. Save them as mutable copies otherwise WebFileDatabase may access the
546 // same dictionaries on a separate thread as it's being modified. We think this fixes 3566336.
547 NSMutableDictionary *pageURLToIconURLCopy = [_private->pageURLToIconURL mutableCopy];
548 [fileDB setObject:pageURLToIconURLCopy forKey:WebURLToIconURLKey];
549 [pageURLToIconURLCopy release];
552 - (void)_applicationWillTerminate:(NSNotification *)notification
554 // Should only cause a write if user quit before 3 seconds after the last _updateFileDatabase
555 [_private->fileDatabase sync];
558 - (int)_totalRetainCountForIconURLString:(NSString *)iconURLString
560 // Add up the retain counts for each associated page, plus the retain counts not associated
561 // with any page, which are stored in _private->iconURLToExtraRetainCount
562 int result = (int)(void *)CFDictionaryGetValue(_private->iconURLToExtraRetainCount, iconURLString);
564 id URLStrings = [_private->iconURLToPageURLs objectForKey:iconURLString];
565 if (URLStrings != nil) {
566 if ([URLStrings isKindOfClass:[NSMutableSet class]]) {
567 NSEnumerator *e = [(NSMutableSet *)URLStrings objectEnumerator];
569 while ((URLString = [e nextObject]) != nil) {
570 ASSERT([URLString isKindOfClass:[NSString class]]);
571 result += (int)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, URLString);
574 ASSERT([URLStrings isKindOfClass:[NSString class]]);
575 result += (int)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, URLStrings);
582 - (NSMutableDictionary *)_iconsForIconURLString:(NSString *)iconURLString
584 ASSERT(iconURLString);
586 if ([_private->iconURLsWithNoIcons containsObject:iconURLString]) {
590 NSMutableDictionary *icons = [_private->iconURLToIcons objectForKey:iconURLString];
596 // Not in memory, check disk
597 if(![_private->iconsOnDiskWithURLs containsObject:iconURLString]){
603 double start = CFAbsoluteTimeGetCurrent();
605 NSData *iconData = [_private->fileDatabase objectForKey:iconURLString];
607 if ([iconData isKindOfClass:[NSNull class]]) {
608 [_private->iconURLsWithNoIcons addObject:iconURLString];
614 NSImage *icon = [[NSImage alloc] initWithData:iconData];
615 icons = [self _iconsBySplittingRepresentationsOfIcon:icon];
618 double duration = CFAbsoluteTimeGetCurrent() - start;
619 LOG(Timing, "loading and creating icon %@ took %f seconds", iconURLString, duration);
621 [_private->iconURLToIcons setObject:icons forKey:iconURLString];
631 - (NSImage *)_iconForFileURL:(NSString *)file withSize:(NSSize)size
636 NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
637 NSString *path = [[NSURL _web_URLWithDataAsString:file] path];
638 NSString *suffix = [path pathExtension];
641 if ([suffix _webkit_isCaseInsensitiveEqualToString:@"htm"] || [suffix _webkit_isCaseInsensitiveEqualToString:@"html"]) {
642 if (!_private->htmlIcons) {
643 icon = [workspace iconForFileType:@"html"];
644 _private->htmlIcons = [[self _iconsBySplittingRepresentationsOfIcon:icon] retain];
646 icon = [self _iconFromDictionary:_private->htmlIcons forSize:size cache:YES];
648 if (!path || ![path isAbsolutePath]) {
649 // Return the generic icon when there is no path.
650 icon = [workspace iconForFileType:@"????"];
652 icon = [workspace iconForFile:path];
654 [self _scaleIcon:icon toSize:size];
660 - (void)_retainIconForIconURLString:(NSString *)iconURLString
662 ASSERT(iconURLString);
664 int retainCount = (int)(void *)CFDictionaryGetValue(_private->iconURLToExtraRetainCount, iconURLString);
665 int newRetainCount = retainCount + 1;
667 CFDictionarySetValue(_private->iconURLToExtraRetainCount, iconURLString, (void *)newRetainCount);
669 if (newRetainCount == 1 && ![_private->iconsOnDiskWithURLs containsObject:iconURLString]){
670 [_private->iconsToSaveWithURLs addObject:iconURLString];
671 [_private->iconsToEraseWithURLs removeObject:iconURLString];
675 - (void)_forgetIconForIconURLString:(NSString *)iconURLString
677 ASSERT_ARG(iconURLString, iconURLString != nil);
678 if([_private->iconsOnDiskWithURLs containsObject:iconURLString]){
679 [_private->iconsToEraseWithURLs addObject:iconURLString];
680 [_private->iconsToSaveWithURLs removeObject:iconURLString];
683 // Remove the icon's images
684 [_private->iconURLToIcons removeObjectForKey:iconURLString];
686 // Remove negative cache item for icon, if any
687 [_private->iconURLsWithNoIcons removeObject:iconURLString];
689 // Remove the icon's associated site URLs, if any
690 [iconURLString retain];
691 id URLs = [_private->iconURLToPageURLs objectForKey:iconURLString];
693 if ([URLs isKindOfClass:[NSMutableSet class]]) {
694 [_private->pageURLToIconURL removeObjectsForKeys:[URLs allObjects]];
696 ASSERT([URLs isKindOfClass:[NSString class]]);
697 [_private->pageURLToIconURL removeObjectForKey:URLs];
700 [_private->iconURLToPageURLs removeObjectForKey:iconURLString];
701 [iconURLString release];
704 - (void)_releaseIconForIconURLString:(NSString *)iconURLString
706 ASSERT(iconURLString);
708 int retainCount = (int)(void *)CFDictionaryGetValue(_private->iconURLToExtraRetainCount, iconURLString);
710 if (retainCount <= 0) {
711 ASSERT_NOT_REACHED();
715 int newRetainCount = retainCount - 1;
716 if (newRetainCount == 0) {
717 CFDictionaryRemoveValue(_private->iconURLToExtraRetainCount, iconURLString);
718 if ([self _totalRetainCountForIconURLString:iconURLString] == 0) {
719 [self _forgetIconForIconURLString:iconURLString];
722 CFDictionarySetValue(_private->iconURLToExtraRetainCount, iconURLString, (void *)newRetainCount);
726 - (void)_retainOriginalIconsOnDisk
728 NSEnumerator *enumerator = [_private->originalIconsOnDiskWithURLs objectEnumerator];;
729 NSString *iconURLString;
730 while ((iconURLString = [enumerator nextObject]) != nil) {
731 [self _retainIconForIconURLString:iconURLString];
735 - (void)_releaseOriginalIconsOnDisk
737 if (_private->cleanupCount > 0) {
738 _private->waitingToCleanup = YES;
742 NSEnumerator *enumerator = [_private->originalIconsOnDiskWithURLs objectEnumerator];
743 NSString *iconURLString;
744 while ((iconURLString = [enumerator nextObject]) != nil) {
745 [self _releaseIconForIconURLString:iconURLString];
748 [_private->originalIconsOnDiskWithURLs release];
749 _private->originalIconsOnDiskWithURLs = nil;
751 _private->didCleanup = YES;
754 - (void)_sendNotificationForURL:(NSString *)URL
758 NSDictionary *userInfo = [NSDictionary dictionaryWithObject:URL
759 forKey:WebIconNotificationUserInfoURLKey];
760 [[NSNotificationCenter defaultCenter] postNotificationName:WebIconDatabaseDidAddIconNotification
765 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons
769 NSEnumerator *enumerator = [icons keyEnumerator];
770 NSValue *currentSize, *largestSize=nil;
771 float largestSizeArea=0;
773 while ((currentSize = [enumerator nextObject]) != nil) {
774 NSSize currentSizeSize = [currentSize sizeValue];
775 float currentSizeArea = currentSizeSize.width * currentSizeSize.height;
776 if(!largestSizeArea || (currentSizeArea > largestSizeArea)){
777 largestSize = currentSize;
778 largestSizeArea = currentSizeArea;
782 return [icons objectForKey:largestSize];
785 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon
789 NSMutableDictionary *icons = [NSMutableDictionary dictionary];
790 NSEnumerator *enumerator = [[icon representations] objectEnumerator];
793 while ((rep = [enumerator nextObject]) != nil) {
794 NSSize size = [rep size];
795 NSImage *subIcon = [[NSImage alloc] initWithSize:size];
796 [subIcon addRepresentation:rep];
797 [icons setObject:subIcon forKey:[NSValue valueWithSize:size]];
801 if([icons count] > 0){
805 ERROR("icon has no representations");
810 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache
815 NSImage *icon = [icons objectForKey:[NSValue valueWithSize:size]];
818 icon = [[[self _largestIconFromDictionary:icons] copy] autorelease];
819 [self _scaleIcon:icon toSize:size];
822 [icons setObject:icon forKey:[NSValue valueWithSize:size]];
829 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size
835 double start = CFAbsoluteTimeGetCurrent();
838 [icon setScalesWhenResized:YES];
842 double duration = CFAbsoluteTimeGetCurrent() - start;
843 LOG(Timing, "scaling icon took %f seconds.", duration);
849 @implementation WebIconDatabasePrivate
853 @implementation NSMutableDictionary (WebIconDatabase)
855 - (void)_web_setObjectUsingSetIfNecessary:(id)object forKey:(id)key
857 id previousObject = [self objectForKey:key];
858 if (previousObject == nil) {
859 [self setObject:object forKey:key];
860 } else if ([previousObject isKindOfClass:[NSMutableSet class]]) {
861 [previousObject addObject:object];
863 NSMutableSet *objects = [[NSMutableSet alloc] initWithObjects:previousObject, object, nil];
864 [self setObject:objects forKey:key];