toolkit/components/satchel/nsFormHistory.js

branch
TOR_BUG_9701
changeset 14
925c144e1f1f
equal deleted inserted replaced
-1:000000000000 0:c4e22bd72b4b
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
6 const Cc = Components.classes;
7 const Ci = Components.interfaces;
8 const Cr = Components.results;
9
10 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
11 Components.utils.import("resource://gre/modules/Services.jsm");
12
13 XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
14 "resource://gre/modules/Deprecated.jsm");
15
16 const DB_VERSION = 4;
17 const DAY_IN_MS = 86400000; // 1 day in milliseconds
18
19 function FormHistory() {
20 Deprecated.warning(
21 "nsIFormHistory2 is deprecated and will be removed in a future version",
22 "https://bugzilla.mozilla.org/show_bug.cgi?id=879118");
23 this.init();
24 }
25
26 FormHistory.prototype = {
27 classID : Components.ID("{0c1bb408-71a2-403f-854a-3a0659829ded}"),
28 QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormHistory2,
29 Ci.nsIObserver,
30 Ci.nsIMessageListener,
31 Ci.nsISupportsWeakReference,
32 ]),
33
34 debug : true,
35 enabled : true,
36
37 // The current database schema.
38 dbSchema : {
39 tables : {
40 moz_formhistory: {
41 "id" : "INTEGER PRIMARY KEY",
42 "fieldname" : "TEXT NOT NULL",
43 "value" : "TEXT NOT NULL",
44 "timesUsed" : "INTEGER",
45 "firstUsed" : "INTEGER",
46 "lastUsed" : "INTEGER",
47 "guid" : "TEXT"
48 },
49 moz_deleted_formhistory: {
50 "id" : "INTEGER PRIMARY KEY",
51 "timeDeleted" : "INTEGER",
52 "guid" : "TEXT"
53 }
54 },
55 indices : {
56 moz_formhistory_index : {
57 table : "moz_formhistory",
58 columns : ["fieldname"]
59 },
60 moz_formhistory_lastused_index : {
61 table : "moz_formhistory",
62 columns : ["lastUsed"]
63 },
64 moz_formhistory_guid_index : {
65 table : "moz_formhistory",
66 columns : ["guid"]
67 },
68 }
69 },
70 dbStmts : null, // Database statements for memoization
71 dbFile : null,
72
73 _uuidService: null,
74 get uuidService() {
75 if (!this._uuidService)
76 this._uuidService = Cc["@mozilla.org/uuid-generator;1"].
77 getService(Ci.nsIUUIDGenerator);
78 return this._uuidService;
79 },
80
81 log : function log(message) {
82 if (!this.debug)
83 return;
84 dump("FormHistory: " + message + "\n");
85 Services.console.logStringMessage("FormHistory: " + message);
86 },
87
88
89 init : function init() {
90 this.updatePrefs();
91
92 this.dbStmts = {};
93
94 // Add observer
95 Services.obs.addObserver(this, "profile-before-change", true);
96 },
97
98 /* ---- nsIFormHistory2 interfaces ---- */
99
100
101 get hasEntries() {
102 return (this.countAllEntries() > 0);
103 },
104
105
106 addEntry : function addEntry(name, value) {
107 if (!this.enabled)
108 return;
109
110 this.log("addEntry for " + name + "=" + value);
111
112 let now = Date.now() * 1000; // microseconds
113
114 let [id, guid] = this.getExistingEntryID(name, value);
115 let stmt;
116
117 if (id != -1) {
118 // Update existing entry.
119 let query = "UPDATE moz_formhistory SET timesUsed = timesUsed + 1, lastUsed = :lastUsed WHERE id = :id";
120 let params = {
121 lastUsed : now,
122 id : id
123 };
124
125 try {
126 stmt = this.dbCreateStatement(query, params);
127 stmt.execute();
128 this.sendStringNotification("modifyEntry", name, value, guid);
129 } catch (e) {
130 this.log("addEntry (modify) failed: " + e);
131 throw e;
132 } finally {
133 if (stmt) {
134 stmt.reset();
135 }
136 }
137
138 } else {
139 // Add new entry.
140 guid = this.generateGUID();
141
142 let query = "INSERT INTO moz_formhistory (fieldname, value, timesUsed, firstUsed, lastUsed, guid) " +
143 "VALUES (:fieldname, :value, :timesUsed, :firstUsed, :lastUsed, :guid)";
144 let params = {
145 fieldname : name,
146 value : value,
147 timesUsed : 1,
148 firstUsed : now,
149 lastUsed : now,
150 guid : guid
151 };
152
153 try {
154 stmt = this.dbCreateStatement(query, params);
155 stmt.execute();
156 this.sendStringNotification("addEntry", name, value, guid);
157 } catch (e) {
158 this.log("addEntry (create) failed: " + e);
159 throw e;
160 } finally {
161 if (stmt) {
162 stmt.reset();
163 }
164 }
165 }
166 },
167
168
169 removeEntry : function removeEntry(name, value) {
170 this.log("removeEntry for " + name + "=" + value);
171
172 let [id, guid] = this.getExistingEntryID(name, value);
173 this.sendStringNotification("before-removeEntry", name, value, guid);
174
175 let stmt;
176 let query = "DELETE FROM moz_formhistory WHERE id = :id";
177 let params = { id : id };
178 let existingTransactionInProgress;
179
180 try {
181 // Don't start a transaction if one is already in progress since we can't nest them.
182 existingTransactionInProgress = this.dbConnection.transactionInProgress;
183 if (!existingTransactionInProgress)
184 this.dbConnection.beginTransaction();
185 this.moveToDeletedTable("VALUES (:guid, :timeDeleted)", {
186 guid: guid,
187 timeDeleted: Date.now()
188 });
189
190 // remove from the formhistory database
191 stmt = this.dbCreateStatement(query, params);
192 stmt.execute();
193 this.sendStringNotification("removeEntry", name, value, guid);
194 } catch (e) {
195 if (!existingTransactionInProgress)
196 this.dbConnection.rollbackTransaction();
197 this.log("removeEntry failed: " + e);
198 throw e;
199 } finally {
200 if (stmt) {
201 stmt.reset();
202 }
203 }
204 if (!existingTransactionInProgress)
205 this.dbConnection.commitTransaction();
206 },
207
208
209 removeEntriesForName : function removeEntriesForName(name) {
210 this.log("removeEntriesForName with name=" + name);
211
212 this.sendStringNotification("before-removeEntriesForName", name);
213
214 let stmt;
215 let query = "DELETE FROM moz_formhistory WHERE fieldname = :fieldname";
216 let params = { fieldname : name };
217 let existingTransactionInProgress;
218
219 try {
220 // Don't start a transaction if one is already in progress since we can't nest them.
221 existingTransactionInProgress = this.dbConnection.transactionInProgress;
222 if (!existingTransactionInProgress)
223 this.dbConnection.beginTransaction();
224 this.moveToDeletedTable(
225 "SELECT guid, :timeDeleted FROM moz_formhistory " +
226 "WHERE fieldname = :fieldname", {
227 fieldname: name,
228 timeDeleted: Date.now()
229 });
230
231 stmt = this.dbCreateStatement(query, params);
232 stmt.execute();
233 this.sendStringNotification("removeEntriesForName", name);
234 } catch (e) {
235 if (!existingTransactionInProgress)
236 this.dbConnection.rollbackTransaction();
237 this.log("removeEntriesForName failed: " + e);
238 throw e;
239 } finally {
240 if (stmt) {
241 stmt.reset();
242 }
243 }
244 if (!existingTransactionInProgress)
245 this.dbConnection.commitTransaction();
246 },
247
248
249 removeAllEntries : function removeAllEntries() {
250 this.log("removeAllEntries");
251
252 this.sendNotification("before-removeAllEntries", null);
253
254 let stmt;
255 let query = "DELETE FROM moz_formhistory";
256 let existingTransactionInProgress;
257
258 try {
259 // Don't start a transaction if one is already in progress since we can't nest them.
260 existingTransactionInProgress = this.dbConnection.transactionInProgress;
261 if (!existingTransactionInProgress)
262 this.dbConnection.beginTransaction();
263 // TODO: Add these items to the deleted items table once we've sorted
264 // out the issues from bug 756701
265 stmt = this.dbCreateStatement(query);
266 stmt.execute();
267 this.sendNotification("removeAllEntries", null);
268 } catch (e) {
269 if (!existingTransactionInProgress)
270 this.dbConnection.rollbackTransaction();
271 this.log("removeAllEntries failed: " + e);
272 throw e;
273 } finally {
274 if (stmt) {
275 stmt.reset();
276 }
277 }
278 if (!existingTransactionInProgress)
279 this.dbConnection.commitTransaction();
280 },
281
282
283 nameExists : function nameExists(name) {
284 this.log("nameExists for name=" + name);
285 let stmt;
286 let query = "SELECT COUNT(1) AS numEntries FROM moz_formhistory WHERE fieldname = :fieldname";
287 let params = { fieldname : name };
288 try {
289 stmt = this.dbCreateStatement(query, params);
290 stmt.executeStep();
291 return (stmt.row.numEntries > 0);
292 } catch (e) {
293 this.log("nameExists failed: " + e);
294 throw e;
295 } finally {
296 if (stmt) {
297 stmt.reset();
298 }
299 }
300 },
301
302 entryExists : function entryExists(name, value) {
303 this.log("entryExists for " + name + "=" + value);
304 let [id, guid] = this.getExistingEntryID(name, value);
305 this.log("entryExists: id=" + id);
306 return (id != -1);
307 },
308
309 removeEntriesByTimeframe : function removeEntriesByTimeframe(beginTime, endTime) {
310 this.log("removeEntriesByTimeframe for " + beginTime + " to " + endTime);
311
312 this.sendIntNotification("before-removeEntriesByTimeframe", beginTime, endTime);
313
314 let stmt;
315 let query = "DELETE FROM moz_formhistory WHERE firstUsed >= :beginTime AND firstUsed <= :endTime";
316 let params = {
317 beginTime : beginTime,
318 endTime : endTime
319 };
320 let existingTransactionInProgress;
321
322 try {
323 // Don't start a transaction if one is already in progress since we can't nest them.
324 existingTransactionInProgress = this.dbConnection.transactionInProgress;
325 if (!existingTransactionInProgress)
326 this.dbConnection.beginTransaction();
327 this.moveToDeletedTable(
328 "SELECT guid, :timeDeleted FROM moz_formhistory " +
329 "WHERE firstUsed >= :beginTime AND firstUsed <= :endTime", {
330 beginTime: beginTime,
331 endTime: endTime
332 });
333
334 stmt = this.dbCreateStatement(query, params);
335 stmt.executeStep();
336 this.sendIntNotification("removeEntriesByTimeframe", beginTime, endTime);
337 } catch (e) {
338 if (!existingTransactionInProgress)
339 this.dbConnection.rollbackTransaction();
340 this.log("removeEntriesByTimeframe failed: " + e);
341 throw e;
342 } finally {
343 if (stmt) {
344 stmt.reset();
345 }
346 }
347 if (!existingTransactionInProgress)
348 this.dbConnection.commitTransaction();
349 },
350
351 moveToDeletedTable : function moveToDeletedTable(values, params) {
352 #ifdef ANDROID
353 this.log("Moving entries to deleted table.");
354
355 let stmt;
356
357 try {
358 // Move the entries to the deleted items table.
359 let query = "INSERT INTO moz_deleted_formhistory (guid, timeDeleted) ";
360 if (values) query += values;
361 stmt = this.dbCreateStatement(query, params);
362 stmt.execute();
363 } catch (e) {
364 this.log("Moving deleted entries failed: " + e);
365 throw e;
366 } finally {
367 if (stmt) {
368 stmt.reset();
369 }
370 }
371 #endif
372 },
373
374 get dbConnection() {
375 // Make sure dbConnection can't be called from now to prevent infinite loops.
376 delete FormHistory.prototype.dbConnection;
377
378 try {
379 this.dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile).clone();
380 this.dbFile.append("formhistory.sqlite");
381 this.log("Opening database at " + this.dbFile.path);
382
383 FormHistory.prototype.dbConnection = this.dbOpen();
384 this.dbInit();
385 } catch (e) {
386 this.log("Initialization failed: " + e);
387 // If dbInit fails...
388 if (e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
389 this.dbCleanup();
390 FormHistory.prototype.dbConnection = this.dbOpen();
391 this.dbInit();
392 } else {
393 throw "Initialization failed";
394 }
395 }
396
397 return FormHistory.prototype.dbConnection;
398 },
399
400 get DBConnection() {
401 return this.dbConnection;
402 },
403
404
405 /* ---- nsIObserver interface ---- */
406
407
408 observe : function observe(subject, topic, data) {
409 switch(topic) {
410 case "nsPref:changed":
411 this.updatePrefs();
412 break;
413 case "profile-before-change":
414 this._dbClose(false);
415 break;
416 default:
417 this.log("Oops! Unexpected notification: " + topic);
418 break;
419 }
420 },
421
422
423 /* ---- helpers ---- */
424
425
426 generateGUID : function() {
427 // string like: "{f60d9eac-9421-4abc-8491-8e8322b063d4}"
428 let uuid = this.uuidService.generateUUID().toString();
429 let raw = ""; // A string with the low bytes set to random values
430 let bytes = 0;
431 for (let i = 1; bytes < 12 ; i+= 2) {
432 // Skip dashes
433 if (uuid[i] == "-")
434 i++;
435 let hexVal = parseInt(uuid[i] + uuid[i + 1], 16);
436 raw += String.fromCharCode(hexVal);
437 bytes++;
438 }
439 return btoa(raw);
440 },
441
442
443 sendStringNotification : function (changeType, str1, str2, str3) {
444 function wrapit(str) {
445 let wrapper = Cc["@mozilla.org/supports-string;1"].
446 createInstance(Ci.nsISupportsString);
447 wrapper.data = str;
448 return wrapper;
449 }
450
451 let strData;
452 if (arguments.length == 2) {
453 // Just 1 string, no need to put it in an array
454 strData = wrapit(str1);
455 } else {
456 // 3 strings, put them in an array.
457 strData = Cc["@mozilla.org/array;1"].
458 createInstance(Ci.nsIMutableArray);
459 strData.appendElement(wrapit(str1), false);
460 strData.appendElement(wrapit(str2), false);
461 strData.appendElement(wrapit(str3), false);
462 }
463 this.sendNotification(changeType, strData);
464 },
465
466
467 sendIntNotification : function (changeType, int1, int2) {
468 function wrapit(int) {
469 let wrapper = Cc["@mozilla.org/supports-PRInt64;1"].
470 createInstance(Ci.nsISupportsPRInt64);
471 wrapper.data = int;
472 return wrapper;
473 }
474
475 let intData;
476 if (arguments.length == 2) {
477 // Just 1 int, no need for an array
478 intData = wrapit(int1);
479 } else {
480 // 2 ints, put them in an array.
481 intData = Cc["@mozilla.org/array;1"].
482 createInstance(Ci.nsIMutableArray);
483 intData.appendElement(wrapit(int1), false);
484 intData.appendElement(wrapit(int2), false);
485 }
486 this.sendNotification(changeType, intData);
487 },
488
489
490 sendNotification : function (changeType, data) {
491 Services.obs.notifyObservers(data, "satchel-storage-changed", changeType);
492 },
493
494
495 getExistingEntryID : function (name, value) {
496 let id = -1, guid = null;
497 let stmt;
498 let query = "SELECT id, guid FROM moz_formhistory WHERE fieldname = :fieldname AND value = :value";
499 let params = {
500 fieldname : name,
501 value : value
502 };
503 try {
504 stmt = this.dbCreateStatement(query, params);
505 if (stmt.executeStep()) {
506 id = stmt.row.id;
507 guid = stmt.row.guid;
508 }
509 } catch (e) {
510 this.log("getExistingEntryID failed: " + e);
511 throw e;
512 } finally {
513 if (stmt) {
514 stmt.reset();
515 }
516 }
517
518 return [id, guid];
519 },
520
521
522 countAllEntries : function () {
523 let query = "SELECT COUNT(1) AS numEntries FROM moz_formhistory";
524
525 let stmt, numEntries;
526 try {
527 stmt = this.dbCreateStatement(query, null);
528 stmt.executeStep();
529 numEntries = stmt.row.numEntries;
530 } catch (e) {
531 this.log("countAllEntries failed: " + e);
532 throw e;
533 } finally {
534 if (stmt) {
535 stmt.reset();
536 }
537 }
538
539 this.log("countAllEntries: counted entries: " + numEntries);
540 return numEntries;
541 },
542
543
544 updatePrefs : function () {
545 this.debug = Services.prefs.getBoolPref("browser.formfill.debug");
546 this.enabled = Services.prefs.getBoolPref("browser.formfill.enable");
547 },
548
549 //**************************************************************************//
550 // Database Creation & Access
551
552 /*
553 * dbCreateStatement
554 *
555 * Creates a statement, wraps it, and then does parameter replacement
556 * Will use memoization so that statements can be reused.
557 */
558 dbCreateStatement : function (query, params) {
559 let stmt = this.dbStmts[query];
560 // Memoize the statements
561 if (!stmt) {
562 this.log("Creating new statement for query: " + query);
563 stmt = this.dbConnection.createStatement(query);
564 this.dbStmts[query] = stmt;
565 }
566 // Replace parameters, must be done 1 at a time
567 if (params)
568 for (let i in params)
569 stmt.params[i] = params[i];
570 return stmt;
571 },
572
573 /*
574 * dbOpen
575 *
576 * Open a connection with the database and returns it.
577 *
578 * @returns a db connection object.
579 */
580 dbOpen : function () {
581 this.log("Open Database");
582
583 let storage = Cc["@mozilla.org/storage/service;1"].
584 getService(Ci.mozIStorageService);
585 return storage.openDatabase(this.dbFile);
586 },
587
588 /*
589 * dbInit
590 *
591 * Attempts to initialize the database. This creates the file if it doesn't
592 * exist, performs any migrations, etc.
593 */
594 dbInit : function () {
595 this.log("Initializing Database");
596
597 let version = this.dbConnection.schemaVersion;
598
599 // Note: Firefox 3 didn't set a schema value, so it started from 0.
600 // So we can't depend on a simple version == 0 check
601 if (version == 0 && !this.dbConnection.tableExists("moz_formhistory"))
602 this.dbCreate();
603 else if (version != DB_VERSION)
604 this.dbMigrate(version);
605 },
606
607
608 dbCreate: function () {
609 this.log("Creating DB -- tables");
610 for (let name in this.dbSchema.tables) {
611 let table = this.dbSchema.tables[name];
612 this.dbCreateTable(name, table);
613 }
614
615 this.log("Creating DB -- indices");
616 for (let name in this.dbSchema.indices) {
617 let index = this.dbSchema.indices[name];
618 let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
619 "(" + index.columns.join(", ") + ")";
620 this.dbConnection.executeSimpleSQL(statement);
621 }
622
623 this.dbConnection.schemaVersion = DB_VERSION;
624 },
625
626 dbCreateTable: function(name, table) {
627 let tSQL = [[col, table[col]].join(" ") for (col in table)].join(", ");
628 this.log("Creating table " + name + " with " + tSQL);
629 this.dbConnection.createTable(name, tSQL);
630 },
631
632 dbMigrate : function (oldVersion) {
633 this.log("Attempting to migrate from version " + oldVersion);
634
635 if (oldVersion > DB_VERSION) {
636 this.log("Downgrading to version " + DB_VERSION);
637 // User's DB is newer. Sanity check that our expected columns are
638 // present, and if so mark the lower version and merrily continue
639 // on. If the columns are borked, something is wrong so blow away
640 // the DB and start from scratch. [Future incompatible upgrades
641 // should swtich to a different table or file.]
642
643 if (!this.dbAreExpectedColumnsPresent())
644 throw Components.Exception("DB is missing expected columns",
645 Cr.NS_ERROR_FILE_CORRUPTED);
646
647 // Change the stored version to the current version. If the user
648 // runs the newer code again, it will see the lower version number
649 // and re-upgrade (to fixup any entries the old code added).
650 this.dbConnection.schemaVersion = DB_VERSION;
651 return;
652 }
653
654 // Upgrade to newer version...
655
656 this.dbConnection.beginTransaction();
657
658 try {
659 for (let v = oldVersion + 1; v <= DB_VERSION; v++) {
660 this.log("Upgrading to version " + v + "...");
661 let migrateFunction = "dbMigrateToVersion" + v;
662 this[migrateFunction]();
663 }
664 } catch (e) {
665 this.log("Migration failed: " + e);
666 this.dbConnection.rollbackTransaction();
667 throw e;
668 }
669
670 this.dbConnection.schemaVersion = DB_VERSION;
671 this.dbConnection.commitTransaction();
672 this.log("DB migration completed.");
673 },
674
675
676 /*
677 * dbMigrateToVersion1
678 *
679 * Updates the DB schema to v1 (bug 463154).
680 * Adds firstUsed, lastUsed, timesUsed columns.
681 */
682 dbMigrateToVersion1 : function () {
683 // Check to see if the new columns already exist (could be a v1 DB that
684 // was downgraded to v0). If they exist, we don't need to add them.
685 let query;
686 ["timesUsed", "firstUsed", "lastUsed"].forEach(function(column) {
687 if (!this.dbColumnExists(column)) {
688 query = "ALTER TABLE moz_formhistory ADD COLUMN " + column + " INTEGER";
689 this.dbConnection.executeSimpleSQL(query);
690 }
691 }, this);
692
693 // Set the default values for the new columns.
694 //
695 // Note that we set the timestamps to 24 hours in the past. We want a
696 // timestamp that's recent (so that "keep form history for 90 days"
697 // doesn't expire things surprisingly soon), but not so recent that
698 // "forget the last hour of stuff" deletes all freshly migrated data.
699 let stmt;
700 query = "UPDATE moz_formhistory " +
701 "SET timesUsed = 1, firstUsed = :time, lastUsed = :time " +
702 "WHERE timesUsed isnull OR firstUsed isnull or lastUsed isnull";
703 let params = { time: (Date.now() - DAY_IN_MS) * 1000 }
704 try {
705 stmt = this.dbCreateStatement(query, params);
706 stmt.execute();
707 } catch (e) {
708 this.log("Failed setting timestamps: " + e);
709 throw e;
710 } finally {
711 if (stmt) {
712 stmt.reset();
713 }
714 }
715 },
716
717
718 /*
719 * dbMigrateToVersion2
720 *
721 * Updates the DB schema to v2 (bug 243136).
722 * Adds lastUsed index, removes moz_dummy_table
723 */
724 dbMigrateToVersion2 : function () {
725 let query = "DROP TABLE IF EXISTS moz_dummy_table";
726 this.dbConnection.executeSimpleSQL(query);
727
728 query = "CREATE INDEX IF NOT EXISTS moz_formhistory_lastused_index ON moz_formhistory (lastUsed)";
729 this.dbConnection.executeSimpleSQL(query);
730 },
731
732
733 /*
734 * dbMigrateToVersion3
735 *
736 * Updates the DB schema to v3 (bug 506402).
737 * Adds guid column and index.
738 */
739 dbMigrateToVersion3 : function () {
740 // Check to see if GUID column already exists, add if needed
741 let query;
742 if (!this.dbColumnExists("guid")) {
743 query = "ALTER TABLE moz_formhistory ADD COLUMN guid TEXT";
744 this.dbConnection.executeSimpleSQL(query);
745
746 query = "CREATE INDEX IF NOT EXISTS moz_formhistory_guid_index ON moz_formhistory (guid)";
747 this.dbConnection.executeSimpleSQL(query);
748 }
749
750 // Get a list of IDs for existing logins
751 let ids = [];
752 query = "SELECT id FROM moz_formhistory WHERE guid isnull";
753 let stmt;
754 try {
755 stmt = this.dbCreateStatement(query);
756 while (stmt.executeStep())
757 ids.push(stmt.row.id);
758 } catch (e) {
759 this.log("Failed getting IDs: " + e);
760 throw e;
761 } finally {
762 if (stmt) {
763 stmt.reset();
764 }
765 }
766
767 // Generate a GUID for each login and update the DB.
768 query = "UPDATE moz_formhistory SET guid = :guid WHERE id = :id";
769 for each (let id in ids) {
770 let params = {
771 id : id,
772 guid : this.generateGUID()
773 };
774
775 try {
776 stmt = this.dbCreateStatement(query, params);
777 stmt.execute();
778 } catch (e) {
779 this.log("Failed setting GUID: " + e);
780 throw e;
781 } finally {
782 if (stmt) {
783 stmt.reset();
784 }
785 }
786 }
787 },
788
789 dbMigrateToVersion4 : function () {
790 if (!this.dbConnection.tableExists("moz_deleted_formhistory")) {
791 this.dbCreateTable("moz_deleted_formhistory", this.dbSchema.tables.moz_deleted_formhistory);
792 }
793 },
794
795 /*
796 * dbAreExpectedColumnsPresent
797 *
798 * Sanity check to ensure that the columns this version of the code expects
799 * are present in the DB we're using.
800 */
801 dbAreExpectedColumnsPresent : function () {
802 for (let name in this.dbSchema.tables) {
803 let table = this.dbSchema.tables[name];
804 let query = "SELECT " +
805 [col for (col in table)].join(", ") +
806 " FROM " + name;
807 try {
808 let stmt = this.dbConnection.createStatement(query);
809 // (no need to execute statement, if it compiled we're good)
810 stmt.finalize();
811 } catch (e) {
812 return false;
813 }
814 }
815
816 this.log("verified that expected columns are present in DB.");
817 return true;
818 },
819
820
821 /*
822 * dbColumnExists
823 *
824 * Checks to see if the named column already exists.
825 */
826 dbColumnExists : function (columnName) {
827 let query = "SELECT " + columnName + " FROM moz_formhistory";
828 try {
829 let stmt = this.dbConnection.createStatement(query);
830 // (no need to execute statement, if it compiled we're good)
831 stmt.finalize();
832 return true;
833 } catch (e) {
834 return false;
835 }
836 },
837
838 /**
839 * _dbClose
840 *
841 * Finalize all statements and close the connection.
842 *
843 * @param aBlocking - Should we spin the loop waiting for the db to be
844 * closed.
845 */
846 _dbClose : function FH__dbClose(aBlocking) {
847 for each (let stmt in this.dbStmts) {
848 stmt.finalize();
849 }
850 this.dbStmts = {};
851
852 let connectionDescriptor = Object.getOwnPropertyDescriptor(FormHistory.prototype, "dbConnection");
853 // Return if the database hasn't been opened.
854 if (!connectionDescriptor || connectionDescriptor.value === undefined)
855 return;
856
857 let completed = false;
858 try {
859 this.dbConnection.asyncClose(function () { completed = true; });
860 } catch (e) {
861 completed = true;
862 Components.utils.reportError(e);
863 }
864
865 let thread = Services.tm.currentThread;
866 while (aBlocking && !completed) {
867 thread.processNextEvent(true);
868 }
869 },
870
871 /*
872 * dbCleanup
873 *
874 * Called when database creation fails. Finalizes database statements,
875 * closes the database connection, deletes the database file.
876 */
877 dbCleanup : function () {
878 this.log("Cleaning up DB file - close & remove & backup")
879
880 // Create backup file
881 let storage = Cc["@mozilla.org/storage/service;1"].
882 getService(Ci.mozIStorageService);
883 let backupFile = this.dbFile.leafName + ".corrupt";
884 storage.backupDatabaseFile(this.dbFile, backupFile);
885
886 this._dbClose(true);
887 this.dbFile.remove(false);
888 }
889 };
890
891 let component = [FormHistory];
892 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);

mercurial