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
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");
|
|
});
|
|
}
|
|
}
|
|
}
|
|
};
|
|
};
|