OLD | NEW |
| (Empty) |
1 // Copyright 2013 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 "webkit/browser/quota/quota_database.h" | |
6 | |
7 #include <string> | |
8 #include <vector> | |
9 | |
10 #include "base/auto_reset.h" | |
11 #include "base/bind.h" | |
12 #include "base/file_util.h" | |
13 #include "base/time/time.h" | |
14 #include "sql/connection.h" | |
15 #include "sql/meta_table.h" | |
16 #include "sql/statement.h" | |
17 #include "sql/transaction.h" | |
18 #include "url/gurl.h" | |
19 #include "webkit/browser/quota/special_storage_policy.h" | |
20 | |
21 namespace storage { | |
22 namespace { | |
23 | |
24 // Definitions for database schema. | |
25 | |
26 const int kCurrentVersion = 4; | |
27 const int kCompatibleVersion = 2; | |
28 | |
29 const char kHostQuotaTable[] = "HostQuotaTable"; | |
30 const char kOriginInfoTable[] = "OriginInfoTable"; | |
31 const char kIsOriginTableBootstrapped[] = "IsOriginTableBootstrapped"; | |
32 | |
33 bool VerifyValidQuotaConfig(const char* key) { | |
34 return (key != NULL && | |
35 (!strcmp(key, QuotaDatabase::kDesiredAvailableSpaceKey) || | |
36 !strcmp(key, QuotaDatabase::kTemporaryQuotaOverrideKey))); | |
37 } | |
38 | |
39 const int kCommitIntervalMs = 30000; | |
40 | |
41 } // anonymous namespace | |
42 | |
43 // static | |
44 const char QuotaDatabase::kDesiredAvailableSpaceKey[] = "DesiredAvailableSpace"; | |
45 const char QuotaDatabase::kTemporaryQuotaOverrideKey[] = | |
46 "TemporaryQuotaOverride"; | |
47 | |
48 const QuotaDatabase::TableSchema QuotaDatabase::kTables[] = { | |
49 { kHostQuotaTable, | |
50 "(host TEXT NOT NULL," | |
51 " type INTEGER NOT NULL," | |
52 " quota INTEGER DEFAULT 0," | |
53 " UNIQUE(host, type))" }, | |
54 { kOriginInfoTable, | |
55 "(origin TEXT NOT NULL," | |
56 " type INTEGER NOT NULL," | |
57 " used_count INTEGER DEFAULT 0," | |
58 " last_access_time INTEGER DEFAULT 0," | |
59 " last_modified_time INTEGER DEFAULT 0," | |
60 " UNIQUE(origin, type))" }, | |
61 }; | |
62 | |
63 // static | |
64 const QuotaDatabase::IndexSchema QuotaDatabase::kIndexes[] = { | |
65 { "HostIndex", | |
66 kHostQuotaTable, | |
67 "(host)", | |
68 false }, | |
69 { "OriginInfoIndex", | |
70 kOriginInfoTable, | |
71 "(origin)", | |
72 false }, | |
73 { "OriginLastAccessTimeIndex", | |
74 kOriginInfoTable, | |
75 "(last_access_time)", | |
76 false }, | |
77 { "OriginLastModifiedTimeIndex", | |
78 kOriginInfoTable, | |
79 "(last_modified_time)", | |
80 false }, | |
81 }; | |
82 | |
83 struct QuotaDatabase::QuotaTableImporter { | |
84 bool Append(const QuotaTableEntry& entry) { | |
85 entries.push_back(entry); | |
86 return true; | |
87 } | |
88 std::vector<QuotaTableEntry> entries; | |
89 }; | |
90 | |
91 // Clang requires explicit out-of-line constructors for them. | |
92 QuotaDatabase::QuotaTableEntry::QuotaTableEntry() | |
93 : type(kStorageTypeUnknown), | |
94 quota(0) { | |
95 } | |
96 | |
97 QuotaDatabase::QuotaTableEntry::QuotaTableEntry( | |
98 const std::string& host, | |
99 StorageType type, | |
100 int64 quota) | |
101 : host(host), | |
102 type(type), | |
103 quota(quota) { | |
104 } | |
105 | |
106 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry() | |
107 : type(kStorageTypeUnknown), | |
108 used_count(0) { | |
109 } | |
110 | |
111 QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry( | |
112 const GURL& origin, | |
113 StorageType type, | |
114 int used_count, | |
115 const base::Time& last_access_time, | |
116 const base::Time& last_modified_time) | |
117 : origin(origin), | |
118 type(type), | |
119 used_count(used_count), | |
120 last_access_time(last_access_time), | |
121 last_modified_time(last_modified_time) { | |
122 } | |
123 | |
124 // QuotaDatabase ------------------------------------------------------------ | |
125 QuotaDatabase::QuotaDatabase(const base::FilePath& path) | |
126 : db_file_path_(path), | |
127 is_recreating_(false), | |
128 is_disabled_(false) { | |
129 } | |
130 | |
131 QuotaDatabase::~QuotaDatabase() { | |
132 if (db_) { | |
133 db_->CommitTransaction(); | |
134 } | |
135 } | |
136 | |
137 void QuotaDatabase::CloseConnection() { | |
138 meta_table_.reset(); | |
139 db_.reset(); | |
140 } | |
141 | |
142 bool QuotaDatabase::GetHostQuota( | |
143 const std::string& host, StorageType type, int64* quota) { | |
144 DCHECK(quota); | |
145 if (!LazyOpen(false)) | |
146 return false; | |
147 | |
148 const char* kSql = | |
149 "SELECT quota" | |
150 " FROM HostQuotaTable" | |
151 " WHERE host = ? AND type = ?"; | |
152 | |
153 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
154 statement.BindString(0, host); | |
155 statement.BindInt(1, static_cast<int>(type)); | |
156 | |
157 if (!statement.Step()) | |
158 return false; | |
159 | |
160 *quota = statement.ColumnInt64(0); | |
161 return true; | |
162 } | |
163 | |
164 bool QuotaDatabase::SetHostQuota( | |
165 const std::string& host, StorageType type, int64 quota) { | |
166 DCHECK_GE(quota, 0); | |
167 if (!LazyOpen(true)) | |
168 return false; | |
169 | |
170 const char* kSql = | |
171 "INSERT OR REPLACE INTO HostQuotaTable" | |
172 " (quota, host, type)" | |
173 " VALUES (?, ?, ?)"; | |
174 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
175 statement.BindInt64(0, quota); | |
176 statement.BindString(1, host); | |
177 statement.BindInt(2, static_cast<int>(type)); | |
178 | |
179 if (!statement.Run()) | |
180 return false; | |
181 | |
182 ScheduleCommit(); | |
183 return true; | |
184 } | |
185 | |
186 bool QuotaDatabase::SetOriginLastAccessTime( | |
187 const GURL& origin, StorageType type, base::Time last_access_time) { | |
188 if (!LazyOpen(true)) | |
189 return false; | |
190 | |
191 sql::Statement statement; | |
192 | |
193 int used_count = 1; | |
194 if (FindOriginUsedCount(origin, type, &used_count)) { | |
195 ++used_count; | |
196 const char* kSql = | |
197 "UPDATE OriginInfoTable" | |
198 " SET used_count = ?, last_access_time = ?" | |
199 " WHERE origin = ? AND type = ?"; | |
200 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
201 } else { | |
202 const char* kSql = | |
203 "INSERT INTO OriginInfoTable" | |
204 " (used_count, last_access_time, origin, type)" | |
205 " VALUES (?, ?, ?, ?)"; | |
206 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
207 } | |
208 statement.BindInt(0, used_count); | |
209 statement.BindInt64(1, last_access_time.ToInternalValue()); | |
210 statement.BindString(2, origin.spec()); | |
211 statement.BindInt(3, static_cast<int>(type)); | |
212 | |
213 if (!statement.Run()) | |
214 return false; | |
215 | |
216 ScheduleCommit(); | |
217 return true; | |
218 } | |
219 | |
220 bool QuotaDatabase::SetOriginLastModifiedTime( | |
221 const GURL& origin, StorageType type, base::Time last_modified_time) { | |
222 if (!LazyOpen(true)) | |
223 return false; | |
224 | |
225 sql::Statement statement; | |
226 | |
227 int dummy; | |
228 if (FindOriginUsedCount(origin, type, &dummy)) { | |
229 const char* kSql = | |
230 "UPDATE OriginInfoTable" | |
231 " SET last_modified_time = ?" | |
232 " WHERE origin = ? AND type = ?"; | |
233 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
234 } else { | |
235 const char* kSql = | |
236 "INSERT INTO OriginInfoTable" | |
237 " (last_modified_time, origin, type) VALUES (?, ?, ?)"; | |
238 statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
239 } | |
240 statement.BindInt64(0, last_modified_time.ToInternalValue()); | |
241 statement.BindString(1, origin.spec()); | |
242 statement.BindInt(2, static_cast<int>(type)); | |
243 | |
244 if (!statement.Run()) | |
245 return false; | |
246 | |
247 ScheduleCommit(); | |
248 return true; | |
249 } | |
250 | |
251 bool QuotaDatabase::RegisterInitialOriginInfo( | |
252 const std::set<GURL>& origins, StorageType type) { | |
253 if (!LazyOpen(true)) | |
254 return false; | |
255 | |
256 typedef std::set<GURL>::const_iterator itr_type; | |
257 for (itr_type itr = origins.begin(), end = origins.end(); | |
258 itr != end; ++itr) { | |
259 const char* kSql = | |
260 "INSERT OR IGNORE INTO OriginInfoTable" | |
261 " (origin, type) VALUES (?, ?)"; | |
262 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
263 statement.BindString(0, itr->spec()); | |
264 statement.BindInt(1, static_cast<int>(type)); | |
265 | |
266 if (!statement.Run()) | |
267 return false; | |
268 } | |
269 | |
270 ScheduleCommit(); | |
271 return true; | |
272 } | |
273 | |
274 bool QuotaDatabase::DeleteHostQuota( | |
275 const std::string& host, StorageType type) { | |
276 if (!LazyOpen(false)) | |
277 return false; | |
278 | |
279 const char* kSql = | |
280 "DELETE FROM HostQuotaTable" | |
281 " WHERE host = ? AND type = ?"; | |
282 | |
283 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
284 statement.BindString(0, host); | |
285 statement.BindInt(1, static_cast<int>(type)); | |
286 | |
287 if (!statement.Run()) | |
288 return false; | |
289 | |
290 ScheduleCommit(); | |
291 return true; | |
292 } | |
293 | |
294 bool QuotaDatabase::DeleteOriginInfo( | |
295 const GURL& origin, StorageType type) { | |
296 if (!LazyOpen(false)) | |
297 return false; | |
298 | |
299 const char* kSql = | |
300 "DELETE FROM OriginInfoTable" | |
301 " WHERE origin = ? AND type = ?"; | |
302 | |
303 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
304 statement.BindString(0, origin.spec()); | |
305 statement.BindInt(1, static_cast<int>(type)); | |
306 | |
307 if (!statement.Run()) | |
308 return false; | |
309 | |
310 ScheduleCommit(); | |
311 return true; | |
312 } | |
313 | |
314 bool QuotaDatabase::GetQuotaConfigValue(const char* key, int64* value) { | |
315 if (!LazyOpen(false)) | |
316 return false; | |
317 DCHECK(VerifyValidQuotaConfig(key)); | |
318 return meta_table_->GetValue(key, value); | |
319 } | |
320 | |
321 bool QuotaDatabase::SetQuotaConfigValue(const char* key, int64 value) { | |
322 if (!LazyOpen(true)) | |
323 return false; | |
324 DCHECK(VerifyValidQuotaConfig(key)); | |
325 return meta_table_->SetValue(key, value); | |
326 } | |
327 | |
328 bool QuotaDatabase::GetLRUOrigin( | |
329 StorageType type, | |
330 const std::set<GURL>& exceptions, | |
331 SpecialStoragePolicy* special_storage_policy, | |
332 GURL* origin) { | |
333 DCHECK(origin); | |
334 if (!LazyOpen(false)) | |
335 return false; | |
336 | |
337 const char* kSql = "SELECT origin FROM OriginInfoTable" | |
338 " WHERE type = ?" | |
339 " ORDER BY last_access_time ASC"; | |
340 | |
341 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
342 statement.BindInt(0, static_cast<int>(type)); | |
343 | |
344 while (statement.Step()) { | |
345 GURL url(statement.ColumnString(0)); | |
346 if (exceptions.find(url) != exceptions.end()) | |
347 continue; | |
348 if (special_storage_policy && | |
349 special_storage_policy->IsStorageUnlimited(url)) | |
350 continue; | |
351 *origin = url; | |
352 return true; | |
353 } | |
354 | |
355 *origin = GURL(); | |
356 return statement.Succeeded(); | |
357 } | |
358 | |
359 bool QuotaDatabase::GetOriginsModifiedSince( | |
360 StorageType type, std::set<GURL>* origins, base::Time modified_since) { | |
361 DCHECK(origins); | |
362 if (!LazyOpen(false)) | |
363 return false; | |
364 | |
365 const char* kSql = "SELECT origin FROM OriginInfoTable" | |
366 " WHERE type = ? AND last_modified_time >= ?"; | |
367 | |
368 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
369 statement.BindInt(0, static_cast<int>(type)); | |
370 statement.BindInt64(1, modified_since.ToInternalValue()); | |
371 | |
372 origins->clear(); | |
373 while (statement.Step()) | |
374 origins->insert(GURL(statement.ColumnString(0))); | |
375 | |
376 return statement.Succeeded(); | |
377 } | |
378 | |
379 bool QuotaDatabase::IsOriginDatabaseBootstrapped() { | |
380 if (!LazyOpen(true)) | |
381 return false; | |
382 | |
383 int flag = 0; | |
384 return meta_table_->GetValue(kIsOriginTableBootstrapped, &flag) && flag; | |
385 } | |
386 | |
387 bool QuotaDatabase::SetOriginDatabaseBootstrapped(bool bootstrap_flag) { | |
388 if (!LazyOpen(true)) | |
389 return false; | |
390 | |
391 return meta_table_->SetValue(kIsOriginTableBootstrapped, bootstrap_flag); | |
392 } | |
393 | |
394 void QuotaDatabase::Commit() { | |
395 if (!db_) | |
396 return; | |
397 | |
398 if (timer_.IsRunning()) | |
399 timer_.Stop(); | |
400 | |
401 db_->CommitTransaction(); | |
402 db_->BeginTransaction(); | |
403 } | |
404 | |
405 void QuotaDatabase::ScheduleCommit() { | |
406 if (timer_.IsRunning()) | |
407 return; | |
408 timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kCommitIntervalMs), | |
409 this, &QuotaDatabase::Commit); | |
410 } | |
411 | |
412 bool QuotaDatabase::FindOriginUsedCount( | |
413 const GURL& origin, StorageType type, int* used_count) { | |
414 DCHECK(used_count); | |
415 if (!LazyOpen(false)) | |
416 return false; | |
417 | |
418 const char* kSql = | |
419 "SELECT used_count FROM OriginInfoTable" | |
420 " WHERE origin = ? AND type = ?"; | |
421 | |
422 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
423 statement.BindString(0, origin.spec()); | |
424 statement.BindInt(1, static_cast<int>(type)); | |
425 | |
426 if (!statement.Step()) | |
427 return false; | |
428 | |
429 *used_count = statement.ColumnInt(0); | |
430 return true; | |
431 } | |
432 | |
433 bool QuotaDatabase::LazyOpen(bool create_if_needed) { | |
434 if (db_) | |
435 return true; | |
436 | |
437 // If we tried and failed once, don't try again in the same session | |
438 // to avoid creating an incoherent mess on disk. | |
439 if (is_disabled_) | |
440 return false; | |
441 | |
442 bool in_memory_only = db_file_path_.empty(); | |
443 if (!create_if_needed && | |
444 (in_memory_only || !base::PathExists(db_file_path_))) { | |
445 return false; | |
446 } | |
447 | |
448 db_.reset(new sql::Connection); | |
449 meta_table_.reset(new sql::MetaTable); | |
450 | |
451 db_->set_histogram_tag("Quota"); | |
452 | |
453 bool opened = false; | |
454 if (in_memory_only) { | |
455 opened = db_->OpenInMemory(); | |
456 } else if (!base::CreateDirectory(db_file_path_.DirName())) { | |
457 LOG(ERROR) << "Failed to create quota database directory."; | |
458 } else { | |
459 opened = db_->Open(db_file_path_); | |
460 if (opened) | |
461 db_->Preload(); | |
462 } | |
463 | |
464 if (!opened || !EnsureDatabaseVersion()) { | |
465 LOG(ERROR) << "Failed to open the quota database."; | |
466 is_disabled_ = true; | |
467 db_.reset(); | |
468 meta_table_.reset(); | |
469 return false; | |
470 } | |
471 | |
472 // Start a long-running transaction. | |
473 db_->BeginTransaction(); | |
474 | |
475 return true; | |
476 } | |
477 | |
478 bool QuotaDatabase::EnsureDatabaseVersion() { | |
479 static const size_t kTableCount = ARRAYSIZE_UNSAFE(kTables); | |
480 static const size_t kIndexCount = ARRAYSIZE_UNSAFE(kIndexes); | |
481 if (!sql::MetaTable::DoesTableExist(db_.get())) | |
482 return CreateSchema(db_.get(), meta_table_.get(), | |
483 kCurrentVersion, kCompatibleVersion, | |
484 kTables, kTableCount, | |
485 kIndexes, kIndexCount); | |
486 | |
487 if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion)) | |
488 return false; | |
489 | |
490 if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) { | |
491 LOG(WARNING) << "Quota database is too new."; | |
492 return false; | |
493 } | |
494 | |
495 if (meta_table_->GetVersionNumber() < kCurrentVersion) { | |
496 if (!UpgradeSchema(meta_table_->GetVersionNumber())) | |
497 return ResetSchema(); | |
498 } | |
499 | |
500 #ifndef NDEBUG | |
501 DCHECK(sql::MetaTable::DoesTableExist(db_.get())); | |
502 for (size_t i = 0; i < kTableCount; ++i) { | |
503 DCHECK(db_->DoesTableExist(kTables[i].table_name)); | |
504 } | |
505 #endif | |
506 | |
507 return true; | |
508 } | |
509 | |
510 // static | |
511 bool QuotaDatabase::CreateSchema( | |
512 sql::Connection* database, | |
513 sql::MetaTable* meta_table, | |
514 int schema_version, int compatible_version, | |
515 const TableSchema* tables, size_t tables_size, | |
516 const IndexSchema* indexes, size_t indexes_size) { | |
517 // TODO(kinuko): Factor out the common code to create databases. | |
518 sql::Transaction transaction(database); | |
519 if (!transaction.Begin()) | |
520 return false; | |
521 | |
522 if (!meta_table->Init(database, schema_version, compatible_version)) | |
523 return false; | |
524 | |
525 for (size_t i = 0; i < tables_size; ++i) { | |
526 std::string sql("CREATE TABLE "); | |
527 sql += tables[i].table_name; | |
528 sql += tables[i].columns; | |
529 if (!database->Execute(sql.c_str())) { | |
530 VLOG(1) << "Failed to execute " << sql; | |
531 return false; | |
532 } | |
533 } | |
534 | |
535 for (size_t i = 0; i < indexes_size; ++i) { | |
536 std::string sql; | |
537 if (indexes[i].unique) | |
538 sql += "CREATE UNIQUE INDEX "; | |
539 else | |
540 sql += "CREATE INDEX "; | |
541 sql += indexes[i].index_name; | |
542 sql += " ON "; | |
543 sql += indexes[i].table_name; | |
544 sql += indexes[i].columns; | |
545 if (!database->Execute(sql.c_str())) { | |
546 VLOG(1) << "Failed to execute " << sql; | |
547 return false; | |
548 } | |
549 } | |
550 | |
551 return transaction.Commit(); | |
552 } | |
553 | |
554 bool QuotaDatabase::ResetSchema() { | |
555 DCHECK(!db_file_path_.empty()); | |
556 DCHECK(base::PathExists(db_file_path_)); | |
557 VLOG(1) << "Deleting existing quota data and starting over."; | |
558 | |
559 db_.reset(); | |
560 meta_table_.reset(); | |
561 | |
562 if (!sql::Connection::Delete(db_file_path_)) | |
563 return false; | |
564 | |
565 // So we can't go recursive. | |
566 if (is_recreating_) | |
567 return false; | |
568 | |
569 base::AutoReset<bool> auto_reset(&is_recreating_, true); | |
570 return LazyOpen(true); | |
571 } | |
572 | |
573 bool QuotaDatabase::UpgradeSchema(int current_version) { | |
574 if (current_version == 2) { | |
575 QuotaTableImporter importer; | |
576 typedef std::vector<QuotaTableEntry> QuotaTableEntries; | |
577 if (!DumpQuotaTable(base::Bind(&QuotaTableImporter::Append, | |
578 base::Unretained(&importer)))) { | |
579 return false; | |
580 } | |
581 ResetSchema(); | |
582 for (QuotaTableEntries::const_iterator iter = importer.entries.begin(); | |
583 iter != importer.entries.end(); ++iter) { | |
584 if (!SetHostQuota(iter->host, iter->type, iter->quota)) | |
585 return false; | |
586 } | |
587 Commit(); | |
588 return true; | |
589 } | |
590 return false; | |
591 } | |
592 | |
593 bool QuotaDatabase::DumpQuotaTable(const QuotaTableCallback& callback) { | |
594 if (!LazyOpen(true)) | |
595 return false; | |
596 | |
597 const char* kSql = "SELECT * FROM HostQuotaTable"; | |
598 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
599 | |
600 while (statement.Step()) { | |
601 QuotaTableEntry entry = QuotaTableEntry( | |
602 statement.ColumnString(0), | |
603 static_cast<StorageType>(statement.ColumnInt(1)), | |
604 statement.ColumnInt64(2)); | |
605 | |
606 if (!callback.Run(entry)) | |
607 return true; | |
608 } | |
609 | |
610 return statement.Succeeded(); | |
611 } | |
612 | |
613 bool QuotaDatabase::DumpOriginInfoTable( | |
614 const OriginInfoTableCallback& callback) { | |
615 | |
616 if (!LazyOpen(true)) | |
617 return false; | |
618 | |
619 const char* kSql = "SELECT * FROM OriginInfoTable"; | |
620 sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); | |
621 | |
622 while (statement.Step()) { | |
623 OriginInfoTableEntry entry( | |
624 GURL(statement.ColumnString(0)), | |
625 static_cast<StorageType>(statement.ColumnInt(1)), | |
626 statement.ColumnInt(2), | |
627 base::Time::FromInternalValue(statement.ColumnInt64(3)), | |
628 base::Time::FromInternalValue(statement.ColumnInt64(4))); | |
629 | |
630 if (!callback.Run(entry)) | |
631 return true; | |
632 } | |
633 | |
634 return statement.Succeeded(); | |
635 } | |
636 | |
637 bool operator<(const QuotaDatabase::QuotaTableEntry& lhs, | |
638 const QuotaDatabase::QuotaTableEntry& rhs) { | |
639 if (lhs.host < rhs.host) return true; | |
640 if (rhs.host < lhs.host) return false; | |
641 if (lhs.type < rhs.type) return true; | |
642 if (rhs.type < lhs.type) return false; | |
643 return lhs.quota < rhs.quota; | |
644 } | |
645 | |
646 bool operator<(const QuotaDatabase::OriginInfoTableEntry& lhs, | |
647 const QuotaDatabase::OriginInfoTableEntry& rhs) { | |
648 if (lhs.origin < rhs.origin) return true; | |
649 if (rhs.origin < lhs.origin) return false; | |
650 if (lhs.type < rhs.type) return true; | |
651 if (rhs.type < lhs.type) return false; | |
652 if (lhs.used_count < rhs.used_count) return true; | |
653 if (rhs.used_count < lhs.used_count) return false; | |
654 return lhs.last_access_time < rhs.last_access_time; | |
655 } | |
656 | |
657 } // namespace storage | |
OLD | NEW |