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:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,160 @@
define(function(require, exports, module) {
'use strict';
var denodeifyAll = require('common/promise').denodeifyAll;
var nextTick = require('common/next_tick');
function Abstract(options) {
var key;
for (key in options) {
if (options.hasOwnProperty(key)) {
this[key] = options[key];
}
}
denodeifyAll(this, [
'eventCapabilities',
'getAccount',
'findCalendars',
'syncEvents',
'ensureRecurrencesExpanded',
'createEvent',
'updateEvent',
'deleteEvent'
]);
}
module.exports = Abstract;
Abstract.prototype = {
// orange (used by local calendar)
defaultColor: '#F97C17',
/**
* Does this provider require credentials.
*/
useCredentials: false,
/**
* Does this provider require a url.
*/
useUrl: false,
/**
* Can provider sync with remote server?
*/
canSync: false,
/**
* Can expand recurring events?
*/
canExpandRecurringEvents: false,
/**
* - domain: (String)
* - password: (String)
* - user: (String)
*
* @param {Object} account user credentials.
* @param {Function} callback node style (err, result).
*/
getAccount: function(account, callback) {},
/**
* Attempts to find all calendars
* for a given account.
*
*
* account: (same as getAccount)
*
* @param {Object} account user credentials.
* @param {Function} callback node style (err, result).
*/
findCalendars: function() {},
/**
* Sync remote and local events.
*
*/
syncEvents: function(account, calendar, callback) {},
/**
* Ensures recurring events are expanded up to the given date.
*
* Its very important to correctly return the second callback arg
* in the subclasses callback. When requiredExpansion is returned as true a
* second call will likely be made to ensureRecurrencesExpanded to verify
* there are no more pending events for the date (controller handles this).
*
* @param {Date} date to expand recurring events to.
* @param {Function} callback [err, requiredExpansion].
* first argument is error, second indicates if any expansion was done.
*/
ensureRecurrencesExpanded: function(date, callback) {},
/**
* Update an event
*
* @param {Object} event record from event store.
*
* @param {Object} [busytime] optional busytime instance
* when a busytime is passed the edit is treated
* as an "exception" and will only edit the one recurrence
* related to the busytime. This may result in the creation
* of a new "event" related to the busytime.
*/
updateEvent: function(event, busytime, callback) {},
/**
* Delete event
*
* @param {Object} event record from the event store.
* @param {Object} [busytime] optional busytime instance
* when given it will only remove this occurence/exception
* of the event rather then the entire sequence of events.
*/
deleteEvent: function(event, busytime, callback) {},
/**
* Create an event
*/
createEvent: function(event, callback) {},
/**
* Returns an object with three keys used to
* determine the capabilities of a given calendar.
*
* - canCreate (Boolean)
* - canUpdate (Boolean)
* - canDelete (Boolean)
*
* @param {Object} calendar full calendar details.
*/
calendarCapabilities: function(calendar) {
return {
canCreateEvent: true,
canUpdateEvent: true,
canDeleteEvent: true
};
},
/**
* Returns the capabilities of a single event.
*
* @param {Object} event object.
* @param {Function} callback [err, caps].
*/
eventCapabilities: function(event, callback) {
var caps = this.calendarCapabilities();
nextTick(function() {
callback(null, {
canCreate: caps.canCreateEvent,
canUpdate: caps.canUpdateEvent,
canDelete: caps.canDeleteEvent
});
});
}
};
});

View file

