Mailspring/packages/nylas-sync/sync-worker.js

215 lines
6.2 KiB
JavaScript
Raw Normal View History

const {
Provider,
SchedulerUtils,
IMAPConnection,
PubsubConnector,
DatabaseConnector,
MessageTypes,
} = require('nylas-core');
2016-06-24 02:20:47 +08:00
const FetchCategoryList = require('./imap/fetch-category-list')
const FetchMessagesInCategory = require('./imap/fetch-messages-in-category')
2016-06-24 06:46:52 +08:00
const SyncbackTaskFactory = require('./syncback-task-factory')
2016-06-19 18:02:32 +08:00
2016-06-28 01:27:38 +08:00
2016-06-20 15:19:16 +08:00
class SyncWorker {
constructor(account, db) {
2016-06-21 08:33:23 +08:00
this._db = db;
this._conn = null;
this._account = account;
this._lastSyncTime = null;
2016-06-19 18:02:32 +08:00
2016-06-21 08:33:23 +08:00
this._syncTimer = null;
this._expirationTimer = null;
this._destroyed = false;
2016-06-19 18:02:32 +08:00
2016-06-21 08:33:23 +08:00
this.syncNow();
this._onMessage = this._onMessage.bind(this)
this._listener = PubsubConnector.observe(account.id).subscribe(this._onMessage)
}
cleanup() {
this._destroyed = true;
this._listener.dispose();
this.closeConnection()
}
closeConnection() {
this._conn.end();
this._conn = null
2016-06-21 08:33:23 +08:00
}
2016-06-19 18:02:32 +08:00
_onMessage(msg = {}) {
switch(msg.type) {
case MessageTypes.ACCOUNT_UPDATED:
this._onAccountUpdated(); break;
case MessageTypes.SYNCBACK_REQUESTED:
this.syncNow(); break;
default:
throw new Error(`Invalid message: ${JSON.stringify(msg)}`)
}
}
_onAccountUpdated() {
console.log("SyncWorker: Detected change to account. Reloading and syncing now.")
DatabaseConnector.forShared().then(({Account}) => {
Account.find({where: {id: this._account.id}}).then((account) => {
this._account = account;
this.syncNow();
})
});
2016-06-21 08:33:23 +08:00
}
onSyncDidComplete() {
const {afterSync} = this._account.syncPolicy;
if (afterSync === 'idle') {
return this.getInboxCategory()
2016-06-28 01:27:38 +08:00
.then((inboxCategory) => this._conn.openBox(inboxCategory.name))
.then(() => console.log('SyncWorker: - Idling on inbox category'))
.catch((error) => {
this.closeConnection()
console.error('SyncWorker: - Unhandled error while attempting to idle on Inbox after sync: ', error)
})
2016-06-21 08:33:23 +08:00
} else if (afterSync === 'close') {
console.log('SyncWorker: - Closing connection');
} else {
console.warn(`SyncWorker: - Unknown afterSync behavior: ${afterSync}. Closing connection`)
2016-06-21 08:33:23 +08:00
}
this.closeConnection()
return Promise.resolve()
2016-06-21 08:33:23 +08:00
}
onConnectionIdleUpdate() {
this.syncNow();
2016-06-21 08:33:23 +08:00
}
2016-06-19 18:02:32 +08:00
2016-06-21 08:33:23 +08:00
getInboxCategory() {
return this._db.Category.find({where: {role: 'inbox'}})
}
ensureConnection() {
if (this._conn) {
return this._conn.connect();
}
const settings = this._account.connectionSettings;
const credentials = this._account.decryptedCredentials();
if (!settings || !settings.imap_host) {
return Promise.reject(new NylasError("ensureConnection: There are no IMAP connection settings for this account."))
}
if (!credentials) {
return Promise.reject(new NylasError("ensureConnection: There are no IMAP connection credentials for this account."))
}
2016-06-21 08:33:23 +08:00
const conn = new IMAPConnection(this._db, Object.assign({}, settings, credentials));
conn.on('mail', () => {
this.onConnectionIdleUpdate();
})
conn.on('update', () => {
this.onConnectionIdleUpdate();
})
conn.on('queue-empty', () => {
});
this._conn = conn;
return this._conn.connect();
2016-06-21 08:33:23 +08:00
}
2016-06-24 02:20:47 +08:00
fetchCategoryList() {
return this._conn.runOperation(new FetchCategoryList())
2016-06-21 08:33:23 +08:00
}
2016-06-24 04:15:30 +08:00
syncbackMessageActions() {
2016-06-24 06:46:52 +08:00
const {SyncbackRequest, accountId, Account} = this._db;
2016-06-29 05:07:54 +08:00
return this._db.Account.find({where: {id: accountId}}).then((account) => {
2016-06-24 06:46:52 +08:00
return Promise.each(SyncbackRequest.findAll().then((reqs = []) =>
reqs.map((request) => {
const task = SyncbackTaskFactory.create(account, request);
2016-06-29 05:07:54 +08:00
if (!task) return Promise.reject("No Task")
2016-06-24 06:46:52 +08:00
return this._conn.runOperation(task)
})
));
});
2016-06-24 04:15:30 +08:00
}
2016-06-28 01:27:38 +08:00
syncAllCategories() {
2016-06-20 15:19:16 +08:00
const {Category} = this._db;
const {folderSyncOptions} = this._account.syncPolicy;
2016-06-21 08:33:23 +08:00
return Category.findAll().then((categories) => {
const priority = ['inbox', 'all', 'drafts', 'sent', 'spam', 'trash'].reverse();
let categoriesToSync = categories.sort((a, b) =>
(priority.indexOf(a.role) - priority.indexOf(b.role)) * -1
2016-06-21 08:33:23 +08:00
)
if (this._account.provider === Provider.Gmail) {
categoriesToSync = categoriesToSync.filter(cat =>
['[Gmail]/All Mail', '[Gmail]/Trash', '[Gmail]/Spam'].includes(cat.name)
)
if (categoriesToSync.length !== 3) {
throw new Error(`Account is missing a core Gmail folder: ${categoriesToSync.join(',')}`)
}
}
return Promise.all(categoriesToSync.map((cat) =>
2016-06-24 02:20:47 +08:00
this._conn.runOperation(new FetchMessagesInCategory(cat, folderSyncOptions))
))
2016-06-20 15:19:16 +08:00
});
2016-06-19 18:02:32 +08:00
}
2016-06-21 08:33:23 +08:00
performSync() {
return this.fetchCategoryList()
.then(() => this.syncbackMessageActions())
.then(() => this.syncAllCategories())
}
2016-06-21 08:33:23 +08:00
syncNow() {
clearTimeout(this._syncTimer);
if (this._account.errored()) {
console.log(`SyncWorker: Account ${this._account.emailAddress} is in error state - Skipping sync`)
return
}
2016-06-24 02:20:47 +08:00
this.ensureConnection()
.then(() => this.performSync())
.then(() => this.onSyncDidComplete())
.catch((error) => this.onSyncError(error))
2016-06-24 02:20:47 +08:00
.finally(() => {
this._lastSyncTime = Date.now()
this.scheduleNextSync()
})
}
onSyncError(error) {
console.error(`SyncWorker: Error while syncing account ${this._account.emailAddress} `, error)
this.closeConnection()
if (error.source === 'socket') {
// Continue to retry if it was a network error
return Promise.resolve()
}
this._account.syncError = error
return this._account.save()
2016-06-21 08:33:23 +08:00
}
scheduleNextSync() {
if (this._account.errored()) { return }
SchedulerUtils.checkIfAccountIsActive(this._account.id).then((active) => {
const {intervals} = this._account.syncPolicy;
const interval = active ? intervals.active : intervals.inactive;
if (interval) {
const target = this._lastSyncTime + interval;
console.log(`SyncWorker: Account ${active ? 'active' : 'inactive'}. Next sync scheduled for ${new Date(target).toLocaleString()}`);
this._syncTimer = setTimeout(() => {
this.syncNow();
}, target - Date.now());
}
});
2016-06-21 08:33:23 +08:00
}
2016-06-19 18:02:32 +08:00
}
module.exports = SyncWorker;