Reviewed by Maciej.
[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 - (NSImage *)_iconForFileURL:(NSString *)fileURL withSize:(NSSize)size;
71 - (void)_resetCachedWebPreferences:(NSNotification *)notification;
72 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons;
73 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon;
74 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache;
75 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size;
76 - (NSData *)_iconDataForIconURL:(NSString *)iconURLString;
77 - (void)_convertToWebCoreFormat; 
78 @end
79
80 @implementation WebIconDatabase
81
82 + (WebIconDatabase *)sharedIconDatabase
83 {
84     static WebIconDatabase *database = nil;
85     
86     if (!database) {
87 #if !LOG_DISABLED
88         double start = CFAbsoluteTimeGetCurrent();
89 #endif
90         database = [[WebIconDatabase alloc] init];
91 #if !LOG_DISABLED
92         LOG(Timing, "initializing icon database with %d sites and %d icons took %f", 
93             [database->_private->pageURLToIconURL count], [database->_private->iconURLToPageURLs count], (CFAbsoluteTimeGetCurrent() - start));
94 #endif
95     }
96     return database;
97 }
98
99
100 - init
101 {
102     [super init];
103     
104     _private = [[WebIconDatabasePrivate alloc] init];
105
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]) {
111         return self;
112     }
113     
114     [self _createFileDatabase];
115     
116     _private->iconURLToIcons = [[NSMutableDictionary alloc] init];
117     _private->iconURLToExtraRetainCount = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
118     _private->pageURLToRetainCount = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
119     _private->iconsToEraseWithURLs = [[NSMutableSet alloc] init];
120     _private->iconsToSaveWithURLs = [[NSMutableSet alloc] init];
121     _private->iconURLsWithNoIcons = [[NSMutableSet alloc] init];
122     _private->iconURLsBoundDuringPrivateBrowsing = [[NSMutableSet alloc] init];
123     _private->pageURLsBoundDuringPrivateBrowsing = [[NSMutableSet alloc] init];
124     _private->privateBrowsingEnabled = [[WebPreferences standardPreferences] privateBrowsingEnabled];
125     
126     [self _loadIconDictionaries];
127
128 #ifdef ICONDEBUG
129     _private->databaseBridge = [WebIconDatabaseBridge sharedBridgeInstance];
130     if (_private->databaseBridge) {
131         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
132         NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
133
134         if (!databaseDirectory) {
135             databaseDirectory = WebIconDatabasePath;
136             [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
137         }
138         databaseDirectory = [databaseDirectory stringByExpandingTildeInPath];
139         [_private->databaseBridge openSharedDatabaseWithPath:databaseDirectory];
140     }
141     
142     [self _convertToWebCoreFormat];
143 #endif
144     
145     [[NSNotificationCenter defaultCenter] addObserver:self
146                                              selector:@selector(_applicationWillTerminate:)
147                                                  name:NSApplicationWillTerminateNotification
148                                                object:NSApp];
149     [[NSNotificationCenter defaultCenter] 
150             addObserver:self selector:@selector(_resetCachedWebPreferences:) 
151                    name:WebPreferencesChangedNotification object:nil];
152                    
153     // FIXME - Once the new iconDB is the only game in town, we need to remove any of the WebFileDatabase code
154     // that is threaded and expects certain files to exist - certain files we rip right out from underneath it
155     // in the _convertToWebCoreFormat method
156
157     return self;
158 }
159
160 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size cache:(BOOL)cache
161 {
162     ASSERT(size.width);
163     ASSERT(size.height);
164
165     if (!URL || ![self _isEnabled])
166         return [self defaultIconWithSize:size];
167
168     // <rdar://problem/4697934> - Move the handling of FileURLs to WebCore and implement in ObjC++
169     if ([URL _webkit_isFileURL])
170         return [self _iconForFileURL:URL withSize:size];
171
172 #ifdef ICONDEBUG        
173     NSImage* image = [_private->databaseBridge iconForPageURL:URL withSize:size];
174     return image ? image : [self defaultIconWithSize:size];
175 #endif
176 }
177
178 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size
179 {
180     return [self iconForURL:URL withSize:size cache:YES];
181 }
182
183 - (NSString *)iconURLForURL:(NSString *)URL
184 {
185     if (![self _isEnabled])
186         return nil;
187         
188 #ifdef ICONDEBUG
189     NSString* iconurl = [_private->databaseBridge iconURLForPageURL:URL];
190     return iconurl;
191 #endif
192 }
193
194 - (NSImage *)defaultIconWithSize:(NSSize)size
195 {
196     ASSERT(size.width);
197     ASSERT(size.height);
198     
199 #ifdef ICONDEBUG
200     return [_private->databaseBridge defaultIconWithSize:size];
201 #endif
202 }
203
204 - (void)retainIconForURL:(NSString *)URL
205 {
206     ASSERT(URL);
207     if (![self _isEnabled])
208         return;
209
210 #ifdef ICONDEBUG
211     [_private->databaseBridge retainIconForURL:URL];
212     return;
213 #endif
214 }
215
216 - (void)releaseIconForURL:(NSString *)pageURL
217 {
218     ASSERT(pageURL);
219     if (![self _isEnabled])
220         return;
221
222 #ifdef ICONDEBUG
223     [_private->databaseBridge releaseIconForURL:pageURL];
224     return;
225 #endif
226 }
227 @end
228
229
230 @implementation WebIconDatabase (WebPendingPublic)
231
232 - (void)removeAllIcons
233 {
234     // FIXME - <rdar://problem/4678414>
235     // Need to create a bridge method that calls through to WebCore and performs a wipe of the DB there
236 }
237
238 - (BOOL)isIconExpiredForIconURL:(NSString *)iconURL
239 {
240     return [_private->databaseBridge isIconExpiredForIconURL:iconURL];
241 }
242
243 @end
244
245 @implementation WebIconDatabase (WebPrivate)
246
247 - (BOOL)_isEnabled
248 {
249     return (_private->fileDatabase != nil);
250 }
251
252 - (void)_setIconData:(NSData *)data forIconURL:(NSString *)iconURL
253 {
254     ASSERT(data);
255     ASSERT(iconURL);
256     ASSERT([self _isEnabled]);   
257     
258     [_private->databaseBridge _setIconData:data forIconURL:iconURL];
259 }
260
261 - (void)_setHaveNoIconForIconURL:(NSString *)iconURL
262 {
263     ASSERT(iconURL);
264     ASSERT([self _isEnabled]);
265
266 #ifdef ICONDEBUG
267     [_private->databaseBridge _setHaveNoIconForIconURL:iconURL];
268     return;
269 #endif
270 }
271
272
273 - (void)_setIconURL:(NSString *)iconURL forURL:(NSString *)URL
274 {
275     ASSERT(iconURL);
276     ASSERT(URL);
277     ASSERT([self _isEnabled]);
278     
279 #ifdef ICONDEBUG
280     // FIXME - The following comment is a holdover from the old icon DB, which handled missing icons
281     // differently than the new db.  It would return early if the icon is in the negative cache,
282     // avoiding the notification.  We should explore and see if a similar optimization can take place-
283         // If the icon is in the negative cache (ie, there is no icon), avoid the
284         // work of delivering a notification for it or saving it to disk. This is a significant
285         // win on the iBench HTML test.
286         
287     // FIXME - The following comment is also a holdover - if the iconURL->pageURL mapping was already the
288     // same, the notification would again be avoided - we should try to do this, too.
289         // Don't do any work if the icon URL is already bound to the site URL
290     
291     // A possible solution for both of these is to have the bridge method return a BOOL saying "Yes, notify" or
292     // "no, don't bother notifying"
293     
294     [_private->databaseBridge _setIconURL:iconURL forPageURL:URL];
295     [self _sendNotificationForURL:URL];
296     return;
297 #endif
298 }
299
300 - (BOOL)_hasEntryForIconURL:(NSString *)iconURL;
301 {
302     ASSERT([self _isEnabled]);
303
304 #ifdef ICONDEBUG
305     return [_private->databaseBridge _hasEntryForIconURL:iconURL];
306 #endif
307 }
308
309 - (void)_sendNotificationForURL:(NSString *)URL
310 {
311     ASSERT(URL);
312     
313     NSDictionary *userInfo = [NSDictionary dictionaryWithObject:URL
314                                                          forKey:WebIconNotificationUserInfoURLKey];
315     [[NSNotificationCenter defaultCenter] postNotificationName:WebIconDatabaseDidAddIconNotification
316                                                         object:self
317                                                       userInfo:userInfo];
318 }
319
320 - (void)loadIconFromURL:(NSString *)iconURL
321 {
322     [_private->databaseBridge loadIconFromURL:iconURL];
323 }
324
325 @end
326
327 @implementation WebIconDatabase (WebInternal)
328
329 - (void)_createFileDatabase
330 {
331     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
332     NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
333
334     if (!databaseDirectory) {
335         databaseDirectory = WebIconDatabasePath;
336         [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
337     }
338     databaseDirectory = [databaseDirectory stringByExpandingTildeInPath];
339     
340     _private->fileDatabase = [[WebFileDatabase alloc] initWithPath:databaseDirectory];
341     [_private->fileDatabase setSizeLimit:20000000];
342     [_private->fileDatabase open];
343 }
344
345 - (void)_clearDictionaries
346 {
347     [_private->pageURLToIconURL release];
348     [_private->iconURLToPageURLs release];
349     [_private->iconsOnDiskWithURLs release];
350     [_private->originalIconsOnDiskWithURLs release];
351     [_private->iconURLsBoundDuringPrivateBrowsing release];
352     [_private->pageURLsBoundDuringPrivateBrowsing release];
353     _private->pageURLToIconURL = [[NSMutableDictionary alloc] init];
354     _private->iconURLToPageURLs = [[NSMutableDictionary alloc] init];
355     _private->iconsOnDiskWithURLs = [[NSMutableSet alloc] init];
356     _private->originalIconsOnDiskWithURLs = [[NSMutableSet alloc] init];
357     _private->iconURLsBoundDuringPrivateBrowsing = [[NSMutableSet alloc] init];
358     _private->pageURLsBoundDuringPrivateBrowsing = [[NSMutableSet alloc] init];
359 }
360
361 - (void)_loadIconDictionaries
362 {
363     WebFileDatabase *fileDB = _private->fileDatabase;
364     
365     // fileDB should be non-nil here because it should have been created by _createFileDatabase 
366     if (!fileDB) {
367         LOG_ERROR("Couldn't load icon dictionaries because file database didn't exist");
368         return;
369     }
370     
371     NSNumber *version = [fileDB objectForKey:WebIconDatabaseVersionKey];
372     int v = 0;
373     // no version means first version
374     if (version == nil) {
375         v = 1;
376     } else if ([version isKindOfClass:[NSNumber class]]) {
377         v = [version intValue];
378     }
379     
380     // Get the site URL to icon URL dictionary from the file DB.
381     NSMutableDictionary *pageURLToIconURL = nil;
382     if (v <= WebIconDatabaseCurrentVersion) {
383         pageURLToIconURL = [fileDB objectForKey:WebURLToIconURLKey];
384         // Remove the old unnecessary mapping files.
385         if (v < WebIconDatabaseCurrentVersion) {
386             [fileDB removeObjectForKey:ObsoleteIconsOnDiskKey];
387             [fileDB removeObjectForKey:ObsoleteIconURLToURLsKey];
388         }        
389     }
390     
391     // Must double-check all values read from disk. If any are bogus, we just throw out the whole icon cache.
392     // We expect this to be nil if the icon cache has been cleared, so we shouldn't whine in that case.
393     if (![pageURLToIconURL isKindOfClass:[NSMutableDictionary class]]) {
394         if (pageURLToIconURL)
395             LOG_ERROR("Clearing icon cache because bad value %@ was found on disk, expected an NSMutableDictionary", pageURLToIconURL);
396         [self _clearDictionaries];
397         return;
398     }
399
400     // Keep a set of icon URLs on disk so we know what we need to write out or remove.
401     NSMutableSet *iconsOnDiskWithURLs = [NSMutableSet setWithArray:[pageURLToIconURL allValues]];
402
403     // Reverse pageURLToIconURL so we have an icon URL to page URLs dictionary. 
404     NSMutableDictionary *iconURLToPageURLs = [NSMutableDictionary dictionaryWithCapacity:[_private->iconsOnDiskWithURLs count]];
405     NSEnumerator *enumerator = [pageURLToIconURL keyEnumerator];
406     NSString *URL;
407     while ((URL = [enumerator nextObject])) {
408         NSString *iconURL = (NSString *)[pageURLToIconURL objectForKey:URL];
409         // Must double-check all values read from disk. If any are bogus, we just throw out the whole icon cache.
410         if (![URL isKindOfClass:[NSString class]] || ![iconURL isKindOfClass:[NSString class]]) {
411             LOG_ERROR("Clearing icon cache because either %@ or %@ was a bad value on disk, expected both to be NSStrings", URL, iconURL);
412             [self _clearDictionaries];
413             return;
414         }
415         [iconURLToPageURLs _web_setObjectUsingSetIfNecessary:URL forKey:iconURL];
416     }
417
418     ASSERT(!_private->pageURLToIconURL);
419     ASSERT(!_private->iconURLToPageURLs);
420     ASSERT(!_private->iconsOnDiskWithURLs);
421     ASSERT(!_private->originalIconsOnDiskWithURLs);
422     
423     _private->pageURLToIconURL = [pageURLToIconURL retain];
424     _private->iconURLToPageURLs = [iconURLToPageURLs retain];
425     _private->iconsOnDiskWithURLs = [iconsOnDiskWithURLs retain];
426     _private->originalIconsOnDiskWithURLs = [iconsOnDiskWithURLs copy];
427 }
428
429
430
431 - (void)_applicationWillTerminate:(NSNotification *)notification
432 {
433 #ifdef ICONDEBUG
434     [_private->databaseBridge closeSharedDatabase];
435     [_private->databaseBridge release];
436     _private->databaseBridge = nil;
437 #endif
438 }
439
440
441 - (NSImage *)_iconForFileURL:(NSString *)file withSize:(NSSize)size
442 {
443     ASSERT(size.width);
444     ASSERT(size.height);
445
446     NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
447     NSString *path = [[NSURL _web_URLWithDataAsString:file] path];
448     NSString *suffix = [path pathExtension];
449     NSImage *icon = nil;
450     
451     if ([suffix _webkit_isCaseInsensitiveEqualToString:@"htm"] || [suffix _webkit_isCaseInsensitiveEqualToString:@"html"]) {
452         if (!_private->htmlIcons) {
453             icon = [workspace iconForFileType:@"html"];
454             _private->htmlIcons = [[self _iconsBySplittingRepresentationsOfIcon:icon] retain];
455         }
456         icon = [self _iconFromDictionary:_private->htmlIcons forSize:size cache:YES];
457     } else {
458         if (!path || ![path isAbsolutePath]) {
459             // Return the generic icon when there is no path.
460             icon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericDocumentIcon)];
461         } else {
462             icon = [workspace iconForFile:path];
463         }
464         [self _scaleIcon:icon toSize:size];
465     }
466
467     return icon;
468 }
469
470 - (void)_resetCachedWebPreferences:(NSNotification *)notification
471 {
472     BOOL privateBrowsingEnabledNow = [[WebPreferences standardPreferences] privateBrowsingEnabled];
473
474 #ifdef ICONDEBUG
475     [_private->databaseBridge setPrivateBrowsingEnabled:privateBrowsingEnabledNow];
476     return;
477 #endif
478 }
479
480 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons
481 {
482     ASSERT(icons);
483     
484     NSEnumerator *enumerator = [icons keyEnumerator];
485     NSValue *currentSize, *largestSize=nil;
486     float largestSizeArea=0;
487
488     while ((currentSize = [enumerator nextObject]) != nil) {
489         NSSize currentSizeSize = [currentSize sizeValue];
490         float currentSizeArea = currentSizeSize.width * currentSizeSize.height;
491         if(!largestSizeArea || (currentSizeArea > largestSizeArea)){
492             largestSize = currentSize;
493             largestSizeArea = currentSizeArea;
494         }
495     }
496
497     return [icons objectForKey:largestSize];
498 }
499
500 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon
501 {
502     ASSERT(icon);
503
504     NSMutableDictionary *icons = [NSMutableDictionary dictionary];
505     NSEnumerator *enumerator = [[icon representations] objectEnumerator];
506     NSImageRep *rep;
507
508     while ((rep = [enumerator nextObject]) != nil) {
509         NSSize size = [rep size];
510         NSImage *subIcon = [[NSImage alloc] initWithSize:size];
511         [subIcon addRepresentation:rep];
512         [icons setObject:subIcon forKey:[NSValue valueWithSize:size]];
513         [subIcon release];
514     }
515
516     if([icons count] > 0)
517         return icons;
518
519     LOG_ERROR("icon has no representations");
520     
521     return nil;
522 }
523
524 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache
525 {
526     ASSERT(size.width);
527     ASSERT(size.height);
528
529     NSImage *icon = [icons objectForKey:[NSValue valueWithSize:size]];
530
531     if(!icon){
532         icon = [[[self _largestIconFromDictionary:icons] copy] autorelease];
533         [self _scaleIcon:icon toSize:size];
534
535         if(cache){
536             [icons setObject:icon forKey:[NSValue valueWithSize:size]];
537         }
538     }
539
540     return icon;
541 }
542
543 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size
544 {
545     ASSERT(size.width);
546     ASSERT(size.height);
547     
548 #if !LOG_DISABLED        
549     double start = CFAbsoluteTimeGetCurrent();
550 #endif
551     
552     [icon setScalesWhenResized:YES];
553     [icon setSize:size];
554     
555 #if !LOG_DISABLED
556     double duration = CFAbsoluteTimeGetCurrent() - start;
557     LOG(Timing, "scaling icon took %f seconds.", duration);
558 #endif
559 }
560
561 - (NSData *)_iconDataForIconURL:(NSString *)iconURLString
562 {
563     ASSERT(iconURLString);
564     
565     NSData *iconData = [_private->fileDatabase objectForKey:iconURLString];
566     
567     if ((id)iconData == (id)[NSNull null]) 
568         return nil;
569         
570     return iconData;
571 }
572
573 - (void)_convertToWebCoreFormat
574 {
575     ASSERT(_private);
576     ASSERT(_private->databaseBridge);
577     
578     // If the WebCore Icon Database is not empty, we assume that this conversion has already
579     // taken place and skip the rest of the steps 
580     if (![_private->databaseBridge _isEmpty])
581         return;
582                 
583     NSEnumerator *enumerator = [_private->pageURLToIconURL keyEnumerator];
584     NSString *url, *iconURL;
585     
586     // First, we'll iterate through the PageURL->IconURL map
587     while ((url = [enumerator nextObject]) != nil) {
588         iconURL = [_private->pageURLToIconURL objectForKey:url];
589         if (!iconURL)
590             continue;
591         [_private->databaseBridge _setIconURL:iconURL forPageURL:url];
592     }    
593     
594     // Second, we'll iterate through the icon data we do have
595     enumerator = [_private->iconsOnDiskWithURLs objectEnumerator];
596     NSData *iconData;
597     
598     while ((url = [enumerator nextObject]) != nil) {
599         iconData = [self _iconDataForIconURL:url];
600         if (iconData)
601             [_private->databaseBridge _setIconData:iconData forIconURL:url];
602         else {
603             // This really *shouldn't* happen, so it'd be good to track down why it might happen in a debug build
604             // however, we do know how to handle it gracefully in release
605             LOG_ERROR("%@ is marked as having an icon on disk, but we couldn't get the data for it", url);
606             [_private->databaseBridge _setHaveNoIconForIconURL:url];
607         }
608     }
609     
610     // Finally, we'll iterate through the negative cache we have
611     enumerator = [_private->iconURLsWithNoIcons objectEnumerator];
612     while ((url = [enumerator nextObject]) != nil) 
613         [_private->databaseBridge _setHaveNoIconForIconURL:url];
614    
615     // After we're done converting old style icons over to webcore icons, we delete the entire directory hierarchy 
616     // for the old icon DB
617     NSString *iconPath = [[NSUserDefaults standardUserDefaults] objectForKey:WebIconDatabaseDirectoryDefaultsKey];
618     if (!iconPath)
619         iconPath = WebIconDatabasePath;
620     
621     NSString *fullIconPath = [iconPath stringByExpandingTildeInPath];    
622     NSFileManager *fileManager = [NSFileManager defaultManager];
623     enumerator = [[fileManager directoryContentsAtPath:fullIconPath] objectEnumerator];
624     
625     NSString *databaseFilename = [_private->databaseBridge defaultDatabaseFilename];
626
627     NSString *file;
628     while ((file = [enumerator nextObject]) != nil) {
629         if ([file isEqualTo:databaseFilename])
630             continue;
631         NSString *filePath = [fullIconPath stringByAppendingPathComponent:file];
632         if (![fileManager  removeFileAtPath:filePath handler:nil])
633             LOG_ERROR("Failed to delete %@ from old icon directory", filePath);
634     }
635 }
636
637 @end
638
639 @implementation WebIconDatabasePrivate
640
641 @end
642
643 @implementation NSMutableDictionary (WebIconDatabase)
644
645 - (void)_web_setObjectUsingSetIfNecessary:(id)object forKey:(id)key
646 {
647     id previousObject = [self objectForKey:key];
648     if (previousObject == nil) {
649         [self setObject:object forKey:key];
650     } else if ([previousObject isKindOfClass:[NSMutableSet class]]) {
651         [previousObject addObject:object];
652     } else {
653         NSMutableSet *objects = [[NSMutableSet alloc] initWithObjects:previousObject, object, nil];
654         [self setObject:objects forKey:key];
655         [objects release];
656     }
657 }
658
659 @end