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