diff --git a/Makefile b/Makefile index 15dd724..58cdb9f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,7 @@ -all: ensure_env build serve +all: ensure_env clean build serve + +clean: + -@rm -rf src/rupload/__pycache__ ensure_env: -@python3 -m venv .venv diff --git a/README.md b/README.md index 0cb4672..8a1a39e 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,30 @@ python3 -m venv .venv ``` ## Usage -``` -rupload.serve [host(127.0.0.1)] [port] [destination path for uploads] [max file size in bytes] -``` +`rupload.serve` [-h] + [--hostname HOSTNAME] + [--port PORT] + [--upload_folder UPLOAD_FOLDER] + [--upload_url UPLOAD_URL] + [--max_file_size MAX_FILE_SIZE] + +Start the file upload server. + +options: + -h, --help show this help message + and exit + --hostname HOSTNAME The hostname for the + server. + --port PORT The port to bind the + server to. + --upload_folder UPLOAD_FOLDER + Directory to store + uploaded files. + --upload_url UPLOAD_URL + HTTP(S) URL where the + server will serve the + uploaded files. + --max_file_size MAX_FILE_SIZE + Maximum file size in + bytes (default is + 50MB). diff --git a/src/rupload/__pycache__/app.cpython-312.pyc b/src/rupload/__pycache__/app.cpython-312.pyc index e8b797e..42295ff 100644 Binary files a/src/rupload/__pycache__/app.cpython-312.pyc and b/src/rupload/__pycache__/app.cpython-312.pyc differ diff --git a/src/rupload/__pycache__/cli.cpython-312.pyc b/src/rupload/__pycache__/cli.cpython-312.pyc index a87e8c1..0e5d83c 100644 Binary files a/src/rupload/__pycache__/cli.cpython-312.pyc and b/src/rupload/__pycache__/cli.cpython-312.pyc differ diff --git a/src/rupload/app.py b/src/rupload/app.py index 449deb1..e7101e7 100644 --- a/src/rupload/app.py +++ b/src/rupload/app.py @@ -5,15 +5,13 @@ import pathlib class Rupload(web.Application): - def __init__(self, upload_path, max_file_size): + def __init__(self, upload_url:str="/uploads/", upload_path:str="uploads", max_file_size:int=1024*1024*50): self.upload_path = upload_path self.max_file_size = max_file_size + self.upload_url = upload_url super().__init__() -UPLOAD_FOLDER = "uploads" -pathlib.Path(UPLOAD_FOLDER).mkdir(parents=True, exist_ok=True) - UPLOAD_PAGE = """ @@ -28,7 +26,7 @@ UPLOAD_PAGE = """ display: flex; justify-content: center; align-items: center; - height: 100vh; + margin: 0; } @@ -37,7 +35,6 @@ UPLOAD_PAGE = """ border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); padding: 20px; - text-align: center; width: 100%; max-width: 500px; } @@ -48,6 +45,13 @@ UPLOAD_PAGE = """ color: #333; } + h2 { + font-size: 20px; + margin-bottom: 20px; + color: #333; + clear: both; + } + form { display: flex; flex-direction: column; @@ -87,37 +91,104 @@ UPLOAD_PAGE = """ font-size: 14px; color: #777; } + + .thumbnail { + width: 100px; + height: 100px; + object-fit: cover; + margin: 10px; + float:left; + }
-

Upload Your File

+

Upload file

+
+ [message] +
-
+
+ Click here to see uploaded files (non-image) at the bottom of this page.
+

Uploaded images:

+
+ [images_html] +
+

Uploaded files:

+
+ [files_html] +
+
""" +def format_size(size): + if size < 1024: + return f"{size} B" + elif size < 1024 * 1024: + return f"{size / 1024:.2f} KB" + elif size < 1024 * 1024 * 1024: + return f"{size / 1024*1024:.2f} MB" + elif size < 1024 * 1024 * 1024: + return f"{size / 1024 * 1024 * 1024:.2f} GB" + else: + return f"{size / 1024 * 1024 * 1024 * 1024:.2f} TB" -async def handle_upload(request): +def get_images(path): + images = [] + for image in pathlib.Path(path).iterdir(): + if image.is_file() and image.suffix in [".png", ".jpg", ".gif", ".jpeg", ".bmp"]: + images.append(image) + return images + +def get_files(path): + images = get_images(path) + files = [] + for file in pathlib.Path(path).iterdir(): + if file.is_file() and file not in images: + files.append(file) + return files + +def create_images_html(url, image_paths): + images_html = "" + for image_path in image_paths: + path = url.rstrip("/") + "/" + image_path.name + images_html += f'{image_path.name}' + return images_html + +def create_files_html(url, file_paths): + files_html = "" + for file_path in file_paths: + path = url.rstrip("/") + "/" + file_path.name + files_html += f'{file_path.name} ({format_size(file_path.stat().st_size)})
' + return files_html + +async def handle_upload(request:web.Request): reader = await request.multipart() field = await reader.next() - + app = request.app if field.name == "file": filename = field.filename - print(filename) - if ".." or "/" in filename: + print(f"Attempting to upload {filename}.") + if "/" in filename: + print(f"Invalid filename: {filename}.") return web.Response(status=400, text="Invalid filename.") - filepath = pathlib.Path(UPLOAD_FOLDER).joinpath(filename) + filepath = pathlib.Path(app.upload_path).joinpath(filename) if filepath.exists(): - return web.Response(status=400, text="File already exists.") + print(f"File {filename} already exists.") + return web.HTTPFound("/?message=File%20already%20exists.") + + pathlib.Path(app.upload_path).mkdir(parents=True, exist_ok=True) with filepath.open("wb") as f: file_size = 0 @@ -126,26 +197,47 @@ async def handle_upload(request): if not chunk: break file_size += len(chunk) - if file_size > max_file_size: + if file_size > app.max_file_size: + print(f"File is too large: {file_size} bytes.") + print(f"Maximum file size is {app.max_file_size} bytes.") + print(f"Deleting file {filename}.") f.close() f.unlink() + print(f"File {filename} deleted.") return web.Response( status=413, text="File is too large. Maximum file size is {} bytes.".format( - max_file_size + app.max_file_size ), ) f.write(chunk) - - return web.Response(text=f"File {filename} uploaded successfully!") + print(f"File {filename} uploaded successfully.") + uploaded_url = app.upload_url.rstrip("/") + "/" + filename + print(f"File {filename} is now available at: {uploaded_url}.") + return web.HTTPFound("/?message=File is succesfully uploaded and is available here:" + uploaded_url) + print("No file uploaded.") return web.Response(status=400, text="No file uploaded.") -async def handle_index(request): - return web.Response(text=UPLOAD_PAGE, content_type="text/html") +async def handle_index(request:web.Request): + image_paths = get_images(request.app.upload_path) + images_html = create_images_html(url=request.app.upload_url,image_paths=image_paths) + file_paths = get_files(request.app.upload_path) + files_html = create_files_html(url=request.app.upload_url,file_paths=file_paths) + html_content = UPLOAD_PAGE.replace("[images_html]",images_html) + html_content = html_content.replace("[files_html]",files_html) + message = request.query.get("message", "") + if request.app.upload_url in message: + url = message[message.find(request.app.upload_url):] + message = message.replace(request.app.upload_url, f" {url}" + if message: + message += "

" + html_content = html_content.replace("[message]",message) + return web.Response(text=html_content, content_type="text/html") -def create_app(upload_path="upload", max_file_size=1024 * 1024 * 50): - app = Rupload(upload_path=upload_path, max_file_size=max_file_size) - app.add_routes([web.get("/", handle_index), web.post("/upload", handle_upload)]) +def create_app(upload_url:str="/", upload_path:str="upload", max_file_size:int=1024 * 1024 * 50): + app = Rupload(upload_url=upload_url, upload_path=upload_path, max_file_size=max_file_size) + app.add_routes([web.get("/", handle_index), web.post("/upload", handle_upload),web.static("/uploads", "uploads")]) return app diff --git a/src/rupload/cli.py b/src/rupload/cli.py index cab2415..eaf1d0f 100644 --- a/src/rupload/cli.py +++ b/src/rupload/cli.py @@ -17,6 +17,12 @@ def parse_args(): default="uploads", help="Directory to store uploaded files.", ) + parser.add_argument( + "--upload_url", + type=str, + default="/uploads/", + help="HTTP(S) URL where the server will serve the uploaded files.", + ) parser.add_argument( "--max_file_size", type=int, @@ -28,7 +34,7 @@ def parse_args(): def main(): args = parse_args() - app = create_app(upload_path=args.upload_folder, max_file_size=args.max_file_size) + app = create_app(upload_url=args.upload_url, upload_path=args.upload_folder, max_file_size=args.max_file_size) web.run_app(app, host=args.hostname, port=args.port)