/* * This file is part of TbSync. * * 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/. */ "use strict"; var { DeferredTask } = ChromeUtils.import("resource://gre/modules/DeferredTask.jsm"); var db = { loaded: false, files: { accounts: { name: "accounts68.json", default: JSON.stringify({ sequence: 0, data : {} }) //data[account] = {row} }, folders: { name: "folders68.json", default: JSON.stringify({}) //assoziative array of assoziative array : folders[accountID][folderID] = {row} }, changelog: { name: "changelog68.json", default: JSON.stringify([]), }, }, load: async function () { //DB Concept: //-- on application start, data is read async from json file into object //-- add-on only works on object //-- each time data is changed, an async write job is initiated ms in the future and is resceduled, if another request arrives within that time for (let f in this.files) { this.files[f].write = new DeferredTask(() => this.writeAsync(f), 6000); try { this[f] = await IOUtils.readJSON(TbSync.io.getAbsolutePath(this.files[f].name)); this.files[f].found = true; } catch (e) { //if there is no file, there is no file... this[f] = JSON.parse(this.files[f].default); this.files[f].found = false; Components.utils.reportError(e); } } function getNewDeviceId4Migration() { //taken from https://jsfiddle.net/briguy37/2MVFd/ let d = new Date().getTime(); let uuid = 'xxxxxxxxxxxxxxxxyxxxxxxxxxxx'.replace(/[xy]/g, function(c) { let r = (d + Math.random()*16)%16 | 0; d = Math.floor(d/16); return (c=='x' ? r : (r&0x3|0x8)).toString(16); }); return "MZTB" + uuid; } // try to migrate old accounts file from TB60 if (!this.files["accounts"].found) { try { let accounts = await IOUtils.readJSON(TbSync.io.getAbsolutePath("accounts.json")); for (let d of Object.values(accounts.data)) { console.log("Migrating: " + JSON.stringify(d)); let settings = {}; settings.status = "disabled"; settings.provider = d.provider; settings.https = (d.https == "1"); switch (d.provider) { case "dav": settings.calDavHost = d.host ? d.host : ""; settings.cardDavHost = d.host2 ? d.host2 : ""; settings.serviceprovider = d.serviceprovider; settings.user = d.user; settings.syncGroups = (d.syncGroups == "1"); settings.useCalendarCache = (d.useCache == "1"); break; case "eas": settings.useragent = d.useragent; settings.devicetype = d.devicetype; settings.deviceId = getNewDeviceId4Migration(); settings.asversionselected = d.asversionselected; settings.asversion = d.asversion; settings.host = d.host; settings.user = d.user; settings.servertype = d.servertype; settings.seperator = d.seperator; settings.provision = (d.provision == "1"); settings.displayoverride = (d.displayoverride == "1"); if (d.hasOwnProperty("galautocomplete")) settings.galautocomplete = (d.galautocomplete == "1"); break; } this.addAccount(d.accountname, settings); } } catch (e) { Components.utils.reportError(e); } } this.loaded = true; }, unload: async function () { if (this.loaded) { for (let f in this.files) { try{ //abort write delay timers and write current file content to disk await this.files[f].write.finalize(); } catch (e) { Components.utils.reportError(e); } } } }, saveFile: function (f) { if (this.loaded) { //cancel any pending write and schedule a new delayed write this.files[f].write.disarm(); this.files[f].write.arm(); } }, writeAsync: async function (f) { // if this file was not found/read on load, do not write default content to prevent clearing of data in case of read-errors if (!this.files[f].found && JSON.stringify(this[f]) == this.files[f].default) { return; } let filepath = TbSync.io.getAbsolutePath(this.files[f].name); await IOUtils.writeJSON(filepath, this[f]); }, // simple convenience wrapper saveAccounts: function () { this.saveFile("accounts"); }, saveFolders: function () { this.saveFile("folders"); }, saveChangelog: function () { this.saveFile("changelog"); }, // CHANGELOG FUNCTIONS getItemStatusFromChangeLog: function (parentId, itemId) { for (let i=0; i-1; i-- ) { if (this.changelog[i].parentId == parentId && this.changelog[i].itemId == itemId) { let row = this.changelog.splice(i,1); if (moveToEnd) this.changelog.push(row[0]); this.saveChangelog(); return; } } }, removeAllItemsFromChangeLogWithStatus: function (parentId, status) { for (let i=this.changelog.length-1; i>-1; i-- ) { if (this.changelog[i].parentId == parentId && this.changelog[i].status == status) { let row = this.changelog.splice(i,1); } } this.saveChangelog(); }, // Remove all cards of a parentId from ChangeLog clearChangeLog: function (parentId) { if (parentId) { // we allow extra parameters added to a parentId, but still want to delete all items of that parent // so we check for startsWith instead of equal for (let i=this.changelog.length-1; i>-1; i-- ) { if (this.changelog[i].parentId.startsWith(parentId)) this.changelog.splice(i,1); } this.saveChangelog(); } }, getItemsFromChangeLog: function (parentId, maxnumbertosend, status = null) { //maxnumbertosend = 0 will return all results let log = []; let counts = 0; for (let i=0; i TbSync.providers.loadedProviders.hasOwnProperty(this.accounts.data[accountID].provider)).sort((a, b) => a - b); accounts.allIDs = Object.keys(this.accounts.data).sort((a, b) => a - b) accounts.data = this.accounts.data; return accounts; }, getAccount: function (accountID) { //check if accountID is known if (this.accounts.data.hasOwnProperty(accountID) == false ) { throw "Unknown accountID!" + "\nThrown by db.getAccount("+accountID+ ")"; } else { return this.accounts.data[accountID]; } }, isValidAccountProperty: function (provider, name) { if (["provider"].includes(name)) //internal properties, do not need to be defined by user/provider return true; //check if provider is installed if (!TbSync.providers.loadedProviders.hasOwnProperty(provider)) { TbSync.dump("Error @ isValidAccountProperty", "Unknown provider <"+provider+">!"); return false; } if (TbSync.providers.getDefaultAccountEntries(provider).hasOwnProperty(name)) { return true; } else { TbSync.dump("Error @ isValidAccountProperty", "Unknown account setting <"+name+">!"); return false; } }, getAccountProperty: function (accountID, name) { // if the requested accountID does not exist, getAccount() will fail let data = this.getAccount(accountID); //check if field is allowed and get value or default value if setting is not set if (this.isValidAccountProperty(data.provider, name)) { if (data.hasOwnProperty(name)) return data[name]; else return TbSync.providers.getDefaultAccountEntries(data.provider)[name]; } }, setAccountProperty: function (accountID , name, value) { // if the requested accountID does not exist, getAccount() will fail let data = this.getAccount(accountID); //check if field is allowed, and set given value if (this.isValidAccountProperty(data.provider, name)) { this.accounts.data[accountID][name] = value; } this.saveAccounts(); }, resetAccountProperty: function (accountID , name) { // if the requested accountID does not exist, getAccount() will fail let data = this.getAccount(accountID); let defaults = TbSync.providers.getDefaultAccountEntries(data.provider); //check if field is allowed, and set given value if (this.isValidAccountProperty(data.provider, name)) { this.accounts.data[accountID][name] = defaults[name]; } this.saveAccounts(); }, // FOLDER FUNCTIONS addFolder: function(accountID) { let folderID = TbSync.generateUUID(); let provider = this.getAccountProperty(accountID, "provider"); if (!this.folders.hasOwnProperty(accountID)) this.folders[accountID] = {}; //create folder with default settings this.folders[accountID][folderID] = TbSync.providers.getDefaultFolderEntries(accountID); this.saveFolders(); return folderID; }, deleteFolder: function(accountID, folderID) { delete (this.folders[accountID][folderID]); //if there are no more folders, delete entire account entry if (Object.keys(this.folders[accountID]).length === 0) delete (this.folders[accountID]); this.saveFolders(); }, isValidFolderProperty: function (accountID, field) { if (["cached"].includes(field)) //internal properties, do not need to be defined by user/provider return true; //check if provider is installed let provider = this.getAccountProperty(accountID, "provider"); if (!TbSync.providers.loadedProviders.hasOwnProperty(provider)) { TbSync.dump("Error @ isValidFolderProperty", "Unknown provider <"+provider+"> for accountID <"+accountID+">!"); return false; } if (TbSync.providers.getDefaultFolderEntries(accountID).hasOwnProperty(field)) { return true; } else { TbSync.dump("Error @ isValidFolderProperty", "Unknown folder setting <"+field+"> for accountID <"+accountID+">!"); return false; } }, getFolderProperty: function(accountID, folderID, field) { //does the field exist? let folder = (this.folders.hasOwnProperty(accountID) && this.folders[accountID].hasOwnProperty(folderID)) ? this.folders[accountID][folderID] : null; if (folder === null) { throw "Unknown folder <"+folderID+">!"; } if (this.isValidFolderProperty(accountID, field)) { if (folder.hasOwnProperty(field)) { return folder[field]; } else { let provider = this.getAccountProperty(accountID, "provider"); let defaultFolder = TbSync.providers.getDefaultFolderEntries(accountID); //handle internal fields, that do not have a default value (see isValidFolderProperty) return (defaultFolder[field] ? defaultFolder[field] : ""); } } }, setFolderProperty: function (accountID, folderID, field, value) { if (this.isValidFolderProperty(accountID, field)) { this.folders[accountID][folderID][field] = value; this.saveFolders(); } }, resetFolderProperty: function (accountID, folderID, field) { let provider = this.getAccountProperty(accountID, "provider"); let defaults = TbSync.providers.getDefaultFolderEntries(accountID); if (this.isValidFolderProperty(accountID, field)) { //handle internal fields, that do not have a default value (see isValidFolderProperty) this.folders[accountID][folderID][field] = defaults[field] ? defaults[field] : ""; this.saveFolders(); } }, findFolders: function (folderQuery = {}, accountQuery = {}) { // folderQuery is an object with one or more key:value pairs (logical AND) :: // {key1: value1, key2: value2} // the value itself may be an array (logical OR) let data = []; let folderQueryEntries = Object.entries(folderQuery); let folderFields = folderQueryEntries.map(pair => pair[0]); let folderValues = folderQueryEntries.map(pair => Array.isArray(pair[1]) ? pair[1] : [pair[1]]); let accountQueryEntries = Object.entries(accountQuery); let accountFields = accountQueryEntries.map(pair => pair[0]); let accountValues = accountQueryEntries.map(pair => Array.isArray(pair[1]) ? pair[1] : [pair[1]]); for (let aID in this.folders) { //is this a leftover folder of an account, which no longer there? if (!this.accounts.data.hasOwnProperty(aID)) { delete (this.folders[aID]); this.saveFolders(); continue; } //skip this folder, if it belongs to an account currently not supported (provider not loaded) if (!TbSync.providers.loadedProviders.hasOwnProperty(this.getAccountProperty(aID, "provider"))) { continue; } //does this account match account search options? let accountmatch = true; for (let a = 0; a < accountFields.length && accountmatch; a++) { accountmatch = accountValues[a].some(item => item === this.getAccountProperty(aID, accountFields[a])); //Services.console.logStringMessage(" " + accountFields[a] + ":" + this.getAccountProperty(aID, accountFields[a]) + " in " + JSON.stringify(accountValues[a]) + " ? " + accountmatch); } if (accountmatch) { for (let fID in this.folders[aID]) { //does this folder match folder search options? let foldermatch = true; for (let f = 0; f < folderFields.length && foldermatch; f++) { foldermatch = folderValues[f].some(item => item === this.getFolderProperty(aID, fID, folderFields[f])); //Services.console.logStringMessage(" " + folderFields[f] + ":" + this.getFolderProperty(aID, fID, folderFields[f]) + " in " + JSON.stringify(folderValues[f]) + " ? " + foldermatch); } if (foldermatch) data.push({accountID: aID, folderID: fID, data: this.folders[aID][fID]}); } } } //still a reference to the original data return data; } };