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

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, 7 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"
Scott Hess - ex-Googler 2016/05/02 20:36:18 Don't think you're using this anywhere.
bburns 2016/05/02 21:44:18 Done.
19 #include "sql/statement.h"
20 #include "sql/transaction.h"
21
22 namespace offline_pages {
23
24 namespace {
25
26 // This is a macro instead of a const so that
27 // it can be used inline in other SQL statements below.
28 #define OFFLINE_PAGES_TABLE_NAME "offlinepages_v1"
29
30 const char kOfflinePagesColumns[] =
31 "(offline_id INTEGER PRIMARY KEY NOT NULL,"
32 " creation_time INTEGER,"
33 " file_size INTEGER,"
34 " version INTEGER,"
35 " last_access_time INTEGER,"
36 " access_count INTEGER,"
37 " status INTEGER,"
38 " user_initiated INTEGER," // this is actually a boolean
Scott Hess - ex-Googler 2016/05/02 20:36:18 Will this be used later? If the boolean-ness is r
bburns 2016/05/02 21:44:18 Thanks for the explanation. We do anticipate using
Scott Hess - ex-Googler 2016/05/02 23:33:31 Do you intend the same for [status] and [offline_u
bburns 2016/05/03 17:15:55 Done.
39 " client_namespace VARCHAR,"
40 " client_id VARCHAR,"
41 " online_url VARCHAR,"
42 " offline_url VARCHAR,"
43 " file_path VARCHAR"
44 ")";
45
46 // This is cloned from //content/browser/appcache/appcache_database.cc
47 struct TableInfo {
48 const char* table_name;
49 const char* columns;
50 };
51
52 const TableInfo kOfflinePagesTable{OFFLINE_PAGES_TABLE_NAME,
53 kOfflinePagesColumns};
54
55 // This enum is used to define the indices for the columns in each row
56 // that hold the different pieces of offline page.
57 enum : int {
58 OP_OFFLINE_ID = 0,
59 OP_CREATION_TIME,
60 OP_FILE_SIZE,
61 OP_VERSION,
62 OP_LAST_ACCESS_TIME,
63 OP_ACCESS_COUNT,
64 OP_STATUS,
65 OP_USER_INITIATED,
66 OP_CLIENT_NAMESPACE,
67 OP_CLIENT_ID,
68 OP_ONLINE_URL,
69 OP_OFFLINE_URL,
70 OP_FILE_PATH
71 };
72
73 bool CreateTable(sql::Connection* db, const TableInfo& table_info) {
74 std::string sql("CREATE TABLE ");
75 sql += table_info.table_name;
76 sql += table_info.columns;
77 return db->Execute(sql.c_str());
78 }
79
80 bool CreateSchema(sql::Connection* db) {
81 // If you create a transaction but don't Commit() it is automatically
82 // rolled back by its destructor when it falls out of scope.
83 sql::Transaction transaction(db);
84 if (!transaction.Begin())
85 return false;
86 if (db->DoesTableExist(kOfflinePagesTable.table_name)) {
Scott Hess - ex-Googler 2016/05/02 20:36:18 Either provide optional {}, or don't, but don't be
bburns 2016/05/02 21:44:18 Ah, you're running up against my personal preferen
Scott Hess - ex-Googler 2016/05/02 23:33:31 :-). My theory has always been to just have the {
bburns 2016/05/03 17:15:55 Acknowledged.
87 return true;
88 }
89 if (!CreateTable(db, kOfflinePagesTable))
90 return false;
91
92 // TODO(bburns): Add indices here.
93 return transaction.Commit();
94 }
95
96 bool DeleteByOfflineId(sql::Connection* db, int64_t offline_id) {
97 static const char kSql[] =
98 "DELETE FROM " OFFLINE_PAGES_TABLE_NAME " WHERE offline_id=?";
99 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
100 statement.BindInt64(0, offline_id);
101 return statement.Run();
102 }
103
104 // Create an offline page item from a SQL result. Expects complete rows with
105 // all columns present.
106 OfflinePageItem MakeOfflinePageItem(sql::Statement* statement) {
107 int64_t id = statement->ColumnInt64(OP_OFFLINE_ID);
108 GURL url(statement->ColumnString(OP_ONLINE_URL));
109 ClientId client_id(statement->ColumnString(OP_CLIENT_NAMESPACE),
110 statement->ColumnString(OP_CLIENT_ID));
111 #if defined(OS_POSIX)
112 base::FilePath path(statement->ColumnString(OP_FILE_PATH));
113 #elif defined(OS_WIN)
114 base::FilePath path(base::UTF8ToWide(statement->ColumnString(OP_FILE_PATH)));
115 #endif
116 int64_t file_size = statement->ColumnInt64(OP_FILE_SIZE);
117 base::Time creation_time =
118 base::Time::FromInternalValue(statement->ColumnInt64(OP_CREATION_TIME));
119
120 OfflinePageItem item(url, id, client_id, path, file_size, creation_time);
121 item.last_access_time = base::Time::FromInternalValue(
122 statement->ColumnInt64(OP_LAST_ACCESS_TIME));
123 item.version = statement->ColumnInt(OP_VERSION);
124 item.access_count = statement->ColumnInt(OP_ACCESS_COUNT);
125 return item;
126 }
127
128 bool InsertOrReplace(sql::Connection* db, const OfflinePageItem& item) {
129 const char kSql[] =
130 "INSERT OR REPLACE INTO " OFFLINE_PAGES_TABLE_NAME
131 " (offline_id, online_url, client_namespace, client_id, file_path, "
132 "file_size, creation_time, last_access_time, version, access_count)"
133 " VALUES "
134 " (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
135
136 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
137 statement.BindInt64(0, item.offline_id);
138 statement.BindString(1, item.url.spec());
139 statement.BindString(2, item.client_id.name_space);
140 statement.BindString(3, item.client_id.id);
141 #if defined(OS_POSIX)
142 std::string path_string = item.file_path.value();
143 #elif defined(OS_WIN)
144 std::string path_string = base::WideToUTF8(item.file_path.value());
Scott Hess - ex-Googler 2016/05/02 20:36:18 Man, I hate this pattern. But I have nothing for
bburns 2016/05/02 21:44:18 Done.
145 #endif
146 statement.BindString(4, path_string);
147 statement.BindInt64(5, item.file_size);
148 statement.BindInt64(6, item.creation_time.ToInternalValue());
149 statement.BindInt64(7, item.last_access_time.ToInternalValue());
150 statement.BindInt(8, item.version);
151 statement.BindInt(9, item.access_count);
152 return statement.Run();
153 }
154
155 bool InitDatabase(sql::Connection* db,
156 base::FilePath path,
157 bool use_in_memory) {
158 db->set_page_size(4096);
159 db->set_cache_size(500);
160 db->set_histogram_tag("OfflinePageMetadata");
Scott Hess - ex-Googler 2016/05/02 20:36:18 Missing histograms.xml change. If you're worried
bburns 2016/05/02 21:44:17 Done.
161 db->set_exclusive_locking();
162
163 if (use_in_memory) {
164 if (!db->OpenInMemory()) {
165 return false;
166 }
Scott Hess - ex-Googler 2016/05/02 20:36:18 Another case to change if you decide to omit optio
bburns 2016/05/02 21:44:18 Done.
167 } else {
168 base::File::Error err;
169 if (!base::CreateDirectoryAndGetError(path.DirName(), &err)) {
170 LOG(ERROR) << "Failed to create offline pages db directory: "
171 << base::File::ErrorToString(err);
172 return false;
173 }
174 if (!db->Open(path)) {
175 LOG(ERROR) << "Failed to open database";
176 return false;
177 }
178 db->Preload();
179 }
180
181 CreateSchema(db);
Scott Hess - ex-Googler 2016/05/02 20:36:18 Why have CreateSchema() return a flag that you're
bburns 2016/05/02 21:44:18 This was an oversight. fixed.
182 return true;
183 }
184
185 } // anonymous namespace
186
187 OfflinePageMetadataStoreSQL::OfflinePageMetadataStoreSQL(
188 scoped_refptr<base::SequencedTaskRunner> background_task_runner,
189 const base::FilePath& path)
190 : background_task_runner_(background_task_runner),
Scott Hess - ex-Googler 2016/05/02 20:36:18 Maybe std::move(). For most structures I wouldn't
bburns 2016/05/02 21:44:18 Done.
191 db_file_path_(path.AppendASCII("OfflinePages.db")),
192 use_in_memory_(false) {}
193
194 OfflinePageMetadataStoreSQL::~OfflinePageMetadataStoreSQL() {
195 if (db_.get() &&
196 !background_task_runner_->DeleteSoon(FROM_HERE, db_.release())) {
197 DLOG(WARNING) << "SQL database will not be deleted.";
198 }
199 }
200
201 void OfflinePageMetadataStoreSQL::LoadSync(
202 sql::Connection* db,
203 const base::FilePath& path,
204 bool in_memory,
205 scoped_refptr<base::SingleThreadTaskRunner> runner,
206 const LoadCallback& callback) {
207 if (!InitDatabase(db, path, in_memory)) {
208 NotifyLoadResult(runner, callback, STORE_INIT_FAILED,
209 std::vector<OfflinePageItem>());
210 return;
211 }
212
213 const char kSql[] = "SELECT * FROM " OFFLINE_PAGES_TABLE_NAME;
214
215 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
216
217 std::vector<OfflinePageItem> result;
218 while (statement.Step()) {
219 result.push_back(MakeOfflinePageItem(&statement));
220 }
221
222 if (statement.Succeeded()) {
223 NotifyLoadResult(runner, callback, LOAD_SUCCEEDED, result);
224 } else {
225 NotifyLoadResult(runner, callback, STORE_LOAD_FAILED,
226 std::vector<OfflinePageItem>());
227 }
228 }
229
230 void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync(
231 const OfflinePageItem& offline_page,
232 sql::Connection* db,
233 scoped_refptr<base::SingleThreadTaskRunner> runner,
234 const UpdateCallback& callback) {
235 DCHECK(db);
Scott Hess - ex-Googler 2016/05/02 20:36:18 This DCHECK is probably redundant. If you feel th
bburns 2016/05/02 21:44:17 Done. (this DCHECK was requested by a different r
Scott Hess - ex-Googler 2016/05/02 23:33:31 I'm fine with the DCHECK (others might not be), mo
bburns 2016/05/03 17:15:55 Acknowledged.
236 // TODO(bburns): add UMA metrics here (and for levelDB).
237 bool ok = InsertOrReplace(db, offline_page);
238 runner->PostTask(FROM_HERE, base::Bind(callback, ok));
239 }
240
241 void OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync(
242 const std::vector<int64_t>& offline_ids,
243 sql::Connection* db,
244 scoped_refptr<base::SingleThreadTaskRunner> runner,
245 const UpdateCallback& callback) {
246 DCHECK(db);
Scott Hess - ex-Googler 2016/05/02 20:36:18 Likewise here, I think.
bburns 2016/05/02 21:44:18 Done.
247 // TODO(bburns): add UMA metrics here (and for levelDB).
248
249 // If you create a transaction but don't Commit() it is automatically
250 // rolled back by its destructor when it falls out of scope.
251 sql::Transaction transaction(db);
252 if (!transaction.Begin()) {
253 runner->PostTask(FROM_HERE, base::Bind(callback, false));
254 return;
255 }
256 for (auto offline_id : offline_ids) {
257 if (!DeleteByOfflineId(db, offline_id)) {
258 runner->PostTask(FROM_HERE, base::Bind(callback, false));
259 return;
260 }
261 }
262
263 bool success = transaction.Commit();
264 runner->PostTask(FROM_HERE, base::Bind(callback, success));
265 }
266
267 void OfflinePageMetadataStoreSQL::ResetSync(
268 sql::Connection* db,
Scott Hess - ex-Googler 2016/05/02 20:36:18 This is taking ownership. If the delete fails, th
bburns 2016/05/02 21:44:18 It turns out unique_ptr and base::Bind don't get a
Scott Hess - ex-Googler 2016/05/02 23:33:31 It's a little funky, you use base::Passed(&db_) to
bburns 2016/05/03 17:15:55 Done.
269 scoped_refptr<base::SingleThreadTaskRunner> runner,
270 const ResetCallback& callback) {
271 const char kSql[] = "DELETE * FROM " OFFLINE_PAGES_TABLE_NAME;
272 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
273 if (!statement.Run()) {
274 runner->PostTask(FROM_HERE, base::Bind(callback, false));
275 return;
276 }
277 delete db;
278
279 runner->PostTask(FROM_HERE, base::Bind(callback, true));
280 }
281
282 void OfflinePageMetadataStoreSQL::NotifyLoadResult(
283 scoped_refptr<base::SingleThreadTaskRunner> runner,
284 const LoadCallback& callback,
285 LoadStatus status,
286 const std::vector<OfflinePageItem>& result) {
287 // TODO(bburns): Switch to SQL specific UMA metrics.
288 UMA_HISTOGRAM_ENUMERATION("OfflinePages.LoadStatus", status,
289 OfflinePageMetadataStore::LOAD_STATUS_COUNT);
290 if (status == LOAD_SUCCEEDED) {
291 UMA_HISTOGRAM_COUNTS("OfflinePages.SavedPageCount", result.size());
292 } else {
293 DVLOG(1) << "Offline pages database loading failed: " << status;
294 }
295 runner->PostTask(FROM_HERE, base::Bind(callback, status, result));
296 }
297
298 void OfflinePageMetadataStoreSQL::Load(const LoadCallback& callback) {
299 db_.reset(new sql::Connection());
300 background_task_runner_->PostTask(
301 FROM_HERE, base::Bind(&OfflinePageMetadataStoreSQL::LoadSync, db_.get(),
302 db_file_path_, use_in_memory_,
303 base::ThreadTaskRunnerHandle::Get(), callback));
304 }
305
306 void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePage(
307 const OfflinePageItem& offline_page,
308 const UpdateCallback& callback) {
309 background_task_runner_->PostTask(
310 FROM_HERE,
311 base::Bind(&OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync,
312 offline_page, db_.get(), base::ThreadTaskRunnerHandle::Get(),
313 callback));
314 }
315
316 void OfflinePageMetadataStoreSQL::RemoveOfflinePages(
317 const std::vector<int64_t>& offline_ids,
318 const UpdateCallback& callback) {
319 if (offline_ids.empty()) {
320 // Nothing to do, but post a callback instead of calling directly
321 // to preserve the async style behavior to prevent bugs.
322 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
323 base::Bind(callback, true));
324 return;
325 }
326
327 background_task_runner_->PostTask(
328 FROM_HERE,
329 base::Bind(&OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync,
330 offline_ids, db_.get(), base::ThreadTaskRunnerHandle::Get(),
331 callback));
332 }
333
334 void OfflinePageMetadataStoreSQL::Reset(const ResetCallback& callback) {
335 background_task_runner_->PostTask(
336 FROM_HERE,
337 base::Bind(&OfflinePageMetadataStoreSQL::ResetSync, db_.release(),
338 base::ThreadTaskRunnerHandle::Get(), callback));
339 }
340
341 void OfflinePageMetadataStoreSQL::SetInMemoryDatabaseForTesting(
342 bool in_memory) {
343 // Must be called prior to Load(...)
344 DCHECK(db_.get() == nullptr);
345
346 use_in_memory_ = in_memory;
347 }
348
349 } // namespace offline_pages
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698