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

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: Updating the data definition code and getting rid of the enum to access columns 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. Columnd are in order they are defined in the table.
Scott Hess - ex-Googler 2016/06/22 18:31:45 s/Columnd/Columns/, I suspect.
fgorski 2016/06/22 23:32:16 Done.
82 SavePageRequest MakeSavePageRequest(const sql::Statement& statement) {
83 const int64_t id = statement.ColumnInt64(0);
84 const base::Time creation_time =
85 base::Time::FromInternalValue(statement.ColumnInt64(1));
86 const base::Time activation_time =
87 base::Time::FromInternalValue(statement.ColumnInt64(2));
88 const base::Time last_attempt_time =
89 base::Time::FromInternalValue(statement.ColumnInt64(3));
90 const int64_t last_attempt_count = statement.ColumnInt64(4);
91 const GURL url(statement.ColumnString(5));
92 const ClientId client_id(statement.ColumnString(6),
93 statement.ColumnString(7));
94
95 SavePageRequest request(id, url, client_id, creation_time, activation_time);
96 request.set_last_attempt_time(last_attempt_time);
97 request.set_attempt_count(last_attempt_count);
98 return request;
99 }
100
101 RequestQueueStore::UpdateStatus InsertOrReplace(
102 sql::Connection* db,
103 const SavePageRequest& request) {
104 // In order to use the enums in the Bind* methods, keep the order of fields
105 // the same as in the definition/select query.
106 const char kInsertSql[] =
107 "INSERT OR REPLACE INTO " REQUEST_QUEUE_TABLE_NAME
108 " (request_id, creation_time, activation_time, last_attempt_time, "
109 " attempt_count, url, client_namespace, client_id) "
110 " VALUES "
111 " (?, ?, ?, ?, ?, ?, ?, ?)";
112
113 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kInsertSql));
114 statement.BindInt64(0, request.request_id());
115 statement.BindInt64(1, request.creation_time().ToInternalValue());
116 statement.BindInt64(2, request.activation_time().ToInternalValue());
117 statement.BindInt64(3, request.last_attempt_time().ToInternalValue());
118 statement.BindInt64(4, request.attempt_count());
119 statement.BindString(5, request.url().spec());
120 statement.BindString(6, request.client_id().name_space);
121 statement.BindString(7, request.client_id().id);
122
123 // TODO(fgorski): Replace the UpdateStatus with boolean in the
124 // RequestQueueStore interface and update this code.
125 return statement.Run() ? RequestQueueStore::UpdateStatus::UPDATED
126 : RequestQueueStore::UpdateStatus::FAILED;
127 }
128
129 bool InitDatabase(sql::Connection* db, const base::FilePath& path) {
130 db->set_page_size(4096);
131 db->set_cache_size(500);
132 db->set_histogram_tag("BackgroundRequestQueue");
133 db->set_exclusive_locking();
134
135 base::File::Error err;
136 if (!base::CreateDirectoryAndGetError(path.DirName(), &err))
137 return false;
138 if (!db->Open(path))
139 return false;
140 db->Preload();
141
142 return CreateSchema(db);
143 }
144
145 } // anonymous namespace
146
147 RequestQueueStoreSQL::RequestQueueStoreSQL(
148 scoped_refptr<base::SequencedTaskRunner> background_task_runner,
149 const base::FilePath& path)
150 : background_task_runner_(std::move(background_task_runner)),
151 db_file_path_(path.AppendASCII("RequestQueue.db")),
152 weak_ptr_factory_(this) {
153 OpenConnection();
154 }
155
156 RequestQueueStoreSQL::~RequestQueueStoreSQL() {
157 if (db_.get())
158 background_task_runner_->DeleteSoon(FROM_HERE, db_.release());
159 }
160
161 // static
162 void RequestQueueStoreSQL::OpenConnectionSync(
163 sql::Connection* db,
164 scoped_refptr<base::SingleThreadTaskRunner> runner,
165 const base::FilePath& path,
166 const base::Callback<void(bool)>& callback) {
167 bool success = InitDatabase(db, path);
168 runner->PostTask(FROM_HERE, base::Bind(callback, success));
169 }
170
171 // static
172 void RequestQueueStoreSQL::GetRequestsSync(
173 sql::Connection* db,
174 scoped_refptr<base::SingleThreadTaskRunner> runner,
175 const GetRequestsCallback& callback) {
176 const char kSql[] =
177 "SELECT request_id, creation_time, activation_time,"
178 " last_attempt_time, attempt_count, url, client_namespace, client_id"
179 " FROM " REQUEST_QUEUE_TABLE_NAME;
Scott Hess - ex-Googler 2016/06/22 18:31:45 I like this. "SELECT * " generally doesn't surviv
fgorski 2016/06/22 23:32:16 Acknowledged. That is what I was going for. I init
180
181 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
182
183 std::vector<SavePageRequest> result;
184 while (statement.Step())
185 result.push_back(MakeSavePageRequest(statement));
186
187 runner->PostTask(FROM_HERE,
188 base::Bind(callback, statement.Succeeded(), result));
189 }
190
191 // static
192 void RequestQueueStoreSQL::AddOrUpdateRequestSync(
193 sql::Connection* db,
194 scoped_refptr<base::SingleThreadTaskRunner> runner,
195 const SavePageRequest& request,
196 const UpdateCallback& callback) {
197 // TODO(fgorski): add UMA metrics here.
198 RequestQueueStore::UpdateStatus status = InsertOrReplace(db, request);
199 runner->PostTask(FROM_HERE, base::Bind(callback, status));
200 }
201
202 // static
203 void RequestQueueStoreSQL::RemoveRequestsSync(
204 sql::Connection* db,
205 scoped_refptr<base::SingleThreadTaskRunner> runner,
206 const std::vector<int64_t>& request_ids,
207 const RemoveCallback& callback) {
208 // TODO(fgorski): add UMA metrics here.
209 int count = 0;
210 if (DeleteRequestsByIds(db, request_ids, &count))
211 runner->PostTask(FROM_HERE, base::Bind(callback, true, count));
212 else
213 runner->PostTask(FROM_HERE, base::Bind(callback, false, 0));
214 }
215
216 // static
217 void RequestQueueStoreSQL::ResetSync(
218 sql::Connection* db,
219 const base::FilePath& db_file_path,
220 scoped_refptr<base::SingleThreadTaskRunner> runner,
221 const ResetCallback& callback) {
222 // This method deletes the content of the whole store and reinitializes it.
223 bool success = db->Raze();
224 db->Close();
225 if (success)
226 success = InitDatabase(db, db_file_path);
227 runner->PostTask(FROM_HERE, base::Bind(callback, success));
228 }
229
230 void RequestQueueStoreSQL::GetRequests(const GetRequestsCallback& callback) {
231 DCHECK(db_.get());
232 if (!db_.get()) {
233 // Nothing to do, but post a callback instead of calling directly
234 // to preserve the async style behavior to prevent bugs.
235 base::ThreadTaskRunnerHandle::Get()->PostTask(
236 FROM_HERE, base::Bind(callback, false, std::vector<SavePageRequest>()));
237 return;
238 }
239
240 background_task_runner_->PostTask(
241 FROM_HERE, base::Bind(&RequestQueueStoreSQL::GetRequestsSync, db_.get(),
242 base::ThreadTaskRunnerHandle::Get(), callback));
243 }
244
245 void RequestQueueStoreSQL::AddOrUpdateRequest(const SavePageRequest& request,
246 const UpdateCallback& callback) {
247 DCHECK(db_.get());
248 if (!db_.get()) {
249 // Nothing to do, but post a callback instead of calling directly
250 // to preserve the async style behavior to prevent bugs.
251 base::ThreadTaskRunnerHandle::Get()->PostTask(
252 FROM_HERE, base::Bind(callback, UpdateStatus::FAILED));
253 return;
254 }
255
256 background_task_runner_->PostTask(
257 FROM_HERE,
258 base::Bind(&RequestQueueStoreSQL::AddOrUpdateRequestSync, db_.get(),
259 base::ThreadTaskRunnerHandle::Get(), request, callback));
260 }
261
262 void RequestQueueStoreSQL::RemoveRequests(
263 const std::vector<int64_t>& request_ids,
264 const RemoveCallback& callback) {
265 DCHECK(db_.get());
266 if (!db_.get()) {
267 // Nothing to do, but post a callback instead of calling directly
268 // to preserve the async style behavior to prevent bugs.
269 base::ThreadTaskRunnerHandle::Get()->PostTask(
270 FROM_HERE, base::Bind(callback, false, 0));
271 return;
272 }
273
274 background_task_runner_->PostTask(
275 FROM_HERE,
276 base::Bind(&RequestQueueStoreSQL::RemoveRequestsSync, db_.get(),
277 base::ThreadTaskRunnerHandle::Get(), request_ids, callback));
278 }
279
280 void RequestQueueStoreSQL::Reset(const ResetCallback& callback) {
281 DCHECK(db_.get());
282 if (!db_.get()) {
283 // Nothing to do, but post a callback instead of calling directly
284 // to preserve the async style behavior to prevent bugs.
285 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
286 base::Bind(callback, false));
287 return;
288 }
289
290 background_task_runner_->PostTask(
291 FROM_HERE,
292 base::Bind(&RequestQueueStoreSQL::ResetSync, db_.get(), db_file_path_,
293 base::ThreadTaskRunnerHandle::Get(),
294 base::Bind(&RequestQueueStoreSQL::OnResetDone,
295 weak_ptr_factory_.GetWeakPtr(), callback)));
296 }
297
298 void RequestQueueStoreSQL::OpenConnection() {
299 DCHECK(!db_);
300 db_.reset(new sql::Connection());
301 background_task_runner_->PostTask(
302 FROM_HERE,
303 base::Bind(&RequestQueueStoreSQL::OpenConnectionSync, db_.get(),
304 base::ThreadTaskRunnerHandle::Get(), db_file_path_,
305 base::Bind(&RequestQueueStoreSQL::OnOpenConnectionDone,
306 weak_ptr_factory_.GetWeakPtr())));
307 }
308
309 void RequestQueueStoreSQL::OnOpenConnectionDone(bool success) {
310 DCHECK(db_.get());
311
312 // Unfortunately we were not able to open DB connection.
313 if (!success)
314 db_.reset();
315 }
316
317 void RequestQueueStoreSQL::OnResetDone(const ResetCallback& callback,
318 bool success) {
319 // Complete connection initialization post reset.
320 OnOpenConnectionDone(success);
321 callback.Run(success);
322 }
323
324 } // namespace offline_pages
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698