merge (still not working, probably box/unbox dumbness)
[mozilla-central.git] / embedding / lite / nsEmbedGlobalHistory.cpp
blob476e7297ae2da8721c51ab7e83c872d3af043b64
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
13 * License.
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.
22 * Contributor(s):
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"
45 #include "nsInt64.h"
46 #include "prtypes.h"
47 #include "nsFixedSizeAllocator.h"
48 #include "nsVoidArray.h"
49 #include "nsIPrefService.h"
51 // Constants
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 //*****************************************************************************
70 // HistoryEntry
71 //*****************************************************************************
73 class HistoryEntry {
74 public:
75 HistoryEntry() :
76 mWritten(PR_FALSE) {}
79 void OnVisited()
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; }
90 PRBool GetIsWritten()
91 { return mWritten; }
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();
102 private:
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()))
119 return nsnull;
121 return sPool->Alloc(size);
124 void HistoryEntry::operator delete(void *p, size_t size)
126 if (!p)
127 return;
128 if (size != sizeof(HistoryEntry))
129 ::operator delete(p);
130 if (!sPool) {
131 NS_ERROR("HistoryEntry outlived its memory pool");
132 return;
134 sPool->Free(p, size);
137 nsresult HistoryEntry::InitPool()
139 if (!sPool) {
140 sPool = new nsFixedSizeAllocator;
141 if (!sPool)
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);
150 if (NS_FAILED(rv))
151 return rv;
153 return NS_OK;
156 void HistoryEntry::ReleasePool()
158 delete sPool;
159 sPool = nsnull;
162 //*****************************************************************************
163 // nsEmbedGlobalHistory - Creation/Destruction
164 //*****************************************************************************
166 NS_IMPL_ISUPPORTS3(nsEmbedGlobalHistory, nsIGlobalHistory, nsIObserver, nsISupportsWeakReference)
168 nsEmbedGlobalHistory::nsEmbedGlobalHistory() :
169 mDataIsLoaded(PR_FALSE), mEntriesAddedSinceFlush(0),
170 mURLTable(nsnull)
172 LL_I2L(mExpirationInterval, kDefaultExpirationIntervalDays);
173 LL_MUL(mExpirationInterval, mExpirationInterval, kMSecsPerDay);
176 nsEmbedGlobalHistory::~nsEmbedGlobalHistory()
178 FlushData();
179 delete mURLTable;
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"));
190 if (prefs) {
191 PRInt32 expireDays;
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");
201 if (observerService)
202 observerService->AddObserver(this, "profile-before-change", PR_TRUE);
204 return NS_OK;
207 //*****************************************************************************
208 // nsEmbedGlobalHistory::nsIGlobalHistory
209 //*****************************************************************************
211 NS_IMETHODIMP nsEmbedGlobalHistory::AddPage(const char *aURL)
213 NS_ENSURE_ARG(aURL);
215 nsresult rv = LoadData();
216 NS_ENSURE_SUCCESS(rv, rv);
218 nsCStringKey asKey(aURL);
219 HistoryEntry *entry = static_cast<HistoryEntry *>(mURLTable->Get(&asKey));
220 if (!entry) {
222 if (++mEntriesAddedSinceFlush >= kNewEntriesBetweenFlush)
223 FlushData(kFlushModeAppend);
225 HistoryEntry *newEntry = new HistoryEntry;
226 if (!newEntry)
227 return NS_ERROR_FAILURE;
228 (void)mURLTable->Put(&asKey, newEntry);
229 entry = newEntry;
231 entry->OnVisited();
233 return NS_OK;
236 NS_IMETHODIMP nsEmbedGlobalHistory::IsVisited(const char *aURL, PRBool *_retval)
238 NS_ENSURE_ARG(aURL);
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));
247 return NS_OK;
250 //*****************************************************************************
251 // nsEmbedGlobalHistory::nsIObserver
252 //*****************************************************************************
254 NS_IMETHODIMP nsEmbedGlobalHistory::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData)
256 nsresult rv = NS_OK;
258 if (strcmp(aTopic, "profile-before-change") == 0) {
259 (void)FlushData();
260 (void)ResetData();
262 return rv;
265 //*****************************************************************************
266 // nsEmbedGlobalHistory
267 //*****************************************************************************
269 nsresult nsEmbedGlobalHistory::LoadData()
271 if (!mDataIsLoaded) {
273 nsresult rv;
274 PRBool exists;
276 mDataIsLoaded = PR_TRUE;
278 rv = GetHistoryFile();
279 if (NS_FAILED(rv))
280 return rv;
281 rv = mHistoryFile->Exists(&exists);
282 if (NS_FAILED(rv))
283 return rv;
284 if (!exists)
285 return NS_OK;
287 FILE *stdFile;
288 rv = mHistoryFile->OpenANSIFileDesc("r", &stdFile);
289 if (NS_FAILED(rv))
290 return rv;
292 nsCAutoString outString;
293 HistoryEntry *newEntry;
294 while (NS_SUCCEEDED(readEntry(stdFile, outString, &newEntry))) {
295 if (EntryHasExpired(newEntry)) {
296 delete newEntry;
298 else {
299 nsCStringKey asKey(outString);
300 mURLTable->Put(&asKey, newEntry);
304 fclose(stdFile);
306 return NS_OK;
309 nsresult nsEmbedGlobalHistory::FlushData(PRIntn mode)
311 if (mHistoryFile) {
313 const char* openMode = (mode == kFlushModeAppend ? "a" : "w");
314 FILE *stdFile;
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);
323 else
324 mURLTable->Enumerate(enumWriteEntry, stdFile);
326 mEntriesAddedSinceFlush = 0;
327 fclose(stdFile);
329 return NS_OK;
332 nsresult nsEmbedGlobalHistory::ResetData()
334 mURLTable->Reset(enumDeleteEntry);
335 mHistoryFile = 0;
336 mDataIsLoaded = PR_FALSE;
337 mEntriesAddedSinceFlush = 0;
338 return NS_OK;
341 nsresult nsEmbedGlobalHistory::GetHistoryFile()
343 nsresult rv;
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);
355 return NS_OK;
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);
375 if (!entry)
376 return PR_FALSE;
377 nsEmbedGlobalHistory *history = static_cast<nsEmbedGlobalHistory*>(closure);
378 if (!history)
379 return kHashEnumerateStop;
381 if (history->EntryHasExpired(entry)) {
382 delete entry;
383 return kHashEnumerateRemove;
385 return kHashEnumerateNext;
389 //*****************************************************************************
390 // Static Functions
391 //*****************************************************************************
393 static nsresult parsePRInt64(FILE *inStm, PRInt64& outValue)
395 int c, charsRead = 0;
396 nsInt64 value = 0;
398 while (PR_TRUE) {
399 c = fgetc(inStm);
400 if (c == EOF || !isdigit(c))
401 break;
403 ++charsRead;
404 PRInt32 digit = c - '0';
405 value *= nsInt64(10);
406 value += nsInt64(digit);
408 if (!charsRead)
409 return NS_ERROR_FAILURE;
410 outValue = value;
411 return NS_OK;
414 nsresult readEntry(FILE *inStream, nsCString& outURL, HistoryEntry **outEntry)
416 nsresult rv;
418 // Get the last visted date
419 PRInt64 value;
420 rv = parsePRInt64(inStream, value);
421 if (NS_FAILED(rv))
422 return rv;
424 // Get the URL
425 int c;
426 char buf[1024];
427 char *next, *end = buf + sizeof(buf);
429 outURL.Truncate(0);
430 next = buf;
432 while (PR_TRUE) {
433 c = fgetc(inStream);
435 if (c == EOF)
436 break;
437 else if (c == '\n')
438 break;
439 else if (c == '\r') {
440 c = fgetc(inStream);
441 if (c != '\n')
442 ungetc(c, inStream);
443 break;
445 else {
446 *next++ = c;
447 if (next >= end) {
448 outURL.Append(buf, next - buf);
449 next = buf;
453 if (next > buf)
454 outURL.Append(buf, next - buf);
456 if (!outURL.Length() && c == EOF)
457 return NS_ERROR_FAILURE;
459 *outEntry = new HistoryEntry;
460 if (!*outEntry)
461 return NS_ERROR_OUT_OF_MEMORY;
462 (*outEntry)->SetLastVisitTime(value);
463 (*outEntry)->SetIsWritten();
465 return NS_OK;
468 static nsresult writePRInt64(FILE *outStm, const PRInt64& inValue)
470 nsInt64 value(inValue);
472 if (value == nsInt64(0)) {
473 fputc('0', outStm);
474 return NS_OK;
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());
491 fputc(':', outStm);
493 fputs(url->GetString(), outStm);
494 entry->SetIsWritten();
496 #if defined (XP_WIN) || defined(XP_OS2)
497 fputc('\r', outStm);
498 fputc('\n', outStm);
499 #elif defined(XP_UNIX)
500 fputc('\n', outStm);
501 #else
502 fputc('\r', outStm);
503 #endif
505 return NS_OK;
508 PRIntn PR_CALLBACK enumWriteEntry(nsHashKey *aKey, void *aData, void* closure)
510 FILE *outStm = static_cast<FILE*>(closure);
511 if (!outStm)
512 return kHashEnumerateStop;
513 nsCStringKey *stringKey = static_cast<nsCStringKey*>(aKey);
514 if (!stringKey)
515 return kHashEnumerateStop;
516 HistoryEntry *entry = static_cast<HistoryEntry*>(aData);
517 if (!entry)
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);
528 if (!outStm)
529 return kHashEnumerateStop;
530 nsCStringKey *stringKey = static_cast<nsCStringKey*>(aKey);
531 if (!stringKey)
532 return kHashEnumerateStop;
533 HistoryEntry *entry = static_cast<HistoryEntry*>(aData);
534 if (!entry)
535 return kHashEnumerateStop;
537 nsresult rv = NS_OK;
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);
547 delete entry;
549 return kHashEnumerateNext;