summaryrefslogtreecommitdiff
path: root/app/imap.js
blob: 4041089d56c832886f6781abf002f59ee6df8625 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
const { ipcMain } = require('electron');
const { ImapFlow } = require('imapflow');

ipcMain.on('imap:listTree:from', listTreeFrom);
ipcMain.on('imap:listTree:to', listTreeTo);
ipcMain.on('imap:migrate', migrate);

/**
 * Connect to server
 *
 * @param {object} options
 *
 * @returns {Promise<ImapFlow>}
 */
async function connect (options) {
  const client = new ImapFlow({
    host: options.server,
    port: options.port,
    auth: {
      user: options.username,
      pass: options.password,
    },
    tls: options.tls,
  });

  await client.connect();

  return client;
}

/**
 * Turn fetch response into array
 *
 * @param {string} range
 * @param {object} query
 * @param {object} options
 *
 * @returns {Promise<*[]>}
 */
ImapFlow.prototype.fetchArray = async function (range, query, options = {}) {
  const msgsGenerator = await this.fetch(range, query, options);
  const msgs = [];
  for await (const msg of msgsGenerator) {
    msgs.push(msg);
  }

  return msgs;
};

/**
 * @param {IpcMainEvent} event
 * @param {object} options
 */
async function listTreeFrom (event, options) {
  try {
    const client = await connect(options);

    event.reply('imap:listTree:from:reply', await client.listTree());
    await client.logout();
  } catch (e) {
    event.reply('imap:from:error', e);
  }
}

/**
 * @param {IpcMainEvent} event
 * @param {object} options
 */
async function listTreeTo (event, options) {
  try {
    const client = await connect(options);

    event.reply('imap:listTree:to:reply', await client.listTree());
    await client.logout();
  } catch (e) {
    event.reply('imap:to:error', e);
  }
}

/**
 * @param {IpcMainEvent} event
 * @param {object} from
 * @param {object} to
 */
async function migrate (event, { from, to }) {
  const fromClient = await connect(from);
  const toClient = await connect(to);

  event.reply('imap:migrate:progress', 'Getting folders');
  const fromFolders = (await fromClient.list()).filter((folder) => folder.subscribed);
  for (const fromFolder of fromFolders) {
    event.reply('imap:migrate:progress', `Working on folder "${fromFolder.path}"`);

    try {
      await toClient.mailboxCreate(fromFolder.path);
    } catch (e) {}

    const appendCommands = [];
    const fromMailbox = await fromClient.getMailboxLock(fromFolder.path);
    const toMailbox = await toClient.getMailboxLock(fromFolder.path);
    try {
      event.reply('imap:migrate:progress', '- Collecting messages from target for comparison');
      const toMsgs = await toClient.fetchArray('1:*', { flags: true, envelope: true, source: true });

      event.reply('imap:migrate:progress', '- Collecting messages from source');
      const fromMsgs = await fromClient.fetchArray('1:*', { flags: true, envelope: true, source: true });

      event.reply('imap:migrate:progress', '- Comparing messages');
      const msgs = fromMsgs.filter((fromMsg) => !toMsgs.some((toMsg) => Buffer.compare(toMsg.source, fromMsg.source) === 0));

      event.reply('imap:migrate:progress', `- Message status: Found ${fromMsgs.length} messages total and ${msgs.length} new messages to migrate`);

      await toClient.noop();
      for (const msg of msgs) {
        appendCommands.push(toClient.append(fromFolder.path, msg.source, Array.from(msg.flags), msg.envelope.date));
      }
    } finally {
      await Promise.all(appendCommands);
      event.reply('imap:migrate:progress', '- Done');
      fromMailbox.release();
      toMailbox.release();
    }
  }

  event.reply('imap:migrate:progress', 'Done');
}