Merge lp:~ivle-dev/ivle/mediahandlers into lp:ivle

Proposed by David Coles
Status: Merged
Merged at revision: 1830
Proposed branch: lp:~ivle-dev/ivle/mediahandlers
Merge into: lp:ivle
Diff against target: 444 lines (+202/-60)
6 files modified
ivle/dispatch/request.py (+5/-0)
ivle/webapp/filesystem/browser/media/browser.css (+1/-0)
ivle/webapp/filesystem/browser/media/browser.js (+163/-33)
ivle/webapp/filesystem/browser/media/editor.js (+25/-24)
ivle/webapp/filesystem/serve/__init__.py (+1/-0)
services/serveservice (+7/-3)
To merge this branch: bzr merge lp:~ivle-dev/ivle/mediahandlers
Reviewer Review Type Date Requested Status
Matt Giuca Approve
David Coles Approve
Review via email: mp+31040@code.launchpad.net

Description of the change

A series of HTML5 based media handlers using the <audio> and <video> tags. This replaces the previous page that just showed a download link (which is already available on the menu).

Known issues are bug #588285. External BHO will not be able to play media due to not having IVLE cookie.

To post a comment you must log in.
Revision history for this message
Matt Giuca (mgiuca) wrote :
Download full text (5.7 KiB)

It looks fantastic! But I see some problems.

* The type handlers. Firstly, looking at the type_handlers dictionary in ivle/webapp/filesystem/browser/media/browser.js, the dictionary only maps to "text" and "video" handlers. I'm a bit confused as to how audio and image handlers work (but I have seen them work, so ... please explain).

* Secondly, I'm not quite sure how the file extension maps onto the handler, but it seems to be a multi-stage process. First, the Python side maps the filename onto a MIME type, right? This would be something like "audio/ogg" or "video/ogg". Then our JavaScript maps the MIME type onto a handler type, like "video" or "audio". The problem is that if you have a ".ogg" file, Python will (I think) map it onto "audio/ogg", so our player will present an OGG Theora video with a ".ogg" extension as an audio file. I suppose there's nothing we can do about this except tell people to use ".ogv" instead. (So this is a non-issue, unless you can think of a better solution.)

* I'm seeing my test video with a length of 1 second, even though it continues playing for minutes. My test video is http://people.xiph.org/~maikmerten/demos/arctic_giant.html -- maybe there's something wrong with the video. Can you offer any suggestions as to why this is? (I see that you added a content-length output to the download link, so that should be working fine. Could be related to the following problem.)

All of the above are permissible. I am rejecting this merge due to the following:

* The major problem: I'm seeing in Firebug that the video is being downloaded in full *twice*. I know that Mozilla Firefox downloads the video multiple times to do buffering, and Mozilla has told us that this doesn't actually equate to double the bandwidth as they cache it. But this is not that -- the browser actually downloads the full file from two separate URLs.

1. GET http://ivle.localhost/fileservice/studenta/stuff/arctic_giant.ogv
Response content-type: text/plain
Response: {"listing": {".": {"isdir": false, "mtime_nice": "Tue Jul 27 2010, 11:58pm", "mtime_short": "Today", "mtime": 1280275123.6, "type": "video/ogg", "type_nice": "OGV file", "size": 23916003}}}

2. GET http://ivle.localhost/fileservice/studenta/stuff/arctic_giant.ogv?return=contents
Response Content-Type: video/ogg
Response body: Full 22MB video

At this point, the video panel appears on the screen in the correct size, with a 1 second total time, and shows a loading throbber.

3. GET http://ivle.localhost/download/studenta/stuff/arctic_giant.ogv
Response Content-Type: video/ogg
Response Content-Length: 23916003
Response body: Full 22MB video

Now the video starts playing.

So here's what's happening:

1. The JavaScript function "navigate" is called. This makes an AJAX call which is request #1 (GET /fileservice). This is a quick JSON response with the data for the file. Upon receiving the contents, it calls handle_response.
2. The "handle_response" function is called with the JSON dictionary for the file. At the bottom of this function, for all files (but not directories), it makes a second AJAX call which is request #2 (GET /fileservice?return=contents). "/* This time, get the contents of th...

Read more...

review: Needs Fixing
Revision history for this message
David Coles (dcoles) wrote :
Download full text (4.8 KiB)

> It looks fantastic! But I see some problems.
>
> * The type handlers. Firstly, looking at the type_handlers dictionary in
> ivle/webapp/filesystem/browser/media/browser.js, the dictionary only maps to
> "text" and "video" handlers. I'm a bit confused as to how audio and image
> handlers work (but I have seen them work, so ... please explain).

