split upload with moar features

This commit is contained in:
Ville Rantanen
2018-03-15 22:29:07 +02:00
parent 61d872fa17
commit acb366120d
5 changed files with 196 additions and 127 deletions

View File

@@ -133,8 +133,14 @@ def upload_join_splitted(name, token):
return "Upload not allowed",400 return "Upload not allowed",400
if not 'filename' in request.form: if not 'filename' in request.form:
return "No filename given", 400 return "No filename given", 400
if not 'parts' in request.form:
return "No parts count given", 400
try:
no_parts = int(request.form['parts'])
except:
return "Parts not parseable", 400
parts = [] parts = []
for part in range(500): for part in range(no_parts):
filename = os.path.join( filename = os.path.join(
share['path'], share['path'],
".%s.part.%03d"%( ".%s.part.%03d"%(
@@ -351,42 +357,11 @@ def script_upload(name = None, token = None):
return share return share
if not get_or_none('upload', share) == True: if not get_or_none('upload', share) == True:
return "Upload not allowed",400 return "Upload not allowed",400
return """#!/bin/bash return render_template(
test -n "$1" || { "upload.sh",
echo "Add files to upload as argument" name = name,
exit 1 token = token,
} rooturl = request.url_root
CAT=$( which cat )
which pv &> /dev/null && CAT=$( which pv )
ROOTURL="%s"
SHARE="%s"
TOKEN="%s"
send_file() {
$CAT "$file_name" | curl -F "file=@-;filename=${base_name}" ${ROOTURL}upload/${SHARE}/${TOKEN}
}
send_folder() {
which pv &> /dev/null && printf -v dusize -- "--size %%dk" $( du -s -k "$file_name" | cut -f1 )
tar cz "$file_name" | $CAT $dusize - | curl -F "file=@-;filename=${base_name}.tgz" ${ROOTURL}upload/${SHARE}/${TOKEN}
}
for file_name in "$@"; do
base_name=$( basename "$file_name" )
test -f "$file_name" && {
printf "Sending file: %%s\n" "$file_name"
send_file
continue
}
test -d "$file_name" && {
printf "Sending folder: %%s\n" "$file_name"
send_folder
continue
}
done
"""%(
request.url_root,
name,
token
) )
@@ -395,99 +370,40 @@ def script_download(name = None, token = None):
(ok,share) = get_share(name, token = token) (ok,share) = get_share(name, token = token)
if not ok: if not ok:
return share return share
files = [] commands = []
for file in iter_folder_files(share['path']): for file in iter_folder_files(share['path']):
status = file_stat(share['path'], file) status = file_stat(share['path'], file)
files.append(status) commands.append('get_file "%s"'%(
script = """#!/bin/bash status['url'],
test "$1" = "-h" && { ))
echo "Add argument -f to overwrite files" return render_template(
exit 0 "download.sh",
} name = name,
test "$1" = "-f" && FORCE=1 token = token,
which curl &> /dev/null || { rooturl = request.url_root,
echo "curl required" commands = "\n".join(commands)
exit 1
}
ROOTURL="%s"
SHARE="%s"
TOKEN="%s"
get_file() {
WRITE=0
FILENAME="$1"
test "$FORCE" = "1" && WRITE=1
test -f "${FILENAME}" || WRITE=1
test "$WRITE" = "1" && {
echo Downloading ${FILENAME}
mkdir -p $( dirname "$FILENAME" )
curl "${ROOTURL}download/${SHARE}/${TOKEN}/${FILENAME}" > "${FILENAME}"
} || {
echo Skipping ${FILENAME}
}
}
"""%(
request.url_root,
name,
token
) )
for file in files:
script += 'get_file "%s"\n'%(
file['url'],
)
return script
@app.route('/script/direct/<name>/<token>', methods=['GET']) @app.route('/script/direct/<name>/<token>', methods=['GET'])
def script_direct(name = None, token = None): def script_direct(name = None, token = None):
(ok,share) = get_share(name, token = token) (ok,share) = get_share(name, token = token)
if not ok: if not ok:
return share return share
files = [] commands = []
for file in iter_folder_files(share['path']): for file in iter_folder_files(share['path']):
status = file_stat(share['path'], file) status = file_stat(share['path'], file)
status.update({ commands.append('get_file "%s" "%s"'%(
'token': get_direct_token(share, file) status['url'],
}) get_direct_token(share, file)
files.append(status) ))
script = """#!/bin/bash return render_template(
test "$1" = "-h" && { "download_direct.sh",
echo "Add argument -f to overwrite files" name = name,
exit 0 rooturl = request.url_root,
} commands = "\n".join(commands)
test "$1" = "-f" && FORCE=1
which curl &> /dev/null || {
echo "curl required"
exit 1
}
ROOTURL="%s"
SHARE="%s"
get_file() {
WRITE=0
FILENAME="$1"
TOKEN="$2"
test "$FORCE" = "1" && WRITE=1
test -f "${FILENAME}" || WRITE=1
test "$WRITE" = "1" && {
echo Downloading ${FILENAME}
mkdir -p $( dirname "$FILENAME" )
curl "${ROOTURL}direct/${SHARE}/${TOKEN}/${FILENAME}" > "${FILENAME}"
} || {
echo Skipping ${FILENAME}
}
}
"""%(
request.url_root,
name,
) )
for file in files:
script += 'get_file "%s" "%s"\n'%(
file['url'],
file['token'],
)
return script
@app.route('/script/upload_split/<name>/<token>', methods=['GET']) @app.route('/script/upload_split/<name>/<token>', methods=['GET'])
def script_upload_split(name = None, token = None): def script_upload_split(name = None, token = None):

View File

@@ -0,0 +1,28 @@
#!/bin/bash
test "$1" = "-h" && {
echo "Add argument -f to overwrite files"
exit 0
}
test "$1" = "-f" && FORCE=1
which curl &> /dev/null || {
echo "curl required"
exit 1
}
ROOTURL="{{ rooturl }}"
SHARE="{{ name }}"
TOKEN="{{ token }}"
get_file() {
WRITE=0
FILENAME="$1"
test "$FORCE" = "1" && WRITE=1
test -f "${FILENAME}" || WRITE=1
test "$WRITE" = "1" && {
echo Downloading ${FILENAME}
mkdir -p $( dirname "$FILENAME" )
curl "${ROOTURL}download/${SHARE}/${TOKEN}/${FILENAME}" > "${FILENAME}"
} || {
echo Skipping ${FILENAME}
}
}
{{ commands }}

View File

@@ -0,0 +1,28 @@
#!/bin/bash
test "$1" = "-h" && {
echo "Add argument -f to overwrite files"
exit 0
}
test "$1" = "-f" && FORCE=1
which curl &> /dev/null || {
echo "curl required"
exit 1
}
ROOTURL="{{ rooturl }}"
SHARE="{{ name }}"
get_file() {
WRITE=0
FILENAME="$1"
TOKEN="$2"
test "$FORCE" = "1" && WRITE=1
test -f "${FILENAME}" || WRITE=1
test "$WRITE" = "1" && {
echo Downloading ${FILENAME}
mkdir -p $( dirname "$FILENAME" )
curl "${ROOTURL}direct/${SHARE}/${TOKEN}/${FILENAME}" > "${FILENAME}"
} || {
echo Skipping ${FILENAME}
}
}
{{ commands }}

32
code/templates/upload.sh Normal file
View File

@@ -0,0 +1,32 @@
#!/bin/bash
test -n "$1" || {
echo "Add files to upload as argument"
exit 1
}
CAT=$( which cat )
which pv &> /dev/null && CAT=$( which pv )
ROOTURL="{{ rooturl }}"
SHARE="{{ name }}"
TOKEN="{{ token }}"
send_file() {
$CAT "$file_name" | curl -F "file=@-;filename=${base_name}" ${ROOTURL}upload/${SHARE}/${TOKEN}
}
send_folder() {
which pv &> /dev/null && printf -v dusize -- "--size %dk" $( du -s -k "$file_name" | cut -f1 )
tar cz "$file_name" | $CAT $dusize - | curl -F "file=@-;filename=${base_name}.tgz" ${ROOTURL}upload/${SHARE}/${TOKEN}
}
for file_name in "$@"; do
base_name=$( basename "$file_name" )
test -f "$file_name" && {
printf "Sending file: %s\n" "$file_name"
send_file
continue
}
test -d "$file_name" && {
printf "Sending folder: %s\n" "$file_name"
send_folder
continue
}
done

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
import argparse,sys,os, subprocess import argparse, sys, os, subprocess, time
from subprocess import Popen, PIPE, STDOUT from subprocess import Popen, PIPE, STDOUT
from io import BytesIO from io import BytesIO
@@ -7,7 +7,64 @@ ROOTURL="{{ rooturl }}"
SHARE="{{ name }}" SHARE="{{ name }}"
TOKEN="{{ token }}" TOKEN="{{ token }}"
class ETA():
def __init__(self,total):
self.total = total
self.memory = 360 # seconds
self.counts = []
self.timestamps = []
self.started = time.time()
def update(self,count):
self.counts.append(count)
self.timestamps.append(time.time())
newest = self.timestamps[-1]
delete_us = []
for i in range(len(self.timestamps)):
if newest - self.timestamps[i] > self.memory:
delete_us.append(i)
for delete in delete_us:
del self.counts[delete]
del self.timestamps[delete]
def get_seconds(self):
if len(self.timestamps)<2:
return None
diff_stamp = self.timestamps[-1] - self.timestamps[0]
diff_count = self.counts[-1] - self.counts[0]
eps = diff_count / diff_stamp
time_left = (self.total - self.counts[-1]) / eps
return time_left
def get_eta(self):
return time.strftime(
'%H:%M:%S',
time.gmtime(
self.get_seconds()
)
)
def get_finished(self):
return time.strftime(
'%H:%M:%S',
time.gmtime(
time.time() - self.started
)
)
def split_upload(path, opts): def split_upload(path, opts):
try:
size = int(subprocess.check_output(['du','-b',path]).split("\t")[0])
except KeyboardInterrupt:
size = 0
eta = ETA(size)
eta.update(0)
split_bytes = opts.split * 1024 * 1024
parts_estimate = -(-size // split_bytes) # clever ceil
if os.path.isdir(path): if os.path.isdir(path):
tar = Popen( tar = Popen(
[ [
@@ -16,7 +73,11 @@ def split_upload(path, opts):
stdout = PIPE stdout = PIPE
) )
reader = tar.stdout reader = tar.stdout
basename = os.path.basename(path.rstrip("/")) + ".tar" basename = os.path.basename(
os.path.abspath(
path
)
) + ".tar"
elif os.path.isfile(path): elif os.path.isfile(path):
reader = open(path, 'rb') reader = open(path, 'rb')
basename = os.path.basename(path) basename = os.path.basename(path)
@@ -25,18 +86,19 @@ def split_upload(path, opts):
return return
try: try:
chunk = reader.read(opts.split * 1024 * 1024) chunk = reader.read(split_bytes)
part = 0 part = 0
eta_str = "ETA"
while chunk != "": while chunk != "":
chunk_name = ".%s.part.%03d"%( chunk_name = ".%s.part.%03d"%(
basename, basename,
part part
) )
print("%s part %d"%( basename, part )) print("%s part %d/%d [%s]"%( basename, part + 1, parts_estimate, eta_str ))
if not is_chunk_sent(chunk_name, opts): if not is_chunk_sent(chunk_name, opts):
p = Popen( p = Popen(
[ [
'curl','-f', 'curl','-s',
'-F','file=@-;filename=%s'%(chunk_name,), '-F','file=@-;filename=%s'%(chunk_name,),
'%supload/%s/%s'%(opts.rooturl, opts.share, opts.token) '%supload/%s/%s'%(opts.rooturl, opts.share, opts.token)
], ],
@@ -45,13 +107,15 @@ def split_upload(path, opts):
stderr=PIPE stderr=PIPE
) )
stdout_data, stderr_data = p.communicate(input=chunk) stdout_data, stderr_data = p.communicate(input=chunk)
print(stdout_data) if len(stderr_data) > 0:
print(stderr_data)
chunk = reader.read(opts.split * 1024 * 1024) chunk = reader.read(split_bytes)
part += 1 part += 1
eta.update(part * split_bytes)
eta_str = eta.get_eta()
finally: finally:
reader.close() reader.close()
join_chunks(basename,opts) join_chunks(basename,part,opts)
return return
@@ -68,11 +132,12 @@ def is_chunk_sent(name, opts):
return stdout_data == str(opts.split * 1024 * 1024) return stdout_data == str(opts.split * 1024 * 1024)
def join_chunks(name,opts): def join_chunks(name,parts,opts):
p = Popen( p = Popen(
[ [
'curl', 'curl',
'-F','filename=%s'%(name,), '-F','filename=%s'%(name,),
'-F','parts=%d'%(parts,),
'%supload_join/%s/%s'%(opts.rooturl, opts.share, opts.token) '%supload_join/%s/%s'%(opts.rooturl, opts.share, opts.token)
], ],
stdout=PIPE, stdout=PIPE,
@@ -84,7 +149,7 @@ def join_chunks(name,opts):
def parse_options(): def parse_options():
parser = argparse.ArgumentParser(description='Flees uploader') parser = argparse.ArgumentParser(description='Flees uploader')
parser.add_argument('-s', action="store", type=int, dest="split", default = 64, parser.add_argument('-s', action="store", type=int, dest="split", default = 32,
help = "Split size in megabytes [%(default)s]" help = "Split size in megabytes [%(default)s]"
) )
parser.add_argument('--rooturl', action="store", dest="rooturl", parser.add_argument('--rooturl', action="store", dest="rooturl",