mirror of
https://github.com/nodemailer/wildduck.git
synced 2024-12-29 03:36:06 +08:00
208 lines
6.4 KiB
JavaScript
208 lines
6.4 KiB
JavaScript
'use strict';
|
|
|
|
const log = require('npmlog');
|
|
const db = require('../db');
|
|
const util = require('util');
|
|
const mailboxTranslations = require('../translations');
|
|
|
|
function timeout(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
async function restore(taskData, options) {
|
|
const messageHandler = options.messageHandler;
|
|
const mailboxHandler = options.mailboxHandler;
|
|
const putMessage = util.promisify(messageHandler.put.bind(messageHandler));
|
|
|
|
const createMailbox = util.promisify((...args) => {
|
|
let callback = args.pop();
|
|
mailboxHandler.create(...args, (err, status, id) => {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
return callback(null, { status, id });
|
|
});
|
|
});
|
|
|
|
let targetMailbox;
|
|
|
|
const ensuretargetMailbox = async (user, language) => {
|
|
if (targetMailbox) {
|
|
return targetMailbox;
|
|
}
|
|
|
|
let d = new Date();
|
|
let year = d.getUTCFullYear();
|
|
let month = (d.getUTCMonth() + 1).toString();
|
|
if (month.length < 2) {
|
|
month = '0' + month;
|
|
}
|
|
let day = d.getUTCDate().toString();
|
|
if (day.length < 2) {
|
|
day = '0' + day;
|
|
}
|
|
|
|
let lcode = (language || '')
|
|
.toLowerCase()
|
|
.split('_')
|
|
.shift();
|
|
|
|
let translation = lcode && mailboxTranslations.hasOwnProperty(lcode) ? mailboxTranslations[lcode] : mailboxTranslations.en;
|
|
let mailboxName = translation.Restored || mailboxTranslations.en.Restored;
|
|
let mailboxPath = `${mailboxName} (${year}-${month}-${day})`;
|
|
|
|
let mailboxData = await db.database.collection('mailboxes').findOne({
|
|
user,
|
|
path: mailboxPath
|
|
});
|
|
|
|
if (mailboxData) {
|
|
targetMailbox = mailboxData._id;
|
|
return targetMailbox;
|
|
}
|
|
|
|
try {
|
|
let { id } = await createMailbox(user, mailboxPath, {
|
|
subscribed: true
|
|
});
|
|
|
|
if (id) {
|
|
targetMailbox = id;
|
|
return targetMailbox;
|
|
}
|
|
} catch (err) {
|
|
//ignore
|
|
}
|
|
|
|
// was not able to create recover mailbox, fallback to INBOX
|
|
mailboxData = await db.database.collection('mailboxes').findOne({
|
|
user,
|
|
path: 'INBOX'
|
|
});
|
|
|
|
if (mailboxData) {
|
|
targetMailbox = mailboxData._id;
|
|
return targetMailbox;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
let userData = await db.users.collection('users').findOne({ _id: taskData.user });
|
|
if (!userData) {
|
|
// no such user anymore
|
|
log.error('Tasks', 'task=restore id=%s user=%s error=%s', taskData._id, taskData.user, 'No such user');
|
|
return true;
|
|
}
|
|
|
|
let cursor = db.database.collection('archived').find({
|
|
user: taskData.user,
|
|
archived: {
|
|
$gte: taskData.start,
|
|
$lte: taskData.end
|
|
}
|
|
});
|
|
|
|
let messageData;
|
|
while ((messageData = await cursor.next())) {
|
|
// use special recovery mailbox
|
|
|
|
const archived = messageData._id;
|
|
|
|
messageData.mailbox = await ensuretargetMailbox(userData._id, userData.language);
|
|
if (!messageData.mailbox) {
|
|
// failed to ensure mailbox
|
|
log.info('Tasks', 'task=restore id=%s user=%s message=%s action=failed target=%s', taskData._id, taskData.user, archived, messageData.mailbox);
|
|
continue;
|
|
}
|
|
|
|
delete messageData.archived;
|
|
delete messageData.exp;
|
|
delete messageData.rdate;
|
|
|
|
// mark message as not deleted
|
|
messageData.flags = (messageData.flags || []).filter(flag => flag !== '\\Deleted');
|
|
messageData.undeleted = true;
|
|
|
|
log.info('Tasks', 'task=restore id=%s user=%s message=%s action=restoring target=%s', taskData._id, taskData.user, archived, messageData.mailbox);
|
|
|
|
let messageResponse;
|
|
try {
|
|
messageResponse = await putMessage(messageData);
|
|
} catch (err) {
|
|
log.error(
|
|
'Tasks',
|
|
'task=restore id=%s user=%s message=%s error=%s',
|
|
taskData._id,
|
|
taskData.user,
|
|
archived,
|
|
'Failed to restore message. ' + err.message
|
|
);
|
|
await timeout(5000);
|
|
continue;
|
|
}
|
|
|
|
if (!messageResponse) {
|
|
log.error('Tasks', 'task=restore id=%s user=%s message=%s error=%s', taskData._id, taskData.user, archived, 'Failed to restore message');
|
|
await timeout(1000);
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
await db.users.collection('users').updateOne(
|
|
{
|
|
_id: taskData.user
|
|
},
|
|
{
|
|
$inc: {
|
|
storageUsed: messageData.size
|
|
}
|
|
}
|
|
);
|
|
} catch (err) {
|
|
// just log the error, nothing more
|
|
log.error(
|
|
'Tasks',
|
|
'task=restore id=%s user=%s message=%s error=%s',
|
|
taskData._id,
|
|
taskData.user,
|
|
archived,
|
|
'Failed to update user quota. ' + err.message
|
|
);
|
|
}
|
|
|
|
log.info(
|
|
'Tasks',
|
|
'task=restore id=%s user=%s message=%s mailbox=%s uid=%s action=restored',
|
|
taskData._id,
|
|
taskData.user,
|
|
messageResponse.message,
|
|
messageResponse.mailbox,
|
|
messageResponse.uid
|
|
);
|
|
|
|
try {
|
|
let r = await db.database.collection('archived').deleteOne({ _id: archived });
|
|
log.info('Tasks', 'task=restore id=%s user=%s message=%s action=deleted count=%s', taskData._id, taskData.user, archived, r.deletedCount);
|
|
} catch (err) {
|
|
log.error(
|
|
'Tasks',
|
|
'task=restore id=%s user=%s message=%s error=%s',
|
|
taskData._id,
|
|
taskData.user,
|
|
archived,
|
|
'Failed to delete archived message. ' + err.message
|
|
);
|
|
}
|
|
}
|
|
await cursor.close();
|
|
}
|
|
|
|
module.exports = (taskData, options, callback) => {
|
|
restore(taskData, options)
|
|
.then(result => callback(null, result))
|
|
.catch(err => {
|
|
log.error('Tasks', 'task=restore id=%s user=%s error=%s', taskData._id, taskData.user, err.message);
|
|
callback(err);
|
|
});
|
|
};
|