@ -0,0 +1,712 @@
define(function(require, exports, module) {
'use strict';
var Abstract = require('./abstract');
var Authentication = require('common/error').Authentication;
var Calc = require('common/calc');
var CaldavPullEvents = require('./caldav_pull_events');
var CalendarError = require('common/error');
var InvalidServer = require('common/error').InvalidServer;
var Local = require('./local');
var ServerFailure = require('common/error').ServerFailure;
var core = require('core');
var isOffline = require('common/is_offline');
var mutations = require('event_mutations');
var nextTick = require('common/next_tick');
var CALDAV_ERROR_MAP = {
'caldav-authentication': Authentication,
'caldav-invalid-entrypoint': InvalidServer,
'caldav-server-failure': ServerFailure
};
function mapError(error, detail) {
console.error('Error with name:', error.name);
var calError = CALDAV_ERROR_MAP[error.name];
if (!calError) {
calError = new CalendarError(error.name, detail);
} else {
calError = new calError(detail);
}
return calError;
}
function CaldavProvider() {
Abstract.apply(this, arguments);
var storeFactory = core.storeFactory;
this.service = core.serviceController;
this.accounts = storeFactory.get('Account');
this.busytimes = storeFactory.get('Busytime');
this.events = storeFactory.get('Event');
this.icalComponents = storeFactory.get('IcalComponent');
}
module.exports = CaldavProvider;
CaldavProvider.prototype = {
__proto__: Abstract.prototype,
role: 'caldav',
useUrl: true,
useCredentials: true,
canSync: true,
canExpandRecurringEvents: true,
// allow us to test the offline behavior
isOffline: isOffline,
/**
* Number of dates in the past to sync.
* This is usually from the first sync date.
*/
daysToSyncInPast: 30,
canCreateEvent: true,
canUpdateEvent: true,
canDeleteEvent: true,
hasAccountSettings: true,
/**
* Error handling can be complex- this is the centralized location where
* methods can send their error state and some context (an account).
*
* this._handleServiceError(
* err,
* { account: account, calendar: calendar }
* );
*
* @param {Object} rawErr from service.
* @param {Object} detail for the error.
*/
_handleServiceError: function(rawErr, detail) {
var calendarErr = mapError(rawErr, detail);
// when we receive a permanent error we should mark the account with an
// error.
if (
calendarErr instanceof Authentication ||
calendarErr instanceof InvalidServer
) {
// there must always be an account
if (detail.account) {
// but we only mark it with a permanent error if its persisted.
if (detail.account._id) {
this.accounts.markWithError(detail.account, calendarErr);
}
} else {
console.error('Permanent server error without an account!');
}
}
return calendarErr;
},
/**
* Determines the capabilities of a specific calendar.
*
* The .remote property should contain a .privilegeSet array
* with the caldav specific names of privileges.
* In the case where .privilegeSet is missing all privileges are granted.
*
* (see http://tools.ietf.org/html/rfc3744#section-5.4).
*
* - write-content: (PUT) can edit/add events
* - unbind: (DELETE) remove events
*
*
* There are aggregate values (write for example) but
* the spec states the specific permissions must also be expanded
* so even if they have full write permissions we only check
* for write-content.
*
* @param {Object} calendar object with caldav remote details.
* @return {Object} object with three properties
* (canUpdate, canDelete, canCreate).
*/
calendarCapabilities: function(calendar) {
var remote = calendar.remote;
if (!remote.privilegeSet) {
return {
canUpdateEvent: true,
canDeleteEvent: true,
canCreateEvent: true
};
}
var privilegeSet = remote.privilegeSet;
var canWriteConent = privilegeSet.indexOf('write-content') !== -1;
return {
canUpdateEvent: canWriteConent,
canCreateEvent: canWriteConent,
canDeleteEvent: privilegeSet.indexOf('unbind') !== -1
};
},
/**
* Returns the capabilities of a single event.
*
* @param {Object} event local object.
* @param {Function} callback [err, caps].
*/
eventCapabilities: function(event, callback) {
if (event.remote.isRecurring) {
// XXX: for now recurring events cannot be edited
nextTick(function() {
callback(null, {
canUpdate: false,
canDelete: false,
canCreate: false
});
});
} else {
var calendarStore = core.storeFactory.get('Calendar');
calendarStore.get(event.calendarId, function(err, calendar) {
if (err) {
return callback(err);
}
var caps = this.calendarCapabilities(
calendar
);
callback(null, {
canCreate: caps.canCreateEvent,
canUpdate: caps.canUpdateEvent,
canDelete: caps.canDeleteEvent
});
}.bind(this));
}
},
getAccount: function(account, callback) {
if (this.bailWhenOffline(callback)) {
return;
}
var self = this;
this.service.request(
'caldav',
'getAccount',
account,
function(err, data) {
if (err) {
return callback(
self._handleServiceError(err, { account: account })
);
}
callback(null, data);
}
);
},
/**
* Hook to format remote data if needed.
*/
formatRemoteCalendar: function(calendar) {
if (!calendar.color) {
calendar.color = this.defaultColor;
}
return calendar;
},
findCalendars: function(account, callback) {
if (this.bailWhenOffline(callback)) {
return;
}
var self = this;
function formatCalendars(err, data) {
if (err) {
return callback(self._handleServiceError(err, {
account: account
}));
}
// format calendars if needed
if (data) {
for (var key in data) {
data[key] = self.formatRemoteCalendar(data[key]);
}
}
callback(err, data);
}
this.service.request(
'caldav',
'findCalendars',
account.toJSON(),
formatCalendars
);
},
_syncEvents: function(account, calendar, cached, callback) {
var startDate;
// calculate the first date we want to sync
if (!calendar.firstEventSyncDate) {
startDate = Calc.createDay(new Date());
// will be persisted if sync is successful (clone is required)
calendar.firstEventSyncDate = new Date(
startDate.valueOf()
);
} else {
startDate = new Date(calendar.firstEventSyncDate.valueOf());
}
// start date - the amount of days is the sync range
startDate.setDate(startDate.getDate() - this.daysToSyncInPast);
var options = {
startDate: startDate,
cached: cached
};
var stream = this.service.stream(
'caldav',
'streamEvents',
account.toJSON(),
calendar.remote,
options
);
var pull = new CaldavPullEvents(stream, {
account: account,
calendar: calendar
});
var calendarStore = core.storeFactory.get('Calendar');
var syncStart = new Date();
var self = this;
stream.request(function(err) {
if (err) {
return callback(
self._handleServiceError(err, {
account: account,
calendar: calendar
})
);
}
var trans = pull.commit(function(commitErr) {
if (commitErr) {
callback(err);
return;
}
callback(null);
});
/**
* Successfully synchronizing a calendar indicates we can remove this
* error.
*/
calendar.error = undefined;
calendar.lastEventSyncToken = calendar.remote.syncToken;
calendar.lastEventSyncDate = syncStart;
calendarStore.persist(calendar, trans);
});
return pull;
},
/**
* Builds list of event urls & sync tokens.
*
* @param {Calendar.Model.Calendar} calender model instance.
* @param {Function} callback node style [err, results].
*/
_cachedEventsFor: function(calendar, callback) {
var store = core.storeFactory.get('Event');
store.eventsForCalendar(calendar._id, function(err, results) {
if (err) {
callback(err);
return;
}
var list = Object.create(null);
var i = 0;
var len = results.length;
var item;
for (; i < len; i++) {
item = results[i];
list[item.remote.url] = {
syncToken: item.remote.syncToken,
id: item._id
};
}
callback(null, list);
});
},
/**
* Sync remote and local events for a calendar.
*/
syncEvents: function(account, calendar, callback) {
var self = this;
if (this.bailWhenOffline(callback)) {
return;
}
if (!calendar._id) {
throw new Error('calendar must be assigned an _id');
}
// Don't attempt to sync when provider cannot
// or we have matching tokens
if ((calendar.lastEventSyncToken &&
calendar.lastEventSyncToken === calendar.remote.syncToken)) {
return nextTick(callback);
}
this._cachedEventsFor(calendar, function(err, results) {
if (err) {
callback(err);
return;
}
self._syncEvents(
account,
calendar,
results,
callback
);
});
},
/**
* See abstract for contract details...
*
* Finds all ical components that have not been expanded
* beyond the given point and expands / persists them.
*
* @param {Date} maxDate maximum date to expand to.
* @param {Function} callback [err, didExpand].
*/
ensureRecurrencesExpanded: function(maxDate, callback) {
var self = this;
this.icalComponents.findRecurrencesBefore(maxDate,
function(err, results) {
if (err) {
callback(err);
return;
}
if (!results.length) {
callback(null, false);
return;
}
// CaldavPullRequest is based on a calendar/account combination
// so we must group all of the outstanding components into
// their calendars before we can begin expanding them.
var groups = Object.create(null);
results.forEach(function(comp) {
var calendarId = comp.calendarId;
if (!(calendarId in groups)) {
groups[calendarId] = [];
}
groups[calendarId].push(comp);
});
var pullGroups = [];
var pending = 0;
var options = {
maxDate: Calc.dateToTransport(maxDate)
};
function next(err, pull) {
pullGroups.push(pull);
if (!(--pending)) {
var trans = core.db.transaction(
['icalComponents', 'alarms', 'busytimes'],
'readwrite'
);
trans.oncomplete = function() {
callback(null, true);
};
trans.onerror = function(event) {
callback(event.result.error.name);
};
pullGroups.forEach(function(pull) {
pull.commit(trans);
});
}
}
for (var calendarId in groups) {
pending++;
self._expandComponents(
calendarId,
groups[calendarId],
options,
next
);
}
});
},
_expandComponents: function(calendarId, comps, options, callback) {
var calStore = core.storeFactory.get('Calendar');
calStore.ownersOf(calendarId, function(err, owners) {
if (err) {
return callback(err);
}
var calendar = owners.calendar;
var account = owners.account;
var stream = this.service.stream(
'caldav',
'expandComponents',
comps,
options
);
var pull = new CaldavPullEvents(
stream,
{
account: account,
calendar: calendar,
stores: [
'busytimes', 'alarms', 'icalComponents'
]
}
);
stream.request(function(err) {
if (err) {
callback(err);
return;
}
callback(null, pull);
});
}.bind(this));
},
createEvent: function(event, busytime, callback) {
if (typeof(busytime) === 'function') {
callback = busytime;
busytime = null;
}
if (this.bailWhenOffline(callback)) {
return;
}
this.events.ownersOf(event, fetchOwners);
var self = this;
var calendar;
var account;
function fetchOwners(err, owners) {
calendar = owners.calendar;
account = owners.account;
self.service.request(
'caldav',
'createEvent',
account,
calendar.remote,
event.remote,
handleRequest
);
}
function handleRequest(err, remote) {
if (err) {
return callback(self._handleServiceError(err, {
account: account,
calendar: calendar
}));
}
var event = {
_id: calendar._id + '-' + remote.id,
calendarId: calendar._id
};
var component = {
eventId: event._id,
ical: remote.icalComponent
};
delete remote.icalComponent;
event.remote = remote;
var create = mutations.create({
event: event,
icalComponent: component
});
create.commit(function(err) {
if (err) {
callback(err);
return;
}
callback(null, create.busytime, create.event);
});
}
},
updateEvent: function(event, busytime, callback) {
if (typeof(busytime) === 'function') {
callback = busytime;
busytime = null;
}
if (this.bailWhenOffline(callback)) {
return;
}
this.events.ownersOf(event, fetchOwners);
var self = this;
var calendar;
var account;
function fetchOwners(err, owners) {
calendar = owners.calendar;
account = owners.account;
self.icalComponents.get(
event._id, fetchComponent
);
}
function fetchComponent(err, ical) {
if (err) {
callback(err);
return;
}
var details = {
event: event.remote,
icalComponent: ical.ical
};
self.service.request(
'caldav',
'updateEvent',
account,
calendar.remote,
details,
handleUpdate
);
}
function handleUpdate(err, remote) {
if (err) {
callback(self._handleServiceError(err, {
account: account,
calendar: calendar
}));
return;
}
var component = {
eventId: event._id,
ical: remote.icalComponent
};
delete remote.icalComponent;
event.remote = remote;
var update = mutations.update({
event: event,
icalComponent: component
});
update.commit(function(err) {
if (err) {
callback(err);
return;
}
callback(null, update.busytime, update.event);
});
}
},
deleteEvent: function(event, busytime, callback) {
if (typeof(busytime) === 'function') {
callback = busytime;
busytime = null;
}
if (this.bailWhenOffline(callback)) {
return;
}
this.events.ownersOf(event, fetchOwners);
var calendar;
var account;
var self = this;
function fetchOwners(err, owners) {
calendar = owners.calendar;
account = owners.account;
self.service.request(
'caldav',
'deleteEvent',
account,
calendar.remote,
event.remote,
handleRequest
);
}
function handleRequest(err) {
if (err) {
callback(self._handleServiceError(err, {
account: account,
calendar: calendar
}));
return;
}
Local.prototype.deleteEvent.call(self, event, busytime, callback);
}
},
bailWhenOffline: function(callback) {
if (!this.offlineMessage && 'mozL10n' in window.navigator) {
this.offlineMessage = window.navigator.mozL10n.get('error-offline');
}
var ret = this.isOffline() && callback;
if (ret) {
var error = new Error();
error.name = 'offline';
error.message = this.offlineMessage;
callback(error);
}
return ret;
}
};
});

