1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
23 * Conrad Carlen <ccarlen@netscape.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsEmbedGlobalHistory.h"
40 #include "nsIObserver.h"
41 #include "nsIObserverService.h"
42 #include "nsWeakReference.h"
43 #include "nsAppDirectoryServiceDefs.h"
44 #include "nsHashtable.h"
47 #include "nsFixedSizeAllocator.h"
48 #include "nsVoidArray.h"
49 #include "nsIPrefService.h"
52 static const PRInt32 kNewEntriesBetweenFlush
= 10;
54 static const PRUint32 kDefaultExpirationIntervalDays
= 7;
56 static const PRInt64 kMSecsPerDay
= LL_INIT(0, 60 * 60 * 24 * 1000);
57 static const PRInt64 kOneThousand
= LL_INIT(0, 1000);
59 #define PREF_BROWSER_HISTORY_EXPIRE_DAYS "browser.history_expire_days"
61 // Static Routine Prototypes
62 static nsresult
readEntry(FILE *inStream
, nsCString
& url
, HistoryEntry
**entry
);
63 static nsresult
writeEntry(FILE *outStm
, nsCStringKey
*url
, HistoryEntry
*entry
);
65 static PRIntn PR_CALLBACK
enumWriteEntry(nsHashKey
*aKey
, void *aData
, void* closure
);
66 static PRIntn PR_CALLBACK
enumWriteEntryIfUnwritten(nsHashKey
*aKey
, void *aData
, void* closure
);
67 static PRIntn PR_CALLBACK
enumDeleteEntry(nsHashKey
*aKey
, void *aData
, void* closure
);
69 //*****************************************************************************
71 //*****************************************************************************
81 mLastVisitTime
= PR_Now();
82 LL_DIV(mLastVisitTime
, mLastVisitTime
, kOneThousand
);
85 PRInt64
GetLastVisitTime()
86 { return mLastVisitTime
; }
87 void SetLastVisitTime(const PRInt64
& aTime
)
88 { mLastVisitTime
= aTime
; }
92 void SetIsWritten(PRBool written
= PR_TRUE
)
93 { mWritten
= PR_TRUE
; }
95 // Memory management stuff
96 static void* operator new(size_t size
) CPP_THROW_NEW
;
97 static void operator delete(void *p
, size_t size
);
99 // Must be called when done with all HistoryEntry objects
100 static void ReleasePool();
103 PRInt64 mLastVisitTime
; // Millisecs
104 PRPackedBool mWritten
; // TRUE if ever persisted
106 static nsresult
InitPool();
107 static nsFixedSizeAllocator
*sPool
;
110 nsFixedSizeAllocator
*HistoryEntry::sPool
;
112 //*****************************************************************************
114 void* HistoryEntry::operator new(size_t size
) CPP_THROW_NEW
116 if (size
!= sizeof(HistoryEntry
))
117 return ::operator new(size
);
118 if (!sPool
&& NS_FAILED(InitPool()))
121 return sPool
->Alloc(size
);
124 void HistoryEntry::operator delete(void *p
, size_t size
)
128 if (size
!= sizeof(HistoryEntry
))
129 ::operator delete(p
);
131 NS_ERROR("HistoryEntry outlived its memory pool");
134 sPool
->Free(p
, size
);
137 nsresult
HistoryEntry::InitPool()
140 sPool
= new nsFixedSizeAllocator
;
142 return NS_ERROR_OUT_OF_MEMORY
;
144 static const size_t kBucketSizes
[] =
145 { sizeof(HistoryEntry
) };
146 static const PRInt32 kInitialPoolSize
=
147 NS_SIZE_IN_HEAP(sizeof(HistoryEntry
)) * 256;
149 nsresult rv
= sPool
->Init("EmbedLite HistoryEntry Pool", kBucketSizes
, 1, kInitialPoolSize
);
156 void HistoryEntry::ReleasePool()
162 //*****************************************************************************
163 // nsEmbedGlobalHistory - Creation/Destruction
164 //*****************************************************************************
166 NS_IMPL_ISUPPORTS3(nsEmbedGlobalHistory
, nsIGlobalHistory
, nsIObserver
, nsISupportsWeakReference
)
168 nsEmbedGlobalHistory::nsEmbedGlobalHistory() :
169 mDataIsLoaded(PR_FALSE
), mEntriesAddedSinceFlush(0),
172 LL_I2L(mExpirationInterval
, kDefaultExpirationIntervalDays
);
173 LL_MUL(mExpirationInterval
, mExpirationInterval
, kMSecsPerDay
);
176 nsEmbedGlobalHistory::~nsEmbedGlobalHistory()
180 HistoryEntry::ReleasePool();
183 NS_IMETHODIMP
nsEmbedGlobalHistory::Init()
185 mURLTable
= new nsHashtable
;
186 NS_ENSURE_TRUE(mURLTable
, NS_ERROR_OUT_OF_MEMORY
);
188 // Get Pref and convert to millisecs
189 nsCOMPtr
<nsIPrefBranch
> prefs(do_GetService("@mozilla.org/preferences-service;1"));
192 prefs
->GetIntPref(PREF_BROWSER_HISTORY_EXPIRE_DAYS
, &expireDays
);
193 LL_I2L(mExpirationInterval
, expireDays
);
194 LL_MUL(mExpirationInterval
, mExpirationInterval
, kMSecsPerDay
);
197 // register to observe profile changes
198 nsCOMPtr
<nsIObserverService
> observerService
=
199 do_GetService("@mozilla.org/observer-service;1");
200 NS_ASSERTION(observerService
, "failed to get observer service");
202 observerService
->AddObserver(this, "profile-before-change", PR_TRUE
);
207 //*****************************************************************************
208 // nsEmbedGlobalHistory::nsIGlobalHistory
209 //*****************************************************************************
211 NS_IMETHODIMP
nsEmbedGlobalHistory::AddPage(const char *aURL
)
215 nsresult rv
= LoadData();
216 NS_ENSURE_SUCCESS(rv
, rv
);
218 nsCStringKey
asKey(aURL
);
219 HistoryEntry
*entry
= static_cast<HistoryEntry
*>(mURLTable
->Get(&asKey
));
222 if (++mEntriesAddedSinceFlush
>= kNewEntriesBetweenFlush
)
223 FlushData(kFlushModeAppend
);
225 HistoryEntry
*newEntry
= new HistoryEntry
;
227 return NS_ERROR_FAILURE
;
228 (void)mURLTable
->Put(&asKey
, newEntry
);
236 NS_IMETHODIMP
nsEmbedGlobalHistory::IsVisited(const char *aURL
, PRBool
*_retval
)
239 NS_ENSURE_ARG_POINTER(_retval
);
241 nsresult rv
= LoadData();
242 NS_ENSURE_SUCCESS(rv
, rv
);
244 nsCStringKey
asKey(aURL
);
246 *_retval
= (mURLTable
->Exists(&asKey
));
250 //*****************************************************************************
251 // nsEmbedGlobalHistory::nsIObserver
252 //*****************************************************************************
254 NS_IMETHODIMP
nsEmbedGlobalHistory::Observe(nsISupports
*aSubject
, const char *aTopic
, const PRUnichar
*aData
)
258 if (strcmp(aTopic
, "profile-before-change") == 0) {
265 //*****************************************************************************
266 // nsEmbedGlobalHistory
267 //*****************************************************************************
269 nsresult
nsEmbedGlobalHistory::LoadData()
271 if (!mDataIsLoaded
) {
276 mDataIsLoaded
= PR_TRUE
;
278 rv
= GetHistoryFile();
281 rv
= mHistoryFile
->Exists(&exists
);
288 rv
= mHistoryFile
->OpenANSIFileDesc("r", &stdFile
);
292 nsCAutoString outString
;
293 HistoryEntry
*newEntry
;
294 while (NS_SUCCEEDED(readEntry(stdFile
, outString
, &newEntry
))) {
295 if (EntryHasExpired(newEntry
)) {
299 nsCStringKey
asKey(outString
);
300 mURLTable
->Put(&asKey
, newEntry
);
309 nsresult
nsEmbedGlobalHistory::FlushData(PRIntn mode
)
313 const char* openMode
= (mode
== kFlushModeAppend
? "a" : "w");
315 nsresult rv
= mHistoryFile
->OpenANSIFileDesc(openMode
, &stdFile
);
316 if (NS_FAILED(rv
)) return rv
;
318 // Before flushing either way, remove dead entries
319 mURLTable
->Enumerate(enumRemoveEntryIfExpired
, this);
321 if (mode
== kFlushModeAppend
)
322 mURLTable
->Enumerate(enumWriteEntryIfUnwritten
, stdFile
);
324 mURLTable
->Enumerate(enumWriteEntry
, stdFile
);
326 mEntriesAddedSinceFlush
= 0;
332 nsresult
nsEmbedGlobalHistory::ResetData()
334 mURLTable
->Reset(enumDeleteEntry
);
336 mDataIsLoaded
= PR_FALSE
;
337 mEntriesAddedSinceFlush
= 0;
341 nsresult
nsEmbedGlobalHistory::GetHistoryFile()
345 // Get the history file in our profile dir.
346 // Notice we are not just getting NS_APP_HISTORY_50_FILE
347 // because it is used by the "real" global history component.
349 nsCOMPtr
<nsIFile
> aFile
;
350 rv
= NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
, getter_AddRefs(aFile
));
351 NS_ENSURE_SUCCESS(rv
, rv
);
352 rv
= aFile
->Append(NS_LITERAL_STRING("history.txt"));
353 NS_ENSURE_SUCCESS(rv
, rv
);
354 mHistoryFile
= do_QueryInterface(aFile
);
358 PRBool
nsEmbedGlobalHistory::EntryHasExpired(HistoryEntry
*entry
)
360 // convert "now" from microsecs to millisecs
361 PRInt64 nowInMilliSecs
= PR_Now();
362 LL_DIV(nowInMilliSecs
, nowInMilliSecs
, kOneThousand
);
364 // determine when the entry would have expired
365 PRInt64 expirationIntervalAgo
;
366 LL_SUB(expirationIntervalAgo
, nowInMilliSecs
, mExpirationInterval
);
368 PRInt64 lastVisitTime
= entry
->GetLastVisitTime();
369 return (LL_CMP(lastVisitTime
, <, expirationIntervalAgo
));
372 PRIntn PR_CALLBACK
nsEmbedGlobalHistory::enumRemoveEntryIfExpired(nsHashKey
*aKey
, void *aData
, void* closure
)
374 HistoryEntry
*entry
= static_cast<HistoryEntry
*>(aData
);
377 nsEmbedGlobalHistory
*history
= static_cast<nsEmbedGlobalHistory
*>(closure
);
379 return kHashEnumerateStop
;
381 if (history
->EntryHasExpired(entry
)) {
383 return kHashEnumerateRemove
;
385 return kHashEnumerateNext
;
389 //*****************************************************************************
391 //*****************************************************************************
393 static nsresult
parsePRInt64(FILE *inStm
, PRInt64
& outValue
)
395 int c
, charsRead
= 0;
400 if (c
== EOF
|| !isdigit(c
))
404 PRInt32 digit
= c
- '0';
405 value
*= nsInt64(10);
406 value
+= nsInt64(digit
);
409 return NS_ERROR_FAILURE
;
414 nsresult
readEntry(FILE *inStream
, nsCString
& outURL
, HistoryEntry
**outEntry
)
418 // Get the last visted date
420 rv
= parsePRInt64(inStream
, value
);
427 char *next
, *end
= buf
+ sizeof(buf
);
439 else if (c
== '\r') {
448 outURL
.Append(buf
, next
- buf
);
454 outURL
.Append(buf
, next
- buf
);
456 if (!outURL
.Length() && c
== EOF
)
457 return NS_ERROR_FAILURE
;
459 *outEntry
= new HistoryEntry
;
461 return NS_ERROR_OUT_OF_MEMORY
;
462 (*outEntry
)->SetLastVisitTime(value
);
463 (*outEntry
)->SetIsWritten();
468 static nsresult
writePRInt64(FILE *outStm
, const PRInt64
& inValue
)
470 nsInt64
value(inValue
);
472 if (value
== nsInt64(0)) {
477 nsCAutoString tempString
;
479 while (value
!= nsInt64(0)) {
480 PRInt32 ones
= PRInt32(value
% nsInt64(10));
481 value
/= nsInt64(10);
482 tempString
.Insert(char('0' + ones
), 0);
484 int result
= fputs(tempString
.get(), outStm
);
485 return (result
== EOF
) ? NS_ERROR_FAILURE
: NS_OK
;
488 nsresult
writeEntry(FILE *outStm
, nsCStringKey
*url
, HistoryEntry
*entry
)
490 writePRInt64(outStm
, entry
->GetLastVisitTime());
493 fputs(url
->GetString(), outStm
);
494 entry
->SetIsWritten();
496 #if defined (XP_WIN) || defined(XP_OS2)
499 #elif defined(XP_UNIX)
508 PRIntn PR_CALLBACK
enumWriteEntry(nsHashKey
*aKey
, void *aData
, void* closure
)
510 FILE *outStm
= static_cast<FILE*>(closure
);
512 return kHashEnumerateStop
;
513 nsCStringKey
*stringKey
= static_cast<nsCStringKey
*>(aKey
);
515 return kHashEnumerateStop
;
516 HistoryEntry
*entry
= static_cast<HistoryEntry
*>(aData
);
518 return kHashEnumerateStop
;
520 nsresult rv
= writeEntry(outStm
, stringKey
, entry
);
522 return NS_SUCCEEDED(rv
) ? kHashEnumerateNext
: kHashEnumerateStop
;
525 PRIntn PR_CALLBACK
enumWriteEntryIfUnwritten(nsHashKey
*aKey
, void *aData
, void* closure
)
527 FILE *outStm
= static_cast<FILE*>(closure
);
529 return kHashEnumerateStop
;
530 nsCStringKey
*stringKey
= static_cast<nsCStringKey
*>(aKey
);
532 return kHashEnumerateStop
;
533 HistoryEntry
*entry
= static_cast<HistoryEntry
*>(aData
);
535 return kHashEnumerateStop
;
538 if (!entry
->GetIsWritten())
539 rv
= writeEntry(outStm
, stringKey
, entry
);
541 return NS_SUCCEEDED(rv
) ? kHashEnumerateNext
: kHashEnumerateStop
;
544 PRIntn PR_CALLBACK
enumDeleteEntry(nsHashKey
*aKey
, void *aData
, void* closure
)
546 HistoryEntry
*entry
= static_cast<HistoryEntry
*>(aData
);
549 return kHashEnumerateNext
;