Push all previous files to repo (26.01.08-LATEST)

This commit is contained in:
Andrew K
2026-01-08 22:15:32 +09:00
parent 9893d81691
commit 231384a259
12 changed files with 1235 additions and 0 deletions

11
.gitignore vendored
View File

@@ -0,0 +1,11 @@
__pycache__/
.vscode/
.DS_Store
logs
users.json
db.json
uploads/
uploads/*
update_db.py
.idea/
*.log

305
app.py Normal file
View File

@@ -0,0 +1,305 @@
from flask import (
Flask,
render_template,
session,
request,
url_for,
send_from_directory,
flash
)
from utils import redirect, dbload, dbsave, udbload, udbsave
from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.utils import secure_filename
import sys
import os
import random
import threading
import time
import logging
import logging.handlers
app = Flask('adrive', static_folder='static', template_folder='templates')
app.config['UPLOAD_DIRECTORY'] = 'uploads/'
app.config['MAX_CONTENT_LENGTH'] = 1000000 * 1024 * 1024
app.config['SECRET_KEY'] = str(random.randint(99999, 9999999))
# Redirect logs to the file 'logs'
handler = logging.handlers.RotatingFileHandler('logs', maxBytes=1024 * 1024)
logging.getLogger('werkzeug').setLevel(logging.DEBUG)
logging.getLogger('werkzeug').addHandler(handler)
app.logger.setLevel(logging.WARNING)
app.logger.addHandler(handler)
@app.route('/')
def index():
return redirect(url_for('upload'))
@app.route('/dashboard')
def dashboard():
if session.get('loggedIn', False) == False:
flash('You must be signed in to view the dashboard!', 'error')
return redirect(url_for('upload'))
db = dbload()
udb = udbload()
username = session.get('username')
userfiles = []
quota_usage_gb = 0
if not username:
flash('An error occurred. Please sign in again.', 'error')
session['loggedIn'] = False
session.pop('username', None)
return redirect(url_for('upload'))
for file in db['files']:
if db['files'][file].get('owner') == username:
db['files'][file]['file'] = file
db['files'][file]['code'] = file.split('_')[-1]
userfiles.append(db['files'][file])
for userfile in userfiles:
usf_mb = userfile['size_megabytes']
if usf_mb:
quota_usage_gb += usf_mb / 1024
quota_usage_gb = round(quota_usage_gb, 1)
return render_template('dashboard.html', files=userfiles, username=username, quota_gb=udb[[user['username'] for user in udb].index(username)]['quota_gb'], quota_usage=quota_usage_gb)
@app.route('/logout')
def logout():
session['loggedIn'] = False
session.pop('username', None)
flash('Successfully signed out!', 'info')
return redirect(url_for('upload'))
@app.route('/login', methods=['GET', 'POST'])
def login():
udb = udbload()
if request.method == 'GET':
return render_template('login.html')
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
for user in udb:
if user['username'] == username and user['password'] == password:
session['loggedIn'] = True
session['username'] = username
flash('Successfully signed in!', 'info')
return redirect(url_for('upload'))
flash('Invalid username or password!', 'error')
return redirect(url_for('login'))
@app.route('/register', methods=['GET', 'POST'])
def register():
udb = udbload()
if request.method == 'GET':
return render_template('register.html')
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
for user in udb:
if user['username'] == username:
flash('Username already taken!', 'error')
return redirect(url_for('register'))
udb.append({
'username': username,
'password': password,
'quota_gb': 3
})
udbsave(udb)
flash('Successfully registered! You can now sign in.', 'info')
return redirect(url_for('login'))
@app.route('/upload')
def upload():
loggedIn = session.get('loggedIn', False)
username = session.get('username', '')
quota_gb = None
quota_usage_gb = 0.0
try:
if loggedIn and username:
db = dbload()
udb = udbload()
# find user record in udb
user_rec = None
for u in udb:
if u.get('username') == username:
user_rec = u
break
if user_rec:
quota_gb = user_rec.get('quota_gb', 0)
else:
quota_gb = 0
userfiles = []
for file in db.get('files', {}):
if db['files'][file].get('owner') == username:
userfiles.append(db['files'][file])
for userfile in userfiles:
usf_mb = userfile.get('size_megabytes', 0)
if usf_mb:
quota_usage_gb += usf_mb / 1024
quota_usage_gb = round(quota_usage_gb, 1)
else:
# not logged in: treat as guest with 1GB manageable quota
quota_gb = 5.0
quota_usage_gb = 0.0
except Exception:
quota_gb = quota_gb or 0
quota_usage_gb = quota_usage_gb or 0.0
return render_template('upload.html', loggedIn=loggedIn, username=username, quota_gb=quota_gb, quota_usage=quota_usage_gb)
@app.route('/sendfile', methods=['POST'])
def sendfile():
db = dbload()
file = request.files['file']
reusable = request.form.get('reusable')
loggedIn = session.get('loggedIn', False)
username = session.get('username', '')
if file:
try:
extension = os.path.splitext(file.filename)[1].lower()
fileid = str(random.randint(100000, 99999999))
dest_name = secure_filename(file.filename) + f'_{fileid}'
dest_path = os.path.join(app.config['UPLOAD_DIRECTORY'], dest_name)
# save file first, then inspect size on disk
file.save(dest_path)
print(dest_name)
# get file size in bytes and convert to MB/GB
try:
size_bytes = os.path.getsize(dest_path)
except Exception:
size_bytes = 0
size_megabytes = round(size_bytes / (1024 * 1024), 1)
file_gb = size_megabytes / 1024
# determine user's remaining quota
remaining_gb = None
if loggedIn and username:
try:
udb = udbload()
# find user record
user_rec = None
for u in udb:
if u.get('username') == username:
user_rec = u
break
user_quota_gb = user_rec.get('quota_gb', 0) if user_rec else 0
# compute current usage
usage_gb = 0.0
for fkey, fval in db.get('files', {}).items():
if fval.get('owner') == username:
usf_mb = fval.get('size_megabytes', 0)
if usf_mb:
usage_gb += usf_mb / 1024
usage_gb = round(usage_gb, 1)
remaining_gb = max(0.0, user_quota_gb - usage_gb)
except Exception:
remaining_gb = 0.0
else:
# guests: they don't get owner set; treat remaining as 0 so owner won't be set
remaining_gb = 0.0
# build DB entry; only set owner if user is logged in AND file fits remaining quota
entry = {"reusable": True if reusable else False, "size_megabytes": size_megabytes}
if loggedIn and username and file_gb <= remaining_gb:
entry["owner"] = username
db["files"][dest_name] = entry
if reusable:
flash('Download code: ' + fileid, 'info')
else:
flash('1-Time Download code: ' + fileid, 'info')
dbsave(db)
return redirect(url_for('upload'))
except RequestEntityTooLarge:
return 'File is larger than the size limit.'
@app.route('/delete/<code>', methods=['GET', 'POST'])
def delete(code):
db = dbload()
for file in db['files']:
if file.split('_')[-1] == code:
if db['files'][file].get('owner') != session.get('username'):
flash('You do not own this file and cannot delete it.', 'error')
return redirect(url_for('upload'))
try:
os.remove('uploads/' + file)
except FileNotFoundError:
print('couldnt delete from os bc file not found.')
try:
os.remove('uploads/' + file.split('_')[0])
except FileNotFoundError:
print('couldnt delete from os bc file not found. (2)')
db['files'].pop(file)
dbsave(db)
flash('File with code ' + code + ' has been deleted.', 'info')
return redirect(url_for('dashboard'))
@app.route('/download')
def download_without_code():
flash('No code provided!', 'error')
return redirect(url_for('upload'))
@app.route('/download/<code>', methods=['GET', 'POST'])
def download(code):
db = dbload()
try:
found = False
filename = ''
files = os.listdir('uploads')
for file in files:
if file.split('_')[-1] == code:
found = True
filename = file
if not found:
print(filename)
# Search db for the entry with this code and remove it
for file_entry in list(db['files'].keys()):
if file_entry.split('_')[-1] == code:
print(f"Deleting: {file_entry}")
print
db['files'].pop(file_entry)
dbsave(db)
print(f"Deleted! DB now has {len(db['files'])} files")
break
flash('Invalid code! Check if you typed the correct code, and for one-time codes, make sure nobody else entered the code before you did.', 'error')
return redirect(url_for('upload'))
else:
os.rename('uploads/' + filename, 'uploads/' + filename.replace(f'_{code}', ''))
def deleteFile():
db['files'].pop(filename)
dbsave(db)
def backRename():
time.sleep(1)
os.rename('uploads/' + filename.replace(f'_{code}', ''), 'uploads/' + filename)
if db['files'][filename]['reusable']:
backToNameFunc = threading.Thread(target=backRename)
backToNameFunc.start()
if db['files'][filename]['reusable'] == False:
deleteFunc = threading.Thread(target=deleteFile)
deleteFunc.start()
return send_from_directory(app.config['UPLOAD_DIRECTORY'], filename.replace(f'_{code}', ''), as_attachment=True)
except Exception as e:
print(f"Error in download: {e}")
print(f"Type: {type(e)}")
import traceback
traceback.print_exc()
flash('Invalid code! Check if you typed the correct code, and for one-time codes, make sure nobody else entered the code before you did.', 'error')
return redirect(url_for('upload'))
app.run(debug=True, port=3133)

2
instructions.txt Normal file
View File

@@ -0,0 +1,2 @@
run 'screen' then hit enter 2 times
run 'sh run.sh'

2
run.sh Normal file
View File

@@ -0,0 +1,2 @@
echo "Running app in debug mode WARNING - disable debug mode for production"
python3 app.py

119
static/script.js Normal file
View File

@@ -0,0 +1,119 @@
if (localStorage.getItem("loaded")) {
document.getElementById("mirrorLogo").className = "fa-solid fa-circle-dot fa-2xs";
document.getElementById("mirrorText").textContent = "drive.fybe.dev";
document.getElementById("mirrorLogo").style.color = "#47cc00";
} else {
setTimeout(() => {
document.getElementById("mirrorLogo").style.color = "#47cc00";
document.getElementById("mirrorText").textContent = "drive.fybe.dev";
setTimeout(() => {
document.getElementById("mirrorLogo").className = "fa-solid fa-circle-dot fa-2xs";
localStorage.setItem("loaded", true);
}, 3000);
}, 3000);
}
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('uploadForm');
if (!form) return;
const fileInput = document.getElementById('fileUpload');
const progress = document.getElementById('uploadProgress');
const progressText = document.getElementById('uploadProgressText');
const uploadBtn = document.getElementById('uploadBtn');
form.addEventListener('submit', function(e) {
e.preventDefault();
if (!fileInput || !fileInput.files || fileInput.files.length === 0) {
return;
}
const file = fileInput.files[0];
const formData = new FormData();
formData.append('file', file);
// include reusable checkbox if present
const reusable = form.querySelector('input[name="reusable"]');
if (reusable && reusable.checked) formData.append('reusable', 'on');
const xhr = new XMLHttpRequest();
xhr.open('POST', form.getAttribute('action'));
xhr.upload.addEventListener('progress', function(ev) {
if (ev.lengthComputable) {
const percent = Math.round((ev.loaded / ev.total) * 100);
if (progress) {
progress.style.display = 'block';
progress.value = percent;
}
if (progressText) {
progressText.style.display = 'block';
progressText.textContent = percent + '%';
}
if (uploadBtn) {
uploadBtn.classList.add('yellowBtn');
uploadBtn.value = '· · ·';
uploadBtn.disabled = true;
}
}
});
xhr.addEventListener('load', function() {
if (xhr.status >= 200 && xhr.status < 400) {
// Try to extract flash messages from the returned HTML and inject them
try {
const tmp = document.createElement('div');
tmp.innerHTML = xhr.responseText;
const newFlashes = tmp.querySelector('ul.flashes');
const oldFlashes = document.querySelector('ul.flashes');
if (newFlashes) {
if (oldFlashes) oldFlashes.replaceWith(newFlashes);
else document.body.insertBefore(newFlashes, document.body.firstChild);
} else {
// fallback to full reload if no flashes present
window.location.href = '/upload';
return;
}
} catch (e) {
window.location.href = '/upload';
return;
} finally {
if (uploadBtn) {
uploadBtn.disabled = false;
uploadBtn.classList.remove('yellowBtn');
uploadBtn.value = 'Upload';
}
if (progress) { progress.style.display = 'none'; progress.value = 0; }
if (progressText) { progressText.style.display = 'none'; progressText.textContent = '0%'; }
// clear file input and reusable checkbox after successful upload
if (fileInput) fileInput.value = '';
if (reusable && reusable.checked) reusable.checked = false;
}
} else {
// error
if (progressText) progressText.textContent = 'Upload failed';
if (uploadBtn) {
uploadBtn.disabled = false;
uploadBtn.classList.remove('yellowBtn');
uploadBtn.value = 'Upload';
}
if (fileInput) fileInput.value = '';
if (progress) { progress.style.display = 'none'; progress.value = 0; }
if (progressText) progressText.style.display = 'none';
}
});
xhr.addEventListener('error', function() {
if (progressText) progressText.textContent = 'Upload error';
if (uploadBtn) {
uploadBtn.disabled = false;
uploadBtn.classList.remove('yellowBtn');
uploadBtn.value = 'Upload';
}
if (fileInput) fileInput.value = '';
if (progress) { progress.style.display = 'none'; progress.value = 0; }
if (progressText) progressText.style.display = 'none';
});
xhr.send(formData);
});
});

336
static/styles.css Normal file
View File

@@ -0,0 +1,336 @@
:root {
--black: #000000;
--almost-black: #080708;
--blue: #3772FF;
--blue-light: #3787ff;
--red: #DF2935;
--yellow: #FDCA40;
--light: #E6E8E6;
--white: #FFFFFF;
}
* {
position: relative;
box-sizing: border-box;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 22px;
line-height: 1.5;
color: var(--white);
background: var(--black);
background-image: linear-gradient(to bottom right, var(--almost-black), var(--black));
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
body,html {
width: 100%; height: 100%;
}
.app {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 50%;
border: 1px solid rgb(40, 40, 40);
padding-bottom: 40px;
}
.form-section {
padding: 15px 0 0 15px;
width: 50%;
color: var(--light);
flex-shrink: 0;
}
@media screen and (max-width: 1200px) {
.app {
flex-direction: column;
}
.form-section {
padding-right: 15px;
padding-bottom: 15px;
width: 100%;
}
.images-section {
padding-top: 0;
width: 100%;
height: auto;
}
}
@media screen and (max-width: 800px) {
.images-section a {
padding-bottom: 60%;
width: calc(100% - 15px);
}
}
/* Forms */
form {
width: 100%;
flex-direction: column;
display: flex;
}
input[type='file'], input[type='text'], input[type='password'] {
margin: 0 0 12px;
padding: 10px 10px;
border-radius: 4px;
font-size: 22px;
color: var(--almost-black);
background: var(--white);
}
input[type='submit'],
button[type='button'],
.sign-in-button {
padding: 13px 20px;
font-size: 22px;
font-weight: bold;
color: var(--white);
border: none;
border-radius: 4px;
outline: none;
box-shadow: none;
cursor: pointer;
background: var(--blue);
background-image: linear-gradient(to bottom, var(--blue-light), var(--blue));
transition: box-shadow .2s ease;
margin-bottom: 30px;
}
input[type='submit']:hover,
button[type='button']:hover,
.sign-in-button:hover {
box-shadow: inset 0 -25px 25px var(--blue-light);
}
.flashes {
display: flex;
flex-direction: column;
gap: 15px;
}
.flashes li {
list-style-type: none;
margin: 0;
padding: 0;
border-radius: 5px;
padding: 10px;
font-size: 20px;
}
.message {
border: 1px solid gray;
}
.error {
border: 1px solid rgb(168, 50, 50);
}
.info {
border: 1px solid rgb(30, 199, 199);
}
.warning, .warn {
border: 1px solid rgb(232, 148, 1);
}
.container {
padding-left: 50px;
}
.yellowBtn {
padding: 13px 20px;
font-size: 22px;
font-weight: bold;
color: var(--white);
border: none;
border-radius: 4px;
outline: none;
box-shadow: none;
cursor: pointer;
background: var(yellow) !important;
background-image: linear-gradient(to bottom, var(--yellow), var(--yellow)) !important;
transition: box-shadow .2s ease;
margin-bottom: 30px;
}
.yellowBtn:hover {
box-shadow: inset 0 -25px 25px var(--yellow) !important;
}
.dashboard-container {
padding: 15px 15px 15px 15px;
width: 100%;
color: var(--light);
flex-shrink: 0;
}
/* Table Styling */
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
background: rgba(20, 20, 20, 0.8);
border: 1px solid rgb(60, 60, 60);
border-radius: 6px;
overflow: hidden;
font-size: 16px;
}
table th {
background: linear-gradient(to bottom, rgb(50, 50, 50), rgb(40, 40, 40));
padding: 14px 16px;
text-align: left;
font-weight: 600;
color: var(--white);
border-bottom: 2px solid rgb(80, 80, 80);
text-transform: uppercase;
letter-spacing: 0.5px;
font-size: 13px;
}
table td {
padding: 12px 16px;
border-bottom: 1px solid rgb(40, 40, 40);
color: var(--light);
}
table tr:last-child td {
border-bottom: none;
}
table tr:hover {
background: rgba(55, 114, 255, 0.08);
}
table a {
color: var(--blue);
text-decoration: none;
font-weight: 500;
transition: color 0.2s ease;
}
table a:hover {
color: var(--blue-light);
text-decoration: underline;
}
/* Styled select for forms */
.fancy-select,
select.fancy-select {
margin: 0 0 12px;
padding: 10px 44px 10px 12px;
border-radius: 4px;
font-size: 22px;
color: var(--almost-black);
background-color: var(--white);
border: 1px solid rgba(0,0,0,0.12);
background-image: linear-gradient(to bottom, #ffffff, #f7f7f7), url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'><path d='M7 10l5 5 5-5z' fill='%23666'/></svg>");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 18px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
cursor: pointer;
transition: box-shadow .12s ease, border-color .12s ease, transform .06s ease;
}
.fancy-select:focus,
select.fancy-select:focus {
outline: none;
border-color: var(--blue-light);
box-shadow: 0 0 0 4px rgba(55,114,255,0.09);
transform: translateY(-1px);
}
/* hide default arrow in IE */
select.fancy-select::-ms-expand {
display: none;
}
/* smaller devices: reduce font-size and padding */
@media (max-width: 520px) {
.fancy-select,
select.fancy-select {
font-size: 18px;
padding-right: 40px;
}
}
/* Quota donut styles */
.dashboard-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
flex-wrap: wrap;
margin-bottom: 10px;
}
.dashboard-meta {
flex: 1 1 260px;
min-width: 180px;
}
.quota-block {
margin: 8px 0 18px;
flex: 0 0 auto;
}
.quota-donut {
position: relative;
width: 110px;
height: 110px;
display: inline-block;
}
.quota-donut svg {
display: block;
transform: rotate(-90deg);
width: 100%;
height: 100%;
}
.donut-ring {
stroke: rgba(255,255,255,0.06);
stroke-linecap: round;
vector-effect: non-scaling-stroke;
shape-rendering: geometricPrecision;
}
.donut-fill {
stroke: var(--blue);
stroke-linecap: round;
vector-effect: non-scaling-stroke;
shape-rendering: geometricPrecision;
transition: stroke-dashoffset 600ms cubic-bezier(.22,.9,.3,1), stroke 200ms ease;
transform-origin: center;
}
.quota-center {
position: absolute;
left: 0; top: 0; right: 0; bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
pointer-events: none;
color: var(--white);
}
.quota-percent {
font-size: 18px;
font-weight: 900;
line-height: 1;
}
.quota-number {
font-size: 12px;
font-weight: 700;
color: var(--light);
}
.quota-label {
font-size: 11px;
color: gray;
}
@media (max-width: 520px) {
.quota-donut { width: 64px; height: 64px; }
.quota-number { font-size: 12px; }
.quota-percent { font-size: 14px; }
}