View file

@ -0,0 +1,391 @@
define(function(require, exports, module) {
'use strict';
var Calc = require('common/calc');
var core = require('core');
var debug = require('common/debug')('pull events');
var uuid = require('ext/uuid');
/**
* Event synchronization class for caldav provider.
*
* Options:
* - account: (Calendar.Models.Account) required
* - calendar: (Calendar.Models.Calendar) required
*
* Example:
*
* // instance of a service stream
* var stream;
*
* var pull = new Calendar.Provider.CaldavPullEvents(stream, {
* calendar: calendarModel,
* account: accountModel,
* });
*
* stream.request(function() {
* // stream is complete here the audit of
* // events can be made. They are flushed
* // to the cache where possible but not actually
* // persisted in the database.
*
* // assuming we are ready commit the changes
* pull.commit(function(err) {
* // all changes have been committed at this point.
* });
* });
*
* @param {Calendar.Responder} stream event emitter usually
* a service stream.
* @param {Object} options options for instance (see above).
*/
function PullEvents(stream, options) {
if (options.calendar) {
this.calendar = options.calendar;
} else {
throw new Error('.calendar option must be given');
}
if (options.account) {
this.account = options.account;
} else {
throw new Error('.account option must be given');
}
stream.on('event', this);
stream.on('component', this);
stream.on('occurrence', this);
stream.on('missingEvents', this);
this.icalQueue = [];
this.eventQueue = [];
this.busytimeQueue = [];
this.alarmQueue = [];
var storeFactory = core.storeFactory;
this._busytimeStore = storeFactory.get('Busytime');
// Catch account events to watch for mid-sync removal
this._accountStore = storeFactory.get('Account');
this._accountStore.on('remove', this._onRemoveAccount.bind(this));
this._aborted = false;
this._trans = null;
}
module.exports = PullEvents;
PullEvents.prototype = {
eventQueue: null,
busytimeQueue: null,
/**
* Get db id for busytime.
*
* @param {Object} busytime service sent busytime.
*/
busytimeIdFromRemote: function(busytime) {
var eventId = this.eventIdFromRemote(busytime, !busytime.isException);
return busytime.start.utc + '-' +
busytime.end.utc + '-' +
eventId;
},
/**
* Get db id for event.
*
* @param {Object} event service sent event or '.remote'
* property in db event.
*
* @param {Boolean} ignoreException when true will ignore
* recurrence exceptions.
*
* @return {String} database object id.
*/
eventIdFromRemote: function(event, ignoreException) {
var id = event.eventId || event.id;
if (!ignoreException && event.recurrenceId) {
id += '-' + event.recurrenceId.utc;
}
return this.calendar._id + '-' + id;
},
/**
* Format an incoming event.
*
* @param {Object} event service sent event.
*/
formatEvent: function(event) {
// get id or parent id we ignore the exception
// rules here so children (exceptions) can lookup
// their parents id.
var id = this.eventIdFromRemote(event, true);
var result = Object.create(null);
result.calendarId = this.calendar._id;
result.remote = event;
if (event.recurrenceId) {
result.parentId = id;
// don't ignore the exceptions
result._id = this.eventIdFromRemote(event);
} else {
result._id = id;
}
return result;
},
/**
* Formats and tags busytime sent from service.
*
* @param {Object} time service sent busytime.
*/
formatBusytime: function(time) {
var eventId = this.eventIdFromRemote(time, !time.isException);
var id = eventId + '-' + uuid.v4();
var calendarId = this.calendar._id;
time._id = id;
time.calendarId = calendarId;
time.eventId = eventId;
if (time.alarms) {
var i = 0;
var len = time.alarms.length;
var alarm;
for (; i < len; i++) {
alarm = time.alarms[i];
alarm.eventId = eventId;
alarm.busytimeId = id;
}
}
return this._busytimeStore.initRecord(time);
},
handleOccurrenceSync: function(item) {
var alarms;
if ('alarms' in item) {
alarms = item.alarms;
delete item.alarms;
if (alarms.length) {
var i = 0;
var len = alarms.length;
var now = Date.now();
for (; i < len; i++) {
var alarm = {
startDate: {},
eventId: item.eventId,
busytimeId: item._id
};
// Copy the start object
for (var j in item.start) {
alarm.startDate[j] = item.start[j];
}
alarm.startDate.utc += (alarms[i].trigger * 1000);
var alarmDate = Calc.dateFromTransport(item.end);
if (alarmDate.valueOf() < now) {
continue;
}
this.alarmQueue.push(alarm);
}
}
}
this.busytimeQueue.push(item);
},
handleComponentSync: function(component) {
component.eventId = this.eventIdFromRemote(component);
component.calendarId = this.calendar._id;
if (!component.lastRecurrenceId) {
delete component.lastRecurrenceId;
}
this.icalQueue.push(component);
},
handleEventSync: function(event) {
var exceptions = event.remote.exceptions;
delete event.remote.exceptions;
var id = event._id;
// clear any busytimes that could possibly be
// related to this event as we will be adding new
// ones as part of the sync.
this._busytimeStore.removeEvent(id);
this.eventQueue.push(event);
var component = event.remote.icalComponent;
delete event.remote.icalComponent;
// don't save components for exceptions.
// the parent has the ical data.
if (!event.remote.recurrenceId) {
this.icalQueue.push({
data: component,
eventId: event._id
});
}
if (exceptions) {
for (var i = 0; i < exceptions.length; i++) {
this.handleEventSync(this.formatEvent(exceptions[i]));
}
}
},
/**
* Account removal event handler. Aborts the rest of sync processing, if
* the account deleted is the subject of the current sync.
*
* @param {String} database object id.
*/
_onRemoveAccount: function(id) {
if (id === this.account._id) {
// This is our account, so abort the sync.
this.abort();
}
},
/**
* Abort the sync. After this, further events will be ignored and commit()
* will do nothing.
*/
abort: function() {
if (this._aborted) {
// Bail, if already aborted.
return;
}
// Flag that the sync should be aborted.
this._aborted = true;
if (this._trans) {
// Attempt to abort the in-progress commit transaction
this._trans.abort();
}
},
handleEvent: function(event) {
if (this._aborted) {
// Ignore all events, if the sync has been aborted.
return;
}
var data = event.data;
switch (event.type) {
case 'missingEvents':
this.removeList = data[0];
break;
case 'occurrence':
var occur = this.formatBusytime(data[0]);
this.handleOccurrenceSync(occur);
break;
case 'component':
this.handleComponentSync(data[0]);
break;
case 'event':
var e = this.formatEvent(data[0]);
this.handleEventSync(e);
break;
}
},
/**
* Commit all pending records.
*
*
* @param {IDBTransaction} [trans] optional transaction.
* @param {Function} callback fired when transaction completes.
*/
commit: function(trans, callback) {
var storeFactory = core.storeFactory;
var eventStore = storeFactory.get('Event');
var icalComponentStore = storeFactory.get('IcalComponent');
var busytimeStore = storeFactory.get('Busytime');
var alarmStore = storeFactory.get('Alarm');
if (typeof(trans) === 'function') {
callback = trans;
trans = core.db.transaction(
['calendars', 'events', 'busytimes', 'alarms', 'icalComponents'],
'readwrite'
);
}
if (this._aborted) {
// Commit nothing, if sync was aborted.
return callback && callback(null);
}
// Stash a reference to the transaction, in case we still need to abort.
this._trans = trans;
var self = this;
this.eventQueue.forEach(function(event) {
debug('add event', event);
eventStore.persist(event, trans);
});
this.icalQueue.forEach(function(ical) {
debug('add component', ical);
icalComponentStore.persist(ical, trans);
});
this.busytimeQueue.forEach(function(busy) {
debug('add busytime', busy);
busytimeStore.persist(busy, trans);
});
this.alarmQueue.forEach(function(alarm) {
debug('add alarm', alarm);
alarmStore.persist(alarm, trans);
});
if (this.removeList) {
this.removeList.forEach(function(id) {
eventStore.remove(id, trans);
});
}
function handleError(e) {
if (e && e.type !== 'abort') {
console.error('Error persisting sync results', e);
}
// if we have an event preventDefault so we don't trigger window.onerror
if (e && e.preventDefault) {
e.preventDefault();
}
self._trans = null;
callback && callback(e);
}
trans.addEventListener('error', handleError);
trans.addEventListener('abort', handleError);
trans.addEventListener('complete', function() {
self._trans = null;
callback && callback(null);
});
return trans;
}
};
});

