WIP
parent
68c9001270
commit
7344b45be0
@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "@joepie91/eslint-config"
|
"extends": "@joepie91/eslint-config",
|
||||||
|
"ignorePatterns": "test/**"
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,55 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const utf7 = require('utf7').imap;
|
||||||
|
|
||||||
|
const RE_BACKSLASH = /\\/g;
|
||||||
|
const RE_DBLQUOTE = /"/g;
|
||||||
|
|
||||||
|
function escape(str) {
|
||||||
|
return str.replace(RE_BACKSLASH, '\\\\').replace(RE_DBLQUOTE, '\\"');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
command: function (strings, ... interpolations) {
|
||||||
|
return {
|
||||||
|
toCommandString: function () {
|
||||||
|
let processedInterpolations = interpolations.map((interpolation) => {
|
||||||
|
let isObject = interpolation != null && typeof interpolation === "object";
|
||||||
|
|
||||||
|
if (isObject && interpolation._isRaw) {
|
||||||
|
return interpolation.string;
|
||||||
|
} else if (isObject && interpolation._is7Bit) {
|
||||||
|
return escape(interpolation.string);
|
||||||
|
} else if (typeof interpolation === "string") {
|
||||||
|
return escape(utf7.encode(interpolation));
|
||||||
|
} else {
|
||||||
|
throw new Error(`Invalid input into command string: ${interpolation}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let combined = "";
|
||||||
|
|
||||||
|
strings.slice(0, -1).forEach((string, i) => {
|
||||||
|
combined += string;
|
||||||
|
combined += processedInterpolations[i];
|
||||||
|
});
|
||||||
|
|
||||||
|
combined += strings[strings.length - 1];
|
||||||
|
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
unsafeRaw: function (string) {
|
||||||
|
return {
|
||||||
|
_isRaw: true,
|
||||||
|
string: string
|
||||||
|
};
|
||||||
|
},
|
||||||
|
already7Bit: function (string) {
|
||||||
|
return {
|
||||||
|
_is7Bit: true,
|
||||||
|
string: string
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,112 @@
|
|||||||
|
"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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,32 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// TODO: Make separate package
|
||||||
|
|
||||||
|
const pTry = require("p-try");
|
||||||
|
|
||||||
|
module.exports = function pInterval(interval, handler) {
|
||||||
|
let timer = setTimeout(doHandler, interval);
|
||||||
|
|
||||||
|
function doHandler() {
|
||||||
|
let startTime = Date.now();
|
||||||
|
|
||||||
|
pTry(() => {
|
||||||
|
return handler();
|
||||||
|
}).then(() => {
|
||||||
|
let timeElapsed = Date.now() - startTime();
|
||||||
|
|
||||||
|
let timeout = (timeElapsed < interval)
|
||||||
|
? interval - timeElapsed
|
||||||
|
: 1; // Be consistently async!
|
||||||
|
|
||||||
|
timer = setTimeout(doHandler, timeout);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return function cancelInterval() {
|
||||||
|
if (timer != null) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,13 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = function pickObject(object, keys) {
|
||||||
|
let newObject = {};
|
||||||
|
|
||||||
|
for (let key of keys) {
|
||||||
|
if (key in object) {
|
||||||
|
newObject[key] = object[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newObject;
|
||||||
|
};
|
@ -1,4 +1,5 @@
|
|||||||
require('fs').readdirSync(__dirname).forEach(function(f) {
|
require('fs').readdirSync(__dirname).forEach(function(f) {
|
||||||
|
// if (f === "test-connection-fetch-dup.js")
|
||||||
if (f.substr(0, 5).toLowerCase() === 'test-')
|
if (f.substr(0, 5).toLowerCase() === 'test-')
|
||||||
require('./' + f);
|
require('./' + f);
|
||||||
});
|
});
|
Loading…
Reference in New Issue