mirror of
https://github.com/onionshare/onionshare.git
synced 2025-02-25 17:01:21 -05:00
support for multiple files and folders (#66)
This commit is contained in:
parent
e34a88b112
commit
c5ced60f8b
@ -1,4 +1,4 @@
|
|||||||
import os, inspect, hashlib, base64, hmac, platform
|
import os, inspect, hashlib, base64, hmac, platform, zipfile
|
||||||
from itertools import izip
|
from itertools import izip
|
||||||
|
|
||||||
def get_platform():
|
def get_platform():
|
||||||
@ -30,10 +30,13 @@ def constant_time_compare(val1, val2):
|
|||||||
result |= x ^ y
|
result |= x ^ y
|
||||||
return result == 0
|
return result == 0
|
||||||
|
|
||||||
def random_string(num_bytes):
|
def random_string(num_bytes, output_len=None):
|
||||||
b = os.urandom(num_bytes)
|
b = os.urandom(num_bytes)
|
||||||
h = hashlib.sha256(b).digest()[:16]
|
h = hashlib.sha256(b).digest()[:16]
|
||||||
return base64.b32encode(h).lower().replace('=','')
|
s = base64.b32encode(h).lower().replace('=','')
|
||||||
|
if not output_len:
|
||||||
|
return s
|
||||||
|
return s[:output_len]
|
||||||
|
|
||||||
def human_readable_filesize(b):
|
def human_readable_filesize(b):
|
||||||
thresh = 1024.0
|
thresh = 1024.0
|
||||||
@ -50,17 +53,46 @@ def human_readable_filesize(b):
|
|||||||
def is_root():
|
def is_root():
|
||||||
return os.geteuid() == 0
|
return os.geteuid() == 0
|
||||||
|
|
||||||
def file_crunching(filename):
|
def dir_size(start_path):
|
||||||
# calculate filehash, file size
|
total_size = 0
|
||||||
BLOCKSIZE = 65536
|
for dirpath, dirnames, filenames in os.walk(start_path):
|
||||||
hasher = hashlib.sha1()
|
for f in filenames:
|
||||||
with open(filename, 'rb') as f:
|
fp = os.path.join(dirpath, f)
|
||||||
buf = f.read(BLOCKSIZE)
|
if not os.path.islink(fp):
|
||||||
while len(buf) > 0:
|
total_size += os.path.getsize(fp)
|
||||||
hasher.update(buf)
|
return total_size
|
||||||
buf = f.read(BLOCKSIZE)
|
|
||||||
filehash = hasher.hexdigest()
|
|
||||||
filesize = os.path.getsize(filename)
|
|
||||||
return filehash, filesize
|
|
||||||
|
|
||||||
|
def get_tmp_dir():
|
||||||
|
if get_platform() == "Windows":
|
||||||
|
if 'Temp' in os.environ:
|
||||||
|
temp = os.environ['Temp'].replace('\\', '/')
|
||||||
|
else:
|
||||||
|
temp = 'C:/tmp'
|
||||||
|
else:
|
||||||
|
temp = '/tmp'
|
||||||
|
return temp
|
||||||
|
|
||||||
|
class ZipWriter(object):
|
||||||
|
def __init__(self, zip_filename=None):
|
||||||
|
if zip_filename:
|
||||||
|
self.zip_filename = zip_filename
|
||||||
|
else:
|
||||||
|
self.zip_filename = '{0}/onionshare_{1}.zip'.format(get_tmp_dir(), random_string(4, 6))
|
||||||
|
|
||||||
|
self.z = zipfile.ZipFile(self.zip_filename, 'w')
|
||||||
|
|
||||||
|
def add_file(self, filename):
|
||||||
|
self.z.write(filename, os.path.basename(filename), zipfile.ZIP_DEFLATED)
|
||||||
|
|
||||||
|
def add_dir(self, filename):
|
||||||
|
dir_to_strip = os.path.dirname(filename)+'/'
|
||||||
|
for dirpath, dirnames, filenames in os.walk(filename):
|
||||||
|
for f in filenames:
|
||||||
|
full_filename = os.path.join(dirpath, f)
|
||||||
|
if not os.path.islink(full_filename):
|
||||||
|
arc_filename = full_filename[len(dir_to_strip):]
|
||||||
|
self.z.write(full_filename, arc_filename, zipfile.ZIP_DEFLATED)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.z.close()
|
||||||
|
|
||||||
|
@ -1,76 +1,103 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>OnionShare</title>
|
<title>OnionShare</title>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
body {
|
body {
|
||||||
background-color: #222222;
|
background-color: #222222;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: arial;
|
font-family: sans-serif;
|
||||||
padding: 5em 1em;
|
padding: 5em 1em;
|
||||||
}
|
}
|
||||||
.metadata {
|
.button {
|
||||||
position: absolute;
|
-moz-box-shadow:inset 0px 1px 0px 0px #cae3fc;
|
||||||
bottom: 0;
|
-webkit-box-shadow:inset 0px 1px 0px 0px #cae3fc;
|
||||||
color: #999999;
|
box-shadow:inset 0px 1px 0px 0px #cae3fc;
|
||||||
text-align: left;
|
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #79bbff), color-stop(1, #4197ee) );
|
||||||
}
|
background:-moz-linear-gradient( center top, #79bbff 5%, #4197ee 100% );
|
||||||
.button {
|
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#79bbff', endColorstr='#4197ee');
|
||||||
-moz-box-shadow:inset 0px 1px 0px 0px #cae3fc;
|
background-color:#79bbff;
|
||||||
-webkit-box-shadow:inset 0px 1px 0px 0px #cae3fc;
|
-webkit-border-top-left-radius:12px;
|
||||||
box-shadow:inset 0px 1px 0px 0px #cae3fc;
|
-moz-border-radius-topleft:12px;
|
||||||
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #79bbff), color-stop(1, #4197ee) );
|
border-top-left-radius:12px;
|
||||||
background:-moz-linear-gradient( center top, #79bbff 5%, #4197ee 100% );
|
-webkit-border-top-right-radius:12px;
|
||||||
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#79bbff', endColorstr='#4197ee');
|
-moz-border-radius-topright:12px;
|
||||||
background-color:#79bbff;
|
border-top-right-radius:12px;
|
||||||
-webkit-border-top-left-radius:12px;
|
-webkit-border-bottom-right-radius:12px;
|
||||||
-moz-border-radius-topleft:12px;
|
-moz-border-radius-bottomright:12px;
|
||||||
border-top-left-radius:12px;
|
border-bottom-right-radius:12px;
|
||||||
-webkit-border-top-right-radius:12px;
|
-webkit-border-bottom-left-radius:12px;
|
||||||
-moz-border-radius-topright:12px;
|
-moz-border-radius-bottomleft:12px;
|
||||||
border-top-right-radius:12px;
|
border-bottom-left-radius:12px;
|
||||||
-webkit-border-bottom-right-radius:12px;
|
text-indent:0;
|
||||||
-moz-border-radius-bottomright:12px;
|
border:1px solid #469df5;
|
||||||
border-bottom-right-radius:12px;
|
display:inline-block;
|
||||||
-webkit-border-bottom-left-radius:12px;
|
color:#ffffff;
|
||||||
-moz-border-radius-bottomleft:12px;
|
font-size:29px;
|
||||||
border-bottom-left-radius:12px;
|
font-weight:bold;
|
||||||
text-indent:0;
|
font-style:normal;
|
||||||
border:1px solid #469df5;
|
height:50px;
|
||||||
display:inline-block;
|
line-height:50px;
|
||||||
color:#ffffff;
|
text-decoration:none;
|
||||||
font-family:Arial;
|
text-align:center;
|
||||||
font-size:29px;
|
text-shadow:1px 1px 0px #287ace;
|
||||||
font-weight:bold;
|
padding: 0 20px;
|
||||||
font-style:normal;
|
}
|
||||||
height:50px;
|
.button:hover {
|
||||||
line-height:50px;
|
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #4197ee), color-stop(1, #79bbff) );
|
||||||
text-decoration:none;
|
background:-moz-linear-gradient( center top, #4197ee 5%, #79bbff 100% );
|
||||||
text-align:center;
|
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#4197ee', endColorstr='#79bbff');
|
||||||
text-shadow:1px 1px 0px #287ace;
|
background-color:#4197ee;
|
||||||
padding: 0 20px;
|
}.button:active {
|
||||||
}
|
position:relative;
|
||||||
.button:hover {
|
top:1px;
|
||||||
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #4197ee), color-stop(1, #79bbff) );
|
}
|
||||||
background:-moz-linear-gradient( center top, #4197ee 5%, #79bbff 100% );
|
|
||||||
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#4197ee', endColorstr='#79bbff');
|
|
||||||
background-color:#4197ee;
|
|
||||||
}.button:active {
|
|
||||||
position:relative;
|
|
||||||
top:1px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<meta name="onionshare-filename" content="{{ filename }}">
|
|
||||||
<meta name="onionshare-filesize" content="{{ filesize }}">
|
|
||||||
<meta name="onionshare-filehash" content="{{ filehash }}">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<p><a class="button" href='/{{ slug }}/download'>{{ filename }} ▼</a></p>
|
|
||||||
|
|
||||||
<div class="metadata">
|
.download-size {
|
||||||
<p>{{strings.filesize}}: <strong title="{{ filesize }} bytes">{{ filesize_human }}</strong></p>
|
color: #999999;
|
||||||
<p>{{strings.sha1_checksum}}: <strong>{{ filehash }}</strong></p>
|
}
|
||||||
</div>
|
|
||||||
</body>
|
.file-list {
|
||||||
|
margin: 50px auto 0 auto;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: left;
|
||||||
|
background-color: #333333;
|
||||||
|
}
|
||||||
|
.file-list th {
|
||||||
|
padding: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.file-list td {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<meta name="onionshare-filename" content="{{ filename }}">
|
||||||
|
<meta name="onionshare-filesize" content="{{ filesize }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p><a class="button" href='/{{ slug }}/download'>{{ filename }} ▼</a></p>
|
||||||
|
<p class="download-size">{{strings.download_size}}: <strong title="{{ filesize }} bytes">{{ filesize_human }}</strong></p>
|
||||||
|
<table class="file-list">
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>{{strings.filename}}</th>
|
||||||
|
<th>{{strings.size}}</th>
|
||||||
|
</tr>
|
||||||
|
{% for info in file_info.dirs %}
|
||||||
|
<tr>
|
||||||
|
<td><img width="30" height="30" title="" alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3ggbFCQXOpwjbQAAAM5JREFUSMftlksOwyAMRDFCbNjkDL1kz9K75Cq9AZF3kYIM7oqoP/KpcFK1mRUrnsbW2Fbq0N+JiPhZRMS1OZAfzLzocwCAGmC9V2Vhjduarj8CzynGqIwxsDl4SXV267E4uFTN33X8deCxDyV1XXeRYJo1UUDEa9M0pxoRK+b4HiqRcz3nVAJKRK+TSxqaGbrkNKUkBn0oNSK2+T0MA1dau9NzO4QwuvPen1lAxQtEsq/vNpSWhvZ9v/3IRMTWOQezkyuEoKy14tfHoU11A6Mr5AxrpuMVAAAAAElFTkSuQmCC" /></td>
|
||||||
|
<td>{{ info.basename }}</td>
|
||||||
|
<td>{{ info.size_human }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% for info in file_info.files %}
|
||||||
|
<tr>
|
||||||
|
<td><img width="30" height="30" title="" alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3ggbFCQqYvRvfAAAAJFJREFUSMftlUkOwCAIAEWJ//+uIUivtYmtG7VN8KYHRhlA5zYtuB6klCTG2BcEAHrBeN4ws3jvX3lxQZmFiogMgVesVrhKXlvgakKf4KqVdAdXL+Ea3O8aILh0GnUMEhjtw9mLbUv1tx33fgItynBVoN+k2hybY3Nsjg08Bs45q4GYuQ4OIQARLYcSkUPEoiUP4j5IFasKOnUAAAAASUVORK5CYII=" /></td>
|
||||||
|
<td>{{ info.basename }}</td>
|
||||||
|
<td>{{ info.size_human }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<table>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -21,16 +21,19 @@ class OnionShare(object):
|
|||||||
# automatically close when download is finished
|
# automatically close when download is finished
|
||||||
self.stay_open = stay_open
|
self.stay_open = stay_open
|
||||||
|
|
||||||
# list of hidden service dirs to cleanup
|
# files and dirs to delete on shutdown
|
||||||
self.hidserv_dirs = []
|
self.cleanup_filenames = []
|
||||||
|
|
||||||
# choose a random port
|
# choose a random port
|
||||||
self.choose_port()
|
self.choose_port()
|
||||||
self.local_host = "127.0.0.1:{0}".format(self.port)
|
self.local_host = "127.0.0.1:{0}".format(self.port)
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
for d in self.hidserv_dirs:
|
for filename in self.cleanup_filenames:
|
||||||
shutil.rmtree(d)
|
if os.path.isfile(filename):
|
||||||
|
os.remove(filename)
|
||||||
|
elif os.path.isdir(filename):
|
||||||
|
shutil.rmtree(filename)
|
||||||
|
|
||||||
def choose_port(self):
|
def choose_port(self):
|
||||||
# let the OS choose a port
|
# let the OS choose a port
|
||||||
@ -65,17 +68,8 @@ class OnionShare(object):
|
|||||||
print strings._("connecting_ctrlport").format(self.port)
|
print strings._("connecting_ctrlport").format(self.port)
|
||||||
|
|
||||||
# come up with a hidden service directory name
|
# come up with a hidden service directory name
|
||||||
hidserv_dir_rand = helpers.random_string(8)
|
hidserv_dir = '{0}/onionshare_{1}'.format(helpers.get_tmp_dir(), helpers.random_string(8))
|
||||||
if helpers.get_platform() == "Windows":
|
self.cleanup_filenames.append(hidserv_dir)
|
||||||
if 'Temp' in os.environ:
|
|
||||||
temp = os.environ['Temp'].replace('\\', '/')
|
|
||||||
else:
|
|
||||||
temp = 'C:/tmp'
|
|
||||||
hidserv_dir = "{0}/onionshare_{1}".format(temp, hidserv_dir_rand)
|
|
||||||
else:
|
|
||||||
hidserv_dir = "/tmp/onionshare_{0}".format(hidserv_dir_rand)
|
|
||||||
|
|
||||||
self.hidserv_dirs.append(hidserv_dir)
|
|
||||||
|
|
||||||
# connect to the tor controlport
|
# connect to the tor controlport
|
||||||
controlports = [9051, 9151]
|
controlports = [9051, 9151]
|
||||||
@ -141,17 +135,25 @@ def main():
|
|||||||
parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only"))
|
parser.add_argument('--local-only', action='store_true', dest='local_only', help=strings._("help_local_only"))
|
||||||
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open"))
|
parser.add_argument('--stay-open', action='store_true', dest='stay_open', help=strings._("help_stay_open"))
|
||||||
parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug"))
|
parser.add_argument('--debug', action='store_true', dest='debug', help=strings._("help_debug"))
|
||||||
parser.add_argument('filename', nargs=1, help=strings._("help_filename"))
|
parser.add_argument('filename', metavar='filename', nargs='+', help=strings._('help_filename'))
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
filename = os.path.abspath(args.filename[0])
|
filenames = args.filename
|
||||||
|
for i in range(len(filenames)):
|
||||||
|
filenames[i] = os.path.abspath(filenames[i])
|
||||||
|
|
||||||
local_only = bool(args.local_only)
|
local_only = bool(args.local_only)
|
||||||
debug = bool(args.debug)
|
debug = bool(args.debug)
|
||||||
stay_open = bool(args.stay_open)
|
stay_open = bool(args.stay_open)
|
||||||
|
|
||||||
if not (filename and os.path.isfile(filename)):
|
# validation
|
||||||
sys.exit(strings._("not_a_file").format(filename))
|
valid = True
|
||||||
filename = os.path.abspath(filename)
|
for filename in filenames:
|
||||||
|
if not os.path.exists(filename):
|
||||||
|
print(strings._("not_a_file").format(filename))
|
||||||
|
valid = False
|
||||||
|
if not valid:
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
# start the onionshare app
|
# start the onionshare app
|
||||||
try:
|
try:
|
||||||
@ -163,10 +165,9 @@ def main():
|
|||||||
sys.exit(e.args[0])
|
sys.exit(e.args[0])
|
||||||
|
|
||||||
# startup
|
# startup
|
||||||
print strings._("calculating_sha1")
|
web.set_file_info(filenames)
|
||||||
filehash, filesize = helpers.file_crunching(filename)
|
app.cleanup_filenames.append(web.zip_filename)
|
||||||
web.set_file_info(filename, filehash, filesize)
|
print strings._("give_this_url")
|
||||||
print '\n' + strings._("give_this_url")
|
|
||||||
print 'http://{0}/{1}'.format(app.onion_host, web.slug)
|
print 'http://{0}/{1}'.format(app.onion_host, web.slug)
|
||||||
print ''
|
print ''
|
||||||
print strings._("ctrlc_to_stop")
|
print strings._("ctrlc_to_stop")
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
"give_this_url": "Give this URL to the person you're sending the file to:",
|
"give_this_url": "Give this URL to the person you're sending the file to:",
|
||||||
"ctrlc_to_stop": "Press Ctrl-C to stop server",
|
"ctrlc_to_stop": "Press Ctrl-C to stop server",
|
||||||
"not_a_file": "{0} is not a file.",
|
"not_a_file": "{0} is not a file.",
|
||||||
"filesize": "File size",
|
"download_size": "Download size",
|
||||||
|
"filename": "Filename",
|
||||||
|
"size": "Size",
|
||||||
"sha1_checksum": "SHA1 checksum",
|
"sha1_checksum": "SHA1 checksum",
|
||||||
"copied_url": "Copied URL to clipboard",
|
"copied_url": "Copied URL to clipboard",
|
||||||
"download_page_loaded": "Download page loaded",
|
"download_page_loaded": "Download page loaded",
|
||||||
@ -24,7 +26,7 @@
|
|||||||
"help_local_only": "Do not attempt to use tor: for development only",
|
"help_local_only": "Do not attempt to use tor: for development only",
|
||||||
"help_stay_open": "Keep hidden service running after download has finished",
|
"help_stay_open": "Keep hidden service running after download has finished",
|
||||||
"help_debug": "Log errors to disk",
|
"help_debug": "Log errors to disk",
|
||||||
"help_filename": "File to share"
|
"help_filename": "List of files or folders to share"
|
||||||
}, "no": {
|
}, "no": {
|
||||||
"calculating_sha1": "Kalkulerer SHA1 sjekksum.",
|
"calculating_sha1": "Kalkulerer SHA1 sjekksum.",
|
||||||
"connecting_ctrlport": "Kobler til Tors kontroll-port for å sette opp en gjemt tjeneste på port {0}.",
|
"connecting_ctrlport": "Kobler til Tors kontroll-port for å sette opp en gjemt tjeneste på port {0}.",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import Queue, mimetypes, platform, os, sys
|
import Queue, mimetypes, platform, os, sys, zipfile
|
||||||
from flask import Flask, Response, request, render_template_string, abort
|
from flask import Flask, Response, request, render_template_string, abort
|
||||||
|
|
||||||
import strings, helpers
|
import strings, helpers
|
||||||
@ -6,12 +6,37 @@ import strings, helpers
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
# information about the file
|
# information about the file
|
||||||
filename = filesize = filehash = None
|
file_info = []
|
||||||
def set_file_info(new_filename, new_filehash, new_filesize):
|
zip_filename = None
|
||||||
global filename, filehash, filesize
|
zip_filesize = None
|
||||||
filename = new_filename
|
def set_file_info(filenames):
|
||||||
filehash = new_filehash
|
global file_info, zip_filename, zip_filesize
|
||||||
filesize = new_filesize
|
|
||||||
|
# build file info list
|
||||||
|
file_info = {'files':[], 'dirs':[]}
|
||||||
|
for filename in filenames:
|
||||||
|
info = {
|
||||||
|
'filename': filename,
|
||||||
|
'basename': os.path.basename(filename)
|
||||||
|
}
|
||||||
|
if os.path.isfile(filename):
|
||||||
|
info['size'] = os.path.getsize(filename)
|
||||||
|
info['size_human'] = helpers.human_readable_filesize(info['size'])
|
||||||
|
file_info['files'].append(info)
|
||||||
|
if os.path.isdir(filename):
|
||||||
|
info['size'] = helpers.dir_size(filename)
|
||||||
|
info['size_human'] = helpers.human_readable_filesize(info['size'])
|
||||||
|
file_info['dirs'].append(info)
|
||||||
|
|
||||||
|
# zip up the files and folders
|
||||||
|
z = helpers.ZipWriter()
|
||||||
|
for info in file_info['files']:
|
||||||
|
z.add_file(info['filename'])
|
||||||
|
for info in file_info['dirs']:
|
||||||
|
z.add_dir(info['filename'])
|
||||||
|
z.close()
|
||||||
|
zip_filename = z.zip_filename
|
||||||
|
zip_filesize = os.path.getsize(zip_filename)
|
||||||
|
|
||||||
REQUEST_LOAD = 0
|
REQUEST_LOAD = 0
|
||||||
REQUEST_DOWNLOAD = 1
|
REQUEST_DOWNLOAD = 1
|
||||||
@ -58,10 +83,10 @@ def index(slug_candidate):
|
|||||||
return render_template_string(
|
return render_template_string(
|
||||||
open('{0}/index.html'.format(helpers.get_onionshare_dir())).read(),
|
open('{0}/index.html'.format(helpers.get_onionshare_dir())).read(),
|
||||||
slug=slug,
|
slug=slug,
|
||||||
filename=os.path.basename(filename).decode("utf-8"),
|
file_info=file_info,
|
||||||
filehash=filehash,
|
filename=os.path.basename(zip_filename).decode("utf-8"),
|
||||||
filesize=filesize,
|
filesize=zip_filesize,
|
||||||
filesize_human=helpers.human_readable_filesize(filesize),
|
filesize_human=helpers.human_readable_filesize(zip_filesize),
|
||||||
strings=strings.strings
|
strings=strings.strings
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -83,13 +108,13 @@ def download(slug_candidate):
|
|||||||
# tell GUI the download started
|
# tell GUI the download started
|
||||||
add_request(REQUEST_DOWNLOAD, path, { 'id':download_id })
|
add_request(REQUEST_DOWNLOAD, path, { 'id':download_id })
|
||||||
|
|
||||||
dirname = os.path.dirname(filename)
|
dirname = os.path.dirname(zip_filename)
|
||||||
basename = os.path.basename(filename)
|
basename = os.path.basename(zip_filename)
|
||||||
|
|
||||||
def generate():
|
def generate():
|
||||||
chunk_size = 102400 # 100kb
|
chunk_size = 102400 # 100kb
|
||||||
|
|
||||||
fp = open(filename, 'rb')
|
fp = open(zip_filename, 'rb')
|
||||||
done = False
|
done = False
|
||||||
while not done:
|
while not done:
|
||||||
chunk = fp.read(102400)
|
chunk = fp.read(102400)
|
||||||
@ -100,7 +125,7 @@ def download(slug_candidate):
|
|||||||
|
|
||||||
# tell GUI the progress
|
# tell GUI the progress
|
||||||
downloaded_bytes = fp.tell()
|
downloaded_bytes = fp.tell()
|
||||||
percent = round((1.0 * downloaded_bytes / filesize) * 100, 2);
|
percent = round((1.0 * downloaded_bytes / zip_filesize) * 100, 2);
|
||||||
sys.stdout.write("\r{0}, {1}% ".format(helpers.human_readable_filesize(downloaded_bytes), percent))
|
sys.stdout.write("\r{0}, {1}% ".format(helpers.human_readable_filesize(downloaded_bytes), percent))
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
add_request(REQUEST_PROGRESS, path, { 'id':download_id, 'bytes':downloaded_bytes })
|
add_request(REQUEST_PROGRESS, path, { 'id':download_id, 'bytes':downloaded_bytes })
|
||||||
@ -116,7 +141,7 @@ def download(slug_candidate):
|
|||||||
shutdown_func()
|
shutdown_func()
|
||||||
|
|
||||||
r = Response(generate())
|
r = Response(generate())
|
||||||
r.headers.add('Content-Length', filesize)
|
r.headers.add('Content-Length', zip_filesize)
|
||||||
r.headers.add('Content-Disposition', 'attachment', filename=basename)
|
r.headers.add('Content-Disposition', 'attachment', filename=basename)
|
||||||
# guess content type
|
# guess content type
|
||||||
(content_type, _) = mimetypes.guess_type(basename, strict=False)
|
(content_type, _) = mimetypes.guess_type(basename, strict=False)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user