Updated readme.
This commit is contained in:
parent
3e72ecab20
commit
67d91a42e5
5
Makefile
5
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
|
||||
|
30
README.md
30
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).
|
||||
|
Binary file not shown.
Binary file not shown.
@ -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">
|
||||
© 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
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user