Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
457
gaia/apps/calendar/js/backend/db.js
Executable file
457
gaia/apps/calendar/js/backend/db.js
Executable file
|
|
@ -0,0 +1,457 @@
|
|||
/* jshint loopfunc: true */
|
||||
define(function(require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var Account = require('models/account');
|
||||
var Local = require('provider/local');
|
||||
var Presets = require('common/presets');
|
||||
var Responder = require('common/responder');
|
||||
var core = require('core');
|
||||
var debug = require('common/debug')('db');
|
||||
var denodeifyAll = require('common/promise').denodeifyAll;
|
||||
var localCalendarId = require('common/constants').localCalendarId;
|
||||
var nextTick = require('common/next_tick');
|
||||
var probablyParseInt = require('common/probably_parse_int');
|
||||
var uuid = require('ext/uuid');
|
||||
|
||||
var idb = self.indexedDB;
|
||||
|
||||
const VERSION = 15;
|
||||
|
||||
var store = Object.freeze({
|
||||
events: 'events',
|
||||
accounts: 'accounts',
|
||||
calendars: 'calendars',
|
||||
busytimes: 'busytimes',
|
||||
settings: 'settings',
|
||||
alarms: 'alarms',
|
||||
icalComponents: 'icalComponents'
|
||||
});
|
||||
|
||||
function Db(name) {
|
||||
this.name = name;
|
||||
Responder.call(this);
|
||||
this._upgradeOperations = [];
|
||||
|
||||
denodeifyAll(this, ['load']);
|
||||
}
|
||||
module.exports = Db;
|
||||
|
||||
Db.prototype = {
|
||||
__proto__: Responder.prototype,
|
||||
|
||||
/**
|
||||
* Database connection
|
||||
*/
|
||||
connection: null,
|
||||
|
||||
load: function(callback) {
|
||||
debug(`Will load ${this.name} db.`);
|
||||
|
||||
var self = this;
|
||||
function setupDefaults() {
|
||||
if (self.oldVersion < 8) {
|
||||
self._setupDefaults(callback);
|
||||
} else {
|
||||
nextTick(callback);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isOpen) {
|
||||
return setupDefaults();
|
||||
}
|
||||
|
||||
this.open(VERSION, setupDefaults);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Opens connection to database.
|
||||
*
|
||||
* @param {Numeric} [version] version of database to open.
|
||||
* default to current version.
|
||||
* Should _only_ be used in testing.
|
||||
*
|
||||
* @param {Function} [callback] first argument is error, second
|
||||
* is result of operation or null
|
||||
* in the error case.
|
||||
*/
|
||||
open: function(version, callback) {
|
||||
if (typeof(version) === 'function') {
|
||||
callback = version;
|
||||
version = VERSION;
|
||||
}
|
||||
|
||||
var req = idb.open(this.name, version);
|
||||
this.version = version;
|
||||
|
||||
var self = this;
|
||||
|
||||
req.onsuccess = function() {
|
||||
self.isOpen = true;
|
||||
self.connection = req.result;
|
||||
|
||||
// if we have pending upgrade operations
|
||||
if (self._upgradeOperations.length) {
|
||||
var pending = self._upgradeOperations.length;
|
||||
|
||||
var operation;
|
||||
while ((operation = self._upgradeOperations.shift())) {
|
||||
operation.call(self, function next() {
|
||||
if (!(--pending)) {
|
||||
callback(null, self);
|
||||
self.emit('open', self);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
callback(null, self);
|
||||
self.emit('open', self);
|
||||
}
|
||||
};
|
||||
|
||||
req.onblocked = function(error) {
|
||||
callback(error, null);
|
||||
self.emit('error', error);
|
||||
};
|
||||
|
||||
req.onupgradeneeded = function(event) {
|
||||
self._handleVersionChange(req.result, event);
|
||||
};
|
||||
|
||||
req.onerror = function(error) {
|
||||
//TODO: steal asuth's error handling...
|
||||
callback(error, null);
|
||||
self.emit('error', error);
|
||||
};
|
||||
},
|
||||
|
||||
transaction: function(list, state) {
|
||||
var names;
|
||||
var self = this;
|
||||
|
||||
if (typeof(list) === 'string') {
|
||||
names = [];
|
||||
names.push(this.store[list] || list);
|
||||
} else {
|
||||
names = list.map(function(name) {
|
||||
return self.store[name] || name;
|
||||
});
|
||||
}
|
||||
|
||||
return this.connection.transaction(names, state || 'readonly');
|
||||
},
|
||||
|
||||
_handleVersionChange: function(db, event) {
|
||||
var newVersion = event.newVersion;
|
||||
var curVersion = event.oldVersion;
|
||||
var transaction = event.currentTarget.transaction;
|
||||
|
||||
this.hasUpgraded = true;
|
||||
this.oldVersion = curVersion;
|
||||
this.upgradedVersion = newVersion;
|
||||
|
||||
for (; curVersion < newVersion; curVersion++) {
|
||||
// if version is < 7 then it was from pre-production
|
||||
// db and we can safely discard its information.
|
||||
if (curVersion < 6) {
|
||||
// ensure clean state if this was an old db.
|
||||
var existingNames = db.objectStoreNames;
|
||||
for (var i = 0; i < existingNames.length; i++) {
|
||||
db.deleteObjectStore(existingNames[i]);
|
||||
}
|
||||
|
||||
// version 0-r are not maintained increment to 6
|
||||
curVersion = 6;
|
||||
|
||||
// busytimes has one event, has one calendar
|
||||
var busytimes = db.createObjectStore(
|
||||
store.busytimes,
|
||||
{ keyPath: '_id' }
|
||||
);
|
||||
|
||||
busytimes.createIndex(
|
||||
'end',
|
||||
'end.utc',
|
||||
{ unique: false, multiEntry: false }
|
||||
);
|
||||
|
||||
busytimes.createIndex(
|
||||
'eventId',
|
||||
'eventId',
|
||||
{ unique: false, multiEntry: false }
|
||||
);
|
||||
|
||||
// events -> belongs to calendar
|
||||
var events = db.createObjectStore(
|
||||
store.events,
|
||||
{ keyPath: '_id' }
|
||||
);
|
||||
|
||||
events.createIndex(
|
||||
'calendarId',
|
||||
'calendarId',
|
||||
{ unique: false, multiEntry: false }
|
||||
);
|
||||
|
||||
events.createIndex(
|
||||
'parentId',
|
||||
'parentId',
|
||||
{ unique: false, multiEntry: false }
|
||||
);
|
||||
|
||||
// accounts -> has many calendars
|
||||
db.createObjectStore(
|
||||
store.accounts, { keyPath: '_id', autoIncrement: true }
|
||||
);
|
||||
|
||||
// calendars -> has many events
|
||||
db.createObjectStore(
|
||||
store.calendars, { keyPath: '_id', autoIncrement: true }
|
||||
);
|
||||
|
||||
} else if (curVersion === 7) {
|
||||
db.createObjectStore(store.settings, { keyPath: '_id' });
|
||||
} else if (curVersion === 8) {
|
||||
var alarms = db.createObjectStore(
|
||||
store.alarms, { keyPath: '_id', autoIncrement: true }
|
||||
);
|
||||
|
||||
alarms.createIndex(
|
||||
'trigger',
|
||||
'trigger.utc',
|
||||
{ unique: false, multiEntry: false }
|
||||
);
|
||||
|
||||
alarms.createIndex(
|
||||
'busytimeId',
|
||||
'busytimeId',
|
||||
{ unique: false, multiEntry: false }
|
||||
);
|
||||
} else if (curVersion === 12) {
|
||||
var icalComponents = db.createObjectStore(
|
||||
store.icalComponents, { keyPath: 'eventId', autoIncrement: false }
|
||||
);
|
||||
|
||||
icalComponents.createIndex(
|
||||
'lastRecurrenceId',
|
||||
'lastRecurrenceId.utc',
|
||||
{ unique: false, multiEntry: false }
|
||||
);
|
||||
} else if (curVersion === 13) {
|
||||
var calendarStore = transaction.objectStore(store.calendars);
|
||||
calendarStore.createIndex(
|
||||
'accountId', 'accountId', { unique: false, multiEntry: false }
|
||||
);
|
||||
} else if (curVersion === 14) {
|
||||
// Bug 851003 - The database may have some busytimes and/or events
|
||||
// which have their calendarId field as a string rather than an int.
|
||||
// We need to fix the calendarIds and also remove any of the idb
|
||||
// objects that have deleted calendars.
|
||||
this.sanitizeEvents(transaction);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 1. Find all events with string calendar ids and index them.
|
||||
* 2. Check for each of the events whether the calendar
|
||||
* they reference still exists.
|
||||
* 3. Fix the events' calendarIds if the calendar still exists else
|
||||
* delete them.
|
||||
* @param {IDBTransaction} trans The active idb transaction during db
|
||||
* upgrade.
|
||||
*/
|
||||
sanitizeEvents: function(trans) {
|
||||
/**
|
||||
* Map from calendar ids to lists of event ids
|
||||
* which we've fixed with that id.
|
||||
* @type {Object.<number, Array.<number>>}
|
||||
*/
|
||||
var badCalendarIdToEventIds = {};
|
||||
|
||||
var objectStore = trans.objectStore(store.events);
|
||||
objectStore.openCursor().onsuccess = (function(evt) {
|
||||
var cursor = evt.target.result;
|
||||
if (!cursor) {
|
||||
return this._updateXorDeleteEvents(badCalendarIdToEventIds, trans);
|
||||
}
|
||||
|
||||
var calendarId = cursor.value.calendarId;
|
||||
if (typeof(calendarId) === 'number') {
|
||||
// Nothing to do here!
|
||||
return cursor.continue();
|
||||
}
|
||||
|
||||
// Record the bad reference.
|
||||
var eventIds = badCalendarIdToEventIds[calendarId] || [];
|
||||
eventIds.push(cursor.key);
|
||||
badCalendarIdToEventIds[calendarId] = eventIds;
|
||||
cursor.continue();
|
||||
}).bind(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* 1. Check for each of the events whether the calendar
|
||||
* they reference still exists.
|
||||
* 2. Fix the events' calendarIds if the calendar still exists else
|
||||
* delete them.
|
||||
*
|
||||
* @param {Object.<number, Array.<number>>} badCalendarIdToEventIds Map
|
||||
* from calendar ids to lists of event ids which we've fixed with
|
||||
* that id.
|
||||
* @param {IDBTransaction} trans The active idb transaction during db
|
||||
* upgrade.
|
||||
* @private
|
||||
*/
|
||||
_updateXorDeleteEvents: function(badCalendarIdToEventIds, trans) {
|
||||
var calendarIds = Object.keys(badCalendarIdToEventIds);
|
||||
calendarIds.forEach(function(calendarId) {
|
||||
//Bug 887698 cases for calendarIds of types strings or integers
|
||||
calendarId = probablyParseInt(calendarId);
|
||||
var eventIds = badCalendarIdToEventIds[calendarId];
|
||||
var calendars = trans.objectStore(store.calendars);
|
||||
calendars.get(calendarId).onsuccess = (function(evt) {
|
||||
var result = evt.target.result;
|
||||
if (result) {
|
||||
this._updateEvents(eventIds, calendarId, trans);
|
||||
} else {
|
||||
this._deleteEvents(eventIds, trans);
|
||||
}
|
||||
}).bind(this);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update a collection of events and the busytimes that depend on them.
|
||||
*
|
||||
* @param {Array.<number>} eventIds An array of event ids for the events.
|
||||
* @param {number} calendarId A numerical id to set as calendarId.
|
||||
* @param {IDBTransaction} trans The active idb transaction during db
|
||||
* upgrade.
|
||||
* @private
|
||||
*/
|
||||
_updateEvents: function(eventIds, calendarId, trans) {
|
||||
var eventStore = trans.objectStore(store.events);
|
||||
var busytimeStore = trans.objectStore(store.busytimes);
|
||||
var busytimeStoreIndexedByEventId = busytimeStore.index('eventId');
|
||||
|
||||
eventIds.forEach(function(eventId) {
|
||||
eventStore.get(eventId).onsuccess = function(evt) {
|
||||
var result = evt.target.result;
|
||||
result.calendarId = calendarId;
|
||||
eventStore.put(result);
|
||||
};
|
||||
|
||||
busytimeStoreIndexedByEventId.get(eventId).onsuccess = function(evt) {
|
||||
var result = evt.target.result;
|
||||
result.calendarId = calendarId;
|
||||
busytimeStore.put(result);
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a collection of events and the busytimes that depend on them.
|
||||
*
|
||||
* @param {Array.<number>} eventIds An array of event ids for the events.
|
||||
* @param {IDBTransaction} trans The active idb transaction during db
|
||||
* upgrade.
|
||||
* @private
|
||||
*/
|
||||
_deleteEvents: function(eventIds, trans) {
|
||||
var events = core.storeFactory.get('Event');
|
||||
eventIds.forEach(function(eventId) {
|
||||
events.remove(eventId, trans);
|
||||
});
|
||||
},
|
||||
|
||||
get store() {
|
||||
return store;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shortcut method for connection.close
|
||||
*/
|
||||
close: function() {
|
||||
if (this.connection) {
|
||||
this.isOpen = false;
|
||||
this.connection.close();
|
||||
this.connection = null;
|
||||
}
|
||||
},
|
||||
|
||||
clearNonCredentials: function(callback) {
|
||||
var stores = ['events', 'busytimes'];
|
||||
var trans = this.transaction(
|
||||
stores,
|
||||
'readwrite'
|
||||
);
|
||||
|
||||
trans.addEventListener('complete', callback);
|
||||
|
||||
stores.forEach(function(store) {
|
||||
store = trans.objectStore(store);
|
||||
store.clear();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup default values for initial calendar load.
|
||||
*/
|
||||
_setupDefaults: function(callback) {
|
||||
debug('Will setup defaults.');
|
||||
var storeFactory = core.storeFactory;
|
||||
var calendarStore = storeFactory.get('Calendar');
|
||||
var accountStore = storeFactory.get('Account');
|
||||
|
||||
var trans = this.transaction(
|
||||
['accounts', 'calendars'],
|
||||
'readwrite'
|
||||
);
|
||||
|
||||
if (callback) {
|
||||
trans.addEventListener('error', function(err) {
|
||||
callback(err);
|
||||
});
|
||||
|
||||
trans.addEventListener('complete', function() {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
var options = Presets.local.options;
|
||||
debug('Creating local calendar with options:', options);
|
||||
var account = new Account(options);
|
||||
account.preset = 'local';
|
||||
account._id = uuid();
|
||||
|
||||
var calendar = {
|
||||
_id: localCalendarId,
|
||||
accountId: account._id,
|
||||
remote: Local.defaultCalendar()
|
||||
};
|
||||
|
||||
accountStore.persist(account, trans);
|
||||
calendarStore.persist(calendar, trans);
|
||||
},
|
||||
|
||||
deleteDatabase: function(callback) {
|
||||
var req = idb.deleteDatabase(this.name);
|
||||
|
||||
req.onblocked = function() {
|
||||
// improve interface
|
||||
callback(new Error('blocked'));
|
||||
};
|
||||
|
||||
req.onsuccess = function(event) {
|
||||
callback(null, event);
|
||||
};
|
||||
|
||||
req.onerror = function(event) {
|
||||
callback(event, null);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
Loading…
Reference in a new issue