Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature.python flask.dev #527

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions samples/python-flask/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from flask import Flask, render_template, request, abort, make_response, jsonify
import os

app = Flask(__name__, static_folder='static', static_url_path='/static')
app.debug = True

temp_base = os.path.expanduser("/home/tmp/uploads")


# landing page
@app.route("/api/upload")
def resumable_example():
return render_template("index.html")


# resumable.js uses a GET request to check if it uploaded the file already.
# NOTE: your validation here needs to match whatever you do in the POST (otherwise it will NEVER find the files)
@app.route("/upload", methods=['GET'])
def resumable_get():
resumableIdentfier = request.args.get('resumableIdentifier', type=str)
resumableFilename = request.args.get('resumableFilename', type=str)
resumableChunkNumber = request.args.get('resumableChunkNumber', type=int)

if not resumableIdentfier or not resumableFilename or not resumableChunkNumber:
# Parameters are missing or invalid
abort(500, 'Parameter error')

# chunk folder path based on the parameters
temp_dir = os.path.join(temp_base, resumableIdentfier)

# chunk path based on the parameters
chunk_file = os.path.join(temp_dir, get_chunk_name(resumableFilename, resumableChunkNumber))
app.logger.debug('Getting chunk: %s', chunk_file)

if os.path.isfile(chunk_file):
# Let resumable.js know this chunk already exists
return 'OK'
else:
# Let resumable.js know this chunk does not exists and needs to be uploaded
abort(404, 'Not found')


# if it didn't already upload, resumable.js sends the file here
@app.route("/upload", methods=['POST'])
def resumable_post():
resumableTotalChunks = request.form.get('resumableTotalChunks', type=int)
resumableChunkNumber = request.form.get('resumableChunkNumber', default=1, type=int)
resumableFilename = request.form.get('resumableFilename', default='error', type=str)
resumableIdentfier = request.form.get('resumableIdentifier', default='error', type=str)

# get the chunk data
chunk_data = request.files['file']

# make our temp directory
temp_dir = os.path.join(temp_base, resumableIdentfier)
if not os.path.isdir(temp_dir):
os.makedirs(temp_dir, 0777)

# save the chunk data
chunk_name = get_chunk_name(resumableFilename, resumableChunkNumber)
chunk_file = os.path.join(temp_dir, chunk_name)
chunk_data.save(chunk_file)
app.logger.debug('Saved chunk: %s', chunk_file)

# check if the upload is complete
chunk_paths = [os.path.join(temp_dir, get_chunk_name(resumableFilename, x)) for x in range(1, resumableTotalChunks+1)]
upload_complete = all([os.path.exists(p) for p in chunk_paths])

# combine all the chunks to create the final file
if upload_complete:
target_file_name = os.path.join(temp_base, resumableFilename)
with open(target_file_name, "ab") as target_file:
for p in chunk_paths:
stored_chunk_file_name = p
stored_chunk_file = open(stored_chunk_file_name, 'rb')
target_file.write(stored_chunk_file.read())
stored_chunk_file.close()
os.unlink(stored_chunk_file_name)
target_file.close()
os.rmdir(temp_dir)
app.logger.debug('File saved to: %s', target_file_name)

return 'OK'


@app.route("/api/resumable.js", methods=['GET'])
def resumable_js():
js_file = open('../../resumable.js', 'rb')
resp = make_response()
resp.headers["content-type"] = "application/javascript"
resp.set_data(js_file.read())
return resp


def get_chunk_name(uploaded_filename, chunk_number):
return uploaded_filename + "_part_%03d" % chunk_number


if __name__ == '__main__':
app.run()
2 changes: 2 additions & 0 deletions samples/python-flask/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Flask

Binary file added samples/python-flask/static/cancel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/python-flask/static/pause.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/python-flask/static/resume.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 51 additions & 0 deletions samples/python-flask/static/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/* Reset */
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,th,var{font-style:normal;font-weight:normal;}ol,ul {list-style:none;}caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;}

