diff --git a/demo.py b/demo.py index 094a22e..ed937a1 100644 --- a/demo.py +++ b/demo.py @@ -3,16 +3,16 @@ import os app = Flask(__name__) -tm = tus_manager(app) +tm = tus_manager(app, upload_url='/newsflow/api/file-upload') @app.route("/demo") def demo(): - return render_template("demo.html") + return render_template("demo.html", upload_url = tm.upload_url ) # serve the uploaded files @app.route('/uploads/', methods=['GET']) def download(filename): - uploads = os.path.join(app.root_path, app.config['TUS_UPLOADSDIR']) + uploads = os.path.join(app.root_path, tm.upload_folder) return send_from_directory(directory=uploads, filename=filename) if __name__ == '__main__': diff --git a/flask_tus.py b/flask_tus.py index fa9ab6d..8ae1d5d 100644 --- a/flask_tus.py +++ b/flask_tus.py @@ -14,42 +14,28 @@ class tus_manager(object): - def __init__(self, app=None): + def __init__(self, app=None, upload_url='/file-upload', upload_folder='uploads/'): self.app = app if app is not None: - self.init_app(app) + self.init_app(app, upload_url, upload_folder) - def init_app(self, app): - app.config.setdefault('TUS_ROOTDIR', '/file-upload') - app.config.setdefault('TUS_UPLOADSDIR', 'uploads') + def init_app(self, app, upload_url='/file-upload', upload_folder='uploads/'): + self.upload_url = upload_url + self.upload_folder = upload_folder self.tus_api_version = '1.0.0' self.tus_api_version_supported = '1.0.0' self.tus_api_extensions = ['creation', 'termination'] self.tus_max_file_size = 4294967296 # 4GByte - # Use the newstyle teardown_appcontext if it's available, - # otherwise fall back to the request context - if hasattr(app, 'teardown_appcontext'): - app.teardown_appcontext(self.teardown) - else: - app.teardown_request(self.teardown) - # register the two file upload endpoints - app.add_url_rule(app.config['TUS_ROOTDIR'], 'file-upload', self.tus_file_upload, methods=['OPTIONS', 'POST']) - app.add_url_rule('{}/'.format( app.config['TUS_ROOTDIR'] ), 'file-upload-chunk', self.tus_file_upload_chunk, methods=['HEAD', 'PATCH', 'DELETE']) + app.add_url_rule(self.upload_url, 'file-upload', self.tus_file_upload, methods=['OPTIONS', 'POST']) + app.add_url_rule('{}/'.format( self.upload_url ), 'file-upload-chunk', self.tus_file_upload_chunk, methods=['HEAD', 'PATCH', 'DELETE']) # handle redis server connection def redis_connect(self): return redis.Redis() - # handle teardown of redis connection - def teardown(self, app): -# ctx = stack.top -# if hasattr(ctx, 'tus_redis'): -# ctx.tus_redis.disconnect() - pass - @property def redis_connection(self): ctx = stack.top @@ -58,7 +44,6 @@ def redis_connection(self): ctx.tus_redis = self.redis_connect() return ctx.tus_redis - def tus_file_upload(self): response = make_response("", 200) @@ -80,7 +65,7 @@ def tus_file_upload(self): metadata[key] = base64.b64decode(value) file_size = int(request.headers.get("Upload-Length", "0")) - resource_id = uuid.uuid4() + resource_id = str(uuid.uuid4()) p = self.redis_connection.pipeline() p.setex("file-uploads/{}/filename".format(resource_id), "{}".format(metadata.get("filename")), 3600) @@ -90,7 +75,7 @@ def tus_file_upload(self): p.execute() try: - f = open("{}/{}".format(self.app.config['TUS_UPLOADSDIR'], resource_id), "wb") + f = open( os.path.join( self.upload_folder, resource_id ), "wb") f.seek( file_size - 1) f.write("\0") f.close() @@ -100,7 +85,7 @@ def tus_file_upload(self): return response response.status_code = 201 - response.headers['Location'] = '{}/{}'.format(self.app.config['TUS_ROOTDIR'], resource_id) + response.headers['Location'] = '{}/{}'.format(self.upload_url, resource_id) response.autocorrect_location_header = False else: @@ -116,7 +101,7 @@ def tus_file_upload_chunk(self, resource_id): response.headers['Tus-Version'] = self.tus_api_version_supported offset = self.redis_connection.get("file-uploads/{}/offset".format( resource_id )) - self.app.logger.info( offset ); + upload_file_path = os.path.join( self.upload_folder, resource_id ) if request.method == 'HEAD': offset = self.redis_connection.get("file-uploads/{}/offset".format( resource_id )) @@ -132,7 +117,7 @@ def tus_file_upload_chunk(self, resource_id): return response if request.method == 'DELETE': - os.unlink("{}/{}".format( self.app.config['TUS_UPLOADSDIR'], resource_id)) + os.unlink( upload_file_path ) p = self.redis_connection.pipeline() p.delete("file-uploads/{}/filename".format(resource_id)) @@ -144,41 +129,41 @@ def tus_file_upload_chunk(self, resource_id): response.status_code = 204 return respose - filename = self.redis_connection.get("file-uploads/{}/filename".format( resource_id )) - if filename is None or os.path.lexists("{}/{}".format(self.app.config['TUS_UPLOADSDIR'], resource_id )) is False: - response.status_code = 410 - return response + if request.method == 'PATCH': + filename = self.redis_connection.get("file-uploads/{}/filename".format( resource_id )) + if filename is None or os.path.lexists( upload_file_path ) is False: + response.status_code = 410 + return response - file_offset = int(request.headers.get("Upload-Offset", 0)) - chunk_size = int(request.headers.get("Content-Length", 0)) - file_size = int( self.redis_connection.get( "file-uploads/{}/file_size".format( resource_id )) ) + file_offset = int(request.headers.get("Upload-Offset", 0)) + chunk_size = int(request.headers.get("Content-Length", 0)) + file_size = int( self.redis_connection.get( "file-uploads/{}/file_size".format( resource_id )) ) - if request.headers.get("Upload-Offset") != self.redis_connection.get( "file-uploads/{}/offset".format( resource_id )): # check to make sure we're in sync - response.status_code = 409 # HTTP 409 Conflict - return response + if request.headers.get("Upload-Offset") != self.redis_connection.get( "file-uploads/{}/offset".format( resource_id )): # check to make sure we're in sync + response.status_code = 409 # HTTP 409 Conflict + return response - try: - f = open( "{}/{}".format(self.app.config['TUS_UPLOADSDIR'], resource_id), "r+b") - except IOError: - f = open( "{}/{}".format(self.app.config['TUS_UPLOADSDIR'], resource_id), "wb") - finally: - f.seek( file_offset ) - f.write(request.data) - f.close() - - new_offset = self.redis_connection.incrby( "file-uploads/{}/offset".format( resource_id ), chunk_size) - response.headers['Upload-Offset'] = new_offset - - if file_size == new_offset: # file transfer complete, rename from resource id to actual filename - filename_parts = os.path.splitext(filename) - counter = 1 - while True: - if os.path.lexists( "{}/{}".format(self.app.config['TUS_UPLOADSDIR'], filename)): - filename = "{}{}.{}".format( filename_parts[0], filename_parts[1], counter ) - counter += 1 - else: - break - - os.rename( "{}/{}".format( self.app.config['TUS_UPLOADSDIR'], resource_id ), "{}/{}".format( self.app.config['TUS_UPLOADSDIR'], filename )) + try: + f = open( upload_file_path, "r+b") + except IOError: + f = open( upload_file_path, "wb") + finally: + f.seek( file_offset ) + f.write(request.data) + f.close() - return response + new_offset = self.redis_connection.incrby( "file-uploads/{}/offset".format( resource_id ), chunk_size) + response.headers['Upload-Offset'] = new_offset + + if file_size == new_offset: # file transfer complete, rename from resource id to actual filename + filename_parts = os.path.splitext(filename) + counter = 1 + while True: + if os.path.lexists( os.path.join( self.upload_folder, filename )): + filename = "{}{}.{}".format( filename_parts[0], filename_parts[1], counter ) + counter += 1 + else: + break + + os.rename( upload_file_path, os.path.join( self.upload_folder, filename )) + return response diff --git a/setup.py b/setup.py index 7e38098..9ef1a40 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name='Flask-Tus', - version='0.1.0', + version='0.2.0', url='http://github.com/matthoskins1980/Flask-Tus/', license='MIT', author='Matt Hoskins', diff --git a/templates/demo.html b/templates/demo.html index faab77d..099ad5f 100644 --- a/templates/demo.html +++ b/templates/demo.html @@ -30,7 +30,7 @@

tus-js-client demo

- +