zip download, and proper config
This commit is contained in:
82
code/app.py
82
code/app.py
@@ -1,23 +1,26 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import os,sys
|
import os,sys,time
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import Flask, render_template, jsonify, \
|
from flask import Flask, render_template, jsonify, \
|
||||||
redirect, url_for, request, g, session, send_from_directory
|
redirect, url_for, request, g, session, send_file, send_from_directory
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import zipfile
|
||||||
from revprox import ReverseProxied
|
from revprox import ReverseProxied
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config.from_object(__name__)
|
app.config.from_object(__name__)
|
||||||
# Read config from json !
|
# Read config from json !
|
||||||
app.config['UPLOAD_FOLDER'] = 'data/'
|
config_values = json.load(open(os.getenv('FLEES_CONFIG'),'rt'))
|
||||||
app.config['SHARES_FILE'] = 'data/shares.json'
|
app.config['UPLOAD_FOLDER'] = config_values['data_folder']
|
||||||
app.config['DATE_FORMAT'] = "%Y-%m-%d %H:%M"
|
app.config['SHARES_FILE'] = config_values['shares_file']
|
||||||
app.config['UID'] = 1000
|
app.config['ZIP_FOLDER'] = config_values['zip_folder']
|
||||||
app.config['DEBUG'] = False
|
app.config['DATE_FORMAT'] = config_values['date_format']
|
||||||
|
app.config['UID'] = config_values['uid']
|
||||||
|
app.config['DEBUG'] = config_values['debug']
|
||||||
|
|
||||||
app.secret_key = 'Cz2dw5NiRt3PSMFBSLTAJJi7U2CdW7iPQqEeOaU6'
|
app.secret_key = 'Cz2dw5NiRt3PSMFBSLTAJJi7U2CdW7iPQqEeOaU6'
|
||||||
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||||
@@ -151,6 +154,8 @@ def download_direct(name,password,filename):
|
|||||||
@app.route('/download/<name>/<password>/<filename>', methods=['GET'])
|
@app.route('/download/<name>/<password>/<filename>', methods=['GET'])
|
||||||
@app.route('/download/<name>/<filename>', methods=['GET'])
|
@app.route('/download/<name>/<filename>', methods=['GET'])
|
||||||
def download_file(name,filename,password = None):
|
def download_file(name,filename,password = None):
|
||||||
|
if password != None:
|
||||||
|
session[name] = password
|
||||||
(ok,share) = get_share(name)
|
(ok,share) = get_share(name)
|
||||||
if not ok:
|
if not ok:
|
||||||
return share
|
return share
|
||||||
@@ -159,14 +164,33 @@ def download_file(name,filename,password = None):
|
|||||||
return 'no such file', 404
|
return 'no such file', 404
|
||||||
return send_from_directory(directory=share['path'], filename=filename)
|
return send_from_directory(directory=share['path'], filename=filename)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/zip/<name>/<password>', methods=['GET'])
|
||||||
|
@app.route('/zip/<name>', methods=['GET'])
|
||||||
|
def download_zip(name,password = None):
|
||||||
|
if password != None:
|
||||||
|
session[name] = password
|
||||||
|
(ok,share) = get_share(name)
|
||||||
|
if not ok:
|
||||||
|
return share
|
||||||
|
zip_clean()
|
||||||
|
zip_path = zip_share(share)
|
||||||
|
return send_file(
|
||||||
|
zip_path,
|
||||||
|
as_attachment = True,
|
||||||
|
attachment_filename = name + ".zip"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def file_stat(filename):
|
def file_stat(filename):
|
||||||
s = os.stat(filename)
|
s = os.stat(filename)
|
||||||
return {
|
return {
|
||||||
'size': file_size_human(s.st_size),
|
'size': file_size_MB(s.st_size),
|
||||||
'mtime': file_date_human(s.st_mtime),
|
'mtime': file_date_human(s.st_mtime),
|
||||||
'name': os.path.basename(filename)
|
'name': os.path.basename(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def file_size_human(num):
|
def file_size_human(num):
|
||||||
for x in [' B','KB','MB','GB','TB']:
|
for x in [' B','KB','MB','GB','TB']:
|
||||||
if num < 1024.0:
|
if num < 1024.0:
|
||||||
@@ -175,6 +199,10 @@ def file_size_human(num):
|
|||||||
return "%3.1f %s" % (num, x)
|
return "%3.1f %s" % (num, x)
|
||||||
num /= 1024.0
|
num /= 1024.0
|
||||||
|
|
||||||
|
def file_size_MB(num):
|
||||||
|
return "%0.2f"%(num/(1024*1024),)
|
||||||
|
|
||||||
|
|
||||||
def file_date_human(num):
|
def file_date_human(num):
|
||||||
return datetime.fromtimestamp(
|
return datetime.fromtimestamp(
|
||||||
num
|
num
|
||||||
@@ -244,6 +272,44 @@ def print_debug(s):
|
|||||||
sys.stderr.write(str(s)+"\n")
|
sys.stderr.write(str(s)+"\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def zip_share(share):
|
||||||
|
|
||||||
|
if not os.path.exists(app.config['ZIP_FOLDER']):
|
||||||
|
os.makedirs(app.config['ZIP_FOLDER'])
|
||||||
|
os.chown(app.config['ZIP_FOLDER'], app.config['UID'], -1)
|
||||||
|
|
||||||
|
zip_path = os.path.join(
|
||||||
|
app.config['ZIP_FOLDER'],
|
||||||
|
"%s-%d.zip"%(
|
||||||
|
share['name'],
|
||||||
|
time.time()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
zf = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED)
|
||||||
|
for file in sorted(os.listdir(share['path'])):
|
||||||
|
print_debug(os.path.join(share['path'],file))
|
||||||
|
zf.write(
|
||||||
|
os.path.join(share['path'],file),
|
||||||
|
arcname = os.path.join(share['name'],file)
|
||||||
|
)
|
||||||
|
zf.close()
|
||||||
|
os.chown(zip_path, app.config['UID'], -1)
|
||||||
|
return zip_path
|
||||||
|
|
||||||
|
def zip_clean():
|
||||||
|
""" delete zip files older than 1 hour """
|
||||||
|
if not os.path.exists(app.config['ZIP_FOLDER']):
|
||||||
|
return
|
||||||
|
for file in os.listdir(app.config['ZIP_FOLDER']):
|
||||||
|
if not file.endswith("zip"):
|
||||||
|
continue
|
||||||
|
mtime = os.stat(
|
||||||
|
os.path.join(app.config['ZIP_FOLDER'],file)
|
||||||
|
).st_mtime
|
||||||
|
if mtime + 3600 < time.time():
|
||||||
|
os.remove(os.path.join(app.config['ZIP_FOLDER'],file))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
||||||
|
|
||||||
|
|||||||
@@ -49,8 +49,11 @@ tr:nth-child(even) {
|
|||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
#list_title {
|
#list_left {
|
||||||
float: left;
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#list_title {
|
||||||
margin-left: 5em;
|
margin-left: 5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,10 +62,21 @@ tr:nth-child(even) {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
border: 4px solid lightslategray;
|
border: 4px solid lightslategray;
|
||||||
background-color: #f3f3fb;
|
background-color: #f3f3fb;
|
||||||
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#list_menu ul {
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
#list_table {
|
#list_table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin-left: 2em;
|
margin-left: 2em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.direct {
|
||||||
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* index */
|
/* index */
|
||||||
@@ -77,6 +91,7 @@ tr:nth-child(even) {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
border: 4px solid lightslategray;
|
border: 4px solid lightslategray;
|
||||||
background-color: #f3f3fb;
|
background-color: #f3f3fb;
|
||||||
|
line-height: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#index_table {
|
#index_table {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/styles.css') }}">
|
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/styles.css') }}">
|
||||||
<script src="{{ url_for('static', filename='js/tablesort.js') }}" type="text/javascript"></script>
|
<script src="{{ url_for('static', filename='js/tablesort.js') }}" type="text/javascript"></script>
|
||||||
<script src="{{ url_for('static', filename='js/scripts.js') }}" type="text/javascript"></script>
|
<script src="{{ url_for('static', filename='js/scripts.js') }}" type="text/javascript"></script>
|
||||||
|
<meta name="viewport" content="width=800" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class=page>
|
<div class=page>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div id=list_title><h1>{{ name }}</h1></div>
|
|
||||||
<div id=list_menu>
|
<div id=list_menu>
|
||||||
{% if upload %}
|
{% if upload %}
|
||||||
<div id=list_upload>
|
<div id=list_upload>
|
||||||
@@ -9,9 +8,6 @@
|
|||||||
<input type=file name=file onchange="document.getElementById('list_upload_button').disabled = false;">
|
<input type=file name=file onchange="document.getElementById('list_upload_button').disabled = false;">
|
||||||
<input id="list_upload_button" type=submit value=Upload disabled>
|
<input id="list_upload_button" type=submit value=Upload disabled>
|
||||||
</form>
|
</form>
|
||||||
{% if overwrite == false %}
|
|
||||||
Overwriting disabled
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div id=list_upload>
|
<div id=list_upload>
|
||||||
@@ -19,44 +15,49 @@
|
|||||||
<input type=submit value=Upload disabled>
|
<input type=submit value=Upload disabled>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id=list_public>
|
<div id=list_info>
|
||||||
|
Share:
|
||||||
|
<ul>
|
||||||
{% if public %}
|
{% if public %}
|
||||||
Share is <a href="{{ url_for('index') }}">public</a>
|
<li>is <a href="{{ url_for('index') }}">public</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
Share is <a href="{{ url_for('index') }}">unlisted</a>
|
<li>is <a href="{{ url_for('index') }}">unlisted</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
<div id=list_expire>
|
|
||||||
{% if expire %}
|
{% if expire %}
|
||||||
Share expires {{ expire }}
|
<li>expires {{ expire }}
|
||||||
{% else %}
|
{% else %}
|
||||||
Share never expires
|
<li>never expires
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% if overwrite == false %}
|
||||||
<div id=list_logout>
|
<li>overwriting is disabled
|
||||||
<a href="{{ url_for('logout',name=name) }}">Logout</a> from this share
|
{% endif %}
|
||||||
|
<li><a href="{{ url_for('download_zip',name=name) }}">Download as zip</a>
|
||||||
|
<li><a href="{{ url_for('logout',name=name) }}">Logout</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class=clear></div>
|
<div id=list_left>
|
||||||
|
<div id=list_title><h1>{{ name }}</h1></div>
|
||||||
<table class="sortable" id="list_table">
|
<table class="sortable" id="list_table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name
|
<th>Name
|
||||||
<th class=td_right>Size
|
<th class=td_right>Size(MB)
|
||||||
<th>Mod.Time
|
<th>Mod.Time
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for entry in entries %}
|
{% for entry in entries %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ url_for('download_file', name = name, filename = entry.name) }}">{{ entry.name }}</a>
|
<td>
|
||||||
{% if direct %}
|
{% if direct %}
|
||||||
<a href="{{ url_for('download_direct', name = name, password = entry.token, filename = entry.name ) }}" title="Direct share link">❖</a>
|
<a href="{{ url_for('download_direct', name = name, password = entry.token, filename = entry.name ) }}" title="Direct share link" class=direct>❖</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<a href="{{ url_for('download_file', name = name, filename = entry.name) }}">{{ entry.name }}</a>
|
||||||
<td class=td_right>{{ entry.size|safe }}
|
<td class=td_right>{{ entry.size|safe }}
|
||||||
<td>{{ entry.mtime|safe }}
|
<td>{{ entry.mtime|safe }}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ services:
|
|||||||
- "${FLEES_EXPOSE}:80"
|
- "${FLEES_EXPOSE}:80"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/:/code/data/
|
- ./data/:/code/data/
|
||||||
|
environment:
|
||||||
|
- FLEES_CONFIG
|
||||||
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
FLEES_EXPOSE=127.0.0.1:8136
|
FLEES_EXPOSE=127.0.0.1:8136
|
||||||
|
FLEES_CONFIG=data/config.json
|
||||||
|
|||||||
Reference in New Issue
Block a user