/* Baseline */
body, p, h1, h2, h3, h4, h5, h6 {font:normal 12px/1.3em Helvetica, Arial, sans-serif; color:#333; }
h1 {font-size:22px; font-weight:bold;}
h2 {font-size:19px; font-weight:bold;}
h3 {font-size:16px; font-weight:bold;}
h4 {font-size:14px; font-weight:bold;}
h5 {font-size:12px; font-weight:bold;}
p {margin:10px 0;}


body {text-align:center; margin:40px;}
#frame {margin:0 auto; width:800px; text-align:left;}



/* Uploader: Drag & Drop */
.resumable-error {display:none; font-size:14px; font-style:italic;}
.resumable-drop {padding:15px; font-size:13px; text-align:center; color:#666; font-weight:bold;background-color:#eee; border:2px dashed #aaa; border-radius:10px; margin-top:40px; z-index:9999; display:none;}
.dragover {padding:30px; color:#555; background-color:#ddd; border:1px solid #999;}

/* Uploader: Progress bar */
.resumable-progress {margin:30px 0 30px 0; width:100%; display:none;}
.progress-container {height:7px; background:#9CBD94; position:relative; }
.progress-bar {position:absolute; top:0; left:0; bottom:0; background:#45913A; width:0;}
.progress-text {font-size:11px; line-height:9px; padding-left:10px;}
.progress-pause {padding:0 0 0 7px;}
.progress-resume-link {display:none;}
.is-paused .progress-resume-link {display:inline;}
.is-paused .progress-pause-link {display:none;}
.is-complete .progress-pause {display:none;}

/* Uploader: List of items being uploaded */
.resumable-list {overflow:auto; margin-right:-20px; display:none;}
.uploader-item {width:148px; height:90px; background-color:#666; position:relative; border:2px solid black; float:left; margin:0 6px 6px 0;}
.uploader-item-thumbnail {width:100%; height:100%; position:absolute; top:0; left:0;}
.uploader-item img.uploader-item-thumbnail {opacity:0;}
.uploader-item-creating-thumbnail {padding:0 5px; font-size:9px; color:white;}
.uploader-item-title {position:absolute; font-size:9px; line-height:11px; padding:3px 50px 3px 5px; bottom:0; left:0; right:0; color:white; background-color:rgba(0,0,0,0.6); min-height:27px;}
.uploader-item-status {position:absolute; bottom:3px; right:3px;}

/* Uploader: Hover & Active status */
.uploader-item:hover, .is-active .uploader-item {border-color:#4a873c; cursor:pointer; }
.uploader-item:hover .uploader-item-title, .is-active .uploader-item .uploader-item-title {background-color:rgba(74,135,60,0.8);}

/* Uploader: Error status */
.is-error .uploader-item:hover, .is-active.is-error .uploader-item {border-color:#900;}
.is-error .uploader-item:hover .uploader-item-title, .is-active.is-error .uploader-item .uploader-item-title {background-color:rgba(153,0,0,0.6);}
.is-error .uploader-item-creating-thumbnail {display:none;}
116 changes: 116 additions & 0 deletions samples/python-flask/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html>
<head>
<title>Resumable.js - Multiple simultaneous, stable and resumable uploads via the HTML5 File API</title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="/static/style.css" />
</head>
<body>
<div id="frame">

<h1>Resumable.js</h1>
<p>It's a JavaScript library providing multiple simultaneous, stable and resumable uploads via the HTML5 File API.</p>

<p>The library is designed to introduce fault-tolerance into the upload of large files through HTTP. This is done by splitting each files into small chunks; whenever the upload of a chunk fails, uploading is retried until the procedure completes. This allows uploads to automatically resume uploading after a network connection is lost either locally or to the server. Additionally, it allows for users to pause and resume uploads without loosing state.</p>

<p>Resumable.js relies on the HTML5 File API and the ability to chunks files into smaller pieces. Currently, this means that support is limited to Firefox 4+ and Chrome 11+.</p>

<hr/>

<h3>Demo</h3>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="resumable.js"></script>

<div class="resumable-error">
Your browser, unfortunately, is not supported by Resumable.js. The library requires support for <a href="http://www.w3.org/TR/FileAPI/">the HTML5 File API</a> along with <a href="http://www.w3.org/TR/FileAPI/#normalization-of-params">file slicing</a>.
</div>

<div class="resumable-drop">
Drop video files here to upload or <a class="resumable-browse"><u>select from your computer</u></a>
</div>

<div class="resumable-progress">
<table>
<tr>
<td width="100%"><div class="progress-container"><div class="progress-bar"></div></div></td>
<td class="progress-text" nowrap="nowrap"></td>
<td class="progress-pause" nowrap="nowrap">
<a href="#" onclick="r.upload(); return(false);" class="progress-resume-link"><img src="/static/resume.png" title="Resume upload" /></a>
<a href="#" onclick="r.pause(); return(false);" class="progress-pause-link"><img src="/static/pause.png" title="Pause upload" /></a>
<a href="#" onclick="r.cancel(); return(false);" class="progress-cancel-link"><img src="/static/cancel.png" title="Cancel upload" /></a>
</td>
</tr>
</table>
</div>

<ul class="resumable-list"></ul>

<script>
var r = new Resumable({
target:'/upload',
chunkSize:1*1024*1024,
simultaneousUploads:4,
testChunks:false,
throttleProgressCallbacks:1
});
// Resumable.js isn't supported, fall back on a different method
if(!r.support) {
$('.resumable-error').show();
} else {
// Show a place for dropping/selecting files
$('.resumable-drop').show();
r.assignDrop($('.resumable-drop')[0]);
r.assignBrowse($('.resumable-browse')[0]);

// Handle file add event
r.on('fileAdded', function(file){
// Show progress pabr
$('.resumable-progress, .resumable-list').show();
// Show pause, hide resume
$('.resumable-progress .progress-resume-link').hide();
$('.resumable-progress .progress-pause-link').show();
// Add the file to the list
$('.resumable-list').append('<li class="resumable-file-'+file.uniqueIdentifier+'">Uploading <span class="resumable-file-name"></span> <span class="resumable-file-progress"></span>');
$('.resumable-file-'+file.uniqueIdentifier+' .resumable-file-name').html(file.fileName);
// Actually start the upload
r.upload();
});
r.on('pause', function(){
// Show resume, hide pause
$('.resumable-progress .progress-resume-link').show();
$('.resumable-progress .progress-pause-link').hide();
});
r.on('complete', function(){
// Hide pause/resume when the upload has completed
$('.resumable-progress .progress-resume-link, .resumable-progress .progress-pause-link').hide();
});
r.on('fileSuccess', function(file,message){
// Reflect that the file upload has completed
$('.resumable-file-'+file.uniqueIdentifier+' .resumable-file-progress').html('(completed)');
});
r.on('fileError', function(file, message){
// Reflect that the file upload has resulted in error
$('.resumable-file-'+file.uniqueIdentifier+' .resumable-file-progress').html('(file could not be uploaded: '+message+')');
});
r.on('fileProgress', function(file){
// Handle progress for both the file and the overall upload
$('.resumable-file-'+file.uniqueIdentifier+' .resumable-file-progress').html(Math.floor(file.progress()*100) + '%');
$('.progress-bar').css({width:Math.floor(r.progress()*100) + '%'});
});
r.on('cancel', function(){
$('.resumable-file-progress').html('canceled');
});
r.on('uploadStart', function(){
// Show pause, hide resume
$('.resumable-progress .progress-resume-link').hide();
$('.resumable-progress .progress-pause-link').show();
});
}
</script>

</div>
</body>
</html>