132
templates/dashboard.html Normal file
View File

@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ADrive Share</title>
<script src="https://kit.fontawesome.com/972393379b.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="app">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul class="flashes">
{% for category, message in messages %}
<!-- category: message, error, info, warning -->
<li class="{{ category }}"><strong>{{ message }}</strong></li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<div class="dashboard-container">
<div class="dashboard-top">
<div class="dashboard-meta">
<h2>Dashboard of {{username}}</h2>
<p style="color: gray; font-size: 15px; text-align: left;">
You are managing {{ quota_usage }}GB out of {{ quota_gb }}GB.
</p>
</div>
<!-- Quota donut visual -->
{% set r = 40 %}
{% set c = (2 * 3.141592653589793 * r) %}
{% if quota_gb and quota_gb > 0 %}
{% set used = quota_usage %}
{% set pct = (used / quota_gb) if quota_gb > 0 else 0 %}
{% else %}
{% set used = 0 %}
{% set pct = 0 %}
{% endif %}
{% if pct >= 0.9 %}
{% set stroke_color = '#DF2935' %}
{% elif pct >= 0.6 %}
{% set stroke_color = '#FDCA40' %}
{% else %}
{% set stroke_color = '#3772FF' %}
{% endif %}
<div class="quota-block">
<div class="quota-donut" role="img" aria-label="Quota usage">
<svg width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
<g transform="translate(50,50)">
<circle class="donut-ring" r="{{ r }}" cx="0" cy="0" fill="transparent" stroke-width="10" stroke-linecap="round"></circle>
<circle class="donut-fill" r="{{ r }}" cx="0" cy="0" fill="transparent" stroke-width="10" stroke-linecap="round"
stroke-dasharray="{{ c }}" stroke-dashoffset="{{ (c * (1 - pct)) }}" stroke="{{ stroke_color }}"></circle>
</g>
</svg>
<div class="quota-center">
<div class="quota-percent">{{ (pct * 100) | round }}%</div>
<div class="quota-number">{{ ('%.1f' % used) if (used is not none) else '0.0' }} / {{ ('%.1f' % quota_gb) if (quota_gb is not none) else '0.0' }} GB</div>
</div>
</div>
</div>
</div>
<table>
<tr>
<th>Filename</th>
<th>Code</th>
<th>Reusable</th>
<th>Download</th>
<th>Delete</th>
</tr>
{% for file in files %}
<tr>
<td>{{ file.file.rsplit('_', 1)[0] }}</td>
<td>{{ file.code }}</td>
<td>{{ 'Yes' if file.reusable else 'No' }}</td>
<td>
{% if file.reusable %}
<a href="/download/{{ file.code }}">Download</a>
{% else %}
<a href="/download/{{ file.code }}" style="color: rgb(255, 162, 0);">Download and Delete</a>
{% endif %}
</td>
<td>
{% if file.reusable %}
<a href="/delete/{{ file.code }}" style="color: red;">Delete</a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
<a href="/upload" class="sign-in-button" style="text-decoration: none; background: rgb(1, 60, 155)">Upload a New File</a>
<a href="/logout" class="sign-in-button" style="text-decoration: none; background: rgb(32, 0, 139)">Sign Out</a>
</div>
</div>
<script>
//document.getElementById("uploadBtn").addEventListener("click", uploadAnim);
function downloadClick(){
const codeEl = document.getElementById("codeInput");
const code = codeEl.value;
// clear input when clicked
codeEl.value = '';
const url = `/download/` + code;
location.href = url;
}
function uploadAnim(e) {
//if (document.getElementById("fileUpload").value == "") {
// document.getElementById("uploadBtn").classList.add("redBtn");
// document.getElementById("uploadBtn").value = "! ! !";
// e.preventDefault();
//} else {
// document.getElementById("uploadBtn").classList.add("yellowBtn");
// document.getElementById("uploadBtn").value = "· · ·";
//}
if (!document.getElementById("fileUpload").value == "") {
document.getElementById("uploadBtn").classList.add("yellowBtn");
document.getElementById("uploadBtn").value = "· · ·";
}
}
</script>
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>

