| 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/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 ''," |
| 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()); |
| 149 #else |
| 150 #error Unknown OS |
| 151 #endif |
| 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 bool InitDatabase(sql::Connection* db, base::FilePath path) { |
| 162 db->set_page_size(4096); |
| 163 db->set_cache_size(500); |
| 164 db->set_histogram_tag("OfflinePageMetadata"); |
| 165 db->set_exclusive_locking(); |
| 166 |
| 167 base::File::Error err; |
| 168 if (!base::CreateDirectoryAndGetError(path.DirName(), &err)) { |
| 169 LOG(ERROR) << "Failed to create offline pages db directory: " |
| 170 << base::File::ErrorToString(err); |
| 171 return false; |
| 172 } |
| 173 if (!db->Open(path)) { |
| 174 LOG(ERROR) << "Failed to open database"; |
| 175 return false; |
| 176 } |
| 177 db->Preload(); |
| 178 |
| 179 return CreateSchema(db); |
| 180 } |
| 181 |
| 182 } // anonymous namespace |
| 183 |
| 184 OfflinePageMetadataStoreSQL::OfflinePageMetadataStoreSQL( |
| 185 scoped_refptr<base::SequencedTaskRunner> background_task_runner, |
| 186 const base::FilePath& path) |
| 187 : background_task_runner_(std::move(background_task_runner)), |
| 188 db_file_path_(path.AppendASCII("OfflinePages.db")) {} |
| 189 |
| 190 OfflinePageMetadataStoreSQL::~OfflinePageMetadataStoreSQL() { |
| 191 if (db_.get() && |
| 192 !background_task_runner_->DeleteSoon(FROM_HERE, db_.release())) { |
| 193 DLOG(WARNING) << "SQL database will not be deleted."; |
| 194 } |
| 195 } |
| 196 |
| 197 void OfflinePageMetadataStoreSQL::LoadSync( |
| 198 sql::Connection* db, |
| 199 const base::FilePath& path, |
| 200 scoped_refptr<base::SingleThreadTaskRunner> runner, |
| 201 const LoadCallback& callback) { |
| 202 if (!InitDatabase(db, path)) { |
| 203 NotifyLoadResult(runner, callback, STORE_INIT_FAILED, |
| 204 std::vector<OfflinePageItem>()); |
| 205 return; |
| 206 } |
| 207 |
| 208 const char kSql[] = "SELECT * FROM " OFFLINE_PAGES_TABLE_NAME; |
| 209 |
| 210 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql)); |
| 211 |
| 212 std::vector<OfflinePageItem> result; |
| 213 while (statement.Step()) { |
| 214 result.push_back(MakeOfflinePageItem(&statement)); |
| 215 } |
| 216 |
| 217 if (statement.Succeeded()) { |
| 218 NotifyLoadResult(runner, callback, LOAD_SUCCEEDED, result); |
| 219 } else { |
| 220 NotifyLoadResult(runner, callback, STORE_LOAD_FAILED, |
| 221 std::vector<OfflinePageItem>()); |
| 222 } |
| 223 } |
| 224 |
| 225 void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync( |
| 226 const OfflinePageItem& offline_page, |
| 227 sql::Connection* db, |
| 228 scoped_refptr<base::SingleThreadTaskRunner> runner, |
| 229 const UpdateCallback& callback) { |
| 230 // TODO(bburns): add UMA metrics here (and for levelDB). |
| 231 bool ok = InsertOrReplace(db, offline_page); |
| 232 runner->PostTask(FROM_HERE, base::Bind(callback, ok)); |
| 233 } |
| 234 |
| 235 void OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync( |
| 236 const std::vector<int64_t>& offline_ids, |
| 237 sql::Connection* db, |
| 238 scoped_refptr<base::SingleThreadTaskRunner> runner, |
| 239 const UpdateCallback& callback) { |
| 240 // TODO(bburns): add UMA metrics here (and for levelDB). |
| 241 |
| 242 // If you create a transaction but don't Commit() it is automatically |
| 243 // rolled back by its destructor when it falls out of scope. |
| 244 sql::Transaction transaction(db); |
| 245 if (!transaction.Begin()) { |
| 246 runner->PostTask(FROM_HERE, base::Bind(callback, false)); |
| 247 return; |
| 248 } |
| 249 for (auto offline_id : offline_ids) { |
| 250 if (!DeleteByOfflineId(db, offline_id)) { |
| 251 runner->PostTask(FROM_HERE, base::Bind(callback, false)); |
| 252 return; |
| 253 } |
| 254 } |
| 255 |
| 256 bool success = transaction.Commit(); |
| 257 runner->PostTask(FROM_HERE, base::Bind(callback, success)); |
| 258 } |
| 259 |
| 260 void OfflinePageMetadataStoreSQL::ResetSync( |
| 261 std::unique_ptr<sql::Connection> db, |
| 262 scoped_refptr<base::SingleThreadTaskRunner> runner, |
| 263 const ResetCallback& callback) { |
| 264 const char kSql[] = "DELETE * FROM " OFFLINE_PAGES_TABLE_NAME; |
| 265 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql)); |
| 266 runner->PostTask(FROM_HERE, base::Bind(callback, statement.Run())); |
| 267 } |
| 268 |
| 269 void OfflinePageMetadataStoreSQL::NotifyLoadResult( |
| 270 scoped_refptr<base::SingleThreadTaskRunner> runner, |
| 271 const LoadCallback& callback, |
| 272 LoadStatus status, |
| 273 const std::vector<OfflinePageItem>& result) { |
| 274 // TODO(bburns): Switch to SQL specific UMA metrics. |
| 275 UMA_HISTOGRAM_ENUMERATION("OfflinePages.LoadStatus", status, |
| 276 OfflinePageMetadataStore::LOAD_STATUS_COUNT); |
| 277 if (status == LOAD_SUCCEEDED) { |
| 278 UMA_HISTOGRAM_COUNTS("OfflinePages.SavedPageCount", result.size()); |
| 279 } else { |
| 280 DVLOG(1) << "Offline pages database loading failed: " << status; |
| 281 } |
| 282 runner->PostTask(FROM_HERE, base::Bind(callback, status, result)); |
| 283 } |
| 284 |
| 285 void OfflinePageMetadataStoreSQL::Load(const LoadCallback& callback) { |
| 286 db_.reset(new sql::Connection()); |
| 287 background_task_runner_->PostTask( |
| 288 FROM_HERE, |
| 289 base::Bind(&OfflinePageMetadataStoreSQL::LoadSync, db_.get(), |
| 290 db_file_path_, base::ThreadTaskRunnerHandle::Get(), callback)); |
| 291 } |
| 292 |
| 293 void OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePage( |
| 294 const OfflinePageItem& offline_page, |
| 295 const UpdateCallback& callback) { |
| 296 DCHECK(db_.get()); |
| 297 background_task_runner_->PostTask( |
| 298 FROM_HERE, |
| 299 base::Bind(&OfflinePageMetadataStoreSQL::AddOrUpdateOfflinePageSync, |
| 300 offline_page, db_.get(), base::ThreadTaskRunnerHandle::Get(), |
| 301 callback)); |
| 302 } |
| 303 |
| 304 void OfflinePageMetadataStoreSQL::RemoveOfflinePages( |
| 305 const std::vector<int64_t>& offline_ids, |
| 306 const UpdateCallback& callback) { |
| 307 DCHECK(db_.get()); |
| 308 |
| 309 if (offline_ids.empty()) { |
| 310 // Nothing to do, but post a callback instead of calling directly |
| 311 // to preserve the async style behavior to prevent bugs. |
| 312 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, |
| 313 base::Bind(callback, true)); |
| 314 return; |
| 315 } |
| 316 |
| 317 background_task_runner_->PostTask( |
| 318 FROM_HERE, |
| 319 base::Bind(&OfflinePageMetadataStoreSQL::RemoveOfflinePagesSync, |
| 320 offline_ids, db_.get(), base::ThreadTaskRunnerHandle::Get(), |
| 321 callback)); |
| 322 } |
| 323 |
| 324 void OfflinePageMetadataStoreSQL::Reset(const ResetCallback& callback) { |
| 325 background_task_runner_->PostTask( |
| 326 FROM_HERE, |
| 327 base::Bind(&OfflinePageMetadataStoreSQL::ResetSync, base::Passed(&db_), |
| 328 base::ThreadTaskRunnerHandle::Get(), callback)); |
| 329 } |
| 330 |
| 331 } // namespace offline_pages |
| OLD | NEW |