@chunk-master/core
Version:
Core library for chunk-based file upload with resumable functionality
300 lines (299 loc) • 8.18 kB
JavaScript
import { inject as N, requestExtender as S } from "@net-vert/core";
import z from "lodash.throttle";
import { reactive as w } from "vue";
const D = {
maxSize: 5 * 1024 * 1024,
chunkSize: 5 * 1024 * 1024,
multiThread: !0,
uploadMethod: "post"
}, U = {
parallelCount: 3,
retries: 0
}, A = (s) => {
const { parallelCount: t, retries: e } = s, { requestor: r, concurrentPool: a } = S.concurrentPoolRequestor({
...U,
parallelCount: t,
retries: e
});
return {
pool: a,
requestor: r
};
}, y = (s) => {
const { request: t } = s;
N(t);
}, R = (s) => {
const t = new FormData();
for (const e in s)
t.append(e, s[e]);
return t;
};
function G(s, t) {
const e = { ...s };
return Object.entries(t).forEach(([r, a]) => {
a != null && (e[r] = a);
}), e;
}
const _ = async (s, t, e) => {
const r = t * e, a = Math.min(r + e, s.size), h = s.slice(r, a), l = i.hashApi && i.hashcalculation ? await i.hashcalculation(h) : void 0;
return {
start: r,
end: a,
index: t,
chunk: h,
size: a - r,
hash: l
};
}, b = async (s, t) => {
const { file: e, start: r, end: a, chunkSize: h } = s, l = [];
try {
for (let n = r; n < a; n++) {
const u = await _(e, n, h);
l.push(u), t(u, n);
}
return l;
} catch (n) {
throw console.error("Error creating chunks:", n), n;
}
};
let i;
const $ = (s) => {
y(s);
const { pool: t, requestor: e } = A(s), {
pool: r,
parallelCount: a,
retries: h,
request: l,
...n
} = s;
return i = {
...G(D, n),
sliceFile: s.sliceFile || b,
requestor: e,
pool: t
}, {
requestor: e
};
};
var o = /* @__PURE__ */ ((s) => (s.PREPARING = "preparing", s.PREPARE_FAILED = "prepare_failed", s.PENDING = "pending", s.PAUSE = "pause", s.UPLOADING = "uploading", s.SUCCESS = "success", s.FAIL = "fail", s))(o || {}), c = /* @__PURE__ */ ((s) => (s.PENDING = "pending", s.SUCCESS = "success", s.FAIL = "fail", s))(c || {});
const P = {
total: 0,
loaded: 0,
progress: 0
};
class F {
// 构造函数
constructor(t, e) {
this.metadata = t, this.uploader = e, this.controller = new AbortController(), this.responseInterval = 1e3, this.status = c.PENDING, this.promise = new Promise(() => {
}), this.updateProgressThrottle = z(this.updateProgress, this.responseInterval), this.metadata = t, this.metadata.hash ? this.id = this.metadata.hash : this.id = `${Date.now()}_${Math.random().toString().slice(2, 8)}_${this.metadata.index}`, this.progressInfo = {
...P,
total: this.metadata.chunk.size
};
}
// 发送请求的方法
async request() {
const t = i.beforeUpload ? await i.beforeUpload(this.metadata, this.uploader) : R(this.metadata);
return i.requestor.request(
{
url: i.uploadUrl,
method: i.uploadMethod,
data: t,
signal: this.controller.signal,
onUploadProgress: this.updateProgressThrottle.bind(this)
}
);
}
// 执行任务,添加到并发控制池中
execute() {
return this.promise = this.request.call(this), this.promise.then((t) => (this.status = c.SUCCESS, t)).catch((t) => (this.status = c.FAIL, t));
}
// 取消任务,根据不同状态进行相应处理
cancel() {
i.pool.remove(this.id), this.controller.abort();
}
// 暂停任务:取消当前任务、重置状态和控制器
pause() {
this.cancel(), this.controller = new AbortController(), this.updateProgressThrottle.cancel(), this.progressInfo = {
...P,
total: this.metadata.chunk.size
};
}
updateProgress(t) {
const { loaded: e, total: r } = t;
this.progressInfo = {
loaded: e,
total: r,
progress: e / r
};
}
}
class L {
constructor(t) {
this.file = t, this._status = o.PREPARING, this.tasks = [], this.fileInfo = null, i.prepareFileInfo ? i.prepareFileInfo(this.file).then((e) => {
this.fileInfo = e, this._status = o.PENDING;
}).catch((e) => {
throw this._status = o.PREPARE_FAILED, e;
}) : this._status = o.PENDING;
}
async start() {
try {
if (this._status = o.UPLOADING, this.tasks.length) {
const [t] = this.tasks;
await t.execute();
} else
await this.uploadFile();
this._status = o.SUCCESS;
} catch (t) {
if (this._status = o.PAUSE)
return t;
throw t;
}
}
async uploadFile() {
const t = w(
new F({
index: 0,
start: 0,
end: this.file.size,
size: this.file.size,
chunk: this.file
}, this)
);
this.tasks.push(t), await t.execute();
}
pause() {
const [t] = this.tasks;
this._status = o.PAUSE, t.pause();
}
cancel() {
const [t] = this.tasks;
t.cancel(), this.tasks = [];
}
get progressInfo() {
const [t] = this.tasks;
return t ? t.progressInfo : {
...P,
total: this.file.size
};
}
get status() {
return this._status;
}
}
const g = (navigator == null ? void 0 : navigator.hardwareConcurrency) || 4, x = async (s, t) => {
const { file: e, start: r, end: a, chunkSize: h } = s;
return new Promise((l, n) => {
const u = Math.ceil((a - r) / g);
if (u === 0)
l([]);
else {
const E = [];
let C = 0;
for (let f = 0; f < g; f++) {
const k = r + f * u, I = Math.min(k + u, a);
if (k >= I) break;
const p = new Worker(new URL("./worker.ts", __dirname), {
type: "module"
});
p.postMessage({
file: e,
start: k,
end: I,
chunkSize: h
}), p.onmessage = (m) => {
m.data.forEach((d) => {
E[d.index] = d, t(d, d.index);
}), p.terminate(), C++, C === Math.min(g, a - r) && l(E);
}, p.onerror = (m) => {
p.terminate(), n(m);
};
}
}
});
}, q = async (s, t, e, r) => {
const a = {
file: s,
start: t,
end: e,
chunkSize: i.chunkSize
};
return i.hashApi && i.hashcalculation && i.multiThread ? x(a, r) : i.sliceFile(a, r);
};
class M {
constructor(t) {
this.file = t, this._status = o.PREPARING, this.tasks = [], this.fileInfo = null, this.totalChunks = Math.ceil(this.file.size / i.chunkSize), i.prepareFileInfo ? i.prepareFileInfo(this.file).then((e) => {
this.fileInfo = e, this._status = o.PENDING;
}).catch((e) => {
throw this._status = o.PREPARE_FAILED, e;
}) : this._status = o.PENDING;
}
// 这里可以实现大文件的分片上传逻辑
async start() {
try {
this._status = o.UPLOADING;
const t = await this.uploadFile();
this.mergeChunks(t), this._status = o.SUCCESS;
} catch (t) {
if (this._status = o.PAUSE)
return t;
throw t;
}
}
async preupload() {
const t = await _(this.file, 0, i.chunkSize);
this.setTask(t, 0);
}
async uploadFile() {
let t = this.tasks.length;
this.tasks.length === 0 ? (this.preupload(), t++) : this.uploadChunks(), await q(this.file, t, this.totalChunks, this.setTask.bind(this));
const e = this.tasks.map((r) => r.promise);
return Promise.all(e);
}
uploadChunks() {
for (const t of this.tasks)
t.status !== c.SUCCESS && t.execute();
}
setTask(t, e) {
const r = w(new F(t, this));
this.tasks[e] = r, this.status === o.UPLOADING && r.execute();
}
mergeChunks(t) {
const e = i.mergeApi;
return e(
t,
this
);
}
pause() {
this._status = o.PAUSE, this.tasks.forEach((t) => {
t.status !== c.SUCCESS && t.pause();
});
}
cancel() {
this.tasks.forEach((t) => {
t.status !== c.SUCCESS && t.cancel();
}), this.tasks = [];
}
get progressInfo() {
const t = this.tasks.reduce((r, a) => {
const h = a.progressInfo;
return r + h.loaded;
}, 0), e = Math.min(t, this.file.size) / this.file.size || 0;
return {
total: this.file.size,
loaded: t,
progress: e
};
}
get status() {
return this._status;
}
}
function B(s) {
return s.size > i.maxSize ? new M(s) : new L(s);
}
export {
o as STATUS,
$ as config,
B as createFileUploader
};