2016-10-12 10:43:58 +03:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Implements an interface of the storage of Form Autofill.
|
|
|
|
*
|
2017-04-24 05:58:37 +03:00
|
|
|
* The data is stored in JSON format, without indentation and the computed
|
|
|
|
* fields, using UTF-8 encoding. With indentation and computed fields applied,
|
|
|
|
* the schema would look like this:
|
2016-10-12 10:43:58 +03:00
|
|
|
*
|
|
|
|
* {
|
|
|
|
* version: 1,
|
2017-05-08 05:43:37 +03:00
|
|
|
* addresses: [
|
2016-10-12 10:43:58 +03:00
|
|
|
* {
|
2017-04-29 02:00:49 +03:00
|
|
|
* guid, // 12 characters
|
2016-10-12 10:43:58 +03:00
|
|
|
*
|
2017-05-08 05:43:37 +03:00
|
|
|
* // address fields
|
2017-03-30 13:08:54 +03:00
|
|
|
* given-name,
|
|
|
|
* additional-name,
|
|
|
|
* family-name,
|
2017-04-29 02:00:49 +03:00
|
|
|
* organization, // Company
|
|
|
|
* street-address, // (Multiline)
|
|
|
|
* address-level2, // City/Town
|
|
|
|
* address-level1, // Province (Standardized code if possible)
|
2017-02-10 07:03:59 +03:00
|
|
|
* postal-code,
|
2017-04-29 02:00:49 +03:00
|
|
|
* country, // ISO 3166
|
2016-10-12 10:43:58 +03:00
|
|
|
* tel,
|
|
|
|
* email,
|
|
|
|
*
|
2017-04-24 05:58:37 +03:00
|
|
|
* // computed fields (These fields are not stored in the file as they are
|
|
|
|
* // generated at runtime.)
|
2017-05-08 05:43:37 +03:00
|
|
|
* name,
|
2017-04-24 05:58:37 +03:00
|
|
|
* address-line1,
|
|
|
|
* address-line2,
|
|
|
|
* address-line3,
|
|
|
|
*
|
2016-10-12 10:43:58 +03:00
|
|
|
* // metadata
|
2017-04-29 02:00:49 +03:00
|
|
|
* timeCreated, // in ms
|
|
|
|
* timeLastUsed, // in ms
|
|
|
|
* timeLastModified, // in ms
|
2016-10-12 10:43:58 +03:00
|
|
|
* timesUsed
|
|
|
|
* }
|
|
|
|
* ]
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
2017-05-04 11:25:46 +03:00
|
|
|
// We expose a singleton from this module. Some tests may import the
|
|
|
|
// constructor via a backstage pass.
|
|
|
|
this.EXPORTED_SYMBOLS = ["profileStorage"];
|
2017-02-15 11:22:38 +03:00
|
|
|
|
2016-10-12 10:43:58 +03:00
|
|
|
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
|
|
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
2017-05-04 11:25:46 +03:00
|
|
|
Cu.import("resource://gre/modules/osfile.jsm");
|
2016-10-12 10:43:58 +03:00
|
|
|
|
2017-02-15 11:22:38 +03:00
|
|
|
Cu.import("resource://formautofill/FormAutofillUtils.jsm");
|
|
|
|
|
2016-10-12 10:43:58 +03:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
|
|
|
|
"resource://gre/modules/JSONFile.jsm");
|
2017-04-12 15:05:54 +03:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillNameUtils",
|
|
|
|
"resource://formautofill/FormAutofillNameUtils.jsm");
|
2016-10-12 10:43:58 +03:00
|
|
|
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
|
|
|
|
"@mozilla.org/uuid-generator;1",
|
|
|
|
"nsIUUIDGenerator");
|
|
|
|
|
2017-05-04 11:25:46 +03:00
|
|
|
const PROFILE_JSON_FILE_NAME = "autofill-profiles.json";
|
|
|
|
|
2016-10-12 10:43:58 +03:00
|
|
|
const SCHEMA_VERSION = 1;
|
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
const VALID_PROFILE_FIELDS = [
|
2017-03-30 13:08:54 +03:00
|
|
|
"given-name",
|
|
|
|
"additional-name",
|
|
|
|
"family-name",
|
2016-10-12 10:43:58 +03:00
|
|
|
"organization",
|
2017-02-10 07:03:59 +03:00
|
|
|
"street-address",
|
|
|
|
"address-level2",
|
|
|
|
"address-level1",
|
|
|
|
"postal-code",
|
2016-10-12 10:43:58 +03:00
|
|
|
"country",
|
|
|
|
"tel",
|
|
|
|
"email",
|
|
|
|
];
|
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
const INTERNAL_FIELDS = [
|
|
|
|
"guid",
|
|
|
|
"timeCreated",
|
|
|
|
"timeLastUsed",
|
|
|
|
"timeLastModified",
|
|
|
|
"timesUsed",
|
|
|
|
];
|
2016-10-12 10:43:58 +03:00
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
/**
|
|
|
|
* Class that manipulates records in a specified collection.
|
|
|
|
*
|
|
|
|
* Note that it is responsible for converting incoming data to a consistent
|
|
|
|
* format in the storage. For example, computed fields will be transformed to
|
|
|
|
* the original fields.
|
|
|
|
*/
|
|
|
|
class AutofillRecords {
|
2016-10-12 10:43:58 +03:00
|
|
|
/**
|
2017-04-29 02:00:49 +03:00
|
|
|
* Creates an AutofillRecords.
|
2016-10-12 10:43:58 +03:00
|
|
|
*
|
2017-04-29 02:00:49 +03:00
|
|
|
* @param {JSONFile} store
|
|
|
|
* An instance of JSONFile.
|
|
|
|
* @param {string} collectionName
|
|
|
|
* A key of "store.data".
|
|
|
|
* @param {Array.<string>} validFields
|
|
|
|
* A list containing non-metadata field names.
|
2016-10-12 10:43:58 +03:00
|
|
|
*/
|
2017-04-29 02:00:49 +03:00
|
|
|
constructor(store, collectionName, validFields) {
|
|
|
|
FormAutofillUtils.defineLazyLogGetter(this, "AutofillRecords:" + collectionName);
|
|
|
|
|
|
|
|
this.VALID_FIELDS = validFields;
|
|
|
|
|
|
|
|
this._store = store;
|
|
|
|
this._collectionName = collectionName;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the schema version number.
|
|
|
|
*
|
|
|
|
* @returns {number}
|
|
|
|
* The current schema version number.
|
|
|
|
*/
|
|
|
|
get version() {
|
|
|
|
return SCHEMA_VERSION;
|
|
|
|
}
|
2016-10-12 10:43:58 +03:00
|
|
|
|
|
|
|
/**
|
2017-04-29 02:00:49 +03:00
|
|
|
* Adds a new record.
|
2016-10-12 10:43:58 +03:00
|
|
|
*
|
2017-04-29 02:00:49 +03:00
|
|
|
* @param {Object} record
|
|
|
|
* The new record for saving.
|
2016-10-12 10:43:58 +03:00
|
|
|
*/
|
2017-04-29 02:00:49 +03:00
|
|
|
add(record) {
|
|
|
|
this.log.debug("add:", record);
|
2016-10-12 10:43:58 +03:00
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
let recordToSave = this._clone(record);
|
|
|
|
this._normalizeRecord(recordToSave);
|
2016-10-12 10:43:58 +03:00
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
let guid;
|
|
|
|
while (!guid || this._findByGUID(guid)) {
|
|
|
|
guid = gUUIDGenerator.generateUUID().toString()
|
|
|
|
.replace(/[{}-]/g, "").substring(0, 12);
|
|
|
|
}
|
|
|
|
recordToSave.guid = guid;
|
2016-10-12 10:43:58 +03:00
|
|
|
|
|
|
|
// Metadata
|
|
|
|
let now = Date.now();
|
2017-04-29 02:00:49 +03:00
|
|
|
recordToSave.timeCreated = now;
|
|
|
|
recordToSave.timeLastModified = now;
|
|
|
|
recordToSave.timeLastUsed = 0;
|
|
|
|
recordToSave.timesUsed = 0;
|
2016-10-12 10:43:58 +03:00
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
this._store.data[this._collectionName].push(recordToSave);
|
2016-10-12 10:43:58 +03:00
|
|
|
this._store.saveSoon();
|
2017-04-29 02:00:49 +03:00
|
|
|
|
2017-02-08 08:42:27 +03:00
|
|
|
Services.obs.notifyObservers(null, "formautofill-storage-changed", "add");
|
2017-04-29 02:00:49 +03:00
|
|
|
}
|
2016-10-12 10:43:58 +03:00
|
|
|
|
|
|
|
/**
|
2017-04-29 02:00:49 +03:00
|
|
|
* Update the specified record.
|
2016-10-12 10:43:58 +03:00
|
|
|
*
|
|
|
|
* @param {string} guid
|
2017-04-29 02:00:49 +03:00
|
|
|
* Indicates which record to update.
|
|
|
|
* @param {Object} record
|
|
|
|
* The new record used to overwrite the old one.
|
2016-10-12 10:43:58 +03:00
|
|
|
*/
|
2017-04-29 02:00:49 +03:00
|
|
|
update(guid, record) {
|
|
|
|
this.log.debug("update:", guid, record);
|
2016-10-12 10:43:58 +03:00
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
let recordFound = this._findByGUID(guid);
|
|
|
|
if (!recordFound) {
|
2017-05-08 05:43:37 +03:00
|
|
|
throw new Error("No matching record.");
|
2016-10-12 10:43:58 +03:00
|
|
|
}
|
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
let recordToUpdate = this._clone(record);
|
|
|
|
this._normalizeRecord(recordToUpdate);
|
|
|
|
for (let field of this.VALID_FIELDS) {
|
|
|
|
if (recordToUpdate[field] !== undefined) {
|
|
|
|
recordFound[field] = recordToUpdate[field];
|
2016-10-12 10:43:58 +03:00
|
|
|
} else {
|
2017-04-29 02:00:49 +03:00
|
|
|
delete recordFound[field];
|
2016-10-12 10:43:58 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
recordFound.timeLastModified = Date.now();
|
2016-10-12 10:43:58 +03:00
|
|
|
|
|
|
|
this._store.saveSoon();
|
2017-04-29 02:00:49 +03:00
|
|
|
|
2017-02-08 08:42:27 +03:00
|
|
|
Services.obs.notifyObservers(null, "formautofill-storage-changed", "update");
|
2017-04-29 02:00:49 +03:00
|
|
|
}
|
2016-10-12 10:43:58 +03:00
|
|
|
|
|
|
|
/**
|
2017-04-29 02:00:49 +03:00
|
|
|
* Notifies the stroage of the use of the specified record, so we can update
|
2016-10-12 10:43:58 +03:00
|
|
|
* the metadata accordingly.
|
|
|
|
*
|
|
|
|
* @param {string} guid
|
2017-04-29 02:00:49 +03:00
|
|
|
* Indicates which record to be notified.
|
2016-10-12 10:43:58 +03:00
|
|
|
*/
|
|
|
|
notifyUsed(guid) {
|
2017-04-29 02:00:49 +03:00
|
|
|
this.log.debug("notifyUsed:", guid);
|
2016-10-12 10:43:58 +03:00
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
let recordFound = this._findByGUID(guid);
|
|
|
|
if (!recordFound) {
|
2017-05-08 05:43:37 +03:00
|
|
|
throw new Error("No matching record.");
|
2016-10-12 10:43:58 +03:00
|
|
|
}
|
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
recordFound.timesUsed++;
|
|
|
|
recordFound.timeLastUsed = Date.now();
|
2016-10-12 10:43:58 +03:00
|
|
|
|
|
|
|
this._store.saveSoon();
|
2017-02-08 08:42:27 +03:00
|
|
|
Services.obs.notifyObservers(null, "formautofill-storage-changed", "notifyUsed");
|
2017-04-29 02:00:49 +03:00
|
|
|
}
|
2016-10-12 10:43:58 +03:00
|
|
|
|
|
|
|
/**
|
2017-04-29 02:00:49 +03:00
|
|
|
* Removes the specified record. No error occurs if the record isn't found.
|
2016-10-12 10:43:58 +03:00
|
|
|
*
|
|
|
|
* @param {string} guid
|
2017-04-29 02:00:49 +03:00
|
|
|
* Indicates which record to remove.
|
2016-10-12 10:43:58 +03:00
|
|
|
*/
|
|
|
|
remove(guid) {
|
2017-04-29 02:00:49 +03:00
|
|
|
this.log.debug("remove:", guid);
|
2016-10-12 10:43:58 +03:00
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
this._store.data[this._collectionName] =
|
|
|
|
this._store.data[this._collectionName].filter(record => record.guid != guid);
|
2016-10-12 10:43:58 +03:00
|
|
|
this._store.saveSoon();
|
2017-04-29 02:00:49 +03:00
|
|
|
|
2017-02-08 08:42:27 +03:00
|
|
|
Services.obs.notifyObservers(null, "formautofill-storage-changed", "remove");
|
2017-04-29 02:00:49 +03:00
|
|
|
}
|
2016-10-12 10:43:58 +03:00
|
|
|
|
|
|
|
/**
|
2017-04-29 02:00:49 +03:00
|
|
|
* Returns the record with the specified GUID.
|
2016-10-12 10:43:58 +03:00
|
|
|
*
|
|
|
|
* @param {string} guid
|
2017-04-29 02:00:49 +03:00
|
|
|
* Indicates which record to retrieve.
|
|
|
|
* @returns {Object}
|
|
|
|
* A clone of the record.
|
2016-10-12 10:43:58 +03:00
|
|
|
*/
|
|
|
|
get(guid) {
|
2017-04-29 02:00:49 +03:00
|
|
|
this.log.debug("get:", guid);
|
2016-10-12 10:43:58 +03:00
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
let recordFound = this._findByGUID(guid);
|
|
|
|
if (!recordFound) {
|
2017-05-08 05:43:37 +03:00
|
|
|
throw new Error("No matching record.");
|
2016-10-12 10:43:58 +03:00
|
|
|
}
|
|
|
|
|
2017-05-08 05:43:37 +03:00
|
|
|
// The record is cloned to avoid accidental modifications from outside.
|
2017-04-29 02:00:49 +03:00
|
|
|
let clonedRecord = this._clone(recordFound);
|
|
|
|
this._recordReadProcessor(clonedRecord);
|
|
|
|
return clonedRecord;
|
|
|
|
}
|
2016-10-12 10:43:58 +03:00
|
|
|
|
|
|
|
/**
|
2017-04-29 02:00:49 +03:00
|
|
|
* Returns all records.
|
2016-10-12 10:43:58 +03:00
|
|
|
*
|
2017-04-29 02:00:49 +03:00
|
|
|
* @param {Object} config
|
|
|
|
* Specifies how data will be retrieved.
|
|
|
|
* @param {boolean} config.noComputedFields
|
|
|
|
* Returns raw record without those computed fields.
|
|
|
|
* @returns {Array.<Object>}
|
|
|
|
* An array containing clones of all records.
|
2016-10-12 10:43:58 +03:00
|
|
|
*/
|
2017-04-29 02:00:49 +03:00
|
|
|
getAll(config = {}) {
|
|
|
|
this.log.debug("getAll", config);
|
2016-10-12 10:43:58 +03:00
|
|
|
|
2017-05-08 05:43:37 +03:00
|
|
|
// Records are cloned to avoid accidental modifications from outside.
|
2017-04-29 02:00:49 +03:00
|
|
|
let clonedRecords = this._store.data[this._collectionName].map(this._clone);
|
|
|
|
clonedRecords.forEach(record => this._recordReadProcessor(record, config));
|
|
|
|
return clonedRecords;
|
|
|
|
}
|
2016-10-12 10:43:58 +03:00
|
|
|
|
2017-01-06 10:37:28 +03:00
|
|
|
/**
|
2017-04-29 02:00:49 +03:00
|
|
|
* Returns the filtered records based on input's information and searchString.
|
2017-01-06 10:37:28 +03:00
|
|
|
*
|
2017-04-29 02:00:49 +03:00
|
|
|
* @returns {Array.<Object>}
|
|
|
|
* An array containing clones of matched record.
|
2017-01-06 10:37:28 +03:00
|
|
|
*/
|
|
|
|
getByFilter({info, searchString}) {
|
2017-04-29 02:00:49 +03:00
|
|
|
this.log.debug("getByFilter:", info, searchString);
|
2017-01-06 10:37:28 +03:00
|
|
|
|
2017-04-24 05:58:37 +03:00
|
|
|
let lcSearchString = searchString.toLowerCase();
|
2017-04-29 02:00:49 +03:00
|
|
|
let result = this.getAll().filter(record => {
|
2017-04-24 05:58:37 +03:00
|
|
|
// Return true if string is not provided and field exists.
|
|
|
|
// TODO: We'll need to check if the address is for billing or shipping.
|
|
|
|
// (Bug 1358941)
|
2017-04-29 02:00:49 +03:00
|
|
|
let name = record[info.fieldName];
|
2017-04-24 05:58:37 +03:00
|
|
|
|
|
|
|
if (!searchString) {
|
|
|
|
return !!name;
|
|
|
|
}
|
|
|
|
|
2017-05-10 10:12:59 +03:00
|
|
|
return name && name.toLowerCase().startsWith(lcSearchString);
|
2017-04-24 05:58:37 +03:00
|
|
|
});
|
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
this.log.debug("getByFilter:", "Returning", result.length, "result(s)");
|
2017-02-15 11:22:38 +03:00
|
|
|
return result;
|
2017-04-29 02:00:49 +03:00
|
|
|
}
|
2017-01-06 10:37:28 +03:00
|
|
|
|
2017-05-08 05:43:37 +03:00
|
|
|
_clone(record) {
|
|
|
|
return Object.assign({}, record);
|
2017-04-29 02:00:49 +03:00
|
|
|
}
|
2016-10-12 10:43:58 +03:00
|
|
|
|
|
|
|
_findByGUID(guid) {
|
2017-04-29 02:00:49 +03:00
|
|
|
let found = this._findIndexByGUID(guid);
|
|
|
|
return found < 0 ? undefined : this._store.data[this._collectionName][found];
|
|
|
|
}
|
|
|
|
|
|
|
|
_findIndexByGUID(guid) {
|
|
|
|
return this._store.data[this._collectionName].findIndex(record => record.guid == guid);
|
|
|
|
}
|
|
|
|
|
|
|
|
_normalizeRecord(record) {
|
|
|
|
this._recordWriteProcessor(record);
|
|
|
|
|
|
|
|
for (let key in record) {
|
|
|
|
if (!this.VALID_FIELDS.includes(key)) {
|
|
|
|
throw new Error(`"${key}" is not a valid field.`);
|
|
|
|
}
|
|
|
|
if (typeof record[key] !== "string" &&
|
|
|
|
typeof record[key] !== "number") {
|
|
|
|
throw new Error(`"${key}" contains invalid data type.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// An interface to be inherited.
|
|
|
|
_recordReadProcessor(record, config) {}
|
|
|
|
|
|
|
|
// An interface to be inherited.
|
|
|
|
_recordWriteProcessor(record) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Addresses extends AutofillRecords {
|
|
|
|
constructor(store) {
|
|
|
|
super(store, "addresses", VALID_PROFILE_FIELDS);
|
|
|
|
}
|
|
|
|
|
|
|
|
_recordReadProcessor(profile, {noComputedFields} = {}) {
|
|
|
|
if (noComputedFields) {
|
|
|
|
return;
|
|
|
|
}
|
2016-10-12 10:43:58 +03:00
|
|
|
|
2017-04-13 12:12:59 +03:00
|
|
|
// Compute name
|
2017-04-29 02:00:49 +03:00
|
|
|
let name = FormAutofillNameUtils.joinNameParts({
|
|
|
|
given: profile["given-name"],
|
|
|
|
middle: profile["additional-name"],
|
|
|
|
family: profile["family-name"],
|
2017-04-13 12:12:59 +03:00
|
|
|
});
|
2017-04-29 02:00:49 +03:00
|
|
|
if (name) {
|
|
|
|
profile.name = name;
|
|
|
|
}
|
2017-04-13 12:12:59 +03:00
|
|
|
|
|
|
|
// Compute address
|
2017-04-29 02:00:49 +03:00
|
|
|
if (profile["street-address"]) {
|
|
|
|
let streetAddress = profile["street-address"].split("\n");
|
2017-04-24 05:58:37 +03:00
|
|
|
// TODO: we should prevent the dataloss by concatenating the rest of lines
|
|
|
|
// with a locale-specific character in the future (bug 1360114).
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
|
|
if (streetAddress[i]) {
|
2017-04-29 02:00:49 +03:00
|
|
|
profile["address-line" + (i + 1)] = streetAddress[i];
|
2017-04-24 05:58:37 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-04-29 02:00:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
_recordWriteProcessor(profile) {
|
|
|
|
// Normalize name
|
|
|
|
if (profile.name) {
|
|
|
|
let nameParts = FormAutofillNameUtils.splitName(profile.name);
|
|
|
|
if (!profile["given-name"] && nameParts.given) {
|
|
|
|
profile["given-name"] = nameParts.given;
|
|
|
|
}
|
|
|
|
if (!profile["additional-name"] && nameParts.middle) {
|
|
|
|
profile["additional-name"] = nameParts.middle;
|
|
|
|
}
|
|
|
|
if (!profile["family-name"] && nameParts.family) {
|
|
|
|
profile["family-name"] = nameParts.family;
|
|
|
|
}
|
|
|
|
delete profile.name;
|
|
|
|
}
|
2017-01-06 10:37:28 +03:00
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
// Normalize address
|
|
|
|
if (profile["address-line1"] || profile["address-line2"] ||
|
|
|
|
profile["address-line3"]) {
|
2017-04-24 05:58:37 +03:00
|
|
|
// Treat "street-address" as "address-line1" if it contains only one line
|
|
|
|
// and "address-line1" is omitted.
|
2017-04-29 02:00:49 +03:00
|
|
|
if (!profile["address-line1"] && profile["street-address"] &&
|
|
|
|
!profile["street-address"].includes("\n")) {
|
|
|
|
profile["address-line1"] = profile["street-address"];
|
|
|
|
delete profile["street-address"];
|
2017-01-06 10:37:28 +03:00
|
|
|
}
|
|
|
|
|
2017-04-24 05:58:37 +03:00
|
|
|
// Remove "address-line*" but keep the values.
|
|
|
|
let addressLines = [1, 2, 3].map(i => {
|
2017-04-29 02:00:49 +03:00
|
|
|
let value = profile["address-line" + i];
|
|
|
|
delete profile["address-line" + i];
|
2017-04-24 05:58:37 +03:00
|
|
|
return value;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Concatenate "address-line*" if "street-address" is omitted.
|
2017-04-29 02:00:49 +03:00
|
|
|
if (!profile["street-address"]) {
|
|
|
|
profile["street-address"] = addressLines.join("\n");
|
2017-04-24 05:58:37 +03:00
|
|
|
}
|
|
|
|
}
|
2017-04-29 02:00:49 +03:00
|
|
|
}
|
|
|
|
}
|
2017-01-06 10:37:28 +03:00
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
function ProfileStorage(path) {
|
|
|
|
this._path = path;
|
|
|
|
this._initializePromise = null;
|
|
|
|
this.INTERNAL_FIELDS = INTERNAL_FIELDS;
|
|
|
|
}
|
2017-04-13 12:12:59 +03:00
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
ProfileStorage.prototype = {
|
|
|
|
get addresses() {
|
|
|
|
if (!this._addresses) {
|
|
|
|
this._store.ensureDataReady();
|
|
|
|
this._addresses = new Addresses(this._store);
|
2017-04-13 12:12:59 +03:00
|
|
|
}
|
2017-04-29 02:00:49 +03:00
|
|
|
return this._addresses;
|
2017-04-13 12:12:59 +03:00
|
|
|
},
|
|
|
|
|
2017-04-29 02:00:49 +03:00
|
|
|
/**
|
|
|
|
* Loads the profile data from file to memory.
|
|
|
|
*
|
|
|
|
* @returns {Promise}
|
|
|
|
* @resolves When the operation finished successfully.
|
|
|
|
* @rejects JavaScript exception.
|
|
|
|
*/
|
|
|
|
initialize() {
|
|
|
|
if (!this._initializePromise) {
|
|
|
|
this._store = new JSONFile({
|
|
|
|
path: this._path,
|
|
|
|
dataPostProcessor: this._dataPostProcessor.bind(this),
|
|
|
|
});
|
|
|
|
this._initializePromise = this._store.load();
|
2016-10-12 10:43:58 +03:00
|
|
|
}
|
2017-04-29 02:00:49 +03:00
|
|
|
return this._initializePromise;
|
2016-10-12 10:43:58 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
_dataPostProcessor(data) {
|
|
|
|
data.version = SCHEMA_VERSION;
|
2017-05-08 05:43:37 +03:00
|
|
|
if (!data.addresses) {
|
|
|
|
data.addresses = [];
|
2016-10-12 10:43:58 +03:00
|
|
|
}
|
|
|
|
return data;
|
|
|
|
},
|
|
|
|
|
|
|
|
// For test only.
|
|
|
|
_saveImmediately() {
|
|
|
|
return this._store._save();
|
|
|
|
},
|
|
|
|
};
|
2017-05-04 11:25:46 +03:00
|
|
|
|
|
|
|
// The singleton exposed by this module.
|
|
|
|
this.profileStorage = new ProfileStorage(
|
|
|
|
OS.Path.join(OS.Constants.Path.profileDir, PROFILE_JSON_FILE_NAME));
|