Remove some iOS #ifdefs by adding SQLiteDatabaseTracker to all the builds
[WebKit-https.git] / Source / WebCore / storage / StorageAreaSync.cpp
1 /*
2  * Copyright (C) 2008, 2009, 2010 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 #include "EventNames.h"
30 #include "FileSystem.h"
31 #include "HTMLElement.h"
32 #include "SQLiteDatabaseTracker.h"
33 #include "SQLiteFileSystem.h"
34 #include "SQLiteStatement.h"
35 #include "SQLiteTransaction.h"
36 #include "SecurityOrigin.h"
37 #include "StorageAreaImpl.h"
38 #include "StorageSyncManager.h"
39 #include "StorageTracker.h"
40 #include "SuddenTermination.h"
41 #include <wtf/Functional.h>
42 #include <wtf/MainThread.h>
43 #include <wtf/text/CString.h>
44
45 namespace WebCore {
46
47 // If the StorageArea undergoes rapid changes, don't sync each change to disk.
48 // Instead, queue up a batch of items to sync and actually do the sync at the following interval.
49 static const double StorageSyncInterval = 1.0;
50
51 // A sane limit on how many items we'll schedule to sync all at once.  This makes it
52 // much harder to starve the rest of LocalStorage and the OS's IO subsystem in general.
53 static const int MaxiumItemsToSync = 100;
54
55 inline StorageAreaSync::StorageAreaSync(PassRefPtr<StorageSyncManager> storageSyncManager, PassRefPtr<StorageAreaImpl> storageArea, const String& databaseIdentifier)
56     : m_syncTimer(this, &StorageAreaSync::syncTimerFired)
57     , m_itemsCleared(false)
58     , m_finalSyncScheduled(false)
59     , m_storageArea(storageArea)
60     , m_syncManager(storageSyncManager)
61     , m_databaseIdentifier(databaseIdentifier.isolatedCopy())
62     , m_clearItemsWhileSyncing(false)
63     , m_syncScheduled(false)
64     , m_syncInProgress(false)
65     , m_databaseOpenFailed(false)
66     , m_syncCloseDatabase(false)
67     , m_importComplete(false)
68 {
69     ASSERT(isMainThread());
70     ASSERT(m_storageArea);
71     ASSERT(m_syncManager);
72
73     // FIXME: If it can't import, then the default WebKit behavior should be that of private browsing,
74     // not silently ignoring it. https://bugs.webkit.org/show_bug.cgi?id=25894
75     m_syncManager->dispatch(bind(&StorageAreaSync::performImport, this));
76 }
77
78 PassRefPtr<StorageAreaSync> StorageAreaSync::create(PassRefPtr<StorageSyncManager> storageSyncManager, PassRefPtr<StorageAreaImpl> storageArea, const String& databaseIdentifier)
79 {
80     RefPtr<StorageAreaSync> area = adoptRef(new StorageAreaSync(storageSyncManager, storageArea, databaseIdentifier));
81
82     return area.release();
83 }
84
85 StorageAreaSync::~StorageAreaSync()
86 {
87     ASSERT(isMainThread());
88     ASSERT(!m_syncTimer.isActive());
89     ASSERT(m_finalSyncScheduled);
90 }
91
92 void StorageAreaSync::scheduleFinalSync()
93 {
94     ASSERT(isMainThread());
95     // FIXME: We do this to avoid races, but it'd be better to make things safe without blocking.
96     blockUntilImportComplete();
97     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.
98
99     if (m_syncTimer.isActive())
100         m_syncTimer.stop();
101     else {
102         // The following is balanced by the call to enableSuddenTermination in the
103         // syncTimerFired function.
104         disableSuddenTermination();
105     }
106     // FIXME: This is synchronous.  We should do it on the background process, but
107     // we should do it safely.
108     m_finalSyncScheduled = true;
109     syncTimerFired(&m_syncTimer);
110
111     m_syncManager->dispatch(bind(&StorageAreaSync::deleteEmptyDatabase, this));
112 }
113
114 void StorageAreaSync::scheduleItemForSync(const String& key, const String& value)
115 {
116     ASSERT(isMainThread());
117     ASSERT(!m_finalSyncScheduled);
118
119     m_changedItems.set(key, value);
120     if (!m_syncTimer.isActive()) {
121         m_syncTimer.startOneShot(StorageSyncInterval);
122
123         // The following is balanced by the call to enableSuddenTermination in the
124         // syncTimerFired function.
125         disableSuddenTermination();
126     }
127 }
128
129 void StorageAreaSync::scheduleClear()
130 {
131     ASSERT(isMainThread());
132     ASSERT(!m_finalSyncScheduled);
133
134     m_changedItems.clear();
135     m_itemsCleared = true;
136     if (!m_syncTimer.isActive()) {
137         m_syncTimer.startOneShot(StorageSyncInterval);
138
139         // The following is balanced by the call to enableSuddenTermination in the
140         // syncTimerFired function.
141         disableSuddenTermination();
142     }
143 }
144
145 void StorageAreaSync::scheduleCloseDatabase()
146 {
147     ASSERT(isMainThread());
148     ASSERT(!m_finalSyncScheduled);
149
150     if (!m_database.isOpen())
151         return;
152
153     m_syncCloseDatabase = true;
154     
155     if (!m_syncTimer.isActive()) {
156         m_syncTimer.startOneShot(StorageSyncInterval);
157         
158         // The following is balanced by the call to enableSuddenTermination in the
159         // syncTimerFired function.
160         disableSuddenTermination();
161     }
162 }
163
164 void StorageAreaSync::syncTimerFired(Timer<StorageAreaSync>*)
165 {
166     ASSERT(isMainThread());
167
168     bool partialSync = false;
169     {
170         MutexLocker locker(m_syncLock);
171
172         // Do not schedule another sync if we're still trying to complete the
173         // previous one.  But, if we're shutting down, schedule it anyway.
174         if (m_syncInProgress && !m_finalSyncScheduled) {
175             ASSERT(!m_syncTimer.isActive());
176             m_syncTimer.startOneShot(StorageSyncInterval);
177             return;
178         }
179
180         if (m_itemsCleared) {
181             m_itemsPendingSync.clear();
182             m_clearItemsWhileSyncing = true;
183             m_itemsCleared = false;
184         }
185
186         HashMap<String, String>::iterator changed_it = m_changedItems.begin();
187         HashMap<String, String>::iterator changed_end = m_changedItems.end();
188         for (int count = 0; changed_it != changed_end; ++count, ++changed_it) {
189             if (count >= MaxiumItemsToSync && !m_finalSyncScheduled) {
190                 partialSync = true;
191                 break;
192             }
193             m_itemsPendingSync.set(changed_it->key.isolatedCopy(), changed_it->value.isolatedCopy());
194         }
195
196         if (partialSync) {
197             // We can't do the fast path of simply clearing all items, so we'll need to manually
198             // remove them one by one.  Done under lock since m_itemsPendingSync is modified by
199             // the background thread.
200             HashMap<String, String>::iterator pending_it = m_itemsPendingSync.begin();
201             HashMap<String, String>::iterator pending_end = m_itemsPendingSync.end();
202             for (; pending_it != pending_end; ++pending_it)
203                 m_changedItems.remove(pending_it->key);
204         }
205
206         if (!m_syncScheduled) {
207             m_syncScheduled = true;
208
209             // The following is balanced by the call to enableSuddenTermination in the
210             // performSync function.
211             disableSuddenTermination();
212
213             m_syncManager->dispatch(bind(&StorageAreaSync::performSync, this));
214         }
215     }
216
217     if (partialSync) {
218         // If we didn't finish syncing, then we need to finish the job later.
219         ASSERT(!m_syncTimer.isActive());
220         m_syncTimer.startOneShot(StorageSyncInterval);
221     } else {
222         // The following is balanced by the calls to disableSuddenTermination in the
223         // scheduleItemForSync, scheduleClear, and scheduleFinalSync functions.
224         enableSuddenTermination();
225
226         m_changedItems.clear();
227     }
228 }
229
230 void StorageAreaSync::openDatabase(OpenDatabaseParamType openingStrategy)
231 {
232     ASSERT(!isMainThread());
233     ASSERT(!m_database.isOpen());
234     ASSERT(!m_databaseOpenFailed);
235
236     SQLiteTransactionInProgressAutoCounter transactionCounter;
237
238     String databaseFilename = m_syncManager->fullDatabaseFilename(m_databaseIdentifier);
239
240     if (!fileExists(databaseFilename) && openingStrategy == SkipIfNonExistent)
241         return;
242
243     if (databaseFilename.isEmpty()) {
244         LOG_ERROR("Filename for local storage database is empty - cannot open for persistent storage");
245         markImported();
246         m_databaseOpenFailed = true;
247         return;
248     }
249
250     // A StorageTracker thread may have been scheduled to delete the db we're
251     // reopening, so cancel possible deletion.
252     StorageTracker::tracker().cancelDeletingOrigin(m_databaseIdentifier);
253
254     if (!m_database.open(databaseFilename)) {
255         LOG_ERROR("Failed to open database file %s for local storage", databaseFilename.utf8().data());
256         markImported();
257         m_databaseOpenFailed = true;
258         return;
259     }
260
261     migrateItemTableIfNeeded();
262
263     if (!m_database.executeCommand("CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB NOT NULL ON CONFLICT FAIL)")) {
264         LOG_ERROR("Failed to create table ItemTable for local storage");
265         markImported();
266         m_databaseOpenFailed = true;
267         return;
268     }
269
270     StorageTracker::tracker().setOriginDetails(m_databaseIdentifier, databaseFilename);
271 }
272
273 void StorageAreaSync::migrateItemTableIfNeeded()
274 {
275     if (!m_database.tableExists("ItemTable"))
276         return;
277
278     {
279         SQLiteStatement query(m_database, "SELECT value FROM ItemTable LIMIT 1");
280         // this query isn't ever executed.
281         if (query.isColumnDeclaredAsBlob(0))
282             return;
283     }
284
285     // alter table for backward compliance, change the value type from TEXT to BLOB.
286     static const char* commands[] = {
287         "DROP TABLE IF EXISTS ItemTable2",
288         "CREATE TABLE ItemTable2 (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB NOT NULL ON CONFLICT FAIL)",
289         "INSERT INTO ItemTable2 SELECT * from ItemTable",
290         "DROP TABLE ItemTable",
291         "ALTER TABLE ItemTable2 RENAME TO ItemTable",
292         0,
293     };
294
295     SQLiteTransaction transaction(m_database, false);
296     transaction.begin();
297     for (size_t i = 0; commands[i]; ++i) {
298         if (!m_database.executeCommand(commands[i])) {
299             LOG_ERROR("Failed to migrate table ItemTable for local storage when executing: %s", commands[i]);
300             transaction.rollback();
301
302             // finally it will try to keep a backup of ItemTable for the future restoration.
303             // NOTICE: this will essentially DELETE the current database, but that's better
304             // than continually hitting this case and never being able to use the local storage.
305             // if this is ever hit, it's definitely a bug.
306             ASSERT_NOT_REACHED();
307             if (!m_database.executeCommand("ALTER TABLE ItemTable RENAME TO Backup_ItemTable"))
308                 LOG_ERROR("Failed to save ItemTable after migration job failed.");
309
310             return;
311         }
312     }
313     transaction.commit();
314 }
315
316 void StorageAreaSync::performImport()
317 {
318     ASSERT(!isMainThread());
319     ASSERT(!m_database.isOpen());
320
321     openDatabase(SkipIfNonExistent);
322     if (!m_database.isOpen()) {
323         markImported();
324         return;
325     }
326
327     SQLiteStatement query(m_database, "SELECT key, value FROM ItemTable");
328     if (query.prepare() != SQLResultOk) {
329         LOG_ERROR("Unable to select items from ItemTable for local storage");
330         markImported();
331         return;
332     }
333
334     HashMap<String, String> itemMap;
335
336     int result = query.step();
337     while (result == SQLResultRow) {
338         itemMap.set(query.getColumnText(0), query.getColumnBlobAsString(1));
339         result = query.step();
340     }
341
342     if (result != SQLResultDone) {
343         LOG_ERROR("Error reading items from ItemTable for local storage");
344         markImported();
345         return;
346     }
347
348     m_storageArea->importItems(itemMap);
349
350     markImported();
351 }
352
353 void StorageAreaSync::markImported()
354 {
355     MutexLocker locker(m_importLock);
356     m_importComplete = true;
357     m_importCondition.signal();
358 }
359
360 // FIXME: In the future, we should allow use of StorageAreas while it's importing (when safe to do so).
361 // Blocking everything until the import is complete is by far the simplest and safest thing to do, but
362 // there is certainly room for safe optimization: Key/length will never be able to make use of such an
363 // optimization (since the order of iteration can change as items are being added). Get can return any
364 // item currently in the map. Get/remove can work whether or not it's in the map, but we'll need a list
365 // of items the import should not overwrite. Clear can also work, but it'll need to kill the import
366 // job first.
367 void StorageAreaSync::blockUntilImportComplete()
368 {
369     ASSERT(isMainThread());
370
371     // Fast path.  We set m_storageArea to 0 only after m_importComplete being true.
372     if (!m_storageArea)
373         return;
374
375     MutexLocker locker(m_importLock);
376     while (!m_importComplete)
377         m_importCondition.wait(m_importLock);
378     m_storageArea = 0;
379 }
380
381 void StorageAreaSync::sync(bool clearItems, const HashMap<String, String>& items)
382 {
383     ASSERT(!isMainThread());
384
385     if (items.isEmpty() && !clearItems && !m_syncCloseDatabase)
386         return;
387     if (m_databaseOpenFailed)
388         return;
389
390     if (!m_database.isOpen() && m_syncCloseDatabase) {
391         m_syncCloseDatabase = false;
392         return;
393     }
394
395     if (!m_database.isOpen())
396         openDatabase(CreateIfNonExistent);
397     if (!m_database.isOpen())
398         return;
399
400     // Closing this db because it is about to be deleted by StorageTracker.
401     // The delete will be cancelled if StorageAreaSync needs to reopen the db
402     // to write new items created after the request to delete the db.
403     if (m_syncCloseDatabase) {
404         m_syncCloseDatabase = false;
405         m_database.close();
406         return;
407     }
408     
409     SQLiteTransactionInProgressAutoCounter transactionCounter;
410
411     // If the clear flag is set, then we clear all items out before we write any new ones in.
412     if (clearItems) {
413         SQLiteStatement clear(m_database, "DELETE FROM ItemTable");
414         if (clear.prepare() != SQLResultOk) {
415             LOG_ERROR("Failed to prepare clear statement - cannot write to local storage database");
416             return;
417         }
418
419         int result = clear.step();
420         if (result != SQLResultDone) {
421             LOG_ERROR("Failed to clear all items in the local storage database - %i", result);
422             return;
423         }
424     }
425
426     SQLiteStatement insert(m_database, "INSERT INTO ItemTable VALUES (?, ?)");
427     if (insert.prepare() != SQLResultOk) {
428         LOG_ERROR("Failed to prepare insert statement - cannot write to local storage database");
429         return;
430     }
431
432     SQLiteStatement remove(m_database, "DELETE FROM ItemTable WHERE key=?");
433     if (remove.prepare() != SQLResultOk) {
434         LOG_ERROR("Failed to prepare delete statement - cannot write to local storage database");
435         return;
436     }
437
438     HashMap<String, String>::const_iterator end = items.end();
439
440     SQLiteTransaction transaction(m_database);
441     transaction.begin();
442     for (HashMap<String, String>::const_iterator it = items.begin(); it != end; ++it) {
443         // Based on the null-ness of the second argument, decide whether this is an insert or a delete.
444         SQLiteStatement& query = it->value.isNull() ? remove : insert;
445
446         query.bindText(1, it->key);
447
448         // If the second argument is non-null, we're doing an insert, so bind it as the value.
449         if (!it->value.isNull())
450             query.bindBlob(2, it->value);
451
452         int result = query.step();
453         if (result != SQLResultDone) {
454             LOG_ERROR("Failed to update item in the local storage database - %i", result);
455             break;
456         }
457
458         query.reset();
459     }
460     transaction.commit();
461 }
462
463 void StorageAreaSync::performSync()
464 {
465     ASSERT(!isMainThread());
466
467     bool clearItems;
468     HashMap<String, String> items;
469     {
470         MutexLocker locker(m_syncLock);
471
472         ASSERT(m_syncScheduled);
473
474         clearItems = m_clearItemsWhileSyncing;
475         m_itemsPendingSync.swap(items);
476
477         m_clearItemsWhileSyncing = false;
478         m_syncScheduled = false;
479         m_syncInProgress = true;
480     }
481
482     sync(clearItems, items);
483
484     {
485         MutexLocker locker(m_syncLock);
486         m_syncInProgress = false;
487     }
488
489     // The following is balanced by the call to disableSuddenTermination in the
490     // syncTimerFired function.
491     enableSuddenTermination();
492 }
493
494 void StorageAreaSync::deleteEmptyDatabase()
495 {
496     ASSERT(!isMainThread());
497     if (!m_database.isOpen())
498         return;
499
500     SQLiteStatement query(m_database, "SELECT COUNT(*) FROM ItemTable");
501     if (query.prepare() != SQLResultOk) {
502         LOG_ERROR("Unable to count number of rows in ItemTable for local storage");
503         return;
504     }
505
506     int result = query.step();
507     if (result != SQLResultRow) {
508         LOG_ERROR("No results when counting number of rows in ItemTable for local storage");
509         return;
510     }
511
512     int count = query.getColumnInt(0);
513     if (!count) {
514         query.finalize();
515         m_database.close();
516         if (StorageTracker::tracker().isActive())
517             StorageTracker::tracker().deleteOriginWithIdentifier(m_databaseIdentifier);
518         else {
519             String databaseFilename = m_syncManager->fullDatabaseFilename(m_databaseIdentifier);
520             if (!SQLiteFileSystem::deleteDatabaseFile(databaseFilename))
521                 LOG_ERROR("Failed to delete database file %s\n", databaseFilename.utf8().data());
522         }
523     }
524 }
525
526 void StorageAreaSync::scheduleSync()
527 {
528     syncTimerFired(&m_syncTimer);
529 }
530
531 } // namespace WebCore