Skip to content

Commit 51e93b3

Browse files
author
Peter Kuma
committed
Refactor iframe upload and fix store size limit.
* Make iframe upload respond with JSON, so that errors can be reported and items can be updated in-place when finished. * New method: Fileshack.uploadIFrame(). * Fix ItemModel.update(json). It would modify its parameter (not expected). * Store size limit has not been enforced. Store.total() is now calculated by summing the size field from DB. * Add related_name="items" to store ForeignKey in Item. * Only show Details in error dialog when window.btoa (base64 conversion) is supported by the browser.
1 parent 284cb62 commit 51e93b3

File tree

5 files changed

+93
-48
lines changed

5 files changed

+93
-48
lines changed

fileshack/models.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,8 @@ def get_absolute_url(self):
4949
return url
5050

5151
def total(self):
52-
total = 0
53-
for dirpath, dirnames, filenames in os.walk(os.path.join(settings.MEDIA_ROOT, "fileshack", self.media)):
54-
for f in filenames:
55-
fp = os.path.join(dirpath, f)
56-
total += os.path.getsize(fp)
57-
return total
52+
if self.items.count() == 0: return 0
53+
return self.items.all().aggregate(Sum("size"))["size__sum"]
5854

5955
def item_upload_to(instance, filename):
6056
key = ""
@@ -64,7 +60,7 @@ def item_upload_to(instance, filename):
6460
return os.path.join(instance.store.media, key, filename)
6561

6662
class Item(Model):
67-
store = ForeignKey(Store, verbose_name=_("store"))
63+
store = ForeignKey(Store, verbose_name=_("store"), related_name="items")
6864
fileobject = FileField(_("file"), upload_to=item_upload_to)
6965
created = DateTimeField(_("created"), auto_now_add=True)
7066
uploaded = DateTimeField(_("uploaded"), auto_now_add=True)

