/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_DataStorage_h #define mozilla_DataStorage_h #include "mozilla/Monitor.h" #include "mozilla/Mutex.h" #include "mozilla/StaticPtr.h" #include "nsCOMPtr.h" #include "nsDataHashtable.h" #include "nsIObserver.h" #include "nsIThread.h" #include "nsITimer.h" #include "nsRefPtrHashtable.h" #include "nsString.h" namespace mozilla { namespace dom { class DataStorageItem; } /** * DataStorage is a threadsafe, generic, narrow string-based hash map that * persists data on disk and additionally handles temporary and private data. * However, if used in a context where there is no profile directory, data * will not be persisted. * * Its lifecycle is as follows: * - Allocate with a filename (this is or will eventually be a file in the * profile directory, if the profile exists). * - Call Init() from the main thread. This spins off an asynchronous read * of the backing file. * - Eventually observers of the topic "data-storage-ready" will be notified * with the backing filename as the data in the notification when this * has completed. * - Should the profile directory not be available, (e.g. in xpcshell), * DataStorage will not initially read any persistent data. The * "data-storage-ready" event will still be emitted. This follows semantics * similar to the permission manager and allows tests that test * unrelated components to proceed without a profile. * - When any persistent data changes, a timer is initialized that will * eventually asynchronously write all persistent data to the backing file. * When this happens, observers will be notified with the topic * "data-storage-written" and the backing filename as the data. * It is possible to receive a "data-storage-written" event while there exist * pending persistent data changes. However, those changes will cause the * timer to be reinitialized and another "data-storage-written" event will * be sent. * - When DataStorage observes the topic "profile-before-change" in * anticipation of shutdown, all persistent data is synchronously written to * the backing file. The worker thread responsible for these writes is then * disabled to prevent further writes to that file (the delayed-write timer * is cancelled when this happens). * - For testing purposes, the preference "test.datastorage.write_timer_ms" can * be set to cause the asynchronous writing of data to happen more quickly. * - To prevent unbounded memory and disk use, the number of entries in each * table is limited to 1024. Evictions are handled in by a modified LRU scheme * (see implementation comments). * - NB: Instances of DataStorage have long lifetimes because they are strong * observers of events and won't go away until the observer service does. * * For each key/value: * - The key must be a non-empty string containing no instances of '\t' or '\n' * (this is a limitation of how the data is stored and will be addressed in * the future). * - The key must have a length no more than 256. * - The value must not contain '\n' and must have a length no more than 1024. * (the length limits are to prevent unbounded disk and memory usage) */ /** * Data that is DataStorage_Persistent is saved on disk. DataStorage_Temporary * and DataStorage_Private are not saved. DataStorage_Private is meant to * only be set and accessed from private contexts. It will be cleared upon * observing the event "last-pb-context-exited". */ enum DataStorageType { DataStorage_Persistent, DataStorage_Temporary, DataStorage_Private }; class DataStorage : public nsIObserver { typedef dom::DataStorageItem DataStorageItem; public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIOBSERVER // If there is a profile directory, there is or will eventually be a file // by the name specified by aFilename there. static already_AddRefed Get(const nsString& aFilename); static already_AddRefed GetIfExists(const nsString& aFilename); // Initializes the DataStorage. Must be called before using. // aDataWillPersist returns whether or not data can be persistently saved. nsresult Init(/*out*/bool& aDataWillPersist); // Given a key and a type of data, returns a value. Returns an empty string if // the key is not present for that type of data. If Get is called before the // "data-storage-ready" event is observed, it will block. NB: It is not // currently possible to differentiate between missing data and data that is // the empty string. nsCString Get(const nsCString& aKey, DataStorageType aType); // Give a key, value, and type of data, adds an entry as appropriate. // Updates existing entries. nsresult Put(const nsCString& aKey, const nsCString& aValue, DataStorageType aType); // Given a key and type of data, removes an entry if present. void Remove(const nsCString& aKey, DataStorageType aType); // Removes all entries of all types of data. nsresult Clear(); // Read all of the data items. void GetAll(InfallibleTArray* aItems); private: explicit DataStorage(const nsString& aFilename); virtual ~DataStorage(); class Writer; class Reader; class Entry { public: Entry(); bool UpdateScore(); uint32_t mScore; int32_t mLastAccessed; // the last accessed time in days since the epoch nsCString mValue; }; // Utility class for scanning tables for an entry to evict. class KeyAndEntry { public: nsCString mKey; Entry mEntry; }; typedef nsDataHashtable DataStorageTable; typedef nsRefPtrHashtable DataStorages; void WaitForReady(); nsresult AsyncWriteData(const MutexAutoLock& aProofOfLock); nsresult AsyncReadData(bool& aHaveProfileDir, const MutexAutoLock& aProofOfLock); nsresult AsyncSetTimer(const MutexAutoLock& aProofOfLock); nsresult DispatchShutdownTimer(const MutexAutoLock& aProofOfLock); static nsresult ValidateKeyAndValue(const nsCString& aKey, const nsCString& aValue); static void TimerCallback(nsITimer* aTimer, void* aClosure); void SetTimer(); void ShutdownTimer(); void NotifyObservers(const char* aTopic); bool GetInternal(const nsCString& aKey, Entry* aEntry, DataStorageType aType, const MutexAutoLock& aProofOfLock); nsresult PutInternal(const nsCString& aKey, Entry& aEntry, DataStorageType aType, const MutexAutoLock& aProofOfLock); void MaybeEvictOneEntry(DataStorageType aType, const MutexAutoLock& aProofOfLock); DataStorageTable& GetTableForType(DataStorageType aType, const MutexAutoLock& aProofOfLock); void ReadAllFromTable(DataStorageType aType, InfallibleTArray* aItems, const MutexAutoLock& aProofOfLock); Mutex mMutex; // This mutex protects access to the following members: DataStorageTable mPersistentDataTable; DataStorageTable mTemporaryDataTable; DataStorageTable mPrivateDataTable; nsCOMPtr mWorkerThread; nsCOMPtr mBackingFile; nsCOMPtr mTimer; // All uses after init must be on the worker thread uint32_t mTimerDelay; // in milliseconds bool mPendingWrite; // true if a write is needed but hasn't been dispatched bool mShuttingDown; bool mInitCalled; // Indicates that Init() has been called. // (End list of members protected by mMutex) Monitor mReadyMonitor; // Do not acquire this at the same time as mMutex. bool mReady; // Indicates that saved data has been read and Get can proceed. const nsString mFilename; static StaticAutoPtr sDataStorages; }; } // namespace mozilla #endif // mozilla_DataStorage_h