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

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

Powered by Google App Engine
This is Rietveld 408576698