VulpesOS/gaia/apps/calendar/js/controllers/periodic_sync.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

259 lines
6.6 KiB
JavaScript
Executable file

/**
* @fileoverview Period sync controller manages
*
* 1. Seeding first sync alarm when app starts.
* 2. Syncing when a sync alarm fires and issuing a new sync alarm.
* 3. Invalidating any existing sync alarms and issuing a new one
* when the sync interval changes.
*/
define(function(require, exports) {
'use strict';
var Responder = require('common/responder');
var core = require('core');
var debug = require('common/debug')('controllers/periodic_sync');
var messageHandler = require('message_handler');
/**
* Cached alarm previously sent to alarms db.
* @type {Object}
*/
var syncAlarm;
/**
* Cached sync value (every x minutes) from scheduling.
* @type {Number}
*/
var prevSyncFrequency;
/**
* Most recently set sync interval (every x minutes).
* @type {Number}
*/
var syncFrequency;
/**
* Cached promise representing pending sync operation.
* @type {Promise}
*/
var syncing;
/**
* Cached promise representing pending schedule operation.
* @type {Promise}
*/
var scheduling;
var events = new Responder();
exports.events = events;
var accounts;
var settings;
exports.observe = function() {
debug('Will start periodic sync controller...');
var storeFactory = core.storeFactory;
accounts = storeFactory.get('Account');
settings = storeFactory.get('Setting');
return Promise.all([
settings.getValue('syncAlarm'),
settings.getValue('syncFrequency')
])
.then(values => {
[syncAlarm, syncFrequency] = values;
// Trigger whenever there is a change to the accounts collection
// since we need to re-evaluate whether periodic sync is still necessary.
accounts.on('persist', onAccountsChange);
accounts.on('remove', onAccountsChange);
// Listen to the settings collection for a change to sync frequency so that
// we can update any alarms we've sent to the alarms api accordingly.
debug('Will listen for syncFrequencyChange...');
settings.on('syncFrequencyChange', exports);
// Listen for sync event from alarms api.
messageHandler.responder.on('sync', exports);
return scheduleSync();
});
};
exports.unobserve = function() {
syncAlarm = null;
prevSyncFrequency = null;
syncFrequency = null;
syncing = null;
scheduling = null;
accounts.off('persist', onAccountsChange);
accounts.off('remove', onAccountsChange);
settings.off('syncFrequencyChange', exports);
messageHandler.responder.off('sync', exports);
};
exports.handleEvent = function(event) {
switch (event.type) {
case 'sync':
// Gets triggered by mozSetMessageHandler alarm event.
return onSync();
case 'syncFrequencyChange':
// Gets triggered by settings db change to sync frequency.
debug('Received syncFrequencyChange!');
return onSyncFrequencyChange(event.data[0]);
}
};
/**
* 1. Wait until we're done with any previous work.
* 2. Sync.
* 3. Schedule the next periodic sync.
*/
function onSync() {
return sync().then(scheduleSync);
}
/**
* 1. Wait until we're done with any previous work.
* 2. Schedule a periodic sync at the new interval.
*/
function onSyncFrequencyChange(value) {
debug('Sync frequency changed to', value);
syncFrequency = value;
return maybeScheduleSync();
}
/**
* No syncable accounts => revoke any previously scheduled sync alarms.
* Syncable accounts and no previously scheduled sync => schedule new sync.
*/
function onAccountsChange() {
debug('Looking up syncable accounts...');
return accounts.syncableAccounts().then(syncable => {
if (!syncable || !syncable.length) {
debug('There are no syncable accounts!');
revokePreviousAlarm();
events.emit('pause');
return;
}
debug('There are', syncable.length, 'syncable accounts');
if (!syncAlarmIssued()) {
debug('The first syncable account was just added.');
return scheduleSync();
}
});
}
function sync() {
if (!syncing) {
syncing = new Promise((resolve, reject) => {
debug('Will request cpu and wifi wake locks...');
var cpuLock = navigator.requestWakeLock('cpu');
var wifiLock = navigator.requestWakeLock('wifi');
debug('Will start periodic sync...');
core.syncController.all(() => {
debug('Sync complete! Will release cpu and wifi wake locks...');
cpuLock.unlock();
wifiLock.unlock();
events.emit('sync');
syncing = null;
resolve();
});
});
}
return syncing;
}
function scheduleSync() {
if (scheduling) {
return scheduling;
}
scheduling = accounts.syncableAccounts()
.then(syncable => {
if (!syncable || !syncable.length) {
debug('There seem to be no syncable accounts, will defer scheduling...');
return Promise.resolve();
}
debug('Will schedule periodic sync in:', syncFrequency);
// Cache the sync interval which we're sending to the alarms api.
prevSyncFrequency = syncFrequency;
revokePreviousAlarm();
return issueSyncAlarm().then(cacheSyncAlarm).then(maybeScheduleSync);
})
.then(() => {
events.emit('schedule');
scheduling = null;
})
.catch(error => {
debug('Error scheduling sync:', error);
console.error(error.toString());
scheduling = null;
});
return scheduling;
}
// TODO: When navigator.mozAlarms.remove (one day) returns a DOMRequest,
// we should make this async...
function revokePreviousAlarm() {
if (!syncAlarmIssued()) {
debug('No sync alarms issued, nothing to revoke...');
return;
}
debug('Will revoke alarm', syncAlarm.alarmId);
var alarms = navigator.mozAlarms;
alarms.remove(syncAlarm.alarmId);
}
function issueSyncAlarm() {
if (!prevSyncFrequency) {
debug('Periodic sync disabled!');
return Promise.resolve({ alarmId: null, start: null, end: null });
}
var start = new Date();
var end = new Date(
start.getTime() +
prevSyncFrequency * 60 * 1000 // minutes to ms
);
var alarms = navigator.mozAlarms;
var request = alarms.add(end, 'ignoreTimezone', { type: 'sync' });
return new Promise((resolve, reject) => {
request.onsuccess = function() {
resolve({ alarmId: this.result, start: start, end: end });
};
request.onerror = function() {
reject(this.error);
};
});
}
function cacheSyncAlarm(alarm) {
debug('Will save alarm:', alarm);
syncAlarm = alarm;
return settings.set('syncAlarm', syncAlarm);
}
function maybeScheduleSync() {
if (syncFrequency === prevSyncFrequency) {
// Nothing to do!
return Promise.resolve();
}
if (scheduling) {
return scheduling.then(scheduleSync);
}
return scheduleSync();
}
function syncAlarmIssued() {
return syncAlarm && !!syncAlarm.alarmId;
}
});