summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/imap.js66
-rw-r--r--app/src/App.vue16
-rw-r--r--app/src/components/Panel.vue40
-rw-r--r--app/src/pages/Home.vue66
-rw-r--r--app/src/store.js2
5 files changed, 154 insertions, 36 deletions
diff --git a/app/imap.js b/app/imap.js
index 181d9cf..4041089 100644
--- a/app/imap.js
+++ b/app/imap.js
@@ -5,6 +5,13 @@ 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,
@@ -13,6 +20,7 @@ async function connect (options) {
user: options.username,
pass: options.password,
},
+ tls: options.tls,
});
await client.connect();
@@ -20,6 +28,15 @@ async function connect (options) {
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 = [];
@@ -30,45 +47,80 @@ ImapFlow.prototype.fetchArray = async function (range, query, options = {}) {
return msgs;
};
+/**
+ * @param {IpcMainEvent} event
+ * @param {object} options
+ */
async function listTreeFrom (event, options) {
- const client = await connect(options);
+ try {
+ const client = await connect(options);
- event.reply('imap:listTree:from:reply', await client.listTree());
- await client.logout();
+ 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) {
- const client = await connect(options);
+ try {
+ const client = await connect(options);
- event.reply('imap:listTree:to:reply', await client.listTree());
- await client.logout();
+ 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) {
- toClient.append(fromFolder.path, msg.source, Array.from(msg.flags), msg.envelope.date);
+ 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');
}
diff --git a/app/src/App.vue b/app/src/App.vue
index 98240ae..87365a2 100644
--- a/app/src/App.vue
+++ b/app/src/App.vue
@@ -1,3 +1,19 @@
<template>
<router-view />
</template>
+
+<script>
+import 'foundation-sites/dist/js/foundation.cjs';
+
+export default {};
+</script>
+
+<style lang="scss">
+@import '~foundation-sites/dist/css/foundation.min.css';
+
+body {
+ font-size: 1rem;
+ font-family: Helvetica, sans-serif;
+ padding: 1rem;
+}
+</style>
diff --git a/app/src/components/Panel.vue b/app/src/components/Panel.vue
index 3ae4000..527a8a9 100644
--- a/app/src/components/Panel.vue
+++ b/app/src/components/Panel.vue
@@ -1,21 +1,35 @@
<template>
- <div class="panel">
- <label>
- Server <input type="text" :value="modelValue.server" @input="emit('server', $event.target.value)">
- </label>
- <label>
- Port <input type="text" :value="modelValue.port" @input="emit('port', $event.target.value)">
- </label>
+ <form data-abide @submit.prevent="$emit('connect')">
+ <div class="grid-x">
+ <div class="cell">
+ <label>
+ Server <input type="text" :value="modelValue.server" required @input="emit('server', $event.target.value)">
+ </label>
+ </div>
+ </div>
+ <div class="grid-x">
+ <div class="cell">
+ <label>
+ Port <input type="text" :value="modelValue.port" required @input="emit('port', $event.target.value)">
+ </label>
+ </div>
+ </div>
<label>
Username <input type="text" :value="modelValue.username" @input="emit('username', $event.target.value)">
</label>
<label>
- Passwort <input type="text" :value="modelValue.password" @input="emit('password', $event.target.value)">
+ Passwort <input type="password" :value="modelValue.password" @input="emit('password', $event.target.value)">
</label>
- <button @click="$emit('connect')">
+ <div class="grid-x">
+ <div class="cell">
+ <input :id="`tls-${uid}`" type="checkbox" :checked="modelValue.tls" @input="emit('tls', $event.target.checked)">
+ <label :for="`tls-${uid}`">TLS enabled</label>
+ </div>
+ </div>
+ <button class="button" type="submit">
Connect
</button>
- </div>
+ </form>
</template>
<script>
@@ -27,6 +41,12 @@ export default {
],
emits: ['connect'],
+
+ data () {
+ return {
+ uid: crypto.getRandomValues(new Uint8Array(1)),
+ };
+ },
};
</script>
diff --git a/app/src/pages/Home.vue b/app/src/pages/Home.vue
index bfe92bf..8385a46 100644
--- a/app/src/pages/Home.vue
+++ b/app/src/pages/Home.vue
@@ -1,22 +1,38 @@
<template>
<div class="wrap">
<div class="panels">
- <Panel v-model="from" @connect="connectFrom" />
- <Panel v-model="to" @connect="connectTo" />
+ <div>
+ <h1>FROM</h1>
+ {{ from.error }}
+ <Panel v-model="from" @connect="connectFrom" />
+ <ul v-if="from.folders">
+ <Folders :folder="from.folders" />
+ </ul>
+ </div>
+
+ <div>
+ <h1>TO</h1>
+ {{ to.error }}
+ <Panel v-model="to" @connect="connectTo" />
+ <ul v-if="to.folders">
+ <Folders :folder="to.folders" />
+ </ul>
+ </div>
</div>
- <button @click="migrate">
+
+ <button class="button" @click="migrate">
Migrate!
</button>
- <h1>FROM</h1>
- <ul v-if="from.folders">
- <Folders :folder="from.folders" />
- </ul>
-
- <h1>TO</h1>
- <ul v-if="to.folders">
- <Folders :folder="to.folders" />
- </ul>
+ <div class="progress-screen">
+ <b>{{ progress }}</b>
+ <br>
+ <div class="progress-screen__log">
+ <div v-for="(msg, idx) in log" :key="idx">
+ {{ msg }}
+ </div>
+ </div>
+ </div>
</div>
</template>
@@ -31,7 +47,10 @@ export default {
},
data () {
- return {};
+ return {
+ progress: '',
+ log: [],
+ };
},
computed: {
@@ -61,6 +80,18 @@ export default {
this.$electron.ipcRenderer.on('imap:listTree:to:reply', (event, folders) => {
this.to.folders = folders;
});
+
+ this.$electron.ipcRenderer.on('imap:from:error', (event, error) => {
+ this.from.error = error;
+ });
+ this.$electron.ipcRenderer.on('imap:to:error', (event, error) => {
+ this.to.error = error;
+ });
+
+ this.$electron.ipcRenderer.on('imap:migrate:progress', (event, progress) => {
+ this.progress = progress;
+ this.log.push(progress);
+ });
},
methods: {
@@ -73,6 +104,9 @@ export default {
},
async migrate () {
+ this.progress = '';
+ this.log = [];
+
this.$electron.ipcRenderer.send('imap:migrate', {
from: JSON.parse(JSON.stringify(this.from)),
to: JSON.parse(JSON.stringify(this.to)),
@@ -83,12 +117,6 @@ export default {
</script>
<style lang="scss">
-body {
- margin: 0;
- font-size: 1rem;
- font-family: Helvetica, sans-serif;
-}
-
.panels {
display: flex;
justify-content: space-between;
diff --git a/app/src/store.js b/app/src/store.js
index 818689b..294380c 100644
--- a/app/src/store.js
+++ b/app/src/store.js
@@ -8,12 +8,14 @@ export default createStore({
port: 3143,
username: 'from@example.org',
password: 'password',
+ tls: false,
},
to: {
server: 'localhost',
port: 31432,
username: 'to@example.org',
password: 'password',
+ tls: false,
},
};
},