type_handers is just an override mechanism to allow us to map special MIME types (mainly application/x) to a specific hander. By default it uses the first part of the content type. From the code:

 * If a file is not on the list, its default action is determined by the first
 * part of its content type, where "text/*", "image/*" and "audio/*" are
 * treated as above, and other types are simply treated as binary.

> * Secondly, I'm not quite sure how the file extension maps onto the handler,
> but it seems to be a multi-stage process. First, the Python side maps the
> filename onto a MIME type, right? This would be something like "audio/ogg" or
> "video/ogg". Then our JavaScript maps the MIME type onto a handler type, like
> "video" or "audio". The problem is that if you have a ".ogg" file, Python will
> (I think) map it onto "audio/ogg", so our player will present an OGG Theora
> video with a ".ogg" extension as an audio file. I suppose there's nothing we
> can do about this except tell people to use ".ogv" instead. (So this is a non-
> issue, unless you can think of a better solution.)

Yes. We just mimetypes.guess_type the MIME type from a file extension. Older versions of Python return "application/ogg", newer return "audio/ogg". It's now recommended that one should treat all ".ogg" files as audio. See http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions.

I'll fix the override for "application/ogg". This should be mapped to "audio".

> * I'm seeing my test video with a length of 1 second, even though it continues
> playing for minutes. My test video is
> http://people.xiph.org/~maikmerten/demos/arctic_giant.html -- maybe there's
> something wrong with the video. Can you offer any suggestions as to why this
> is? (I see that you added a content-length output to the download link, so
> that should be working fine. Could be related to the following problem.)

See https://bugs.edge.launchpad.net/ivle/+bug/490691/comments/3.

In short, unless we set the X-Content-Duration header or allow Partial Content requests the browser can't work out how long the file is until the end since duration is not in the ogg headers.

> All of the above are permissible. I am rejecting this merge due to the
> following:
>
> * The major problem: I'm seeing in Firebug that the video is being downloaded
> in full *twice*. I know that Mozilla Firefox downloads the video multiple
> times to do buffering, and Mozilla has told us that this doesn't actually
> equate to double the bandwidth as they cache it. But this is not that -- the
> browser actually downloads the full file from two separate URLs.

Right. This is bug #588337.

> Now this was a problem before as well, because we would download the contents
> 1 time if it was binary when we should download 0. But it's much worse with
> video because we download the c...

Read more...

Revision history for this message
Matt Giuca (mgiuca) wrote :

> type_handers is just an override mechanism to allow us to map special MIME
> types (mainly application/x) to a specific hander. By default it uses the
> first part of the content type.

Oh OK. That makes sense.

> Yes. We just mimetypes.guess_type the MIME type from a file extension. Older
> versions of Python return "application/ogg", newer return "audio/ogg". It's
> now recommended that one should treat all ".ogg" files as audio. See
> http://wiki.xiph.org/index.php/MIME_Types_and_File_Extensions.
>
> I'll fix the override for "application/ogg". This should be mapped to "audio".

OK. I'm happy with this behaviour (after that fix is applied).

> In short, unless we set the X-Content-Duration header or allow Partial Content
> requests the browser can't work out how long the file is until the end since
> duration is not in the ogg headers.

Oh yes. I remember this issue now. We should probably set X-Content-Duration. But that can be done after the merge (and after 1.0.2). Is there a bug on this? If not I'll file one now.

> > * The major problem: I'm seeing in Firebug that the video is being
> downloaded
> > in full *twice*. I know that Mozilla Firefox downloads the video multiple
> > times to do buffering, and Mozilla has told us that this doesn't actually
> > equate to double the bandwidth as they cache it. But this is not that -- the
> > browser actually downloads the full file from two separate URLs.
>
> Right. This is bug #588337.

OK. I didn't notice that bug before. I've posted my suggestion to that bug. It should probably be fixed in trunk, then merged to mediahandlers.

With those two fixes (application/ogg is audio, and the double-download bug), I'll accept the merge.

lp:~ivle-dev/ivle/mediahandlers updated
1797. By David Coles

Map 'application/ogg' to audio hander as recomended by XIPH. Note: Newer versions of Python's guess_type map '.ogg' to 'audio/ogg' anyway.

1798. By David Coles

Change handle_content_response so that we only fetch file contents inside-
fetch_text and not for all media types.

