| /* |
| * 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 <Foundation/NSError.h> |
| #import <JavaScriptCore/Assertions.h> |
| #import <WebCore/WebCoreHistory.h> |
| #import <wtf/Vector.h> |
| |
| 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<int> 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<int> 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 <WebCoreHistoryProvider> |
| { |
| 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 |