d311e4efe3c47ac3aa22e11100b4037d73710776
[WebKit-https.git] / WebKit / History.subproj / WebHistory.m
1 //
2 //  WebHistory.m
3 //  WebKit
4 //
5 //  Created by John Sullivan on Mon Feb 18 2002.
6 //  Copyright (c) 2002 Apple Computer, Inc. All rights reserved.
7 //
8
9 #import <WebKit/WebHistory.h>
10 #import <WebKit/WebHistoryPrivate.h>
11
12 #import <WebKit/WebHistoryItem.h>
13 #import <WebKit/WebHistoryItemPrivate.h>
14 #import <WebKit/WebKitLogging.h>
15 #import <WebKit/WebNSURLExtras.h>
16 #import <Foundation/NSError.h>
17 #import <WebKit/WebAssertions.h>
18 #import <WebCore/WebCoreHistory.h>
19
20 #import <Foundation/NSCalendarDate_NSURLExtras.h>
21
22
23 NSString *WebHistoryItemsAddedNotification = @"WebHistoryItemsAddedNotification";
24 NSString *WebHistoryItemsRemovedNotification = @"WebHistoryItemsRemovedNotification";
25 NSString *WebHistoryAllItemsRemovedNotification = @"WebHistoryAllItemsRemovedNotification";
26 NSString *WebHistoryLoadedNotification = @"WebHistoryLoadedNotification";
27 NSString *WebHistorySavedNotification = @"WebHistorySavedNotification";
28 NSString *WebHistoryItemsKey = @"WebHistoryItems";
29
30 static WebHistory *_sharedHistory = nil;
31
32
33
34 NSString *FileVersionKey = @"WebHistoryFileVersion";
35 NSString *DatesArrayKey = @"WebHistoryDates";
36
37 #define currentFileVersion      1
38
39 @implementation WebHistoryPrivate
40
41 #pragma mark OBJECT FRAMEWORK
42
43 + (void)initialize
44 {
45     [[NSUserDefaults standardUserDefaults] registerDefaults:
46         [NSDictionary dictionaryWithObjectsAndKeys:
47             @"1000", @"WebKitHistoryItemLimit",
48             @"7", @"WebKitHistoryAgeInDaysLimit",
49             nil]];    
50 }
51
52 - (id)init
53 {
54     if (![super init]) {
55         return nil;
56     }
57     
58     _entriesByURL = [[NSMutableDictionary alloc] init];
59     _datesWithEntries = [[NSMutableArray alloc] init];
60     _entriesByDate = [[NSMutableArray alloc] init];
61
62     return self;
63 }
64
65 - (void)dealloc
66 {
67     [_entriesByURL release];
68     [_datesWithEntries release];
69     [_entriesByDate release];
70     
71     [super dealloc];
72 }
73
74 #pragma mark MODIFYING CONTENTS
75
76 // Returns whether the day is already in the list of days,
77 // and fills in *index with the found or proposed index.
78 - (BOOL)findIndex: (int *)index forDay: (NSCalendarDate *)date
79 {
80     int count;
81
82     ASSERT_ARG(index, index != nil);
83
84     //FIXME: just does linear search through days; inefficient if many days
85     count = [_datesWithEntries count];
86     for (*index = 0; *index < count; ++*index) {
87         NSComparisonResult result = [date _web_compareDay: [_datesWithEntries objectAtIndex: *index]];
88         if (result == NSOrderedSame) {
89             return YES;
90         }
91         if (result == NSOrderedDescending) {
92             return NO;
93         }
94     }
95
96     return NO;
97 }
98
99 - (void)insertItem: (WebHistoryItem *)entry atDateIndex: (int)dateIndex
100 {
101     int index, count;
102     NSMutableArray *entriesForDate;
103     NSCalendarDate *entryDate;
104
105     ASSERT_ARG(entry, entry != nil);
106     ASSERT_ARG(dateIndex, dateIndex >= 0 && (uint)dateIndex < [_entriesByDate count]);
107
108     //FIXME: just does linear search through entries; inefficient if many entries for this date
109     entryDate = [entry _lastVisitedDate];
110     entriesForDate = [_entriesByDate objectAtIndex: dateIndex];
111     count = [entriesForDate count];
112     // optimized for inserting oldest to youngest
113     for (index = 0; index < count; ++index) {
114         if ([entryDate compare: [[entriesForDate objectAtIndex: index] _lastVisitedDate]] != NSOrderedAscending) {
115             break;
116         }
117     }
118
119     [entriesForDate insertObject: entry atIndex: index];
120 }
121
122 - (BOOL)removeItemForURLString: (NSString *)URLString
123 {
124     NSMutableArray *entriesForDate;
125     WebHistoryItem *entry;
126     int dateIndex;
127     BOOL foundDate;
128
129     entry = [_entriesByURL objectForKey: URLString];
130     if (entry == nil) {
131         return NO;
132     }
133
134     [_entriesByURL removeObjectForKey: URLString];
135
136     foundDate = [self findIndex: &dateIndex forDay: [entry _lastVisitedDate]];
137
138     ASSERT(foundDate);
139     
140     entriesForDate = [_entriesByDate objectAtIndex: dateIndex];
141     [entriesForDate removeObjectIdenticalTo: entry];
142
143     // remove this date entirely if there are no other entries on it
144     if ([entriesForDate count] == 0) {
145         [_entriesByDate removeObjectAtIndex: dateIndex];
146         [_datesWithEntries removeObjectAtIndex: dateIndex];
147     }
148
149     return YES;
150 }
151
152
153 - (void)addItem: (WebHistoryItem *)entry
154 {
155     int dateIndex;
156     NSString *URLString;
157
158     ASSERT_ARG(entry, entry);
159     ASSERT_ARG(entry, [entry lastVisitedTimeInterval] != 0);
160
161     URLString = [entry URLString];
162
163     // If we already have an item with this URL, we need to merge info that drives the
164     // URL autocomplete heuristics from that item into the new one.
165     WebHistoryItem *oldEntry = [_entriesByURL objectForKey: URLString];
166     if (oldEntry) {
167         [entry _mergeAutoCompleteHints:oldEntry];
168     }
169
170     [self removeItemForURLString: URLString];
171
172     if ([self findIndex: &dateIndex forDay: [entry _lastVisitedDate]]) {
173         // other entries already exist for this date
174         [self insertItem: entry atDateIndex: dateIndex];
175     } else {
176         // no other entries exist for this date
177         [_datesWithEntries insertObject: [entry _lastVisitedDate] atIndex: dateIndex];
178         [_entriesByDate insertObject: [NSMutableArray arrayWithObject:entry] atIndex: dateIndex];
179     }
180
181     [_entriesByURL setObject: entry forKey: URLString];
182 }
183
184 - (BOOL)removeItem: (WebHistoryItem *)entry
185 {
186     WebHistoryItem *matchingEntry;
187     NSString *URLString;
188
189     URLString = [entry URLString];
190
191     // If this exact object isn't stored, then make no change.
192     // FIXME: Is this the right behavior if this entry isn't present, but another entry for the same URL is?
193     // Maybe need to change the API to make something like removeEntryForURLString public instead.
194     matchingEntry = [_entriesByURL objectForKey: URLString];
195     if (matchingEntry != entry) {
196         return NO;
197     }
198
199     [self removeItemForURLString: URLString];
200
201     return YES;
202 }
203
204 - (BOOL)removeItems: (NSArray *)entries
205 {
206     int index, count;
207
208     count = [entries count];
209     if (count == 0) {
210         return NO;
211     }
212
213     for (index = 0; index < count; ++index) {
214         [self removeItem:[entries objectAtIndex:index]];
215     }
216     
217     return YES;
218 }
219
220 - (BOOL)removeAllItems
221 {
222     if ([_entriesByURL count] == 0) {
223         return NO;
224     }
225
226     [_entriesByDate removeAllObjects];
227     [_datesWithEntries removeAllObjects];
228     [_entriesByURL removeAllObjects];
229
230     return YES;
231 }
232
233 - (void)addItems:(NSArray *)newEntries
234 {
235     NSEnumerator *enumerator;
236     WebHistoryItem *entry;
237
238     // There is no guarantee that the incoming entries are in any particular
239     // order, but if this is called with a set of entries that were created by
240     // iterating through the results of orderedLastVisitedDays and orderedItemsLastVisitedOnDayy
241     // then they will be ordered chronologically from newest to oldest. We can make adding them
242     // faster (fewer compares) by inserting them from oldest to newest.
243     enumerator = [newEntries reverseObjectEnumerator];
244     while ((entry = [enumerator nextObject]) != nil) {
245         [self addItem:entry];
246     }
247 }
248
249 #pragma mark DATE-BASED RETRIEVAL
250
251 - (NSArray *)orderedLastVisitedDays
252 {
253     return _datesWithEntries;
254 }
255
256 - (NSArray *)orderedItemsLastVisitedOnDay: (NSCalendarDate *)date
257 {
258     int index;
259
260     if ([self findIndex: &index forDay: date]) {
261         return [_entriesByDate objectAtIndex: index];
262     }
263
264     return nil;
265 }
266
267 #pragma mark URL MATCHING
268
269 - (WebHistoryItem *)itemForURLString:(NSString *)URLString
270 {
271     return [_entriesByURL objectForKey: URLString];
272 }
273
274 - (BOOL)containsItemForURLString: (NSString *)URLString
275 {
276     return [self itemForURLString:URLString] != nil;
277 }
278
279 - (BOOL)containsURL: (NSURL *)URL
280 {
281     return [self itemForURLString:[URL _web_originalDataAsString]] != nil;
282 }
283
284 - (WebHistoryItem *)itemForURL:(NSURL *)URL
285 {
286     return [self itemForURLString:[URL _web_originalDataAsString]];
287 }       
288
289 #pragma mark ARCHIVING/UNARCHIVING
290
291 - (void)setHistoryAgeInDaysLimit:(int)limit
292 {
293     ageInDaysLimitSet = YES;
294     ageInDaysLimit = limit;
295 }
296
297 - (int)historyAgeInDaysLimit
298 {
299     if (ageInDaysLimitSet)
300         return ageInDaysLimit;
301     return [[NSUserDefaults standardUserDefaults] integerForKey: @"WebKitHistoryAgeInDaysLimit"];
302 }
303
304 - (void)setHistoryItemLimit:(int)limit
305 {
306     itemLimitSet = YES;
307     itemLimit = limit;
308 }
309
310 - (int)historyItemLimit
311 {
312     if (itemLimitSet)
313         return itemLimit;
314     return [[NSUserDefaults standardUserDefaults] integerForKey: @"WebKitHistoryItemLimit"];
315 }
316
317 // Return a date that marks the age limit for history entries saved to or
318 // loaded from disk. Any entry on this day or older should be rejected,
319 // as tested with -[NSCalendarDate compareDay:]
320 - (NSCalendarDate *)_ageLimitDate
321 {
322     return [[NSCalendarDate calendarDate] dateByAddingYears:0 months:0 days:-[self historyAgeInDaysLimit]
323                                                       hours:0 minutes:0 seconds:0];
324 }
325
326 // Return a flat array of WebHistoryItems. Leaves out entries older than the age limit.
327 // Stops filling array when item count limit is reached, even if there are currently
328 // more entries than that.
329 - (NSArray *)arrayRepresentation
330 {
331     int dateCount, dateIndex;
332     int limit;
333     int totalSoFar;
334     NSMutableArray *arrayRep;
335     NSCalendarDate *ageLimitDate;
336
337     arrayRep = [NSMutableArray array];
338
339     limit = [self historyItemLimit];
340     ageLimitDate = [self _ageLimitDate];
341     totalSoFar = 0;
342     
343     dateCount = [_entriesByDate count];
344     for (dateIndex = 0; dateIndex < dateCount; ++dateIndex) {
345         int entryCount, entryIndex;
346         NSArray *entries;
347
348         // skip remaining days if they are older than the age limit
349         if ([[_datesWithEntries objectAtIndex:dateIndex] _web_compareDay:ageLimitDate] != NSOrderedDescending) {
350             break;
351         }
352
353         entries = [_entriesByDate objectAtIndex:dateIndex];
354         entryCount = [entries count];
355         for (entryIndex = 0; entryIndex < entryCount; ++entryIndex) {
356             if (totalSoFar++ >= limit) {
357                 break;
358             }
359             [arrayRep addObject: [[entries objectAtIndex:entryIndex] dictionaryRepresentation]];
360         }
361     }
362
363     return arrayRep;
364 }
365
366 - (BOOL)_loadHistoryGuts: (int *)numberOfItemsLoaded URL:(NSURL *)URL error:(NSError **)error
367 {
368     *numberOfItemsLoaded = 0;
369
370     NSData *data = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:URL] returningResponse:nil error:error];
371     id propertyList = nil;
372     if (data && [data length] > 0) {
373         propertyList = [NSPropertyListSerialization propertyListFromData:data
374                                                         mutabilityOption:NSPropertyListImmutable
375                                                                   format:nil
376                                                         errorDescription:nil];
377     }
378
379     // propertyList might be an old-style NSArray or a more modern NSDictionary.
380     // If it's an NSArray, convert it to new format before further processing.
381     NSDictionary *fileAsDictionary = nil;
382     if ([propertyList isKindOfClass:[NSDictionary class]]) {
383         fileAsDictionary = propertyList;
384     } else if ([propertyList isKindOfClass:[NSArray class]]) {
385         // Convert old-style array into new-style dictionary
386         fileAsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
387             propertyList, DatesArrayKey,
388             [NSNumber numberWithInt:1], FileVersionKey,
389             nil];
390     } else {
391         if ([URL isFileURL] && [[NSFileManager defaultManager] fileExistsAtPath: [URL path]]) {
392             ERROR("unable to read history from file %@; perhaps contents are corrupted", [URL path]);
393         }
394         return NO;
395     }
396
397     NSNumber *fileVersionObject = [fileAsDictionary objectForKey:FileVersionKey];
398     int fileVersion;
399     // we don't trust data read from disk, so double-check
400     if (fileVersionObject != nil && [fileVersionObject isKindOfClass:[NSNumber class]]) {
401         fileVersion = [fileVersionObject intValue];
402     } else {
403         ERROR("history file version can't be determined, therefore not loading");
404         return NO;
405     }
406     if (fileVersion > currentFileVersion) {
407         ERROR("history file version is %d, newer than newest known version %d, therefore not loading", fileVersion, currentFileVersion);
408         return NO;
409     }    
410
411     NSArray *array = [fileAsDictionary objectForKey:DatesArrayKey];
412         
413     int limit = [[NSUserDefaults standardUserDefaults] integerForKey: @"WebKitHistoryItemLimit"];
414     NSCalendarDate *ageLimitDate = [self _ageLimitDate];
415     int index = 0;
416     // reverse dates so you're loading the oldest first, to minimize the number of comparisons
417     NSEnumerator *enumerator = [array reverseObjectEnumerator];
418     BOOL ageLimitPassed = NO;
419
420     NSDictionary *itemAsDictionary;
421     while ((itemAsDictionary = [enumerator nextObject]) != nil) {
422         WebHistoryItem *entry;
423
424         entry = [[[WebHistoryItem alloc] initFromDictionaryRepresentation:itemAsDictionary] autorelease];
425
426         if ([entry URLString] == nil) {
427             // entry without URL is useless; data on disk must have been bad; ignore
428             continue;
429         }
430
431         // test against date limit
432         if (!ageLimitPassed) {
433             if ([[entry _lastVisitedDate] _web_compareDay:ageLimitDate] != NSOrderedDescending) {
434                 continue;
435             } else {
436                 ageLimitPassed = YES;
437             }
438         }
439         
440         [self addItem: entry];
441         if (++index >= limit) {
442             break;
443         }
444     }
445
446     *numberOfItemsLoaded = MIN(index, limit);
447     return YES;    
448 }
449
450 - (BOOL)loadFromURL:(NSURL *)URL error:(NSError **)error
451 {
452     int numberOfItems;
453     double start, duration;
454     BOOL result;
455
456     start = CFAbsoluteTimeGetCurrent();
457     result = [self _loadHistoryGuts: &numberOfItems URL:URL error:error];
458
459     if (result) {
460         duration = CFAbsoluteTimeGetCurrent() - start;
461         LOG(Timing, "loading %d history entries from %@ took %f seconds",
462             numberOfItems, URL, duration);
463     }
464
465     return result;
466 }
467
468 - (BOOL)_saveHistoryGuts: (int *)numberOfItemsSaved URL:(NSURL *)URL error:(NSError **)error
469 {
470     *numberOfItemsSaved = 0;
471
472     // FIXME:  Correctly report error when new API is ready.
473     if (error)
474         *error = nil;
475
476     NSArray *array = [self arrayRepresentation];
477     NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
478         array, DatesArrayKey,
479         [NSNumber numberWithInt:currentFileVersion], FileVersionKey,
480         nil];
481     if (![dictionary writeToURL:URL atomically:YES]) {
482         ERROR("attempt to save %@ to %@ failed", dictionary, URL);
483         return NO;
484     }
485     
486     *numberOfItemsSaved = [array count];
487     return YES;
488 }
489
490 - (BOOL)saveToURL:(NSURL *)URL error:(NSError **)error
491 {
492     int numberOfItems;
493     double start, duration;
494     BOOL result;
495
496     start = CFAbsoluteTimeGetCurrent();
497     result = [self _saveHistoryGuts: &numberOfItems URL:URL error:error];
498
499     if (result) {
500         duration = CFAbsoluteTimeGetCurrent() - start;
501         LOG(Timing, "saving %d history entries to %@ took %f seconds",
502             numberOfItems, URL, duration);
503     }
504
505     return result;
506 }
507
508 @end
509
510 @interface _WebCoreHistoryProvider : NSObject  <WebCoreHistoryProvider> 
511 {
512     WebHistory *history;
513 }
514 - initWithHistory: (WebHistory *)h;
515 @end
516
517 @implementation _WebCoreHistoryProvider
518 - initWithHistory: (WebHistory *)h
519 {
520     history = [h retain];
521     return self;
522 }
523
524 static inline bool matchLetter(char c, char lowercaseLetter)
525 {
526     return (c | 0x20) == lowercaseLetter;
527 }
528
529 static inline bool matchUnicodeLetter(UniChar c, UniChar lowercaseLetter)
530 {
531     return (c | 0x20) == lowercaseLetter;
532 }
533
534 #define BUFFER_SIZE 2048
535
536 - (BOOL)containsItemForURLLatin1:(const char *)latin1 length:(unsigned)length
537 {
538     const char *latin1Str = latin1;
539     char staticStrBuffer[BUFFER_SIZE];
540     char *strBuffer = NULL;
541     BOOL needToAddSlash = FALSE;
542
543     if (length >= 6 &&
544         matchLetter(latin1[0], 'h') &&
545         matchLetter(latin1[1], 't') &&
546         matchLetter(latin1[2], 't') &&
547         matchLetter(latin1[3], 'p') &&
548         (latin1[4] == ':' 
549          || (matchLetter(latin1[4], 's') && latin1[5] == ':'))) {
550         int pos = latin1[4] == ':' ? 5 : 6;
551         // skip possible initial two slashes
552         if (latin1[pos] == '/' && latin1[pos + 1] == '/') {
553             pos += 2;
554         }
555
556         char *nextSlash = strchr(latin1 + pos, '/');
557         if (nextSlash == NULL) {
558             needToAddSlash = TRUE;
559         }
560     }
561
562     if (needToAddSlash) {
563         if (length + 1 <= 2048) {
564             strBuffer = staticStrBuffer;
565         } else {
566             strBuffer = malloc(length + 2);
567         }
568         memcpy(strBuffer, latin1, length + 1);
569         strBuffer[length] = '/';
570         strBuffer[length+1] = '\0';
571         length++;
572
573         latin1Str = strBuffer;
574     }
575
576     CFStringRef str = CFStringCreateWithCStringNoCopy(NULL, latin1Str, kCFStringEncodingWindowsLatin1, kCFAllocatorNull);
577     BOOL result = [history containsItemForURLString:(id)str];
578     CFRelease(str);
579
580     if (strBuffer != staticStrBuffer) {
581         free(strBuffer);
582     }
583
584     return result;
585 }
586
587 - (BOOL)containsItemForURLUnicode:(const UniChar *)unicode length:(unsigned)length
588 {
589     const UniChar *unicodeStr = unicode;
590     UniChar staticStrBuffer[1024];
591     UniChar *strBuffer = NULL;
592     BOOL needToAddSlash = FALSE;
593
594     if (length >= 6 &&
595         matchUnicodeLetter(unicode[0], 'h') &&
596         matchUnicodeLetter(unicode[1], 't') &&
597         matchUnicodeLetter(unicode[2], 't') &&
598         matchUnicodeLetter(unicode[3], 'p') &&
599         (unicode[4] == ':' 
600          || (matchLetter(unicode[4], 's') && unicode[5] == ':'))) {
601
602         unsigned pos = unicode[4] == ':' ? 5 : 6;
603
604         // skip possible initial two slashes
605         if (pos + 1 < length && unicode[pos] == '/' && unicode[pos + 1] == '/') {
606             pos += 2;
607         }
608
609         while (pos < length && unicode[pos] != '/') {
610             pos++;
611         }
612
613         if (pos == length) {
614             needToAddSlash = TRUE;
615         }
616     }
617
618     if (needToAddSlash) {
619         if (length + 1 <= 1024) {
620             strBuffer = staticStrBuffer;
621         } else {
622             strBuffer = malloc(sizeof(UniChar) * (length + 1));
623         }
624         memcpy(strBuffer, unicode, 2 * length);
625         strBuffer[length] = '/';
626         length++;
627
628         unicodeStr = strBuffer;
629     }
630
631     CFStringRef str = CFStringCreateWithCharactersNoCopy(NULL, unicodeStr, length, kCFAllocatorNull);
632     BOOL result = [history containsItemForURLString:(id)str];
633     CFRelease(str);
634
635     if (strBuffer != staticStrBuffer) {
636         free(strBuffer);
637     }
638
639     return result;
640 }
641
642 - (void)dealloc
643 {
644     [history release];
645     [super dealloc];
646 }
647
648 @end
649
650 @implementation WebHistory
651
652 + (WebHistory *)optionalSharedHistory
653 {
654     return _sharedHistory;
655 }
656
657
658 + (void)setOptionalSharedHistory: (WebHistory *)history
659 {
660     // FIXME.  Need to think about multiple instances of WebHistory per application
661     // and correct synchronization of history file between applications.
662     [WebCoreHistory setHistoryProvider: [[[_WebCoreHistoryProvider alloc] initWithHistory: history] autorelease]];
663     if (_sharedHistory != history){
664         [_sharedHistory release];
665         _sharedHistory = [history retain];
666     }
667 }
668
669 - (id)init
670 {
671     if ((self = [super init]) != nil) {
672         _historyPrivate = [[WebHistoryPrivate alloc] init];
673     }
674
675     return self;
676 }
677
678 - (void)dealloc
679 {
680     [_historyPrivate release];
681     [super dealloc];
682 }
683
684 #pragma mark MODIFYING CONTENTS
685
686 - (void)_sendNotification:(NSString *)name entries:(NSArray *)entries
687 {
688     NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:entries, WebHistoryItemsKey, nil];
689     [[NSNotificationCenter defaultCenter]
690         postNotificationName: name object: self userInfo: userInfo];
691 }
692
693 - (WebHistoryItem *)addItemForURL: (NSURL *)URL
694 {
695     WebHistoryItem *entry = [[WebHistoryItem alloc] initWithURL:URL title:nil];
696     [entry _setLastVisitedTimeInterval: [NSDate timeIntervalSinceReferenceDate]];
697     [self addItem: entry];
698     [entry release];
699     return entry;
700 }
701
702
703 - (void)addItem: (WebHistoryItem *)entry
704 {
705     LOG (History, "adding %@", entry);
706     [_historyPrivate addItem: entry];
707     [self _sendNotification: WebHistoryItemsAddedNotification
708                     entries: [NSArray arrayWithObject:entry]];
709 }
710
711 - (void)removeItem: (WebHistoryItem *)entry
712 {
713     if ([_historyPrivate removeItem: entry]) {
714         [self _sendNotification: WebHistoryItemsRemovedNotification
715                         entries: [NSArray arrayWithObject:entry]];
716     }
717 }
718
719 - (void)removeItems: (NSArray *)entries
720 {
721     if ([_historyPrivate removeItems:entries]) {
722         [self _sendNotification: WebHistoryItemsRemovedNotification
723                         entries: entries];
724     }
725 }
726
727 - (void)removeAllItems
728 {
729     if ([_historyPrivate removeAllItems]) {
730         [[NSNotificationCenter defaultCenter]
731             postNotificationName: WebHistoryAllItemsRemovedNotification
732                           object: self];
733     }
734 }
735
736 - (void)addItems:(NSArray *)newEntries
737 {
738     [_historyPrivate addItems:newEntries];
739     [self _sendNotification: WebHistoryItemsAddedNotification
740                     entries: newEntries];
741 }
742
743 #pragma mark DATE-BASED RETRIEVAL
744
745 - (NSArray *)orderedLastVisitedDays
746 {
747     return [_historyPrivate orderedLastVisitedDays];
748 }
749
750 - (NSArray *)orderedItemsLastVisitedOnDay: (NSCalendarDate *)date
751 {
752     return [_historyPrivate orderedItemsLastVisitedOnDay: date];
753 }
754
755 #pragma mark URL MATCHING
756
757 - (BOOL)containsItemForURLString: (NSString *)URLString
758 {
759     return [_historyPrivate containsItemForURLString: URLString];
760 }
761
762 - (BOOL)containsURL: (NSURL *)URL
763 {
764     return [_historyPrivate containsURL: URL];
765 }
766
767 - (WebHistoryItem *)itemForURL:(NSURL *)URL
768 {
769     return [_historyPrivate itemForURL:URL];
770 }
771
772 #pragma mark SAVING TO DISK
773
774 - (BOOL)loadFromURL:(NSURL *)URL error:(NSError **)error
775 {
776     if ([_historyPrivate loadFromURL:URL error:error]) {
777         [[NSNotificationCenter defaultCenter]
778             postNotificationName: WebHistoryLoadedNotification
779                           object: self];
780         return YES;
781     }
782     return NO;
783 }
784
785 - (BOOL)saveToURL:(NSURL *)URL error:(NSError **)error
786 {
787     // FIXME:  Use new foundation API to get error when ready.
788     if([_historyPrivate saveToURL:URL error:error]){
789         [[NSNotificationCenter defaultCenter]
790             postNotificationName: WebHistorySavedNotification
791                           object: self];
792         return YES;
793     }
794     return NO;    
795 }
796
797 - (WebHistoryItem *)_itemForURLString:(NSString *)URLString
798 {
799     return [_historyPrivate itemForURLString: URLString];
800 }
801
802 - (NSCalendarDate*)ageLimitDate
803 {
804     return [_historyPrivate _ageLimitDate];
805 }
806
807 - (void)setHistoryItemLimit:(int)limit
808 {
809     [_historyPrivate setHistoryItemLimit:limit];
810 }
811
812 - (int)historyItemLimit
813 {
814     return [_historyPrivate historyItemLimit];
815 }
816
817 - (void)setHistoryAgeInDaysLimit:(int)limit
818 {
819     [_historyPrivate setHistoryAgeInDaysLimit:limit];
820 }
821
822 - (int)historyAgeInDaysLimit
823 {
824     return [_historyPrivate historyAgeInDaysLimit];
825 }
826
827 @end