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