Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
329 lines
8.3 KiB
JavaScript
Executable file
329 lines
8.3 KiB
JavaScript
Executable file
define(function(require, exports, module) {
|
|
'use strict';
|
|
|
|
var Abstract = require('./abstract');
|
|
var CalendarModel = require('models/calendar');
|
|
var core = require('core');
|
|
var denodeifyAll = require('common/promise').denodeifyAll;
|
|
var localCalendarId = require('common/constants').localCalendarId;
|
|
var probablyParseInt = require('common/probably_parse_int');
|
|
|
|
function Store() {
|
|
Abstract.apply(this, arguments);
|
|
this._usedColors = [];
|
|
|
|
denodeifyAll(this, [
|
|
'markWithError',
|
|
'remotesByAccount',
|
|
'sync',
|
|
'providerFor',
|
|
'ownersOf'
|
|
]);
|
|
}
|
|
module.exports = Store;
|
|
|
|
/**
|
|
* Remote calendar colors
|
|
*/
|
|
Store.REMOTE_COLORS = [
|
|
'#00aacc', // light blue
|
|
'#bad600', // light green
|
|
'#df4784', // pink
|
|
'#f9bc17', // yellow
|
|
'#0766b7', // dark blue
|
|
'#76a408', // dark green
|
|
'#33a185' // teal
|
|
];
|
|
|
|
/**
|
|
* Local calendar color (orange)
|
|
*/
|
|
Store.LOCAL_COLOR = '#f97c17',
|
|
|
|
/**
|
|
* List of possible calendar capabilities.
|
|
*/
|
|
Store.capabilities = {
|
|
createEvent: 'canCreateEvent',
|
|
updateEvent: 'canUpdateEvent',
|
|
deleteEvent: 'canDeleteEvent'
|
|
};
|
|
|
|
Store.prototype = {
|
|
__proto__: Abstract.prototype,
|
|
|
|
_store: 'calendars',
|
|
|
|
_dependentStores: [
|
|
'calendars', 'events', 'busytimes',
|
|
'alarms', 'icalComponents'
|
|
],
|
|
|
|
_parseId: probablyParseInt,
|
|
|
|
_createModel: function(obj, id) {
|
|
if (!(obj instanceof CalendarModel)) {
|
|
obj = new CalendarModel(obj);
|
|
}
|
|
|
|
if (typeof(id) !== 'undefined') {
|
|
obj._id = id;
|
|
}
|
|
|
|
return obj;
|
|
},
|
|
|
|
_removeDependents: function(id, trans) {
|
|
var store = core.storeFactory.get('Event');
|
|
store.removeByIndex('calendarId', id, trans);
|
|
},
|
|
|
|
/**
|
|
* Marks a given calendar with an error.
|
|
*
|
|
* Emits a 'error' event immediately.. This method is typically
|
|
* triggered by an account wide error.
|
|
*
|
|
*
|
|
* @param {Object} calendar model.
|
|
* @param {Calendar.Error} error for given calendar.
|
|
* @param {IDBTransaction} transaction optional.
|
|
* @param {Function} callback fired when model is saved [err, id, model].
|
|
*/
|
|
markWithError: function(calendar, error, trans, callback) {
|
|
if (typeof(trans) === 'function') {
|
|
callback = trans;
|
|
trans = null;
|
|
}
|
|
|
|
if (!calendar._id) {
|
|
throw new Error('given calendar must be persisted.');
|
|
}
|
|
|
|
calendar.error = {
|
|
name: error.name,
|
|
date: new Date()
|
|
};
|
|
|
|
this.persist(calendar, trans, callback);
|
|
},
|
|
|
|
persist: function(calendar, trans, callback) {
|
|
if (typeof(trans) === 'function') {
|
|
callback = trans;
|
|
trans = undefined;
|
|
}
|
|
|
|
this._updateCalendarColor(calendar);
|
|
|
|
var cb = callback;
|
|
var cached = this._cached[calendar._id];
|
|
|
|
if (cached && cached.localDisplayed !== calendar.localDisplayed) {
|
|
cb = function(err, id, model) {
|
|
this.emit('calendarVisibilityChange', id, model);
|
|
callback(err, id, model);
|
|
}.bind(this);
|
|
}
|
|
|
|
Abstract.prototype.persist.call(this, calendar, trans, cb);
|
|
},
|
|
|
|
remove: function(id, trans, callback) {
|
|
this._removeCalendarColorFromCache(id);
|
|
Abstract.prototype.remove.apply(this, arguments);
|
|
},
|
|
|
|
_updateCalendarColor: function(calendar) {
|
|
// we avoid storing multiple colors for same calendar in case of an
|
|
// "update" operation
|
|
this._removeCalendarColorFromCache(calendar._id);
|
|
this._setCalendarColor(calendar);
|
|
// cache is built asynchronously, we need to store the color as soon as
|
|
// possible to avoid adding same color multiple times in a row (eg.
|
|
// account with multiple calendars will call persist multiple times)
|
|
this._usedColors.push(calendar.color);
|
|
},
|
|
|
|
_removeCalendarColorFromCache: function(id) {
|
|
// we need to remove the color from index as soon as possible to avoid
|
|
// race conditions (remove is async)
|
|
var color = this.getColorByCalendarId(id);
|
|
var index = this._usedColors.indexOf(color);
|
|
if (index !== -1) {
|
|
this._usedColors.splice(index, 1);
|
|
}
|
|
},
|
|
|
|
getColorByCalendarId: function(id) {
|
|
return this._cached[id] && this._cached[id].color;
|
|
},
|
|
|
|
_setCalendarColor: function(calendar) {
|
|
// local calendar should always use the same color
|
|
if (calendar._id === localCalendarId) {
|
|
calendar.color = Store.LOCAL_COLOR;
|
|
return;
|
|
}
|
|
|
|
// restore previous color only if it is part of the palette, otherwise we
|
|
// get the next available color (or least used)
|
|
var prevColor = this.getColorByCalendarId(calendar._id);
|
|
if (prevColor && Store.REMOTE_COLORS.indexOf(prevColor) !== -1) {
|
|
calendar.color = prevColor;
|
|
} else {
|
|
calendar.color = this._getNextColor();
|
|
}
|
|
},
|
|
|
|
_getNextColor: function() {
|
|
var available = Store.REMOTE_COLORS.filter(function(color) {
|
|
return this._usedColors.indexOf(color) === -1;
|
|
}, this);
|
|
|
|
return available.length ? available[0] : this._getLeastUsedColor();
|
|
},
|
|
|
|
_getLeastUsedColor: function() {
|
|
var counter = {};
|
|
this._usedColors.forEach(function(color) {
|
|
counter[color] = (counter[color] || 0) + 1;
|
|
});
|
|
|
|
var leastUsedColor;
|
|
var leastUsedCount = Infinity;
|
|
for (var color in counter) {
|
|
if (counter[color] < leastUsedCount) {
|
|
leastUsedCount = counter[color];
|
|
leastUsedColor = color;
|
|
}
|
|
}
|
|
|
|
return leastUsedColor;
|
|
},
|
|
|
|
shouldDisplayCalendar: function(calendarId) {
|
|
var calendar = this._cached[calendarId];
|
|
return calendar && calendar.localDisplayed;
|
|
},
|
|
|
|
/**
|
|
* Find calendars in a specific account.
|
|
* Results will be returned in an object where
|
|
* the key is the remote.id and the value is the calendar.
|
|
*
|
|
* @param {String|Numeric} accountId id of account.
|
|
* @param {Function} callback [err, object] see above.
|
|
*/
|
|
remotesByAccount: function(accountId, trans, callback) {
|
|
if (typeof(trans) === 'function') {
|
|
callback = trans;
|
|
trans = null;
|
|
}
|
|
|
|
if (!trans) {
|
|
trans = core.db.transaction(this._store);
|
|
}
|
|
|
|
var store = trans.objectStore(this._store);
|
|
|
|
var reqKey = IDBKeyRange.only(accountId);
|
|
var req = store.index('accountId').mozGetAll(reqKey);
|
|
|
|
req.onerror = function remotesError(e) {
|
|
callback(e.target.error);
|
|
};
|
|
|
|
var self = this;
|
|
req.onsuccess = function remotesSuccess(e) {
|
|
var result = Object.create(null);
|
|
e.target.result.forEach(function(calendar) {
|
|
result[calendar.remote.id] = self._createModel(
|
|
calendar,
|
|
calendar._id
|
|
);
|
|
});
|
|
|
|
callback(null, result);
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Sync remote and local events for a calendar.
|
|
*
|
|
* TODO: Deprecate use of this function in favor of a sync methods
|
|
* inside of providers.
|
|
*/
|
|
sync: function(account, calendar, callback) {
|
|
var provider = core.providerFactory.get(account.providerType);
|
|
provider.syncEvents(account, calendar, callback);
|
|
},
|
|
|
|
/**
|
|
* Shortcut to find provider for calendar.
|
|
*
|
|
* @param {Calendar.Models.Calendar} calendar input calendar.
|
|
* @param {Function} callback [err, provider].
|
|
*/
|
|
providerFor: function(calendar, callback) {
|
|
this.ownersOf(calendar, function(err, owners) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
callback(null, core.providerFactory.get(owners.account.providerType));
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Finds calendar/account for a given event.
|
|
*
|
|
* TODO: think about moving this function into its
|
|
* own file as a mixin.
|
|
*
|
|
* @param {Object|String|Numeric} objectOrId must contain .calendarId.
|
|
* @param {Function} callback [err, { ... }].
|
|
*/
|
|
ownersOf: function(objectOrId, callback) {
|
|
var result = {};
|
|
|
|
var accountStore = core.storeFactory.get('Account');
|
|
|
|
// case 1. given a calendar
|
|
if (objectOrId instanceof CalendarModel) {
|
|
result.calendar = objectOrId;
|
|
accountStore.get(objectOrId.accountId, fetchAccount);
|
|
return;
|
|
}
|
|
|
|
// case 2 given a calendar id or object
|
|
|
|
if (typeof(objectOrId) === 'object') {
|
|
objectOrId = objectOrId.calendarId;
|
|
}
|
|
|
|
// why??? because we use this method in event store too..
|
|
var calendarStore = core.storeFactory.get('Calendar');
|
|
calendarStore.get(objectOrId, fetchCalendar);
|
|
|
|
function fetchCalendar(err, calendar) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
result.calendar = calendar;
|
|
accountStore.get(calendar.accountId, fetchAccount);
|
|
}
|
|
|
|
function fetchAccount(err, account) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
result.account = account;
|
|
callback(null, result);
|
|
}
|
|
}
|
|
};
|
|
|
|
});
|