Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Williams <dcbw@redhat.com>2007-05-03 02:23:01 (GMT)
committer Dan Williams <dcbw@redhat.com>2007-05-03 02:23:01 (GMT)
commit3f480c14952e4db466ede0c0997b63822e4631fa (patch)
tree20fb1792d5922155f74d9875f2c5d75c022792ee
parent587bc3be19059e9d894f83c59fc0b998f9ac92a5 (diff)
Chunked glib-integrated HTTP server and url downloader classes
-rw-r--r--sugar/p2p/network.py172
1 files changed, 172 insertions, 0 deletions
diff --git a/sugar/p2p/network.py b/sugar/p2p/network.py
index 4504715..3636b2c 100644
--- a/sugar/p2p/network.py
+++ b/sugar/p2p/network.py
@@ -18,14 +18,18 @@
# pylint: disable-msg = W0221
import socket
+import os
import threading
import traceback
import xmlrpclib
import sys
import httplib
+import urllib
+import fcntl
import gobject
import SimpleXMLRPCServer
+import SimpleHTTPServer
import SocketServer
@@ -68,6 +72,174 @@ class GlibTCPServer(SocketServer.TCPServer):
self.handle_request()
return True
+
+class ChunkedGlibHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+ """RequestHandler class that integrates with Glib mainloop. It writes
+ the specified file to the client in chunks, returning control to the
+ mainloop between chunks.
+ """
+
+ CHUNK_SIZE = 4096
+
+ def __init__(self, request, client_address, server):
+ self._file = None
+ self._srcid = 0
+ SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, request, client_address, server)
+
+ def log_request(self, code='-', size='-'):
+ pass
+
+ def do_GET(self):
+ """Serve a GET request."""
+ self._file = self.send_head()
+ if self._file:
+ self._srcid = gobject.io_add_watch(self.wfile, gobject.IO_OUT | gobject.IO_ERR, self._send_next_chunk)
+ else:
+ f.close()
+ self._cleanup()
+
+ def _send_next_chunk(self, source, condition):
+ if condition & gobject.IO_ERR:
+ self._cleanup()
+ return False
+ if not (condition & gobject.IO_OUT):
+ self._cleanup()
+ return False
+ data = self._file.read(self.CHUNK_SIZE)
+ count = os.write(self.wfile.fileno(), data)
+ if count != len(data) or len(data) != self.CHUNK_SIZE:
+ self._cleanup()
+ return False
+ return True
+
+ def _cleanup(self):
+ if self._file:
+ self._file.close()
+ if self._srcid > 0:
+ gobject.source_remove(self._srcid)
+ self._srcid = 0
+ if not self.wfile.closed:
+ self.wfile.flush()
+ self.wfile.close()
+ self.rfile.close()
+
+ def finish(self):
+ """Close the sockets when we're done, not before"""
+ pass
+
+ def send_head(self):
+ """Common code for GET and HEAD commands.
+
+ This sends the response code and MIME headers.
+
+ Return value is either a file object (which has to be copied
+ to the outputfile by the caller unless the command was HEAD,
+ and must be closed by the caller under all circumstances), or
+ None, in which case the caller has nothing further to do.
+
+ ** [dcbw] modified to send Content-disposition filename too
+ """
+ path = self.translate_path(self.path)
+ f = None
+ if os.path.isdir(path):
+ for index in "index.html", "index.htm":
+ index = os.path.join(path, index)
+ if os.path.exists(index):
+ path = index
+ break
+ else:
+ return self.list_directory(path)
+ ctype = self.guess_type(path)
+ try:
+ # Always read in binary mode. Opening files in text mode may cause
+ # newline translations, making the actual size of the content
+ # transmitted *less* than the content-length!
+ f = open(path, 'rb')
+ except IOError:
+ self.send_error(404, "File not found")
+ return None
+ self.send_response(200)
+ self.send_header("Content-type", ctype)
+ self.send_header("Content-Length", str(os.fstat(f.fileno())[6]))
+ self.send_header("Content-Disposition", 'attachment; filename="%s"' % os.path.basename(path))
+ self.end_headers()
+ return f
+
+class GlibURLDownloader(gobject.GObject):
+ """Grabs a URL in chunks, returning to the mainloop after each chunk"""
+
+ __gsignals__ = {
+ 'finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT]))
+ }
+
+ CHUNK_SIZE = 4096
+
+ def __init__(self, url, destdir=None):
+ self._url = url
+ if not destdir:
+ destdir = "/tmp"
+ self._destdir = destdir
+ self._srcid = 0
+ self._fname = None
+ self._outf = None
+ gobject.GObject.__init__(self)
+
+ def start(self):
+ self._info = urllib.urlopen(self._url)
+ self._fname = _get_filename_from_headers(info.headers)
+ if not self._fname:
+ import tempfile
+ garbage, path = urllib.splittype(url)
+ garbage, path = urllib.splithost(path or "")
+ path, garbage = urllib.splitquery(path or "")
+ path, garbage = urllib.splitattr(path or "")
+ suffix = os.path.splitext(path)[1]
+ (outf, self._fname) = tempfile.mkstemp(suffix=suffix, dir=self._destdir)
+ else:
+ outf = open(os.path.join(self._destdir, self._fname), "w")
+
+ fcntl.fcntl(self._info.fp.fileno(), fcntl.F_SETFD, os.O_NDELAY)
+ self._srcid = gobject.io_add_watch(self._info.fp.fileno(), gobject.IO_IN, self._read_next_chunk)
+
+ def _get_filename_from_headers(self, headers):
+ if not headers.has_key("Content-Disposition"):
+ return None
+
+ ftag = "filename="
+ data = headers["Content-Disposition"]
+ fidx = data.find(ftag)
+ if fidx < 0:
+ return None
+ fname = data[fidx+len(ftag):]
+ if fname[0] == '"' or fname[0] == "'":
+ fname = fname[1:]
+ if fname[len(fname)-1] == '"' or fname[len(fname)-1] == "'":
+ fname = fname[:len(fname)-1]
+ return fname
+
+ def _read_next_chunk(self, source, condition):
+ if not (condition & gobject.IO_IN):
+ self.cleanup()
+ self.emit("finished", None)
+ return False
+
+ data = info.fp.read(self.CHUNK_SIZE)
+ count = outf.write(data)
+ if len(data) < self.CHUNK_SIZE:
+ self.cleanup()
+ self.emit("finished", self._fname)
+ return False
+ return True
+
+ def cleanup(self):
+ if self._srcid > 0:
+ gobject.source_remove(self._srcid)
+ self._srcid = 0
+ del self._info
+ self._outf.close()
+
+
class GlibXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
""" GlibXMLRPCRequestHandler