Hi Sean,

Attached is some code using MochiKit which provides an AJAX upload progress bar. I've been meaning to write it up as an article for the wiki but you might find it useful as is. You'll need a progress-bar.png file for the background.

WARNING: It has been a long time since I wrote this so check through it before running it, I don't guarantee it works!

It might get you started anyway. I'll try and write it up properly at the weekend because there are a few subtleties in there that are quite tricky if you aren't aware of them.

James


Matt Feifarek wrote:
This from Paste is probably where you want to start digging:
http://pythonpaste.org/module-paste.progress.html

On 1/4/07, *Sean Davis* <[EMAIL PROTECTED] <mailto:[EMAIL PROTECTED]>> wrote:

    Does pylons have built-in support for upload progress bar that I
    have missed?  If not, can someone point me in the direction of how
    this might be done?  It looks like I need to use an iframe for the
    actual upload, an AJAX call to stat the filesize on a regular basis,
    and a way to read the uploaded file in chunks.  In particular, does
pylons offer a way to do the last part?


>


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"pylons-discuss" group.
To post to this group, send email to pylons-discuss@googlegroups.com
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/pylons-discuss?hl=en
-~----------~----~----~----~------~----~------~--~---

"""
Example Python WSGI file upload with AJAX progress information.

Features
========

* Graphical progress bar
* Transfer informtation
* Cancel option

Tested On
=========

* Firefox 2rc2, Windows XP
* Internet Explorer 6, Windows XP
* Opera 9, Windows XP

ToDo
====

* Multiple uploads
* FireFox download-style graphics
* Maximum filesize

License
=======

Copyright (c) 2006 James Gardner james _at_ pythonweb.org

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.  """

import time
import os
import cgi
import simplejson
from paste.urlmap import URLMap
from paste.cascade import Cascade
from paste.urlparser import StaticURLParser
from paste.httpserver import serve

data_dir = "/var/www/web1/data/"
public_dir = "/var/www/web1/public/"

def index(environ, start_response):
   id = environ['PATH_INFO'].split('/')[-1]
   start_response('200 OK', [('Content-type','text/html')])
   data = """
   <html>
    <head>
       <title>Make a Montage</title>
       <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
       <style type="text/css">
           .invisible { display: none; }
       </style>
       <script type="text/javascript">
           function makeInvisible(elem) {
               addElementClass(elem, "invisible");
           }
           function makeVisible(elem) {
               removeElementClass(elem, "invisible");
           }
           var status = {
               "%(id)s":"active"
           };
           var started = {};
           function restore(id){
               status[id] = "stopped";
               swapDOM(getElement("status"), P({"id": "status"}, "Cancelled."));
} function format_size(bytes) {
               if (bytes>(1024*1024)) {
                   return numberFormatter("###.#")(bytes/(1024*1024))+"MB";
               } else {
                   return numberFormatter("###")(bytes/(1024))+"KB";
               }
} function start_upload(id){
               status[id] = "active";
               started[id] = new Date()
               makeInvisible(getElement("form0"));
               makeVisible(getElement("bar"));
               makeVisible(getElement("status"));
               swapDOM(getElement("status"), P({"id": "status"}, "Starting 
upload..."));
               return upload_progress(id);
           }
           function update_bar(percent){
               var bar = getElement("bar")
               if (compare(bar.style.width, percent*200) != 0){
                   var width = truncToFixed(percent*200, 0);
                   var newBar = DIV(
                      {"id": "bar"},
                      DIV(
                          {"style":"width: 200px; clear: both; border: 1px solid 
#666; background: #42D745 url('progress-bar.png') repeat-x;"},
                          DIV(
                              {"style":"width: "+width+"px; margin-left: 
"+(200-width)+"px; background: #fff; height: 13px;"}
                          )
                      )
                  )
                  swapDOM(bar, newBar);
               }
           }
           function upload_progress(id){
               var d = loadJSONDoc("/monitor/"+id, {'q':toISOTimestamp(new 
Date())});
               logDebug('deferred created: '+d);
               var success = function (meta) {
               if (compare(status[id], "stopped") != 0) {
                   if (compare(meta.size, meta.transferred) == 0){
                       swapDOM(getElement("status"), P({"id": "status"}, 
"Successfully uploaded ", TT(meta.filename)));
                   } else {
                       var speed = (meta.transferred)/((new 
Date()-started[id])/1000);
                       var remaining = meta.size - meta.transferred
                       if (remaining < 0){remaining = 1;}
                           update_bar(remaining/meta.size);
                           var newStatus = P({"id": "status"}, "Uploaded: "+format_size(meta.transferred)+" of "+format_size(meta.size)+" at "+format_size(speed)+"/sec; 
"+numberFormatter("#,###")(remaining/speed)+" sec(s) remain ", A({"href":"/cancel.html", "onclick":"restore('"+id+"');", 
"target":"form0_iframe"}, "Cancel"));
                           swapDOM(getElement("status"), newStatus);
                           return upload_progress(id);
                       };
                   };
               };
               var failure = function (err) {
                 alert("Failed to get progress on the upload."+err);
               };
               d.addCallbacks(success, failure);
               return true;
           }
       </script>
    </head>
<body> <p>Please select the images from which you wish to make a montage</p>
       <form class="visible" id="form0" action="/upload/%(id)s" enctype="multipart/form-data" 
method="POST" target="form0_iframe">
           <input type="file" name="upload" size="40" /><br/>
           <input type="submit" value="Upload images" 
onclick="start_upload('%(id)s')" />
       </form>
   <div id="bar" class="invisible"></div>
   <div id="status" class="invisible"></div>
   <iframe name="form0_iframe" scroll="0" frameborder="0" id="form0_iframe" width="0" 
height="0">Empty.</iframe>
   <a href="javascript:void(createLoggingPane(true));">Inline LoggingPane</a>
   </body>
   </html>
   """
   return data%{'id':id}

