OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "components/offline_pages/background/request_queue_store_sql.h" | 5 #include "components/offline_pages/background/request_queue_store_sql.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/files/file_path.h" | 8 #include "base/files/file_path.h" |
9 #include "base/files/file_util.h" | 9 #include "base/files/file_util.h" |
10 #include "base/location.h" | 10 #include "base/location.h" |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
54 if (!CreateRequestQueueTable(db)) | 54 if (!CreateRequestQueueTable(db)) |
55 return false; | 55 return false; |
56 | 56 |
57 // TODO(fgorski): Add indices here. | 57 // TODO(fgorski): Add indices here. |
58 return true; | 58 return true; |
59 } | 59 } |
60 | 60 |
61 // Create a save page request from a SQL result. Expects complete rows with | 61 // Create a save page request from a SQL result. Expects complete rows with |
62 // all columns present. Columns are in order they are defined in select query | 62 // all columns present. Columns are in order they are defined in select query |
63 // in |RequestQueueStore::RequestSync| method. | 63 // in |RequestQueueStore::RequestSync| method. |
64 SavePageRequest MakeSavePageRequest(const sql::Statement& statement) { | 64 std::unique_ptr<SavePageRequest> MakeSavePageRequest( |
| 65 const sql::Statement& statement) { |
65 const int64_t id = statement.ColumnInt64(0); | 66 const int64_t id = statement.ColumnInt64(0); |
66 const base::Time creation_time = | 67 const base::Time creation_time = |
67 base::Time::FromInternalValue(statement.ColumnInt64(1)); | 68 base::Time::FromInternalValue(statement.ColumnInt64(1)); |
68 const base::Time activation_time = | 69 const base::Time activation_time = |
69 base::Time::FromInternalValue(statement.ColumnInt64(2)); | 70 base::Time::FromInternalValue(statement.ColumnInt64(2)); |
70 const base::Time last_attempt_time = | 71 const base::Time last_attempt_time = |
71 base::Time::FromInternalValue(statement.ColumnInt64(3)); | 72 base::Time::FromInternalValue(statement.ColumnInt64(3)); |
72 const int64_t started_attempt_count = statement.ColumnInt64(4); | 73 const int64_t started_attempt_count = statement.ColumnInt64(4); |
73 const int64_t completed_attempt_count = statement.ColumnInt64(5); | 74 const int64_t completed_attempt_count = statement.ColumnInt64(5); |
74 const SavePageRequest::RequestState state = | 75 const SavePageRequest::RequestState state = |
75 static_cast<SavePageRequest::RequestState>(statement.ColumnInt64(6)); | 76 static_cast<SavePageRequest::RequestState>(statement.ColumnInt64(6)); |
76 const GURL url(statement.ColumnString(7)); | 77 const GURL url(statement.ColumnString(7)); |
77 const ClientId client_id(statement.ColumnString(8), | 78 const ClientId client_id(statement.ColumnString(8), |
78 statement.ColumnString(9)); | 79 statement.ColumnString(9)); |
79 | 80 |
80 DVLOG(2) << "making save page request - id " << id << " url " << url | 81 DVLOG(2) << "making save page request - id " << id << " url " << url |
81 << " client_id " << client_id.name_space << "-" << client_id.id | 82 << " client_id " << client_id.name_space << "-" << client_id.id |
82 << " creation time " << creation_time << " user requested " | 83 << " creation time " << creation_time << " user requested " |
83 << kUserRequested; | 84 << kUserRequested; |
84 | 85 |
85 SavePageRequest request(id, url, client_id, creation_time, activation_time, | 86 std::unique_ptr<SavePageRequest> request(new SavePageRequest( |
86 kUserRequested); | 87 id, url, client_id, creation_time, activation_time, kUserRequested)); |
87 request.set_last_attempt_time(last_attempt_time); | 88 request->set_last_attempt_time(last_attempt_time); |
88 request.set_started_attempt_count(started_attempt_count); | 89 request->set_started_attempt_count(started_attempt_count); |
89 request.set_completed_attempt_count(completed_attempt_count); | 90 request->set_completed_attempt_count(completed_attempt_count); |
90 request.set_request_state(state); | 91 request->set_request_state(state); |
91 return request; | 92 return request; |
92 } | 93 } |
93 | 94 |
94 // Get a request for a specific id. | 95 // Get a request for a specific id. |
95 SavePageRequest GetOneRequest(sql::Connection* db, const int64_t request_id) { | 96 std::unique_ptr<SavePageRequest> GetOneRequest(sql::Connection* db, |
| 97 const int64_t request_id) { |
96 const char kSql[] = | 98 const char kSql[] = |
97 "SELECT request_id, creation_time, activation_time," | 99 "SELECT request_id, creation_time, activation_time," |
98 " last_attempt_time, started_attempt_count, completed_attempt_count," | 100 " last_attempt_time, started_attempt_count, completed_attempt_count," |
99 " state, url, client_namespace, client_id" | 101 " state, url, client_namespace, client_id" |
100 " FROM " REQUEST_QUEUE_TABLE_NAME " WHERE request_id=?"; | 102 " FROM " REQUEST_QUEUE_TABLE_NAME " WHERE request_id=?"; |
101 | 103 |
102 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql)); | 104 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql)); |
103 statement.BindInt64(0, request_id); | 105 statement.BindInt64(0, request_id); |
104 | 106 |
105 statement.Run(); | 107 statement.Run(); |
(...skipping 21 matching lines...) Expand all Loading... |
127 const SavePageRequest::RequestState new_state) { | 129 const SavePageRequest::RequestState new_state) { |
128 const char kSql[] = "UPDATE " REQUEST_QUEUE_TABLE_NAME | 130 const char kSql[] = "UPDATE " REQUEST_QUEUE_TABLE_NAME |
129 " SET state=?" | 131 " SET state=?" |
130 " WHERE request_id=?"; | 132 " WHERE request_id=?"; |
131 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql)); | 133 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql)); |
132 statement.BindInt64(0, static_cast<int64_t>(new_state)); | 134 statement.BindInt64(0, static_cast<int64_t>(new_state)); |
133 statement.BindInt64(1, request_id); | 135 statement.BindInt64(1, request_id); |
134 return statement.Run(); | 136 return statement.Run(); |
135 } | 137 } |
136 | 138 |
137 bool DeleteRequestsByIds(sql::Connection* db, | 139 // Helper function to delete requests corresponding to passed in requestIds, |
138 const std::vector<int64_t>& request_ids, | 140 // and fill an outparam with the removed requests. |
139 RequestQueue::UpdateMultipleRequestResults& results, | 141 bool DeleteRequestsByIds( |
140 std::vector<SavePageRequest>& requests) { | 142 sql::Connection* db, |
| 143 const std::vector<int64_t>& request_ids, |
| 144 RequestQueue::UpdateMultipleRequestResults& results, |
| 145 std::vector<std::unique_ptr<SavePageRequest>>& requests) { |
141 // If you create a transaction but don't Commit() it is automatically | 146 // If you create a transaction but don't Commit() it is automatically |
142 // rolled back by its destructor when it falls out of scope. | 147 // rolled back by its destructor when it falls out of scope. |
143 sql::Transaction transaction(db); | 148 sql::Transaction transaction(db); |
144 if (!transaction.Begin()) { | 149 if (!transaction.Begin()) { |
145 BuildFailedResultList(request_ids, results); | 150 BuildFailedResultList(request_ids, results); |
146 return false; | 151 return false; |
147 } | 152 } |
148 | 153 |
149 // Read the request before we delete it, and if the delete worked, put it on | 154 // Read the request before we delete it, and if the delete worked, put it on |
150 // the queue of requests that got deleted. | 155 // the queue of requests that got deleted. |
151 for (int64_t request_id : request_ids) { | 156 for (int64_t request_id : request_ids) { |
152 SavePageRequest request = GetOneRequest(db, request_id); | 157 std::unique_ptr<SavePageRequest> request = GetOneRequest(db, request_id); |
153 RequestQueue::UpdateRequestResult result; | 158 RequestQueue::UpdateRequestResult result; |
154 if (DeleteRequestById(db, request_id)) { | 159 if (DeleteRequestById(db, request_id)) { |
155 result = RequestQueue::UpdateRequestResult::SUCCESS; | 160 result = RequestQueue::UpdateRequestResult::SUCCESS; |
156 requests.push_back(request); | 161 requests.push_back(std::move(request)); |
157 } else { | 162 } else { |
158 result = RequestQueue::UpdateRequestResult::REQUEST_DOES_NOT_EXIST; | 163 result = RequestQueue::UpdateRequestResult::REQUEST_DOES_NOT_EXIST; |
159 } | 164 } |
160 results.push_back(std::make_pair(request_id, result)); | 165 results.push_back(std::make_pair(request_id, result)); |
161 requests.push_back(request); | 166 requests.push_back(std::move(request)); |
162 } | 167 } |
163 | 168 |
164 if (!transaction.Commit()) { | 169 if (!transaction.Commit()) { |
165 requests.clear(); | 170 requests.clear(); |
166 BuildFailedResultList(request_ids, results); | 171 BuildFailedResultList(request_ids, results); |
167 return false; | 172 return false; |
168 } | 173 } |
169 | 174 |
170 return true; | 175 return true; |
171 } | 176 } |
172 | 177 |
173 bool ChangeRequestsState(sql::Connection* db, | 178 bool ChangeRequestsState( |
174 const std::vector<int64_t>& request_ids, | 179 sql::Connection* db, |
175 SavePageRequest::RequestState new_state, | 180 const std::vector<int64_t>& request_ids, |
176 RequestQueue::UpdateMultipleRequestResults& results, | 181 SavePageRequest::RequestState new_state, |
177 std::vector<SavePageRequest>& requests) { | 182 RequestQueue::UpdateMultipleRequestResults& results, |
| 183 std::vector<std::unique_ptr<SavePageRequest>>& requests) { |
178 // If you create a transaction but don't Commit() it is automatically | 184 // If you create a transaction but don't Commit() it is automatically |
179 // rolled back by its destructor when it falls out of scope. | 185 // rolled back by its destructor when it falls out of scope. |
180 sql::Transaction transaction(db); | 186 sql::Transaction transaction(db); |
181 if (!transaction.Begin()) { | 187 if (!transaction.Begin()) { |
182 BuildFailedResultList(request_ids, results); | 188 BuildFailedResultList(request_ids, results); |
183 return false; | 189 return false; |
184 } | 190 } |
185 | 191 |
186 // Update a request, then get it, and put the item we got on the output list. | 192 // Update a request, then get it, and put the item we got on the output list. |
187 for (const auto& request_id : request_ids) { | 193 for (const auto& request_id : request_ids) { |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
285 scoped_refptr<base::SingleThreadTaskRunner> runner, | 291 scoped_refptr<base::SingleThreadTaskRunner> runner, |
286 const GetRequestsCallback& callback) { | 292 const GetRequestsCallback& callback) { |
287 const char kSql[] = | 293 const char kSql[] = |
288 "SELECT request_id, creation_time, activation_time," | 294 "SELECT request_id, creation_time, activation_time," |
289 " last_attempt_time, started_attempt_count, completed_attempt_count," | 295 " last_attempt_time, started_attempt_count, completed_attempt_count," |
290 " state, url, client_namespace, client_id" | 296 " state, url, client_namespace, client_id" |
291 " FROM " REQUEST_QUEUE_TABLE_NAME; | 297 " FROM " REQUEST_QUEUE_TABLE_NAME; |
292 | 298 |
293 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql)); | 299 sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql)); |
294 | 300 |
295 std::vector<SavePageRequest> result; | 301 std::vector<std::unique_ptr<SavePageRequest>> requests; |
296 while (statement.Step()) | 302 while (statement.Step()) |
297 result.push_back(MakeSavePageRequest(statement)); | 303 requests.push_back(MakeSavePageRequest(statement)); |
298 | 304 |
299 runner->PostTask(FROM_HERE, | 305 runner->PostTask(FROM_HERE, base::Bind(callback, statement.Succeeded(), |
300 base::Bind(callback, statement.Succeeded(), result)); | 306 base::Passed(std::move(requests)))); |
301 } | 307 } |
302 | 308 |
303 // static | 309 // static |
304 void RequestQueueStoreSQL::AddOrUpdateRequestSync( | 310 void RequestQueueStoreSQL::AddOrUpdateRequestSync( |
305 sql::Connection* db, | 311 sql::Connection* db, |
306 scoped_refptr<base::SingleThreadTaskRunner> runner, | 312 scoped_refptr<base::SingleThreadTaskRunner> runner, |
307 const SavePageRequest& request, | 313 const SavePageRequest& request, |
308 const UpdateCallback& callback) { | 314 const UpdateCallback& callback) { |
309 // TODO(fgorski): add UMA metrics here. | 315 // TODO(fgorski): add UMA metrics here. |
310 RequestQueueStore::UpdateStatus status = InsertOrReplace(db, request); | 316 RequestQueueStore::UpdateStatus status = InsertOrReplace(db, request); |
311 runner->PostTask(FROM_HERE, base::Bind(callback, status)); | 317 runner->PostTask(FROM_HERE, base::Bind(callback, status)); |
312 } | 318 } |
313 | 319 |
314 // static | 320 // static |
315 void RequestQueueStoreSQL::RemoveRequestsSync( | 321 void RequestQueueStoreSQL::RemoveRequestsSync( |
316 sql::Connection* db, | 322 sql::Connection* db, |
317 scoped_refptr<base::SingleThreadTaskRunner> runner, | 323 scoped_refptr<base::SingleThreadTaskRunner> runner, |
318 const std::vector<int64_t>& request_ids, | 324 const std::vector<int64_t>& request_ids, |
319 const RemoveCallback& callback) { | 325 const RemoveCallback& callback) { |
320 RequestQueue::UpdateMultipleRequestResults results; | 326 RequestQueue::UpdateMultipleRequestResults results; |
321 std::vector<SavePageRequest> requests; | 327 std::vector<std::unique_ptr<SavePageRequest>> requests; |
322 // TODO(fgorski): add UMA metrics here. | 328 // TODO(fgorski): add UMA metrics here. |
323 DeleteRequestsByIds(db, request_ids, results, requests); | 329 DeleteRequestsByIds(db, request_ids, results, requests); |
324 runner->PostTask(FROM_HERE, base::Bind(callback, results, requests)); | 330 runner->PostTask(FROM_HERE, base::Bind(callback, results, |
| 331 base::Passed(std::move(requests)))); |
325 } | 332 } |
326 | 333 |
327 // static | 334 // static |
328 void RequestQueueStoreSQL::ChangeRequestsStateSync( | 335 void RequestQueueStoreSQL::ChangeRequestsStateSync( |
329 sql::Connection* db, | 336 sql::Connection* db, |
330 scoped_refptr<base::SingleThreadTaskRunner> runner, | 337 scoped_refptr<base::SingleThreadTaskRunner> runner, |
331 const std::vector<int64_t>& request_ids, | 338 const std::vector<int64_t>& request_ids, |
332 const SavePageRequest::RequestState new_state, | 339 const SavePageRequest::RequestState new_state, |
333 const UpdateMultipleRequestsCallback& callback) { | 340 const UpdateMultipleRequestsCallback& callback) { |
334 RequestQueue::UpdateMultipleRequestResults results; | 341 RequestQueue::UpdateMultipleRequestResults results; |
335 std::vector<SavePageRequest> requests; | 342 std::vector<std::unique_ptr<SavePageRequest>> requests; |
336 // TODO(fgorski): add UMA metrics here. | 343 // TODO(fgorski): add UMA metrics here. |
337 offline_pages::ChangeRequestsState(db, request_ids, new_state, results, | 344 offline_pages::ChangeRequestsState(db, request_ids, new_state, results, |
338 requests); | 345 requests); |
339 runner->PostTask(FROM_HERE, base::Bind(callback, results, requests)); | 346 runner->PostTask(FROM_HERE, base::Bind(callback, results, |
| 347 base::Passed(std::move(requests)))); |
340 } | 348 } |
341 | 349 |
342 // static | 350 // static |
343 void RequestQueueStoreSQL::ResetSync( | 351 void RequestQueueStoreSQL::ResetSync( |
344 sql::Connection* db, | 352 sql::Connection* db, |
345 const base::FilePath& db_file_path, | 353 const base::FilePath& db_file_path, |
346 scoped_refptr<base::SingleThreadTaskRunner> runner, | 354 scoped_refptr<base::SingleThreadTaskRunner> runner, |
347 const ResetCallback& callback) { | 355 const ResetCallback& callback) { |
348 // This method deletes the content of the whole store and reinitializes it. | 356 // This method deletes the content of the whole store and reinitializes it. |
349 bool success = db->Raze(); | 357 bool success = db->Raze(); |
(...skipping 10 matching lines...) Expand all Loading... |
360 // to preserve the async style behavior to prevent bugs. | 368 // to preserve the async style behavior to prevent bugs. |
361 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, | 369 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, |
362 base::Bind(callback)); | 370 base::Bind(callback)); |
363 return false; | 371 return false; |
364 } | 372 } |
365 return true; | 373 return true; |
366 } | 374 } |
367 | 375 |
368 void RequestQueueStoreSQL::GetRequests(const GetRequestsCallback& callback) { | 376 void RequestQueueStoreSQL::GetRequests(const GetRequestsCallback& callback) { |
369 DCHECK(db_.get()); | 377 DCHECK(db_.get()); |
370 if (!CheckDb(base::Bind(callback, false, std::vector<SavePageRequest>()))) | 378 std::vector<std::unique_ptr<SavePageRequest>> requests; |
| 379 if (!CheckDb(base::Bind(callback, false, base::Passed(std::move(requests))))) |
371 return; | 380 return; |
372 | 381 |
373 background_task_runner_->PostTask( | 382 background_task_runner_->PostTask( |
374 FROM_HERE, base::Bind(&RequestQueueStoreSQL::GetRequestsSync, db_.get(), | 383 FROM_HERE, base::Bind(&RequestQueueStoreSQL::GetRequestsSync, db_.get(), |
375 base::ThreadTaskRunnerHandle::Get(), callback)); | 384 base::ThreadTaskRunnerHandle::Get(), callback)); |
376 } | 385 } |
377 | 386 |
378 void RequestQueueStoreSQL::AddOrUpdateRequest(const SavePageRequest& request, | 387 void RequestQueueStoreSQL::AddOrUpdateRequest(const SavePageRequest& request, |
379 const UpdateCallback& callback) { | 388 const UpdateCallback& callback) { |
380 DCHECK(db_.get()); | 389 DCHECK(db_.get()); |
381 if (!CheckDb(base::Bind(callback, UpdateStatus::FAILED))) | 390 if (!CheckDb(base::Bind(callback, UpdateStatus::FAILED))) |
382 return; | 391 return; |
383 | 392 |
384 background_task_runner_->PostTask( | 393 background_task_runner_->PostTask( |
385 FROM_HERE, | 394 FROM_HERE, |
386 base::Bind(&RequestQueueStoreSQL::AddOrUpdateRequestSync, db_.get(), | 395 base::Bind(&RequestQueueStoreSQL::AddOrUpdateRequestSync, db_.get(), |
387 base::ThreadTaskRunnerHandle::Get(), request, callback)); | 396 base::ThreadTaskRunnerHandle::Get(), request, callback)); |
388 } | 397 } |
389 | 398 |
390 // RemoveRequestsByRequestId to be more parallell with RemoveRequestsByClientId. | 399 // RemoveRequestsByRequestId to be more parallell with RemoveRequestsByClientId. |
391 void RequestQueueStoreSQL::RemoveRequests( | 400 void RequestQueueStoreSQL::RemoveRequests( |
392 const std::vector<int64_t>& request_ids, | 401 const std::vector<int64_t>& request_ids, |
393 const RemoveCallback& callback) { | 402 const RemoveCallback& callback) { |
394 // Set up a failed set of results in case we fail the DB check. | 403 // Set up a failed set of results in case we fail the DB check. |
395 RequestQueue::UpdateMultipleRequestResults results; | 404 RequestQueue::UpdateMultipleRequestResults results; |
396 std::vector<SavePageRequest> requests; | |
397 for (int64_t request_id : request_ids) | 405 for (int64_t request_id : request_ids) |
398 results.push_back(std::make_pair( | 406 results.push_back(std::make_pair( |
399 request_id, RequestQueue::UpdateRequestResult::STORE_FAILURE)); | 407 request_id, RequestQueue::UpdateRequestResult::STORE_FAILURE)); |
400 | 408 |
401 if (!CheckDb(base::Bind(callback, results, requests))) | 409 if (!CheckDb(base::Bind( |
| 410 callback, results, |
| 411 base::Passed(std::vector<std::unique_ptr<SavePageRequest>>())))) |
402 return; | 412 return; |
403 | 413 |
404 background_task_runner_->PostTask( | 414 background_task_runner_->PostTask( |
405 FROM_HERE, | 415 FROM_HERE, |
406 base::Bind(&RequestQueueStoreSQL::RemoveRequestsSync, db_.get(), | 416 base::Bind(&RequestQueueStoreSQL::RemoveRequestsSync, db_.get(), |
407 base::ThreadTaskRunnerHandle::Get(), request_ids, callback)); | 417 base::ThreadTaskRunnerHandle::Get(), request_ids, callback)); |
408 } | 418 } |
409 | 419 |
410 void RequestQueueStoreSQL::ChangeRequestsState( | 420 void RequestQueueStoreSQL::ChangeRequestsState( |
411 const std::vector<int64_t>& request_ids, | 421 const std::vector<int64_t>& request_ids, |
412 const SavePageRequest::RequestState new_state, | 422 const SavePageRequest::RequestState new_state, |
413 const UpdateMultipleRequestsCallback& callback) { | 423 const UpdateMultipleRequestsCallback& callback) { |
414 RequestQueue::UpdateMultipleRequestResults results; | 424 RequestQueue::UpdateMultipleRequestResults results; |
415 std::vector<SavePageRequest> requests; | 425 std::vector<std::unique_ptr<SavePageRequest>> requests; |
416 if (!CheckDb(base::Bind(callback, results, requests))) | 426 if (!CheckDb( |
| 427 base::Bind(callback, results, base::Passed(std::move(requests))))) |
417 return; | 428 return; |
418 | 429 |
419 background_task_runner_->PostTask( | 430 background_task_runner_->PostTask( |
420 FROM_HERE, base::Bind(&RequestQueueStoreSQL::ChangeRequestsStateSync, | 431 FROM_HERE, base::Bind(&RequestQueueStoreSQL::ChangeRequestsStateSync, |
421 db_.get(), base::ThreadTaskRunnerHandle::Get(), | 432 db_.get(), base::ThreadTaskRunnerHandle::Get(), |
422 request_ids, new_state, callback)); | 433 request_ids, new_state, callback)); |
423 } | 434 } |
424 | 435 |
425 void RequestQueueStoreSQL::Reset(const ResetCallback& callback) { | 436 void RequestQueueStoreSQL::Reset(const ResetCallback& callback) { |
426 DCHECK(db_.get()); | 437 DCHECK(db_.get()); |
(...skipping 28 matching lines...) Expand all Loading... |
455 } | 466 } |
456 | 467 |
457 void RequestQueueStoreSQL::OnResetDone(const ResetCallback& callback, | 468 void RequestQueueStoreSQL::OnResetDone(const ResetCallback& callback, |
458 bool success) { | 469 bool success) { |
459 // Complete connection initialization post reset. | 470 // Complete connection initialization post reset. |
460 OnOpenConnectionDone(success); | 471 OnOpenConnectionDone(success); |
461 callback.Run(success); | 472 callback.Run(success); |
462 } | 473 } |
463 | 474 |
464 } // namespace offline_pages | 475 } // namespace offline_pages |
OLD | NEW |