Fixed: <rdar://problem/3890944> disable icon database for Dashboard
[WebKit-https.git] / WebKit / Misc.subproj / WebIconDatabase.m
1 //
2 //  WebIconDatabase.m
3 //  WebKit
4 //
5 //  Created by Chris Blumenberg on Tue Aug 27 2002.
6 //  Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
7 //
8
9 #import <WebKit/WebIconDatabase.h>
10
11 #import <WebKit/WebIconDatabasePrivate.h>
12 #import <WebKit/WebFileDatabase.h>
13 #import <WebKit/WebKitLogging.h>
14 #import <WebKit/WebNSURLExtras.h>
15
16 #import <Foundation/NSString_NSURLExtras.h>
17
18 NSString * const WebIconDatabaseVersionKey =    @"WebIconDatabaseVersion";
19 NSString * const WebURLToIconURLKey =           @"WebSiteURLToIconURLKey";
20
21 NSString * const ObsoleteIconsOnDiskKey =       @"WebIconsOnDisk";
22 NSString * const ObsoleteIconURLToURLsKey =     @"WebIconURLToSiteURLs";
23
24 static const int WebIconDatabaseCurrentVersion = 2;
25
26 NSString *WebIconDatabaseDidAddIconNotification =   @"WebIconDatabaseDidAddIconNotification";
27 NSString *WebIconNotificationUserInfoURLKey =       @"WebIconNotificationUserInfoURLKey";
28
29 NSString *WebIconDatabaseDirectoryDefaultsKey = @"WebIconDatabaseDirectoryDefaultsKey";
30 NSString *WebIconDatabaseEnabledDefaultsKey =   @"WebIconDatabaseEnabled";
31
32 NSSize WebIconSmallSize = {16, 16};
33 NSSize WebIconMediumSize = {32, 32};
34 NSSize WebIconLargeSize = {128, 128};
35
36 @interface NSMutableDictionary (WebIconDatabase)
37 - (void)_web_setObjectUsingSetIfNecessary:(id)object forKey:(id)key;
38 @end
39
40 @interface WebIconDatabase (WebInternal)
41 - (void)_createFileDatabase;
42 - (void)_loadIconDictionaries;
43 - (void)_updateFileDatabase;
44 - (NSMutableDictionary *)_iconsForIconURLString:(NSString *)iconURL;
45 - (NSImage *)_iconForFileURL:(NSString *)fileURL withSize:(NSSize)size;
46 - (void)_retainIconForIconURLString:(NSString *)iconURL;
47 - (void)_releaseIconForIconURLString:(NSString *)iconURL;
48 - (void)_retainFutureIconForURL:(NSString *)URL;
49 - (void)_releaseFutureIconForURL:(NSString *)URL;
50 - (void)_retainOriginalIconsOnDisk;
51 - (void)_releaseOriginalIconsOnDisk;
52 - (void)_sendNotificationForURL:(NSString *)URL;
53 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons;
54 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon;
55 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache;
56 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size;
57 @end
58
59
60 @implementation WebIconDatabase
61
62 + (WebIconDatabase *)sharedIconDatabase
63 {
64     static WebIconDatabase *database = nil;
65     
66     if (!database) {
67 #if !LOG_DISABLED
68         double start = CFAbsoluteTimeGetCurrent();
69 #endif
70         database = [[WebIconDatabase alloc] init];
71 #if !LOG_DISABLED
72         LOG(Timing, "initializing icon database with %d sites and %d icons took %f", 
73             [database->_private->URLToIconURL count], [database->_private->iconURLToURLs count], (CFAbsoluteTimeGetCurrent() - start));
74 #endif
75     }
76     return database;
77 }
78
79 - init
80 {
81     [super init];
82     
83     _private = [[WebIconDatabasePrivate alloc] init];
84
85     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
86     NSDictionary *initialDefaults = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:YES], WebIconDatabaseEnabledDefaultsKey, nil];
87     [defaults registerDefaults:initialDefaults];
88     [initialDefaults release];
89     if (![defaults boolForKey:WebIconDatabaseEnabledDefaultsKey]) {
90         return self;
91     }
92     
93     [self _createFileDatabase];
94     [self _loadIconDictionaries];
95
96     _private->iconURLToIcons = [[NSMutableDictionary alloc] init];
97     _private->iconURLToRetainCount = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
98     _private->futureURLToRetainCount = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
99     _private->iconsToEraseWithURLs = [[NSMutableSet alloc] init];
100     _private->iconsToSaveWithURLs = [[NSMutableSet alloc] init];
101     _private->iconURLsWithNoIcons = [[NSMutableSet alloc] init];
102     
103     [[NSNotificationCenter defaultCenter] addObserver:self
104                                              selector:@selector(_applicationWillTerminate:)
105                                                  name:NSApplicationWillTerminateNotification
106                                                object:NSApp];
107
108     // Retain icons on disk then release them once clean-up has begun.
109     // This gives the client the opportunity to retain them before they are erased.
110     [self _retainOriginalIconsOnDisk];
111     [self performSelector:@selector(_releaseOriginalIconsOnDisk) withObject:nil afterDelay:0];
112     
113     return self;
114 }
115
116 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size cache:(BOOL)cache
117 {
118     ASSERT(size.width);
119     ASSERT(size.height);
120     
121     if (!URL || ![self _isEnabled]) {
122         return [self defaultIconWithSize:size];
123     }
124
125     if ([URL _web_isFileURL]) {
126         return [self _iconForFileURL:URL withSize:size];
127     }
128     
129     NSString *iconURLString = [_private->URLToIconURL objectForKey:URL];
130     if (!iconURLString) {
131         // Don't have it
132         return [self defaultIconWithSize:size];
133     }
134
135     NSMutableDictionary *icons = [self _iconsForIconURLString:iconURLString];
136     if (!icons) {
137         if (![_private->iconURLsWithNoIcons containsObject:iconURLString]) {
138             ERROR("WebIconDatabase said it had %@, but it doesn't.", iconURLString);
139         }
140         return [self defaultIconWithSize:size];
141     }        
142
143     return [self _iconFromDictionary:icons forSize:size cache:cache];
144 }
145
146 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size
147 {
148     return [self iconForURL:URL withSize:size cache:YES];
149 }
150
151 - (NSString *)iconURLForURL:(NSString *)URL
152 {
153     if (![self _isEnabled]) {
154         return nil;
155     }
156     return URL ? [_private->URLToIconURL objectForKey:URL] : nil;
157 }
158
159 - (NSImage *)defaultIconWithSize:(NSSize)size
160 {
161     ASSERT(size.width);
162     ASSERT(size.height);
163     
164     if (!_private->defaultIcons) {
165         NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"url_icon" ofType:@"tiff"];
166         if (path) {
167             NSImage *icon = [[NSImage alloc] initByReferencingFile:path];
168             _private->defaultIcons = [[NSMutableDictionary dictionaryWithObject:icon
169                                             forKey:[NSValue valueWithSize:[icon size]]] retain];
170             [icon release];
171         }
172     }
173
174     return [self _iconFromDictionary:_private->defaultIcons forSize:size cache:YES];
175 }
176
177 - (void)retainIconForURL:(NSString *)URL
178 {
179     ASSERT(URL);
180     
181     if (![self _isEnabled]) {
182         return;
183     }    
184     
185     NSString *iconURLString = [_private->URLToIconURL objectForKey:URL];
186     
187     if(iconURLString){
188         [self _retainIconForIconURLString:iconURLString];
189     }else{
190         [self _retainFutureIconForURL:URL];
191     }
192 }
193
194 - (void)releaseIconForURL:(NSString *)URL
195 {
196     ASSERT(URL);
197     
198     if (![self _isEnabled]) {
199         return;
200     }    
201     
202     NSString *iconURLString = [_private->URLToIconURL objectForKey:URL];
203     
204     if(iconURLString){
205         [self _releaseIconForIconURLString:iconURLString];
206     }else{
207         [self _releaseFutureIconForURL:URL];        
208     }
209 }
210
211 - (void)delayDatabaseCleanup
212 {
213     if (![self _isEnabled]) {
214         return;
215     }
216     
217     if(_private->didCleanup){
218         ERROR("delayDatabaseCleanup cannot be called after cleanup has begun");
219         return;
220     }
221     
222     _private->cleanupCount++;
223 }
224
225 - (void)allowDatabaseCleanup
226 {
227     if (![self _isEnabled]) {
228         return;
229     }
230     
231     if(_private->didCleanup){
232         ERROR("allowDatabaseCleanup cannot be called after cleanup has begun");
233         return;
234     }
235     
236     _private->cleanupCount--;
237
238     if(_private->cleanupCount == 0 && _private->waitingToCleanup){
239         [self _releaseOriginalIconsOnDisk];
240     }
241 }
242
243 @end
244
245
246 @implementation WebIconDatabase (WebPrivate)
247
248 - (BOOL)_isEnabled
249 {
250     return (_private->fileDatabase != nil);
251 }
252
253 - (void)_setIcon:(NSImage *)icon forIconURL:(NSString *)iconURL
254 {
255     ASSERT(icon);
256     ASSERT(iconURL);
257     ASSERT([self _isEnabled]);
258     
259     NSMutableDictionary *icons = [self _iconsBySplittingRepresentationsOfIcon:icon];
260     
261     if (!icons) {
262         return;
263     }
264     
265     [_private->iconURLToIcons setObject:icons forKey:iconURL];
266     
267     [self _retainIconForIconURLString:iconURL];
268     
269     // Release the newly created icon much like an autorelease.
270     // This gives the client enough time to retain it.
271     // FIXME: Should use an actual autorelease here using a proxy object instead.
272     [self performSelector:@selector(_releaseIconForIconURLString:) withObject:iconURL afterDelay:0];
273 }
274
275 - (void)_setHaveNoIconForIconURL:(NSString *)iconURL
276 {
277     ASSERT(iconURL);
278     ASSERT([self _isEnabled]);
279
280     [_private->iconURLsWithNoIcons addObject:iconURL];
281     
282     [self _retainIconForIconURLString:iconURL];
283     
284     // Release the newly created icon much like an autorelease.
285     // This gives the client enough time to retain it.
286     // FIXME: Should use an actual autorelease here using a proxy object instead.
287     [self performSelector:@selector(_releaseIconForIconURLString:) withObject:iconURL afterDelay:0];
288 }
289
290
291 - (void)_setIconURL:(NSString *)iconURL forURL:(NSString *)URL
292 {
293     ASSERT(iconURL);
294     ASSERT(URL);
295     ASSERT([self _isEnabled]);
296     ASSERT([self _hasIconForIconURL:iconURL]);    
297     
298     if ([[_private->URLToIconURL objectForKey:URL] isEqualToString:iconURL] &&
299         [_private->iconsOnDiskWithURLs containsObject:iconURL]) {
300         // Don't do any work if the icon URL is already bound to the site URL
301         return;
302     }
303     
304     [_private->URLToIconURL setObject:iconURL forKey:URL];
305     [_private->iconURLToURLs _web_setObjectUsingSetIfNecessary:URL forKey:iconURL];
306     
307     int futureRetainCount = (int)(void *)CFDictionaryGetValue(_private->futureURLToRetainCount, URL);
308     
309     if (futureRetainCount != 0) {
310         int retainCount = (int)(void *)CFDictionaryGetValue(_private->iconURLToRetainCount, iconURL);
311         int newRetainCount = retainCount + futureRetainCount;
312         CFDictionarySetValue(_private->iconURLToRetainCount, iconURL, (void *)newRetainCount);
313         CFDictionaryRemoveValue(_private->futureURLToRetainCount, URL);
314     }
315     
316     [self _sendNotificationForURL:URL];
317     [self _updateFileDatabase];
318 }
319
320 - (BOOL)_hasIconForIconURL:(NSString *)iconURL;
321 {
322     ASSERT([self _isEnabled]);
323     
324     return (([_private->iconURLToIcons objectForKey:iconURL] ||
325              [_private->iconURLsWithNoIcons containsObject:iconURL] ||
326              [_private->iconsOnDiskWithURLs containsObject:iconURL]) &&
327             CFDictionaryGetValue(_private->iconURLToRetainCount,iconURL));
328 }
329
330 @end
331
332 @implementation WebIconDatabase (WebInternal)
333
334 - (void)_createFileDatabase
335 {
336     // FIXME: Make defaults key public somehow
337     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
338     NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
339
340     if (!databaseDirectory) {
341         databaseDirectory = @"~/Library/Icons";
342         [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
343     }
344     databaseDirectory = [databaseDirectory stringByExpandingTildeInPath];
345     
346     _private->fileDatabase = [[WebFileDatabase alloc] initWithPath:databaseDirectory];
347     [_private->fileDatabase setSizeLimit:20000000];
348     [_private->fileDatabase open];
349 }
350
351 - (void)_clearDictionaries
352 {
353     [_private->URLToIconURL release];
354     [_private->iconURLToURLs release];
355     [_private->iconsOnDiskWithURLs release];
356     [_private->originalIconsOnDiskWithURLs release];
357     _private->URLToIconURL = [[NSMutableDictionary alloc] init];
358     _private->iconURLToURLs = [[NSMutableDictionary alloc] init];
359     _private->iconsOnDiskWithURLs = [[NSMutableSet alloc] init];
360     _private->originalIconsOnDiskWithURLs = [[NSMutableSet alloc] init];
361 }
362
363 - (void)_loadIconDictionaries
364 {
365     WebFileDatabase *fileDB = _private->fileDatabase;
366     if (!fileDB) {
367         return;
368     }
369     
370     NSNumber *version = [fileDB objectForKey:WebIconDatabaseVersionKey];
371     int v = 0;
372     // no version means first version
373     if (version == nil) {
374         v = 1;
375     } else if ([version isKindOfClass:[NSNumber class]]) {
376         v = [version intValue];
377     }
378     
379     // Get the site URL to icon URL dictionary from the file DB.
380     NSMutableDictionary *URLToIconURL = nil;
381     if (v <= WebIconDatabaseCurrentVersion) {
382         URLToIconURL = [[fileDB objectForKey:WebURLToIconURLKey] retain];
383         // Remove the old unnecessary mapping files.
384         if (v < WebIconDatabaseCurrentVersion) {
385             [fileDB removeObjectForKey:ObsoleteIconsOnDiskKey];
386             [fileDB removeObjectForKey:ObsoleteIconURLToURLsKey];
387         }        
388     }
389     
390     if (![URLToIconURL isKindOfClass:[NSMutableDictionary class]]) {
391         [self _clearDictionaries];
392         return;
393     }
394
395     // Keep a set of icon URLs on disk so we know what we need to write out or remove.
396     NSMutableSet *iconsOnDiskWithURLs = [[NSMutableSet alloc] initWithArray:[URLToIconURL allValues]];
397
398     // Reverse URLToIconURL so we have an icon URL to site URLs dictionary. 
399     NSMutableDictionary *iconURLToURLs = [[NSMutableDictionary alloc] initWithCapacity:[_private->iconsOnDiskWithURLs count]];
400     NSEnumerator *enumerator = [URLToIconURL keyEnumerator];
401     NSString *URL;
402     while ((URL = [enumerator nextObject])) {
403         NSString *iconURL = (NSString *)[URLToIconURL objectForKey:URL];
404         if (![URL isKindOfClass:[NSString class]] || ![iconURL isKindOfClass:[NSString class]]) {
405             [self _clearDictionaries];
406             return;
407         }
408         [iconURLToURLs _web_setObjectUsingSetIfNecessary:URL forKey:iconURL];
409     }
410
411     _private->URLToIconURL = URLToIconURL;
412     _private->iconURLToURLs = iconURLToURLs;
413     _private->iconsOnDiskWithURLs = iconsOnDiskWithURLs;
414     _private->originalIconsOnDiskWithURLs = [iconsOnDiskWithURLs copy];
415 }
416
417 // Only called by _setIconURL:forKey:
418 - (void)_updateFileDatabase
419 {
420     if(_private->cleanupCount != 0){
421         return;
422     }
423
424     WebFileDatabase *fileDB = _private->fileDatabase;
425     if (!fileDB) {
426         return;
427     }
428
429     [fileDB setObject:[NSNumber numberWithInt:WebIconDatabaseCurrentVersion] forKey:WebIconDatabaseVersionKey];
430
431     // Erase icons that have been released that are on disk.
432     // Must remove icons before writing them to disk or else we could potentially remove the newly written ones.
433     NSEnumerator *enumerator = [_private->iconsToEraseWithURLs objectEnumerator];
434     NSString *iconURLString;
435     
436     while ((iconURLString = [enumerator nextObject]) != nil) {
437         [fileDB removeObjectForKey:iconURLString];
438         [_private->iconsOnDiskWithURLs removeObject:iconURLString];
439     }
440
441     // Save icons that have been retained that are not already on disk
442     enumerator = [_private->iconsToSaveWithURLs objectEnumerator];
443
444     while ((iconURLString = [enumerator nextObject]) != nil) {
445         NSMutableDictionary *icons = [_private->iconURLToIcons objectForKey:iconURLString];
446         if (icons) {
447             // Save the 16 x 16 size icons as this is the only size that Safari uses.
448             // If we ever use larger sizes, we should save the largest size so icons look better when scaling up.
449             // This also works around the problem with cnet's blank 32x32 icon (3105486).
450             NSImage *icon = [icons objectForKey:[NSValue valueWithSize:NSMakeSize(16,16)]];
451             if (!icon) {
452                 // In case there is no 16 x 16 size.
453                 icon = [self _largestIconFromDictionary:icons];
454             }
455             NSData *iconData = [icon TIFFRepresentation];
456             if (iconData) {
457                 //NSLog(@"Writing icon: %@", iconURLString);
458                 [fileDB setObject:iconData forKey:iconURLString];
459                 [_private->iconsOnDiskWithURLs addObject:iconURLString];
460             }
461         } else if ([_private->iconURLsWithNoIcons containsObject:iconURLString]) {
462             [fileDB setObject:[NSNull null] forKey:iconURLString];
463             [_private->iconsOnDiskWithURLs addObject:iconURLString];
464         }
465     }
466     
467     [_private->iconsToEraseWithURLs removeAllObjects];
468     [_private->iconsToSaveWithURLs removeAllObjects];
469
470     // Save the icon dictionaries to disk. Save them as mutable copies otherwise WebFileDatabase may access the 
471     // same dictionaries on a separate thread as it's being modified. We think this fixes 3566336.
472     NSMutableDictionary *URLToIconURLCopy = [_private->URLToIconURL mutableCopy];
473     [fileDB setObject:URLToIconURLCopy forKey:WebURLToIconURLKey];
474     [URLToIconURLCopy release];
475 }
476
477 - (void)_applicationWillTerminate:(NSNotification *)notification
478 {
479     // Should only cause a write if user quit before 3 seconds after the last _updateFileDatabase
480     [_private->fileDatabase sync];
481 }
482
483 - (NSMutableDictionary *)_iconsForIconURLString:(NSString *)iconURLString
484 {
485     ASSERT(iconURLString);
486
487     if ([_private->iconURLsWithNoIcons containsObject:iconURLString]) {
488         return nil;
489     }
490     
491     NSMutableDictionary *icons = [_private->iconURLToIcons objectForKey:iconURLString];
492
493     if (icons) {
494         return icons;
495     }
496         
497     // Not in memory, check disk
498     if(![_private->iconsOnDiskWithURLs containsObject:iconURLString]){
499         return nil;
500     }
501
502     
503 #if !LOG_DISABLED         
504     double start = CFAbsoluteTimeGetCurrent();
505 #endif
506     NSData *iconData = [_private->fileDatabase objectForKey:iconURLString];
507
508     if ([iconData isKindOfClass:[NSNull class]]) {
509         [_private->iconURLsWithNoIcons addObject:iconURLString];
510         return nil;
511     }
512     
513     if (iconData) {
514         NS_DURING
515             NSImage *icon = [[NSImage alloc] initWithData:iconData];
516             icons = [self _iconsBySplittingRepresentationsOfIcon:icon];
517             if (icons) {
518 #if !LOG_DISABLED 
519                 double duration = CFAbsoluteTimeGetCurrent() - start;
520                 LOG(Timing, "loading and creating icon %@ took %f seconds", iconURLString, duration);
521 #endif
522                 [_private->iconURLToIcons setObject:icons forKey:iconURLString];
523             }
524         NS_HANDLER
525             icons = nil;
526         NS_ENDHANDLER
527     }
528     
529     return icons;
530 }
531
532 - (NSImage *)_iconForFileURL:(NSString *)file withSize:(NSSize)size
533 {
534     ASSERT(size.width);
535     ASSERT(size.height);
536
537     NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
538     NSString *path = [[NSURL _web_URLWithDataAsString:file] path];
539     NSString *suffix = [path pathExtension];
540     NSImage *icon = nil;
541     
542     if ([suffix _web_isCaseInsensitiveEqualToString:@"htm"] || [suffix _web_isCaseInsensitiveEqualToString:@"html"]) {
543         if (!_private->htmlIcons) {
544             icon = [workspace iconForFileType:@"html"];
545             _private->htmlIcons = [[self _iconsBySplittingRepresentationsOfIcon:icon] retain];
546         }
547         icon = [self _iconFromDictionary:_private->htmlIcons forSize:size cache:YES];
548     } else {
549         if (!path || ![path isAbsolutePath]) {
550             // Return the generic icon when there is no path.
551             icon = [workspace iconForFileType:@"????"];
552         } else {
553             icon = [workspace iconForFile:path];
554         }
555         [self _scaleIcon:icon toSize:size];
556     }
557
558     return icon;
559 }
560
561 - (void)_retainIconForIconURLString:(NSString *)iconURLString
562 {
563     ASSERT(iconURLString);
564     
565     int retainCount = (int)(void *)CFDictionaryGetValue(_private->iconURLToRetainCount, iconURLString);
566     int newRetainCount = retainCount + 1;
567
568     CFDictionarySetValue(_private->iconURLToRetainCount, iconURLString, (void *)newRetainCount);
569
570     if (newRetainCount == 1 && ![_private->iconsOnDiskWithURLs containsObject:iconURLString]){
571         [_private->iconsToSaveWithURLs addObject:iconURLString];
572         [_private->iconsToEraseWithURLs removeObject:iconURLString];
573     }
574 }
575
576 - (void)_releaseIconForIconURLString:(NSString *)iconURLString
577 {
578     ASSERT(iconURLString);
579     
580     int retainCount = (int)(void *)CFDictionaryGetValue(_private->iconURLToRetainCount, iconURLString);
581
582     if (!retainCount) {
583         ASSERT_NOT_REACHED();
584         return;
585     }
586     
587     int newRetainCount = retainCount - 1;
588     CFDictionarySetValue(_private->iconURLToRetainCount, iconURLString, (void *)newRetainCount);
589
590     if (newRetainCount == 0) {
591         if([_private->iconsOnDiskWithURLs containsObject:iconURLString]){
592             [_private->iconsToEraseWithURLs addObject:iconURLString];
593             [_private->iconsToSaveWithURLs removeObject:iconURLString];
594         }
595
596         // Remove the icon's images
597         [_private->iconURLToIcons removeObjectForKey:iconURLString];
598
599         // Remove negative cache item for icon, if any
600         [_private->iconURLsWithNoIcons removeObject:iconURLString];
601
602         // Remove the icon's retain count
603         CFDictionaryRemoveValue(_private->iconURLToRetainCount, iconURLString);
604
605         // Remove the icon's associated site URLs
606         [iconURLString retain];
607         id URLs = [_private->iconURLToURLs objectForKey:iconURLString];
608         if ([URLs isKindOfClass:[NSMutableSet class]]) {
609             [_private->URLToIconURL removeObjectsForKeys:[URLs allObjects]];
610         } else {
611             ASSERT([URLs isKindOfClass:[NSString class]]);
612             [_private->URLToIconURL removeObjectForKey:URLs];
613         }
614         [_private->iconURLToURLs removeObjectForKey:iconURLString];
615         [iconURLString release];
616     }
617 }
618
619 - (void)_retainFutureIconForURL:(NSString *)URL
620 {
621     ASSERT(URL);
622     
623     int retainCount = (int)(void *)CFDictionaryGetValue(_private->futureURLToRetainCount, URL);
624     CFDictionarySetValue(_private->futureURLToRetainCount, URL, (void *)(retainCount + 1));
625 }
626
627 - (void)_releaseFutureIconForURL:(NSString *)URL
628 {
629     ASSERT(URL);
630     
631     int retainCount = (int)(void *)CFDictionaryGetValue(_private->futureURLToRetainCount, URL);
632
633     if (!retainCount) {
634         ERROR("The future icon for %@ was released before it was retained.", URL);
635         return;
636     }
637
638     int newRetainCount = retainCount - 1;
639
640     if (newRetainCount == 0){
641         CFDictionaryRemoveValue(_private->futureURLToRetainCount, URL);
642     } else {
643         CFDictionarySetValue(_private->futureURLToRetainCount, URL, (void *)newRetainCount);
644     }
645 }
646
647 - (void)_retainOriginalIconsOnDisk
648 {
649     NSEnumerator *enumerator = [_private->originalIconsOnDiskWithURLs objectEnumerator];;
650     NSString *iconURLString;
651     while ((iconURLString = [enumerator nextObject]) != nil) {
652         [self _retainIconForIconURLString:iconURLString];
653     }
654 }
655
656 - (void)_releaseOriginalIconsOnDisk
657 {
658     if (_private->cleanupCount > 0) {
659         _private->waitingToCleanup = YES;
660         return;
661     }
662
663     NSEnumerator *enumerator = [_private->originalIconsOnDiskWithURLs objectEnumerator];
664     NSString *iconURLString;
665     while ((iconURLString = [enumerator nextObject]) != nil) {
666         [self _releaseIconForIconURLString:iconURLString];
667     }
668     
669     [_private->originalIconsOnDiskWithURLs release];
670     _private->originalIconsOnDiskWithURLs = nil;
671
672     _private->didCleanup = YES;
673 }
674
675 - (void)_sendNotificationForURL:(NSString *)URL
676 {
677     ASSERT(URL);
678     
679     NSDictionary *userInfo = [NSDictionary dictionaryWithObject:URL
680                                                          forKey:WebIconNotificationUserInfoURLKey];
681     [[NSNotificationCenter defaultCenter] postNotificationName:WebIconDatabaseDidAddIconNotification
682                                                         object:self
683                                                       userInfo:userInfo];
684 }
685
686 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons
687 {
688     ASSERT(icons);
689     
690     NSEnumerator *enumerator = [icons keyEnumerator];
691     NSValue *currentSize, *largestSize=nil;
692     float largestSizeArea=0;
693
694     while ((currentSize = [enumerator nextObject]) != nil) {
695         NSSize currentSizeSize = [currentSize sizeValue];
696         float currentSizeArea = currentSizeSize.width * currentSizeSize.height;
697         if(!largestSizeArea || (currentSizeArea > largestSizeArea)){
698             largestSize = currentSize;
699             largestSizeArea = currentSizeArea;
700         }
701     }
702
703     return [icons objectForKey:largestSize];
704 }
705
706 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon
707 {
708     ASSERT(icon);
709
710     NSMutableDictionary *icons = [NSMutableDictionary dictionary];
711     NSEnumerator *enumerator = [[icon representations] objectEnumerator];
712     NSImageRep *rep;
713
714     while ((rep = [enumerator nextObject]) != nil) {
715         NSSize size = [rep size];
716         NSImage *subIcon = [[NSImage alloc] initWithSize:size];
717         [subIcon addRepresentation:rep];
718         [icons setObject:subIcon forKey:[NSValue valueWithSize:size]];
719         [subIcon release];
720     }
721
722     if([icons count] > 0){
723         return icons;
724     }
725
726     ERROR("icon has no representations");
727     
728     return nil;
729 }
730
731 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache
732 {
733     ASSERT(size.width);
734     ASSERT(size.height);
735
736     NSImage *icon = [icons objectForKey:[NSValue valueWithSize:size]];
737
738     if(!icon){
739         icon = [[[self _largestIconFromDictionary:icons] copy] autorelease];
740         [self _scaleIcon:icon toSize:size];
741
742         if(cache){
743             [icons setObject:icon forKey:[NSValue valueWithSize:size]];
744         }
745     }
746
747     return icon;
748 }
749
750 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size
751 {
752     ASSERT(size.width);
753     ASSERT(size.height);
754     
755 #if !LOG_DISABLED        
756     double start = CFAbsoluteTimeGetCurrent();
757 #endif
758     
759     [icon setScalesWhenResized:YES];
760     [icon setSize:size];
761     
762 #if !LOG_DISABLED
763     double duration = CFAbsoluteTimeGetCurrent() - start;
764     LOG(Timing, "scaling icon took %f seconds.", duration);
765 #endif
766 }
767
768 @end
769
770 @implementation WebIconDatabasePrivate
771
772 @end
773
774 @implementation NSMutableDictionary (WebIconDatabase)
775
776 - (void)_web_setObjectUsingSetIfNecessary:(id)object forKey:(id)key
777 {
778     id previousObject = [self objectForKey:key];
779     if (previousObject == nil) {
780         [self setObject:object forKey:key];
781     } else if ([previousObject isKindOfClass:[NSMutableSet class]]) {
782         [previousObject addObject:object];
783     } else {
784         NSMutableSet *objects = [[NSMutableSet alloc] initWithObjects:previousObject, object, nil];
785         [self setObject:objects forKey:key];
786         [objects release];
787     }
788 }
789
790 @end