Also remove present_editorhead since it's only useful action is an unreachable
codepath and requires threading some otherwise unneeded varaibles.

1799. By David Coles

Add generic <object> media handler for suporting other media types (such as SVG)

1800. By David Coles

Use "?return=contents" URL instead of '/download/' since "Content-Disposition:
attachment" causes issues with certain embeded media (mainly SVG).

NB: The URLs generated do not support browsing revisions. See bug #610745.

Revision history for this message
David Coles (dcoles) wrote :

Ok. Fixed both issues. The double-download fix is pretty much done as you described.

Also added a generic object handler to you can view SVG files (does not work with handle_image) and use ?return=content URLs for media rather than download URLs (with extra Content-Disposition headers). Still doesn't fix Chomium's issue with not playing ogg media from IVLE (will have to open a bug if this is merged as is).

Revision history for this message
Matt Giuca (mgiuca) wrote :

I'd say open a bug on that last issue and merge as-is.

Just tried to merge; got 2 merge conflicts. Must have been with stuff Will has been doing today. Can you merge from trunk into mediahandlers, then I'll approve.

review: Needs Fixing
Revision history for this message
David Coles (dcoles) wrote :

On 28 July 2010 17:22, Matt Giuca <email address hidden> wrote:

> Review: Needs Fixing
> I'd say open a bug on that last issue and merge as-is.
>
> Just tried to merge; got 2 merge conflicts. Must have been with stuff Will
> has been doing today. Can you merge from trunk into mediahandlers, then I'll
> approve.
> --
> https://code.launchpad.net/~ivle-dev/ivle/mediahandlers/+merge/31040<https://code.launchpad.net/%7Eivle-dev/ivle/mediahandlers/+merge/31040>
> You proposed lp:~ivle-dev/ivle/mediahandlers for merging.
>

Right. I'll open the bug now - won't be able to merge until I get home (At
"work" waiting for people to head home).

lp:~ivle-dev/ivle/mediahandlers updated
1801. By David Coles

Merge from trunk

Revision history for this message
David Coles (dcoles) wrote :

Resolved conflicts with trunk. Mostly just due to cjson->json changes.

Will merge into trunk.

review: Approve
Revision history for this message
Matt Giuca (mgiuca) wrote :

