Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustin Lewis <jtl1728@rit.edu>2010-01-07 20:09:47 (GMT)
committer Justin Lewis <jtl1728@rit.edu>2010-01-07 20:09:47 (GMT)
commit413077ea5cfac8e6db6a10cbd895b8897da7b6eb (patch)
tree7bfc1e3ad8bc7909ae0a37dd85313e28316a1180
parent91ae913f9c299e800c6383c382eea2dedc32212c (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.py238
-rw-r--r--FileShare.activity/GuiView.py113
-rw-r--r--FileShare.activity/MultipartPostHandler.py132
-rw-r--r--FileShare.activity/po/POTFILES.in2
-rw-r--r--FileShare.activity/throbber.gifbin0 -> 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
new file mode 100644
index 0000000..0ca7ada
--- /dev/null
+++ b/FileShare.activity/throbber.gif
Binary files differ