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

Side by Side Diff: components/offline_pages/background/request_queue_store_sql.cc

Issue 2053163002: [Offline pages] Adding persistent request queue based on SQLite (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fixing code comments Created 4 years, 6 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/background/request_queue_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/sequenced_task_runner.h"
12 #include "base/threading/thread_task_runner_handle.h"
13 #include "components/offline_pages/background/save_page_request.h"
14 #include "sql/connection.h"
15 #include "sql/statement.h"
16 #include "sql/transaction.h"
17
18 namespace offline_pages {
19
20 namespace {
21
22 // This is a macro instead of a const so that
23 // it can be used inline in other SQL statements below.
24 #define REQUEST_QUEUE_TABLE_NAME "request_queue_v1"
25
26 bool CreateRequestQueueTable(sql::Connection* db) {
27 const char kSql[] = "CREATE TABLE IF NOT EXISTS " REQUEST_QUEUE_TABLE_NAME
28 " (request_id INTEGER PRIMARY KEY NOT NULL,"
29 " creation_time INTEGER NOT NULL,"
30 " activation_time INTEGER NOT NULL DEFAULT 0,"
31 " last_attempt_time INTEGER NOT NULL DEFAULT 0,"
32 " attempt_count INTEGER NOT NULL,"
33 " url VARCHAR NOT NULL,"
34 " client_namespace VARCHAR NOT NULL,"
35 " client_id VARCHAR NOT NULL"
36 ")";
37 return db->Execute(kSql);
38 }
39
40 bool CreateSchema(sql::Connection* db) {
41 // TODO(fgorski): Upgrade code goes here and requires transaction.
42 if (!CreateRequestQueueTable(db))
43 return false;
44
45 // TODO(fgorski): Add indices here.
46 return true;
47 }
48
49 bool DeleteRequestById(sql::Connection* db, int64_t request_id) {
50 const char kSql[] =
51 "DELETE FROM " REQUEST_QUEUE_TABLE_NAME " WHERE request_id=?";
52 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
53 statement.BindInt64(0, request_id);
54 return statement.Run();
55 }
56
57 bool DeleteRequestsByIds(sql::Connection* db,
58 const std::vector<int64_t>& request_ids,
59 int* count) {
60 DCHECK(count);
61 // If you create a transaction but don't Commit() it is automatically
62 // rolled back by its destructor when it falls out of scope.
63 sql::Transaction transaction(db);
64 if (!transaction.Begin())
65 return false;
66
67 *count = 0;
68 for (auto request_id : request_ids) {
69 if (!DeleteRequestById(db, request_id))
70 return false;
71 *count += db->GetLastChangeCount();
72 }
73
74 if (!transaction.Commit())
75 return false;
76
77 return true;
78 }
79
80 // Create a save page request from a SQL result. Expects complete rows with
81 // all columns present. Columns are in order they are defined in select query
82 // in |RequestQueueStore::RequestSync| method.
83 SavePageRequest MakeSavePageRequest(const sql::Statement& statement) {
84 const int64_t id = statement.ColumnInt64(0);
85 const base::Time creation_time =
86 base::Time::FromInternalValue(statement.ColumnInt64(1));
87 const base::Time activation_time =
88 base::Time::FromInternalValue(statement.ColumnInt64(2));
89 const base::Time last_attempt_time =
90 base::Time::FromInternalValue(statement.ColumnInt64(3));
91 const int64_t last_attempt_count = statement.ColumnInt64(4);
92 const GURL url(statement.ColumnString(5));
93 const ClientId client_id(statement.ColumnString(6),
94 statement.ColumnString(7));
95
96 SavePageRequest request(id, url, client_id, creation_time, activation_time);
97 request.set_last_attempt_time(last_attempt_time);
98 request.set_attempt_count(last_attempt_count);
99 return request;
100 }
101
102 RequestQueueStore::UpdateStatus InsertOrReplace(
103 sql::Connection* db,
104 const SavePageRequest& request) {
105 // In order to use the enums in the Bind* methods, keep the order of fields
106 // the same as in the definition/select query.
107 const char kInsertSql[] =
108 "INSERT OR REPLACE INTO " REQUEST_QUEUE_TABLE_NAME
109 " (request_id, creation_time, activation_time, last_attempt_time, "
110 " attempt_count, url, client_namespace, client_id) "
111 " VALUES "
112 " (?, ?, ?, ?, ?, ?, ?, ?)";
113
114 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kInsertSql));
115 statement.BindInt64(0, request.request_id());
116 statement.BindInt64(1, request.creation_time().ToInternalValue());
117 statement.BindInt64(2, request.activation_time().ToInternalValue());
118 statement.BindInt64(3, request.last_attempt_time().ToInternalValue());
119 statement.BindInt64(4, request.attempt_count());
120 statement.BindString(5, request.url().spec());
121 statement.BindString(6, request.client_id().name_space);
122 statement.BindString(7, request.client_id().id);
123
124 // TODO(fgorski): Replace the UpdateStatus with boolean in the
125 // RequestQueueStore interface and update this code.
126 return statement.Run() ? RequestQueueStore::UpdateStatus::UPDATED
127 : RequestQueueStore::UpdateStatus::FAILED;
128 }
129
130 bool InitDatabase(sql::Connection* db, const base::FilePath& path) {
131 db->set_page_size(4096);
132 db->set_cache_size(500);
pasko 2017/03/31 12:29:16 curious: did the default values not work here for
fgorski 2017/03/31 16:15:21 This is following recommendation from shess@ made
133 db->set_histogram_tag("BackgroundRequestQueue");
134 db->set_exclusive_locking();
135
136 base::File::Error err;
137 if (!base::CreateDirectoryAndGetError(path.DirName(), &err))
138 return false;
139 if (!db->Open(path))
140 return false;
141 db->Preload();
142
143 return CreateSchema(db);
144 }
145
146 } // anonymous namespace
147
148 RequestQueueStoreSQL::RequestQueueStoreSQL(
149 scoped_refptr<base::SequencedTaskRunner> background_task_runner,
150 const base::FilePath& path)
151 : background_task_runner_(std::move(background_task_runner)),
152 db_file_path_(path.AppendASCII("RequestQueue.db")),
153 weak_ptr_factory_(this) {
154 OpenConnection();
155 }
156
157 RequestQueueStoreSQL::~RequestQueueStoreSQL() {
158 if (db_.get())
159 background_task_runner_->DeleteSoon(FROM_HERE, db_.release());
160 }
161
162 // static
163 void RequestQueueStoreSQL::OpenConnectionSync(
164 sql::Connection* db,
165 scoped_refptr<base::SingleThreadTaskRunner> runner,
166 const base::FilePath& path,
167 const base::Callback<void(bool)>& callback) {
168 bool success = InitDatabase(db, path);
169 runner->PostTask(FROM_HERE, base::Bind(callback, success));
170 }
171
172 // static
173 void RequestQueueStoreSQL::GetRequestsSync(
174 sql::Connection* db,
175 scoped_refptr<base::SingleThreadTaskRunner> runner,
176 const GetRequestsCallback& callback) {
177 const char kSql[] =
178 "SELECT request_id, creation_time, activation_time,"
179 " last_attempt_time, attempt_count, url, client_namespace, client_id"
180 " FROM " REQUEST_QUEUE_TABLE_NAME;
181
182 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
183
184 std::vector<SavePageRequest> result;
185 while (statement.Step())
186 result.push_back(MakeSavePageRequest(statement));
187
188 runner->PostTask(FROM_HERE,
189 base::Bind(callback, statement.Succeeded(), result));
190 }
191
192 // static
193 void RequestQueueStoreSQL::AddOrUpdateRequestSync(
194 sql::Connection* db,
195 scoped_refptr<base::SingleThreadTaskRunner> runner,
196 const SavePageRequest& request,
197 const UpdateCallback& callback) {
198 // TODO(fgorski): add UMA metrics here.
199 RequestQueueStore::UpdateStatus status = InsertOrReplace(db, request);
200 runner->PostTask(FROM_HERE, base::Bind(callback, status));
201 }
202
203 // static
204 void RequestQueueStoreSQL::RemoveRequestsSync(
205 sql::Connection* db,
206 scoped_refptr<base::SingleThreadTaskRunner> runner,
207 const std::vector<int64_t>& request_ids,
208 const RemoveCallback& callback) {
209 // TODO(fgorski): add UMA metrics here.
210 int count = 0;
211 if (DeleteRequestsByIds(db, request_ids, &count))
212 runner->PostTask(FROM_HERE, base::Bind(callback, true, count));
213 else
214 runner->PostTask(FROM_HERE, base::Bind(callback, false, 0));
215 }
216
217 // static
218 void RequestQueueStoreSQL::ResetSync(
219 sql::Connection* db,
220 const base::FilePath& db_file_path,
221 scoped_refptr<base::SingleThreadTaskRunner> runner,
222 const ResetCallback& callback) {
223 // This method deletes the content of the whole store and reinitializes it.
224 bool success = db->Raze();
225 db->Close();
226 if (success)
227 success = InitDatabase(db, db_file_path);
228 runner->PostTask(FROM_HERE, base::Bind(callback, success));
229 }
230
231 void RequestQueueStoreSQL::GetRequests(const GetRequestsCallback& callback) {
232 DCHECK(db_.get());
233 if (!db_.get()) {
234 // Nothing to do, but post a callback instead of calling directly
235 // to preserve the async style behavior to prevent bugs.
236 base::ThreadTaskRunnerHandle::Get()->PostTask(
237 FROM_HERE, base::Bind(callback, false, std::vector<SavePageRequest>()));
238 return;
239 }
240
241 background_task_runner_->PostTask(
242 FROM_HERE, base::Bind(&RequestQueueStoreSQL::GetRequestsSync, db_.get(),
243 base::ThreadTaskRunnerHandle::Get(), callback));
244 }
245
246 void RequestQueueStoreSQL::AddOrUpdateRequest(const SavePageRequest& request,
247 const UpdateCallback& callback) {
248 DCHECK(db_.get());
249 if (!db_.get()) {
250 // Nothing to do, but post a callback instead of calling directly
251 // to preserve the async style behavior to prevent bugs.
252 base::ThreadTaskRunnerHandle::Get()->PostTask(
253 FROM_HERE, base::Bind(callback, UpdateStatus::FAILED));
254 return;
255 }
256
257 background_task_runner_->PostTask(
258 FROM_HERE,
259 base::Bind(&RequestQueueStoreSQL::AddOrUpdateRequestSync, db_.get(),
260 base::ThreadTaskRunnerHandle::Get(), request, callback));
261 }
262
263 void RequestQueueStoreSQL::RemoveRequests(
264 const std::vector<int64_t>& request_ids,
265 const RemoveCallback& callback) {
266 DCHECK(db_.get());
267 if (!db_.get()) {
268 // Nothing to do, but post a callback instead of calling directly
269 // to preserve the async style behavior to prevent bugs.
270 base::ThreadTaskRunnerHandle::Get()->PostTask(
271 FROM_HERE, base::Bind(callback, false, 0));
272 return;
273 }
274
275 background_task_runner_->PostTask(
276 FROM_HERE,
277 base::Bind(&RequestQueueStoreSQL::RemoveRequestsSync, db_.get(),
278 base::ThreadTaskRunnerHandle::Get(), request_ids, callback));
279 }
280
281 void RequestQueueStoreSQL::Reset(const ResetCallback& callback) {
282 DCHECK(db_.get());
283 if (!db_.get()) {
284 // Nothing to do, but post a callback instead of calling directly
285 // to preserve the async style behavior to prevent bugs.
286 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
287 base::Bind(callback, false));
288 return;
289 }
290
291 background_task_runner_->PostTask(
292 FROM_HERE,
293 base::Bind(&RequestQueueStoreSQL::ResetSync, db_.get(), db_file_path_,
294 base::ThreadTaskRunnerHandle::Get(),
295 base::Bind(&RequestQueueStoreSQL::OnResetDone,
296 weak_ptr_factory_.GetWeakPtr(), callback)));
297 }
298
299 void RequestQueueStoreSQL::OpenConnection() {
300 DCHECK(!db_);
301 db_.reset(new sql::Connection());
302 background_task_runner_->PostTask(
303 FROM_HERE,
304 base::Bind(&RequestQueueStoreSQL::OpenConnectionSync, db_.get(),
305 base::ThreadTaskRunnerHandle::Get(), db_file_path_,
306 base::Bind(&RequestQueueStoreSQL::OnOpenConnectionDone,
307 weak_ptr_factory_.GetWeakPtr())));
308 }
309
310 void RequestQueueStoreSQL::OnOpenConnectionDone(bool success) {
311 DCHECK(db_.get());
312
313 // Unfortunately we were not able to open DB connection.
314 if (!success)
315 db_.reset();
316 }
317
318 void RequestQueueStoreSQL::OnResetDone(const ResetCallback& callback,
319 bool success) {
320 // Complete connection initialization post reset.
321 OnOpenConnectionDone(success);
322 callback.Run(success);
323 }
324
325 } // namespace offline_pages
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698