I say you can merge too!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ivle/dispatch/request.py'
2--- ivle/dispatch/request.py 2010-07-03 11:03:10 +0000
3+++ ivle/dispatch/request.py 2010-07-28 10:48:51 +0000
4@@ -95,6 +95,8 @@
5 in class Request.
6 content_type (write)
7 String. The Content-Type (mime type) header value.
8+ content_length (write)
9+ Integer. The number of octets to be transfered.
10 location (write)
11 String. Response "Location" header value. Used with HTTP redirect
12 responses.
13@@ -148,6 +150,7 @@
14 # Default values for the output members
15 self.status = Request.HTTP_OK
16 self.content_type = None # Use Apache's default
17+ self.content_length = None # Don't provide Content-Length
18 self.location = None
19 # In some cases we don't want the template JS (such as the username
20 # and public FQDN) in the output HTML. In that case, set this to 0.
21@@ -176,6 +179,8 @@
22 # Prepare the HTTP and HTML headers before the first write is made
23 if self.content_type != None:
24 self.apache_req.content_type = self.content_type
25+ if self.content_length:
26+ self.apache_req.set_content_length(self.content_length)
27 self.apache_req.status = self.status
28 if self.location != None:
29 self.apache_req.headers_out['Location'] = self.location
30
31=== modified file 'ivle/webapp/filesystem/browser/media/browser.css'
32--- ivle/webapp/filesystem/browser/media/browser.css 2010-02-15 07:21:58 +0000
33+++ ivle/webapp/filesystem/browser/media/browser.css 2010-07-28 10:48:51 +0000
34@@ -47,6 +47,7 @@
35 #actions {
36 padding: 0.3em 0.5em;
37 border-top: white 1px solid; /* Top edge of 3D effect */
38+ line-height: 1.6em;
39 }
40 /* class "choice" is for all choices, enabled and disabled. This is for both
41 * the <a> actions and <option> actions.
42
43=== modified file 'ivle/webapp/filesystem/browser/media/browser.js'
44--- ivle/webapp/filesystem/browser/media/browser.js 2010-07-20 05:42:59 +0000
45+++ ivle/webapp/filesystem/browser/media/browser.js 2010-07-28 10:48:51 +0000
46@@ -31,6 +31,7 @@
47 * "text" : When navigating to a text file, the text editor is opened.
48 * "image" : When navigating to an image, the image is displayed (rather than
49 * going to the text editor).
50+ * "video" : When navigation to a video file, a "play" button is presented.
51 * "audio" : When navigating to an audio file, a "play" button is presented.
52 * "binary" : When navigating to a binary file, offer it as a download through
53 * "serve".
54@@ -43,7 +44,9 @@
55 "application/x-javascript" : "text",
56 "application/javascript" : "text",
57 "application/json" : "text",
58- "application/xml" : "text"
59+ "application/xml" : "text",
60+ "application/ogg" : "audio",
61+ "image/svg+xml": "object"
62 };
63
64 /* Mapping MIME types to icons, just the file's basename */
65@@ -193,7 +196,8 @@
66 }
67
68 /** Determines the "handler type" from a MIME type.
69- * The handler type is a string, either "text", "image", "audio" or "binary".
70+ * The handler type is a string, either "text", "image", "video", "audio",
71+ * "object" or "binary".
72 */
73 function get_handler_type(content_type)
74 {
75@@ -205,7 +209,7 @@
76 { /* Based on the first part of the MIME type */
77 var handler_type = content_type.split('/')[0];
78 if (handler_type != "text" && handler_type != "image" &&
79- handler_type != "audio")
80+ handler_type != "video" && handler_type != "audio")
81 handler_type = "binary";
82 return handler_type;
83 }
84@@ -329,46 +333,37 @@
85 }
86 else
87 {
88- /* Need to make a 2nd ajax call, this time get the actual file
89- * contents */
90- callback = function(response)
91- {
92- /* Read the response and set up the page accordingly */
93- handle_contents_response(path, response);
94- }
95- /* Call the server and request the listing. */
96- if (url_args)
97- args = shallow_clone_object(url_args);
98- else
99- args = {};
100- /* This time, get the contents of the file, not its metadata */
101- args['return'] = "contents";
102- ajax_call(callback, service_app, path, args, "GET");
103+ /* Read the response and set up the page accordingly */
104+ var content_type = current_file.type;
105+ handle_contents_response(path, content_type, url_args);
106+
107 }
108 update_actions(isdir);
109 }
110
111-function handle_contents_response(path, response)
112+function handle_contents_response(path, content_type)
113 {
114 /* Treat this as an ordinary file. Get the file type. */
115- var content_type = response.getResponseHeader("Content-Type");
116+ //var content_type = response.getResponseHeader("Content-Type");
117 var handler_type = get_handler_type(content_type);
118- would_be_handler_type = handler_type;
119 /* handler_type should now be set to either
120- * "text", "image", "audio" or "binary". */
121+ * "text", "image", "video", "audio" or "binary". */
122 switch (handler_type)
123 {
124 case "text":
125- handle_text(path, response.responseText,
126- would_be_handler_type);
127+ handle_text(path, content_type);
128 break;
129 case "image":
130- /* TODO: Custom image handler */
131- handle_binary(path, response.responseText);
132+ handle_image(path);
133+ break;
134+ case "video":
135+ handle_video(path, content_type);
136 break;
137 case "audio":
138- /* TODO: Custom audio handler */
139- handle_binary(path, response.responseText);
140+ handle_audio(path, content_type);
141+ break;
142+ case "object":
143+ handle_object(path, content_type);
144 break;
145 case "binary":
146 handle_binary(path);
147@@ -540,6 +535,11 @@
148 */
149 function handle_binary(path)
150 {
151+ // Disable save button
152+ using_codepress = false;
153+ disable_save_if_safe();
154+
155+ // Show download link
156 var files = document.getElementById("filesbody");
157 var div = document.createElement("div");
158 files.appendChild(div);
159@@ -554,6 +554,141 @@
160 div.appendChild(par2);
161 }
162
163+/** Displays an image file.
164+ */
165+function handle_image(path)
166+{
167+ /* Disable save button */
168+ using_codepress = false;
169+ disable_save_if_safe();
170+
171+ /* URL */
172+ var url = app_url(service_app, path) + "?return=contents";
173+
174+ /* Image Preview */
175+ var img = $("<img />");
176+ img.attr("alt", path);
177+ img.attr("src", url);
178+
179+ /* Show Preview */
180+ var div = $('<div class="padding" />');
181+ div.append('<h1>Image Preview</h1>');
182+ div.append(img);
183+ $("#filesbody").append(div);
184+}
185+
186+/** Displays a video.
187+ */
188+function handle_video(path, type)
189+{
190+ /* Disable save button and hide the save panel */
191+ using_codepress = false;
192+ disable_save_if_safe();
193+
194+ /* URL */
195+ var url = app_url(service_app, path) + "?return=contents";
196+ var download_url = app_url(download_app, path);
197+
198+ /* Fallback Download Link */
199+ var link = $('<p>Could not display video in browser.<p><p><a /></p>');
200+ var a = link.find('a');
201+ a.attr("href", download_url);
202+ a.text("Download " + path);
203+
204+ /* Fallback Object Tag */
205+ var obj = $('<object />');
206+ obj.attr("type", type);
207+ obj.attr("data", url);
208+ obj.append(link);
209+
210+ /* HTML 5 Video Tag */
211+ var video = $('<video controls="true" autoplay="true" />');
212+ video.attr("src", url);
213+ var support = video[0].canPlayType && video[0].canPlayType(type);
214+ if (support != "probably" && support != "maybe") {
215+ // Use Fallback
216+ video = obj;
217+ }
218+
219+ /* Show Preview */
220+ var div = $('<div class="padding" />');
221+ div.append('<h1>Video Preview</h1>');
222+ div.append(video);
223+ $("#filesbody").append(div);
224+}
225+
226+/** Display audio content
227+ */
228+function handle_audio(path, type)
229+{
230+ /* Disable save button and hide the save panel */
231+ using_codepress = false;
232+ disable_save_if_safe();
233+
234+ /* URL */
235+ var url = app_url(service_app, path) + "?return=contents";
236+ var download_url = app_url(download_app, path);
237+
238+ /* Fallback Download Link */
239+ var link = $('<p>Could not display audio in browser.<p><p><a /></p>');
240+ var a = link.find('a');
241+ a.attr("href", download_url);
242+ a.text("Download " + path);
243+
244+ /* Fallback Object Tag */
245+ var obj = $('<object />');
246+ obj.attr("type", type);
247+ obj.attr("data", url);
248+ obj.append(link);
249+
250+ /* HTML 5 Audio Tag */
251+ var audio = $('<audio controls="true" autoplay="true" />');
252+ audio.attr("src", url);
253+ var support = audio[0].canPlayType && audio[0].canPlayType(type);
254+ if (support != "probably" && support != "maybe") {
255+ // Use Fallback
256+ audio = obj;
257+ }
258+
259+ /* Show Preview */
260+ var div = $('<div class="padding" />');
261+ div.append('<h1>Audio Preview</h1>');
262+ div.append(audio);
263+ $("#filesbody").append(div);
264+}
265+
266+/** Display generic object content
267+ */
268+function handle_object(path, content_type)
269+{
270+ /* Disable save button and hide the save panel */
271+ using_codepress = false;
272+ disable_save_if_safe();
273+
274+ /* URL */
275+ var url = app_url(service_app, path) + "?return=contents";
276+ var download_url = app_url(download_app, path);
277+
278+ /* Fallback Download Link */
279+ var link = $('<p><a /></p>');
280+ var a = link.find('a');
281+ a.attr("href", download_url);
282+ a.text("Download " + path);
283+
284+ /* Object Tag */
285+ var obj = $('<object width="100%" height="500px" />');
286+ obj.attr("type", content_type);
287+ obj.attr("data", url);
288+ obj.append('Could not load object');
289+
290+ /* Show Preview */
291+ var div = $('<div class="padding" />');
292+ div.append('<h1>Preview</h1>');
293+ div.append(obj);
294+ div.append(link);
295+ $("#filesbody").append(div);
296+}
297+
298 /* Enable or disable actions1 moreactions actions. Takes either a single
299 * name, or an array of them.*/
300 function set_action_state(names, which, allow_on_revision)
301@@ -841,11 +976,6 @@
302 var moreactions = document.getElementById("moreactions_area");
303 moreactions.setAttribute("style", "display: inline;");
304 }
305- else
306- {
307- var actions2_file = document.getElementById("actions2_file");
308- actions2_file.setAttribute("style", "display: inline;");
309- }
310
311 return;
312 }
313
314=== modified file 'ivle/webapp/filesystem/browser/media/editor.js'
315--- ivle/webapp/filesystem/browser/media/editor.js 2010-07-28 04:12:08 +0000
316+++ ivle/webapp/filesystem/browser/media/editor.js 2010-07-28 10:48:51 +0000
317@@ -58,25 +58,6 @@
318 window.onbeforeunload = confirm_beforeunload;
319 }
320
321-/** Presents the "editor heading" inserting it into a given element at
322- * the front. Note that the save widget is handled by the Python.
323- */
324-function present_editorhead(elem, path, handler_type)
325-{
326- var div = document.getElementById("actions2");
327-
328- /* Print a warning message if this is not actually a text file.
329- */
330- if (handler_type != "text")
331- {
332- var warn = dom_make_text_elem("p",
333- "Warning: You are editing a binary " +
334- "file, which explains any strange characters you may see. If " +
335- "you save this file, you could corrupt it.");
336- div.appendChild(warn);
337- }
338-}
339-
340 function highlighting_changed(select)
341 {
342 codemirror_language(select.value);
343@@ -84,23 +65,40 @@
344
345 /** Presents the text editor.
346 */
347-function handle_text(path, text, handler_type)
348+function handle_text(path, content_type, url_args)
349+{
350+ /* Need to make a 2nd ajax call, this time get the actual file
351+ * contents */
352+ callback = function(response)
353+ {
354+ /* Read the response and set up the page accordingly */
355+ handle_text_response(path, content_type, response.responseText);
356+ }
357+ /* Call the server and request the listing. */
358+ if (url_args)
359+ args = shallow_clone_object(url_args);
360+ else
361+ args = {};
362+ /* This time, get the contents of the file, not its metadata */
363+ args['return'] = "contents";
364+ ajax_call(callback, service_app, path, args, "GET");
365+}
366+
367+function handle_text_response(path, content_type, response_text)
368 {
369 /* Create a textarea with the text in it
370 * (The makings of a primitive editor).
371 */
372 var files = document.getElementById("filesbody");
373- /* Put our UI at the top */
374- present_editorhead(files, path, handler_type);
375
376 var div = document.createElement("div");
377 div.style.height = '100%';
378 files.appendChild(div);
379 var txt_elem = document.createElement("textarea");
380- txt_elem.value = text.toString();
381+ txt_elem.value = response_text.toString();
382 div.appendChild(txt_elem);
383 txt_elem.setAttribute("id", "editbox");
384- language = language_from_mime(current_file.type);
385+ language = language_from_mime(content_type);
386
387 // Assume plaintext if no type can be determined.
388 language = language ? language : "text";
389@@ -176,6 +174,9 @@
390 } else {
391 codemirror.setParser("DummyParser")
392 }
393+
394+ // Show actions bar
395+ $("#actions2_file").show();
396 }
397
398 function language_from_mime(mime)
399
400=== modified file 'ivle/webapp/filesystem/serve/__init__.py'
401--- ivle/webapp/filesystem/serve/__init__.py 2010-07-28 05:06:15 +0000
402+++ ivle/webapp/filesystem/serve/__init__.py 2010-07-28 10:48:51 +0000
403@@ -139,6 +139,7 @@
404 "attachment; filename=%s" %
405 response['name'].encode('utf-8'))
406 req.content_type = response['type'].encode('utf-8')
407+ req.content_length = response.get('size')
408 req.write(out)
409
410 class DownloadView(ServeView):
411
412=== modified file 'services/serveservice'
413--- services/serveservice 2010-07-20 04:52:31 +0000
414+++ services/serveservice 2010-07-28 10:48:51 +0000
415@@ -37,7 +37,7 @@
416 def determine_file_type(filename):
417 filetype = mimetypes.guess_type(filename)[0]
418 if filetype is None:
419- filetype = ivle.mimetypes.DEFAULT_MIMETYPE
420+ filetype = ivle.mimetypes.DEFAULT_MIMETYPE
421 return filetype
422
423 def throw_error(message, extra={}):
424@@ -142,14 +142,18 @@
425 zipmod.make_zip(zipbasepath, paths, zipfile)
426
427 print json.dumps({'type': zip_mimetype,
428- 'name': zipfilename.decode('utf-8')})
429+ 'name': zipfilename.decode('utf-8'),
430+ 'size': len(zipfile.getvalue()),
431+ })
432
433 stream = zipfile
434 stream.seek(0)
435 else:
436
437 print json.dumps({'type': determine_file_type(filename),
438- 'name': os.path.basename(filename).decode('utf-8')})
439+ 'name': os.path.basename(filename).decode('utf-8'),
440+ 'size': os.path.getsize(filename),
441+ })
442 stream = open(filename)
443
444 next = stream.read(1024)

Subscribers

People subscribed via source and target branches