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 /* This is a JavaScript module (JSM) to be imported via
6   Components.utils.import() and acts as a singleton. Only the following
7   listed symbols will exposed on import, and only when and where imported.
8  */
9
10var EXPORTED_SYMBOLS = ["FormData"];
11
12const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
13
14Cu.import("resource://tps/logger.jsm");
15
16Cu.import("resource://gre/modules/FormHistory.jsm");
17Cu.import("resource://gre/modules/Log.jsm");
18
19/**
20 * FormDB
21 *
22 * Helper object containing methods to interact with the FormHistory module.
23 */
24var FormDB = {
25  _update(data) {
26    return new Promise((resolve, reject) => {
27      let handlers = {
28        handleError(error) {
29          Logger.logError("Error occurred updating form history: " + Log.exceptionStr(error));
30          reject(error);
31        },
32        handleCompletion(reason) {
33          resolve();
34        }
35      }
36      FormHistory.update(data, handlers);
37    });
38  },
39
40  /**
41   * insertValue
42   *
43   * Adds the specified value for the specified fieldname into form history.
44   *
45   * @param fieldname The form fieldname to insert
46   * @param value The form value to insert
47   * @param us The time, in microseconds, to use for the lastUsed
48   *        and firstUsed columns
49   * @return Promise<undefined>
50   */
51  insertValue(fieldname, value, us) {
52    let data = { op: "add", fieldname, value, timesUsed: 1,
53                 firstUsed: us, lastUsed: us }
54    return this._update(data);
55  },
56
57  /**
58   * updateValue
59   *
60   * Updates a row in the moz_formhistory table with a new value.
61   *
62   * @param id The id of the row to update
63   * @param newvalue The new value to set
64   * @return Promise<undefined>
65   */
66  updateValue(id, newvalue) {
67    return this._update({ op: "update", guid: id, value: newvalue });
68  },
69
70  /**
71   * getDataForValue
72   *
73   * Retrieves a set of values for a row in the database that
74   * corresponds to the given fieldname and value.
75   *
76   * @param fieldname The fieldname of the row to query
77   * @param value The value of the row to query
78   * @return Promise<null if no row is found with the specified fieldname and value,
79   *         or an object containing the row's guid, lastUsed, and firstUsed
80   *         values>
81   */
82  getDataForValue(fieldname, value) {
83    return new Promise((resolve, reject) => {
84      let result = null;
85      let handlers = {
86        handleResult(oneResult) {
87          if (result != null) {
88            reject("more than 1 result for this query");
89            return;
90          }
91          result = oneResult;
92        },
93        handleError(error) {
94          Logger.logError("Error occurred updating form history: " + Log.exceptionStr(error));
95          reject(error);
96        },
97        handleCompletion(reason) {
98          resolve(result);
99        }
100      }
101      FormHistory.search(["guid", "lastUsed", "firstUsed"], { fieldname }, handlers);
102    });
103  },
104
105  /**
106   * remove
107   *
108   * Removes the specified GUID from the database.
109   *
110   * @param guid The guid of the item to delete
111   * @return Promise<>
112   */
113   remove(guid) {
114    return this._update({ op: "remove", guid });
115  },
116};
117
118/**
119 * FormData class constructor
120 *
121 * Initializes instance properties.
122 */
123function FormData(props, usSinceEpoch) {
124  this.fieldname = null;
125  this.value = null;
126  this.date = 0;
127  this.newvalue = null;
128  this.usSinceEpoch = usSinceEpoch;
129
130  for (var prop in props) {
131    if (prop in this)
132      this[prop] = props[prop];
133  }
134}
135
136/**
137 * FormData instance methods
138 */
139FormData.prototype = {
140  /**
141   * hours_to_us
142   *
143   * Converts hours since present to microseconds since epoch.
144   *
145   * @param hours The number of hours since the present time (e.g., 0 is
146   *        'now', and -1 is 1 hour ago)
147   * @return the corresponding number of microseconds since the epoch
148   */
149  hours_to_us: function(hours) {
150    return this.usSinceEpoch + (hours * 60 * 60 * 1000 * 1000);
151  },
152
153  /**
154   * Create
155   *
156   * If this FormData object doesn't exist in the moz_formhistory database,
157   * add it.  Throws on error.
158   *
159   * @return nothing
160   */
161  Create: function() {
162    Logger.AssertTrue(this.fieldname != null && this.value != null,
163      "Must specify both fieldname and value");
164
165    return FormDB.getDataForValue(this.fieldname, this.value).then(formdata => {
166      if (!formdata) {
167        // this item doesn't exist yet in the db, so we need to insert it
168        return FormDB.insertValue(this.fieldname, this.value,
169                                  this.hours_to_us(this.date));
170      } else {
171        /* Right now, we ignore this case.  If bug 552531 is ever fixed,
172           we might need to add code here to update the firstUsed or
173           lastUsed fields, as appropriate.
174         */
175      }
176    });
177  },
178
179  /**
180   * Find
181   *
182   * Attempts to locate an entry in the moz_formhistory database that
183   * matches the fieldname and value for this FormData object.
184   *
185   * @return true if this entry exists in the database, otherwise false
186   */
187  Find: function() {
188    return FormDB.getDataForValue(this.fieldname, this.value).then(formdata => {
189      let status = formdata != null;
190      if (status) {
191        /*
192        //form history dates currently not synced!  bug 552531
193        let us = this.hours_to_us(this.date);
194        status = Logger.AssertTrue(
195          us >= formdata.firstUsed && us <= formdata.lastUsed,
196          "No match for with that date value");
197
198        if (status)
199        */
200          this.id = formdata.guid;
201      }
202      return status;
203    });
204  },
205
206  /**
207   * Remove
208   *
209   * Removes the row represented by this FormData instance from the
210   * moz_formhistory database.
211   *
212   * @return nothing
213   */
214  Remove: function() {
215    /* Right now Weave doesn't handle this correctly, see bug 568363.
216     */
217    return FormDB.remove(this.id);
218  },
219};
220