Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(141)

Side by Side Diff: components/offline_pages/offline_page_metadata_store_sql.cc

Issue 1834563002: initial add of SQL based storage (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: address comments. Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/offline_pages/offline_page_metadata_store_sql.h"
6
7 #include "base/bind.h"
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/location.h"
11 #include "base/logging.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/sequenced_task_runner.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "components/offline_pages/offline_page_item.h"
17 #include "sql/connection.h"
18 #include "sql/error_delegate_util.h"
19 #include "sql/meta_table.h"
20 #include "sql/statement.h"
21 #include "sql/transaction.h"
22
23 namespace offline_pages {
24
25 namespace {
26
27 const int kCurrentVersion = 1;
28 const int kCompatibleVersion = 1;
29
30 // This is a macro instead of a const so that
31 // it can be used inline in other SQL statements below.
32 #define OFFLINE_PAGES_TABLE_NAME "offlinepages"
33
34 const char kOfflinePagesColumns[] =
35 "(offline_id INTEGER PRIMARY KEY NOT NULL,"
36 " creation_time INTEGER,"
37 " file_size INTEGER,"
38 " version INTEGER,"
39 " last_access_time INTEGER,"
40 " access_count INTEGER,"
41 " status INTEGER,"
42 " user_initiated INTEGER," // this is actually a boolean
43 " client_namespace VARCHAR,"
44 " client_id VARCHAR,"
45 " online_url VARCHAR,"
46 " offline_url VARCHAR,"
47 " file_path VARCHAR"
48 ")";
49
50 // This is cloned from //content/browser/appcache/appcache_database.cc
51 struct TableInfo {
52 const char* table_name;
53 const char* columns;
54 };
55
56 const TableInfo kOfflinePagesTable{OFFLINE_PAGES_TABLE_NAME,
57 kOfflinePagesColumns};
58
59 // This enum is used to define the indices for the columns in each row
60 // that hold the different pieces of offline page.
61 enum : int {
62 OP_OFFLINE_ID = 0,
63 OP_CREATION_TIME,
64 OP_FILE_SIZE,
65 OP_VERSION,
66 OP_LAST_ACCESS_TIME,
67 OP_ACCESS_COUNT,
68 OP_STATUS,
69 OP_USER_INITIATED,
70 OP_CLIENT_NAMESPACE,
71 OP_CLIENT_ID,
72 OP_ONLINE_URL,
73 OP_OFFLINE_URL,
74 OP_FILE_PATH
75 };
76
77 bool CreateTable(sql::Connection* db, const TableInfo& table_info) {
78 std::string sql("CREATE TABLE ");
79 sql += table_info.table_name;
80 sql += table_info.columns;
81 return db->Execute(sql.c_str());
82 }
83
84 bool CreateSchema(sql::MetaTable* meta_table, sql::Connection* db) {
85 // If you create a transaction but don't Commit() it is automatically
86 // rolled back by its destructor when it falls out of scope.
87 sql::Transaction transaction(db);
88 if (!transaction.Begin())
89 return false;
90
91 if (!meta_table->Init(db, kCurrentVersion, kCompatibleVersion))
92 return false;
93
94 if (!CreateTable(db, kOfflinePagesTable))
95 return false;
96
97 // TODO(bburns): Add indices here.
98 return transaction.Commit();
99 }
100
101 bool DeleteByOfflineId(sql::Connection* db, int64_t offline_id) {
102 const char kSql[] =
103 "DELETE FROM " OFFLINE_PAGES_TABLE_NAME " WHERE offline_id=?";
104 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
105 statement.BindInt64(0, offline_id);
106 return statement.Run();
107 }
108
109 // Create an offline page item from a SQL result. Expects complete rows with
110 // all columns present.
111 OfflinePageItem MakeOfflinePageItem(sql::Statement* statement) {
112 int64_t id = statement->ColumnInt64(OP_OFFLINE_ID);
113 GURL url(statement->ColumnString(OP_ONLINE_URL));
114 ClientId client_id(statement->ColumnString(OP_CLIENT_NAMESPACE),
115 statement->ColumnString(OP_CLIENT_ID));
116 #if defined(OS_POSIX)
117 base::FilePath path(statement->ColumnString(OP_FILE_PATH));
118 #elif defined(OS_WIN)
119 base::FilePath path(base::UTF8ToWide(statement->ColumnString(OP_FILE_PATH)));
120 #endif
121 int64_t file_size = statement->ColumnInt64(OP_FILE_SIZE);
122 base::Time creation_time =
123 base::Time::FromInternalValue(statement->ColumnInt64(OP_CREATION_TIME));
124
125 OfflinePageItem item(url, id, client_id, path, file_size, creation_time);
126 item.last_access_time = base::Time::FromInternalValue(
127 statement->ColumnInt64(OP_LAST_ACCESS_TIME));
128 item.version = statement->ColumnInt(OP_VERSION);
129 item.access_count = statement->ColumnInt(OP_ACCESS_COUNT);
130 return item;
131 }
132
133 bool InsertOrReplace(sql::Connection* db, const OfflinePageItem& item) {
134 const char kSql[] =
135 "INSERT OR REPLACE INTO " OFFLINE_PAGES_TABLE_NAME
136 " (offline_id, online_url, client_namespace, client_id, file_path, "
137 "file_size, creation_time, last_access_time, version, access_count)"
138 " VALUES "
139 " (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
140
141 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
142 statement.BindInt64(0, item.offline_id);
143 statement.BindString(1, item.url.spec());
144 statement.BindString(2, item.client_id.name_space);
145 statement.BindString(3, item.client_id.id);
146 #if defined(OS_POSIX)
147 std::string path_string = item.file_path.value();
148 #elif defined(OS_WIN)
149 std::string path_string = base::WideToUTF8(item.file_path.value());
150 #endif
151 statement.BindString(4, path_string);
152 statement.BindInt64(5, item.file_size);
153 statement.BindInt64(6, item.creation_time.ToInternalValue());
154 statement.BindInt64(7, item.last_access_time.ToInternalValue());
155 statement.BindInt(8, item.version);
156 statement.BindInt(9, item.access_count);
157 return statement.Run();
158 }
159
160 } // anonymous namespace
161
162 OfflinePageMetadataStoreSQL::OfflinePageMetadataStoreSQL(
163 scoped_refptr<base::SequencedTaskRunner> background_task_runner,
164 const base::FilePath& path)
165 : background_task_runner_(background_task_runner),
166 db_file_path_(path.AppendASCII("OfflinePages.db")),
167 use_in_memory_(false),
168 weak_ptr_factory_(this) {}
169
170 OfflinePageMetadataStoreSQL::~OfflinePageMetadataStoreSQL() {
171 if (db_.get() &&
172 !background_task_runner_->DeleteSoon(FROM_HERE, db_.release())) {
173 DLOG(WARNING) << "SQL database will not be deleted.";
174 }
175 }
176
177 void OfflinePageMetadataStoreSQL::LoadSync(
178 scoped_refptr<base::SingleThreadTaskRunner> runner,
179 const LoadCallback& callback) {
180 bool opened = false;
181 db_.reset(new sql::Connection);
182 std::unique_ptr<sql::MetaTable> meta_table(new sql::MetaTable());
183
184 if (use_in_memory_) {
185 opened = db_->OpenInMemory();
186 } else {
187 base::File::Error err;
188 if (!base::CreateDirectoryAndGetError(db_file_path_.DirName(), &err)) {
189 LOG(ERROR) << "Failed to create offline pages db directory: "
190 << base::File::ErrorToString(err);
191 } else {
192 opened = db_->Open(db_file_path_);
193 if (opened)
194 db_->Preload();
195 }
196 }
197 if (!opened) {
198 LOG(ERROR) << "Failed to open database";
199 NotifyLoadResult(runner, callback, STORE_INIT_FAILED,
200 std::vector<OfflinePageItem>());
201 return;
202 }
203
204 if (!sql::MetaTable::DoesTableExist(db_.get())) {
Scott Hess - ex-Googler 2016/04/25 19:57:55 The design doc suggestions that you'd use table_na
bburns 2016/04/26 23:27:02 Ah, this was copy/pasted before I really knew what
205 if (!CreateSchema(meta_table.get(), db_.get())) {
206 LOG(ERROR) << "Failed to create schema";
207 NotifyLoadResult(runner, callback, STORE_INIT_FAILED,
208 std::vector<OfflinePageItem>());
209 return;
210 }
211 } else if (!meta_table->Init(db_.get(), kCurrentVersion,
212 kCompatibleVersion)) {
213 LOG(ERROR) << "Failed to initialize database";
214 NotifyLoadResult(runner, callback, STORE_INIT_FAILED,
215 std::vector<OfflinePageItem>());
216 return;
217 }
218
219 if (meta_table->GetCompatibleVersionNumber() > kCurrentVersion) {
220 LOG(WARNING) << "Offline database is too new.";
221 NotifyLoadResult(runner, callback, STORE_INIT_FAILED,
222 std::vector<OfflinePageItem>());
223 return;
224 }
225
226 const char kSql[] = "SELECT * FROM " OFFLINE_PAGES_TABLE_NAME;
227
228 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
229
230 std::vector<OfflinePageItem> result;
231 while (statement.Step()) {
232 result.push_back(MakeOfflinePageItem(&statement));
233 }
234
235 if (statement.Succeeded()) {
236 NotifyLoadResult(runner, callback, LOAD_SUCCEEDED, result);
237 } else {
238 NotifyLoadResult(runner, callback, STORE_LOAD_FAILED,
239 std::vector<OfflinePageItem>());
240 }
241 }
242
243 void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync(
244 const OfflinePageItem& offline_page,
245 scoped_refptr<base::SingleThreadTaskRunner> runner,
246 const UpdateCallback& callback) {
247 DCHECK(db_.get());
248 // TODO(bburns): add UMA metrics here (and for levelDB).
249 bool ok = InsertOrReplace(db_.get(), offline_page);
250 runner->PostTask(FROM_HERE, base::Bind(callback, ok));
251 }
252
253 void OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync(
254 const std::vector<int64_t>& offline_ids,
255 scoped_refptr<base::SingleThreadTaskRunner> runner,
256 const UpdateCallback& callback) {
257 DCHECK(db_.get());
258 // TODO(bburns): add UMA metrics here (and for levelDB).
259
260 // If you create a transaction but don't Commit() it is automatically
261 // rolled back by its destructor when it falls out of scope.
262 sql::Transaction transaction(db_.get());
263 if (!transaction.Begin()) {
264 runner->PostTask(FROM_HERE, base::Bind(callback, false));
265 return;
266 }
267 for (auto offline_id : offline_ids) {
268 if (!DeleteByOfflineId(db_.get(), offline_id)) {
269 runner->PostTask(FROM_HERE, base::Bind(callback, false));
270 return;
271 }
272 }
273
274 bool success = transaction.Commit();
275 runner->PostTask(FROM_HERE, base::Bind(callback, success));
276 }
277
278 void OfflinePageMetadataStoreSQL::ResetSync(
279 scoped_refptr<base::SingleThreadTaskRunner> runner,
280 const ResetCallback& callback) {
281 const char kSql[] = "DELETE * FROM " OFFLINE_PAGES_TABLE_NAME;
282 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
283 if (!statement.Run()) {
284 runner->PostTask(FROM_HERE, base::Bind(callback, false));
285 return;
286 }
287 db_.reset();
288 weak_ptr_factory_.InvalidateWeakPtrs();
289
290 runner->PostTask(FROM_HERE, base::Bind(callback, true));
291 }
292
293 void OfflinePageMetadataStoreSQL::NotifyLoadResult(
294 scoped_refptr<base::SingleThreadTaskRunner> runner,
295 const LoadCallback& callback,
296 LoadStatus status,
297 const std::vector<OfflinePageItem>& result) {
298 // TODO(bburns): Switch to SQL specific UMA metrics.
299 UMA_HISTOGRAM_ENUMERATION("OfflinePages.LoadStatus", status,
300 OfflinePageMetadataStore::LOAD_STATUS_COUNT);
301 if (status == LOAD_SUCCEEDED) {
302 UMA_HISTOGRAM_COUNTS("OfflinePages.SavedPageCount", result.size());
303 } else {
304 DVLOG(1) << "Offline pages database loading failed: " << status;
305 db_.reset();
306 }
307 runner->PostTask(FROM_HERE, base::Bind(callback, status, result));
308 }
Scott Hess - ex-Googler 2016/04/25 19:57:55 AFAICT, the only reason LoadSync() needs the runne
bburns 2016/04/26 23:27:02 Mostly done. I retained NotifyLoadResult since th
309
310 void OfflinePageMetadataStoreSQL::Load(const LoadCallback& callback) {
311 background_task_runner_->PostTask(
312 FROM_HERE, base::Bind(&OfflinePageMetadataStoreSQL::LoadSync,
313 weak_ptr_factory_.GetWeakPtr(),
314 base::ThreadTaskRunnerHandle::Get(), callback));
315 }
316
317 void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePage(
318 const OfflinePageItem& offline_page,
319 const UpdateCallback& callback) {
320 background_task_runner_->PostTask(
321 FROM_HERE,
322 base::Bind(&OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync,
323 weak_ptr_factory_.GetWeakPtr(), offline_page,
324 base::ThreadTaskRunnerHandle::Get(), callback));
325 }
326
327 void OfflinePageMetadataStoreSQL::RemoveOfflinePages(
328 const std::vector<int64_t>& offline_ids,
329 const UpdateCallback& callback) {
330 if (offline_ids.empty()) {
331 // Nothing to do, but post a callback instead of calling directly
332 // to preserve the async style behavior to prevent bugs.
333 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
334 base::Bind(callback, true));
335 return;
336 }
337
338 background_task_runner_->PostTask(
339 FROM_HERE,
340 base::Bind(&OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync,
341 weak_ptr_factory_.GetWeakPtr(), offline_ids,
342 base::ThreadTaskRunnerHandle::Get(), callback));
343 }
344
345 void OfflinePageMetadataStoreSQL::Reset(const ResetCallback& callback) {
346 background_task_runner_->PostTask(
347 FROM_HERE, base::Bind(&OfflinePageMetadataStoreSQL::ResetSync,
348 weak_ptr_factory_.GetWeakPtr(),
349 base::ThreadTaskRunnerHandle::Get(), callback));
350 }
351
352 void OfflinePageMetadataStoreSQL::SetInMemoryDatabaseForTesting(
353 bool in_memory) {
354 // Must be called prior to Load(...)
355 DCHECK(db_.get() == NULL);
356
357 use_in_memory_ = in_memory;
358 }
359
360 } // namespace offline_pages
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698