diff options
author | Justin Lewis <jtl1728@rit.edu> | 2010-01-07 20:09:47 (GMT) |
---|---|---|
committer | Justin Lewis <jtl1728@rit.edu> | 2010-01-07 20:09:47 (GMT) |
commit | 413077ea5cfac8e6db6a10cbd895b8897da7b6eb (patch) | |
tree | 7bfc1e3ad8bc7909ae0a37dd85313e28316a1180 | |
parent | 91ae913f9c299e800c6383c382eea2dedc32212c (diff) |
Added server mode ability. Updated and moved GUI code.
Added throbber for blocking/cpu intensive tasks (like network and
packaging files) to make UI appear more robust.
-rw-r--r-- | FileShare.activity/FileShareActivity.py | 238 | ||||
-rw-r--r-- | FileShare.activity/GuiView.py | 113 | ||||
-rw-r--r-- | FileShare.activity/MultipartPostHandler.py | 132 | ||||
-rw-r--r-- | FileShare.activity/po/POTFILES.in | 2 | ||||
-rw-r--r-- | FileShare.activity/throbber.gif | bin | 0 -> 6820 bytes |
5 files changed, 401 insertions, 84 deletions
diff --git a/FileShare.activity/FileShareActivity.py b/FileShare.activity/FileShareActivity.py index 34465b0..6f66500 100644 --- a/FileShare.activity/FileShareActivity.py +++ b/FileShare.activity/FileShareActivity.py @@ -31,11 +31,16 @@ from sugar.graphics.objectchooser import ObjectChooser from sugar.graphics.alert import NotifyAlert from sugar.presence.tubeconn import TubeConnection from sugar import network +from sugar import profile +from GuiView import GuiView from TubeSpeak import TubeSpeak import FileInfo from hashlib import sha1 +import urllib, urllib2, MultipartPostHandler, httplib +import threading + import logging _logger = logging.getLogger('fileshare-activity') @@ -57,6 +62,7 @@ class FileShareActivity(Activity): def __init__(self, handle): Activity.__init__(self, handle) #wait a moment so that our debug console capture mistakes + gobject.threads_init() gobject.idle_add( self._doInit, None ) def _doInit(self, handle): @@ -88,13 +94,89 @@ class FileShareActivity(Activity): # Set to true when closing for keep cleanup self._close_requested = False + # If they are the server, ask them if they want to be the server and use + # P2P share system or if they want to do a client/server with an + # external server + + self._mode = "P2P" + prof = profile.get_profile() + + jabber_serv = None + #Need to check if on 82 or higher + if hasattr(profile, 'jabber_server'): + jabber_serv = profile.jabber_server + else: + #Higher, everything was moved to gconf + import gconf + client = gconf.client_get_default() + jabber_serv = client.get_string("/desktop/sugar/collaboration/jabber_server") + + if jabber_serv: + self.server_ip = jabber_serv + self.server_port= 14623 + + if self.isServer and self.check_for_server() and self._server_mode(): + self._mode = "SERVER" + self.isServer = False + # Build and display gui self._buildGui() - # Connect to shared and join calls - self.connect('shared', self._shared_cb) - self.connect('joined', self._joined_cb) + if self._mode == 'P2P': + # Connect to shared and join calls + self.connect('shared', self._shared_cb) + self.connect('joined', self._joined_cb) + + else: + #IN SERVER MODE, GET SERVER FILE LIST + def call(): + conn = httplib.HTTPConnection( self.server_ip, self.server_port) + conn.request("GET", "/filelist") + r1 = conn.getresponse() + if r1.status == 200: + data = r1.read() + conn.close() + self.incomingRequest('filelist',data) + else: + self._alert("Error getting file list") + self.show_throbber(False) + self.show_throbber(True, _("Please Wait... Requesting file list from server")) + threading.Thread(target=call).start() + + def check_for_server(self): + s_version = None + try: + conn = httplib.HTTPConnection( self.server_ip, self.server_port) + conn.request("GET", "/version") + r1 = conn.getresponse() + if r1.status == 200: + s_version= r1.read() + conn.close() + return s_version + else: + return False + except: + return False + + + def show_throbber(self, show, mesg=""): + if show: + #Build Throbber + throbber = gtk.VBox() + img = gtk.Image() + img.set_from_file('throbber.gif') + throbber.pack_start(img) + throbber.pack_start(gtk.Label(mesg)) + + self.set_canvas(throbber) + self.show_all() + else: + self.set_canvas(self.disp) + self.show_all() + + while gtk.events_pending(): + gtk.main_iteration() def requestAddFile(self, widget, data=None): _logger.info('Requesting to add file') @@ -105,6 +187,8 @@ class FileShareActivity(Activity): # get object and build file jobject = chooser.get_selected_object() + self.show_throbber(True, _("Please Wait... Packaging File") ) + if jobject.metadata.has_key("activity_id") and str(jobject.metadata['activity_id']): objectHash = str(jobject.metadata['activity_id']) bundle_path = os.path.join(self._filepath, '%s.xoj' % objectHash) @@ -112,6 +196,7 @@ class FileShareActivity(Activity): # If file in share, return don't build file if os.path.exists(bundle_path): self._alert(_("File Not Added"), _("File already shared")) + self.show_throbber( False ) return journalentrybundle.from_jobject(jobject, bundle_path ) @@ -128,6 +213,7 @@ class FileShareActivity(Activity): if os.path.exists(bundle_path): self._alert(_("File Not Added"), _("File already shared")) + self.show_throbber( False ) return journalentrybundle.from_jobject(jobject, bundle_path ) @@ -140,9 +226,8 @@ class FileShareActivity(Activity): bundle_path = os.path.join(self._filepath, '%s.xoj' % objectHash) journalentrybundle.from_jobject(jobject, bundle_path ) - return - # Build file array + # Build file information desc = "" if not jobject.metadata.has_key('description') else str( jobject.metadata['description'] ) title = _("Untitled") if str(jobject.metadata['title']) == "" else str(jobject.metadata['title']) tags = "" if not jobject.metadata.has_key('tags') else str( jobject.metadata['tags'] ) @@ -152,6 +237,25 @@ class FileShareActivity(Activity): fi = FileInfo.FileInfo(objectHash, title, desc, tags, size, True) self._addFileToUIList( objectHash, fi ) + # If added by upload button, data will have upload key + if data and data.has_key('upload'): + def call(): + params = { 'jdata': simplejson.dumps(fi.share_dump()), + 'file': open(bundle_path, 'rb') + } + opener = urllib2.build_opener( MultipartPostHandler.MultipartPostHandler) + try: + opener.open("http://%s:%d/upload"%(self.server_ip, self.server_port), params) + except: + self._alert( _("Failed to upload file") ) + _remFileFromUIList( objectHash ) + os.remove( bundle_path ) + self.show_throbber( False ) + self.show_throbber(True, _("Please Wait... Uploading file to server")) + threading.Thread(target=call).start() + else: + self.show_throbber( False ) + finally: chooser.destroy() del chooser @@ -166,6 +270,21 @@ class FileShareActivity(Activity): key = model.get_value(iter, 0) self._remFileFromUIList(key) + # If added by rem from server button, data will have remove key + if data and data.has_key('remove'): + def call(): + params = { 'id': key } + + try: + opener = urllib2.build_opener( MultipartPostHandler.MultipartPostHandler) + opener.open("http://%s:%d/remove"%(self.server_ip, self.server_port), params) + except: + self._alert( _("Failed to send remove request to server") ) + self.show_throbber( False ) + + self.show_throbber(True, _("Please Wait... Sending request to server")) + threading.Thread(target=call).start() + # Attempt to remove file from system bundle_path = os.path.join(self._filepath, '%s.xoj' % key) @@ -196,7 +315,10 @@ class FileShareActivity(Activity): iter = model.get_iter(path) fi = model.get_value(iter, 1) if fi.aquired == 0: - self._get_document(str( model.get_value(iter, 0))) + if self._mode == 'SERVER': + self._server_download_document( str( model.get_value(iter, 0)) ) + else: + self._get_document(str( model.get_value(iter, 0))) else: self._alert(_("File has already or is currently being downloaded")) else: @@ -244,6 +366,22 @@ class FileShareActivity(Activity): else: _logger.debug("INVALID PATH",path[1:]) + def _server_mode(self): + dialog = gtk.Dialog(_("Please Select Share Mode"), self, 0, + (_("Share with others"), gtk.RESPONSE_CANCEL, _("Connect to Server"), gtk.RESPONSE_OK)) + + dialog.vbox.pack_start(gtk.Label(_("Share with others or connect to server?")), False, False, 0) + + dialog.show_all() + response = dialog.run() + dialog.destroy() + + if response == gtk.RESPONSE_OK: + return True + else: + return False + + def _buildGui(self): self.set_title('File Share') @@ -253,82 +391,8 @@ class FileShareActivity(Activity): self.set_toolbox(toolbox) toolbox.show() - # Create button bar - ################### - hbbox = gtk.HButtonBox() - - if self.isServer: - addFileButton = gtk.Button(_("Add File")) - addFileButton.connect("clicked", self.requestAddFile, None) - hbbox.add(addFileButton) - - insFileButton = gtk.Button(_("Copy to Journal")) - insFileButton.connect("clicked", self.requestInsFile, None) - hbbox.add(insFileButton) - - remFileButton = gtk.Button(_("Remove Selected File")) - remFileButton.connect("clicked", self.requestRemFile, None) - hbbox.add(remFileButton) - - else: - downloadFileButton = gtk.Button(_("Download File")) - downloadFileButton.connect("clicked", self.requestDownloadFile, None) - hbbox.add(downloadFileButton) - - # Create File Tree - ################## - table = gtk.Table(rows=10, columns=1, homogeneous=False) - self.treeview = gtk.TreeView(gtk.TreeStore(str,object)) - - # create the TreeViewColumn to display the data - colName = gtk.TreeViewColumn(_('File Name')) - colDesc = gtk.TreeViewColumn(_('Description')) - colTags = gtk.TreeViewColumn(_('Tags')) - colSize = gtk.TreeViewColumn(_('File Size')) - colProg = gtk.TreeViewColumn('') - - self.treeview.append_column(colName) - self.treeview.append_column(colDesc) - self.treeview.append_column(colTags) - self.treeview.append_column(colSize) - self.treeview.append_column(colProg) - - # create a CellRendererText to render the data - cell = gtk.CellRendererText() - pbar = gtk.CellRendererProgress() - - # add the cell to the tvcolumn and allow it to expand - colName.pack_start(cell, True) - colDesc.pack_start(cell, True) - colTags.pack_start(cell, True) - colSize.pack_start(cell, True) - colProg.pack_start(pbar, True) - - # set the cell "text" attribute- retrieve text - # from that column in treestore - colName.set_cell_data_func(cell, FileInfo.file_name) - colDesc.set_cell_data_func(cell, FileInfo.file_desc) - colTags.set_cell_data_func(cell, FileInfo.file_tags) - colSize.set_cell_data_func(cell, FileInfo.file_size) - colProg.set_cell_data_func(pbar, FileInfo.load_bar) - - # make it searchable - self.treeview.set_search_column(1) - - # Allow sorting on the column - colName.set_sort_column_id(1) - - # Allow Multiple Selections - self.treeview.get_selection().set_mode( gtk.SELECTION_MULTIPLE ) - - # Put table into scroll window to allow it to scroll - window = gtk.ScrolledWindow() - window.add_with_viewport(self.treeview) - - table.attach(hbbox,0,1,0,1) - table.attach(window,0,1,1,10) - - self.set_canvas(table) + self.disp = GuiView(self) + self.set_canvas(self.disp) self.show_all() def update_progress(self, id, bytes ): @@ -423,6 +487,13 @@ class FileShareActivity(Activity): ('127.0.0.1', dbus.UInt16(self.port)), telepathy.SOCKET_ACCESS_CONTROL_LOCALHOST, 0) + def _server_download_document( self, fileId ): + addr = [self.server_ip, self.server_port] + # Download the file at next avaialbe time. + gobject.idle_add(self._download_document, addr, fileId) + return False + + def _get_document(self,fileId): if not self.addr: try: @@ -512,8 +583,7 @@ class FileShareActivity(Activity): bundle_path = os.path.join(self._filepath, '%s.xoj' % documentId) port = int(addr[1]) - getter = network.GlibURLDownloader("http://%s:%d/%s" - % (addr[0], port,documentId)) + getter = network.GlibURLDownloader("http://%s:%d/%s" % (addr[0], port,documentId)) getter.connect("finished", self._download_result_cb, documentId) getter.connect("progress", self._download_progress_cb, documentId) getter.connect("error", self._download_error_cb, documentId) diff --git a/FileShare.activity/GuiView.py b/FileShare.activity/GuiView.py new file mode 100644 index 0000000..d37c834 --- /dev/null +++ b/FileShare.activity/GuiView.py @@ -0,0 +1,113 @@ +# Copyright (C) 2009, Justin Lewis (jtl1728@rit.edu) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +import FileInfo +from gettext import gettext as _ + +class GuiView(gtk.Table): + """ + This class is used to just remove the table setup from the main file + """ + def __init__(self, activity): + gtk.Table.__init__(self, rows=10, columns=1, homogeneous=False) + self.build_table(activity) + + def build_table(self, activity): + # Create button bar + ################### + hbbox = gtk.HButtonBox() + + if activity.isServer: + addFileButton = gtk.Button(_("Add File")) + addFileButton.connect("clicked", activity.requestAddFile, None) + hbbox.add(addFileButton) + + insFileButton = gtk.Button(_("Copy to Journal")) + insFileButton.connect("clicked", activity.requestInsFile, None) + hbbox.add(insFileButton) + + remFileButton = gtk.Button(_("Remove Selected File")) + remFileButton.connect("clicked", activity.requestRemFile, None) + hbbox.add(remFileButton) + + else: + if activity._mode == 'SERVER': + addFileButton = gtk.Button(_("Upload A File")) + addFileButton.connect("clicked", activity.requestAddFile, {'upload':True}) + hbbox.add(addFileButton) + + remFileButton = gtk.Button(_("Remove From Server")) + remFileButton.connect("clicked", activity.requestRemFile, {'remove':True}) + hbbox.add(remFileButton) + + + + downloadFileButton = gtk.Button(_("Download File")) + downloadFileButton.connect("clicked", activity.requestDownloadFile, None) + hbbox.add(downloadFileButton) + + # Create File Tree + ################## + activity.treeview = gtk.TreeView(gtk.TreeStore(str,object)) + + # create the TreeViewColumn to display the data + colName = gtk.TreeViewColumn(_('File Name')) + colDesc = gtk.TreeViewColumn(_('Description')) + colTags = gtk.TreeViewColumn(_('Tags')) + colSize = gtk.TreeViewColumn(_('File Size')) + colProg = gtk.TreeViewColumn('') + + activity.treeview.append_column(colName) + activity.treeview.append_column(colDesc) + activity.treeview.append_column(colTags) + activity.treeview.append_column(colSize) + activity.treeview.append_column(colProg) + + # create a CellRendererText to render the data + cell = gtk.CellRendererText() + pbar = gtk.CellRendererProgress() + + # add the cell to the tvcolumn and allow it to expand + colName.pack_start(cell, True) + colDesc.pack_start(cell, True) + colTags.pack_start(cell, True) + colSize.pack_start(cell, True) + colProg.pack_start(pbar, True) + + # set the cell "text" attribute- retrieve text + # from that column in treestore + colName.set_cell_data_func(cell, FileInfo.file_name) + colDesc.set_cell_data_func(cell, FileInfo.file_desc) + colTags.set_cell_data_func(cell, FileInfo.file_tags) + colSize.set_cell_data_func(cell, FileInfo.file_size) + colProg.set_cell_data_func(pbar, FileInfo.load_bar) + + # make it searchable + activity.treeview.set_search_column(1) + + # Allow sorting on the column + colName.set_sort_column_id(1) + + # Allow Multiple Selections + activity.treeview.get_selection().set_mode( gtk.SELECTION_MULTIPLE ) + + # Put table into scroll window to allow it to scroll + window = gtk.ScrolledWindow() + window.add_with_viewport(activity.treeview) + + self.attach(hbbox,0,1,0,1) + self.attach(window,0,1,1,10) diff --git a/FileShare.activity/MultipartPostHandler.py b/FileShare.activity/MultipartPostHandler.py new file mode 100644 index 0000000..b9e3fcf --- /dev/null +++ b/FileShare.activity/MultipartPostHandler.py @@ -0,0 +1,132 @@ +#!/usr/bin/python + +#### +# 02/2006 Will Holcomb <wholcomb@gmail.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +""" +Usage: + Enables the use of multipart/form-data for posting forms + +Inspirations: + Upload files in python: + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 + urllib2_file: + Fabien Seisen: <fabien@seisen.org> + +Example: + import MultipartPostHandler, urllib2, cookielib + + cookies = cookielib.CookieJar() + opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), + MultipartPostHandler.MultipartPostHandler) + params = { "username" : "bob", "password" : "riviera", + "file" : open("filename", "rb") } + opener.open("http://wwww.bobsite.com/upload/", params) + +Further Example: + The main function of this file is a sample which downloads a page and + then uploads it to the W3C validator. +""" + +import urllib +import urllib2 +import mimetools, mimetypes +import os, stat + +class Callable: + def __init__(self, anycallable): + self.__call__ = anycallable + +# Controls how sequences are uncoded. If true, elements may be given multiple values by +# assigning a sequence. +doseq = 1 + +class MultipartPostHandler(urllib2.BaseHandler): + handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first + + def http_request(self, request): + data = request.get_data() + if data is not None and type(data) != str: + v_files = [] + v_vars = [] + try: + for(key, value) in data.items(): + if type(value) == file: + v_files.append((key, value)) + else: + v_vars.append((key, value)) + except TypeError: + systype, value, traceback = sys.exc_info() + raise TypeError, "not a valid non-string sequence or mapping object", traceback + + if len(v_files) == 0: + data = urllib.urlencode(v_vars, doseq) + else: + boundary, data = self.multipart_encode(v_vars, v_files) + contenttype = 'multipart/form-data; boundary=%s' % boundary + if(request.has_header('Content-Type') + and request.get_header('Content-Type').find('multipart/form-data') != 0): + print "Replacing %s with %s" % (request.get_header('content-type'), 'multipart/form-data') + request.add_unredirected_header('Content-Type', contenttype) + + request.add_data(data) + return request + + def multipart_encode(vars, files, boundary = None, buffer = None): + if boundary is None: + boundary = mimetools.choose_boundary() + if buffer is None: + buffer = '' + for(key, value) in vars: + buffer += '--%s\r\n' % boundary + buffer += 'Content-Disposition: form-data; name="%s"' % key + buffer += '\r\n\r\n' + value + '\r\n' + for(key, fd) in files: + file_size = os.fstat(fd.fileno())[stat.ST_SIZE] + filename = os.path.basename(fd.name) + contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' + buffer += '--%s\r\n' % boundary + buffer += 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename) + buffer += 'Content-Type: %s\r\n' % contenttype + # buffer += 'Content-Length: %s\r\n' % file_size + fd.seek(0) + buffer += '\r\n' + fd.read() + '\r\n' + buffer += '--%s--\r\n\r\n' % boundary + return boundary, buffer + multipart_encode = Callable(multipart_encode) + + https_request = http_request + +def main(): + import tempfile, sys + + validatorURL = "http://validator.w3.org/check" + opener = urllib2.build_opener(MultipartPostHandler) + + def validateFile(url): + temp = tempfile.mkstemp(suffix=".html") + os.write(temp[0], opener.open(url).read()) + params = { "ss" : "0", # show source + "doctype" : "Inline", + "uploaded_file" : open(temp[1], "rb") } + print opener.open(validatorURL, params).read() + os.remove(temp[1]) + + if len(sys.argv[1:]) > 0: + for arg in sys.argv[1:]: + validateFile(arg) + else: + validateFile("http://www.google.com") + +if __name__=="__main__": + main() diff --git a/FileShare.activity/po/POTFILES.in b/FileShare.activity/po/POTFILES.in index 741f643..02c6658 100644 --- a/FileShare.activity/po/POTFILES.in +++ b/FileShare.activity/po/POTFILES.in @@ -1,2 +1,4 @@ encoding: UTF-8 FileShareActivity.py +FileInfo.py +GuiView.py diff --git a/FileShare.activity/throbber.gif b/FileShare.activity/throbber.gif Binary files differnew file mode 100644 index 0000000..0ca7ada --- /dev/null +++ b/FileShare.activity/throbber.gif |