Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "mozilla/dom/cache/DBAction.h"
8 :
9 : #include "mozilla/dom/cache/Connection.h"
10 : #include "mozilla/dom/cache/DBSchema.h"
11 : #include "mozilla/dom/cache/FileUtils.h"
12 : #include "mozilla/dom/quota/PersistenceType.h"
13 : #include "mozilla/net/nsFileProtocolHandler.h"
14 : #include "mozIStorageConnection.h"
15 : #include "mozIStorageService.h"
16 : #include "mozStorageCID.h"
17 : #include "nsIFile.h"
18 : #include "nsIURI.h"
19 : #include "nsIFileURL.h"
20 : #include "nsThreadUtils.h"
21 :
22 : namespace mozilla {
23 : namespace dom {
24 : namespace cache {
25 :
26 : using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
27 : using mozilla::dom::quota::PersistenceType;
28 :
29 0 : DBAction::DBAction(Mode aMode)
30 0 : : mMode(aMode)
31 : {
32 0 : }
33 :
34 0 : DBAction::~DBAction()
35 : {
36 0 : }
37 :
38 : void
39 0 : DBAction::RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo,
40 : Data* aOptionalData)
41 : {
42 0 : MOZ_ASSERT(!NS_IsMainThread());
43 0 : MOZ_DIAGNOSTIC_ASSERT(aResolver);
44 0 : MOZ_DIAGNOSTIC_ASSERT(aQuotaInfo.mDir);
45 :
46 0 : if (IsCanceled()) {
47 0 : aResolver->Resolve(NS_ERROR_ABORT);
48 0 : return;
49 : }
50 :
51 0 : nsCOMPtr<nsIFile> dbDir;
52 0 : nsresult rv = aQuotaInfo.mDir->Clone(getter_AddRefs(dbDir));
53 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
54 0 : aResolver->Resolve(rv);
55 0 : return;
56 : }
57 :
58 0 : rv = dbDir->Append(NS_LITERAL_STRING("cache"));
59 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
60 0 : aResolver->Resolve(rv);
61 0 : return;
62 : }
63 :
64 0 : nsCOMPtr<mozIStorageConnection> conn;
65 :
66 : // Attempt to reuse the connection opened by a previous Action.
67 0 : if (aOptionalData) {
68 0 : conn = aOptionalData->GetConnection();
69 : }
70 :
71 : // If there is no previous Action, then we must open one.
72 0 : if (!conn) {
73 0 : rv = OpenConnection(aQuotaInfo, dbDir, getter_AddRefs(conn));
74 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
75 0 : aResolver->Resolve(rv);
76 0 : return;
77 : }
78 0 : MOZ_DIAGNOSTIC_ASSERT(conn);
79 :
80 : // Save this connection in the shared Data object so later Actions can
81 : // use it. This avoids opening a new connection for every Action.
82 0 : if (aOptionalData) {
83 : // Since we know this connection will be around for as long as the
84 : // Cache is open, use our special wrapped connection class. This
85 : // will let us perform certain operations once the Cache origin
86 : // is closed.
87 0 : nsCOMPtr<mozIStorageConnection> wrapped = new Connection(conn);
88 0 : aOptionalData->SetConnection(wrapped);
89 : }
90 : }
91 :
92 0 : RunWithDBOnTarget(aResolver, aQuotaInfo, dbDir, conn);
93 : }
94 :
95 : nsresult
96 0 : DBAction::OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
97 : mozIStorageConnection** aConnOut)
98 : {
99 0 : MOZ_ASSERT(!NS_IsMainThread());
100 0 : MOZ_DIAGNOSTIC_ASSERT(aDBDir);
101 0 : MOZ_DIAGNOSTIC_ASSERT(aConnOut);
102 :
103 0 : nsCOMPtr<mozIStorageConnection> conn;
104 :
105 : bool exists;
106 0 : nsresult rv = aDBDir->Exists(&exists);
107 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
108 :
109 0 : if (!exists) {
110 0 : if (NS_WARN_IF(mMode != Create)) { return NS_ERROR_FILE_NOT_FOUND; }
111 0 : rv = aDBDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
112 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
113 : }
114 :
115 0 : nsCOMPtr<nsIFile> dbFile;
116 0 : rv = aDBDir->Clone(getter_AddRefs(dbFile));
117 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
118 :
119 0 : rv = dbFile->Append(NS_LITERAL_STRING("caches.sqlite"));
120 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
121 :
122 0 : rv = dbFile->Exists(&exists);
123 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
124 :
125 : // Use our default file:// protocol handler directly to construct the database
126 : // URL. This avoids any problems if a plugin registers a custom file://
127 : // handler. If such a custom handler used javascript, then we would have a
128 : // bad time running off the main thread here.
129 0 : RefPtr<nsFileProtocolHandler> handler = new nsFileProtocolHandler();
130 0 : rv = handler->Init();
131 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
132 :
133 0 : nsCOMPtr<nsIURI> uri;
134 0 : rv = handler->NewFileURI(dbFile, getter_AddRefs(uri));
135 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
136 :
137 0 : nsCOMPtr<nsIFileURL> dbFileUrl = do_QueryInterface(uri);
138 0 : if (NS_WARN_IF(!dbFileUrl)) { return NS_ERROR_UNEXPECTED; }
139 :
140 0 : nsAutoCString type;
141 0 : PersistenceTypeToText(PERSISTENCE_TYPE_DEFAULT, type);
142 :
143 0 : rv = dbFileUrl->SetQuery(
144 0 : NS_LITERAL_CSTRING("persistenceType=") + type +
145 0 : NS_LITERAL_CSTRING("&group=") + aQuotaInfo.mGroup +
146 0 : NS_LITERAL_CSTRING("&origin=") + aQuotaInfo.mOrigin +
147 0 : NS_LITERAL_CSTRING("&cache=private"));
148 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
149 :
150 : nsCOMPtr<mozIStorageService> ss =
151 0 : do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
152 0 : if (NS_WARN_IF(!ss)) { return NS_ERROR_UNEXPECTED; }
153 :
154 0 : rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
155 0 : if (rv == NS_ERROR_FILE_CORRUPTED) {
156 0 : NS_WARNING("Cache database corrupted. Recreating empty database.");
157 :
158 0 : conn = nullptr;
159 :
160 : // There is nothing else we can do to recover. Also, this data can
161 : // be deleted by QuotaManager at any time anyways.
162 0 : rv = WipeDatabase(aQuotaInfo, dbFile, aDBDir);
163 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
164 :
165 0 : rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
166 : }
167 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
168 :
169 : // Check the schema to make sure it is not too old.
170 0 : int32_t schemaVersion = 0;
171 0 : rv = conn->GetSchemaVersion(&schemaVersion);
172 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
173 0 : if (schemaVersion > 0 && schemaVersion < db::kFirstShippedSchemaVersion) {
174 0 : conn = nullptr;
175 0 : rv = WipeDatabase(aQuotaInfo, dbFile, aDBDir);
176 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
177 :
178 0 : rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
179 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
180 : }
181 :
182 0 : rv = db::InitializeConnection(conn);
183 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
184 :
185 0 : conn.forget(aConnOut);
186 :
187 0 : return rv;
188 : }
189 :
190 : nsresult
191 0 : DBAction::WipeDatabase(const QuotaInfo& aQuotaInfo, nsIFile* aDBFile,
192 : nsIFile* aDBDir)
193 : {
194 0 : MOZ_DIAGNOSTIC_ASSERT(aDBFile);
195 0 : MOZ_DIAGNOSTIC_ASSERT(aDBDir);
196 :
197 0 : nsresult rv = RemoveNsIFile(aQuotaInfo, aDBFile);
198 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
199 :
200 : // Note, the -wal journal file will be automatically deleted by sqlite when
201 : // the new database is created. No need to explicitly delete it here.
202 :
203 : // Delete the morgue as well.
204 0 : rv = BodyDeleteDir(aQuotaInfo, aDBDir);
205 0 : if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
206 :
207 0 : return rv;
208 : }
209 :
210 0 : SyncDBAction::SyncDBAction(Mode aMode)
211 0 : : DBAction(aMode)
212 : {
213 0 : }
214 :
215 0 : SyncDBAction::~SyncDBAction()
216 : {
217 0 : }
218 :
219 : void
220 0 : SyncDBAction::RunWithDBOnTarget(Resolver* aResolver,
221 : const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
222 : mozIStorageConnection* aConn)
223 : {
224 0 : MOZ_ASSERT(!NS_IsMainThread());
225 0 : MOZ_DIAGNOSTIC_ASSERT(aResolver);
226 0 : MOZ_DIAGNOSTIC_ASSERT(aDBDir);
227 0 : MOZ_DIAGNOSTIC_ASSERT(aConn);
228 :
229 0 : nsresult rv = RunSyncWithDBOnTarget(aQuotaInfo, aDBDir, aConn);
230 0 : aResolver->Resolve(rv);
231 0 : }
232 :
233 : } // namespace cache
234 : } // namespace dom
235 : } // namespace mozilla
|