VulpesOS/gaia/apps/calendar/js/controllers/recurring_events.js
OverSu b38dea0597 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.
2026-09-30 23:13:03 +02:00

248 lines
6.1 KiB
JavaScript
Executable file

define(function(require, exports, module) {
'use strict';
var Responder = require('common/responder');
var core = require('core');
var debug = require('common/debug')('controllers/recurring_events');
var nextTick = require('common/next_tick');
function RecurringEvents() {
this.accounts = core.storeFactory.get('Account');
Responder.call(this);
}
module.exports = RecurringEvents;
RecurringEvents.prototype = {
__proto__: Responder.prototype,
startEvent: 'expandStart',
completeEvent: 'expandComplete',
/**
* Adds N number of days to the window to expand
* events until. Its very important for this number
* to be greater then the maximum number of days displayed
* in the month view (or a view with more days) otherwise
* the view may be loaded without actually expanding all
* the visible days.
*
* @type Number
*/
paddingInDays: 85,
/**
* Amount of time (in MS) to wait between triggering
* the recurring event expansions.
*/
waitBeforeMove: 750,
/**
* We need to limit the number of tries on expansions
* otherwise its possible we never complete during error
* or long recurring event.
*/
maximumExpansions: 25,
/**
* private timeout (as in setTimeout id) use with waitBeforeMove.
*/
_moveTimeout: null,
/**
* True when queue is running...
*/
pending: false,
unobserve: function() {
core.timeController.removeEventListener(
'monthChange',
this
);
core.syncController.removeEventListener(
'syncComplete',
this
);
},
observe: function() {
var time = core.timeController;
// expand initial time this is necessary
// for cases where user has device off for long periods of time.
if (time.position) {
this.queueExpand(time.position);
}
// register observers
time.on('monthChange', this);
// we must re-expand after sync so events at least
// expand to the current position....
core.syncController.on('syncComplete', this);
},
handleEvent: function(event) {
switch (event.type) {
case 'syncComplete':
this.queueExpand(
core.timeController.position
);
break;
case 'monthChange':
if (this._moveTimeout !== null) {
clearTimeout(this._moveTimeout);
this._moveTimeout = null;
}
// trigger the event queue when we move
this._moveTimeout = setTimeout(
// data[0] is the new date.
this.queueExpand.bind(this, event.data[0]),
this.waitBeforeMove
);
break;
}
},
/**
* Attempts to expand provider until no events require expansion.
*
* @param {Date} expandDate expands up to this date.
* @param {Calendar.Provider.Abstract} provider instance.
* @param {Function} callback
* fired when maximumExpansions is hit or
* no more events require expansion.
*
*/
_expandProvider: function(expandDate, provider, callback) {
debug('Will attempt to expand provider until:', expandDate);
var tries = 0;
var max = this.maximumExpansions;
function attemptCompleteExpand() {
debug('Will try to complete expansion (tries = ' + tries + ')');
if (tries >= max) {
return callback(new Error(
'could not complete expansion after "' + tries + '"'
));
}
provider.ensureRecurrencesExpanded(expandDate, function(err, didExpand) {
if (err) {
return callback(err);
}
debug('Expansion attempt did expand:', didExpand);
if (!didExpand) {
// successfully expanded and no events need expansion
// for this date anymore...
callback();
} else {
tries++;
// attempt another expand without stack.
nextTick(attemptCompleteExpand);
}
});
}
attemptCompleteExpand();
},
/**
* Queues an expansion. If the given date is before
* any dates in the stack it will be discarded.
*/
queueExpand: function(expandTo) {
if (this.pending) {
if (!this._next) {
this._next = expandTo;
} else if (expandTo > this._next) {
this._next = expandTo;
}
// don't start the queue if pending...
return;
}
// either way we need to process an event
// so increment pending for running and non-running cases.
this.pending = true;
this.emit('expandStart');
var self = this;
function expandNext(date) {
self.expand(date, function() {
if (date === self._next) {
self._next = null;
}
var next = self._next;
// when the queue is empty emit expandComplete
if (!next) {
self.pending = false;
self.emit('expandComplete');
return;
}
expandNext(next);
});
}
expandNext(expandTo);
},
/**
* Ensures we have time converage until the given date.
* Additional time will be added to the date see .paddingInDays.
*
* @param {Date} expandTo date to expand to.
*/
expand: function(expandTo, callback) {
debug('expand', expandTo);
this.accounts.all((err, accounts) => {
if (err) {
return callback(err);
}
// add minimum padding...
var expandDate = new Date(expandTo.valueOf());
expandDate.setDate(expandDate.getDate() + this.paddingInDays);
var providers = this._getExpandableProviders(accounts);
var pending = providers.length;
if (!pending) {
return nextTick(callback);
}
providers.forEach(provider => {
this._expandProvider(expandDate, provider, () => {
if (--pending <= 0) {
callback();
}
});
});
});
},
_getExpandableProviders: function(accounts) {
var providers = [];
Object.keys(accounts).forEach(key => {
var account = accounts[key];
var provider = core.providerFactory.get(account.providerType);
if (provider &&
provider.canExpandRecurringEvents &&
providers.indexOf(provider) === -1) {
providers.push(provider);
}
});
return providers;
}
};
});