74
templates/login.html Normal file
View File

@@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ADrive Share</title>
<script src="https://kit.fontawesome.com/972393379b.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="app">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul class="flashes">
{% for category, message in messages %}
<!-- category: message, error, info, warning -->
<li class="{{ category }}"><strong>{{ message }}</strong></li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<div class="form-section">
<h1>Sign In</h1>
<p style="color: gray; font-size: 15px; text-align: left;">
Enter your credentials to access your dashboard and manage your files.
</p>
<form id="loginForm" action="/login" method="post">
Username: <input type="text" name="username" id="username" placeholder="📋" required aria-required>
Passphrase: <input type="password" name="password" id="password" placeholder="🗝️" aria-required>
<input type="submit" value="Sign In" id="uploadBtn">
</form>
<a href="/register" class="sign-in-button" style="text-decoration: none; background: rgb(32, 0, 139)">Sign Up</a>
<a href="/upload" class="sign-in-button" style="text-decoration: none; background: rgb(1, 60, 155)">Guest</a>
<p style="color: gray; font-size: 11px; text-align: center;">Signing in is not required. You can at any time register and sign in to manage your existing codes for free.</p>
</div>
</div>
<script>
//document.getElementById("uploadBtn").addEventListener("click", uploadAnim);
function downloadClick(){
const codeEl = document.getElementById("codeInput");
const code = codeEl.value;
// clear input when clicked
codeEl.value = '';
const url = `/download/` + code;
location.href = url;
}
function uploadAnim(e) {
//if (document.getElementById("fileUpload").value == "") {
// document.getElementById("uploadBtn").classList.add("redBtn");
// document.getElementById("uploadBtn").value = "! ! !";
// e.preventDefault();
//} else {
// document.getElementById("uploadBtn").classList.add("yellowBtn");
// document.getElementById("uploadBtn").value = "· · ·";
//}
if (!document.getElementById("fileUpload").value == "") {
document.getElementById("uploadBtn").classList.add("yellowBtn");
document.getElementById("uploadBtn").value = "· · ·";
}
}
</script>
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>