View file

@ -0,0 +1,71 @@
define(function(require, exports, module) {
'use strict';
var Calc = require('common/calc');
/**
* This is a more crude version of what asuth does in email.
* Right now this class is here only for debugging sync issues.
* We need to add the settings so we can optionally turn this on.
*/
function EventLogger() {
this.events = Object.create(null);
this.occurs = Object.create(null);
this.richLog = {};
}
module.exports = EventLogger;
EventLogger.prototype = {
displayLog: function(id, string) {
if (!(id in this.richLog)) {
this.richLog[id] = [];
}
this.richLog[id].push(string);
},
addEvent: function(event) {
var id = event.id;
this.events[id] = event;
var log = this.formatEvent(event);
this.displayLog(id, log);
},
addBusytime: function(busy) {
var id = busy.eventId;
this.occurs[id] = busy;
this.displayLog(id, this.formatBusytime(busy));
},
formatEvent: function(event) {
var format = [
'add event: (' + event.id + ')',
'title:' + event.title,
'start:' + (Calc.dateFromTransport(event.start)).toString(),
'end:' + (Calc.dateFromTransport(event.end)).toString(),
'isException:' + event.isException
];
return format.join(' || ');
},
formatBusytime: function(busy) {
var event = this.events[busy.eventId];
var title = 'busytime for event: ' + busy.eventId;
if (event) {
title = 'busytime for event: ' + event.title;
}
var format = [
title,
'start:' + (Calc.dateFromTransport(busy.start)).toString(),
'end:' + (Calc.dateFromTransport(busy.end)).toString(),
'isException:' + busy.isException
];
return format.join(' || ');
}
};
});

