be28b1e2def59e772fbf638322c4e78dfa8b22a8
[WebKit-https.git] / WebKit / Misc / WebIconDatabase.m
1 /*
2  * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
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. 
16  *
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.
27  */
28 #import <WebKit/WebIconDatabase.h>
29
30 #import <WebKit/WebIconDatabasePrivate.h>
31 #import <WebKit/WebFileDatabase.h>
32 #import <WebKit/WebKitLogging.h>
33 #import <WebKit/WebKitNSStringExtras.h>
34 #import <WebKit/WebNSURLExtras.h>
35 #import <WebKit/WebPreferences.h>
36
37 #import <WebKit/WebIconDatabaseBridge.h>
38
39 #import "WebTypesInternal.h"
40
41 NSString * const WebIconDatabaseVersionKey =    @"WebIconDatabaseVersion";
42 NSString * const WebURLToIconURLKey =           @"WebSiteURLToIconURLKey";
43
44 NSString * const ObsoleteIconsOnDiskKey =       @"WebIconsOnDisk";
45 NSString * const ObsoleteIconURLToURLsKey =     @"WebIconURLToSiteURLs";
46
47 static const int WebIconDatabaseCurrentVersion = 2;
48
49 NSString *WebIconDatabaseDidAddIconNotification =          @"WebIconDatabaseDidAddIconNotification";
50 NSString *WebIconNotificationUserInfoURLKey =              @"WebIconNotificationUserInfoURLKey";
51 NSString *WebIconDatabaseDidRemoveAllIconsNotification =   @"WebIconDatabaseDidRemoveAllIconsNotification";
52
53 NSString *WebIconDatabaseDirectoryDefaultsKey = @"WebIconDatabaseDirectoryDefaultsKey";
54 NSString *WebIconDatabaseEnabledDefaultsKey =   @"WebIconDatabaseEnabled";
55
56 NSString *WebIconDatabasePath = @"~/Library/Icons";
57
58 NSSize WebIconSmallSize = {16, 16};
59 NSSize WebIconMediumSize = {32, 32};
60 NSSize WebIconLargeSize = {128, 128};
61
62 @interface NSMutableDictionary (WebIconDatabase)
63 - (void)_web_setObjectUsingSetIfNecessary:(id)object forKey:(id)key;
64 @end
65
66 @interface WebIconDatabase (WebInternal)
67 - (void)_clearDictionaries;
68 - (void)_createFileDatabase;
69 - (void)_loadIconDictionaries;
70 - (void)_updateFileDatabase;
71 - (void)_forgetIconForIconURLString:(NSString *)iconURLString;
72 - (NSMutableDictionary *)_iconsForIconURLString:(NSString *)iconURL;
73 - (NSImage *)_iconForFileURL:(NSString *)fileURL withSize:(NSSize)size;
74 - (void)_retainIconForIconURLString:(NSString *)iconURL;
75 - (void)_releaseIconForIconURLString:(NSString *)iconURL;
76 - (void)_retainOriginalIconsOnDisk;
77 - (void)_releaseOriginalIconsOnDisk;
78 - (void)_resetCachedWebPreferences:(NSNotification *)notification;
79 - (int)_totalRetainCountForIconURLString:(NSString *)iconURLString;
80 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons;
81 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon;
82 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache;
83 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size;
84 - (NSData *)_iconDataForIconURL:(NSString *)iconURLString;
85 - (void)_convertToWebCoreFormat; 
86 @end
87
88 @implementation WebIconDatabase
89
90 + (WebIconDatabase *)sharedIconDatabase
91 {
92     static WebIconDatabase *database = nil;
93     
94     if (!database) {
95 #if !LOG_DISABLED
96         double start = CFAbsoluteTimeGetCurrent();
97 #endif
98         database = [[WebIconDatabase alloc] init];
99 #if !LOG_DISABLED
100         LOG(Timing, "initializing icon database with %d sites and %d icons took %f", 
101             [database->_private->pageURLToIconURL count], [database->_private->iconURLToPageURLs count], (CFAbsoluteTimeGetCurrent() - start));
102 #endif
103     }
104     return database;
105 }
106
107
108 - init
109 {
110     [super init];
111     
112     _private = [[WebIconDatabasePrivate alloc] init];
113
114     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
115     NSDictionary *initialDefaults = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:YES], WebIconDatabaseEnabledDefaultsKey, nil];
116     [defaults registerDefaults:initialDefaults];
117     [initialDefaults release];
118     if (![defaults boolForKey:WebIconDatabaseEnabledDefaultsKey]) {
119         return self;
120     }
121     
122     [self _createFileDatabase];
123     [self _loadIconDictionaries];
124
125     _isClosing = NO;
126
127 #ifdef ICONDEBUG
128     _private->databaseBridge = [WebIconDatabaseBridge sharedBridgeInstance];
129     if (_private->databaseBridge) {
130         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
131         NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
132
133         if (!databaseDirectory) {
134             databaseDirectory = WebIconDatabasePath;
135             [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
136         }
137         databaseDirectory = [databaseDirectory stringByExpandingTildeInPath];
138         [_private->databaseBridge openSharedDatabaseWithPath:databaseDirectory];
139     }
140     
141     [self _convertToWebCoreFormat];
142 #else
143     _private->databaseBridge = nil;
144 #endif
145     
146     _private->iconURLToIcons = [[NSMutableDictionary alloc] init];
147     _private->iconURLToExtraRetainCount = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
148     _private->pageURLToRetainCount = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
149     _private->iconsToEraseWithURLs = [[NSMutableSet alloc] init];
150     _private->iconsToSaveWithURLs = [[NSMutableSet alloc] init];
151     _private->iconURLsWithNoIcons = [[NSMutableSet alloc] init];
152     _private->iconURLsBoundDuringPrivateBrowsing = [[NSMutableSet alloc] init];
153     _private->pageURLsBoundDuringPrivateBrowsing = [[NSMutableSet alloc] init];
154     _private->privateBrowsingEnabled = [[WebPreferences standardPreferences] privateBrowsingEnabled];
155     
156     [[NSNotificationCenter defaultCenter] addObserver:self
157                                              selector:@selector(_applicationWillTerminate:)
158                                                  name:NSApplicationWillTerminateNotification
159                                                object:NSApp];
160     [[NSNotificationCenter defaultCenter] 
161             addObserver:self selector:@selector(_resetCachedWebPreferences:) 
162                    name:WebPreferencesChangedNotification object:nil];
163                    
164     // FIXME - Once the new iconDB is the only game in town, we need to remove any of the WebFileDatabase code
165     // that is threaded and expects certain files to exist - certain files we rip right out from underneath it
166     // in the _convertToWebCoreFormat method
167 #ifndef ICONDEBUG
168     // Retain icons on disk then release them once clean-up has begun.
169     // This gives the client the opportunity to retain them before they are erased.
170     [self _retainOriginalIconsOnDisk];
171     [self performSelector:@selector(_releaseOriginalIconsOnDisk) withObject:nil afterDelay:0];
172 #endif
173
174     return self;
175 }
176
177 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size cache:(BOOL)cache
178 {
179     ASSERT(size.width);
180     ASSERT(size.height);
181
182     if (!URL || ![self _isEnabled])
183         return [self defaultIconWithSize:size];
184
185     if ([URL _webkit_isFileURL])
186         return [self _iconForFileURL:URL withSize:size];
187
188 #ifdef ICONDEBUG        
189     NSImage* image = [_private->databaseBridge iconForPageURL:URL withSize:size];
190         
191     // FIXME - We currently don't embed the default icon in the new WebCore IconDB, so we'll return the old version of it;
192     return image ? image : [self defaultIconWithSize:size];
193 #endif
194
195     NSString *iconURLString = [_private->pageURLToIconURL objectForKey:URL];
196     if (!iconURLString)
197         // Don't have it
198         return [self defaultIconWithSize:size];
199
200     NSMutableDictionary *icons = [self _iconsForIconURLString:iconURLString];
201
202     if (!icons) {
203         if (![_private->iconURLsWithNoIcons containsObject:iconURLString]) {
204            // We used to have this icon, but don't have it anymore for some reason. (Bug? Deleted from
205            // disk behind our back?). Forget that we ever had it so it will be re-fetched next time.
206             LOG_ERROR("WebIconDatabase used to contain %@, but the icon file is missing. Now forgetting that we ever knew about this icon.", iconURLString);
207             [self _forgetIconForIconURLString:iconURLString];
208         }
209         return [self defaultIconWithSize:size];
210     }        
211
212     return [self _iconFromDictionary:icons forSize:size cache:cache];
213 }
214
215 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size
216 {
217     return [self iconForURL:URL withSize:size cache:YES];
218 }
219
220 - (NSString *)iconURLForURL:(NSString *)URL
221 {
222     if (![self _isEnabled])
223         return nil;
224         
225 #ifdef ICONDEBUG
226     NSString* iconurl = [_private->databaseBridge iconURLForPageURL:URL];
227     return iconurl;
228 #endif
229
230     return URL ? [_private->pageURLToIconURL objectForKey:URL] : nil;
231 }
232
233 - (NSImage *)defaultIconWithSize:(NSSize)size
234 {
235     ASSERT(size.width);
236     ASSERT(size.height);
237     
238 #ifdef ICONDEBUG
239     return [_private->databaseBridge defaultIconWithSize:size];
240 #endif
241     
242     if (!_private->defaultIcons) {
243         NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"url_icon" ofType:@"tiff"];
244         if (path) {
245             NSImage *icon = [[NSImage alloc] initByReferencingFile:path];
246             _private->defaultIcons = [[NSMutableDictionary dictionaryWithObject:icon
247                                             forKey:[NSValue valueWithSize:[icon size]]] retain];
248             [icon release];
249         }
250     }
251
252     return [self _iconFromDictionary:_private->defaultIcons forSize:size cache:YES];
253 }
254
255 - (void)retainIconForURL:(NSString *)URL
256 {
257     ASSERT(URL);
258     if (![self _isEnabled])
259         return;
260
261 #ifdef ICONDEBUG
262     [_private->databaseBridge retainIconForURL:URL];
263     return;
264 #endif
265
266     WebNSUInteger retainCount = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, URL);
267     CFDictionarySetValue(_private->pageURLToRetainCount, URL, (void *)(retainCount + 1));
268
269 }
270
271 - (void)releaseIconForURL:(NSString *)pageURL
272 {
273     ASSERT(pageURL);
274     if (![self _isEnabled])
275         return;
276         
277 #ifdef ICONDEBUG
278     [_private->databaseBridge releaseIconForURL:pageURL];
279     return;
280 #endif
281
282     WebNSUInteger retainCount = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, pageURL);
283     
284     if (retainCount <= 0) {
285         LOG_ERROR("The icon for %@ was released more times than it was retained.", pageURL);
286         return;
287     }
288     
289     WebNSUInteger newRetainCount = retainCount - 1;
290
291     if (newRetainCount == 0) {
292         // Forget association between this page URL and a retain count
293         CFDictionaryRemoveValue(_private->pageURLToRetainCount, pageURL);
294
295         // If there's a known iconURL for this page URL, we need to do more cleanup
296         NSString *iconURL = [_private->pageURLToIconURL objectForKey:pageURL];
297         if (iconURL != nil) {
298             // If there are no other retainers of this icon, forget it entirely
299             if ([self _totalRetainCountForIconURLString:iconURL] == 0) {
300                 [self _forgetIconForIconURLString:iconURL];
301             } else {
302                 // There's at least one other retainer of this icon, so we need to forget the
303                 // two-way links between this page URL and the icon URL, without blowing away
304                 // the icon entirely.                
305                 id pageURLs = [_private->iconURLToPageURLs objectForKey:iconURL];
306                 if ([pageURLs isKindOfClass:[NSMutableSet class]]) {
307                     ASSERT([pageURLs containsObject:pageURL]);
308                     [pageURLs removeObject:pageURL];
309                     
310                     // Maybe this was the last page URL mapped to this icon URL
311                     if ([(NSMutableSet *)pageURLs count] == 0) {
312                         [_private->iconURLToPageURLs removeObjectForKey:iconURL];
313                     }
314                 } else {
315                     // Only one page URL was associated with this icon URL; it must have been us
316                     ASSERT([pageURLs isKindOfClass:[NSString class]]);
317                     ASSERT([pageURLs isEqualToString:pageURL]);
318                     [_private->iconURLToPageURLs removeObjectForKey:pageURL];
319                 }
320                 
321                 // Remove iconURL from this dictionary last, since this might be the last
322                 // reference and we need to use it as a key for _private->iconURLToPageURLs above.
323                 [_private->pageURLToIconURL removeObjectForKey:pageURL];
324             }
325         }
326     } else {
327         CFDictionarySetValue(_private->pageURLToRetainCount, pageURL, (void *)newRetainCount);
328     }
329 }
330
331 - (void)delayDatabaseCleanup
332 {
333     if (![self _isEnabled]) {
334         return;
335     }
336     
337     if(_private->didCleanup){
338         LOG_ERROR("delayDatabaseCleanup cannot be called after cleanup has begun");
339         return;
340     }
341     
342     _private->cleanupCount++;
343 }
344
345 - (void)allowDatabaseCleanup
346 {
347     if (![self _isEnabled]) {
348         return;
349     }
350     
351     if(_private->didCleanup){
352         LOG_ERROR("allowDatabaseCleanup cannot be called after cleanup has begun");
353         return;
354     }
355     
356     _private->cleanupCount--;
357
358     if(_private->cleanupCount == 0 && _private->waitingToCleanup){
359         [self _releaseOriginalIconsOnDisk];
360     }
361 }
362
363 @end
364
365
366 @implementation WebIconDatabase (WebPendingPublic)
367
368 - (void)removeAllIcons
369 {
370     NSArray *keys = [(NSDictionary *)_private->iconURLToPageURLs allKeys];
371     unsigned count = [keys count];
372     for (unsigned i = 0; i < count; i++)
373         [self _forgetIconForIconURLString:[keys objectAtIndex:i]];
374     
375     // Delete entire file database immediately. This has at least three advantages over waiting for
376     // _updateFileDatabase to execute:
377     // (1) _updateFileDatabase won't execute until an icon has been added
378     // (2) this is faster
379     // (3) this deletes all the on-disk hierarchy (especially useful if due to past problems there are
380     // some stale files in that hierarchy)
381     [_private->fileDatabase removeAllObjects];
382     [_private->iconsToEraseWithURLs removeAllObjects];
383     [_private->iconsToSaveWithURLs removeAllObjects];
384     [_private->iconURLsBoundDuringPrivateBrowsing removeAllObjects];
385     [_private->pageURLsBoundDuringPrivateBrowsing removeAllObjects];
386     [self _clearDictionaries];
387     [[NSNotificationCenter defaultCenter] postNotificationName:WebIconDatabaseDidRemoveAllIconsNotification
388                                                         object:self
389                                                       userInfo:nil];
390 }
391 - (BOOL)isIconExpiredForIconURL:(NSString *)iconURL
392 {
393     return [_private->databaseBridge isIconExpiredForIconURL:iconURL];
394 }
395
396 @end
397
398 @implementation WebIconDatabase (WebPrivate)
399
400 - (BOOL)_isEnabled
401 {
402     return (_private->fileDatabase != nil);
403 }
404
405 - (void)_setIcon:(NSImage *)icon forIconURL:(NSString *)iconURL
406 {
407     ASSERT(icon);
408     ASSERT(iconURL);
409     ASSERT([self _isEnabled]);
410     
411     NSMutableDictionary *icons = [self _iconsBySplittingRepresentationsOfIcon:icon];
412     
413     if (!icons)
414         return;
415     
416     [_private->iconURLToIcons setObject:icons forKey:iconURL];
417     
418     // Don't update any icon information on disk during private browsing. Remember which icons have been
419     // affected during private browsing so we can forget this information when private browsing is turned off.
420     if (_private->privateBrowsingEnabled)
421         [_private->iconURLsBoundDuringPrivateBrowsing addObject:iconURL];
422
423     [self _retainIconForIconURLString:iconURL];
424     
425     // Release the newly created icon much like an autorelease.
426     // This gives the client enough time to retain it.
427     // FIXME: Should use an actual autorelease here using a proxy object instead.
428     [self performSelector:@selector(_releaseIconForIconURLString:) withObject:iconURL afterDelay:0];
429 }
430
431 - (void)_setHaveNoIconForIconURL:(NSString *)iconURL
432 {
433     ASSERT(iconURL);
434     ASSERT([self _isEnabled]);
435
436 #ifdef ICONDEBUG
437     [_private->databaseBridge _setHaveNoIconForIconURL:iconURL];
438     return;
439 #endif
440
441     [_private->iconURLsWithNoIcons addObject:iconURL];
442     
443     // Don't update any icon information on disk during private browsing. Remember which icons have been
444     // affected during private browsing so we can forget this information when private browsing is turned off.
445     if (_private->privateBrowsingEnabled)
446         [_private->iconURLsBoundDuringPrivateBrowsing addObject:iconURL];
447
448     [self _retainIconForIconURLString:iconURL];
449
450     // Release the newly created icon much like an autorelease.
451     // This gives the client enough time to retain it.
452     // FIXME: Should use an actual autorelease here using a proxy object instead.
453     [self performSelector:@selector(_releaseIconForIconURLString:) withObject:iconURL afterDelay:0];
454 }
455
456
457 - (void)_setIconURL:(NSString *)iconURL forURL:(NSString *)URL
458 {
459     ASSERT(iconURL);
460     ASSERT(URL);
461     ASSERT([self _isEnabled]);
462     ASSERT(_private->pageURLToIconURL);
463     
464 #ifdef ICONDEBUG
465     [_private->databaseBridge _setIconURL:iconURL forPageURL:URL];
466     [self _sendNotificationForURL:URL];
467     return;
468 #endif
469
470     if ([[_private->pageURLToIconURL objectForKey:URL] isEqualToString:iconURL]) {
471         // Don't do any work if the icon URL is already bound to the site URL
472         return;
473     }
474
475     // Keep track of which entries in pageURLToIconURL were created during private browsing so that they can be skipped
476     // when saving to disk.
477     if (_private->privateBrowsingEnabled)
478         [_private->pageURLsBoundDuringPrivateBrowsing addObject:URL];
479
480     [_private->pageURLToIconURL setObject:iconURL forKey:URL];
481     [_private->iconURLToPageURLs _web_setObjectUsingSetIfNecessary:URL forKey:iconURL];
482
483     if ([_private->iconURLsWithNoIcons containsObject:iconURL]) {
484         // If the icon is in the negative cache (ie, there is no icon), avoid the
485         // work of delivering a notification for it or saving it to disk. This is a significant
486         // win on the iBench HTML test.
487         
488         // This return must occur after the dictionary set calls above, so the icon record
489         // is properly retained. Otherwise, we'll forget that the site had no icon, and
490         // inefficiently request its icon again.
491         return;
492     }
493     
494     [self _sendNotificationForURL:URL];
495     [self _updateFileDatabase];
496 }
497
498 - (BOOL)_hasEntryForIconURL:(NSString *)iconURL;
499 {
500     ASSERT([self _isEnabled]);
501
502 #ifdef ICONDEBUG
503     BOOL result = [_private->databaseBridge _hasEntryForIconURL:iconURL];
504     if (result)
505         LOG(IconDatabase, "NewDB has icon for IconURL %@", iconURL);
506     else
507         LOG(IconDatabase, "NewDB has NO icon for IconURL %@", iconURL);
508     return result;
509 #endif
510
511     return (([_private->iconURLToIcons objectForKey:iconURL] ||
512              [_private->iconURLsWithNoIcons containsObject:iconURL] ||
513              [_private->iconsOnDiskWithURLs containsObject:iconURL]) &&
514              [self _totalRetainCountForIconURLString:iconURL] > 0);
515 }
516
517 - (void)_sendNotificationForURL:(NSString *)URL
518 {
519     ASSERT(URL);
520     
521     NSDictionary *userInfo = [NSDictionary dictionaryWithObject:URL
522                                                          forKey:WebIconNotificationUserInfoURLKey];
523     [[NSNotificationCenter defaultCenter] postNotificationName:WebIconDatabaseDidAddIconNotification
524                                                         object:self
525                                                       userInfo:userInfo];
526 }
527
528 - (void)loadIconFromURL:(NSString *)iconURL
529 {
530     [_private->databaseBridge loadIconFromURL:iconURL];
531 }
532
533 @end
534
535 @implementation WebIconDatabase (WebInternal)
536
537 - (void)_createFileDatabase
538 {
539     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
540     NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
541
542     if (!databaseDirectory) {
543         databaseDirectory = WebIconDatabasePath;
544         [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
545     }
546     databaseDirectory = [databaseDirectory stringByExpandingTildeInPath];
547     
548     _private->fileDatabase = [[WebFileDatabase alloc] initWithPath:databaseDirectory];
549     [_private->fileDatabase setSizeLimit:20000000];
550     [_private->fileDatabase open];
551 }
552
553 - (void)_clearDictionaries
554 {
555     [_private->pageURLToIconURL release];
556     [_private->iconURLToPageURLs release];
557     [_private->iconsOnDiskWithURLs release];
558     [_private->originalIconsOnDiskWithURLs release];
559     [_private->iconURLsBoundDuringPrivateBrowsing release];
560     [_private->pageURLsBoundDuringPrivateBrowsing release];
561     _private->pageURLToIconURL = [[NSMutableDictionary alloc] init];
562     _private->iconURLToPageURLs = [[NSMutableDictionary alloc] init];
563     _private->iconsOnDiskWithURLs = [[NSMutableSet alloc] init];
564     _private->originalIconsOnDiskWithURLs = [[NSMutableSet alloc] init];
565     _private->iconURLsBoundDuringPrivateBrowsing = [[NSMutableSet alloc] init];
566     _private->pageURLsBoundDuringPrivateBrowsing = [[NSMutableSet alloc] init];
567 }
568
569 - (void)_loadIconDictionaries
570 {
571     WebFileDatabase *fileDB = _private->fileDatabase;
572     
573     // fileDB should be non-nil here because it should have been created by _createFileDatabase 
574     if (!fileDB) {
575         LOG_ERROR("Couldn't load icon dictionaries because file database didn't exist");
576         return;
577     }
578     
579     NSNumber *version = [fileDB objectForKey:WebIconDatabaseVersionKey];
580     int v = 0;
581     // no version means first version
582     if (version == nil) {
583         v = 1;
584     } else if ([version isKindOfClass:[NSNumber class]]) {
585         v = [version intValue];
586     }
587     
588     // Get the site URL to icon URL dictionary from the file DB.
589     NSMutableDictionary *pageURLToIconURL = nil;
590     if (v <= WebIconDatabaseCurrentVersion) {
591         pageURLToIconURL = [fileDB objectForKey:WebURLToIconURLKey];
592         // Remove the old unnecessary mapping files.
593         if (v < WebIconDatabaseCurrentVersion) {
594             [fileDB removeObjectForKey:ObsoleteIconsOnDiskKey];
595             [fileDB removeObjectForKey:ObsoleteIconURLToURLsKey];
596         }        
597     }
598     
599     // Must double-check all values read from disk. If any are bogus, we just throw out the whole icon cache.
600     // We expect this to be nil if the icon cache has been cleared, so we shouldn't whine in that case.
601     if (![pageURLToIconURL isKindOfClass:[NSMutableDictionary class]]) {
602         if (pageURLToIconURL)
603             LOG_ERROR("Clearing icon cache because bad value %@ was found on disk, expected an NSMutableDictionary", pageURLToIconURL);
604         [self _clearDictionaries];
605         return;
606     }
607
608     // Keep a set of icon URLs on disk so we know what we need to write out or remove.
609     NSMutableSet *iconsOnDiskWithURLs = [NSMutableSet setWithArray:[pageURLToIconURL allValues]];
610
611     // Reverse pageURLToIconURL so we have an icon URL to page URLs dictionary. 
612     NSMutableDictionary *iconURLToPageURLs = [NSMutableDictionary dictionaryWithCapacity:[_private->iconsOnDiskWithURLs count]];
613     NSEnumerator *enumerator = [pageURLToIconURL keyEnumerator];
614     NSString *URL;
615     while ((URL = [enumerator nextObject])) {
616         NSString *iconURL = (NSString *)[pageURLToIconURL objectForKey:URL];
617         // Must double-check all values read from disk. If any are bogus, we just throw out the whole icon cache.
618         if (![URL isKindOfClass:[NSString class]] || ![iconURL isKindOfClass:[NSString class]]) {
619             LOG_ERROR("Clearing icon cache because either %@ or %@ was a bad value on disk, expected both to be NSStrings", URL, iconURL);
620             [self _clearDictionaries];
621             return;
622         }
623         [iconURLToPageURLs _web_setObjectUsingSetIfNecessary:URL forKey:iconURL];
624     }
625
626     ASSERT(!_private->pageURLToIconURL);
627     ASSERT(!_private->iconURLToPageURLs);
628     ASSERT(!_private->iconsOnDiskWithURLs);
629     ASSERT(!_private->originalIconsOnDiskWithURLs);
630     
631     _private->pageURLToIconURL = [pageURLToIconURL retain];
632     _private->iconURLToPageURLs = [iconURLToPageURLs retain];
633     _private->iconsOnDiskWithURLs = [iconsOnDiskWithURLs retain];
634     _private->originalIconsOnDiskWithURLs = [iconsOnDiskWithURLs copy];
635 }
636
637 // Only called by _setIconURL:forKey:
638 - (void)_updateFileDatabase
639 {
640     if (_private->cleanupCount != 0)
641         return;
642
643     WebFileDatabase *fileDB = _private->fileDatabase;
644     if (!fileDB) {
645         LOG_ERROR("Couldn't update file database because it didn't exist");
646         return;
647     }
648
649     [fileDB setObject:[NSNumber numberWithInt:WebIconDatabaseCurrentVersion] forKey:WebIconDatabaseVersionKey];
650
651     // Erase icons that have been released that are on disk.
652     // Must remove icons before writing them to disk or else we could potentially remove the newly written ones.
653     NSEnumerator *enumerator = [_private->iconsToEraseWithURLs objectEnumerator];
654     NSString *iconURLString;
655     
656     while ((iconURLString = [enumerator nextObject]) != nil) {
657         [fileDB removeObjectForKey:iconURLString];
658         [_private->iconsOnDiskWithURLs removeObject:iconURLString];
659     }
660
661     // Save icons that have been retained that are not already on disk
662     enumerator = [_private->iconsToSaveWithURLs objectEnumerator];
663
664     while ((iconURLString = [enumerator nextObject]) != nil) {
665         NSMutableDictionary *icons = [_private->iconURLToIcons objectForKey:iconURLString];
666         if (icons) {
667             // Save the 16 x 16 size icons as this is the only size that Safari uses.
668             // If we ever use larger sizes, we should save the largest size so icons look better when scaling up.
669             // This also works around the problem with cnet's blank 32x32 icon (3105486).
670             NSImage *icon = [icons objectForKey:[NSValue valueWithSize:NSMakeSize(16,16)]];
671             if (!icon) {
672                 // In case there is no 16 x 16 size.
673                 icon = [self _largestIconFromDictionary:icons];
674             }
675             NSData *iconData = [icon TIFFRepresentation];
676             if (iconData) {
677                 //NSLog(@"Writing icon: %@", iconURLString);
678                 [fileDB setObject:iconData forKey:iconURLString];
679                 [_private->iconsOnDiskWithURLs addObject:iconURLString];
680             }
681         } else if ([_private->iconURLsWithNoIcons containsObject:iconURLString]) {
682             [fileDB setObject:[NSNull null] forKey:iconURLString];
683             [_private->iconsOnDiskWithURLs addObject:iconURLString];
684         }
685     }
686     
687     [_private->iconsToEraseWithURLs removeAllObjects];
688     [_private->iconsToSaveWithURLs removeAllObjects];
689
690     // Save the icon dictionaries to disk, after removing any values created during private browsing.
691     // Even if we weren't modifying the dictionary we'd still want to use a copy so that WebFileDatabase
692     // doesn't look at the original from a different thread. (We used to think this would fix 3566336
693     // but that bug's progeny are still alive and kicking.)
694     NSMutableDictionary *pageURLToIconURLCopy = [_private->pageURLToIconURL mutableCopy];
695     [pageURLToIconURLCopy removeObjectsForKeys:[_private->pageURLsBoundDuringPrivateBrowsing allObjects]];
696     [fileDB setObject:pageURLToIconURLCopy forKey:WebURLToIconURLKey];
697     [pageURLToIconURLCopy release];
698 }
699
700 - (void)_applicationWillTerminate:(NSNotification *)notification
701 {
702     // Should only cause a write if user quit before 3 seconds after the last _updateFileDatabase
703     [_private->fileDatabase sync];
704     
705 #ifdef ICONDEBUG
706     [_private->databaseBridge closeSharedDatabase];
707     [_private->databaseBridge release];
708     _private->databaseBridge = nil;
709 #endif
710     _isClosing = YES;
711 }
712
713 - (int)_totalRetainCountForIconURLString:(NSString *)iconURLString
714 {
715     // Add up the retain counts for each associated page, plus the retain counts not associated
716     // with any page, which are stored in _private->iconURLToExtraRetainCount
717     WebNSUInteger result = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->iconURLToExtraRetainCount, iconURLString);
718     
719     id URLStrings = [_private->iconURLToPageURLs objectForKey:iconURLString];
720     if (URLStrings != nil) {
721         if ([URLStrings isKindOfClass:[NSMutableSet class]]) {
722             NSEnumerator *e = [(NSMutableSet *)URLStrings objectEnumerator];
723             NSString *URLString;
724             while ((URLString = [e nextObject]) != nil) {
725                 ASSERT([URLString isKindOfClass:[NSString class]]);
726                 result += (WebNSUInteger)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, URLString);
727             }
728         } else {
729             ASSERT([URLStrings isKindOfClass:[NSString class]]);
730             result += (WebNSUInteger)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, URLStrings);
731         }
732     }
733
734     return result;
735 }
736
737 - (NSMutableDictionary *)_iconsForIconURLString:(NSString *)iconURLString
738 {
739     ASSERT(iconURLString);
740
741     if ([_private->iconURLsWithNoIcons containsObject:iconURLString])
742         return nil;
743     
744     NSMutableDictionary *icons = [_private->iconURLToIcons objectForKey:iconURLString];
745
746     if (icons)
747         return icons;
748         
749     // Not in memory, check disk
750     if(![_private->iconsOnDiskWithURLs containsObject:iconURLString])
751         return nil;
752     
753 #if !LOG_DISABLED         
754     double start = CFAbsoluteTimeGetCurrent();
755 #endif
756     NSData *iconData = [_private->fileDatabase objectForKey:iconURLString];
757
758     if ([iconData isKindOfClass:[NSNull class]]) {
759         [_private->iconURLsWithNoIcons addObject:iconURLString];
760         return nil;
761     }
762     
763     if (iconData) {
764         NS_DURING
765             NSImage *icon = [[NSImage alloc] initWithData:iconData];
766             icons = [self _iconsBySplittingRepresentationsOfIcon:icon];
767             if (icons) {
768 #if !LOG_DISABLED 
769                 double duration = CFAbsoluteTimeGetCurrent() - start;
770                 LOG(Timing, "loading and creating icon %@ took %f seconds", iconURLString, duration);
771 #endif
772                 [_private->iconURLToIcons setObject:icons forKey:iconURLString];
773             }
774         NS_HANDLER
775             icons = nil;
776         NS_ENDHANDLER
777     }
778     
779     return icons;
780 }
781
782 - (NSImage *)_iconForFileURL:(NSString *)file withSize:(NSSize)size
783 {
784     ASSERT(size.width);
785     ASSERT(size.height);
786
787     NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
788     NSString *path = [[NSURL _web_URLWithDataAsString:file] path];
789     NSString *suffix = [path pathExtension];
790     NSImage *icon = nil;
791     
792     if ([suffix _webkit_isCaseInsensitiveEqualToString:@"htm"] || [suffix _webkit_isCaseInsensitiveEqualToString:@"html"]) {
793         if (!_private->htmlIcons) {
794             icon = [workspace iconForFileType:@"html"];
795             _private->htmlIcons = [[self _iconsBySplittingRepresentationsOfIcon:icon] retain];
796         }
797         icon = [self _iconFromDictionary:_private->htmlIcons forSize:size cache:YES];
798     } else {
799         if (!path || ![path isAbsolutePath]) {
800             // Return the generic icon when there is no path.
801             icon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericDocumentIcon)];
802         } else {
803             icon = [workspace iconForFile:path];
804         }
805         [self _scaleIcon:icon toSize:size];
806     }
807
808     return icon;
809 }
810
811 - (void)_retainIconForIconURLString:(NSString *)iconURLString
812 {
813     ASSERT(iconURLString);
814     
815     WebNSUInteger retainCount = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->iconURLToExtraRetainCount, iconURLString);
816     WebNSUInteger newRetainCount = retainCount + 1;
817
818     CFDictionarySetValue(_private->iconURLToExtraRetainCount, iconURLString, (void *)newRetainCount);
819
820     if (newRetainCount == 1 && !_private->privateBrowsingEnabled) {
821
822         // Either we know nothing about this icon and need to save it to disk, or we were planning to remove it
823         // from disk (as set up in _forgetIconForIconURLString:) and should stop that process.
824         if ([_private->iconsOnDiskWithURLs containsObject:iconURLString]) {
825             ASSERT(![_private->iconsToSaveWithURLs containsObject:iconURLString]);
826             [_private->iconsToEraseWithURLs removeObject:iconURLString];
827         } else {
828             ASSERT(![_private->iconsToEraseWithURLs containsObject:iconURLString]);
829             [_private->iconsToSaveWithURLs addObject:iconURLString];
830         }
831     }
832 }
833
834 - (void)_forgetIconForIconURLString:(NSString *)iconURLString
835 {
836     ASSERT_ARG(iconURLString, iconURLString != nil);
837     if([_private->iconsOnDiskWithURLs containsObject:iconURLString]){
838         [_private->iconsToEraseWithURLs addObject:iconURLString];
839         [_private->iconsToSaveWithURLs removeObject:iconURLString];
840     }
841         
842     // Remove the icon's images
843     [_private->iconURLToIcons removeObjectForKey:iconURLString];
844     
845     // Remove negative cache item for icon, if any
846     [_private->iconURLsWithNoIcons removeObject:iconURLString];
847     
848     // Remove the icon's associated site URLs, if any
849     [iconURLString retain];
850     id URLs = [_private->iconURLToPageURLs objectForKey:iconURLString];
851     if (URLs != nil) {
852         if ([URLs isKindOfClass:[NSMutableSet class]])
853             [_private->pageURLToIconURL removeObjectsForKeys:[URLs allObjects]];
854         else {
855             ASSERT([URLs isKindOfClass:[NSString class]]);
856             [_private->pageURLToIconURL removeObjectForKey:URLs];
857         }
858     }
859     [_private->iconURLToPageURLs removeObjectForKey:iconURLString];
860     CFDictionaryRemoveValue(_private->iconURLToExtraRetainCount, iconURLString);
861     [iconURLString release];
862 }
863
864 - (void)_releaseIconForIconURLString:(NSString *)iconURLString
865 {
866     ASSERT(iconURLString);
867     
868     if (![self _isEnabled])
869         return;
870     
871     WebNSUInteger retainCount = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->iconURLToExtraRetainCount, iconURLString);
872
873     // This error used to be an ASSERT() that was causing the build bot to fail.  The build bot was getting itself into a reproducible
874     // situation of having an icon for 127.0.0.1:8000/favicon.ico registered in the database but not finding the file for it.  This situation
875     // triggers a call to _forgetIconForIconURL which dumps everything about the icon - including the retain count.  A later call to releaseIconForURL
876     // would then ASSERT and crash the test as the retain count had be internally reset to zero
877     // The reason the build bot was getting into this situation is not yet understood but the cause of the ASSERT is - and the condition was already
878     // handled gracefully in release builds.  Therefore we're changing it to a LOG_ERROR with the understanding that the sqlite icon database will not 
879     // have this issue due to its entirely different nature
880     if (retainCount <= 0) {
881         if (!_isClosing)
882             LOG_ERROR("Trying to release an icon whose retain-count is already non-positive");
883         return;
884     }
885     
886     WebNSUInteger newRetainCount = retainCount - 1;
887     if (newRetainCount == 0) {
888         CFDictionaryRemoveValue(_private->iconURLToExtraRetainCount, iconURLString);
889         if ([self _totalRetainCountForIconURLString:iconURLString] == 0) {
890             [self _forgetIconForIconURLString:iconURLString];
891         }
892     } else {
893         CFDictionarySetValue(_private->iconURLToExtraRetainCount, iconURLString, (void *)newRetainCount);
894     }
895 }
896
897 - (void)_retainOriginalIconsOnDisk
898 {
899     NSEnumerator *enumerator = [_private->originalIconsOnDiskWithURLs objectEnumerator];;
900     NSString *iconURLString;
901     while ((iconURLString = [enumerator nextObject]) != nil) {
902         [self _retainIconForIconURLString:iconURLString];
903     }
904 }
905
906 - (void)_releaseOriginalIconsOnDisk
907 {
908     if (_private->cleanupCount > 0) {
909         _private->waitingToCleanup = YES;
910         return;
911     }
912
913     NSEnumerator *enumerator = [_private->originalIconsOnDiskWithURLs objectEnumerator];
914     NSString *iconURLString;
915     while ((iconURLString = [enumerator nextObject]) != nil) {
916         [self _releaseIconForIconURLString:iconURLString];
917     }
918     
919     [_private->originalIconsOnDiskWithURLs release];
920     _private->originalIconsOnDiskWithURLs = nil;
921
922     _private->didCleanup = YES;
923 }
924
925 - (void)_resetCachedWebPreferences:(NSNotification *)notification
926 {
927     BOOL privateBrowsingEnabledNow = [[WebPreferences standardPreferences] privateBrowsingEnabled];
928
929 #ifdef ICONDEBUG
930     [_private->databaseBridge setPrivateBrowsingEnabled:privateBrowsingEnabledNow];
931     return;
932 #endif
933
934     if (privateBrowsingEnabledNow == _private->privateBrowsingEnabled)
935         return;
936     
937     _private->privateBrowsingEnabled = privateBrowsingEnabledNow;
938     
939     // When private browsing is turned off, forget everything we learned while it was on 
940     if (!_private->privateBrowsingEnabled) {
941         // Forget all of the icons whose existence we learned of during private browsing.
942         NSEnumerator *iconEnumerator = [_private->iconURLsBoundDuringPrivateBrowsing objectEnumerator];
943         NSString *iconURLString;
944         while ((iconURLString = [iconEnumerator nextObject]) != nil)
945             [self _forgetIconForIconURLString:iconURLString];
946
947         // Forget the relationships between page and icon that we learned during private browsing.
948         NSEnumerator *pageEnumerator = [_private->pageURLsBoundDuringPrivateBrowsing objectEnumerator];
949         NSString *pageURLString;
950         while ((pageURLString = [pageEnumerator nextObject]) != nil) {
951             [_private->pageURLToIconURL removeObjectForKey:pageURLString];
952             // Tell clients that these pages' icons have changed (to generic). The notification is named
953             // WebIconDatabaseDidAddIconNotification but it really means just "did change icon".
954             [self _sendNotificationForURL:pageURLString];
955         }
956         
957         [_private->iconURLsBoundDuringPrivateBrowsing removeAllObjects];
958         [_private->pageURLsBoundDuringPrivateBrowsing removeAllObjects];
959     }
960 }
961
962 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons
963 {
964     ASSERT(icons);
965     
966     NSEnumerator *enumerator = [icons keyEnumerator];
967     NSValue *currentSize, *largestSize=nil;
968     float largestSizeArea=0;
969
970     while ((currentSize = [enumerator nextObject]) != nil) {
971         NSSize currentSizeSize = [currentSize sizeValue];
972         float currentSizeArea = currentSizeSize.width * currentSizeSize.height;
973         if(!largestSizeArea || (currentSizeArea > largestSizeArea)){
974             largestSize = currentSize;
975             largestSizeArea = currentSizeArea;
976         }
977     }
978
979     return [icons objectForKey:largestSize];
980 }
981
982 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon
983 {
984     ASSERT(icon);
985
986     NSMutableDictionary *icons = [NSMutableDictionary dictionary];
987     NSEnumerator *enumerator = [[icon representations] objectEnumerator];
988     NSImageRep *rep;
989
990     while ((rep = [enumerator nextObject]) != nil) {
991         NSSize size = [rep size];
992         NSImage *subIcon = [[NSImage alloc] initWithSize:size];
993         [subIcon addRepresentation:rep];
994         [icons setObject:subIcon forKey:[NSValue valueWithSize:size]];
995         [subIcon release];
996     }
997
998     if([icons count] > 0)
999         return icons;
1000
1001     LOG_ERROR("icon has no representations");
1002     
1003     return nil;
1004 }
1005
1006 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache
1007 {
1008     ASSERT(size.width);
1009     ASSERT(size.height);
1010
1011     NSImage *icon = [icons objectForKey:[NSValue valueWithSize:size]];
1012
1013     if(!icon){
1014         icon = [[[self _largestIconFromDictionary:icons] copy] autorelease];
1015         [self _scaleIcon:icon toSize:size];
1016
1017         if(cache){
1018             [icons setObject:icon forKey:[NSValue valueWithSize:size]];
1019         }
1020     }
1021
1022     return icon;
1023 }
1024
1025 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size
1026 {
1027     ASSERT(size.width);
1028     ASSERT(size.height);
1029     
1030 #if !LOG_DISABLED        
1031     double start = CFAbsoluteTimeGetCurrent();
1032 #endif
1033     
1034     [icon setScalesWhenResized:YES];
1035     [icon setSize:size];
1036     
1037 #if !LOG_DISABLED
1038     double duration = CFAbsoluteTimeGetCurrent() - start;
1039     LOG(Timing, "scaling icon took %f seconds.", duration);
1040 #endif
1041 }
1042
1043 - (NSData *)_iconDataForIconURL:(NSString *)iconURLString
1044 {
1045     ASSERT(iconURLString);
1046     
1047     NSData *iconData = [_private->fileDatabase objectForKey:iconURLString];
1048     
1049     if ((id)iconData == (id)[NSNull null]) 
1050         return nil;
1051         
1052     return iconData;
1053 }
1054
1055 - (void)_convertToWebCoreFormat
1056 {
1057     ASSERT(_private);
1058     ASSERT(_private->databaseBridge);
1059     
1060     // If the WebCore Icon Database is not empty, we assume that this conversion has already
1061     // taken place and skip the rest of the steps 
1062     if (![_private->databaseBridge _isEmpty])
1063         return;
1064                 
1065     NSEnumerator *enumerator = [_private->pageURLToIconURL keyEnumerator];
1066     NSString *url, *iconURL;
1067     
1068     // First, we'll iterate through the PageURL->IconURL map
1069     while ((url = [enumerator nextObject]) != nil) {
1070         iconURL = [_private->pageURLToIconURL objectForKey:url];
1071         if (!iconURL)
1072             continue;
1073         [_private->databaseBridge _setIconURL:iconURL forPageURL:url];
1074     }    
1075     
1076     // Second, we'll iterate through the icon data we do have
1077     enumerator = [_private->iconsOnDiskWithURLs objectEnumerator];
1078     NSData *iconData;
1079     
1080     while ((url = [enumerator nextObject]) != nil) {
1081         iconData = [self _iconDataForIconURL:url];
1082         if (iconData)
1083             [_private->databaseBridge _setIconData:iconData forIconURL:url];
1084         else {
1085             // This really *shouldn't* happen, so it'd be good to track down why it might happen in a debug build
1086             // however, we do know how to handle it gracefully in release
1087             LOG_ERROR("%@ is marked as having an icon on disk, but we couldn't get the data for it", url);
1088             [_private->databaseBridge _setHaveNoIconForIconURL:url];
1089         }
1090     }
1091     
1092     // Finally, we'll iterate through the negative cache we have
1093     enumerator = [_private->iconURLsWithNoIcons objectEnumerator];
1094     while ((url = [enumerator nextObject]) != nil) 
1095         [_private->databaseBridge _setHaveNoIconForIconURL:url];
1096    
1097     // After we're done converting old style icons over to webcore icons, we delete the entire directory hierarchy 
1098     // for the old icon DB
1099     NSString *iconPath = [[NSUserDefaults standardUserDefaults] objectForKey:WebIconDatabaseDirectoryDefaultsKey];
1100     if (!iconPath)
1101         iconPath = WebIconDatabasePath;
1102     
1103     NSString *fullIconPath = [iconPath stringByExpandingTildeInPath];    
1104     NSFileManager *fileManager = [NSFileManager defaultManager];
1105     enumerator = [[fileManager directoryContentsAtPath:fullIconPath] objectEnumerator];
1106     
1107     NSString *databaseFilename = [_private->databaseBridge defaultDatabaseFilename];
1108
1109     NSString *file;
1110     while ((file = [enumerator nextObject]) != nil) {
1111         if ([file isEqualTo:databaseFilename])
1112             continue;
1113         NSString *filePath = [fullIconPath stringByAppendingPathComponent:file];
1114         if (![fileManager  removeFileAtPath:filePath handler:nil])
1115             LOG_ERROR("Failed to delete %@ from old icon directory", filePath);
1116     }
1117 }
1118
1119 @end
1120
1121 @implementation WebIconDatabasePrivate
1122
1123 @end
1124
1125 @implementation NSMutableDictionary (WebIconDatabase)
1126
1127 - (void)_web_setObjectUsingSetIfNecessary:(id)object forKey:(id)key
1128 {
1129     id previousObject = [self objectForKey:key];
1130     if (previousObject == nil) {
1131         [self setObject:object forKey:key];
1132     } else if ([previousObject isKindOfClass:[NSMutableSet class]]) {
1133         [previousObject addObject:object];
1134     } else {
1135         NSMutableSet *objects = [[NSMutableSet alloc] initWithObjects:previousObject, object, nil];
1136         [self setObject:objects forKey:key];
1137         [objects release];
1138     }
1139 }
1140
1141 @end