Mailspring/sync/sync-worker.js

173 lines
4.7 KiB
JavaScript
Raw Normal View History

2016-06-19 18:02:32 +08:00
const Imap = require('imap');
2016-06-20 15:19:16 +08:00
const EventEmitter = require('events');
2016-06-19 18:02:32 +08:00
2016-06-20 15:19:16 +08:00
const RefreshMailboxesOperation = require('./imap/refresh-mailboxes-operation')
const DiscoverMessagesOperation = require('./imap/discover-messages-operation')
const ScanUIDsOperation = require('./imap/scan-uids-operation')
2016-06-19 18:02:32 +08:00
const Capabilities = {
Gmail: 'X-GM-EXT-1',
Quota: 'QUOTA',
UIDPlus: 'UIDPLUS',
Condstore: 'CONDSTORE',
Search: 'ESEARCH',
Sort: 'SORT',
}
2016-06-20 15:19:16 +08:00
class IMAPConnectionStateMachine extends EventEmitter {
constructor(db, settings) {
super();
this._db = db;
2016-06-19 18:02:32 +08:00
this._queue = [];
this._current = null;
this._capabilities = [];
this._imap = Promise.promisifyAll(new Imap(settings));
this._imap.once('ready', () => {
for (const key of Object.keys(Capabilities)) {
const val = Capabilities[key];
if (this._imap.serverSupports(val)) {
this._capabilities.push(val);
}
}
2016-06-20 15:19:16 +08:00
this.emit('ready');
2016-06-19 18:02:32 +08:00
});
2016-06-20 15:19:16 +08:00
2016-06-19 18:02:32 +08:00
this._imap.once('error', (err) => {
console.log(err);
});
2016-06-20 15:19:16 +08:00
2016-06-19 18:02:32 +08:00
this._imap.once('end', () => {
console.log('Connection ended');
});
2016-06-20 15:19:16 +08:00
this._imap.on('alert', (msg) => {
console.log(`IMAP SERVER SAYS: ${msg}`)
})
2016-06-19 18:02:32 +08:00
2016-06-20 15:19:16 +08:00
// Emitted when new mail arrives in the currently open mailbox.
// Fix https://github.com/mscdex/node-imap/issues/445
let lastMailEventBox = null;
this._imap.on('mail', () => {
if (lastMailEventBox === this._imap._box.name) {
this.emit('mail');
2016-06-19 18:02:32 +08:00
}
2016-06-20 15:19:16 +08:00
lastMailEventBox = this._imap._box.name
2016-06-19 18:02:32 +08:00
});
2016-06-20 15:19:16 +08:00
// Emitted if the UID validity value for the currently open mailbox
// changes during the current session.
this._imap.on('uidvalidity', () => this.emit('uidvalidity'))
2016-06-19 18:02:32 +08:00
2016-06-20 15:19:16 +08:00
// Emitted when message metadata (e.g. flags) changes externally.
this._imap.on('update', () => this.emit('update'))
2016-06-19 18:02:32 +08:00
2016-06-20 15:19:16 +08:00
this._imap.connect();
2016-06-19 18:02:32 +08:00
}
2016-06-20 15:19:16 +08:00
getIMAP() {
return this._imap;
}
2016-06-19 18:02:32 +08:00
2016-06-20 15:19:16 +08:00
runOperation(operation) {
return new Promise((resolve, reject) => {
this._queue.push({operation, resolve, reject});
if (this._imap.state === 'authenticated' && !this._current) {
this.processNextOperation();
}
2016-06-19 18:02:32 +08:00
});
}
2016-06-20 15:19:16 +08:00
processNextOperation() {
if (this._current) { return; }
2016-06-19 18:02:32 +08:00
2016-06-20 15:19:16 +08:00
this._current = this._queue.shift();
2016-06-19 18:02:32 +08:00
2016-06-20 15:19:16 +08:00
if (!this._current) {
this.emit('queue-empty');
return;
}
2016-06-19 18:02:32 +08:00
2016-06-20 15:19:16 +08:00
const {operation, resolve, reject} = this._current;
2016-06-19 18:02:32 +08:00
2016-06-20 15:19:16 +08:00
console.log(`Starting task ${operation.description()}`)
const result = operation.run(this._db, this._imap);
if (result instanceof Promise === false) {
throw new Error(`Expected ${operation.constructor.name} to return promise.`);
}
result.catch((err) => {
this._current = null;
console.error(err);
reject();
2016-06-19 18:02:32 +08:00
})
.then(() => {
2016-06-20 15:19:16 +08:00
this._current = null;
console.log(`Finished task ${operation.description()}`)
resolve();
})
.finally(() => {
this.processNextOperation();
2016-06-19 18:02:32 +08:00
});
}
}
2016-06-20 15:19:16 +08:00
class SyncWorker {
constructor(account, db) {
const main = new IMAPConnectionStateMachine(db, {
user: 'inboxapptest1@fastmail.fm',
password: 'trar2e',
host: 'mail.messagingengine.com',
port: 993,
tls: true,
2016-06-19 18:02:32 +08:00
});
2016-06-20 15:19:16 +08:00
// Todo: SyncWorker should decide what operations to queue and what params
// to pass them, and how often, based on SyncPolicy model (TBD).
2016-06-19 18:02:32 +08:00
2016-06-20 15:19:16 +08:00
main.on('ready', () => {
main.runOperation(new RefreshMailboxesOperation())
.then(() =>
this._db.Category.find({where: {role: 'inbox'}})
).then((inboxCategory) => {
if (!inboxCategory) {
throw new Error("Unable to find an inbox category.")
}
main.on('mail', () => {
main.runOperation(new DiscoverMessagesOperation(inboxCategory));
})
main.on('update', () => {
main.runOperation(new ScanUIDsOperation(inboxCategory));
})
main.on('queue-empty', () => {
main.getIMAP().openBoxAsync(inboxCategory.name, true).then(() => {
console.log("Idling on inbox category");
});
2016-06-19 18:02:32 +08:00
});
2016-06-20 15:19:16 +08:00
setInterval(() => this.syncAllMailboxes(), 120 * 1000);
this.syncAllMailboxes();
2016-06-19 18:02:32 +08:00
});
});
2016-06-20 15:19:16 +08:00
this._db = db;
this._main = main;
}
2016-06-19 18:02:32 +08:00
2016-06-20 15:19:16 +08:00
syncAllMailboxes() {
const {Category} = this._db;
Category.findAll().then((categories) => {
const priority = ['inbox', 'drafts', 'sent'];
const sorted = categories.sort((a, b) => {
return priority.indexOf(b.role) - priority.indexOf(a.role);
})
for (const cat of sorted) {
this._main.runOperation(new DiscoverMessagesOperation(cat));
this._main.runOperation(new ScanUIDsOperation(cat));
}
});
2016-06-19 18:02:32 +08:00
}
}
module.exports = SyncWorker;