76
templates/register.html Normal file
View File

@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ADrive Share</title>
<script src="https://kit.fontawesome.com/972393379b.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="app">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul class="flashes">
{% for category, message in messages %}
<!-- category: message, error, info, warning -->
<li class="{{ category }}"><strong>{{ message }}</strong></li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<div class="form-section">
<h1>Sign Up </h1>
<p style="color: gray; font-size: 15px; text-align: left;">
With an account, you can manage up to 3GB in one dashboard. If you use up your limit, you can delete to free up space or upload files without the ability to manage them.
</p>
<form id="loginForm" action="/register" method="post">
Username: <input type="text" name="username" id="username" placeholder="📋" required aria-required>
Passphrase: <input type="password" name="password" id="password" placeholder="🗝️" aria-required>
Managable Storage Quota: <select name="quota_files" id="quota_files" class="fancy-select" disabled aria-disabled>
<option value="3GB">3GB</option>
</select>
<input type="submit" class="sign-in-button" style="background: rgb(32, 0, 139)" value="Sign Up" id="uploadBtn">
</form>
<a class="sign-in-button" href="/login" style="background: rgb(0, 95, 139); text-decoration: none;" id="signininstead">Sign In Instead</a>
<a href="/upload" class="sign-in-button" style="text-decoration: none; background: rgb(1, 60, 155)">Guest</a>
<p style="color: gray; font-size: 11px; text-align: center;">Signing in is not required. You can at any time register and sign in to manage your existing codes for free.</p>
</div>
</div>
<script>
//document.getElementById("uploadBtn").addEventListener("click", uploadAnim);
function downloadClick(){
const codeEl = document.getElementById("codeInput");
const code = codeEl.value;
// clear input when clicked
codeEl.value = '';
const url = `/download/` + code;
location.href = url;
}
function uploadAnim(e) {
//if (document.getElementById("fileUpload").value == "") {
// document.getElementById("uploadBtn").classList.add("redBtn");
// document.getElementById("uploadBtn").value = "! ! !";
// e.preventDefault();
//} else {
// document.getElementById("uploadBtn").classList.add("yellowBtn");
// document.getElementById("uploadBtn").value = "· · ·";
//}
if (!document.getElementById("fileUpload").value == "") {
document.getElementById("uploadBtn").classList.add("yellowBtn");
document.getElementById("uploadBtn").value = "· · ·";
}
}
</script>
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>

