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

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: fixed tests 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/metrics/histogram_macros.h"
12 #include "base/sequenced_task_runner.h"
13 #include "base/thread_task_runner_handle.h"
14 #include "components/offline_pages/offline_page_item.h"
15 #include "sql/connection.h"
16 #include "sql/error_delegate_util.h"
17 #include "sql/meta_table.h"
18 #include "sql/statement.h"
19 #include "sql/transaction.h"
20
21 namespace offline_pages {
22
23 namespace {
24
25 const int kCurrentVersion = 1;
26 const int kCompatibleVersion = 1;
27
28 // This is a #define instead of a const so that
jianli 2016/03/31 23:40:54 nit: #define => macro
bburns 2016/04/01 17:12:56 Done.
29 // it can be used inline in other SQL statements below.
30 #define kOfflinePagesTableName "offlinepages"
jianli 2016/03/31 23:40:54 Add an empty line to separate from other constants
bburns 2016/04/01 17:12:56 Done.
31 const char kOfflinePagesColumns[] =
32 "(offline_id INTEGER NOT NULL,"
33 " client_namespace VARCHAR(256),"
34 " client_id VARCHAR(256),"
35 " online_url VARCHAR(2048),"
36 " offline_url VARCHAR(2048),"
37 " version INTEGER,"
38 " creation_time INTEGER,"
39 " file_path VARCHAR(1024),"
40 " file_size INTEGER,"
41 " last_access_time INTEGER,"
42 " access_count INTEGER,"
43 " status INTEGER,"
44 " user_initiated BOOLEAN,"
45 " UNIQUE(offline_id))";
46
47 // This is cloned from //content/browser/appcache/appcache_database.cc
48 struct TableInfo {
49 const char* table_name;
50 const char* columns;
51 };
52
53 const TableInfo kOfflinePagesTable{kOfflinePagesTableName,
54 kOfflinePagesColumns};
55
56 enum : int {
jianli 2016/03/31 23:40:54 Please comment on this enum.
bburns 2016/04/01 17:12:56 Done.
57 OP_OFFLINE_ID = 0,
58 OP_CLIENT_NAMESPACE,
59 OP_CLIENT_ID,
60 OP_ONLINE_URL,
61 OP_OFFLINE_URL,
62 OP_VERSION,
63 OP_CREATION_TIME,
64 OP_FILE_PATH,
65 OP_FILE_SIZE,
66 OP_LAST_ACCESS_TIME,
67 OP_ACCESS_COUNT,
68 OP_STATUS,
69 OP_USER_INITIATED
70 };
71
72 bool CreateTable(sql::Connection* db,
73 const char* table_name,
74 const char* columns) {
75 std::string sql("CREATE TABLE ");
76 sql += table_name;
77 sql += columns;
78 return db->Execute(sql.c_str());
79 }
80
81 bool CreateSchema(sql::MetaTable* meta_table, sql::Connection* db) {
82 // If you create a transaction but don't Commit() it is automatically
83 // rolled back by its destructor when it falls out of scope.
84 sql::Transaction transaction(db);
85 if (!transaction.Begin())
86 return false;
87
88 if (!meta_table->Init(db, kCurrentVersion, kCompatibleVersion))
89 return false;
90
91 if (!CreateTable(db, kOfflinePagesTableName, kOfflinePagesColumns))
92 return false;
93
94 // TODO(bburns): indexes here
jianli 2016/03/31 23:40:54 nit: end with period
bburns 2016/04/01 17:12:56 Done.
95 return transaction.Commit();
96 }
97
98 bool DeleteByOfflineId(sql::Connection* db, int64_t offline_id) {
99 const char kSql[] =
100 "DELETE FROM " kOfflinePagesTableName " 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 = base::FilePath(statement->ColumnString(OP_FILE_PATH));
jianli 2016/03/31 23:40:54 Can we use AppendASCII to simply the logic here?
bburns 2016/04/01 17:12:56 Simplified into just a declaration.
115 #elif defined(OS_WIN)
116 base::FilePath path =
117 base::FilePath(base::UTF8ToWide(statement->ColumnString(OP_FILE_PATH)));
118 #endif
119 int64_t file_size = statement->ColumnInt64(OP_FILE_SIZE);
120 base::Time creation_time =
121 base::Time::FromInternalValue(statement->ColumnInt64(OP_CREATION_TIME));
122
123 OfflinePageItem item(url, id, client_id, path, file_size, creation_time);
124 item.last_access_time = base::Time::FromInternalValue(
125 statement->ColumnInt64(OP_LAST_ACCESS_TIME));
126 item.version = statement->ColumnInt64(OP_VERSION);
jianli 2016/03/31 23:40:54 Should this be ColumnInt?
bburns 2016/04/01 17:12:56 Done.
127 item.access_count = statement->ColumnInt64(OP_ACCESS_COUNT);
128 return item;
129 }
130
131 bool InsertOrReplace(sql::Connection* db, const OfflinePageItem& item) {
132 const char kSql[] =
133 "INSERT OR REPLACE INTO " kOfflinePagesTableName
134 " (offline_id, online_url, client_namespace, client_id, file_path, "
135 "file_size, creation_time, last_access_time, version, access_count)"
136 " VALUES "
137 " (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
138
139 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
140 statement.BindInt64(0, item.offline_id);
141 statement.BindString(1, item.url.spec());
142 statement.BindString(2, item.client_id.name_space);
143 statement.BindString(3, item.client_id.id);
144 std::string path_string;
145 #if defined(OS_POSIX)
146 path_string = item.file_path.value();
jianli 2016/03/31 23:40:54 Is it better to call AsUTF8Unsafe?
bburns 2016/04/01 17:12:55 This is a copy of the code in OfflinePageMetadataI
147 #elif defined(OS_WIN)
148 path_string = base::WideToUTF8(item.file_path.value());
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.BindInt64(8, item.version);
jianli 2016/03/31 23:40:55 Should this be int?
bburns 2016/04/01 17:12:56 Done.
155 statement.BindInt64(9, item.access_count);
156 return statement.Run();
157 }
jianli 2016/03/31 23:40:55 nit: add an empty line
bburns 2016/04/01 17:12:56 Done.
158 } // anonymous namespace
159
160 OfflinePageMetadataStoreSQL::OfflinePageMetadataStoreSQL(
161 scoped_refptr<base::SequencedTaskRunner> background_task_runner,
162 const base::FilePath& path)
163 : background_task_runner_(background_task_runner),
164 db_file_path_(path.AppendASCII("OfflinePages.db")),
165 use_in_memory_(false),
166 weak_ptr_factory_(this) {}
167
168 OfflinePageMetadataStoreSQL::~OfflinePageMetadataStoreSQL() {}
jianli 2016/03/31 23:40:54 This is invoked from main thread from which we can
bburns 2016/04/01 17:12:56 why not? It's just an in-memory object...
jianli 2016/04/01 20:57:59 When sql::Connection object is destructed, Connect
bburns 2016/04/01 22:51:44 Done.
169
170 void OfflinePageMetadataStoreSQL::LoadSync(
171 scoped_refptr<base::SingleThreadTaskRunner> runner,
jianli 2016/03/31 23:40:54 Rename runner to foreground_runner to improve read
bburns 2016/04/01 17:12:56 I disagree with this. The code doesn't care which
172 const LoadCallback& callback) {
173 bool opened = false;
174 db_.reset(new sql::Connection);
175 meta_table_.reset(new sql::MetaTable);
176
177 if (use_in_memory_) {
178 opened = db_->OpenInMemory();
179 } else {
180 base::File::Error err;
181 if (!base::CreateDirectoryAndGetError(db_file_path_.DirName(), &err)) {
182 LOG(ERROR) << "Failed to create offline pages db directory: "
183 << base::File::ErrorToString(err);
184 } else {
185 opened = db_->Open(db_file_path_);
186 if (opened)
187 db_->Preload();
188 }
189 }
190 if (!opened) {
191 LOG(ERROR) << "Failed to open database";
192 NotifyLoadResult(runner, callback, STORE_INIT_FAILED,
193 std::vector<OfflinePageItem>());
194 return;
195 }
196
197 if (!sql::MetaTable::DoesTableExist(db_.get())) {
198 if (!CreateSchema(meta_table_.get(), db_.get())) {
199 LOG(ERROR) << "Failed to create schema";
200 NotifyLoadResult(runner, callback, STORE_INIT_FAILED,
201 std::vector<OfflinePageItem>());
202 return;
203 }
204 } else if (!meta_table_->Init(db_.get(), kCurrentVersion,
205 kCompatibleVersion)) {
206 LOG(ERROR) << "Failed to initialize database";
207 NotifyLoadResult(runner, callback, STORE_INIT_FAILED,
208 std::vector<OfflinePageItem>());
209 return;
210 }
211
212 if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) {
213 LOG(WARNING) << "Offline database is too new.";
214 NotifyLoadResult(runner, callback, STORE_INIT_FAILED,
215 std::vector<OfflinePageItem>());
216 return;
217 }
218
219 const char kSql[] = "SELECT * FROM " kOfflinePagesTableName;
220
221 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
222
223 std::vector<OfflinePageItem> result;
224 while (statement.Step()) {
225 result.push_back(MakeOfflinePageItem(&statement));
226 }
227
228 if (!statement.Succeeded()) {
229 NotifyLoadResult(runner, callback, STORE_LOAD_FAILED,
jianli 2016/03/31 23:40:54 nit: swap then part and else part for better reada
bburns 2016/04/01 17:12:56 Done.
230 std::vector<OfflinePageItem>());
231 } else {
232 NotifyLoadResult(runner, callback, LOAD_SUCCEEDED, result);
233 }
234 }
235
236 void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync(
237 const OfflinePageItem& offline_page,
238 scoped_refptr<base::SingleThreadTaskRunner> runner,
239 const UpdateCallback& callback) {
jianli 2016/03/31 23:40:54 Add DCHECK for db_.
bburns 2016/04/01 17:12:56 Done.
240 // TODO(bburns): add UMA metrics here (and for levelDB)
jianli 2016/03/31 23:40:54 nit: end comment with period
bburns 2016/04/01 17:12:56 Done.
241 bool ok = InsertOrReplace(db_.get(), offline_page);
242 runner->PostTask(FROM_HERE, base::Bind(callback, ok));
243 }
244
245 void OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync(
246 const std::vector<int64_t>& offline_ids,
247 scoped_refptr<base::SingleThreadTaskRunner> runner,
248 const UpdateCallback& callback) {
249 // TODO(bburns): add UMA metrics here (and for levelDB)
jianli 2016/03/31 23:40:54 ditto
bburns 2016/04/01 17:12:56 Done.
250
251 // If you create a transaction but don't Commit() it is automatically
252 // rolled back by its destructor when it falls out of scope.
253 sql::Transaction transaction(db_.get());
254 if (!transaction.Begin()) {
255 runner->PostTask(FROM_HERE, base::Bind(callback, false));
jianli 2016/03/31 23:40:54 Do we want to bail out now?
bburns 2016/04/01 17:12:56 Done.
256 }
257 for (auto offline_id : offline_ids) {
258 if (!DeleteByOfflineId(db_.get(), offline_id)) {
259 runner->PostTask(FROM_HERE, base::Bind(callback, false));
260 return;
261 }
262 }
263
264 bool success = transaction.Commit();
265 runner->PostTask(FROM_HERE, base::Bind(callback, success));
266 }
267
268 void OfflinePageMetadataStoreSQL::ResetSync(
269 scoped_refptr<base::SingleThreadTaskRunner> runner,
270 const ResetCallback& callback) {
271 meta_table_.reset(NULL);
272 db_.reset(NULL);
jianli 2016/03/31 23:40:54 We need to wipe out the database.
bburns 2016/04/01 17:12:56 Done.
273 weak_ptr_factory_.InvalidateWeakPtrs();
274
275 runner->PostTask(FROM_HERE, base::Bind(callback, true));
276 }
277
278 void OfflinePageMetadataStoreSQL::NotifyLoadResult(
279 scoped_refptr<base::SingleThreadTaskRunner> runner,
280 const LoadCallback& callback,
281 LoadStatus status,
282 const std::vector<OfflinePageItem>& result) {
283 // TODO(bburns): Switch to SQL specific UMA metrics.
284 UMA_HISTOGRAM_ENUMERATION("OfflinePages.LoadStatus", status,
285 OfflinePageMetadataStore::LOAD_STATUS_COUNT);
286 if (status == LOAD_SUCCEEDED) {
287 UMA_HISTOGRAM_COUNTS("OfflinePages.SavedPageCount", result.size());
288 } else {
289 DVLOG(1) << "Offline pages database loading failed: " << status;
290 db_.reset();
jianli 2016/03/31 23:40:54 Do we also need to reset meta_table_?
bburns 2016/04/01 17:12:56 Done.
291 }
292 runner->PostTask(FROM_HERE, base::Bind(callback, status, result));
293 }
294
295 void OfflinePageMetadataStoreSQL::Load(const LoadCallback& callback) {
296 background_task_runner_->PostTask(
297 FROM_HERE, base::Bind(&OfflinePageMetadataStoreSQL::LoadSync,
298 weak_ptr_factory_.GetWeakPtr(),
299 base::ThreadTaskRunnerHandle::Get(), callback));
300 }
301
302 void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePage(
303 const OfflinePageItem& offline_page,
304 const UpdateCallback& callback) {
305 background_task_runner_->PostTask(
306 FROM_HERE,
307 base::Bind(&OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync,
308 weak_ptr_factory_.GetWeakPtr(), offline_page,
309 base::ThreadTaskRunnerHandle::Get(), callback));
310 }
311
312 void OfflinePageMetadataStoreSQL::RemoveOfflinePages(
313 const std::vector<int64_t>& offline_ids,
314 const UpdateCallback& callback) {
315 if (offline_ids.size() == 0) {
316 // Nothing to do, but post a callback instead of calling directly
317 // to preserve the async style behavior to prevent bugs.
318 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
319 base::Bind(callback, true));
320 return;
321 }
322
323 background_task_runner_->PostTask(
324 FROM_HERE,
325 base::Bind(&OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync,
326 weak_ptr_factory_.GetWeakPtr(), offline_ids,
327 base::ThreadTaskRunnerHandle::Get(), callback));
328 }
329
330 void OfflinePageMetadataStoreSQL::Reset(const ResetCallback& callback) {
331 background_task_runner_->PostTask(
332 FROM_HERE, base::Bind(&OfflinePageMetadataStoreSQL::ResetSync,
333 weak_ptr_factory_.GetWeakPtr(),
334 base::ThreadTaskRunnerHandle::Get(), callback));
335 }
336
337 void OfflinePageMetadataStoreSQL::SetInMemoryDatabaseForTesting(
338 bool in_memory) {
339 // Must be called prior to Load(...)
340 DCHECK(db_.get() == NULL);
341
342 use_in_memory_ = in_memory;
343 }
344
345 } // namespace offline_pages
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698