Updated readme.

This commit is contained in:
retoor 2024-11-26 05:57:12 +01:00
parent 3e72ecab20
commit 67d91a42e5
6 changed files with 153 additions and 28 deletions

View File

@ -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

View File

@ -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).

View File

@ -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 = """
<!DOCTYPE html>
<html lang="en">
@ -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;
}
</style>
</head>
<body>
<div class="container">
<h1>Upload Your File</h1>
<h1>Upload file</h1>
<div class="message">
[message]
</div>
<form action="/upload" method="POST" enctype="multipart/form-data">
<label for="file">Choose a file to upload:</label>
<input type="file" name="file" id="file" required>
<button type="submit">Upload</button>
</form>
<br />
Click <a href="#files">here</a> to see <a href="#files">uploaded files</a> (non-image) at the bottom of this page.<br />
<h2>Uploaded images:</h2>
<div class="container-image">
[images_html]
</div>
<h2 id="files">Uploaded files:</h2>
<div class="container-file">
[files_html]
</div>
<div class="footer">
&copy; 2024 Retoor
</div>
</div>
</body>
</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'<img class="thumbnail" src="{path}" alt="{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'<a href="{path}">{file_path.name}</a> ({format_size(file_path.stat().st_size)})<br>'
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" <a href=\"{url}")
message += f"\">{url}</a>"
if message:
message += "<br /><br />"
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

View File

@ -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)