offline_page_metadata_store.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  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. #include "components/offline_pages/core/offline_page_metadata_store.h"
  5. #include <utility>
  6. #include "base/bind.h"
  7. #include "base/files/file_path.h"
  8. #include "base/files/file_util.h"
  9. #include "base/location.h"
  10. #include "base/metrics/histogram_macros.h"
  11. #include "base/strings/utf_string_conversions.h"
  12. #include "base/task/sequenced_task_runner.h"
  13. #include "base/task/single_thread_task_runner.h"
  14. #include "base/trace_event/trace_event.h"
  15. #include "components/offline_pages/core/client_namespace_constants.h"
  16. #include "components/offline_pages/core/offline_page_item.h"
  17. #include "components/offline_pages/core/offline_store_types.h"
  18. #include "components/offline_pages/core/offline_store_utils.h"
  19. #include "sql/database.h"
  20. #include "sql/meta_table.h"
  21. #include "sql/statement.h"
  22. #include "sql/transaction.h"
  23. namespace offline_pages {
  24. const int OfflinePageMetadataStore::kFirstPostLegacyVersion;
  25. const int OfflinePageMetadataStore::kCurrentVersion;
  26. const int OfflinePageMetadataStore::kCompatibleVersion;
  27. namespace {
  28. // This is a macro instead of a const so that
  29. // it can be used inline in other SQL statements below.
  30. #define OFFLINE_PAGES_TABLE_NAME "offlinepages_v1"
  31. void ReportStoreEvent(OfflinePagesStoreEvent event) {
  32. UMA_HISTOGRAM_ENUMERATION("OfflinePages.SQLStorage.StoreEvent", event);
  33. }
  34. bool CreateOfflinePagesTable(sql::Database* db) {
  35. static const char kCreateLatestOfflinePagesTableSql[] =
  36. "CREATE TABLE IF NOT EXISTS " OFFLINE_PAGES_TABLE_NAME
  37. "(offline_id INTEGER PRIMARY KEY NOT NULL,"
  38. " creation_time INTEGER NOT NULL,"
  39. " file_size INTEGER NOT NULL,"
  40. " last_access_time INTEGER NOT NULL,"
  41. " access_count INTEGER NOT NULL,"
  42. " system_download_id INTEGER NOT NULL DEFAULT 0,"
  43. " file_missing_time INTEGER NOT NULL DEFAULT 0,"
  44. // upgrade_attempt is deprecated, and should be removed next time the
  45. // schema needs to be updated.
  46. " upgrade_attempt INTEGER NOT NULL DEFAULT 0,"
  47. " client_namespace VARCHAR NOT NULL,"
  48. " client_id VARCHAR NOT NULL,"
  49. " online_url VARCHAR NOT NULL,"
  50. " file_path VARCHAR NOT NULL,"
  51. " title VARCHAR NOT NULL DEFAULT '',"
  52. " original_url VARCHAR NOT NULL DEFAULT '',"
  53. " request_origin VARCHAR NOT NULL DEFAULT '',"
  54. " digest VARCHAR NOT NULL DEFAULT '',"
  55. " snippet VARCHAR NOT NULL DEFAULT '',"
  56. " attribution VARCHAR NOT NULL DEFAULT ''"
  57. ")";
  58. return db->Execute(kCreateLatestOfflinePagesTableSql);
  59. }
  60. bool UpgradeWithQuery(sql::Database* db, const char* upgrade_sql) {
  61. if (!db->Execute("ALTER TABLE " OFFLINE_PAGES_TABLE_NAME
  62. " RENAME TO temp_" OFFLINE_PAGES_TABLE_NAME)) {
  63. return false;
  64. }
  65. static const char kCreateOfflinePagesTableVersion1Sql[] =
  66. "CREATE TABLE IF NOT EXISTS " OFFLINE_PAGES_TABLE_NAME
  67. "(offline_id INTEGER PRIMARY KEY NOT NULL,"
  68. " creation_time INTEGER NOT NULL,"
  69. " file_size INTEGER NOT NULL,"
  70. " last_access_time INTEGER NOT NULL,"
  71. " access_count INTEGER NOT NULL,"
  72. " system_download_id INTEGER NOT NULL DEFAULT 0,"
  73. " file_missing_time INTEGER NOT NULL DEFAULT 0,"
  74. " upgrade_attempt INTEGER NOT NULL DEFAULT 0,"
  75. " client_namespace VARCHAR NOT NULL,"
  76. " client_id VARCHAR NOT NULL,"
  77. " online_url VARCHAR NOT NULL,"
  78. " file_path VARCHAR NOT NULL,"
  79. " title VARCHAR NOT NULL DEFAULT '',"
  80. " original_url VARCHAR NOT NULL DEFAULT '',"
  81. " request_origin VARCHAR NOT NULL DEFAULT '',"
  82. " digest VARCHAR NOT NULL DEFAULT ''"
  83. ")";
  84. if (!db->Execute(kCreateOfflinePagesTableVersion1Sql))
  85. return false;
  86. if (!db->Execute(upgrade_sql))
  87. return false;
  88. if (!db->Execute("DROP TABLE IF EXISTS temp_" OFFLINE_PAGES_TABLE_NAME))
  89. return false;
  90. return true;
  91. }
  92. bool UpgradeFrom52(sql::Database* db) {
  93. static const char kSql[] =
  94. "INSERT INTO " OFFLINE_PAGES_TABLE_NAME
  95. " (offline_id, creation_time, file_size, last_access_time, "
  96. "access_count, client_namespace, client_id, "
  97. "online_url, file_path) "
  98. "SELECT "
  99. "offline_id, creation_time, file_size, last_access_time, "
  100. "access_count, client_namespace, client_id, "
  101. "online_url, file_path "
  102. "FROM temp_" OFFLINE_PAGES_TABLE_NAME;
  103. return UpgradeWithQuery(db, kSql);
  104. }
  105. bool UpgradeFrom53(sql::Database* db) {
  106. static const char kSql[] =
  107. "INSERT INTO " OFFLINE_PAGES_TABLE_NAME
  108. " (offline_id, creation_time, file_size, last_access_time, "
  109. "access_count, client_namespace, client_id, online_url, "
  110. "file_path) "
  111. "SELECT "
  112. "offline_id, creation_time, file_size, last_access_time, "
  113. "access_count, client_namespace, client_id, online_url, "
  114. "file_path "
  115. "FROM temp_" OFFLINE_PAGES_TABLE_NAME;
  116. return UpgradeWithQuery(db, kSql);
  117. }
  118. bool UpgradeFrom54(sql::Database* db) {
  119. static const char kSql[] =
  120. "INSERT INTO " OFFLINE_PAGES_TABLE_NAME
  121. " (offline_id, creation_time, file_size, last_access_time, "
  122. "access_count, client_namespace, client_id, online_url, "
  123. "file_path, title) "
  124. "SELECT "
  125. "offline_id, creation_time, file_size, last_access_time, "
  126. "access_count, client_namespace, client_id, online_url, "
  127. "file_path, title "
  128. "FROM temp_" OFFLINE_PAGES_TABLE_NAME;
  129. return UpgradeWithQuery(db, kSql);
  130. }
  131. bool UpgradeFrom55(sql::Database* db) {
  132. static const char kSql[] =
  133. "INSERT INTO " OFFLINE_PAGES_TABLE_NAME
  134. " (offline_id, creation_time, file_size, last_access_time, "
  135. "access_count, client_namespace, client_id, online_url, "
  136. "file_path, title) "
  137. "SELECT "
  138. "offline_id, creation_time, file_size, last_access_time, "
  139. "access_count, client_namespace, client_id, online_url, "
  140. "file_path, title "
  141. "FROM temp_" OFFLINE_PAGES_TABLE_NAME;
  142. return UpgradeWithQuery(db, kSql);
  143. }
  144. bool UpgradeFrom56(sql::Database* db) {
  145. static const char kSql[] =
  146. "INSERT INTO " OFFLINE_PAGES_TABLE_NAME
  147. " (offline_id, creation_time, file_size, last_access_time, "
  148. "access_count, client_namespace, client_id, online_url, "
  149. "file_path, title, original_url) "
  150. "SELECT "
  151. "offline_id, creation_time, file_size, last_access_time, "
  152. "access_count, client_namespace, client_id, online_url, "
  153. "file_path, title, original_url "
  154. "FROM temp_" OFFLINE_PAGES_TABLE_NAME;
  155. return UpgradeWithQuery(db, kSql);
  156. }
  157. bool UpgradeFrom57(sql::Database* db) {
  158. static const char kSql[] =
  159. "INSERT INTO " OFFLINE_PAGES_TABLE_NAME
  160. " (offline_id, creation_time, file_size, last_access_time, "
  161. "access_count, client_namespace, client_id, online_url, "
  162. "file_path, title, original_url) "
  163. "SELECT "
  164. "offline_id, creation_time, file_size, last_access_time, "
  165. "access_count, client_namespace, client_id, online_url, "
  166. "file_path, title, original_url "
  167. "FROM temp_" OFFLINE_PAGES_TABLE_NAME;
  168. return UpgradeWithQuery(db, kSql);
  169. }
  170. bool UpgradeFrom61(sql::Database* db) {
  171. static const char kSql[] =
  172. "INSERT INTO " OFFLINE_PAGES_TABLE_NAME
  173. " (offline_id, creation_time, file_size, last_access_time, "
  174. "access_count, client_namespace, client_id, online_url, "
  175. "file_path, title, original_url, request_origin) "
  176. "SELECT "
  177. "offline_id, creation_time, file_size, last_access_time, "
  178. "access_count, client_namespace, client_id, online_url, "
  179. "file_path, title, original_url, request_origin "
  180. "FROM temp_" OFFLINE_PAGES_TABLE_NAME;
  181. return UpgradeWithQuery(db, kSql);
  182. }
  183. bool CreatePageThumbnailsTable(sql::Database* db) {
  184. // TODO: The next schema change that modifies existing columns on this table
  185. // should also add "DEFAULT x''" to the definition of the "thumbnail" column.
  186. static const char kSql[] =
  187. "CREATE TABLE IF NOT EXISTS page_thumbnails"
  188. " (offline_id INTEGER PRIMARY KEY NOT NULL,"
  189. " expiration INTEGER NOT NULL,"
  190. " thumbnail BLOB NOT NULL,"
  191. " favicon BLOB NOT NULL DEFAULT x''"
  192. ")";
  193. return db->Execute(kSql);
  194. }
  195. bool CreateLatestSchema(sql::Database* db) {
  196. sql::Transaction transaction(db);
  197. if (!transaction.Begin())
  198. return false;
  199. // First time database initialization.
  200. if (!CreateOfflinePagesTable(db))
  201. return false;
  202. if (!CreatePageThumbnailsTable(db))
  203. return false;
  204. sql::MetaTable meta_table;
  205. if (!meta_table.Init(db, OfflinePageMetadataStore::kCurrentVersion,
  206. OfflinePageMetadataStore::kCompatibleVersion))
  207. return false;
  208. return transaction.Commit();
  209. }
  210. // Upgrades the database from before the database version was stored in the
  211. // MetaTable. This function should never need to be modified.
  212. bool UpgradeFromLegacyVersion(sql::Database* db) {
  213. sql::Transaction transaction(db);
  214. if (!transaction.Begin())
  215. return false;
  216. // Legacy upgrade section. Details are described in the header file.
  217. if (!db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "expiration_time") &&
  218. !db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "title")) {
  219. if (!UpgradeFrom52(db))
  220. return false;
  221. } else if (!db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "title")) {
  222. if (!UpgradeFrom53(db))
  223. return false;
  224. } else if (db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "offline_url")) {
  225. if (!UpgradeFrom54(db))
  226. return false;
  227. } else if (!db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "original_url")) {
  228. if (!UpgradeFrom55(db))
  229. return false;
  230. } else if (db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "expiration_time")) {
  231. if (!UpgradeFrom56(db))
  232. return false;
  233. } else if (!db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "request_origin")) {
  234. if (!UpgradeFrom57(db))
  235. return false;
  236. } else if (!db->DoesColumnExist(OFFLINE_PAGES_TABLE_NAME, "digest")) {
  237. if (!UpgradeFrom61(db))
  238. return false;
  239. }
  240. sql::MetaTable meta_table;
  241. if (!meta_table.Init(db, OfflinePageMetadataStore::kFirstPostLegacyVersion,
  242. OfflinePageMetadataStore::kCompatibleVersion))
  243. return false;
  244. return transaction.Commit();
  245. }
  246. bool UpgradeFromVersion1ToVersion2(sql::Database* db,
  247. sql::MetaTable* meta_table) {
  248. meta_table->SetVersionNumber(2);
  249. // No actual changes necessary, because upgrade_attempt was deprecated.
  250. return true;
  251. }
  252. bool UpgradeFromVersion2ToVersion3(sql::Database* db,
  253. sql::MetaTable* meta_table) {
  254. sql::Transaction transaction(db);
  255. if (!transaction.Begin())
  256. return false;
  257. static const char kCreatePageThumbnailsSql[] =
  258. "CREATE TABLE IF NOT EXISTS page_thumbnails"
  259. " (offline_id INTEGER PRIMARY KEY NOT NULL,"
  260. "expiration INTEGER NOT NULL,"
  261. "thumbnail BLOB NOT NULL"
  262. ")";
  263. if (!db->Execute(kCreatePageThumbnailsSql))
  264. return false;
  265. meta_table->SetVersionNumber(3);
  266. return transaction.Commit();
  267. }
  268. bool UpgradeFromVersion3ToVersion4(sql::Database* db,
  269. sql::MetaTable* meta_table) {
  270. sql::Transaction transaction(db);
  271. if (!transaction.Begin())
  272. return false;
  273. const char kSql[] = "ALTER TABLE " OFFLINE_PAGES_TABLE_NAME
  274. " ADD COLUMN snippet VARCHAR NOT NULL DEFAULT ''; "
  275. "ALTER TABLE " OFFLINE_PAGES_TABLE_NAME
  276. " ADD COLUMN attribution VARCHAR NOT NULL DEFAULT '';";
  277. if (!db->Execute(kSql))
  278. return false;
  279. const char kUpgradeThumbnailsTableSql[] =
  280. "ALTER TABLE page_thumbnails"
  281. " ADD COLUMN favicon BLOB NOT NULL DEFAULT x''";
  282. if (!db->Execute(kUpgradeThumbnailsTableSql))
  283. return false;
  284. meta_table->SetVersionNumber(4);
  285. return transaction.Commit();
  286. }
  287. bool CreateSchema(sql::Database* db) {
  288. if (!sql::MetaTable::DoesTableExist(db)) {
  289. // If this looks like a completely empty DB, simply start from scratch.
  290. if (!db->DoesTableExist(OFFLINE_PAGES_TABLE_NAME))
  291. return CreateLatestSchema(db);
  292. // Otherwise we need to run a legacy upgrade.
  293. if (!UpgradeFromLegacyVersion(db))
  294. return false;
  295. }
  296. sql::MetaTable meta_table;
  297. if (!meta_table.Init(db, OfflinePageMetadataStore::kCurrentVersion,
  298. OfflinePageMetadataStore::kCompatibleVersion))
  299. return false;
  300. for (;;) {
  301. switch (meta_table.GetVersionNumber()) {
  302. case 1:
  303. if (!UpgradeFromVersion1ToVersion2(db, &meta_table))
  304. return false;
  305. break;
  306. case 2:
  307. if (!UpgradeFromVersion2ToVersion3(db, &meta_table))
  308. return false;
  309. break;
  310. case 3:
  311. if (!UpgradeFromVersion3ToVersion4(db, &meta_table))
  312. return false;
  313. break;
  314. case OfflinePageMetadataStore::kCurrentVersion:
  315. return true;
  316. default:
  317. return false;
  318. }
  319. }
  320. }
  321. StoreState InitializationStatusToStoreState(
  322. SqlStoreBase::InitializationStatus status) {
  323. switch (status) {
  324. case SqlStoreBase::InitializationStatus::kNotInitialized:
  325. return StoreState::NOT_LOADED;
  326. case SqlStoreBase::InitializationStatus::kInProgress:
  327. return StoreState::INITIALIZING;
  328. case SqlStoreBase::InitializationStatus::kSuccess:
  329. return StoreState::LOADED;
  330. case SqlStoreBase::InitializationStatus::kFailure:
  331. return StoreState::FAILED_LOADING;
  332. }
  333. }
  334. } // anonymous namespace
  335. OfflinePageMetadataStore::OfflinePageMetadataStore(
  336. scoped_refptr<base::SequencedTaskRunner> background_task_runner)
  337. : SqlStoreBase("OfflinePageMetadata",
  338. std::move(background_task_runner),
  339. base::FilePath()) {}
  340. OfflinePageMetadataStore::OfflinePageMetadataStore(
  341. scoped_refptr<base::SequencedTaskRunner> background_task_runner,
  342. const base::FilePath& path)
  343. : SqlStoreBase("OfflinePageMetadata",
  344. std::move(background_task_runner),
  345. path.AppendASCII("OfflinePages.db")) {}
  346. OfflinePageMetadataStore::~OfflinePageMetadataStore() = default;
  347. base::OnceCallback<bool(sql::Database* db)>
  348. OfflinePageMetadataStore::GetSchemaInitializationFunction() {
  349. return base::BindOnce(&CreateSchema);
  350. }
  351. StoreState OfflinePageMetadataStore::GetStateForTesting() const {
  352. return InitializationStatusToStoreState(initialization_status_for_testing());
  353. }
  354. void OfflinePageMetadataStore::OnOpenStart(base::TimeTicks last_closing_time) {
  355. TRACE_EVENT_ASYNC_BEGIN1("offline_pages", "Metadata Store", this, "is reopen",
  356. !last_closing_time.is_null());
  357. ReportStoreEvent(last_closing_time.is_null()
  358. ? OfflinePagesStoreEvent::kOpenedFirstTime
  359. : OfflinePagesStoreEvent::kReopened);
  360. }
  361. void OfflinePageMetadataStore::OnOpenDone(bool success) {
  362. TRACE_EVENT_ASYNC_STEP_PAST1("offline_pages", "Metadata Store", this,
  363. "Initializing", "succeeded", success);
  364. if (!success) {
  365. TRACE_EVENT_ASYNC_END0("offline_pages", "Metadata Store", this);
  366. }
  367. }
  368. void OfflinePageMetadataStore::OnTaskBegin(bool is_initialized) {
  369. TRACE_EVENT_ASYNC_BEGIN1("offline_pages", "Metadata Store: task execution",
  370. this, "is store loaded", is_initialized);
  371. }
  372. void OfflinePageMetadataStore::OnTaskRunComplete() {
  373. // Note: the time recorded for this trace step will include thread hop wait
  374. // times to the background thread and back.
  375. TRACE_EVENT_ASYNC_STEP_PAST0("offline_pages",
  376. "Metadata Store: task execution", this, "Task");
  377. }
  378. void OfflinePageMetadataStore::OnTaskReturnComplete() {
  379. TRACE_EVENT_ASYNC_STEP_PAST0(
  380. "offline_pages", "Metadata Store: task execution", this, "Callback");
  381. TRACE_EVENT_ASYNC_END0("offline_pages", "Metadata Store: task execution",
  382. this);
  383. }
  384. void OfflinePageMetadataStore::OnCloseStart(
  385. InitializationStatus status_before_close) {
  386. if (status_before_close != InitializationStatus::kSuccess) {
  387. ReportStoreEvent(OfflinePagesStoreEvent::kCloseSkipped);
  388. return;
  389. }
  390. TRACE_EVENT_ASYNC_STEP_PAST0("offline_pages", "Metadata Store", this, "Open");
  391. ReportStoreEvent(OfflinePagesStoreEvent::kClosed);
  392. }
  393. void OfflinePageMetadataStore::OnCloseComplete() {
  394. TRACE_EVENT_ASYNC_STEP_PAST0("offline_pages", "Metadata Store", this,
  395. "Closing");
  396. TRACE_EVENT_ASYNC_END0("offline_pages", "Metadata Store", this);
  397. }
  398. } // namespace offline_pages