From bdf16adb526bbc2aa0a25145c811cf07b210899b Mon Sep 17 00:00:00 2001 From: Sanj Date: Wed, 1 Jun 2011 03:46:57 +0530 Subject: [PATCH] basic file upload works --- edgware/files/models.py | 20 +- edgware/files/urls.py | 11 + edgware/files/views.py | 75 ++++- edgware/settings.py | 1 + edgware/static/js/upload/firefogg.js | 56 ++++ .../static/js/upload/jquery.uploadProgress.js | 97 ++++++ edgware/static/js/upload/progressbar.js | 119 +++++++ edgware/static/js/upload/upload.js | 301 ++++++++++++++++++ edgware/urls.py | 2 +- 9 files changed, 679 insertions(+), 3 deletions(-) create mode 100644 edgware/static/js/upload/firefogg.js create mode 100644 edgware/static/js/upload/jquery.uploadProgress.js create mode 100644 edgware/static/js/upload/progressbar.js create mode 100644 edgware/static/js/upload/upload.js diff --git a/edgware/files/models.py b/edgware/files/models.py index c521c9f..578d151 100755 --- a/edgware/files/models.py +++ b/edgware/files/models.py @@ -9,6 +9,8 @@ from convert import convertFile from django.db.models.signals import post_save from settings import UPLOAD_ROOT, MEDIA_ROOT from utils.add_file import hashFile, fileInfo, getFileType +from django.core.files.base import ContentFile + #FIXME: The following two functions are ridiculous. please remove and clean up all references to them. def baseFileName(filename): @@ -72,6 +74,7 @@ class File(models.Model): file = models.FileField('File', upload_to=UPLOAD_ROOT, max_length=1024) full_path = models.CharField(max_length=2048, blank=True, db_index=True) #makes it more efficient to retrieve a file by path instead of using self.file.path title = models.CharField(max_length=255) + done = models.BooleanField(default=False) description = models.TextField(blank=True) tags = TagField("Tags", help_text="Enter as many tags as you like, separated by commas") userID = models.ForeignKey(User) @@ -85,6 +88,21 @@ class File(models.Model): # folder = models.ForeignKey("Folder") info = models.CharField(max_length=1024, blank=True, null=True) + def save_chunk(self, chunk, name='data.bin'): + if not self.done: + if not self.file: + self.file.save(name, ContentFile(chunk)) + self.filename = name + self.save() + else: + f = open(self.file.path, 'a') + f.write(chunk) + f.close() + return True + return False + + + @classmethod def add_from_path(cls, category, user, path, **kwargs): if cls.objects.filter(full_path=path).count() > 0: @@ -156,4 +174,4 @@ class Type(models.Model): verbose_name = 'File Type' verbose_name_plural = 'File Types' -post_save.connect(convertFile, sender=File) +# post_save.connect(convertFile, sender=File) diff --git a/edgware/files/urls.py b/edgware/files/urls.py index 56f706d..2458a6d 100644 --- a/edgware/files/urls.py +++ b/edgware/files/urls.py @@ -4,4 +4,15 @@ import views urlpatterns = patterns('', (r'addFolder', views.add_folder), (r'upload', views.upload_files), + (r'add', views.add), + (r'^([A-Z0-9].*)/chunk$', views.chunk) ) + +# urlpatterns += patterns('views', +# (r'^$', 'index'), +# (r'^add$', 'add'), +# (r'^([A-Z0-9].*)/$', 'view'), +# (r'^([A-Z0-9].*)/chunk$', 'chunk'), +# (r'^([A-Z0-9].*)/(.+)$', 'download'), +# (r'^([A-Z0-9].*)$', 'view'), +# ) diff --git a/edgware/files/views.py b/edgware/files/views.py index d668556..09f0346 100644 --- a/edgware/files/views.py +++ b/edgware/files/views.py @@ -8,7 +8,9 @@ from settings import FTP_ROOT, UPLOAD_ROOT import os from os.path import join, isdir, getmtime, basename import shutil - +from django.views.decorators.csrf import csrf_exempt +from ox.django.shortcuts import render_to_json_response +from django.shortcuts import get_object_or_404 ''' class folder_names(object): @@ -85,6 +87,77 @@ def add_folder(request): def editor(request): return render_to_response("editor.html") +''' def gallery(request): return HttpResponseRedirect('http://www.urbaanedge.com/wordpress') +''' + +#Start Upload stuff - from hostb.org/host/views.py + +class ChunkForm(forms.Form): + chunk = forms.FileField() + done = forms.IntegerField(required=False) + +@csrf_exempt +def chunk(request, id): + if request.method == 'POST': + item = get_object_or_404(File, id=id) + form = ChunkForm(request.POST, request.FILES) + canEdit = True + if form.is_valid() and canEdit: + f = form.cleaned_data['chunk'] + response = { + 'resultUrl': '/files/' + str(item.id) + } + if item.title: + name = item.title + else: + name = f.name + if not item.save_chunk(f.read(), name): + response['result'] = 'failed' + elif form.cleaned_data['done']: + item.done = True + item.save() + response['done'] = 1 + response['result'] = 1 + else: + response['result'] = 1 + return render_to_json_response(response) + response = { + 'result': -1, + 'fileUrl': '/' + } + return render_to_json_response(response) + +@csrf_exempt +def add(request): + if request.method == 'POST': + if request.POST.get('firefogg', False): + file = File() + file.userID = request.user + file.info = '' #WTF! + file.title = request.POST.get('name', '') + file.save() + response = { + 'result': 1, + 'maxRetry': 10, + 'uploadUrl': '/files/' + str(file.id) + "/chunk" +# 'uploadUrl': request.build_absolute_uri("%s/chunk" % file.get_absolute_url()) + } + return render_to_json_response(response) + # Save any files that were uploaded (ignoring empty form fields) + if 'file' in request.FILES: + new_file = request.FILES['file'] + file = File(title=new_file.name) + file.userID = request.user + file.done = True + file.info = '' #WTF! + file.file.save(new_file.name, new_file) + file.save() + if request.POST.get('api', False): + return HttpResponse(request.build_absolute_uri(file.get_absolute_url()), content_type="text/plain") + return HttpResponseRedirect(file.get_absolute_url()) + + #no upload + return HttpResponseRedirect('/') diff --git a/edgware/settings.py b/edgware/settings.py index 762c859..d82ca8c 100644 --- a/edgware/settings.py +++ b/edgware/settings.py @@ -4,6 +4,7 @@ from os.path import join DEBUG = True TEMPLATE_DEBUG = DEBUG +JSON_DEBUG = DEBUG LOCAL_DEVELOPMENT = True APPEND_SLASH = True LOGGING_INTERCEPT_REDIRECTS = False diff --git a/edgware/static/js/upload/firefogg.js b/edgware/static/js/upload/firefogg.js new file mode 100644 index 0000000..0508a1d --- /dev/null +++ b/edgware/static/js/upload/firefogg.js @@ -0,0 +1,56 @@ +$(document).ready(function() { + $('#firefogg').hide(); + if(typeof(Firefogg) != 'undefined') { + $('#addFile').hide(); + $('#firefogg').show(); + $('#submitFile').hide(); + + ogg = new Firefogg(); + + $('#selectFile').click(function() { + if(ogg.selectVideo()) { + $('#selectFile').hide(); + $('#submitFile').show(); + } + }); + $('#submitFile').click(function() { + $('#submitFile').hide(); + $('#progressbar').show(); + $('#progressbar').width(200); + $('#progressbar').css('background-color', '#eee'); + $('#progressbar').html('
') + options = JSON.stringify({'passthrough': true}); + var _data = $('#firefogg').serializeArray(); + var data = {} + $(_data).each(function() { + data[this.name] = this.value; + }) + data['firefogg'] = 1; + var data = JSON.stringify(data); + ogg.upload(options, add_url, data); + var updateStatus = function() { + var status = ogg.status(); + var progress = ogg.progress(); + + //do something with status and progress, i.e. set progressbar width: + var progressbar = document.getElementById('progress'); + progressbar.style.width= parseInt(progress*200) +'px'; + $('#progressstatus').html(parseInt(progress*100) + '% - ' + status); + + //loop to get new status if still encoding + if(ogg.state == 'encoding' || ogg.state == 'uploading') { + setTimeout(updateStatus, 500); + } + //encoding sucessfull, state can also be 'encoding failed' + else if (ogg.state == 'done') { + if(ogg.resultUrl) + document.location.href = ogg.resultUrl; + } else { + $('#progressstatus').html(ogg.state); + } + } + updateStatus(); + }); + } +}); + diff --git a/edgware/static/js/upload/jquery.uploadProgress.js b/edgware/static/js/upload/jquery.uploadProgress.js new file mode 100644 index 0000000..52f4987 --- /dev/null +++ b/edgware/static/js/upload/jquery.uploadProgress.js @@ -0,0 +1,97 @@ +/* +* jquery.uploadProgress +* +* Copyright (c) 2008 Piotr Sarnacki (drogomir.com) +* +* Licensed under the MIT license: +* http://www.opensource.org/licenses/mit-license.php +* +*/ +(function($) { + $.fn.uploadProgress = function(options) { + options = $.extend({ + dataType: "json", + interval: 2000, + progressBar: "#progressbar", + progressUrl: "/progress", + start: function() {}, + uploading: function() {}, + complete: function() {}, + success: function() {}, + error: function() {}, + preloadImages: [], + uploadProgressPath: '/javascripts/jquery.uploadProgress.js', + jqueryPath: '/javascripts/jquery.js', + timer: "" + }, options); + + $(function() { + //preload images + for(var i = 0; i").attr("src", options.preloadImages[i]); + } + /* tried to add iframe after submit (to not always load it) but it won't work. + safari can't get scripts properly while submitting files */ + if($.browser.safari && top.document == document) { + /* iframe to send ajax requests in safari + thanks to Michele Finotto for idea */ + iframe = document.createElement('iframe'); + iframe.name = "progressFrame"; + $(iframe).css({width: '0', height: '0', position: 'absolute', top: '-3000px'}); + document.body.appendChild(iframe); + + var d = iframe.contentWindow.document; + d.open(); + /* weird - safari won't load scripts without this lines... */ + d.write(''); + d.close(); + + var b = d.body; + var s = d.createElement('script'); + s.src = options.jqueryPath; + /* must be sure that jquery is loaded */ + s.onload = function() { + var s1 = d.createElement('script'); + s1.src = options.uploadProgressPath; + b.appendChild(s1); + } + b.appendChild(s); + } + }); + + return this.each(function(){ + $(this).bind('submit', function() { + var uuid = ""; + for (i = 0; i < 32; i++) { uuid += Math.floor(Math.random() * 16).toString(16); } + + /* update uuid */ + options.uuid = uuid; + /* start callback */ + options.start(); + + /* patch the form-action tag to include the progress-id if X-Progress-ID has been already added just replace it */ + if(old_id = /X-Progress-ID=([^&]+)/.exec($(this).attr("action"))) { + var action = $(this).attr("action").replace(old_id[1], uuid); + $(this).attr("action", action); + } else { + $(this).attr("action", jQuery(this).attr("action") + "?X-Progress-ID=" + uuid); + } + var uploadProgress = $.browser.safari ? progressFrame.jQuery.uploadProgress : jQuery.uploadProgress; + options.timer = window.setInterval(function() { uploadProgress(this, options) }, options.interval); + }); + }); + }; + +jQuery.uploadProgress = function(e, options) { + jQuery.ajax({ + type: "GET", + url: options.progressUrl + "?X-Progress-ID=" + options.uuid, + dataType: options.dataType, + success: function(upload) { + options.uploading(upload); + } + }); +}; + +})(jQuery); diff --git a/edgware/static/js/upload/progressbar.js b/edgware/static/js/upload/progressbar.js new file mode 100644 index 0000000..8282e50 --- /dev/null +++ b/edgware/static/js/upload/progressbar.js @@ -0,0 +1,119 @@ +/* +* based on jquery.uploadProgress +* +* Copyright (c) 2008 Piotr Sarnacki (drogomir.com) +* +* Licensed under the MIT license: +* http://www.opensource.org/licenses/mit-license.php +* +*/ +(function($) { + $.fn.uploadProgress = function(options) { + options = $.extend({ + dataType: "json", + interval: 2000, + progressBar: "#progressbar", + progressUrl: "/progress", + start: function() {}, + uploading: function() {}, + complete: function() {}, + success: function() {}, + error: function() {}, + preloadImages: [], + uploadProgressPath: '/javascripts/jquery.uploadProgress.js', + jqueryPath: '/javascripts/jquery.js', + timer: "" + }, options); + + $(function() { + //preload images + for(var i = 0; i").attr("src", options.preloadImages[i]); + } + /* tried to add iframe after submit (to not always load it) but it won't work. + safari can't get scripts properly while submitting files */ + if($.browser.safari && top.document == document) { + /* iframe to send ajax requests in safari + thanks to Michele Finotto for idea */ + iframe = document.createElement('iframe'); + iframe.name = "progressFrame"; + $(iframe).css({width: '0', height: '0', position: 'absolute', top: '-3000px'}); + document.body.appendChild(iframe); + + var d = iframe.contentWindow.document; + d.open(); + /* weird - safari won't load scripts without this lines... */ + d.write(''); + d.close(); + + var b = d.body; + var s = d.createElement('script'); + s.src = options.jqueryPath; + /* must be sure that jquery is loaded */ + s.onload = function() { + var s1 = d.createElement('script'); + s1.src = options.uploadProgressPath; + b.appendChild(s1); + } + b.appendChild(s); + } + }); + + return this.each(function(){ + $(this).bind('submit', function() { + var uuid = ""; + for (i = 0; i < 32; i++) { uuid += Math.floor(Math.random() * 16).toString(16); } + + /* update uuid */ + options.uuid = uuid; + /* start callback */ + options.start(); + + /* patch the form-action tag to include the progress-id if X-Progress-ID has been already added just replace it */ + if(old_id = /X-Progress-ID=([^&]+)/.exec($(this).attr("action"))) { + var action = $(this).attr("action").replace(old_id[1], uuid); + $(this).attr("action", action); + } else { + $(this).attr("action", jQuery(this).attr("action") + "?X-Progress-ID=" + uuid); + } + var uploadProgress = $.browser.safari ? progressFrame.jQuery.uploadProgress : jQuery.uploadProgress; + options.timer = window.setInterval(function() { uploadProgress(this, options) }, options.interval); + }); + }); + }; + +jQuery.uploadProgress = function(e, options) { + jQuery.ajax({ + type: "GET", + url: options.progressUrl + "?X-Progress-ID=" + options.uuid, + dataType: options.dataType, + success: function(upload) { + options.uploading(upload); + } + }); +}; + +})(jQuery); + +$(document).ready(function() { + $('#addFile').uploadProgress({ + /* scripts locations for safari */ + jqueryPath: "/static/js/jquery.js", + uploadProgressPath: "/static/js/upload/jquery.uploadProgress.js", + start: function(upload) { + $('#addFile').hide(); + $('#progressbar').show(); + $('#progressbar').html('...'); + }, + uploading: function(upload) { + var filename = $('#file').val(); + filename = filename.split('/'); + filename = filename[filename.length-1]; + $('#progressbar').html(upload.progressbar_html); + if(upload.precents) + document.title = upload.precents + "% of " + filename; + }, + interval: 2500 + }); +}); diff --git a/edgware/static/js/upload/upload.js b/edgware/static/js/upload/upload.js new file mode 100644 index 0000000..b1b0e87 --- /dev/null +++ b/edgware/static/js/upload/upload.js @@ -0,0 +1,301 @@ +// -*- coding: utf-8 -*- +// vi:si:et:sw=2:sts=2:ts=2 + +// Workaround for Firefox 4.0 sending an empty string +// as filename for Blobs in FormData requests +// https://bugzilla.mozilla.org/show_bug.cgi?id=649150 +function geckoFormData() { + var self = this, + that = {}, + boundary = '------XX' + Math.random(), + dashdash = '--', + crlf = '\r\n', + builder = '', // Build RFC2388 string. + wait = 0; + + builder += dashdash + boundary + crlf; + + that.append = function(name, data) { + // Generate headers. + builder += 'Content-Disposition: form-data; name="'+ name +'"'; + builder += crlf; + builder += crlf; + + // Write data. + builder += data; + builder += crlf; + + // Write boundary. + builder += dashdash + boundary + crlf; + }; + that.appendFile = function(name, data, type, filename) { + builder += 'Content-Disposition: form-data; name="'+ name +'"'; + builder += '; filename="' + filename + '"'; + builder += crlf; + builder += 'Content-Type: ' + type; + builder += crlf; + builder += crlf; + + // Write binary data. + builder += data; + builder += crlf; + + // Write boundary. + builder += dashdash + boundary + crlf; + }; + that.appendBlob = function(name, blob, filename) { + wait++; + var reader = new FileReader(); + reader.onload = function(e) { + that.appendFile(name, e.target.result, blob.type, filename); + // Call onload after last Blob + wait--; + if(!wait && self.onload) { + self.onload(); + } + }; + reader.readAsBinaryString(blob); + }; + + that.send = function(xhr) { + self.onload = function() { + // Mark end of the request. + builder += dashdash + boundary + dashdash + crlf; + + // Send to server + xhr.setRequestHeader("Content-type", "multipart/form-data; boundary=" + boundary); + xhr.sendAsBinary(builder); + }; + if(!wait) { + self.onload(); + } + }; + return that; +} + +function FirefoggUploader(file, uploadUrl, uploadData, callback, progress_callback) { + var self = this, + that = { + progress: 0 + }, + chunkSize = 1024*1024, + chunkUrl, + gecko = /gecko\//.test(navigator.userAgent.toLowerCase()), + maxRetry = -1, + progress, + retries = 0; + + progress_callback = progress_callback || function(progress) {}; + callback = callback || function(result) {}; + + function uploadChunk(chunkId) { + var bytesAvailable = file.size, + chunk, + chunkOffset = chunkId * chunkSize; + //Slice API was taken out and new version now require start/end and not start/length + if(file.slice) + chunk = file.slice(chunkOffset, chunkSize, file.type); + else if(file.mozSlice) + chunk = file.mozSlice(chunkOffset, chunkOffset+chunkSize, file.type); + else if(file.webkitSlice) + chunk = file.webkitSlice(chunkOffset, chunkOffset+chunkSize, file.type); + + that.progress = parseFloat(chunkOffset)/bytesAvailable; + self.req = new XMLHttpRequest(); + self.req.addEventListener("load", function (evt) { + var response; + that.responseText = evt.target.responseText; + try { + response = JSON.parse(evt.target.responseText); + } catch(e) { + response = {}; + } + if (response.done == 1) { + //upload finished + that.resultUrl = response.resultUrl; + that.progress = 1; + that.status = 'done'; + callback(that); + } + else if (response.result == 1) { + //reset retry counter + retries = 0; + //start uploading next chunk + uploadChunk(chunkId + 1); + } else { + //failed to upload, try again in 3 second + retries++; + if (maxRetry > 0 && retries > maxRetry) { + that.status = 'uplaod failed'; + that.progress = -1; + callback(that); + } else { + setTimeout(function() { + uploadChunk(chunkId); + }, 3000); + } + } + }, false); + self.req.addEventListener("error", function (evt) { + //failed to upload, try again in 3 second + retries++; + if (maxRetry > 0 && retries > maxRetry) { + that.status = 'uplaod failed'; + that.progress = -1; + callback(that); + } else { + setTimeout(function() { + uploadChunk(chunkId); + }, 3000); + } + }, false); + self.req.upload.addEventListener("progress", function (evt) { + if (evt.lengthComputable) { + that.progress = parseFloat(chunkOffset + evt.loaded) / bytesAvailable; + progress_callback(that.progress); + } + }, false); + self.req.addEventListener("abort", function (evt) { + that.status = 'aborted'; + that.progress = -1; + callback(that); + }, false); + + var formData; + if(gecko) { + formData = new geckoFormData(); + } else { + formData = new FormData(); + } + formData.append('chunkId', chunkId); + if (bytesAvailable <= chunkOffset + chunkSize) { + formData.append('done', 1); + } + if(gecko) { + formData.appendBlob('chunk', chunk, 'chunk.bin'); + } else { + formData.append('chunk', chunk); + } + self.req.open("POST", chunkUrl, true); + if(gecko) { + formData.send(self.req); + } else { + self.req.send(formData); + } + } + + //request upload slot from server + that.status = 'requesting chunk upload'; + self.req = new XMLHttpRequest(); + self.req.addEventListener("load", function (evt) { + var response = {}; + that.responseText = evt.target.responseText; + try { + response = JSON.parse(evt.target.responseText); + } catch(e) { + response = {}; + that.status = "failed to parse response"; + that.progress = -1; + callback(that); + } + if (response.maxRetry) { + maxRetry = response.maxRetry; + } + chunkUrl = response.uploadUrl; + if (chunkUrl) { + that.status = 'uploading'; + that.progress = 0.0; + //start upload + uploadChunk(0); + } else { + that.status = 'upload failed, no upload url provided'; + that.progress = -1; + callback(that); + } + }, false); + self.req.addEventListener("error", function (evt) { + that.status = 'uplaod failed'; + that.progress = -1; + that.responseText = evt.target.responseText; + callback(that); + }, false); + self.req.addEventListener("abort", function (evt) { + that.status = 'aborted'; + that.progress = -1; + callback(that); + }, false); + var formData = new FormData(); + for(var key in uploadData) { + if (uploadData.hasOwnProperty(key)) { + formData.append(key, uploadData[key]); + } + } + self.req.open("POST", uploadUrl); + self.req.send(formData); + + //public interface + that.abort = function() { + if (self.req) { + self.req.abort(); + self.req = null; + } + }; + return that; +} + +// Activate on page is possible +$(document).ready(function() { + var Chrome = /chrome/.test(navigator.userAgent.toLowerCase()), + GeckoVersion = navigator.userAgent.match(/rv\:(.+)\) Gecko/), + WebKitVersion = navigator.userAgent.match(/AppleWebKit\/([\d\.]+)/); + if (WebKitVersion) { + WebKitVersion = WebKitVersion[1]; + } + if (GeckoVersion) { + GeckoVersion = GeckoVersion[1]; + } + + if(Chrome || WebKitVersion >= '534.28' || + (GeckoVersion >= '2.0')) { + $('#firefogg').hide(); + $('#submit').hide(); + $('#file').change(function(e) { + if(this.files.length>0) { + $('#file').hide(); + $('#upload').show(); + } + }); + if($('#file')[0].files.length>0) { + $('#file').hide(); + $('#upload').show(); + } else { + $('#upload').hide(); + } + $('#upload').click(function(e) { + $('#upload').hide(); + e.stopPropagation(); + var file = $('#file')[0].files[0]; + var data = { + 'firefogg': 1, + 'name': file.name // Send filename here since Blobs will have no name later + }; + var ogg = FirefoggUploader(file, add_url, data, function(ogg) { //callback + if(ogg.resultUrl) { + document.location.href = ogg.resultUrl; + } else { + $('#progressstatus').html(ogg.status); + } + }, function(progress) { //progress callback + //do something with status and progress, i.e. set progressbar width: + $('#progress').css('width', parseInt(progress*100, 10) +'%'); + $('#progressstatus').html(parseInt(progress*100, 10) + '% - ' + ogg.status); + }); + + $('#submitFile').hide(); + $('#progressbar').show(); + $('#progressbar').width(200); + $('#progressbar').css('background-color', '#eee'); + $('#progressbar').html('
uploading
'); + }); + } +}); diff --git a/edgware/urls.py b/edgware/urls.py index 5ba5ab8..f853d60 100644 --- a/edgware/urls.py +++ b/edgware/urls.py @@ -26,7 +26,7 @@ urlpatterns = patterns('', (r'^comments/', include('django.contrib.comments.urls')), (r'^edit/', include('editor.urls')), (r'files/', include('files.urls')), - (r'^gallery/', 'files.views.gallery'), +# (r'^gallery/', 'files.views.gallery'), # Uncomment the next line to enable the admin: (r'^admin/', include(admin.site.urls)), (r'^accounts/login/$', 'django.contrib.auth.views.login'),