Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 | |
|
fgorski
2016/03/29 06:50:18
nit: sentence case.
bburns
2016/03/30 16:54:40
Done.
| |
| 29 // I can use it inline in other SQL statements below | |
|
fgorski
2016/03/29 06:50:19
so it can be used.
bburns
2016/03/30 16:54:40
Done.
| |
| 30 #define kOfflinePagesTableName "offlinepages" | |
| 31 const char kOfflinePagesColumns[] = | |
| 32 "(offline_id INTEGER PRIMARY_KEY," | |
| 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 | |
| 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{kOfflinePagesTableName, | |
| 53 kOfflinePagesColumns}; | |
| 54 | |
| 55 enum : int { | |
| 56 OP_OFFLINE_ID = 0, | |
| 57 OP_CLIENT_NAMESPACE, | |
| 58 OP_CLIENT_ID, | |
| 59 OP_ONLINE_URL, | |
| 60 OP_OFFLINE_URL, | |
| 61 OP_VERSION, | |
| 62 OP_CREATION_TIME, | |
| 63 OP_FILE_PATH, | |
| 64 OP_FILE_SIZE, | |
| 65 OP_LAST_ACCESS_TIME, | |
| 66 OP_ACCESS_COUNT, | |
| 67 OP_STATUS, | |
| 68 OP_USER_INITIATED | |
| 69 }; | |
| 70 | |
| 71 bool CreateTable(sql::Connection* db, | |
| 72 const char* table_name, | |
| 73 const char* columns) { | |
| 74 std::string sql("CREATE TABLE "); | |
| 75 sql += table_name; | |
| 76 sql += columns; | |
| 77 return db->Execute(sql.c_str()); | |
| 78 } | |
| 79 | |
| 80 bool CreateSchema(sql::MetaTable* meta_table, 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 | |
| 87 if (!meta_table->Init(db, kCurrentVersion, kCompatibleVersion)) | |
| 88 return false; | |
| 89 | |
| 90 if (!CreateTable(db, kOfflinePagesTableName, kOfflinePagesColumns)) | |
| 91 return false; | |
| 92 | |
| 93 // TODO(bburns): indexes here | |
| 94 return transaction.Commit(); | |
| 95 } | |
| 96 | |
| 97 bool DropTable(sql::Connection* db, const char* table_name) { | |
| 98 std::string sql("DROP TABLE "); | |
| 99 sql += table_name; | |
| 100 return db->Execute(sql.c_str()); | |
| 101 } | |
| 102 | |
| 103 bool DeleteByOfflineId(sql::Connection* db, int64_t offline_id) { | |
| 104 const char sql[] = | |
| 105 "DELETE FROM " kOfflinePagesTableName " WHERE offline_id=?"; | |
| 106 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, sql)); | |
| 107 statement.BindInt64(0, offline_id); | |
| 108 return db->Execute(sql); | |
| 109 } | |
| 110 | |
| 111 // Create an offline page item from a SQL result. Expects complete rows with | |
| 112 // all columns present. | |
| 113 OfflinePageItem MakeOfflinePageItem(sql::Statement* statement) { | |
| 114 int64_t id = statement->ColumnInt64(OP_OFFLINE_ID); | |
| 115 GURL url(statement->ColumnString(OP_ONLINE_URL)); | |
| 116 ClientId client_id(statement->ColumnString(OP_CLIENT_NAMESPACE), | |
| 117 statement->ColumnString(OP_CLIENT_ID)); | |
| 118 base::FilePath path(statement->ColumnString(OP_FILE_PATH)); | |
|
fgorski
2016/03/29 06:50:19
From the level db store impl:
#if defined(OS_POSIX
bburns
2016/03/30 16:54:39
Is this really required? It seems like something
fgorski
2016/03/30 17:19:31
It's actually file path that is weird.
class BASE
| |
| 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); | |
| 127 return item; | |
| 128 } | |
| 129 | |
| 130 bool InsertOrReplace(sql::Connection* db, const OfflinePageItem& item) { | |
| 131 const char sql[] = | |
|
fgorski
2016/03/29 06:50:19
since it is a const, kSql
bburns
2016/03/30 16:54:40
Done.
| |
| 132 "INSERT OR REPLACE INTO " kOfflinePagesTableName | |
| 133 " (offline_id, online_url, client_namespace, client_id, file_path, " | |
| 134 "file_size, creation_time, last_access_time, version)" | |
| 135 " VALUES " | |
| 136 " (?, ?, ?, ?, ?, ?, ?, ?, ?)"; | |
| 137 | |
| 138 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, sql)); | |
| 139 statement.BindInt64(0, item.offline_id); | |
| 140 statement.BindString(1, item.url.spec()); | |
| 141 statement.BindString(2, item.client_id.name_space); | |
| 142 statement.BindString(3, item.client_id.id); | |
| 143 statement.BindString(4, item.file_path.value()); | |
| 144 statement.BindInt64(5, item.file_size); | |
| 145 statement.BindInt64(6, item.creation_time.ToInternalValue()); | |
| 146 statement.BindInt64(7, item.last_access_time.ToInternalValue()); | |
| 147 statement.BindInt64(8, item.version); | |
| 148 return statement.Run(); | |
| 149 } | |
| 150 } // anonymous namespace | |
| 151 | |
| 152 OfflinePageMetadataStoreSQL::OfflinePageMetadataStoreSQL( | |
| 153 scoped_refptr<base::SequencedTaskRunner> background_task_runner, | |
| 154 const base::FilePath& path, | |
| 155 bool in_memory) | |
| 156 : background_task_runner_(background_task_runner), | |
| 157 db_file_path_(path), | |
| 158 use_in_memory_(in_memory), | |
| 159 weak_ptr_factory_(this) {} | |
| 160 | |
| 161 OfflinePageMetadataStoreSQL::~OfflinePageMetadataStoreSQL() {} | |
| 162 | |
| 163 void OfflinePageMetadataStoreSQL::LoadSync( | |
| 164 scoped_refptr<base::SingleThreadTaskRunner> runner, | |
| 165 const LoadCallback& callback) { | |
| 166 bool opened = false; | |
| 167 db_.reset(new sql::Connection); | |
| 168 meta_table_.reset(new sql::MetaTable); | |
| 169 | |
| 170 if (use_in_memory_) { | |
| 171 opened = db_->OpenInMemory(); | |
| 172 } else if (!base::CreateDirectory(db_file_path_.DirName())) { | |
|
fgorski
2016/03/29 06:50:18
since you are logging here, probably makes sense t
bburns
2016/03/30 16:54:40
Done.
| |
| 173 LOG(ERROR) << "Failed to create offline pages db directory."; | |
| 174 } else { | |
| 175 opened = db_->Open(db_file_path_); | |
| 176 if (opened) | |
| 177 db_->Preload(); | |
| 178 } | |
| 179 if (!opened) { | |
| 180 NotifyLoadResult(callback, runner, STORE_INIT_FAILED, | |
| 181 std::vector<OfflinePageItem>()); | |
| 182 } | |
| 183 | |
| 184 if (!sql::MetaTable::DoesTableExist(db_.get())) { | |
| 185 if (!CreateSchema(meta_table_.get(), db_.get())) { | |
| 186 NotifyLoadResult(callback, runner, STORE_INIT_FAILED, | |
| 187 std::vector<OfflinePageItem>()); | |
|
fgorski
2016/03/29 06:50:19
do you intend to stop execution here?
if so return
bburns
2016/03/30 16:54:40
Done.
| |
| 188 } | |
| 189 } | |
| 190 | |
| 191 if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion)) | |
| 192 NotifyLoadResult(callback, runner, STORE_INIT_FAILED, | |
|
fgorski
2016/03/29 06:50:19
this is a single line, but a multiline body, surro
bburns
2016/03/30 16:54:40
Done.
| |
| 193 std::vector<OfflinePageItem>()); | |
| 194 | |
| 195 if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) { | |
| 196 LOG(WARNING) << "Offline database is too new."; | |
| 197 NotifyLoadResult(callback, runner, STORE_INIT_FAILED, | |
| 198 std::vector<OfflinePageItem>()); | |
|
fgorski
2016/03/29 06:50:19
return missing
bburns
2016/03/30 16:54:40
Done.
| |
| 199 } | |
| 200 | |
| 201 const char sql[] = "SELECT * FROM " kOfflinePagesTableName; | |
| 202 | |
| 203 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, sql)); | |
| 204 | |
| 205 std::vector<OfflinePageItem> result; | |
| 206 while (statement.Step()) { | |
| 207 result.push_back(MakeOfflinePageItem(&statement)); | |
| 208 } | |
| 209 | |
| 210 if (!statement.Succeeded()) { | |
| 211 NotifyLoadResult(callback, runner, STORE_LOAD_FAILED, | |
| 212 std::vector<OfflinePageItem>()); | |
| 213 } else { | |
| 214 NotifyLoadResult(callback, runner, LOAD_SUCCEEDED, result); | |
| 215 } | |
| 216 } | |
| 217 | |
| 218 void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync( | |
| 219 const OfflinePageItem& offline_page, | |
| 220 scoped_refptr<base::SingleThreadTaskRunner> runner, | |
| 221 const UpdateCallback& callback) { | |
| 222 bool ok = InsertOrReplace(db_.get(), offline_page); | |
| 223 runner->PostTask(FROM_HERE, base::Bind(callback, ok)); | |
| 224 } | |
| 225 | |
| 226 void OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync( | |
| 227 const std::vector<int64_t>& offline_ids, | |
| 228 scoped_refptr<base::SingleThreadTaskRunner> runner, | |
| 229 const UpdateCallback& callback) { | |
| 230 // If you create a transaction but don't Commit() it is automatically | |
| 231 // rolled back by its destructor when it falls out of scope. | |
| 232 sql::Transaction transaction(db_.get()); | |
| 233 | |
| 234 for (auto offline_id : offline_ids) { | |
| 235 if (!DeleteByOfflineId(db_.get(), offline_id)) { | |
| 236 runner->PostTask(FROM_HERE, base::Bind(callback, false)); | |
| 237 return; | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 if (!transaction.Commit()) { | |
|
fgorski
2016/03/29 06:50:18
This posts the result twice upon failure, because
bburns
2016/03/30 16:54:40
Done.
| |
| 242 runner->PostTask(FROM_HERE, base::Bind(callback, false)); | |
| 243 } | |
| 244 runner->PostTask(FROM_HERE, base::Bind(callback, true)); | |
| 245 } | |
| 246 | |
| 247 void OfflinePageMetadataStoreSQL::Reset(const ResetCallback& callback) { | |
| 248 bool success = DropTable(db_.get(), kOfflinePagesTableName); | |
|
fgorski
2016/03/29 06:50:19
shouldn't DDL be called on the background thread?
bburns
2016/03/30 16:54:39
If you want. Drop table seemed like it was so fas
fgorski
2016/03/30 17:19:31
Let's stay consistent with choice of threads. If y
Scott Hess - ex-Googler
2016/04/12 21:57:23
While SQLite _can_ be thread-safe in certain ways,
| |
| 249 callback.Run(success); | |
| 250 } | |
| 251 | |
| 252 void OfflinePageMetadataStoreSQL::NotifyLoadResult( | |
| 253 const LoadCallback& callback, | |
|
fgorski
2016/03/29 06:50:19
I don't mean to argue either order is better:
call
bburns
2016/03/30 16:54:39
Done.
| |
| 254 scoped_refptr<base::SingleThreadTaskRunner> runner, | |
| 255 LoadStatus status, | |
| 256 const std::vector<OfflinePageItem>& result) { | |
| 257 UMA_HISTOGRAM_ENUMERATION("OfflinePages.LoadStatus", status, | |
| 258 OfflinePageMetadataStore::LOAD_STATUS_COUNT); | |
| 259 if (status == LOAD_SUCCEEDED) { | |
| 260 UMA_HISTOGRAM_COUNTS("OfflinePages.SavedPageCount", result.size()); | |
| 261 } else { | |
| 262 DVLOG(1) << "Offline pages database loading failed: " << status; | |
| 263 db_.reset(); | |
| 264 } | |
| 265 runner->PostTask(FROM_HERE, base::Bind(callback, status, result)); | |
| 266 } | |
| 267 | |
| 268 void OfflinePageMetadataStoreSQL::Load(const LoadCallback& callback) { | |
| 269 background_task_runner_->PostTask( | |
| 270 FROM_HERE, base::Bind(&OfflinePageMetadataStoreSQL::LoadSync, | |
| 271 weak_ptr_factory_.GetWeakPtr(), | |
| 272 base::ThreadTaskRunnerHandle::Get(), callback)); | |
| 273 } | |
| 274 | |
| 275 void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePage( | |
| 276 const OfflinePageItem& offline_page, | |
| 277 const UpdateCallback& callback) { | |
| 278 background_task_runner_->PostTask( | |
| 279 FROM_HERE, | |
| 280 base::Bind(&OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync, | |
| 281 weak_ptr_factory_.GetWeakPtr(), offline_page, | |
| 282 base::ThreadTaskRunnerHandle::Get(), callback)); | |
| 283 } | |
| 284 | |
| 285 void OfflinePageMetadataStoreSQL::RemoveOfflinePages( | |
| 286 const std::vector<int64_t>& offline_ids, | |
| 287 const UpdateCallback& callback) { | |
| 288 if (offline_ids.size() == 0) { | |
| 289 // Nothing to do. | |
|
fgorski
2016/03/29 06:50:19
Good shortcut, but for safety you can post it to t
bburns
2016/03/30 16:54:40
Done.
fgorski
2016/03/30 17:19:31
I meant something like:
base::ThreadTaskRunnerHand
| |
| 290 callback.Run(true); | |
| 291 return; | |
| 292 } | |
| 293 | |
| 294 background_task_runner_->PostTask( | |
| 295 FROM_HERE, | |
| 296 base::Bind(&OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync, | |
| 297 weak_ptr_factory_.GetWeakPtr(), offline_ids, | |
| 298 base::ThreadTaskRunnerHandle::Get(), callback)); | |
| 299 } | |
| 300 | |
| 301 } // namespace offline_pages | |
| OLD | NEW |