2026-05-20 18:25:15 +02:00
|
|
|
/** @typedef { import('types/UploadchunkResponse.js').UploadchunkResponse} UploadchunkResponse */
|
|
|
|
|
|
|
|
|
|
export const CHUNK_SIZE = 8192;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* An uploaded file instance.
|
|
|
|
|
* Create a new file data.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} name Filename
|
|
|
|
|
* @param {number} totalChunks Total number of chunks of the file.
|
|
|
|
|
* @param rawFile The JS file element relation.
|
|
|
|
|
*/
|
|
|
|
|
export function FSFileData(name, totalChunks, rawFile ) {
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Filename.
|
|
|
|
|
* @type {string}
|
|
|
|
|
*/
|
|
|
|
|
name,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Number of total chunks based on CHUNK_SIZE.
|
|
|
|
|
* @type {number}
|
|
|
|
|
*/
|
|
|
|
|
totalChunks,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The JS file element relation.
|
|
|
|
|
*/
|
|
|
|
|
rawFile,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Upload progression value.
|
|
|
|
|
* @type {number}
|
|
|
|
|
*/
|
|
|
|
|
progressValue: 0,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Current chunk uploaded.
|
|
|
|
|
* @type {number}
|
|
|
|
|
*/
|
|
|
|
|
currentChunk: 0,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* If the upload of the file is finished.
|
|
|
|
|
* @type {boolean}
|
|
|
|
|
*/
|
|
|
|
|
done: false,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* If there is an error during file uploading.
|
|
|
|
|
* @type {any|null}
|
|
|
|
|
*/
|
|
|
|
|
error: null,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* UUID v4 for the file.
|
|
|
|
|
* @type {`${string}-${string}-${string}-${string}-${string}`}
|
|
|
|
|
*/
|
|
|
|
|
uuid: crypto.randomUUID(),
|
|
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
/**
|
|
|
|
|
* Current file state
|
|
|
|
|
*/
|
|
|
|
|
state: 'public',
|
2026-05-20 18:25:15 +02:00
|
|
|
|
2026-06-08 16:25:52 +02:00
|
|
|
/**
|
|
|
|
|
* If the online patcher is enabled
|
|
|
|
|
*/
|
|
|
|
|
meta_online_patcher: false,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* If this patch is a secondary patch.
|
|
|
|
|
*/
|
|
|
|
|
meta_secondary_online_patcher: false,
|
|
|
|
|
|
2026-05-20 18:25:15 +02:00
|
|
|
/**
|
2026-06-02 20:54:10 +02:00
|
|
|
* Look if this file is currently uploading.
|
|
|
|
|
* @returns {boolean}
|
2026-05-20 18:25:15 +02:00
|
|
|
*/
|
2026-06-02 20:54:10 +02:00
|
|
|
get isUploading()
|
|
|
|
|
{
|
|
|
|
|
return !this.done && !this.error;
|
|
|
|
|
},
|
2026-05-20 18:25:15 +02:00
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
/**
|
|
|
|
|
* Build API url.
|
|
|
|
|
* @param {string} section
|
|
|
|
|
* @returns {string} The API url.
|
|
|
|
|
*/
|
|
|
|
|
buildUrl(section)
|
|
|
|
|
{
|
|
|
|
|
return `/api/fs/upload-chunk/${section}`;
|
|
|
|
|
},
|
2026-05-20 18:25:15 +02:00
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
/**
|
|
|
|
|
* Upload the file.
|
|
|
|
|
* @param {string} section section of the file.
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
|
|
|
|
async upload(section)
|
|
|
|
|
{
|
2026-05-20 18:25:15 +02:00
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
if (!this.rawFile)
|
|
|
|
|
return; // Can't upload in that case.
|
2026-05-20 18:25:15 +02:00
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
/**
|
|
|
|
|
* Get CSRF token for uploading request.
|
|
|
|
|
* @type {string}
|
|
|
|
|
*/
|
|
|
|
|
const CSRF = document.querySelector('meta[name=csrf-token]')?.content ?? '';
|
2026-05-20 18:25:15 +02:00
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
for (let i = 0; i < this.totalChunks; i++) {
|
2026-05-20 18:25:15 +02:00
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
if (this.error)
|
|
|
|
|
return; // Abort the process.
|
2026-05-20 18:25:15 +02:00
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
const start = i * CHUNK_SIZE;
|
|
|
|
|
const end = Math.min(start + CHUNK_SIZE, this.rawFile.size);
|
|
|
|
|
const chunk = this.rawFile.slice(start, end);
|
2026-05-20 18:25:15 +02:00
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
const formData = new FormData();
|
2026-05-20 18:25:15 +02:00
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
formData.append('file', chunk);
|
|
|
|
|
formData.append('file_uuid', this.uuid);
|
|
|
|
|
formData.append('current_chunk', i);
|
|
|
|
|
formData.append('total_chunks', this.totalChunks);
|
|
|
|
|
formData.append('filename', this.rawFile.name);
|
|
|
|
|
formData.append('_token', CSRF);
|
2026-05-20 18:25:15 +02:00
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
// -----
|
|
|
|
|
// UPLOAD TIME !
|
|
|
|
|
// -----
|
2026-05-20 18:25:15 +02:00
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
try {
|
|
|
|
|
const RESPONSE = await fetch(this.buildUrl(section), {method: 'POST', body: formData});
|
2026-05-20 18:25:15 +02:00
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
if (!RESPONSE.ok) // Problem with the request.
|
|
|
|
|
throw new Error(`${RESPONSE.status} ${RESPONSE.statusText}`);
|
|
|
|
|
|
|
|
|
|
/** @type {UploadchunkResponse} */
|
|
|
|
|
const DATA = await RESPONSE.json();
|
|
|
|
|
|
|
|
|
|
if (DATA.success !== true || DATA.uploaded !== true)
|
|
|
|
|
// The request reached the file server but could not be sent.
|
|
|
|
|
throw new Error(`${DATA.error}`);
|
|
|
|
|
|
|
|
|
|
this.currentChunk = i + 1;
|
|
|
|
|
this.progressValue = Math.round(((i + 1) / this.totalChunks) * 100);
|
|
|
|
|
|
|
|
|
|
if (DATA.finished === true) {
|
|
|
|
|
this.done = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
this.error = 'Error on chunk ' + (i + 1) + '. ' + err.message;
|
|
|
|
|
this.progressValue = 0;
|
2026-05-20 18:25:15 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|