/* * Copyright (C) 2005 Apple Computer, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "WebHistory.h" #import "WebHistoryPrivate.h" #import "WebHistoryItem.h" #import "WebHistoryItemInternal.h" #import "WebHistoryItemPrivate.h" #import "WebKitLogging.h" #import "WebNSURLExtras.h" #import #import #import #import NSString *WebHistoryItemsAddedNotification = @"WebHistoryItemsAddedNotification"; NSString *WebHistoryItemsRemovedNotification = @"WebHistoryItemsRemovedNotification"; NSString *WebHistoryAllItemsRemovedNotification = @"WebHistoryAllItemsRemovedNotification"; NSString *WebHistoryLoadedNotification = @"WebHistoryLoadedNotification"; NSString *WebHistoryItemsDiscardedWhileLoadingNotification = @"WebHistoryItemsDiscardedWhileLoadingNotification"; NSString *WebHistorySavedNotification = @"WebHistorySavedNotification"; NSString *WebHistoryItemsKey = @"WebHistoryItems"; static WebHistory *_sharedHistory = nil; NSString *FileVersionKey = @"WebHistoryFileVersion"; NSString *DatesArrayKey = @"WebHistoryDates"; #define currentFileVersion 1 @implementation WebHistoryPrivate #pragma mark OBJECT FRAMEWORK + (void)initialize { [[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithObjectsAndKeys: @"1000", @"WebKitHistoryItemLimit", @"7", @"WebKitHistoryAgeInDaysLimit", nil]]; } - (id)init { if (![super init]) { return nil; } _entriesByURL = [[NSMutableDictionary alloc] init]; _entriesByDate = new DateToEntriesMap; return self; } - (void)dealloc { [_entriesByURL release]; [_orderedLastVisitedDays release]; delete _entriesByDate; [super dealloc]; } - (void)finalize { delete _entriesByDate; [super finalize]; } #pragma mark MODIFYING CONTENTS WebHistoryDateKey timeIntervalForBeginningOfDay(NSTimeInterval interval) { CFTimeZoneRef timeZone = CFTimeZoneCopyDefault(); CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(interval, timeZone); date.hour = 0; date.minute = 0; date.second = 0; NSTimeInterval result = CFGregorianDateGetAbsoluteTime(date, timeZone); CFRelease(timeZone); // Converting from double to int64_t is safe here as NSDate's useful range // is -2**48 .. 2**47 which will safely fit in an int64_t. return (WebHistoryDateKey)result; } // Returns whether the day is already in the list of days, // and fills in *key with the key used to access its location - (BOOL)findKey:(WebHistoryDateKey*)key forDay:(NSTimeInterval)date { ASSERT_ARG(key, key != nil); *key = timeIntervalForBeginningOfDay(date); return _entriesByDate->contains(*key); } - (void)insertItem:(WebHistoryItem *)entry forDateKey:(WebHistoryDateKey)dateKey { ASSERT_ARG(entry, entry != nil); ASSERT(_entriesByDate->contains(dateKey)); NSMutableArray *entriesForDate = _entriesByDate->get(dateKey).get(); NSTimeInterval entryDate = [entry lastVisitedTimeInterval]; unsigned count = [entriesForDate count]; // The entries for each day are stored in a sorted array with the most recent entry first // Check for the common cases of the entry being newer than all existing entries or the first entry of the day if (!count || [[entriesForDate objectAtIndex:0] lastVisitedTimeInterval] < entryDate) { [entriesForDate insertObject:entry atIndex:0]; return; } // .. or older than all existing entries if (count > 0 && [[entriesForDate objectAtIndex:count - 1] lastVisitedTimeInterval] >= entryDate) { [entriesForDate insertObject:entry atIndex:count]; return; } unsigned low = 0; unsigned high = count; while (low < high) { unsigned mid = low + (high - low) / 2; if ([[entriesForDate objectAtIndex:mid] lastVisitedTimeInterval] >= entryDate) low = mid + 1; else high = mid; } // low is now the index of the first entry that is older than entryDate [entriesForDate insertObject:entry atIndex:low]; } - (BOOL)_removeItemFromDateCaches:(WebHistoryItem *)entry { WebHistoryDateKey dateKey; BOOL foundDate = [self findKey:&dateKey forDay:[entry lastVisitedTimeInterval]]; if (!foundDate) return NO; DateToEntriesMap::iterator it = _entriesByDate->find(dateKey); NSMutableArray *entriesForDate = it->second.get(); [entriesForDate removeObjectIdenticalTo:entry]; // remove this date entirely if there are no other entries on it if ([entriesForDate count] == 0) { _entriesByDate->remove(it); // Clear _orderedLastVisitedDays so it will be regenerated when next requested. [_orderedLastVisitedDays release]; _orderedLastVisitedDays = nil; } return YES; } - (BOOL)removeItemForURLString: (NSString *)URLString { WebHistoryItem *entry = [_entriesByURL objectForKey: URLString]; if (entry == nil) { return NO; } [_entriesByURL removeObjectForKey: URLString]; #if ASSERT_DISABLED [self _removeItemFromDateCaches:entry]; #else BOOL itemWasInDateCaches = [self _removeItemFromDateCaches:entry]; ASSERT(itemWasInDateCaches); #endif return YES; } - (void)_addItemToDateCaches:(WebHistoryItem *)entry { WebHistoryDateKey dateKey; if ([self findKey:&dateKey forDay:[entry lastVisitedTimeInterval]]) // other entries already exist for this date [self insertItem:entry forDateKey:dateKey]; else { // no other entries exist for this date NSMutableArray *entries = [[NSMutableArray alloc] initWithObjects:&entry count:1]; _entriesByDate->set(dateKey, entries); [entries release]; // Clear _orderedLastVisitedDays so it will be regenerated when next requested. [_orderedLastVisitedDays release]; _orderedLastVisitedDays = nil; } } - (void)addItem:(WebHistoryItem *)entry { ASSERT_ARG(entry, entry); ASSERT_ARG(entry, [entry lastVisitedTimeInterval] != 0); NSString *URLString = [entry URLString]; WebHistoryItem *oldEntry = [_entriesByURL objectForKey:URLString]; if (oldEntry) { // The last reference to oldEntry might be this dictionary, so we hold onto a reference // until we're done with oldEntry. [oldEntry retain]; [self removeItemForURLString:URLString]; // If we already have an item with this URL, we need to merge info that drives the // URL autocomplete heuristics from that item into the new one. [entry _mergeAutoCompleteHints:oldEntry]; [oldEntry release]; } [self _addItemToDateCaches:entry]; [_entriesByURL setObject:entry forKey:URLString]; } - (void)setLastVisitedTimeInterval:(NSTimeInterval)time forItem:(WebHistoryItem *)entry { #if ASSERT_DISABLED [self _removeItemFromDateCaches:entry]; #else BOOL entryWasPresent = [self _removeItemFromDateCaches:entry]; ASSERT(entryWasPresent); #endif [entry _setLastVisitedTimeInterval:time]; [self _addItemToDateCaches:entry]; // Don't send notification until entry is back in the right place in the date caches, // since observers might fetch history by date when they receive the notification. [[NSNotificationCenter defaultCenter] postNotificationName:WebHistoryItemChangedNotification object:entry userInfo:nil]; } - (BOOL)removeItem: (WebHistoryItem *)entry { WebHistoryItem *matchingEntry; NSString *URLString; URLString = [entry URLString]; // If this exact object isn't stored, then make no change. // FIXME: Is this the right behavior if this entry isn't present, but another entry for the same URL is? // Maybe need to change the API to make something like removeEntryForURLString public instead. matchingEntry = [_entriesByURL objectForKey: URLString]; if (matchingEntry != entry) { return NO; } [self removeItemForURLString: URLString]; return YES; } - (BOOL)removeItems: (NSArray *)entries { int index, count; count = [entries count]; if (count == 0) { return NO; } for (index = 0; index < count; ++index) { [self removeItem:[entries objectAtIndex:index]]; } return YES; } - (BOOL)removeAllItems { if ([_entriesByURL count] == 0) { return NO; } _entriesByDate->clear(); [_entriesByURL removeAllObjects]; // Clear _orderedLastVisitedDays so it will be regenerated when next requested. [_orderedLastVisitedDays release]; _orderedLastVisitedDays = nil; return YES; } - (void)addItems:(NSArray *)newEntries { NSEnumerator *enumerator; WebHistoryItem *entry; // There is no guarantee that the incoming entries are in any particular // order, but if this is called with a set of entries that were created by // iterating through the results of orderedLastVisitedDays and orderedItemsLastVisitedOnDayy // then they will be ordered chronologically from newest to oldest. We can make adding them // faster (fewer compares) by inserting them from oldest to newest. enumerator = [newEntries reverseObjectEnumerator]; while ((entry = [enumerator nextObject]) != nil) { [self addItem:entry]; } } #pragma mark DATE-BASED RETRIEVAL - (NSArray *)orderedLastVisitedDays { if (!_orderedLastVisitedDays) { Vector daysAsTimeIntervals; daysAsTimeIntervals.reserveCapacity(_entriesByDate->size()); DateToEntriesMap::const_iterator end = _entriesByDate->end(); for (DateToEntriesMap::const_iterator it = _entriesByDate->begin(); it != end; ++it) daysAsTimeIntervals.append(it->first); std::sort(daysAsTimeIntervals.begin(), daysAsTimeIntervals.end()); size_t count = daysAsTimeIntervals.size(); _orderedLastVisitedDays = [[NSMutableArray alloc] initWithCapacity:count]; for (int i = count - 1; i >= 0; i--) { NSTimeInterval interval = daysAsTimeIntervals[i]; NSCalendarDate *date = [[NSCalendarDate alloc] initWithTimeIntervalSinceReferenceDate:interval]; [_orderedLastVisitedDays addObject:date]; [date release]; } } return _orderedLastVisitedDays; } - (NSArray *)orderedItemsLastVisitedOnDay: (NSCalendarDate *)date { WebHistoryDateKey dateKey; if ([self findKey:&dateKey forDay:[date timeIntervalSinceReferenceDate]]) return _entriesByDate->get(dateKey).get(); return nil; } #pragma mark URL MATCHING - (WebHistoryItem *)itemForURLString:(NSString *)URLString { return [_entriesByURL objectForKey: URLString]; } - (BOOL)containsItemForURLString: (NSString *)URLString { return [self itemForURLString:URLString] != nil; } - (BOOL)containsURL: (NSURL *)URL { return [self itemForURLString:[URL _web_originalDataAsString]] != nil; } - (WebHistoryItem *)itemForURL:(NSURL *)URL { return [self itemForURLString:[URL _web_originalDataAsString]]; } #pragma mark ARCHIVING/UNARCHIVING - (void)setHistoryAgeInDaysLimit:(int)limit { ageInDaysLimitSet = YES; ageInDaysLimit = limit; } - (int)historyAgeInDaysLimit { if (ageInDaysLimitSet) return ageInDaysLimit; return [[NSUserDefaults standardUserDefaults] integerForKey: @"WebKitHistoryAgeInDaysLimit"]; } - (void)setHistoryItemLimit:(int)limit { itemLimitSet = YES; itemLimit = limit; } - (int)historyItemLimit { if (itemLimitSet) return itemLimit; return [[NSUserDefaults standardUserDefaults] integerForKey: @"WebKitHistoryItemLimit"]; } // Return a date that marks the age limit for history entries saved to or // loaded from disk. Any entry older than this item should be rejected. - (NSCalendarDate *)_ageLimitDate { return [[NSCalendarDate calendarDate] dateByAddingYears:0 months:0 days:-[self historyAgeInDaysLimit] hours:0 minutes:0 seconds:0]; } // Return a flat array of WebHistoryItems. Ignores the date and item count limits; these are // respected when loading instead of when saving, so that clients can learn of discarded items // by listening to WebHistoryItemsDiscardedWhileLoadingNotification. - (NSArray *)arrayRepresentation { NSMutableArray *arrayRep = [NSMutableArray array]; Vector dateKeys; dateKeys.reserveCapacity(_entriesByDate->size()); DateToEntriesMap::const_iterator end = _entriesByDate->end(); for (DateToEntriesMap::const_iterator it = _entriesByDate->begin(); it != end; ++it) dateKeys.append(it->first); std::sort(dateKeys.begin(), dateKeys.end()); for (int dateIndex = dateKeys.size() - 1; dateIndex >= 0; dateIndex--) { NSArray *entries = _entriesByDate->get(dateKeys[dateIndex]).get(); int entryCount = [entries count]; for (int entryIndex = 0; entryIndex < entryCount; ++entryIndex) [arrayRep addObject:[[entries objectAtIndex:entryIndex] dictionaryRepresentation]]; } return arrayRep; } - (BOOL)_loadHistoryGutsFromURL:(NSURL *)URL savedItemsCount:(int *)numberOfItemsLoaded collectDiscardedItemsInto:(NSMutableArray *)discardedItems error:(NSError **)error { *numberOfItemsLoaded = 0; NSDictionary *dictionary = nil; // Optimize loading from local file, which is faster than using the general URL loading mechanism if ([URL isFileURL]) { dictionary = [NSDictionary dictionaryWithContentsOfFile:[URL path]]; if (!dictionary) { #if !LOG_DISABLED if ([[NSFileManager defaultManager] fileExistsAtPath:[URL path]]) LOG_ERROR("unable to read history from file %@; perhaps contents are corrupted", [URL path]); #endif // else file doesn't exist, which is normal the first time return NO; } } else { NSData *data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:URL] returningResponse:nil error:error]; if (data && [data length] > 0) { dictionary = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:nil errorDescription:nil]; } } // We used to support NSArrays here, but that was before Safari 1.0 shipped. We will no longer support // that ancient format, so anything that isn't an NSDictionary is bogus. if (![dictionary isKindOfClass:[NSDictionary class]]) return NO; NSNumber *fileVersionObject = [dictionary objectForKey:FileVersionKey]; int fileVersion; // we don't trust data obtained from elsewhere, so double-check if (fileVersionObject != nil && [fileVersionObject isKindOfClass:[NSNumber class]]) { fileVersion = [fileVersionObject intValue]; } else { LOG_ERROR("history file version can't be determined, therefore not loading"); return NO; } if (fileVersion > currentFileVersion) { LOG_ERROR("history file version is %d, newer than newest known version %d, therefore not loading", fileVersion, currentFileVersion); return NO; } NSArray *array = [dictionary objectForKey:DatesArrayKey]; int itemCountLimit = [self historyItemLimit]; NSTimeInterval ageLimitDate = [[self _ageLimitDate] timeIntervalSinceReferenceDate]; NSEnumerator *enumerator = [array objectEnumerator]; BOOL ageLimitPassed = NO; BOOL itemLimitPassed = NO; ASSERT(*numberOfItemsLoaded == 0); NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSDictionary *itemAsDictionary; while ((itemAsDictionary = [enumerator nextObject]) != nil) { WebHistoryItem *item = [[WebHistoryItem alloc] initFromDictionaryRepresentation:itemAsDictionary]; // item without URL is useless; data on disk must have been bad; ignore if ([item URLString]) { // Test against date limit. Since the items are ordered newest to oldest, we can stop comparing // once we've found the first item that's too old. if (!ageLimitPassed && [item lastVisitedTimeInterval] <= ageLimitDate) ageLimitPassed = YES; if (ageLimitPassed || itemLimitPassed) [discardedItems addObject:item]; else { [self addItem:item]; ++(*numberOfItemsLoaded); if (*numberOfItemsLoaded == itemCountLimit) itemLimitPassed = YES; // Draining the autorelease pool every 50 iterations was found by experimentation to be optimal if (*numberOfItemsLoaded % 50 == 0) { [pool drain]; pool = [[NSAutoreleasePool alloc] init]; } } } [item release]; } [pool drain]; return YES; } - (BOOL)loadFromURL:(NSURL *)URL collectDiscardedItemsInto:(NSMutableArray *)discardedItems error:(NSError **)error { int numberOfItems; double start, duration; BOOL result; start = CFAbsoluteTimeGetCurrent(); result = [self _loadHistoryGutsFromURL:URL savedItemsCount:&numberOfItems collectDiscardedItemsInto:discardedItems error:error]; if (result) { duration = CFAbsoluteTimeGetCurrent() - start; LOG(Timing, "loading %d history entries from %@ took %f seconds", numberOfItems, URL, duration); } return result; } - (BOOL)_saveHistoryGuts: (int *)numberOfItemsSaved URL:(NSURL *)URL error:(NSError **)error { *numberOfItemsSaved = 0; // FIXME: Correctly report error when new API is ready. if (error) *error = nil; NSArray *array = [self arrayRepresentation]; NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys: array, DatesArrayKey, [NSNumber numberWithInt:currentFileVersion], FileVersionKey, nil]; NSData *data = [NSPropertyListSerialization dataFromPropertyList:dictionary format:NSPropertyListBinaryFormat_v1_0 errorDescription:nil]; if (![data writeToURL:URL atomically:YES]) { LOG_ERROR("attempt to save %@ to %@ failed", dictionary, URL); return NO; } *numberOfItemsSaved = [array count]; return YES; } - (BOOL)saveToURL:(NSURL *)URL error:(NSError **)error { int numberOfItems; double start, duration; BOOL result; start = CFAbsoluteTimeGetCurrent(); result = [self _saveHistoryGuts: &numberOfItems URL:URL error:error]; if (result) { duration = CFAbsoluteTimeGetCurrent() - start; LOG(Timing, "saving %d history entries to %@ took %f seconds", numberOfItems, URL, duration); } return result; } @end @interface _WebCoreHistoryProvider : NSObject { WebHistory *history; } - initWithHistory: (WebHistory *)h; @end @implementation _WebCoreHistoryProvider - initWithHistory: (WebHistory *)h { history = [h retain]; return self; } static inline bool matchLetter(char c, char lowercaseLetter) { return (c | 0x20) == lowercaseLetter; } static inline bool matchUnicodeLetter(UniChar c, UniChar lowercaseLetter) { return (c | 0x20) == lowercaseLetter; } #define UNICODE_BUFFER_SIZE 1024 - (BOOL)containsURL:(const UniChar *)unicode length:(unsigned)length { const UniChar *unicodeStr = unicode; UniChar staticStrBuffer[UNICODE_BUFFER_SIZE]; UniChar *strBuffer = NULL; BOOL needToAddSlash = FALSE; if (length >= 6 && matchUnicodeLetter(unicode[0], 'h') && matchUnicodeLetter(unicode[1], 't') && matchUnicodeLetter(unicode[2], 't') && matchUnicodeLetter(unicode[3], 'p') && (unicode[4] == ':' || (matchUnicodeLetter(unicode[4], 's') && unicode[5] == ':'))) { unsigned pos = unicode[4] == ':' ? 5 : 6; // skip possible initial two slashes if (pos + 1 < length && unicode[pos] == '/' && unicode[pos + 1] == '/') { pos += 2; } while (pos < length && unicode[pos] != '/') { pos++; } if (pos == length) { needToAddSlash = TRUE; } } if (needToAddSlash) { if (length + 1 <= UNICODE_BUFFER_SIZE) { strBuffer = staticStrBuffer; } else { strBuffer = (UniChar*)malloc(sizeof(UniChar) * (length + 1)); } memcpy(strBuffer, unicode, 2 * length); strBuffer[length] = '/'; length++; unicodeStr = strBuffer; } CFStringRef str = CFStringCreateWithCharactersNoCopy(NULL, unicodeStr, length, kCFAllocatorNull); BOOL result = [history containsItemForURLString:(id)str]; CFRelease(str); if (strBuffer != staticStrBuffer) { free(strBuffer); } return result; } - (void)dealloc { [history release]; [super dealloc]; } @end @implementation WebHistory + (WebHistory *)optionalSharedHistory { return _sharedHistory; } + (void)setOptionalSharedHistory: (WebHistory *)history { // FIXME. Need to think about multiple instances of WebHistory per application // and correct synchronization of history file between applications. [WebCoreHistory setHistoryProvider: [[[_WebCoreHistoryProvider alloc] initWithHistory: history] autorelease]]; if (_sharedHistory != history){ [_sharedHistory release]; _sharedHistory = [history retain]; } } - (id)init { if ((self = [super init]) != nil) { _historyPrivate = [[WebHistoryPrivate alloc] init]; } return self; } - (void)dealloc { [_historyPrivate release]; [super dealloc]; } #pragma mark MODIFYING CONTENTS - (void)_sendNotification:(NSString *)name entries:(NSArray *)entries { NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:entries, WebHistoryItemsKey, nil]; [[NSNotificationCenter defaultCenter] postNotificationName: name object: self userInfo: userInfo]; } - (WebHistoryItem *)addItemForURL: (NSURL *)URL { WebHistoryItem *entry = [[WebHistoryItem alloc] initWithURL:URL title:nil]; [entry _setLastVisitedTimeInterval: [NSDate timeIntervalSinceReferenceDate]]; [self addItem: entry]; [entry release]; return entry; } - (void)addItem: (WebHistoryItem *)entry { LOG (History, "adding %@", entry); [_historyPrivate addItem: entry]; [self _sendNotification: WebHistoryItemsAddedNotification entries: [NSArray arrayWithObject:entry]]; } - (void)removeItem: (WebHistoryItem *)entry { if ([_historyPrivate removeItem: entry]) { [self _sendNotification: WebHistoryItemsRemovedNotification entries: [NSArray arrayWithObject:entry]]; } } - (void)removeItems: (NSArray *)entries { if ([_historyPrivate removeItems:entries]) { [self _sendNotification: WebHistoryItemsRemovedNotification entries: entries]; } } - (void)removeAllItems { if ([_historyPrivate removeAllItems]) { [[NSNotificationCenter defaultCenter] postNotificationName: WebHistoryAllItemsRemovedNotification object: self]; } } - (void)addItems:(NSArray *)newEntries { [_historyPrivate addItems:newEntries]; [self _sendNotification: WebHistoryItemsAddedNotification entries: newEntries]; } - (void)setLastVisitedTimeInterval:(NSTimeInterval)time forItem:(WebHistoryItem *)entry { [_historyPrivate setLastVisitedTimeInterval:time forItem:entry]; } #pragma mark DATE-BASED RETRIEVAL - (NSArray *)orderedLastVisitedDays { return [_historyPrivate orderedLastVisitedDays]; } - (NSArray *)orderedItemsLastVisitedOnDay: (NSCalendarDate *)date { return [_historyPrivate orderedItemsLastVisitedOnDay: date]; } #pragma mark URL MATCHING - (BOOL)containsItemForURLString: (NSString *)URLString { return [_historyPrivate containsItemForURLString: URLString]; } - (BOOL)containsURL: (NSURL *)URL { return [_historyPrivate containsURL: URL]; } - (WebHistoryItem *)itemForURL:(NSURL *)URL { return [_historyPrivate itemForURL:URL]; } #pragma mark SAVING TO DISK - (BOOL)loadFromURL:(NSURL *)URL error:(NSError **)error { NSMutableArray *discardedItems = [NSMutableArray array]; if ([_historyPrivate loadFromURL:URL collectDiscardedItemsInto:discardedItems error:error]) { [[NSNotificationCenter defaultCenter] postNotificationName:WebHistoryLoadedNotification object:self]; if ([discardedItems count] > 0) [self _sendNotification:WebHistoryItemsDiscardedWhileLoadingNotification entries:discardedItems]; return YES; } return NO; } - (BOOL)saveToURL:(NSURL *)URL error:(NSError **)error { // FIXME: Use new foundation API to get error when ready. if([_historyPrivate saveToURL:URL error:error]){ [[NSNotificationCenter defaultCenter] postNotificationName: WebHistorySavedNotification object: self]; return YES; } return NO; } - (WebHistoryItem *)_itemForURLString:(NSString *)URLString { return [_historyPrivate itemForURLString: URLString]; } - (NSCalendarDate*)ageLimitDate { return [_historyPrivate _ageLimitDate]; } - (void)setHistoryItemLimit:(int)limit { [_historyPrivate setHistoryItemLimit:limit]; } - (int)historyItemLimit { return [_historyPrivate historyItemLimit]; } - (void)setHistoryAgeInDaysLimit:(int)limit { [_historyPrivate setHistoryAgeInDaysLimit:limit]; } - (int)historyAgeInDaysLimit { return [_historyPrivate historyAgeInDaysLimit]; } @end