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

Side by Side Diff: chrome/browser/history/download_database.cc

Issue 10823203: Fix downloads db state=3 corruption using version=23 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: version=23 Created 8 years, 4 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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 "chrome/browser/history/download_database.h" 5 #include "chrome/browser/history/download_database.h"
6 6
7 #include <limits> 7 #include <limits>
8 #include <string> 8 #include <string>
9 #include <vector> 9 #include <vector>
10 10
(...skipping 20 matching lines...) Expand all
31 "id INTEGER PRIMARY KEY," // SQLite-generated primary key. 31 "id INTEGER PRIMARY KEY," // SQLite-generated primary key.
32 "full_path LONGVARCHAR NOT NULL," // Location of the download on disk. 32 "full_path LONGVARCHAR NOT NULL," // Location of the download on disk.
33 "url LONGVARCHAR NOT NULL," // URL of the downloaded file. 33 "url LONGVARCHAR NOT NULL," // URL of the downloaded file.
34 "start_time INTEGER NOT NULL," // When the download was started. 34 "start_time INTEGER NOT NULL," // When the download was started.
35 "received_bytes INTEGER NOT NULL," // Total size downloaded. 35 "received_bytes INTEGER NOT NULL," // Total size downloaded.
36 "total_bytes INTEGER NOT NULL," // Total size of the download. 36 "total_bytes INTEGER NOT NULL," // Total size of the download.
37 "state INTEGER NOT NULL," // 1=complete, 2=cancelled, 4=interrupted 37 "state INTEGER NOT NULL," // 1=complete, 2=cancelled, 4=interrupted
38 "end_time INTEGER NOT NULL," // When the download completed. 38 "end_time INTEGER NOT NULL," // When the download completed.
39 "opened INTEGER NOT NULL)"; // 1 if it has ever been opened else 0 39 "opened INTEGER NOT NULL)"; // 1 if it has ever been opened else 0
40 40
41 // These constants and next two functions are used to allow
42 // DownloadItem::DownloadState to change without breaking the database schema.
43 // They guarantee that the values of the |state| field in the database are one
44 // of the values returned by StateToInt, and that the values of the |state|
45 // field of the DownloadPersistentStoreInfos returned by QueryDownloads() are
46 // one of the values returned by IntToState().
47 static const int kStateInvalid = -1;
48 static const int kStateInProgress = 0;
49 static const int kStateComplete = 1;
50 static const int kStateCancelled = 2;
51 static const int kStateBug140687 = 3;
52 static const int kStateInterrupted = 4;
53
54 int StateToInt(DownloadItem::DownloadState state) {
55 switch (state) {
56 case DownloadItem::IN_PROGRESS: return kStateInProgress;
57 case DownloadItem::COMPLETE: return kStateComplete;
58 case DownloadItem::CANCELLED: return kStateCancelled;
59 case DownloadItem::REMOVING: return kStateInvalid;
60 case DownloadItem::INTERRUPTED: return kStateInterrupted;
61 case DownloadItem::MAX_DOWNLOAD_STATE: return kStateInvalid;
62 default: return kStateInvalid;
63 }
64 }
65
66 DownloadItem::DownloadState IntToState(int state) {
67 switch (state) {
68 case kStateInProgress: return DownloadItem::IN_PROGRESS;
69 case kStateComplete: return DownloadItem::COMPLETE;
70 case kStateCancelled: return DownloadItem::CANCELLED;
71 // We should not need kStateBug140687 here because MigrateDownloadState()
72 // is called in HistoryDatabase::Init().
73 case kStateInterrupted: return DownloadItem::INTERRUPTED;
74 default: return DownloadItem::MAX_DOWNLOAD_STATE;
75 }
76 }
77
41 #if defined(OS_POSIX) 78 #if defined(OS_POSIX)
42 79
43 // Binds/reads the given file path to the given column of the given statement. 80 // Binds/reads the given file path to the given column of the given statement.
44 void BindFilePath(sql::Statement& statement, const FilePath& path, int col) { 81 void BindFilePath(sql::Statement& statement, const FilePath& path, int col) {
45 statement.BindString(col, path.value()); 82 statement.BindString(col, path.value());
46 } 83 }
47 FilePath ColumnFilePath(sql::Statement& statement, int col) { 84 FilePath ColumnFilePath(sql::Statement& statement, int col) {
48 return FilePath(statement.ColumnString(col)); 85 return FilePath(statement.ColumnString(col));
49 } 86 }
50 87
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
85 } 122 }
86 } 123 }
87 124
88 bool DownloadDatabase::EnsureColumnExists( 125 bool DownloadDatabase::EnsureColumnExists(
89 const std::string& name, const std::string& type) { 126 const std::string& name, const std::string& type) {
90 std::string add_col = "ALTER TABLE downloads ADD COLUMN " + name + " " + type; 127 std::string add_col = "ALTER TABLE downloads ADD COLUMN " + name + " " + type;
91 return GetDB().DoesColumnExist("downloads", name.c_str()) || 128 return GetDB().DoesColumnExist("downloads", name.c_str()) ||
92 GetDB().Execute(add_col.c_str()); 129 GetDB().Execute(add_col.c_str());
93 } 130 }
94 131
132 bool DownloadDatabase::MigrateDownloadsState() {
133 sql::Statement statement(GetDB().GetUniqueStatement(
134 "UPDATE downloads SET state=? WHERE state=?"));
135 statement.BindInt(0, kStateInterrupted);
136 statement.BindInt(1, kStateBug140687);
137 return statement.Run();
138 }
139
95 bool DownloadDatabase::InitDownloadTable() { 140 bool DownloadDatabase::InitDownloadTable() {
96 CheckThread(); 141 CheckThread();
97 GetMetaTable().GetValue(kNextDownloadId, &next_id_); 142 GetMetaTable().GetValue(kNextDownloadId, &next_id_);
98 if (GetDB().DoesTableExist("downloads")) { 143 if (GetDB().DoesTableExist("downloads")) {
99 return EnsureColumnExists("end_time", "INTEGER NOT NULL DEFAULT 0") && 144 return EnsureColumnExists("end_time", "INTEGER NOT NULL DEFAULT 0") &&
100 EnsureColumnExists("opened", "INTEGER NOT NULL DEFAULT 0"); 145 EnsureColumnExists("opened", "INTEGER NOT NULL DEFAULT 0");
101 } else { 146 } else {
102 return GetDB().Execute(kSchema); 147 return GetDB().Execute(kSchema);
103 } 148 }
104 } 149 }
(...skipping 18 matching lines...) Expand all
123 "ORDER BY start_time")); 168 "ORDER BY start_time"));
124 169
125 while (statement.Step()) { 170 while (statement.Step()) {
126 DownloadPersistentStoreInfo info; 171 DownloadPersistentStoreInfo info;
127 info.db_handle = statement.ColumnInt64(0); 172 info.db_handle = statement.ColumnInt64(0);
128 info.path = ColumnFilePath(statement, 1); 173 info.path = ColumnFilePath(statement, 1);
129 info.url = GURL(statement.ColumnString(2)); 174 info.url = GURL(statement.ColumnString(2));
130 info.start_time = base::Time::FromTimeT(statement.ColumnInt64(3)); 175 info.start_time = base::Time::FromTimeT(statement.ColumnInt64(3));
131 info.received_bytes = statement.ColumnInt64(4); 176 info.received_bytes = statement.ColumnInt64(4);
132 info.total_bytes = statement.ColumnInt64(5); 177 info.total_bytes = statement.ColumnInt64(5);
133 info.state = statement.ColumnInt(6); 178 info.state = IntToState(statement.ColumnInt(6));
134 info.end_time = base::Time::FromTimeT(statement.ColumnInt64(7)); 179 info.end_time = base::Time::FromTimeT(statement.ColumnInt64(7));
135 info.opened = statement.ColumnInt(8) != 0; 180 info.opened = statement.ColumnInt(8) != 0;
181 if (info.state == DownloadItem::MAX_DOWNLOAD_STATE) {
182 continue;
Randy Smith (Not in Mondays) 2012/08/07 20:36:16 UMA? (You're welcome to say not in this CL, but I
sky 2012/08/07 20:54:17 Why add this? At best we do a DCHECK for this sort
asanka 2012/08/07 20:56:14 If you skip this early, wouldn't you risk not upda
Randy Smith (Not in Mondays) 2012/08/07 21:01:35 The basic problem is that almost any sanitization
benjhayden 2012/08/08 01:11:52 This layer of abstraction separates the compiler-g
benjhayden 2012/08/08 01:11:52 Done.
benjhayden 2012/08/08 01:11:52 Done.
benjhayden 2012/08/08 01:11:52 This isn't just sanitization. It's a mapping, a la
Randy Smith (Not in Mondays) 2012/08/08 01:52:25 Good point. Ok.
183 }
136 results->push_back(info); 184 results->push_back(info);
137 if (info.db_handle >= next_db_handle_) 185 if (info.db_handle >= next_db_handle_)
138 next_db_handle_ = info.db_handle + 1; 186 next_db_handle_ = info.db_handle + 1;
139 if (!db_handles.insert(info.db_handle).second) { 187 if (!db_handles.insert(info.db_handle).second) {
140 // info.db_handle was already in db_handles. The database is corrupt. 188 // info.db_handle was already in db_handles. The database is corrupt.
141 base::debug::Alias(&info.db_handle); 189 base::debug::Alias(&info.db_handle);
142 DCHECK(false); 190 DCHECK(false);
143 } 191 }
144 } 192 }
145 } 193 }
146 194
147 bool DownloadDatabase::UpdateDownload(const DownloadPersistentStoreInfo& data) { 195 bool DownloadDatabase::UpdateDownload(const DownloadPersistentStoreInfo& data) {
148 CheckThread(); 196 CheckThread();
149 DCHECK(data.db_handle > 0); 197 DCHECK(data.db_handle > 0);
198 int state = StateToInt(data.state);
199 if (state == kStateInvalid)
200 return false;
Randy Smith (Not in Mondays) 2012/08/07 20:36:16 DCHECK? If we get an invalid state here, it means
benjhayden 2012/08/08 01:11:52 Is it possible for UpdateDownload to see a downloa
Randy Smith (Not in Mondays) 2012/08/08 01:52:25 Not sure enough for this CL :-} :-|.
150 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 201 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
151 "UPDATE downloads " 202 "UPDATE downloads "
152 "SET received_bytes=?, state=?, end_time=?, opened=? WHERE id=?")); 203 "SET received_bytes=?, state=?, end_time=?, opened=? WHERE id=?"));
153 statement.BindInt64(0, data.received_bytes); 204 statement.BindInt64(0, data.received_bytes);
154 statement.BindInt(1, data.state); 205 statement.BindInt(1, state);
155 statement.BindInt64(2, data.end_time.ToTimeT()); 206 statement.BindInt64(2, data.end_time.ToTimeT());
156 statement.BindInt(3, (data.opened ? 1 : 0)); 207 statement.BindInt(3, (data.opened ? 1 : 0));
157 statement.BindInt64(4, data.db_handle); 208 statement.BindInt64(4, data.db_handle);
158 209
159 return statement.Run(); 210 return statement.Run();
160 } 211 }
161 212
162 bool DownloadDatabase::UpdateDownloadPath(const FilePath& path, 213 bool DownloadDatabase::UpdateDownloadPath(const FilePath& path,
163 DownloadID db_handle) { 214 DownloadID db_handle) {
164 CheckThread(); 215 CheckThread();
165 DCHECK(db_handle > 0); 216 DCHECK(db_handle > 0);
166 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 217 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
167 "UPDATE downloads SET full_path=? WHERE id=?")); 218 "UPDATE downloads SET full_path=? WHERE id=?"));
168 BindFilePath(statement, path, 0); 219 BindFilePath(statement, path, 0);
169 statement.BindInt64(1, db_handle); 220 statement.BindInt64(1, db_handle);
170 221
171 return statement.Run(); 222 return statement.Run();
172 } 223 }
173 224
174 bool DownloadDatabase::CleanUpInProgressEntries() { 225 bool DownloadDatabase::CleanUpInProgressEntries() {
175 CheckThread(); 226 CheckThread();
176 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 227 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
177 "UPDATE downloads SET state=? WHERE state=?")); 228 "UPDATE downloads SET state=? WHERE state=?"));
178 statement.BindInt(0, DownloadItem::CANCELLED); 229 statement.BindInt(0, kStateCancelled);
179 statement.BindInt(1, DownloadItem::IN_PROGRESS); 230 statement.BindInt(1, kStateInProgress);
180 231
181 return statement.Run(); 232 return statement.Run();
182 } 233 }
183 234
184 int64 DownloadDatabase::CreateDownload( 235 int64 DownloadDatabase::CreateDownload(
185 const DownloadPersistentStoreInfo& info) { 236 const DownloadPersistentStoreInfo& info) {
186 CheckThread(); 237 CheckThread();
187 238
188 if (next_db_handle_ == 0) { 239 if (next_db_handle_ == 0) {
189 // This is unlikely. All current known tests and users already call 240 // This is unlikely. All current known tests and users already call
190 // QueryDownloads() before CreateDownload(). 241 // QueryDownloads() before CreateDownload().
191 std::vector<DownloadPersistentStoreInfo> results; 242 std::vector<DownloadPersistentStoreInfo> results;
192 QueryDownloads(&results); 243 QueryDownloads(&results);
193 CHECK_NE(0, next_db_handle_); 244 CHECK_NE(0, next_db_handle_);
194 } 245 }
195 246
247 int state = StateToInt(info.state);
248 if (state == kStateInvalid)
249 return false;
250
196 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 251 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
197 "INSERT INTO downloads " 252 "INSERT INTO downloads "
198 "(id, full_path, url, start_time, received_bytes, total_bytes, state, " 253 "(id, full_path, url, start_time, received_bytes, total_bytes, state, "
199 "end_time, opened) " 254 "end_time, opened) "
200 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")); 255 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"));
201 256
202 int db_handle = next_db_handle_++; 257 int db_handle = next_db_handle_++;
203 258
204 statement.BindInt64(0, db_handle); 259 statement.BindInt64(0, db_handle);
205 BindFilePath(statement, info.path, 1); 260 BindFilePath(statement, info.path, 1);
206 statement.BindString(2, info.url.spec()); 261 statement.BindString(2, info.url.spec());
207 statement.BindInt64(3, info.start_time.ToTimeT()); 262 statement.BindInt64(3, info.start_time.ToTimeT());
208 statement.BindInt64(4, info.received_bytes); 263 statement.BindInt64(4, info.received_bytes);
209 statement.BindInt64(5, info.total_bytes); 264 statement.BindInt64(5, info.total_bytes);
210 statement.BindInt(6, info.state); 265 statement.BindInt(6, state);
211 statement.BindInt64(7, info.end_time.ToTimeT()); 266 statement.BindInt64(7, info.end_time.ToTimeT());
212 statement.BindInt(8, info.opened ? 1 : 0); 267 statement.BindInt(8, info.opened ? 1 : 0);
213 268
214 if (statement.Run()) { 269 if (statement.Run()) {
215 // TODO(benjhayden) if(info.id>next_id_){setvalue;next_id_=info.id;} 270 // TODO(benjhayden) if(info.id>next_id_){setvalue;next_id_=info.id;}
216 GetMetaTable().SetValue(kNextDownloadId, ++next_id_); 271 GetMetaTable().SetValue(kNextDownloadId, ++next_id_);
217 272
218 return db_handle; 273 return db_handle;
219 } 274 }
220 return 0; 275 return 0;
(...skipping 17 matching lines...) Expand all
238 293
239 int num_downloads_deleted = -1; 294 int num_downloads_deleted = -1;
240 { 295 {
241 sql::Statement count(GetDB().GetCachedStatement(SQL_FROM_HERE, 296 sql::Statement count(GetDB().GetCachedStatement(SQL_FROM_HERE,
242 "SELECT count(*) FROM downloads WHERE start_time >= ? " 297 "SELECT count(*) FROM downloads WHERE start_time >= ? "
243 "AND start_time < ? AND (State = ? OR State = ? OR State = ?)")); 298 "AND start_time < ? AND (State = ? OR State = ? OR State = ?)"));
244 count.BindInt64(0, start_time); 299 count.BindInt64(0, start_time);
245 count.BindInt64( 300 count.BindInt64(
246 1, 301 1,
247 end_time ? end_time : std::numeric_limits<int64>::max()); 302 end_time ? end_time : std::numeric_limits<int64>::max());
248 count.BindInt(2, DownloadItem::COMPLETE); 303 count.BindInt(2, kStateComplete);
249 count.BindInt(3, DownloadItem::CANCELLED); 304 count.BindInt(3, kStateCancelled);
250 count.BindInt(4, DownloadItem::INTERRUPTED); 305 count.BindInt(4, kStateInterrupted);
251 if (count.Step()) 306 if (count.Step())
252 num_downloads_deleted = count.ColumnInt(0); 307 num_downloads_deleted = count.ColumnInt(0);
253 } 308 }
254 309
255 310
256 bool success = false; 311 bool success = false;
257 base::TimeTicks started_removing = base::TimeTicks::Now(); 312 base::TimeTicks started_removing = base::TimeTicks::Now();
258 { 313 {
259 // This does not use an index. We currently aren't likely to have enough 314 // This does not use an index. We currently aren't likely to have enough
260 // downloads where an index by time will give us a lot of benefit. 315 // downloads where an index by time will give us a lot of benefit.
261 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 316 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
262 "DELETE FROM downloads WHERE start_time >= ? AND start_time < ? " 317 "DELETE FROM downloads WHERE start_time >= ? AND start_time < ? "
263 "AND (State = ? OR State = ? OR State = ?)")); 318 "AND (State = ? OR State = ? OR State = ?)"));
264 statement.BindInt64(0, start_time); 319 statement.BindInt64(0, start_time);
265 statement.BindInt64( 320 statement.BindInt64(
266 1, 321 1,
267 end_time ? end_time : std::numeric_limits<int64>::max()); 322 end_time ? end_time : std::numeric_limits<int64>::max());
268 statement.BindInt(2, DownloadItem::COMPLETE); 323 statement.BindInt(2, kStateComplete);
269 statement.BindInt(3, DownloadItem::CANCELLED); 324 statement.BindInt(3, kStateCancelled);
270 statement.BindInt(4, DownloadItem::INTERRUPTED); 325 statement.BindInt(4, kStateInterrupted);
271 326
272 success = statement.Run(); 327 success = statement.Run();
273 } 328 }
274 329
275 base::TimeTicks finished_removing = base::TimeTicks::Now(); 330 base::TimeTicks finished_removing = base::TimeTicks::Now();
276 331
277 if (num_downloads_deleted >= 0) { 332 if (num_downloads_deleted >= 0) {
278 UMA_HISTOGRAM_COUNTS("Download.DatabaseRemoveDownloadsCount", 333 UMA_HISTOGRAM_COUNTS("Download.DatabaseRemoveDownloadsCount",
279 num_downloads_deleted); 334 num_downloads_deleted);
280 base::TimeDelta micros = (1000 * (finished_removing - started_removing)); 335 base::TimeDelta micros = (1000 * (finished_removing - started_removing));
281 UMA_HISTOGRAM_TIMES("Download.DatabaseRemoveDownloadsTime", micros); 336 UMA_HISTOGRAM_TIMES("Download.DatabaseRemoveDownloadsTime", micros);
282 if (num_downloads_deleted > 0) { 337 if (num_downloads_deleted > 0) {
283 UMA_HISTOGRAM_TIMES("Download.DatabaseRemoveDownloadsTimePerRecord", 338 UMA_HISTOGRAM_TIMES("Download.DatabaseRemoveDownloadsTimePerRecord",
284 (1000 * micros) / num_downloads_deleted); 339 (1000 * micros) / num_downloads_deleted);
285 } 340 }
286 } 341 }
287 342
288 return success; 343 return success;
289 } 344 }
290 345
291 } // namespace history 346 } // namespace history
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698