Home | History | Annotate | Download | only in storage
      1 /*
      2  * Copyright (C) 2008, 2009 Apple Inc. All Rights Reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "StorageAreaSync.h"
     28 
     29 #if ENABLE(DOM_STORAGE)
     30 
     31 #include "CString.h"
     32 #include "EventNames.h"
     33 #include "HTMLElement.h"
     34 #include "SecurityOrigin.h"
     35 #include "SQLiteStatement.h"
     36 #include "StorageAreaImpl.h"
     37 #include "StorageSyncManager.h"
     38 #include "SuddenTermination.h"
     39 
     40 namespace WebCore {
     41 
     42 // If the StorageArea undergoes rapid changes, don't sync each change to disk.
     43 // Instead, queue up a batch of items to sync and actually do the sync at the following interval.
     44 static const double StorageSyncInterval = 1.0;
     45 
     46 PassRefPtr<StorageAreaSync> StorageAreaSync::create(PassRefPtr<StorageSyncManager> storageSyncManager, PassRefPtr<StorageAreaImpl> storageArea, String databaseIdentifier)
     47 {
     48     return adoptRef(new StorageAreaSync(storageSyncManager, storageArea, databaseIdentifier));
     49 }
     50 
     51 StorageAreaSync::StorageAreaSync(PassRefPtr<StorageSyncManager> storageSyncManager, PassRefPtr<StorageAreaImpl> storageArea, String databaseIdentifier)
     52     : m_syncTimer(this, &StorageAreaSync::syncTimerFired)
     53     , m_itemsCleared(false)
     54     , m_finalSyncScheduled(false)
     55     , m_storageArea(storageArea)
     56     , m_syncManager(storageSyncManager)
     57     , m_databaseIdentifier(databaseIdentifier.crossThreadString())
     58     , m_clearItemsWhileSyncing(false)
     59     , m_syncScheduled(false)
     60     , m_importComplete(false)
     61 {
     62     ASSERT(isMainThread());
     63     ASSERT(m_storageArea);
     64     ASSERT(m_syncManager);
     65 
     66     // FIXME: If it can't import, then the default WebKit behavior should be that of private browsing,
     67     // not silently ignoring it.  https://bugs.webkit.org/show_bug.cgi?id=25894
     68     if (!m_syncManager->scheduleImport(this))
     69         m_importComplete = true;
     70 }
     71 
     72 StorageAreaSync::~StorageAreaSync()
     73 {
     74     ASSERT(isMainThread());
     75     ASSERT(!m_syncTimer.isActive());
     76     ASSERT(m_finalSyncScheduled);
     77 }
     78 
     79 void StorageAreaSync::scheduleFinalSync()
     80 {
     81     ASSERT(isMainThread());
     82     // FIXME: We do this to avoid races, but it'd be better to make things safe without blocking.
     83     blockUntilImportComplete();
     84     m_storageArea = 0;  // This is done in blockUntilImportComplete() but this is here as a form of documentation that we must be absolutely sure the ref count cycle is broken.
     85 
     86     if (m_syncTimer.isActive())
     87         m_syncTimer.stop();
     88     else {
     89         // The following is balanced by the call to enableSuddenTermination in the
     90         // syncTimerFired function.
     91         disableSuddenTermination();
     92     }
     93     // FIXME: This is synchronous.  We should do it on the background process, but
     94     // we should do it safely.
     95     syncTimerFired(&m_syncTimer);
     96     m_finalSyncScheduled = true;
     97 }
     98 
     99 void StorageAreaSync::scheduleItemForSync(const String& key, const String& value)
    100 {
    101     ASSERT(isMainThread());
    102     ASSERT(!m_finalSyncScheduled);
    103 
    104     m_changedItems.set(key, value);
    105     if (!m_syncTimer.isActive()) {
    106         m_syncTimer.startOneShot(StorageSyncInterval);
    107 
    108         // The following is balanced by the call to enableSuddenTermination in the
    109         // syncTimerFired function.
    110         disableSuddenTermination();
    111     }
    112 }
    113 
    114 void StorageAreaSync::scheduleClear()
    115 {
    116     ASSERT(isMainThread());
    117     ASSERT(!m_finalSyncScheduled);
    118 
    119     m_changedItems.clear();
    120     m_itemsCleared = true;
    121     if (!m_syncTimer.isActive()) {
    122         m_syncTimer.startOneShot(StorageSyncInterval);
    123 
    124         // The following is balanced by the call to enableSuddenTermination in the
    125         // syncTimerFired function.
    126         disableSuddenTermination();
    127     }
    128 }
    129 
    130 void StorageAreaSync::syncTimerFired(Timer<StorageAreaSync>*)
    131 {
    132     ASSERT(isMainThread());
    133 
    134     HashMap<String, String>::iterator it = m_changedItems.begin();
    135     HashMap<String, String>::iterator end = m_changedItems.end();
    136 
    137     {
    138         MutexLocker locker(m_syncLock);
    139 
    140         if (m_itemsCleared) {
    141             m_itemsPendingSync.clear();
    142             m_clearItemsWhileSyncing = true;
    143             m_itemsCleared = false;
    144         }
    145 
    146         for (; it != end; ++it)
    147             m_itemsPendingSync.set(it->first.crossThreadString(), it->second.crossThreadString());
    148 
    149         if (!m_syncScheduled) {
    150             m_syncScheduled = true;
    151 
    152             // The following is balanced by the call to enableSuddenTermination in the
    153             // performSync function.
    154             disableSuddenTermination();
    155 
    156             m_syncManager->scheduleSync(this);
    157         }
    158     }
    159 
    160     // The following is balanced by the calls to disableSuddenTermination in the
    161     // scheduleItemForSync, scheduleClear, and scheduleFinalSync functions.
    162     enableSuddenTermination();
    163 
    164     m_changedItems.clear();
    165 }
    166 
    167 void StorageAreaSync::performImport()
    168 {
    169     ASSERT(!isMainThread());
    170     ASSERT(!m_database.isOpen());
    171 
    172     String databaseFilename = m_syncManager->fullDatabaseFilename(m_databaseIdentifier);
    173 
    174     if (databaseFilename.isEmpty()) {
    175         LOG_ERROR("Filename for local storage database is empty - cannot open for persistent storage");
    176         markImported();
    177         return;
    178     }
    179 
    180     if (!m_database.open(databaseFilename)) {
    181         LOG_ERROR("Failed to open database file %s for local storage", databaseFilename.utf8().data());
    182         markImported();
    183         return;
    184     }
    185 
    186     if (!m_database.executeCommand("CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value TEXT NOT NULL ON CONFLICT FAIL)")) {
    187         LOG_ERROR("Failed to create table ItemTable for local storage");
    188         markImported();
    189         return;
    190     }
    191 
    192     SQLiteStatement query(m_database, "SELECT key, value FROM ItemTable");
    193     if (query.prepare() != SQLResultOk) {
    194         LOG_ERROR("Unable to select items from ItemTable for local storage");
    195         markImported();
    196         return;
    197     }
    198 
    199     HashMap<String, String> itemMap;
    200 
    201     int result = query.step();
    202     while (result == SQLResultRow) {
    203         itemMap.set(query.getColumnText(0), query.getColumnText(1));
    204         result = query.step();
    205     }
    206 
    207     if (result != SQLResultDone) {
    208         LOG_ERROR("Error reading items from ItemTable for local storage");
    209         markImported();
    210         return;
    211     }
    212 
    213     HashMap<String, String>::iterator it = itemMap.begin();
    214     HashMap<String, String>::iterator end = itemMap.end();
    215 
    216     for (; it != end; ++it)
    217         m_storageArea->importItem(it->first, it->second);
    218 
    219     markImported();
    220 }
    221 
    222 void StorageAreaSync::markImported()
    223 {
    224     MutexLocker locker(m_importLock);
    225     m_importComplete = true;
    226     m_importCondition.signal();
    227 }
    228 
    229 // FIXME: In the future, we should allow use of StorageAreas while it's importing (when safe to do so).
    230 // Blocking everything until the import is complete is by far the simplest and safest thing to do, but
    231 // there is certainly room for safe optimization: Key/length will never be able to make use of such an
    232 // optimization (since the order of iteration can change as items are being added). Get can return any
    233 // item currently in the map. Get/remove can work whether or not it's in the map, but we'll need a list
    234 // of items the import should not overwrite. Clear can also work, but it'll need to kill the import
    235 // job first.
    236 void StorageAreaSync::blockUntilImportComplete()
    237 {
    238     ASSERT(isMainThread());
    239 
    240     // Fast path.  We set m_storageArea to 0 only after m_importComplete being true.
    241     if (!m_storageArea)
    242         return;
    243 
    244     MutexLocker locker(m_importLock);
    245     while (!m_importComplete)
    246         m_importCondition.wait(m_importLock);
    247     m_storageArea = 0;
    248 }
    249 
    250 void StorageAreaSync::sync(bool clearItems, const HashMap<String, String>& items)
    251 {
    252     ASSERT(!isMainThread());
    253 
    254     if (!m_database.isOpen())
    255         return;
    256 
    257     // If the clear flag is set, then we clear all items out before we write any new ones in.
    258     if (clearItems) {
    259         SQLiteStatement clear(m_database, "DELETE FROM ItemTable");
    260         if (clear.prepare() != SQLResultOk) {
    261             LOG_ERROR("Failed to prepare clear statement - cannot write to local storage database");
    262             return;
    263         }
    264 
    265         int result = clear.step();
    266         if (result != SQLResultDone) {
    267             LOG_ERROR("Failed to clear all items in the local storage database - %i", result);
    268             return;
    269         }
    270     }
    271 
    272     SQLiteStatement insert(m_database, "INSERT INTO ItemTable VALUES (?, ?)");
    273     if (insert.prepare() != SQLResultOk) {
    274         LOG_ERROR("Failed to prepare insert statement - cannot write to local storage database");
    275         return;
    276     }
    277 
    278     SQLiteStatement remove(m_database, "DELETE FROM ItemTable WHERE key=?");
    279     if (remove.prepare() != SQLResultOk) {
    280         LOG_ERROR("Failed to prepare delete statement - cannot write to local storage database");
    281         return;
    282     }
    283 
    284     HashMap<String, String>::const_iterator end = items.end();
    285 
    286     for (HashMap<String, String>::const_iterator it = items.begin(); it != end; ++it) {
    287         // Based on the null-ness of the second argument, decide whether this is an insert or a delete.
    288         SQLiteStatement& query = it->second.isNull() ? remove : insert;
    289 
    290         query.bindText(1, it->first);
    291 
    292         // If the second argument is non-null, we're doing an insert, so bind it as the value.
    293         if (!it->second.isNull())
    294             query.bindText(2, it->second);
    295 
    296         int result = query.step();
    297         if (result != SQLResultDone) {
    298             LOG_ERROR("Failed to update item in the local storage database - %i", result);
    299             break;
    300         }
    301 
    302         query.reset();
    303     }
    304 }
    305 
    306 void StorageAreaSync::performSync()
    307 {
    308     ASSERT(!isMainThread());
    309 
    310     bool clearItems;
    311     HashMap<String, String> items;
    312     {
    313         MutexLocker locker(m_syncLock);
    314 
    315         ASSERT(m_syncScheduled);
    316 
    317         clearItems = m_clearItemsWhileSyncing;
    318         m_itemsPendingSync.swap(items);
    319 
    320         m_clearItemsWhileSyncing = false;
    321         m_syncScheduled = false;
    322     }
    323 
    324     sync(clearItems, items);
    325 
    326     // The following is balanced by the call to disableSuddenTermination in the
    327     // syncTimerFired function.
    328     enableSuddenTermination();
    329 }
    330 
    331 } // namespace WebCore
    332 
    333 #endif // ENABLE(DOM_STORAGE)
    334