2020-08-01 03:17:07 +03:00
|
|
|
import * as LibraryManager from "~/node_common/managers/library";
|
|
|
|
import * as Utilities from "~/node_common/utilities";
|
2020-09-22 03:36:45 +03:00
|
|
|
import * as Social from "~/node_common/social";
|
2020-10-09 21:46:42 +03:00
|
|
|
import * as Strings from "~/common/strings";
|
|
|
|
import * as Logs from "~/node_common/script-logging";
|
2020-08-01 03:17:07 +03:00
|
|
|
|
2020-10-09 03:40:26 +03:00
|
|
|
import AbortController from "abort-controller";
|
2020-08-16 12:23:02 +03:00
|
|
|
import B from "busboy";
|
2020-10-09 21:46:42 +03:00
|
|
|
import Queue from "p-queue";
|
2020-08-16 02:42:26 +03:00
|
|
|
|
2020-08-16 12:23:02 +03:00
|
|
|
const HIGH_WATER_MARK = 1024 * 1024 * 3;
|
2020-08-16 02:42:26 +03:00
|
|
|
|
2020-10-09 21:46:42 +03:00
|
|
|
// NOTE(jim): This method is my attempt to be forceful around busboy calls.
|
|
|
|
// We don't want a ton of async methods flying around.
|
|
|
|
// So force one function at a time.
|
|
|
|
function safeForcedSingleConcurrencyFn(
|
|
|
|
actionFn,
|
|
|
|
rejectFn,
|
|
|
|
queue,
|
|
|
|
req,
|
|
|
|
user,
|
|
|
|
controller
|
|
|
|
) {
|
|
|
|
queue.add(async function() {
|
|
|
|
try {
|
|
|
|
await actionFn();
|
|
|
|
} catch (e) {
|
|
|
|
// NOTE(jim): immediately pause the queue
|
|
|
|
Logs.error("emergency: pausing the queue");
|
|
|
|
queue.pause();
|
|
|
|
|
|
|
|
// NOTE(jim): Guarantee we kill the signal on textiles end.
|
|
|
|
Logs.error("emergency: sending abort signal to textile");
|
|
|
|
controller.abort();
|
|
|
|
|
|
|
|
Logs.error("emergency: reporting bug to slack");
|
|
|
|
// NOTE(jim): Report any bugs to slack.
|
|
|
|
Social.sendTextileSlackMessage({
|
|
|
|
file: "/node_common/upload.js",
|
|
|
|
user,
|
|
|
|
message: e.message,
|
|
|
|
code: e.code,
|
|
|
|
functionName: `safeCall`,
|
|
|
|
});
|
|
|
|
|
|
|
|
// NOTE(jim): Kill the pipe
|
|
|
|
Logs.error("emergency: unpipe");
|
|
|
|
req.unpipe();
|
|
|
|
|
|
|
|
Logs.error("emergency: exit promise with failure");
|
|
|
|
return rejectFn({
|
|
|
|
decorator: "UPLOAD_FAILURE",
|
|
|
|
error: true,
|
|
|
|
message: e.message,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-09-23 14:17:56 +03:00
|
|
|
export const formMultipart = async (req, res, { user, bucketName }) => {
|
2020-10-09 21:46:42 +03:00
|
|
|
const heapUsed = Strings.bytesToSize(process.memoryUsage().heapUsed);
|
|
|
|
Logs.task(`Starting heap size ${heapUsed}`, "BROWSER->RENDER->TEXTILE");
|
|
|
|
|
|
|
|
// NOTE(jim): Variable for the file to save to DB later.
|
2020-08-17 07:22:35 +03:00
|
|
|
let data = null;
|
2020-10-09 21:46:42 +03:00
|
|
|
let dataPath = null;
|
|
|
|
|
|
|
|
// NOTE(jim): One function at a time.
|
|
|
|
const singleConcurrencyQueue = new Queue({ concurrency: 1 });
|
|
|
|
|
|
|
|
// NOTE(jim): Prepares the abort controller.
|
2020-10-09 03:40:26 +03:00
|
|
|
const controller = new AbortController();
|
|
|
|
const { signal } = controller;
|
2020-08-16 02:42:26 +03:00
|
|
|
|
2020-10-09 21:46:42 +03:00
|
|
|
// NOTE(jim): Prepares the buckets state for this file.
|
|
|
|
let {
|
|
|
|
buckets,
|
|
|
|
bucketKey,
|
|
|
|
bucketRoot,
|
|
|
|
} = await Utilities.getBucketAPIFromUserToken({
|
|
|
|
user,
|
|
|
|
bucketName,
|
|
|
|
});
|
2020-10-09 05:32:30 +03:00
|
|
|
|
2020-10-09 21:46:42 +03:00
|
|
|
const _upload = async () => {
|
|
|
|
return new Promise(function(resolve, reject) {
|
2020-08-17 07:22:35 +03:00
|
|
|
let form = new B({
|
|
|
|
headers: req.headers,
|
|
|
|
highWaterMark: HIGH_WATER_MARK,
|
2020-08-16 02:42:26 +03:00
|
|
|
});
|
|
|
|
|
2020-10-09 21:46:42 +03:00
|
|
|
// NOTE(jim): We only use this method to get a perfect stream
|
|
|
|
form.on("file", function(fieldname, stream, filename, encoding, mime) {
|
|
|
|
return safeForcedSingleConcurrencyFn(
|
|
|
|
async () => {
|
|
|
|
data = LibraryManager.createLocalDataIncomplete({
|
|
|
|
name: filename,
|
|
|
|
type: mime,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!buckets) {
|
|
|
|
throw new Error("!buckets");
|
|
|
|
}
|
|
|
|
|
|
|
|
let push = await buckets.pushPath(bucketKey, data.id, stream, {
|
|
|
|
root: bucketRoot,
|
|
|
|
signal,
|
|
|
|
});
|
|
|
|
|
|
|
|
// NOTE(jim): Save to memory so when busboy is finished
|
|
|
|
// We can resolve this elegantly.
|
|
|
|
dataPath = push.path.path;
|
|
|
|
|
|
|
|
// NOTE(jim): We don't need the pipe anymore !!!
|
|
|
|
req.unpipe();
|
|
|
|
Logs.task("Busboy pipe finish", "BROWSER->RENDER->TEXTILE");
|
|
|
|
},
|
|
|
|
reject,
|
|
|
|
singleConcurrencyQueue,
|
|
|
|
req,
|
2020-09-23 14:17:56 +03:00
|
|
|
user,
|
2020-10-09 21:46:42 +03:00
|
|
|
controller
|
|
|
|
);
|
2020-08-16 12:23:02 +03:00
|
|
|
});
|
2020-08-04 04:35:31 +03:00
|
|
|
|
2020-10-09 21:46:42 +03:00
|
|
|
// NOTE(jim): We don't need this, but lets elegantly handle events.
|
|
|
|
form.on("finish", () => {
|
|
|
|
return safeForcedSingleConcurrencyFn(
|
|
|
|
() => {
|
|
|
|
Logs.task(dataPath, "BROWSER->RENDER->TEXTILE");
|
|
|
|
|
|
|
|
if (Strings.isEmpty(dataPath)) {
|
|
|
|
throw new Error("IPFS asset URL missing");
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolve({
|
|
|
|
decorator: "UPLOAD_STREAM_SUCCESS",
|
|
|
|
data: dataPath,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
reject,
|
|
|
|
singleConcurrencyQueue,
|
|
|
|
req,
|
2020-09-22 05:31:17 +03:00
|
|
|
user,
|
2020-10-09 21:46:42 +03:00
|
|
|
controller
|
|
|
|
);
|
|
|
|
});
|
2020-09-22 05:31:17 +03:00
|
|
|
|
2020-10-09 21:46:42 +03:00
|
|
|
// NOTE(jim): We don't need this, shouldn't happen.
|
|
|
|
form.on("error", function(e) {
|
|
|
|
return safeForcedSingleConcurrencyFn(
|
|
|
|
() => {
|
|
|
|
throw new Error(e);
|
|
|
|
},
|
|
|
|
reject,
|
|
|
|
singleConcurrencyQueue,
|
|
|
|
req,
|
|
|
|
user,
|
|
|
|
controller
|
|
|
|
);
|
2020-08-17 07:22:35 +03:00
|
|
|
});
|
2020-08-01 03:17:07 +03:00
|
|
|
|
2020-10-09 21:46:42 +03:00
|
|
|
Logs.task("Busboy pipe start", "BROWSER->RENDER->TEXTILE");
|
|
|
|
|
2020-08-17 07:22:35 +03:00
|
|
|
req.pipe(form);
|
2020-08-01 03:17:07 +03:00
|
|
|
});
|
2020-10-09 21:46:42 +03:00
|
|
|
};
|
2020-08-16 12:23:02 +03:00
|
|
|
|
2020-10-09 21:46:42 +03:00
|
|
|
const response = await _upload();
|
|
|
|
|
|
|
|
console.log(response);
|
2020-08-17 07:22:35 +03:00
|
|
|
|
2020-09-22 05:31:17 +03:00
|
|
|
if (response && response.error) {
|
2020-08-17 07:22:35 +03:00
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
2020-10-09 21:46:42 +03:00
|
|
|
let refreshed = await Utilities.getBucketAPIFromUserToken({
|
2020-09-23 14:17:56 +03:00
|
|
|
user,
|
|
|
|
bucketName,
|
|
|
|
});
|
2020-09-22 10:01:48 +03:00
|
|
|
|
2020-10-09 21:46:42 +03:00
|
|
|
if (!refreshed.buckets) {
|
|
|
|
Logs.error("upload failed");
|
2020-09-22 10:01:48 +03:00
|
|
|
return {
|
2020-10-09 21:46:42 +03:00
|
|
|
decorator: "UPLOAD_FAILURE",
|
2020-09-22 10:01:48 +03:00
|
|
|
error: true,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-08-17 07:22:35 +03:00
|
|
|
try {
|
2020-10-09 21:46:42 +03:00
|
|
|
const newUpload = await refreshed.buckets.listIpfsPath(response.data);
|
2020-08-17 07:22:35 +03:00
|
|
|
data.size = newUpload.size;
|
2020-10-09 21:46:42 +03:00
|
|
|
|
|
|
|
Logs.task(
|
|
|
|
`A new ${Strings.bytesToSize(data.size)} file was uploaded successfully`,
|
|
|
|
"BROWSER->RENDER->TEXTILE"
|
|
|
|
);
|
2020-08-17 07:22:35 +03:00
|
|
|
} catch (e) {
|
2020-09-22 03:36:45 +03:00
|
|
|
Social.sendTextileSlackMessage({
|
|
|
|
file: "/node_common/upload.js",
|
|
|
|
user,
|
|
|
|
message: e.message,
|
|
|
|
code: e.code,
|
2020-10-09 21:46:42 +03:00
|
|
|
functionName: `refreshed.listIpfsPath`,
|
2020-09-22 03:36:45 +03:00
|
|
|
});
|
|
|
|
|
2020-08-17 07:22:35 +03:00
|
|
|
return {
|
2020-10-09 21:46:42 +03:00
|
|
|
decorator: "UPLOAD_VERIFY_FAILURE",
|
2020-08-17 07:22:35 +03:00
|
|
|
error: true,
|
2020-10-07 13:02:08 +03:00
|
|
|
message: e.message,
|
2020-08-17 07:22:35 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-10-09 21:46:42 +03:00
|
|
|
Logs.task(`Upload workflow complete !!!`, "BROWSER->RENDER->TEXTILE");
|
|
|
|
Logs.task(`Upload workflow complete !!!`, "BROWSER->RENDER->TEXTILE");
|
|
|
|
Logs.task(`Upload workflow complete !!!`, "BROWSER->RENDER->TEXTILE");
|
|
|
|
return { decorator: "UPLOAD_SUCCESS", data, ipfs: response.data };
|
2020-08-17 07:22:35 +03:00
|
|
|
};
|