1/* 2 * Copyright (C) 2005, 2008, 2009 Apple 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 29#import "WebHistoryInternal.h" 30 31#import "WebHistoryItemInternal.h" 32#import "WebKitLogging.h" 33#import "WebNSURLExtras.h" 34#import "WebTypesInternal.h" 35#import <WebCore/HistoryItem.h> 36#import <WebCore/HistoryPropertyList.h> 37#import <WebCore/PageGroup.h> 38 39using namespace WebCore; 40using namespace std; 41 42typedef int64_t WebHistoryDateKey; 43typedef HashMap<WebHistoryDateKey, RetainPtr<NSMutableArray> > DateToEntriesMap; 44 45NSString *WebHistoryItemsAddedNotification = @"WebHistoryItemsAddedNotification"; 46NSString *WebHistoryItemsRemovedNotification = @"WebHistoryItemsRemovedNotification"; 47NSString *WebHistoryAllItemsRemovedNotification = @"WebHistoryAllItemsRemovedNotification"; 48NSString *WebHistoryLoadedNotification = @"WebHistoryLoadedNotification"; 49NSString *WebHistoryItemsDiscardedWhileLoadingNotification = @"WebHistoryItemsDiscardedWhileLoadingNotification"; 50NSString *WebHistorySavedNotification = @"WebHistorySavedNotification"; 51NSString *WebHistoryItemsKey = @"WebHistoryItems"; 52 53static WebHistory *_sharedHistory = nil; 54 55NSString *FileVersionKey = @"WebHistoryFileVersion"; 56NSString *DatesArrayKey = @"WebHistoryDates"; 57 58#define currentFileVersion 1 59 60class WebHistoryWriter : public HistoryPropertyListWriter { 61public: 62 WebHistoryWriter(DateToEntriesMap*); 63 64private: 65 virtual void writeHistoryItems(BinaryPropertyListObjectStream&); 66 67 DateToEntriesMap* m_entriesByDate; 68 Vector<int> m_dateKeys; 69}; 70 71@interface WebHistoryPrivate : NSObject { 72@private 73 NSMutableDictionary *_entriesByURL; 74 DateToEntriesMap* _entriesByDate; 75 NSMutableArray *_orderedLastVisitedDays; 76 BOOL itemLimitSet; 77 int itemLimit; 78 BOOL ageInDaysLimitSet; 79 int ageInDaysLimit; 80} 81 82- (WebHistoryItem *)visitedURL:(NSURL *)url withTitle:(NSString *)title increaseVisitCount:(BOOL)increaseVisitCount; 83 84- (BOOL)addItem:(WebHistoryItem *)entry discardDuplicate:(BOOL)discardDuplicate; 85- (void)addItems:(NSArray *)newEntries; 86- (BOOL)removeItem:(WebHistoryItem *)entry; 87- (BOOL)removeItems:(NSArray *)entries; 88- (BOOL)removeAllItems; 89 90- (NSArray *)orderedLastVisitedDays; 91- (NSArray *)orderedItemsLastVisitedOnDay:(NSCalendarDate *)calendarDate; 92- (BOOL)containsURL:(NSURL *)URL; 93- (WebHistoryItem *)itemForURL:(NSURL *)URL; 94- (WebHistoryItem *)itemForURLString:(NSString *)URLString; 95- (NSArray *)allItems; 96 97- (BOOL)loadFromURL:(NSURL *)URL collectDiscardedItemsInto:(NSMutableArray *)discardedItems error:(NSError **)error; 98- (BOOL)saveToURL:(NSURL *)URL error:(NSError **)error; 99 100- (NSCalendarDate *)ageLimitDate; 101 102- (void)setHistoryItemLimit:(int)limit; 103- (int)historyItemLimit; 104- (void)setHistoryAgeInDaysLimit:(int)limit; 105- (int)historyAgeInDaysLimit; 106 107- (void)addVisitedLinksToPageGroup:(PageGroup&)group; 108 109@end 110 111@implementation WebHistoryPrivate 112 113#pragma mark OBJECT FRAMEWORK 114 115+ (void)initialize 116{ 117 [[NSUserDefaults standardUserDefaults] registerDefaults: 118 [NSDictionary dictionaryWithObjectsAndKeys: 119 @"1000", @"WebKitHistoryItemLimit", 120 @"7", @"WebKitHistoryAgeInDaysLimit", 121 nil]]; 122} 123 124- (id)init 125{ 126 if (![super init]) 127 return nil; 128 129 _entriesByURL = [[NSMutableDictionary alloc] init]; 130 _entriesByDate = new DateToEntriesMap; 131 132 return self; 133} 134 135- (void)dealloc 136{ 137 [_entriesByURL release]; 138 [_orderedLastVisitedDays release]; 139 delete _entriesByDate; 140 [super dealloc]; 141} 142 143- (void)finalize 144{ 145 delete _entriesByDate; 146 [super finalize]; 147} 148 149#pragma mark MODIFYING CONTENTS 150 151static WebHistoryDateKey timeIntervalForBeginningOfDay(NSTimeInterval interval) 152{ 153 CFTimeZoneRef timeZone = CFTimeZoneCopyDefault(); 154 CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(interval, timeZone); 155 date.hour = 0; 156 date.minute = 0; 157 date.second = 0; 158 NSTimeInterval result = CFGregorianDateGetAbsoluteTime(date, timeZone); 159 CFRelease(timeZone); 160 161 // Converting from double to int64_t is safe here as NSDate's useful range 162 // is -2**48 .. 2**47 which will safely fit in an int64_t. 163 return (WebHistoryDateKey)result; 164} 165 166// Returns whether the day is already in the list of days, 167// and fills in *key with the key used to access its location 168- (BOOL)findKey:(WebHistoryDateKey*)key forDay:(NSTimeInterval)date 169{ 170 ASSERT_ARG(key, key != nil); 171 *key = timeIntervalForBeginningOfDay(date); 172 return _entriesByDate->contains(*key); 173} 174 175- (void)insertItem:(WebHistoryItem *)entry forDateKey:(WebHistoryDateKey)dateKey 176{ 177 ASSERT_ARG(entry, entry != nil); 178 ASSERT(_entriesByDate->contains(dateKey)); 179 180 NSMutableArray *entriesForDate = _entriesByDate->get(dateKey).get(); 181 NSTimeInterval entryDate = [entry lastVisitedTimeInterval]; 182 183 unsigned count = [entriesForDate count]; 184 185 // The entries for each day are stored in a sorted array with the most recent entry first 186 // Check for the common cases of the entry being newer than all existing entries or the first entry of the day 187 if (!count || [[entriesForDate objectAtIndex:0] lastVisitedTimeInterval] < entryDate) { 188 [entriesForDate insertObject:entry atIndex:0]; 189 return; 190 } 191 // .. or older than all existing entries 192 if (count > 0 && [[entriesForDate objectAtIndex:count - 1] lastVisitedTimeInterval] >= entryDate) { 193 [entriesForDate insertObject:entry atIndex:count]; 194 return; 195 } 196 197 unsigned low = 0; 198 unsigned high = count; 199 while (low < high) { 200 unsigned mid = low + (high - low) / 2; 201 if ([[entriesForDate objectAtIndex:mid] lastVisitedTimeInterval] >= entryDate) 202 low = mid + 1; 203 else 204 high = mid; 205 } 206 207 // low is now the index of the first entry that is older than entryDate 208 [entriesForDate insertObject:entry atIndex:low]; 209} 210 211- (BOOL)removeItemFromDateCaches:(WebHistoryItem *)entry 212{ 213 WebHistoryDateKey dateKey; 214 BOOL foundDate = [self findKey:&dateKey forDay:[entry lastVisitedTimeInterval]]; 215 216 if (!foundDate) 217 return NO; 218 219 DateToEntriesMap::iterator it = _entriesByDate->find(dateKey); 220 NSMutableArray *entriesForDate = it->second.get(); 221 [entriesForDate removeObjectIdenticalTo:entry]; 222 223 // remove this date entirely if there are no other entries on it 224 if ([entriesForDate count] == 0) { 225 _entriesByDate->remove(it); 226 // Clear _orderedLastVisitedDays so it will be regenerated when next requested. 227 [_orderedLastVisitedDays release]; 228 _orderedLastVisitedDays = nil; 229 } 230 231 return YES; 232} 233 234- (BOOL)removeItemForURLString:(NSString *)URLString 235{ 236 WebHistoryItem *entry = [_entriesByURL objectForKey:URLString]; 237 if (!entry) 238 return NO; 239 240 [_entriesByURL removeObjectForKey:URLString]; 241 242#if ASSERT_DISABLED 243 [self removeItemFromDateCaches:entry]; 244#else 245 BOOL itemWasInDateCaches = [self removeItemFromDateCaches:entry]; 246 ASSERT(itemWasInDateCaches); 247#endif 248 249 if (![_entriesByURL count]) 250 PageGroup::removeAllVisitedLinks(); 251 252 return YES; 253} 254 255- (void)addItemToDateCaches:(WebHistoryItem *)entry 256{ 257 WebHistoryDateKey dateKey; 258 if ([self findKey:&dateKey forDay:[entry lastVisitedTimeInterval]]) 259 // other entries already exist for this date 260 [self insertItem:entry forDateKey:dateKey]; 261 else { 262 // no other entries exist for this date 263 NSMutableArray *entries = [[NSMutableArray alloc] initWithObjects:&entry count:1]; 264 _entriesByDate->set(dateKey, entries); 265 [entries release]; 266 // Clear _orderedLastVisitedDays so it will be regenerated when next requested. 267 [_orderedLastVisitedDays release]; 268 _orderedLastVisitedDays = nil; 269 } 270} 271 272- (WebHistoryItem *)visitedURL:(NSURL *)url withTitle:(NSString *)title increaseVisitCount:(BOOL)increaseVisitCount 273{ 274 ASSERT(url); 275 ASSERT(title); 276 277 NSString *URLString = [url _web_originalDataAsString]; 278 WebHistoryItem *entry = [_entriesByURL objectForKey:URLString]; 279 280 if (entry) { 281 LOG(History, "Updating global history entry %@", entry); 282 // Remove the item from date caches before changing its last visited date. Otherwise we might get duplicate entries 283 // as seen in <rdar://problem/6570573>. 284 BOOL itemWasInDateCaches = [self removeItemFromDateCaches:entry]; 285 ASSERT_UNUSED(itemWasInDateCaches, itemWasInDateCaches); 286 287 [entry _visitedWithTitle:title increaseVisitCount:increaseVisitCount]; 288 } else { 289 LOG(History, "Adding new global history entry for %@", url); 290 entry = [[WebHistoryItem alloc] initWithURLString:URLString title:title lastVisitedTimeInterval:[NSDate timeIntervalSinceReferenceDate]]; 291 [entry _recordInitialVisit]; 292 [_entriesByURL setObject:entry forKey:URLString]; 293 [entry release]; 294 } 295 296 [self addItemToDateCaches:entry]; 297 298 return entry; 299} 300 301- (BOOL)addItem:(WebHistoryItem *)entry discardDuplicate:(BOOL)discardDuplicate 302{ 303 ASSERT_ARG(entry, entry); 304 ASSERT_ARG(entry, [entry lastVisitedTimeInterval] != 0); 305 306 NSString *URLString = [entry URLString]; 307 308 WebHistoryItem *oldEntry = [_entriesByURL objectForKey:URLString]; 309 if (oldEntry) { 310 if (discardDuplicate) 311 return NO; 312 313 // The last reference to oldEntry might be this dictionary, so we hold onto a reference 314 // until we're done with oldEntry. 315 [oldEntry retain]; 316 [self removeItemForURLString:URLString]; 317 318 // If we already have an item with this URL, we need to merge info that drives the 319 // URL autocomplete heuristics from that item into the new one. 320 [entry _mergeAutoCompleteHints:oldEntry]; 321 [oldEntry release]; 322 } 323 324 [self addItemToDateCaches:entry]; 325 [_entriesByURL setObject:entry forKey:URLString]; 326 327 return YES; 328} 329 330- (BOOL)removeItem:(WebHistoryItem *)entry 331{ 332 NSString *URLString = [entry URLString]; 333 334 // If this exact object isn't stored, then make no change. 335 // FIXME: Is this the right behavior if this entry isn't present, but another entry for the same URL is? 336 // Maybe need to change the API to make something like removeEntryForURLString public instead. 337 WebHistoryItem *matchingEntry = [_entriesByURL objectForKey:URLString]; 338 if (matchingEntry != entry) 339 return NO; 340 341 [self removeItemForURLString:URLString]; 342 343 return YES; 344} 345 346- (BOOL)removeItems:(NSArray *)entries 347{ 348 NSUInteger count = [entries count]; 349 if (!count) 350 return NO; 351 352 for (NSUInteger index = 0; index < count; ++index) 353 [self removeItem:[entries objectAtIndex:index]]; 354 355 return YES; 356} 357 358- (BOOL)removeAllItems 359{ 360 if (_entriesByDate->isEmpty()) 361 return NO; 362 363 _entriesByDate->clear(); 364 [_entriesByURL removeAllObjects]; 365 366 // Clear _orderedLastVisitedDays so it will be regenerated when next requested. 367 [_orderedLastVisitedDays release]; 368 _orderedLastVisitedDays = nil; 369 370 PageGroup::removeAllVisitedLinks(); 371 372 return YES; 373} 374 375- (void)addItems:(NSArray *)newEntries 376{ 377 // There is no guarantee that the incoming entries are in any particular 378 // order, but if this is called with a set of entries that were created by 379 // iterating through the results of orderedLastVisitedDays and orderedItemsLastVisitedOnDayy 380 // then they will be ordered chronologically from newest to oldest. We can make adding them 381 // faster (fewer compares) by inserting them from oldest to newest. 382 NSEnumerator *enumerator = [newEntries reverseObjectEnumerator]; 383 while (WebHistoryItem *entry = [enumerator nextObject]) 384 [self addItem:entry discardDuplicate:NO]; 385} 386 387#pragma mark DATE-BASED RETRIEVAL 388 389- (NSArray *)orderedLastVisitedDays 390{ 391 if (!_orderedLastVisitedDays) { 392 Vector<int> daysAsTimeIntervals; 393 daysAsTimeIntervals.reserveCapacity(_entriesByDate->size()); 394 DateToEntriesMap::const_iterator end = _entriesByDate->end(); 395 for (DateToEntriesMap::const_iterator it = _entriesByDate->begin(); it != end; ++it) 396 daysAsTimeIntervals.append(it->first); 397 398 sort(daysAsTimeIntervals.begin(), daysAsTimeIntervals.end()); 399 size_t count = daysAsTimeIntervals.size(); 400 _orderedLastVisitedDays = [[NSMutableArray alloc] initWithCapacity:count]; 401 for (int i = count - 1; i >= 0; i--) { 402 NSTimeInterval interval = daysAsTimeIntervals[i]; 403 NSCalendarDate *date = [[NSCalendarDate alloc] initWithTimeIntervalSinceReferenceDate:interval]; 404 [_orderedLastVisitedDays addObject:date]; 405 [date release]; 406 } 407 } 408 return _orderedLastVisitedDays; 409} 410 411- (NSArray *)orderedItemsLastVisitedOnDay:(NSCalendarDate *)date 412{ 413 WebHistoryDateKey dateKey; 414 if (![self findKey:&dateKey forDay:[date timeIntervalSinceReferenceDate]]) 415 return nil; 416 return _entriesByDate->get(dateKey).get(); 417} 418 419#pragma mark URL MATCHING 420 421- (WebHistoryItem *)itemForURLString:(NSString *)URLString 422{ 423 return [_entriesByURL objectForKey:URLString]; 424} 425 426- (BOOL)containsURL:(NSURL *)URL 427{ 428 return [self itemForURLString:[URL _web_originalDataAsString]] != nil; 429} 430 431- (WebHistoryItem *)itemForURL:(NSURL *)URL 432{ 433 return [self itemForURLString:[URL _web_originalDataAsString]]; 434} 435 436- (NSArray *)allItems 437{ 438 return [_entriesByURL allValues]; 439} 440 441#pragma mark ARCHIVING/UNARCHIVING 442 443- (void)setHistoryAgeInDaysLimit:(int)limit 444{ 445 ageInDaysLimitSet = YES; 446 ageInDaysLimit = limit; 447} 448 449- (int)historyAgeInDaysLimit 450{ 451 if (ageInDaysLimitSet) 452 return ageInDaysLimit; 453 return [[NSUserDefaults standardUserDefaults] integerForKey:@"WebKitHistoryAgeInDaysLimit"]; 454} 455 456- (void)setHistoryItemLimit:(int)limit 457{ 458 itemLimitSet = YES; 459 itemLimit = limit; 460} 461 462- (int)historyItemLimit 463{ 464 if (itemLimitSet) 465 return itemLimit; 466 return [[NSUserDefaults standardUserDefaults] integerForKey:@"WebKitHistoryItemLimit"]; 467} 468 469// Return a date that marks the age limit for history entries saved to or 470// loaded from disk. Any entry older than this item should be rejected. 471- (NSCalendarDate *)ageLimitDate 472{ 473 return [[NSCalendarDate calendarDate] dateByAddingYears:0 months:0 days:-[self historyAgeInDaysLimit] 474 hours:0 minutes:0 seconds:0]; 475} 476 477- (BOOL)loadHistoryGutsFromURL:(NSURL *)URL savedItemsCount:(int *)numberOfItemsLoaded collectDiscardedItemsInto:(NSMutableArray *)discardedItems error:(NSError **)error 478{ 479 *numberOfItemsLoaded = 0; 480 NSDictionary *dictionary = nil; 481 482 // Optimize loading from local file, which is faster than using the general URL loading mechanism 483 if ([URL isFileURL]) { 484 dictionary = [NSDictionary dictionaryWithContentsOfFile:[URL path]]; 485 if (!dictionary) { 486#if !LOG_DISABLED 487 if ([[NSFileManager defaultManager] fileExistsAtPath:[URL path]]) 488 LOG_ERROR("unable to read history from file %@; perhaps contents are corrupted", [URL path]); 489#endif 490 // else file doesn't exist, which is normal the first time 491 return NO; 492 } 493 } else { 494 NSData *data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:URL] returningResponse:nil error:error]; 495 if (data && [data length] > 0) { 496 dictionary = [NSPropertyListSerialization propertyListFromData:data 497 mutabilityOption:NSPropertyListImmutable 498 format:nil 499 errorDescription:nil]; 500 } 501 } 502 503 // We used to support NSArrays here, but that was before Safari 1.0 shipped. We will no longer support 504 // that ancient format, so anything that isn't an NSDictionary is bogus. 505 if (![dictionary isKindOfClass:[NSDictionary class]]) 506 return NO; 507 508 NSNumber *fileVersionObject = [dictionary objectForKey:FileVersionKey]; 509 int fileVersion; 510 // we don't trust data obtained from elsewhere, so double-check 511 if (!fileVersionObject || ![fileVersionObject isKindOfClass:[NSNumber class]]) { 512 LOG_ERROR("history file version can't be determined, therefore not loading"); 513 return NO; 514 } 515 fileVersion = [fileVersionObject intValue]; 516 if (fileVersion > currentFileVersion) { 517 LOG_ERROR("history file version is %d, newer than newest known version %d, therefore not loading", fileVersion, currentFileVersion); 518 return NO; 519 } 520 521 NSArray *array = [dictionary objectForKey:DatesArrayKey]; 522 523 int itemCountLimit = [self historyItemLimit]; 524 NSTimeInterval ageLimitDate = [[self ageLimitDate] timeIntervalSinceReferenceDate]; 525 NSEnumerator *enumerator = [array objectEnumerator]; 526 BOOL ageLimitPassed = NO; 527 BOOL itemLimitPassed = NO; 528 ASSERT(*numberOfItemsLoaded == 0); 529 530 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 531 NSDictionary *itemAsDictionary; 532 while ((itemAsDictionary = [enumerator nextObject]) != nil) { 533 WebHistoryItem *item = [[WebHistoryItem alloc] initFromDictionaryRepresentation:itemAsDictionary]; 534 535 // item without URL is useless; data on disk must have been bad; ignore 536 if ([item URLString]) { 537 // Test against date limit. Since the items are ordered newest to oldest, we can stop comparing 538 // once we've found the first item that's too old. 539 if (!ageLimitPassed && [item lastVisitedTimeInterval] <= ageLimitDate) 540 ageLimitPassed = YES; 541 542 if (ageLimitPassed || itemLimitPassed) 543 [discardedItems addObject:item]; 544 else { 545 if ([self addItem:item discardDuplicate:YES]) 546 ++(*numberOfItemsLoaded); 547 if (*numberOfItemsLoaded == itemCountLimit) 548 itemLimitPassed = YES; 549 550 // Draining the autorelease pool every 50 iterations was found by experimentation to be optimal 551 if (*numberOfItemsLoaded % 50 == 0) { 552 [pool drain]; 553 pool = [[NSAutoreleasePool alloc] init]; 554 } 555 } 556 } 557 [item release]; 558 } 559 [pool drain]; 560 561 return YES; 562} 563 564- (BOOL)loadFromURL:(NSURL *)URL collectDiscardedItemsInto:(NSMutableArray *)discardedItems error:(NSError **)error 565{ 566#if !LOG_DISABLED 567 double start = CFAbsoluteTimeGetCurrent(); 568#endif 569 570 int numberOfItems; 571 if (![self loadHistoryGutsFromURL:URL savedItemsCount:&numberOfItems collectDiscardedItemsInto:discardedItems error:error]) 572 return NO; 573 574#if !LOG_DISABLED 575 double duration = CFAbsoluteTimeGetCurrent() - start; 576 LOG(Timing, "loading %d history entries from %@ took %f seconds", numberOfItems, URL, duration); 577#endif 578 579 return YES; 580} 581 582- (NSData *)data 583{ 584 if (_entriesByDate->isEmpty()) { 585 static NSData *emptyHistoryData = (NSData *)CFDataCreate(0, 0, 0); 586 return emptyHistoryData; 587 } 588 589 // Ignores the date and item count limits; these are respected when loading instead of when saving, so 590 // that clients can learn of discarded items by listening to WebHistoryItemsDiscardedWhileLoadingNotification. 591 WebHistoryWriter writer(_entriesByDate); 592 writer.writePropertyList(); 593 return [[(NSData *)writer.releaseData().get() retain] autorelease]; 594} 595 596- (BOOL)saveToURL:(NSURL *)URL error:(NSError **)error 597{ 598#if !LOG_DISABLED 599 double start = CFAbsoluteTimeGetCurrent(); 600#endif 601 602 BOOL result = [[self data] writeToURL:URL options:0 error:error]; 603 604#if !LOG_DISABLED 605 double duration = CFAbsoluteTimeGetCurrent() - start; 606 LOG(Timing, "saving history to %@ took %f seconds", URL, duration); 607#endif 608 609 return result; 610} 611 612- (void)addVisitedLinksToPageGroup:(PageGroup&)group 613{ 614 NSEnumerator *enumerator = [_entriesByURL keyEnumerator]; 615 while (NSString *url = [enumerator nextObject]) { 616 size_t length = [url length]; 617 const UChar* characters = CFStringGetCharactersPtr(reinterpret_cast<CFStringRef>(url)); 618 if (characters) 619 group.addVisitedLink(characters, length); 620 else { 621 Vector<UChar, 512> buffer(length); 622 [url getCharacters:buffer.data()]; 623 group.addVisitedLink(buffer.data(), length); 624 } 625 } 626} 627 628@end 629 630@implementation WebHistory 631 632+ (WebHistory *)optionalSharedHistory 633{ 634 return _sharedHistory; 635} 636 637+ (void)setOptionalSharedHistory:(WebHistory *)history 638{ 639 if (_sharedHistory == history) 640 return; 641 // FIXME: Need to think about multiple instances of WebHistory per application 642 // and correct synchronization of history file between applications. 643 [_sharedHistory release]; 644 _sharedHistory = [history retain]; 645 PageGroup::setShouldTrackVisitedLinks(history); 646 PageGroup::removeAllVisitedLinks(); 647} 648 649- (id)init 650{ 651 self = [super init]; 652 if (!self) 653 return nil; 654 _historyPrivate = [[WebHistoryPrivate alloc] init]; 655 return self; 656} 657 658- (void)dealloc 659{ 660 [_historyPrivate release]; 661 [super dealloc]; 662} 663 664#pragma mark MODIFYING CONTENTS 665 666- (void)_sendNotification:(NSString *)name entries:(NSArray *)entries 667{ 668 NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:entries, WebHistoryItemsKey, nil]; 669 [[NSNotificationCenter defaultCenter] 670 postNotificationName:name object:self userInfo:userInfo]; 671} 672 673- (void)removeItems:(NSArray *)entries 674{ 675 if ([_historyPrivate removeItems:entries]) { 676 [self _sendNotification:WebHistoryItemsRemovedNotification 677 entries:entries]; 678 } 679} 680 681- (void)removeAllItems 682{ 683 NSArray *entries = [_historyPrivate allItems]; 684 if ([_historyPrivate removeAllItems]) 685 [self _sendNotification:WebHistoryAllItemsRemovedNotification entries:entries]; 686} 687 688- (void)addItems:(NSArray *)newEntries 689{ 690 [_historyPrivate addItems:newEntries]; 691 [self _sendNotification:WebHistoryItemsAddedNotification 692 entries:newEntries]; 693} 694 695#pragma mark DATE-BASED RETRIEVAL 696 697- (NSArray *)orderedLastVisitedDays 698{ 699 return [_historyPrivate orderedLastVisitedDays]; 700} 701 702- (NSArray *)orderedItemsLastVisitedOnDay:(NSCalendarDate *)date 703{ 704 return [_historyPrivate orderedItemsLastVisitedOnDay:date]; 705} 706 707#pragma mark URL MATCHING 708 709- (BOOL)containsURL:(NSURL *)URL 710{ 711 return [_historyPrivate containsURL:URL]; 712} 713 714- (WebHistoryItem *)itemForURL:(NSURL *)URL 715{ 716 return [_historyPrivate itemForURL:URL]; 717} 718 719#pragma mark SAVING TO DISK 720 721- (BOOL)loadFromURL:(NSURL *)URL error:(NSError **)error 722{ 723 NSMutableArray *discardedItems = [[NSMutableArray alloc] init]; 724 if (![_historyPrivate loadFromURL:URL collectDiscardedItemsInto:discardedItems error:error]) { 725 [discardedItems release]; 726 return NO; 727 } 728 729 [[NSNotificationCenter defaultCenter] 730 postNotificationName:WebHistoryLoadedNotification 731 object:self]; 732 733 if ([discardedItems count]) 734 [self _sendNotification:WebHistoryItemsDiscardedWhileLoadingNotification entries:discardedItems]; 735 736 [discardedItems release]; 737 return YES; 738} 739 740- (BOOL)saveToURL:(NSURL *)URL error:(NSError **)error 741{ 742 if (![_historyPrivate saveToURL:URL error:error]) 743 return NO; 744 [[NSNotificationCenter defaultCenter] 745 postNotificationName:WebHistorySavedNotification 746 object:self]; 747 return YES; 748} 749 750- (void)setHistoryItemLimit:(int)limit 751{ 752 [_historyPrivate setHistoryItemLimit:limit]; 753} 754 755- (int)historyItemLimit 756{ 757 return [_historyPrivate historyItemLimit]; 758} 759 760- (void)setHistoryAgeInDaysLimit:(int)limit 761{ 762 [_historyPrivate setHistoryAgeInDaysLimit:limit]; 763} 764 765- (int)historyAgeInDaysLimit 766{ 767 return [_historyPrivate historyAgeInDaysLimit]; 768} 769 770@end 771 772@implementation WebHistory (WebPrivate) 773 774- (WebHistoryItem *)_itemForURLString:(NSString *)URLString 775{ 776 return [_historyPrivate itemForURLString:URLString]; 777} 778 779- (NSArray *)allItems 780{ 781 return [_historyPrivate allItems]; 782} 783 784- (NSData *)_data 785{ 786 return [_historyPrivate data]; 787} 788 789@end 790 791@implementation WebHistory (WebInternal) 792 793- (void)_visitedURL:(NSURL *)url withTitle:(NSString *)title method:(NSString *)method wasFailure:(BOOL)wasFailure increaseVisitCount:(BOOL)increaseVisitCount 794{ 795 WebHistoryItem *entry = [_historyPrivate visitedURL:url withTitle:title increaseVisitCount:increaseVisitCount]; 796 797 HistoryItem* item = core(entry); 798 item->setLastVisitWasFailure(wasFailure); 799 800 if ([method length]) 801 item->setLastVisitWasHTTPNonGet([method caseInsensitiveCompare:@"GET"] && (![[url scheme] caseInsensitiveCompare:@"http"] || ![[url scheme] caseInsensitiveCompare:@"https"])); 802 803 item->setRedirectURLs(0); 804 805 NSArray *entries = [[NSArray alloc] initWithObjects:entry, nil]; 806 [self _sendNotification:WebHistoryItemsAddedNotification entries:entries]; 807 [entries release]; 808} 809 810- (void)_addVisitedLinksToPageGroup:(WebCore::PageGroup&)group 811{ 812 [_historyPrivate addVisitedLinksToPageGroup:group]; 813} 814 815@end 816 817WebHistoryWriter::WebHistoryWriter(DateToEntriesMap* entriesByDate) 818 : m_entriesByDate(entriesByDate) 819{ 820 m_dateKeys.reserveCapacity(m_entriesByDate->size()); 821 DateToEntriesMap::const_iterator end = m_entriesByDate->end(); 822 for (DateToEntriesMap::const_iterator it = m_entriesByDate->begin(); it != end; ++it) 823 m_dateKeys.append(it->first); 824 sort(m_dateKeys.begin(), m_dateKeys.end()); 825} 826 827void WebHistoryWriter::writeHistoryItems(BinaryPropertyListObjectStream& stream) 828{ 829 for (int dateIndex = m_dateKeys.size() - 1; dateIndex >= 0; dateIndex--) { 830 NSArray *entries = m_entriesByDate->get(m_dateKeys[dateIndex]).get(); 831 NSUInteger entryCount = [entries count]; 832 for (NSUInteger entryIndex = 0; entryIndex < entryCount; ++entryIndex) 833 writeHistoryItem(stream, core([entries objectAtIndex:entryIndex])); 834 } 835} 836