You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

113 lines
2.8 KiB
JavaScript

"use strict";
const events = require("events");
const syncpipe = require("syncpipe");
const defaultValue = require("default-value");
const splitFilter = require("split-filter");
const mapObject = require("map-obj");
const pickObject = require("./pick-object");
const RE_BODYPART = /^BODY\[/;
const RE_ESCAPE = /\\\\/g;
const FETCH_ATTR_MAP = {
'RFC822.SIZE': 'size',
'BODY': 'struct',
'BODYSTRUCTURE': 'struct',
'ENVELOPE': 'envelope',
'INTERNALDATE': 'date'
};
function mapIncomingAttributeKey(key) {
return defaultValue(FETCH_ATTR_MAP[key], key.toLowerCase());
}
// FIXME: Get rid of the separate-emitter hackery here, and make the task itself an emitter instead, or find some other better way to wire things up
module.exports = function createFetchTaskTracker() {
let tasks = new Map();
return {
get: function (id) {
// TODO: Eventually make this fail hard, after the calling code has been fully refactored
return tasks.get(id);
},
create: function (id, keysToFetch) {
let emitter = new events.EventEmitter();
let task = createFetchTask({ emitter, keysToFetch });
// FIXME: Delete after completion
tasks.set(id, task);
return task;
},
getOrCreate: function (id, keysToFetch) {
if (tasks.has(id)) {
return this.get(id);
} else {
return this.create(id, keysToFetch);
}
}
};
};
function createFetchTask({ emitter, keysToFetch }) {
let attributes = {};
let endHandled = false;
return {
emitter: emitter,
getRemainingKeys: function () {
return keysToFetch;
},
processFetchResponse: function (payload) {
if (keysToFetch.length > 0) {
let caseMappedPayload = mapObject(payload, (key, value) => {
return [ key.toUpperCase(), value ];
});
let [ existingKeys, missingKeys ] = splitFilter(keysToFetch, (key) => caseMappedPayload[key] != null);
let relevantKeys = existingKeys.filter((key) => RE_BODYPART.test(key) === false);
let newAttributes = syncpipe(caseMappedPayload, [
(_) => pickObject(_, relevantKeys),
(_) => mapObject(_, (key, value) => {
return [
/* key */ mapIncomingAttributeKey(key),
/* value */ (key === 'X-GM-LABELS')
// TODO: Why is this special case needed?
? value.map((label) => String(label).replace(RE_ESCAPE, '\\'))
: value
];
})
]);
Object.assign(attributes, newAttributes);
keysToFetch = missingKeys;
let isDone = (missingKeys.length === 0);
if (isDone === true) {
endHandled = true;
// FIXME: Why nextTick?
process.nextTick(function() {
emitter.emit("attributes", attributes);
emitter.emit("end");
});
}
} else {
if (endHandled === false) {
endHandled = true;
// FIXME: Why nextTick?
process.nextTick(function() {
emitter.emit("end");
});
}
}
}
};
};