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/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" | |
|
Scott Hess - ex-Googler
2016/04/12 21:49:21
Are you actually planning to ever use this to some
bburns
2016/04/19 17:59:45
Its repeated in several places, so I have a consta
Scott Hess - ex-Googler
2016/04/20 18:22:37
It's really your call. I'd be more averse if you
bburns
2016/04/20 20:34:24
Thanks, I think I prefer this way of doing things.
| |
| 33 | |
| 34 const char kOfflinePagesColumns[] = | |
| 35 "(offline_id INTEGER NOT NULL," | |
| 36 " client_namespace VARCHAR(256)," | |
| 37 " client_id VARCHAR(256)," | |
| 38 " online_url VARCHAR(2048)," | |
| 39 " offline_url VARCHAR(2048)," | |
|
Scott Hess - ex-Googler
2016/04/12 21:49:22
VARCHAR(N) has no meaning to SQLite, it will all b
bburns
2016/04/19 17:59:45
Done.
| |
| 40 " version INTEGER," | |
| 41 " creation_time INTEGER," | |
| 42 " file_path VARCHAR(1024)," | |
| 43 " file_size INTEGER," | |
| 44 " last_access_time INTEGER," | |
| 45 " access_count INTEGER," | |
| 46 " status INTEGER," | |
| 47 " user_initiated BOOLEAN," | |
|
Scott Hess - ex-Googler
2016/04/12 21:49:21
BOOLEAN has no meaning to SQLite.
Scott Hess - ex-Googler
2016/04/14 18:18:53
SQLite stores most types as a type code plus the t
bburns
2016/04/19 17:59:45
Done.
bburns
2016/04/19 17:59:45
Acknowledged.
| |
| 48 " UNIQUE(offline_id))"; | |
|
Scott Hess - ex-Googler
2016/04/12 21:49:21
Why not just "offline_id INTEGER PRIMARY KEY" in t
bburns
2016/04/19 17:59:45
I had this. For some reason it didn't work correc
Scott Hess - ex-Googler
2016/04/20 18:22:36
AFAICT, it should work fine. I mean when I play a
bburns
2016/04/20 20:34:24
Hrm, it works now. I blame the cosmic rays...
Do
| |
| 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. | |
|
Scott Hess - ex-Googler
2016/04/12 21:49:21
This feels all indexy and static-compile-checking,
bburns
2016/04/19 17:59:45
This is really just to declare constants for the c
Scott Hess - ex-Googler
2016/04/20 18:22:36
I'd probably prefer an enum to a #define, too. Bu
bburns
2016/04/20 20:34:24
Acknowledged. I think I prefer the descriptive na
| |
| 61 enum : int { | |
| 62 OP_OFFLINE_ID = 0, | |
| 63 OP_CLIENT_NAMESPACE, | |
| 64 OP_CLIENT_ID, | |
| 65 OP_ONLINE_URL, | |
| 66 OP_OFFLINE_URL, | |
| 67 OP_VERSION, | |
| 68 OP_CREATION_TIME, | |
| 69 OP_FILE_PATH, | |
| 70 OP_FILE_SIZE, | |
| 71 OP_LAST_ACCESS_TIME, | |
| 72 OP_ACCESS_COUNT, | |
| 73 OP_STATUS, | |
| 74 OP_USER_INITIATED | |
| 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 std::string path_string; | |
| 147 #if defined(OS_POSIX) | |
| 148 path_string = item.file_path.value(); | |
| 149 #elif defined(OS_WIN) | |
| 150 path_string = base::WideToUTF8(item.file_path.value()); | |
| 151 #endif | |
|
Scott Hess - ex-Googler
2016/04/12 21:49:22
Declaring the variable inline in the conditional w
bburns
2016/04/19 17:59:45
Done.
| |
| 152 statement.BindString(4, path_string); | |
| 153 statement.BindInt64(5, item.file_size); | |
| 154 statement.BindInt64(6, item.creation_time.ToInternalValue()); | |
| 155 statement.BindInt64(7, item.last_access_time.ToInternalValue()); | |
| 156 statement.BindInt(8, item.version); | |
| 157 statement.BindInt(9, item.access_count); | |
| 158 return statement.Run(); | |
| 159 } | |
| 160 | |
| 161 } // anonymous namespace | |
| 162 | |
| 163 OfflinePageMetadataStoreSQL::OfflinePageMetadataStoreSQL( | |
| 164 scoped_refptr<base::SequencedTaskRunner> background_task_runner, | |
| 165 const base::FilePath& path) | |
| 166 : background_task_runner_(background_task_runner), | |
| 167 db_file_path_(path.AppendASCII("OfflinePages.db")), | |
| 168 use_in_memory_(false), | |
| 169 weak_ptr_factory_(this) {} | |
| 170 | |
| 171 OfflinePageMetadataStoreSQL::~OfflinePageMetadataStoreSQL() { | |
| 172 if (db_.get() && | |
| 173 !background_task_runner_->DeleteSoon(FROM_HERE, db_.release())) { | |
| 174 DLOG(WARNING) << "SQL database will not be deleted."; | |
| 175 } | |
| 176 } | |
| 177 | |
| 178 void OfflinePageMetadataStoreSQL::LoadSync( | |
| 179 scoped_refptr<base::SingleThreadTaskRunner> runner, | |
| 180 const LoadCallback& callback) { | |
| 181 bool opened = false; | |
| 182 db_.reset(new sql::Connection); | |
| 183 meta_table_.reset(new sql::MetaTable); | |
|
Scott Hess - ex-Googler
2016/04/12 21:49:21
You never use meta_table_ outside of this method.
bburns
2016/04/19 17:59:45
Done.
| |
| 184 | |
| 185 if (use_in_memory_) { | |
| 186 opened = db_->OpenInMemory(); | |
| 187 } else { | |
| 188 base::File::Error err; | |
| 189 if (!base::CreateDirectoryAndGetError(db_file_path_.DirName(), &err)) { | |
|
Scott Hess - ex-Googler
2016/04/12 21:49:21
Are there lots of these?
bburns
2016/04/19 17:59:45
Lots of errors? Probably not, but still worth han
Scott Hess - ex-Googler
2016/04/20 18:22:36
Apologies - I mean are there lots of distinct data
bburns
2016/04/20 20:34:24
Ah, I'm basically just mirroring the existing Leve
Scott Hess - ex-Googler
2016/04/21 03:25:11
It totally makes sense with leveldb, since that's
| |
| 190 LOG(ERROR) << "Failed to create offline pages db directory: " | |
| 191 << base::File::ErrorToString(err); | |
| 192 } else { | |
| 193 opened = db_->Open(db_file_path_); | |
| 194 if (opened) | |
| 195 db_->Preload(); | |
| 196 } | |
| 197 } | |
| 198 if (!opened) { | |
| 199 LOG(ERROR) << "Failed to open database"; | |
| 200 NotifyLoadResult(runner, callback, STORE_INIT_FAILED, | |
| 201 std::vector<OfflinePageItem>()); | |
| 202 return; | |
| 203 } | |
| 204 | |
| 205 if (!sql::MetaTable::DoesTableExist(db_.get())) { | |
| 206 if (!CreateSchema(meta_table_.get(), db_.get())) { | |
| 207 LOG(ERROR) << "Failed to create schema"; | |
| 208 NotifyLoadResult(runner, callback, STORE_INIT_FAILED, | |
| 209 std::vector<OfflinePageItem>()); | |
| 210 return; | |
| 211 } | |
| 212 } else if (!meta_table_->Init(db_.get(), kCurrentVersion, | |
| 213 kCompatibleVersion)) { | |
| 214 LOG(ERROR) << "Failed to initialize database"; | |
| 215 NotifyLoadResult(runner, callback, STORE_INIT_FAILED, | |
| 216 std::vector<OfflinePageItem>()); | |
| 217 return; | |
| 218 } | |
| 219 | |
| 220 if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) { | |
| 221 LOG(WARNING) << "Offline database is too new."; | |
| 222 NotifyLoadResult(runner, callback, STORE_INIT_FAILED, | |
| 223 std::vector<OfflinePageItem>()); | |
| 224 return; | |
| 225 } | |
| 226 | |
| 227 const char kSql[] = "SELECT * FROM " OFFLINE_PAGES_TABLE_NAME; | |
| 228 | |
| 229 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
| 230 | |
| 231 std::vector<OfflinePageItem> result; | |
| 232 while (statement.Step()) { | |
| 233 result.push_back(MakeOfflinePageItem(&statement)); | |
| 234 } | |
| 235 | |
| 236 if (statement.Succeeded()) { | |
| 237 NotifyLoadResult(runner, callback, LOAD_SUCCEEDED, result); | |
| 238 } else { | |
| 239 NotifyLoadResult(runner, callback, STORE_LOAD_FAILED, | |
| 240 std::vector<OfflinePageItem>()); | |
| 241 } | |
| 242 } | |
| 243 | |
| 244 void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync( | |
| 245 const OfflinePageItem& offline_page, | |
| 246 scoped_refptr<base::SingleThreadTaskRunner> runner, | |
| 247 const UpdateCallback& callback) { | |
| 248 DCHECK(db_.get()); | |
| 249 // TODO(bburns): add UMA metrics here (and for levelDB). | |
|
Scott Hess - ex-Googler
2016/04/12 21:49:21
What does this mean? It sounds like you're buildi
bburns
2016/04/19 17:59:44
There's an existing implementation:
https://code.
Scott Hess - ex-Googler
2016/04/20 18:22:36
OK, we had an offline discussion. Reasoning for c
| |
| 250 bool ok = InsertOrReplace(db_.get(), offline_page); | |
| 251 runner->PostTask(FROM_HERE, base::Bind(callback, ok)); | |
| 252 } | |
| 253 | |
| 254 void OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync( | |
| 255 const std::vector<int64_t>& offline_ids, | |
| 256 scoped_refptr<base::SingleThreadTaskRunner> runner, | |
| 257 const UpdateCallback& callback) { | |
| 258 DCHECK(db_.get()); | |
| 259 // TODO(bburns): add UMA metrics here (and for levelDB). | |
| 260 | |
| 261 // If you create a transaction but don't Commit() it is automatically | |
| 262 // rolled back by its destructor when it falls out of scope. | |
| 263 sql::Transaction transaction(db_.get()); | |
| 264 if (!transaction.Begin()) { | |
| 265 runner->PostTask(FROM_HERE, base::Bind(callback, false)); | |
| 266 return; | |
| 267 } | |
| 268 for (auto offline_id : offline_ids) { | |
| 269 if (!DeleteByOfflineId(db_.get(), offline_id)) { | |
| 270 runner->PostTask(FROM_HERE, base::Bind(callback, false)); | |
| 271 return; | |
| 272 } | |
| 273 } | |
| 274 | |
| 275 bool success = transaction.Commit(); | |
| 276 runner->PostTask(FROM_HERE, base::Bind(callback, success)); | |
| 277 } | |
| 278 | |
| 279 void OfflinePageMetadataStoreSQL::ResetSync( | |
| 280 scoped_refptr<base::SingleThreadTaskRunner> runner, | |
| 281 const ResetCallback& callback) { | |
| 282 const char kSql[] = "DELETE * FROM " OFFLINE_PAGES_TABLE_NAME; | |
| 283 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
| 284 if (!statement.Run()) { | |
| 285 runner->PostTask(FROM_HERE, base::Bind(callback, false)); | |
| 286 return; | |
| 287 } | |
| 288 meta_table_.reset(); | |
| 289 db_.reset(); | |
| 290 weak_ptr_factory_.InvalidateWeakPtrs(); | |
| 291 | |
| 292 runner->PostTask(FROM_HERE, base::Bind(callback, true)); | |
| 293 } | |
| 294 | |
| 295 void OfflinePageMetadataStoreSQL::NotifyLoadResult( | |
| 296 scoped_refptr<base::SingleThreadTaskRunner> runner, | |
| 297 const LoadCallback& callback, | |
| 298 LoadStatus status, | |
| 299 const std::vector<OfflinePageItem>& result) { | |
| 300 // TODO(bburns): Switch to SQL specific UMA metrics. | |
| 301 UMA_HISTOGRAM_ENUMERATION("OfflinePages.LoadStatus", status, | |
| 302 OfflinePageMetadataStore::LOAD_STATUS_COUNT); | |
| 303 if (status == LOAD_SUCCEEDED) { | |
| 304 UMA_HISTOGRAM_COUNTS("OfflinePages.SavedPageCount", result.size()); | |
| 305 } else { | |
| 306 DVLOG(1) << "Offline pages database loading failed: " << status; | |
| 307 meta_table_.reset(); | |
| 308 db_.reset(); | |
| 309 } | |
| 310 runner->PostTask(FROM_HERE, base::Bind(callback, status, result)); | |
| 311 } | |
| 312 | |
| 313 void OfflinePageMetadataStoreSQL::Load(const LoadCallback& callback) { | |
| 314 background_task_runner_->PostTask( | |
| 315 FROM_HERE, base::Bind(&OfflinePageMetadataStoreSQL::LoadSync, | |
| 316 weak_ptr_factory_.GetWeakPtr(), | |
| 317 base::ThreadTaskRunnerHandle::Get(), callback)); | |
| 318 } | |
| 319 | |
| 320 void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePage( | |
| 321 const OfflinePageItem& offline_page, | |
| 322 const UpdateCallback& callback) { | |
| 323 background_task_runner_->PostTask( | |
| 324 FROM_HERE, | |
| 325 base::Bind(&OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync, | |
| 326 weak_ptr_factory_.GetWeakPtr(), offline_page, | |
| 327 base::ThreadTaskRunnerHandle::Get(), callback)); | |
| 328 } | |
| 329 | |
| 330 void OfflinePageMetadataStoreSQL::RemoveOfflinePages( | |
| 331 const std::vector<int64_t>& offline_ids, | |
| 332 const UpdateCallback& callback) { | |
| 333 if (offline_ids.size() == 0) { | |
|
Scott Hess - ex-Googler
2016/04/12 21:49:21
.empty() may be more robust.
bburns
2016/04/19 17:59:45
Done.
| |
| 334 // Nothing to do, but post a callback instead of calling directly | |
| 335 // to preserve the async style behavior to prevent bugs. | |
| 336 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, | |
| 337 base::Bind(callback, true)); | |
| 338 return; | |
| 339 } | |
| 340 | |
| 341 background_task_runner_->PostTask( | |
| 342 FROM_HERE, | |
| 343 base::Bind(&OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync, | |
| 344 weak_ptr_factory_.GetWeakPtr(), offline_ids, | |
| 345 base::ThreadTaskRunnerHandle::Get(), callback)); | |
| 346 } | |
| 347 | |
| 348 void OfflinePageMetadataStoreSQL::Reset(const ResetCallback& callback) { | |
| 349 background_task_runner_->PostTask( | |
| 350 FROM_HERE, base::Bind(&OfflinePageMetadataStoreSQL::ResetSync, | |
| 351 weak_ptr_factory_.GetWeakPtr(), | |
| 352 base::ThreadTaskRunnerHandle::Get(), callback)); | |
| 353 } | |
| 354 | |
| 355 void OfflinePageMetadataStoreSQL::SetInMemoryDatabaseForTesting( | |
| 356 bool in_memory) { | |
| 357 // Must be called prior to Load(...) | |
| 358 DCHECK(db_.get() == NULL); | |
| 359 | |
| 360 use_in_memory_ = in_memory; | |
| 361 } | |
| 362 | |
| 363 } // namespace offline_pages | |
| OLD | NEW |