def monitor(environ, start_response): filename = data_dir+environ['PATH_INFO'].split('/')[-1]
   # Don't return straight away, give the updates every 1 second
   time.sleep(1)
   try:
       fp = open(filename+'.meta', 'rb')
       meta = simplejson.loads(fp.read())
       fp.close()
       meta["transferred"] = os.stat(filename)[6]
   except IOError:
       meta = {"size":None, "filename":None, "transferred":0}
   start_response('200 OK', [('Content-type','text/json')])
   print "Monitor: ", simplejson.dumps(meta)
   return [simplejson.dumps(meta)]

def upload_factory(server_filename):
class UploadFieldStorage(cgi.FieldStorage):
       """
       Custom FieldStorage implementation which saves the data to a file with
       a known location as it is being read. We can then query the file size
       of this file from other places in the code.
       """
       def __init__(self, fp=None, headers=None, outerboundary="",
                    environ=os.environ, keep_blank_values=0, strict_parsing=0):
           self.server_filename = server_filename
           return cgi.FieldStorage.__init__(self, fp, headers, outerboundary, 
environ, keep_blank_values, strict_parsing)

       def make_file(self, binary=None):
           """
           The 'binary' argument is unused -- the file is always opened
           in binary mode.
           """
           print "Upload: ", server_filename
           if os.path.exists(server_filename):
               raise Exception('File %s already exists'%server_filename)
           return open(server_filename, mode="w+b")

   return UploadFieldStorage

def upload(environ, start_response):
   print "here"
   filename = data_dir+environ['PATH_INFO'].split('/')[-1]
   if os.path.exists(filename):
       raise Exception('File %s already exists'%filename)
   fp = open(filename+'.meta', 'wb')
   #print int(environ['CONTENT_LENGTH'])        
   #print len(str(environ['CONTENT_TYPE']))
   #print environ
   size_guess = 
int(environ['CONTENT_LENGTH'])#-len(str(environ['CONTENT_TYPE']))-139
   fp.write(simplejson.dumps({"size": size_guess}))
   fp.close()

   start_response('200 OK', [('Content-type','text/plain')])
   field_storage = upload_factory(filename)(environ=environ, 
strict_parsing=True, fp=environ['wsgi.input'])
   original_filename = field_storage["upload"].filename
# Handle small files too
   if not os.path.exists(filename):
       fp = open(filename, "w+b")
       fp.write(field_storage["upload"].file.read())
       fp.close()

   meta  = {"size": os.stat(filename)[6], "filename": original_filename}
   fp = open(filename+'.meta', 'wb')
   fp.write(simplejson.dumps(meta))
   fp.close()

   return ['All done.']

def test(environ, start_response):
   start_response("200 OK", [("Content-type", "text/plain")])
   return [str(x)+ " => "+str(k)+"\n" for x, k in environ.items()]

# Set up the URL map for the app
app = URLMap()
app['/'] = index
app['/monitor'] = monitor
app['/test'] = test
app['/upload'] = upload

# Setup the app, combined with a static file parser for the javascript files
app = Cascade([StaticURLParser(public_dir), app])

# We need a multithreaded server so it can handle the AJAX calls during the 
upload
httpd = serve(app, port=8005, host='0.0.0.0')
httpd.serve_forever()

Reply via email to