View file

@ -0,0 +1,27 @@
define(function(require, exports) {
'use strict';
var debug = require('common/debug')('provider/factory');
var ctors = {
Caldav: require('./caldav'),
Local: require('./local')
};
var providers = exports.providers = Object.create(null);
exports.get = function(name) {
if (!providers[name]) {
try {
debug(`Initializing provider ${name}`);
providers[name] = new ctors[name]();
} catch (e) {
console.error('Error', e.name, e.message);
console.error('Failed to initialize provider', name, e.stack);
}
}
return providers[name];
};
});

View file

@ -0,0 +1,129 @@
define(function(require, exports, module) {
'use strict';
var Abstract = require('./abstract');
var core = require('core');
var localCalendarId = require('common/constants').localCalendarId;
var mutations = require('event_mutations');
var uuid = require('ext/uuid');
function Local() {
Abstract.apply(this, arguments);
var storeFactory = core.storeFactory;
this.events = storeFactory.get('Event');
this.busytimes = storeFactory.get('Busytime');
this.alarms = storeFactory.get('Alarm');
}
module.exports = Local;
/**
* Returns the details for the default calendars.
*/
Local.defaultCalendar = function() {
// XXX: Make async
//var l10nId = 'calendar-local';
var name;
/* TODO(gareth): Make l10n available in backend.
if ('mozL10n' in window.navigator) {
name = window.navigator.mozL10n.get(l10nId);
if (name === l10nId) {
name = null;
}
}
if (!name) {
name = 'Offline calendar';
}
*/
name = 'Offline calendar';
return {
// XXX localize this name somewhere
name: name,
id: localCalendarId,
color: Local.prototype.defaultColor
};
};
Local.prototype = {
__proto__: Abstract.prototype,
canExpandRecurringEvents: false,
getAccount: function(account, callback) {
callback(null, {});
},
findCalendars: function(account, callback) {
var list = {};
list[localCalendarId] = Local.defaultCalendar();
callback(null, list);
},
syncEvents: function(account, calendar, cb) {
cb(null);
},
/**
* @return {Calendar.EventMutations.Create} mutation object.
*/
createEvent: function(event, callback) {
// most providers come with their own built in
// id system when creating a local event we need to generate
// our own UUID.
if (!event.remote.id) {
// TOOD: uuid is provided by ext/uuid.js
// if/when platform supports a safe
// random number generator (values never conflict)
// we can use that instead of uuid.js
event.remote.id = uuid();
}
var create = mutations.create({ event: event });
create.commit(function(err) {
if (err) {
return callback(err);
}
callback(null, create.busytime, create.event);
});
return create;
},
deleteEvent: function(event, busytime, callback) {
if (typeof(busytime) === 'function') {
callback = busytime;
busytime = null;
}
var storeFactory = core.storeFactory;
storeFactory.get('Event').remove(event._id, callback);
},
/**
* @return {Calendar.EventMutations.Update} mutation object.
*/
updateEvent: function(event, busytime, callback) {
if (typeof(busytime) === 'function') {
callback = busytime;
busytime = null;
}
var update = mutations.update({ event: event });
update.commit(function(err) {
if (err) {
return callback(err);
}
callback(null, update.busytime, update.event);
});
return update;
}
};
});