Back to index

lightning-sunbird  0.9+nobinonly
nsEmbedGlobalHistory.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Conrad Carlen <ccarlen@netscape.com>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsEmbedGlobalHistory.h"
00040 #include "nsIObserver.h"
00041 #include "nsIObserverService.h"
00042 #include "nsWeakReference.h"
00043 #include "nsAppDirectoryServiceDefs.h"
00044 #include "nsHashtable.h"
00045 #include "nsInt64.h"
00046 #include "prtypes.h"
00047 #include "nsFixedSizeAllocator.h"
00048 #include "nsVoidArray.h"
00049 #include "nsIPrefService.h"
00050 
00051 // Constants
00052 static const PRInt32 kNewEntriesBetweenFlush = 10;
00053 
00054 static const PRUint32 kDefaultExpirationIntervalDays = 7;
00055 
00056 static const PRInt64 kMSecsPerDay = LL_INIT(0, 60 * 60 * 24 * 1000);
00057 static const PRInt64 kOneThousand = LL_INIT(0, 1000);
00058 
00059 #define PREF_BROWSER_HISTORY_EXPIRE_DAYS "browser.history_expire_days"
00060 
00061 // Static Routine Prototypes
00062 static nsresult readEntry(FILE *inStream, nsCString& url, HistoryEntry **entry);
00063 static nsresult writeEntry(FILE *outStm, nsCStringKey *url, HistoryEntry *entry);
00064 
00065 static PRIntn PR_CALLBACK enumWriteEntry(nsHashKey *aKey, void *aData, void* closure);
00066 static PRIntn PR_CALLBACK enumWriteEntryIfUnwritten(nsHashKey *aKey, void *aData, void* closure);
00067 static PRIntn PR_CALLBACK enumDeleteEntry(nsHashKey *aKey, void *aData, void* closure);
00068 
00069 //*****************************************************************************
00070 // HistoryEntry
00071 //*****************************************************************************   
00072 
00073 class HistoryEntry {
00074 public:
00075                 HistoryEntry() :
00076                   mWritten(PR_FALSE) {}
00077   
00078 
00079   void          OnVisited()
00080                 {
00081                   mLastVisitTime = PR_Now(); 
00082                   LL_DIV(mLastVisitTime, mLastVisitTime, kOneThousand);
00083                 }
00084                 
00085   PRInt64       GetLastVisitTime()
00086                 { return mLastVisitTime; }
00087   void          SetLastVisitTime(const PRInt64& aTime)
00088                 { mLastVisitTime = aTime; }
00089 
00090   PRBool        GetIsWritten()
00091                 { return mWritten; }
00092   void          SetIsWritten(PRBool written = PR_TRUE)
00093                 { mWritten = PR_TRUE; }
00094                   
00095   // Memory management stuff    
00096   static void*  operator new(size_t size) CPP_THROW_NEW;
00097   static void   operator delete(void *p, size_t size);
00098   
00099   // Must be called when done with all HistoryEntry objects
00100   static void ReleasePool();
00101  
00102  private:
00103   PRInt64       mLastVisitTime; // Millisecs
00104   PRPackedBool  mWritten; // TRUE if ever persisted
00105 
00106   static nsresult InitPool();
00107   static nsFixedSizeAllocator *sPool;
00108 };
00109 
00110 nsFixedSizeAllocator *HistoryEntry::sPool;
00111 
00112 //*****************************************************************************   
00113 
00114 void* HistoryEntry::operator new(size_t size) CPP_THROW_NEW
00115 {
00116   if (size != sizeof(HistoryEntry))
00117     return ::operator new(size);
00118   if (!sPool && NS_FAILED(InitPool()))
00119     return nsnull;
00120     
00121   return sPool->Alloc(size);
00122 }
00123 
00124 void HistoryEntry::operator delete(void *p, size_t size)
00125 {
00126   if (!p)
00127     return;
00128   if (size != sizeof(HistoryEntry))
00129     ::operator delete(p);
00130   if (!sPool) {
00131     NS_ERROR("HistoryEntry outlived its memory pool");
00132     return;
00133   }
00134   sPool->Free(p, size);
00135 }
00136 
00137 nsresult HistoryEntry::InitPool()
00138 {
00139   if (!sPool) {
00140     sPool = new nsFixedSizeAllocator;
00141     if (!sPool)
00142       return NS_ERROR_OUT_OF_MEMORY;
00143 
00144     static const size_t kBucketSizes[] =
00145       { sizeof(HistoryEntry) };
00146     static const PRInt32 kInitialPoolSize =
00147       NS_SIZE_IN_HEAP(sizeof(HistoryEntry)) * 256;
00148 
00149     nsresult rv = sPool->Init("EmbedLite HistoryEntry Pool", kBucketSizes, 1, kInitialPoolSize);
00150     if (NS_FAILED(rv))
00151       return rv;
00152   }
00153   return NS_OK;
00154 }
00155 
00156 void HistoryEntry::ReleasePool()
00157 {
00158   delete sPool;
00159   sPool = nsnull;
00160 }
00161 
00162 //*****************************************************************************
00163 // nsEmbedGlobalHistory - Creation/Destruction
00164 //*****************************************************************************   
00165 
00166 NS_IMPL_ISUPPORTS3(nsEmbedGlobalHistory, nsIGlobalHistory, nsIObserver, nsISupportsWeakReference)
00167 
00168 nsEmbedGlobalHistory::nsEmbedGlobalHistory() :
00169   mDataIsLoaded(PR_FALSE), mEntriesAddedSinceFlush(0),
00170   mURLTable(nsnull)
00171 {  
00172   LL_I2L(mExpirationInterval, kDefaultExpirationIntervalDays);
00173   LL_MUL(mExpirationInterval, mExpirationInterval, kMSecsPerDay);
00174 }
00175 
00176 nsEmbedGlobalHistory::~nsEmbedGlobalHistory()
00177 {
00178   FlushData();
00179   delete mURLTable;
00180   HistoryEntry::ReleasePool();
00181 }
00182 
00183 NS_IMETHODIMP nsEmbedGlobalHistory::Init()
00184 {
00185   mURLTable = new nsHashtable;
00186   NS_ENSURE_TRUE(mURLTable, NS_ERROR_OUT_OF_MEMORY);
00187   
00188   // Get Pref and convert to millisecs
00189   nsCOMPtr<nsIPrefBranch> prefs(do_GetService("@mozilla.org/preferences-service;1"));
00190   if (prefs) {
00191     PRInt32 expireDays;
00192     prefs->GetIntPref(PREF_BROWSER_HISTORY_EXPIRE_DAYS, &expireDays);
00193     LL_I2L(mExpirationInterval, expireDays);
00194     LL_MUL(mExpirationInterval, mExpirationInterval, kMSecsPerDay);
00195   }
00196   
00197   // register to observe profile changes
00198   nsCOMPtr<nsIObserverService> observerService = 
00199        do_GetService("@mozilla.org/observer-service;1");
00200   NS_ASSERTION(observerService, "failed to get observer service");
00201   if (observerService)
00202     observerService->AddObserver(this, "profile-before-change", PR_TRUE);
00203 
00204   return NS_OK;
00205 }
00206 
00207 //*****************************************************************************
00208 // nsEmbedGlobalHistory::nsIGlobalHistory
00209 //*****************************************************************************   
00210 
00211 NS_IMETHODIMP nsEmbedGlobalHistory::AddPage(const char *aURL)
00212 {
00213   NS_ENSURE_ARG(aURL);
00214 
00215   nsresult rv = LoadData();
00216   NS_ENSURE_SUCCESS(rv, rv);
00217   
00218   nsCStringKey asKey(aURL);
00219   HistoryEntry *entry = NS_STATIC_CAST(HistoryEntry *, mURLTable->Get(&asKey));
00220   if (!entry) {
00221     
00222     if (++mEntriesAddedSinceFlush >= kNewEntriesBetweenFlush)
00223       FlushData(kFlushModeAppend);
00224 
00225     HistoryEntry *newEntry = new HistoryEntry;
00226     if (!newEntry)
00227       return NS_ERROR_FAILURE;
00228     (void)mURLTable->Put(&asKey, newEntry);
00229     entry = newEntry;
00230   }
00231   entry->OnVisited(); 
00232   
00233   return NS_OK;
00234 }
00235 
00236 NS_IMETHODIMP nsEmbedGlobalHistory::IsVisited(const char *aURL, PRBool *_retval)
00237 {
00238   NS_ENSURE_ARG(aURL);
00239   NS_ENSURE_ARG_POINTER(_retval);
00240   
00241   nsresult rv = LoadData();
00242   NS_ENSURE_SUCCESS(rv, rv);
00243 
00244   nsCStringKey asKey(aURL);
00245   
00246   *_retval = (mURLTable->Exists(&asKey));
00247   return NS_OK;
00248 }
00249 
00250 //*****************************************************************************
00251 // nsEmbedGlobalHistory::nsIObserver
00252 //*****************************************************************************   
00253 
00254 NS_IMETHODIMP nsEmbedGlobalHistory::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData)
00255 {
00256   nsresult rv = NS_OK;
00257     
00258   if (strcmp(aTopic, "profile-before-change") == 0) {
00259     (void)FlushData();
00260     (void)ResetData();
00261   }
00262   return rv;
00263 }
00264 
00265 //*****************************************************************************
00266 // nsEmbedGlobalHistory
00267 //*****************************************************************************   
00268 
00269 nsresult nsEmbedGlobalHistory::LoadData()
00270 {
00271   if (!mDataIsLoaded) {
00272     
00273     nsresult rv;            
00274     PRBool exists;
00275 
00276     mDataIsLoaded = PR_TRUE;
00277 
00278     rv = GetHistoryFile();
00279     if (NS_FAILED(rv))
00280       return rv;
00281     rv = mHistoryFile->Exists(&exists);
00282     if (NS_FAILED(rv))
00283       return rv;
00284     if (!exists)
00285       return NS_OK;
00286     
00287     FILE *stdFile;
00288     rv = mHistoryFile->OpenANSIFileDesc("r", &stdFile);
00289     if (NS_FAILED(rv))
00290       return rv;
00291       
00292     nsCAutoString outString;
00293     HistoryEntry *newEntry;      
00294     while (NS_SUCCEEDED(readEntry(stdFile, outString, &newEntry))) {
00295       if (EntryHasExpired(newEntry)) {
00296         delete newEntry;
00297       }
00298       else {
00299         nsCStringKey asKey(outString);
00300         mURLTable->Put(&asKey, newEntry);
00301       }
00302     }
00303     
00304     fclose(stdFile);
00305   }
00306   return NS_OK;
00307 }
00308 
00309 nsresult nsEmbedGlobalHistory::FlushData(PRIntn mode)
00310 {  
00311   if (mHistoryFile) {
00312 
00313     const char* openMode = (mode == kFlushModeAppend ? "a" : "w");
00314     FILE *stdFile;
00315     nsresult rv = mHistoryFile->OpenANSIFileDesc(openMode, &stdFile);
00316     if (NS_FAILED(rv)) return rv;
00317 
00318     // Before flushing either way, remove dead entries
00319     mURLTable->Enumerate(enumRemoveEntryIfExpired, this);  
00320     
00321     if (mode == kFlushModeAppend)
00322         mURLTable->Enumerate(enumWriteEntryIfUnwritten, stdFile);
00323     else
00324         mURLTable->Enumerate(enumWriteEntry, stdFile);
00325     
00326     mEntriesAddedSinceFlush = 0;
00327     fclose(stdFile);
00328   }
00329   return NS_OK; 
00330 }
00331 
00332 nsresult nsEmbedGlobalHistory::ResetData()
00333 {
00334   mURLTable->Reset(enumDeleteEntry);
00335   mHistoryFile = 0;
00336   mDataIsLoaded = PR_FALSE;
00337   mEntriesAddedSinceFlush = 0;
00338   return NS_OK;
00339 }
00340 
00341 nsresult nsEmbedGlobalHistory::GetHistoryFile()
00342 {
00343   nsresult rv;
00344 
00345   // Get the history file in our profile dir.
00346   // Notice we are not just getting NS_APP_HISTORY_50_FILE
00347   // because it is used by the "real" global history component.
00348   
00349   nsCOMPtr<nsIFile> aFile; 
00350   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(aFile));
00351   NS_ENSURE_SUCCESS(rv, rv);
00352   rv = aFile->Append(NS_LITERAL_STRING("history.txt"));
00353   NS_ENSURE_SUCCESS(rv, rv);
00354   mHistoryFile = do_QueryInterface(aFile);
00355   return NS_OK;
00356 }
00357 
00358 PRBool nsEmbedGlobalHistory::EntryHasExpired(HistoryEntry *entry)
00359 {
00360   // convert "now" from microsecs to millisecs
00361   PRInt64 nowInMilliSecs = PR_Now(); 
00362   LL_DIV(nowInMilliSecs, nowInMilliSecs, kOneThousand);
00363 
00364   // determine when the entry would have expired
00365   PRInt64 expirationIntervalAgo;
00366   LL_SUB(expirationIntervalAgo, nowInMilliSecs, mExpirationInterval);
00367 
00368   PRInt64 lastVisitTime = entry->GetLastVisitTime();
00369   return (LL_CMP(lastVisitTime, <, expirationIntervalAgo));
00370 }
00371 
00372 PRIntn PR_CALLBACK nsEmbedGlobalHistory::enumRemoveEntryIfExpired(nsHashKey *aKey, void *aData, void* closure)
00373 {
00374   HistoryEntry *entry = NS_STATIC_CAST(HistoryEntry*, aData);
00375   if (!entry)
00376     return PR_FALSE;
00377   nsEmbedGlobalHistory *history = NS_STATIC_CAST(nsEmbedGlobalHistory*, closure);
00378   if (!history)
00379     return kHashEnumerateStop;
00380       
00381   if (history->EntryHasExpired(entry)) {
00382     delete entry;
00383     return kHashEnumerateRemove;
00384   }
00385   return kHashEnumerateNext;
00386 }
00387 
00388 
00389 //*****************************************************************************
00390 // Static Functions
00391 //*****************************************************************************   
00392 
00393 static nsresult parsePRInt64(FILE *inStm, PRInt64& outValue)
00394 {
00395   int c, charsRead = 0;
00396   nsInt64 value = 0;
00397 
00398   while (PR_TRUE) {
00399     c = fgetc(inStm);
00400     if (c == EOF || !isdigit(c))
00401       break;
00402       
00403     ++charsRead;
00404     PRInt32 digit = c - '0';
00405     value *= nsInt64(10);
00406     value += nsInt64(digit);
00407   }
00408   if (!charsRead)
00409     return NS_ERROR_FAILURE;
00410   outValue = value;
00411   return NS_OK;
00412 }
00413 
00414 nsresult readEntry(FILE *inStream, nsCString& outURL, HistoryEntry **outEntry)
00415 {
00416   nsresult rv;
00417 
00418   // Get the last visted date
00419   PRInt64 value;
00420   rv = parsePRInt64(inStream, value);
00421   if (NS_FAILED(rv))
00422     return rv;
00423   
00424   // Get the URL
00425   int c;
00426   char buf[1024];
00427   char *next, *end = buf + sizeof(buf);
00428     
00429   outURL.Truncate(0);
00430   next = buf;
00431   
00432   while (PR_TRUE) {
00433     c = fgetc(inStream);
00434     
00435     if (c == EOF)
00436       break;
00437     else if (c == '\n')
00438       break;
00439     else if (c == '\r') {
00440       c = fgetc(inStream);
00441       if (c != '\n')
00442         ungetc(c, inStream);
00443       break;
00444     }
00445     else {
00446       *next++ = c;
00447       if (next >= end) {
00448         outURL.Append(buf, next - buf);
00449         next = buf;
00450       }
00451     }
00452   }
00453   if (next > buf)
00454     outURL.Append(buf, next - buf);
00455     
00456   if (!outURL.Length() && c == EOF)
00457     return NS_ERROR_FAILURE;
00458     
00459   *outEntry = new HistoryEntry;
00460   if (!*outEntry)
00461     return NS_ERROR_OUT_OF_MEMORY;
00462   (*outEntry)->SetLastVisitTime(value);
00463   (*outEntry)->SetIsWritten();
00464     
00465   return NS_OK;
00466 }
00467 
00468 static nsresult writePRInt64(FILE *outStm, const PRInt64& inValue)
00469 {
00470   nsInt64 value(inValue);
00471   
00472   if (value == nsInt64(0)) {
00473     fputc('0', outStm);
00474     return NS_OK;
00475   }
00476   
00477   nsCAutoString tempString;
00478 
00479   while (value != nsInt64(0)) {
00480     PRInt32 ones = PRInt32(value % nsInt64(10));
00481     value /= nsInt64(10);
00482     tempString.Insert(char('0' + ones), 0);
00483   }
00484   int result = fputs(tempString.get(), outStm);
00485   return (result == EOF) ? NS_ERROR_FAILURE : NS_OK;
00486 }
00487 
00488 nsresult writeEntry(FILE *outStm, nsCStringKey *url, HistoryEntry *entry)
00489 {
00490   writePRInt64(outStm, entry->GetLastVisitTime());
00491   fputc(':', outStm);
00492 
00493   fputs(url->GetString(), outStm);
00494   entry->SetIsWritten();
00495 
00496 #if defined (XP_WIN) || defined(XP_OS2)
00497   fputc('\r', outStm);
00498   fputc('\n', outStm);
00499 #elif defined(XP_UNIX)
00500   fputc('\n', outStm);
00501 #else
00502   fputc('\r', outStm);
00503 #endif
00504 
00505   return NS_OK;
00506 }
00507 
00508 PRIntn PR_CALLBACK enumWriteEntry(nsHashKey *aKey, void *aData, void* closure)
00509 {
00510   FILE *outStm = NS_STATIC_CAST(FILE*, closure);
00511   if (!outStm)
00512     return kHashEnumerateStop;
00513   nsCStringKey *stringKey = NS_STATIC_CAST(nsCStringKey*, aKey);
00514   if (!stringKey)
00515     return kHashEnumerateStop;
00516   HistoryEntry *entry = NS_STATIC_CAST(HistoryEntry*, aData);
00517   if (!entry)
00518     return kHashEnumerateStop;
00519 
00520   nsresult rv = writeEntry(outStm, stringKey, entry);
00521     
00522   return NS_SUCCEEDED(rv) ? kHashEnumerateNext : kHashEnumerateStop;
00523 }
00524 
00525 PRIntn PR_CALLBACK enumWriteEntryIfUnwritten(nsHashKey *aKey, void *aData, void* closure)
00526 {
00527   FILE *outStm = NS_STATIC_CAST(FILE*, closure);
00528   if (!outStm)
00529     return kHashEnumerateStop;
00530   nsCStringKey *stringKey = NS_STATIC_CAST(nsCStringKey*, aKey);
00531   if (!stringKey)
00532     return kHashEnumerateStop;
00533   HistoryEntry *entry = NS_STATIC_CAST(HistoryEntry*, aData);
00534   if (!entry)
00535     return kHashEnumerateStop;
00536 
00537   nsresult rv = NS_OK;
00538   if (!entry->GetIsWritten())
00539     rv = writeEntry(outStm, stringKey, entry);
00540     
00541   return NS_SUCCEEDED(rv) ? kHashEnumerateNext : kHashEnumerateStop;
00542 }
00543 
00544 PRIntn PR_CALLBACK enumDeleteEntry(nsHashKey *aKey, void *aData, void* closure)
00545 {
00546   HistoryEntry *entry = NS_STATIC_CAST(HistoryEntry*, aData);
00547   delete entry;
00548   
00549   return kHashEnumerateNext;
00550 }