summaryrefslogtreecommitdiff
path: root/app/imap.js
blob: d8caa0ef91c4bcaa87fb0be2126c8e33b059c44c (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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
const { ipcMain } = require('electron');
const { ImapFlow } = require('imapflow');

ipcMain.on('imap:connect', apiConnect);
ipcMain.on('imap:listTree:from', apiListTreeFrom);
ipcMain.on('imap:listTree:to', apiListTreeTo);
ipcMain.on('imap:migrate', apiMigrate);

/**
 * 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 apiConnect (event, options) {
  try {
    const client = await connect(options);

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

/**
 * @param {IpcMainEvent} event
 * @param {object} options
 */
async function apiListTreeFrom (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 apiListTreeTo (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 apiMigrate (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', `- Found ${fromMsgs.length} messages total and ${msgs.length} new messages to migrate`);

      if (msgs.length > 0) {
        event.reply('imap:migrate:progress', '- Starting migration');

        await toClient.noop();
        for (const msg of msgs) {
          appendCommands.push(toClient.append(fromFolder.path, msg.source, Array.from(msg.flags), msg.envelope.date));
        }

        await Promise.all(appendCommands);
        event.reply('imap:migrate:progress', '- Done');
      }

      fromMailbox.release();
      toMailbox.release();
    } catch (e) {
      event.reply('imap:migrate:error', e);
    }
  }

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