fileshack/static/fileshack/js/fileshack.js

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ var FileShack = new Class({
4343

4444
var dropbox = $('dropbox');
4545
var dropboxInput = $('dropbox-input');
46-
var iframe = $('iframe');
4746

4847
// Drag & Drop.
4948
if (typeof dropbox.addEventListener != 'undefined' &&
@@ -79,18 +78,7 @@ var FileShack = new Class({
7978
dropboxInput.click();
8079
window.setTimeout(function() { if (!clickDelegated) this_.fallback(); }, 100);
8180
} else { // Fallback to upload by the iframe hack (IE).
82-
if (typeof iframe.contentDocument != 'undefined')
83-
var form = iframe.contentDocument.forms[0];
84-
else
85-
var form = iframe.getDocument().forms[0];
86-
form.file.onchange = function() {
87-
var item = this_.upload(form);
88-
iframe.onload = function() {
89-
item.model.remove();
90-
this_.update();
91-
};
92-
};
93-
form.file.click();
81+
this_.uploadIFrame();
9482
}
9583
});
9684
}
@@ -195,7 +183,7 @@ var FileShack = new Class({
195183
});
196184

197185
// If this is a File, ask the user about resume.
198-
if (i && data instanceof File) {
186+
if (i && typeof File != 'undefined' && data instanceof File) {
199187
var c = confirm('A stale file with the same name and size has been found.\nDo you want to resume uploading?');
200188
if (c) {
201189
item = i;
@@ -241,6 +229,43 @@ var FileShack = new Class({
241229
return item;
242230
},
243231

232+
uploadIFrame: function() {
233+
var this_ = this;
234+
var iframe = $('iframe');
235+
var form = iframe.contentDocument.forms[0];
236+
237+
form.file.onchange = function() {
238+
var item = this_.upload(form);
239+
iframe.onload = function() {
240+
var responseText = iframe.contentDocument.body.innerHTML;
241+
iframe.onload = null;
242+
iframe.src = iframe.src;
243+
try {
244+
var response = JSON.decode(responseText);
245+
} catch(e) {
246+
item.onError({
247+
label: 'Upload failed',
248+
message: 'The server responded with an invalid message',
249+
details: responseText
250+
});
251+
return;
252+
}
253+
item.model.update(response.item);
254+
if (response.status != 'success') {
255+
item.onError({
256+
label: response.error_label,
257+
message: response.error_message,
258+
details: response.details
259+
});
260+
return;
261+
}
262+
item.model.set('type', 'complete');
263+
item.model.update(response.item);
264+
};
265+
};
266+
form.file.click();
267+
},
268+
244269
removeStaleItems: function(validIds) {
245270
Object.each(this.items.all(), function(item) {
246271
if (item.model.type != 'pending' && !validIds.contains(item.model.id))

fileshack/static/fileshack/js/models.js

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,26 +72,24 @@ var Item = new Class({
7272
},
7373

7474
update: function(json) {
75-
json.modified = new Date().parse(json.modified);
76-
json.uploaded = new Date().parse(json.uploaded);
77-
json.created = new Date().parse(json.created);
78-
79-
if (this.type == 'pending') {
80-
// Do not update status and size of pending items.
81-
json.status = this.status;
82-
json.size = this.size;
83-
}
84-
85-
if (json.status == 'READY')
75+
if (this.type == 'pending')
76+
; // Do nothing.
77+
else if (json.status == 'READY')
8678
this.type = 'complete';
87-
if (json.status == 'UPLOADING' && this.type != 'pending')
79+
else if (json.status == 'UPLOADING')
8880
this.type = 'unfinished';
89-
if (json.status == 'STALE')
81+
else if (json.status == 'STALE')
9082
this.type = 'stale';
91-
//this.parent(json);
83+
9284
for (var attr in json) {
85+
if (this.type == 'pending' && (attr == 'status' || attr == 'size'))
86+
continue;
9387
this[attr] = json[attr];
9488
}
89+
this.modified = new Date().parse(json.modified);
90+
this.uploaded = new Date().parse(json.uploaded);
91+
this.created = new Date().parse(json.created);
92+
9593
this.fireEvent('change');
9694
},
9795

fileshack/static/fileshack/js/views.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ var ItemView = new Class({
126126
this.error_label.set('text', e.label);
127127
if (e.message_html) this.error_message.set('html', e.message_html);
128128
else this.error_message.set('text', e.message)
129-
if (e.details) {
129+
if (e.details && window.btoa) {
130130
this.error_details.setStyle('display', 'block');
131131
this.error_details.href = 'data:text/html;base64,' + window.btoa(e.details);
132132
}

fileshack/views.py

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -134,18 +134,44 @@ def index(request, store):
134134
@require_store
135135
@require_login
136136
def iframe(request, store):
137-
if request.method == "POST" and request.FILES.has_key("file"):
138-
f = request.FILES["file"]
139-
item = Item()
140-
item.store = store
141-
item.fileobject.save(urllib.unquote(f.name), f)
142-
item.size = f.size
143-
item.size_total = f.size
144-
item.save()
137+
if request.method != "POST":
138+
t = loader.get_template("fileshack/iframe.html")
139+
c = RequestContext(request)
140+
return HttpResponse(t.render(c))
145141

146-
t = loader.get_template("fileshack/iframe.html")
147-
c = RequestContext(request)
148-
return HttpResponse(t.render(c))
142+
if not request.FILES.has_key("file"):
143+
return HttpResponseForbidden()
144+
f = request.FILES["file"]
145+
146+
item = Item()
147+
item.fileobject.name = urllib.unquote(f.name)
148+
item.store = store
149+
item.size = f.size
150+
item.size_total = f.size
151+
152+
if store.item_limit and f.size > store.item_limit*1024*1024:
153+
return HttpResponse(JSONEncoder().encode({
154+
"status": "itemlimitreached",
155+
"error_label": "Upload failed",
156+
"error_message": "Item size is limited to %d MB" % store.item_limit,
157+
"item": item.simple(),
158+
}))
159+
160+
if store.store_limit and store.total() + f.size > store.store_limit*1024*1024:
161+
return HttpResponse(JSONEncoder().encode({
162+
"status": "storelimitreached",
163+
"error_label": "Upload failed",
164+
"error_message": "The store size limit of %d MB has been reached" % store.store_limit,
165+
"item": item.simple(),
166+
}))
167+
168+
item.fileobject.save(urllib.unquote(f.name), f)
169+
item.save()
170+
171+
return HttpResponse(JSONEncoder().encode({
172+
"status": "success",
173+
"item": Item.objects.get(pk=item.pk).simple()
174+
}))
149175

150176
@never_cache
151177
@require_store
@@ -185,7 +211,7 @@ def upload(request, store, id):
185211
}
186212
return HttpResponseServerError(JSONEncoder().encode(data))
187213

188-
if store.store_limit and size_total and store.total() + size_total > store.store_limit*1024*1024:
214+
if store.store_limit and size_total and store.total() + size_total - offset > store.store_limit*1024*1024:
189215
data = {
190216
"status": "storelimitreached",
191217
"error_label": "Upload failed",

0 commit comments

Comments
 (0)