152
templates/upload.html Normal file
View File

@@ -0,0 +1,152 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ADrive Share</title>
<script src="https://kit.fontawesome.com/972393379b.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="app">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul class="flashes">
{% for category, message in messages %}
<!-- category: message, error, info, warning -->
<li class="{{ category }}"><strong>{{ message }}</strong></li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<div class="form-section">
<form id="uploadForm" action="/sendfile" method="post" enctype="multipart/form-data">
{% if loggedIn %}
<h4>You are currently logged in as {{ username }}.</h4>
{% else %}
<h4>You are currently not logged in.</h4>
{% endif %}
<span style="font-size: 20px;">
<i class="fa-solid fa-server fa-xs" style="line-height: 25px; font-size: 15px; display: inline-block;"></i>
<span id="mirrorText">Finding Nearby Mirror</span> <i class="fa-solid fa-circle-dot fa-beat-fade fa-2xs" style="color: #ff9500;" id="mirrorLogo"></i>
</span>
<span style="font-size: 20px;">
<i class="fa-solid fa-server fa-xs" style="line-height: 25px; font-size: 15px; display: inline-block;"></i>
Location Not Found <i class="fa-solid fa-circle-dot fa-2xs" style="color: #c10101;"></i>
</span>
<input type="file" name="file" id="fileUpload" required aria-required>
<input type="hidden" id="quotaGB" value="{{ quota_gb }}">
<input type="hidden" id="quotaUsedGB" value="{{ quota_usage }}">
<progress id="uploadProgress" value="0" max="100" style="display:none;width:100%;margin-top:8px"></progress>
<div id="uploadProgressText" style="display:none;font-size:13px;margin-top:4px">0%</div>
<div style="display: flex; flex-direction: row;">
<input type="checkbox" name="reusable" style="display: inline-block;">
<span>Reusable?</span>
</div>
<input type="submit" value="Upload" id="uploadBtn" onclick="uploadAnim()" style="transition-duration: 400ms;">
</form>
<form>
<input type="text" id="codeInput">
<button type="button" value="Download" onclick="downloadClick()">Download</button>
</form>
{% if not loggedIn %}
<a href="/login" class="sign-in-button" style="text-decoration: none; background: rgb(0, 81, 139)">Sign In</a>
<a href="/register" class="sign-in-button" style="text-decoration: none; background: rgb(32, 0, 139)">Register</a><br>
<p style="color: gray; font-size: 11px; text-align: center;">Signing in is not required. You can at any time register and sign in to manage your existing codes for free. You can manage up to 1GB of files.</p>
{% else %}
<a href="/dashboard" class="sign-in-button" style="text-decoration: none; background: blue">View Dashboard</a>
<a href="/logout" class="sign-in-button" style="text-decoration: none; background: rgb(79, 92, 178)">Sign Out</a>
{% endif %}
</div>
</div>
<script>
//document.getElementById("uploadBtn").addEventListener("click", uploadAnim);
function downloadClick(){
const codeEl = document.getElementById("codeInput");
const code = codeEl.value;
// clear input when clicked
codeEl.value = '';
const url = `/download/` + code;
location.href = url;
}
function uploadAnim(e) {
//if (document.getElementById("fileUpload").value == "") {
// document.getElementById("uploadBtn").classList.add("redBtn");
// document.getElementById("uploadBtn").value = "! ! !";
// e.preventDefault();
//} else {
// document.getElementById("uploadBtn").classList.add("yellowBtn");
// document.getElementById("uploadBtn").value = "· · ·";
//}
if (!document.getElementById("fileUpload").value == "") {
document.getElementById("uploadBtn").classList.add("yellowBtn");
document.getElementById("uploadBtn").value = "· · ·";
}
}
</script>
<script>
// Client-side quota check before uploading large files
(function(){
const fileInput = document.getElementById('fileUpload');
const uploadBtn = document.getElementById('uploadBtn');
const quotaGB = parseFloat(document.getElementById('quotaGB').value) || 0;
const usedGB = parseFloat(document.getElementById('quotaUsedGB').value) || 0;
const remainingGB = Math.max(0, quotaGB - usedGB);
function ensureFlashesContainer(){
let flashes = document.querySelector('.flashes');
if (!flashes){
flashes = document.createElement('ul');
flashes.className = 'flashes';
const app = document.querySelector('.app');
if (app) app.insertBefore(flashes, app.firstChild);
}
return flashes;
}
function clearClientFlash(){
const flashes = document.querySelectorAll('.flashes li.client-quota-warning');
flashes.forEach(n => n.remove());
}
function showClientFlash(message){
clearClientFlash();
const flashes = ensureFlashesContainer();
const li = document.createElement('li');
li.className = 'warning client-quota-warning';
const strong = document.createElement('strong');
strong.textContent = message;
li.appendChild(strong);
flashes.prepend(li);
}
if (fileInput){
fileInput.addEventListener('change', function(e){
clearClientFlash();
uploadBtn.disabled = false;
const f = fileInput.files && fileInput.files[0];
if (!f) return;
const fileGB = f.size / (1024 * 1024 * 1024);
if (fileGB > remainingGB){
showClientFlash('You do not have sufficient quota to manage this file. If you choose to upload this file, you will not be able to manage it through your dashboard.');
// still allow upload; server will accept but will not set owner when quota insufficient
} else {
clearClientFlash();
}
});
}
})();
</script>
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>

5
test.py Normal file
View File

@@ -0,0 +1,5 @@
from utils import *
db = dbload()
db['files'].pop("I_Love_You_-_Boy_Next_Door_1.mp3_45475777")
dbsave(db)

21
utils.py Normal file
View File

@@ -0,0 +1,21 @@
from flask import redirect as fredirect
import json
def redirect(url):
return fredirect(url)
def dbload():
with open('db.json', 'r') as f:
return json.load(f)
def dbsave(obj):
with open('db.json', 'w') as f:
json.dump(obj, f)
def udbload():
with open('users.json', 'r') as f:
return json.load(f)
def udbsave(obj):
with open('users.json', 'w') as f:
json.dump(obj, f)