mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
Chunked uploads
This commit is contained in:
parent
96b766cef7
commit
c30d17cbf8
@ -44,6 +44,7 @@ from app.classes.web.routes.api.servers.server.files import (
|
||||
ApiServersServerFilesIndexHandler,
|
||||
ApiServersServerFilesCreateHandler,
|
||||
ApiServersServerFilesZipHandler,
|
||||
ApiServersServerFilesUploadHandler,
|
||||
)
|
||||
from app.classes.web.routes.api.servers.server.tasks.task.children import (
|
||||
ApiServersServerTasksTaskChildrenHandler,
|
||||
@ -243,6 +244,11 @@ def api_handlers(handler_args):
|
||||
ApiServersServerFilesZipHandler,
|
||||
handler_args,
|
||||
),
|
||||
(
|
||||
r"/api/v2/servers/([a-z0-9-]+)/files/upload/?",
|
||||
ApiServersServerFilesUploadHandler,
|
||||
handler_args,
|
||||
),
|
||||
(
|
||||
r"/api/v2/servers/([a-z0-9-]+)/tasks/?",
|
||||
ApiServersServerTasksIndexHandler,
|
||||
|
@ -6,6 +6,7 @@ from jsonschema import validate
|
||||
from jsonschema.exceptions import ValidationError
|
||||
from app.classes.models.server_permissions import EnumPermissionsServer
|
||||
from app.classes.shared.helpers import Helpers
|
||||
from app.classes.shared.main_controller import WebSocketManager, Controller
|
||||
from app.classes.shared.file_helpers import FileHelpers
|
||||
from app.classes.web.base_api_handler import BaseApiHandler
|
||||
|
||||
@ -577,3 +578,91 @@ class ApiServersServerFilesZipHandler(BaseApiHandler):
|
||||
},
|
||||
)
|
||||
return self.finish_json(200, {"status": "ok"})
|
||||
|
||||
|
||||
class ApiServersServerFilesUploadHandler(BaseApiHandler):
|
||||
async def post(self, server_id: str):
|
||||
for header, value in self.request.headers.items():
|
||||
print(f"{header}: {value}")
|
||||
fileHash = self.request.headers.get("fileHash", 0)
|
||||
chunkHash = self.request.headers.get("chunk-hash", 0)
|
||||
file_size = self.request.headers.get("fileSize", None)
|
||||
self.file_id = self.request.headers.get("fileId")
|
||||
self.chunked = self.request.headers.get("chunked", True)
|
||||
self.filename = self.request.headers.get("filename", None)
|
||||
try:
|
||||
total_chunks = int(self.request.headers.get("total_chunks", None))
|
||||
except TypeError:
|
||||
return self.finish_json(
|
||||
400, {"status": "error", "data": "INVALID CHUNK COUNT"}
|
||||
)
|
||||
self.chunk_index = self.request.headers.get("chunkId")
|
||||
self.location = self.request.headers.get("location", None)
|
||||
self.upload_dir = self.location
|
||||
self.temp_dir = os.path.join(self.controller.project_root, "temp", self.file_id)
|
||||
if self.chunked and not self.chunk_index:
|
||||
return self.finish_json(
|
||||
200, {"status": "ok", "data": {"file-id": self.file_id}}
|
||||
)
|
||||
|
||||
# Create the upload and temp directories if they don't exist
|
||||
os.makedirs(self.upload_dir, exist_ok=True)
|
||||
os.makedirs(self.temp_dir, exist_ok=True)
|
||||
|
||||
# Read headers and query parameters
|
||||
content_length = int(self.request.headers.get("Content-Length"))
|
||||
if content_length <= 0:
|
||||
return self.finish_json(
|
||||
400, {"status": "error", "data": {"message": "Invalid content length"}}
|
||||
)
|
||||
|
||||
if not self.filename or self.chunk_index is None or total_chunks is None:
|
||||
return self.finish_json(
|
||||
400,
|
||||
{
|
||||
"status": "error",
|
||||
"data": {
|
||||
"message": "Filename, chunk_index,"
|
||||
" and total_chunks are required"
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
# File paths
|
||||
file_path = os.path.join(self.upload_dir, self.filename)
|
||||
chunk_path = os.path.join(
|
||||
self.temp_dir, f"{self.filename}.part{self.chunk_index}"
|
||||
)
|
||||
|
||||
# Save the chunk
|
||||
with open(chunk_path, "wb") as f:
|
||||
f.write(self.request.body)
|
||||
|
||||
# Check if all chunks are received
|
||||
received_chunks = [
|
||||
f
|
||||
for f in os.listdir(self.temp_dir)
|
||||
if f.startswith(f"{self.filename}.part")
|
||||
]
|
||||
if len(received_chunks) == total_chunks:
|
||||
with open(file_path, "wb") as outfile:
|
||||
for i in range(total_chunks):
|
||||
chunk_file = os.path.join(self.temp_dir, f"{self.filename}.part{i}")
|
||||
with open(chunk_file, "rb") as infile:
|
||||
outfile.write(infile.read())
|
||||
os.remove(chunk_file)
|
||||
|
||||
self.write(
|
||||
json.dumps(
|
||||
{"status": "completed", "message": "File uploaded successfully"}
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.write(
|
||||
json.dumps(
|
||||
{
|
||||
"status": "partial",
|
||||
"message": f"Chunk {self.chunk_index} received",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -67,7 +67,8 @@
|
||||
translate('serverFiles', 'download', data['lang']) }}</a>
|
||||
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteFile" href="#"
|
||||
style="color: red">{{ translate('serverFiles', 'delete', data['lang']) }}</a>
|
||||
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteDir" href="#" style="color: red">{{
|
||||
<a onclick="deleteFileE(event)" href="javascript:void(0)" id="deleteDir" href="#"
|
||||
style="color: red">{{
|
||||
translate('serverFiles', 'delete', data['lang']) }}</a>
|
||||
<a href="javascript:void(0)" class="closebtn" style="color: var(--info);"
|
||||
onclick="document.getElementById('files-tree-nav').style.display = 'none';">{{
|
||||
@ -156,7 +157,8 @@
|
||||
right: 35px;
|
||||
}
|
||||
}
|
||||
.tree-file:hover{
|
||||
|
||||
.tree-file:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@ -721,6 +723,84 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadFile(file, path, onProgress) {
|
||||
const fileId = uuidv4();
|
||||
const token = getCookie("_xsrf")
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
if (!file) {
|
||||
alert("Please select a file first.");
|
||||
return;
|
||||
}
|
||||
|
||||
const chunkSize = 1024 * 1024; // 1MB
|
||||
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||
|
||||
const uploadPromises = [];
|
||||
let res = await fetch(`/api/v2/servers/${serverId}/files/upload/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-XSRFToken': token,
|
||||
'chunked': true,
|
||||
'total_chunks': totalChunks,
|
||||
'location': path,
|
||||
'filename': file.name,
|
||||
'fileId': fileId,
|
||||
},
|
||||
body: null,
|
||||
});
|
||||
|
||||
let responseData = await res.json();
|
||||
|
||||
let file_id = ""
|
||||
if (responseData.status === "ok") {
|
||||
file_id = responseData.data["file-id"]
|
||||
}
|
||||
for (let i = 0; i < totalChunks; i++) {
|
||||
const start = i * chunkSize;
|
||||
const end = Math.min(start + chunkSize, file.size);
|
||||
const chunk = file.slice(start, end);
|
||||
|
||||
const uploadPromise = fetch(`/api/v2/servers/${serverId}/files/upload/`, {
|
||||
method: 'POST',
|
||||
body: chunk,
|
||||
headers: {
|
||||
'Content-Range': `bytes ${start}-${end - 1}/${file.size}`,
|
||||
'Content-Length': chunk.size,
|
||||
'chunked': true,
|
||||
'total_chunks': totalChunks,
|
||||
'filename': file.name,
|
||||
'location': path,
|
||||
'filename': file.name,
|
||||
'fileId': fileId,
|
||||
'chunkId': i,
|
||||
},
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === "completed") {
|
||||
alert("File uploaded successfully!");
|
||||
} else if (data.status !== "partial") {
|
||||
throw new Error(data.message);
|
||||
}
|
||||
});
|
||||
|
||||
uploadPromises.push(uploadPromise);
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all(uploadPromises);
|
||||
} catch (error) {
|
||||
alert("Error uploading file: " + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = Math.random() * 16 | 0,
|
||||
v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
async function sendFile(file, path, serverId, left, i, onProgress) {
|
||||
let xmlHttpRequest = new XMLHttpRequest();
|
||||
let token = getCookie("_xsrf")
|
||||
@ -881,7 +961,7 @@
|
||||
`;
|
||||
$('#upload-progress-bar-parent').append(progressHtml);
|
||||
|
||||
await sendFile(files.files[i], path, serverId, nFiles - i - 1, i, (progress) => {
|
||||
await uploadFile(files.files[i], path, (progress) => {
|
||||
$(`#upload-progress-bar-${i + 1}`).attr('aria-valuenow', progress)
|
||||
$(`#upload-progress-bar-${i + 1}`).css('width', progress + '%');
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user