Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Uploads #9

Merged
merged 11 commits into from
Jan 22, 2025
117 changes: 117 additions & 0 deletions api/endpoints/upload_route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"""Contains routes for uploading files and accessing uploaded files."""

from __future__ import annotations

from fastapi import APIRouter, File, Form, HTTPException, UploadFile

from api.utils.upload_helper import (
calculate_md5_checksum,
get_all_filenames,
reassemble_file,
save_chunk,
save_file,
)

router = APIRouter(prefix="/upload", tags=["upload"])


@router.post("/chunk")
async def upload_chunk(
file: UploadFile = None,
chunk_number: int = Form(...),
total_chunks: int = Form(...),
chunk_hash: str = Form(...),
):
Cbameron12 marked this conversation as resolved.
Show resolved Hide resolved
Cbameron12 marked this conversation as resolved.
Show resolved Hide resolved
"""
Allow individual chunks to be uploaded and later reassembled.

Parameters
----------
file : UploadFile
The chunk file to be uploaded.
chunk_number : int
The number of the chunk being uploaded.
total_chunks : int
The total number of chunks for the file.
chunk_hash : str
The MD5 hash of the chunk.

Returns
-------
dict
A dictionary containing a message and the path where the chunk was saved.

Raises
------
HTTPException
If there is an error during the upload process.
"""
if file is None:
file = File(...)
Cbameron12 marked this conversation as resolved.
Show resolved Hide resolved
print(file.filename)
print(f"Received chunk {chunk_number} of {total_chunks}")
print(f"Received Chunk MD5 Hash: {chunk_hash}")
Cbameron12 marked this conversation as resolved.
Show resolved Hide resolved
try:
file_content = await file.read()
calculated_hash = calculate_md5_checksum(file_content)
print(f"Calculated Chunk MD5 Hash: {calculated_hash}")
print(f"Hash matches: {calculated_hash == chunk_hash}")

chunk_path = save_chunk(file_content, chunk_number, file.filename)
print(f"Chunk saved at: {chunk_path}")

if chunk_number == total_chunks - 1:
reassemble_file(total_chunks, file.filename)
return {"message": "Chunk uploaded successfully", "chunk_path": chunk_path}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) from e


@router.post("/single")
async def upload_single(file: UploadFile = None, file_hash: str = Form(...)):
Cbameron12 marked this conversation as resolved.
Show resolved Hide resolved
"""
Upload a single file.

Parameters
----------
file : UploadFile
The file to be uploaded.
file_hash : str
The MD5 hash of the file.

Returns
-------
dict
A dictionary containing a message and the path where the file was saved.

Raises
------
HTTPException
If there is an error during the upload process.
"""
if file is None:
Cbameron12 marked this conversation as resolved.
Show resolved Hide resolved
file = File(...)
print(f"Received File MD5 Hash: {file_hash}")
try:
file_content = await file.read()
calculated_hash = calculate_md5_checksum(file_content)
print(f"Calculated File MD5 Hash: {calculated_hash}")
print(f"Hash matches: {calculated_hash == file_hash}")

file_path = save_file(file)
return {"message": "File uploaded successfully", "file_path": file_path}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) from e


@router.get("/files")
async def get_files() -> list[str]:
"""
Get a list of all uploaded files.

Returns
-------
list of str
Cbameron12 marked this conversation as resolved.
Show resolved Hide resolved
A list of filenames of all uploaded files.
"""
return get_all_filenames()
122 changes: 122 additions & 0 deletions api/utils/upload_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""Helper functions for uploading files and accessing uploaded files."""

from __future__ import annotations

import hashlib
import os

DATA_DIR = "/home/ubuntu/janus-api/janus-web/data"


def save_chunk(file, chunk_number, original_filename, directory=DATA_DIR):
Cbameron12 marked this conversation as resolved.
Show resolved Hide resolved
"""
Save a chunk of a file to the specified directory.

Parameters
----------
file : bytes
The content of the chunk to be saved.
chunk_number : int
The number of the chunk being saved.
original_filename : str
The original filename of the file being chunked.
directory : str, optional
The directory where the chunk will be saved (default is DATA_DIR).

Returns
-------
str
The path where the chunk was saved.
"""
os.makedirs(directory, exist_ok=True)
chunk_path = os.path.join(directory, f"{original_filename}_chunk_{chunk_number}")
Cbameron12 marked this conversation as resolved.
Show resolved Hide resolved
with open(chunk_path, "wb") as chunk_file:
chunk_file.write(file)
return chunk_path


def reassemble_file(total_chunks, original_filename, directory=DATA_DIR):
Cbameron12 marked this conversation as resolved.
Show resolved Hide resolved
"""
Reassemble a file from its chunks.

Parameters
----------
total_chunks : int
The total number of chunks.
original_filename : str
The original filename of the file being reassembled.
directory : str, optional
The directory where the chunks are stored (default is DATA_DIR).

Returns
-------
str
The path where the reassembled file was saved.
"""
output_path = os.path.join(directory, original_filename)
with open(output_path, "wb") as complete_file:
for i in range(total_chunks):
chunk_path = os.path.join(directory, f"{original_filename}_chunk_{i}")
with open(chunk_path, "rb") as chunk_file:
complete_file.write(chunk_file.read())
os.remove(chunk_path)
return output_path


def save_file(file, directory=DATA_DIR):
Cbameron12 marked this conversation as resolved.
Show resolved Hide resolved
"""
Save a file to the specified directory.

Parameters
----------
file : UploadFile
The file to be saved.
directory : str, optional
The directory where the file will be saved (default is DATA_DIR).

Returns
-------
str
The path where the file was saved.
"""
os.makedirs(directory, exist_ok=True)
file_path = os.path.join(directory, file.filename)
Cbameron12 marked this conversation as resolved.
Show resolved Hide resolved
with open(file_path, "wb") as buffer:
buffer.write(file.file.read())
return file_path


def calculate_md5_checksum(file_chunk):
"""
Calculate the MD5 checksum of a file chunk.

Parameters
----------
file_chunk : bytes
The content of the file chunk.

Returns
-------
str
The MD5 checksum of the file chunk.
"""
md5 = hashlib.md5()
md5.update(file_chunk)
return md5.hexdigest()


def get_all_filenames() -> list[str]:
"""
Get a list of all filenames in the data directory.

Returns
-------
list of str
A list of filenames in the data directory.
"""
filenames = [
filename
for filename in os.listdir(DATA_DIR)
if os.path.isfile(os.path.join(DATA_DIR, filename))
]
return filenames if filenames else ["No files found"]
Cbameron12 marked this conversation as resolved.
Show resolved Hide resolved
17 changes: 17 additions & 0 deletions main.py
Cbameron12 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Main module for the application."""

from __future__ import annotations

from fastapi import FastAPI

from api.endpoints import upload_route

app = FastAPI()

app.include_router(upload_route.router)


if __name__ == "__main__":
import uvicorn

uvicorn.run(app, host="0.0.0.0", port=8000)
Loading