From 244bb8735a88530dbcefd6e8e25b1db69a0a95ec Mon Sep 17 00:00:00 2001 From: Walter Bender Date: Fri, 18 Feb 2011 00:44:23 +0000 Subject: new plugin management method --- (limited to 'gnome_plugins') diff --git a/gnome_plugins/__init__.py b/gnome_plugins/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/gnome_plugins/__init__.py diff --git a/gnome_plugins/collaboration_plugin.py b/gnome_plugins/collaboration_plugin.py new file mode 100644 index 0000000..54abdc8 --- /dev/null +++ b/gnome_plugins/collaboration_plugin.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender +#Copyright (c) 2011 Collabora Ltd. + +#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 sys +sys.path.append("..") + +import dbus +from gettext import gettext as _ +import gobject +import gtk + +from plugin import Plugin + +from util.menubuilder import MenuBuilder +from util.configfile import ConfigFile +from util.configwizard import ConfigWizard + +import telepathy +from collaboration.neighborhood import get_neighborhood +from collaboration.connectionmanager import get_connection_manager +from collaboration.activity import Activity +from collaboration import telepathyclient +from collaboration.tubeconn import TubeConnection + +from TurtleArt.tacollaboration import Collaboration + +import traceback + +CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \ + 'org.laptop.Telepathy.ActivityProperties' + + +class Collaboration_plugin(Plugin): + + __gsignals__ = { + 'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ()), + 'shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ()), + } + + def __init__(self, parent): + Plugin.__init__(self) + + self._parent = parent + self._neighborhood = None + self._title = _('My Turtle Art session') + self._bundle_id = "org.laptop.TurtleArt" + # This could be hashed from the file path (if resuming) + self._activity_id = "1234567" + self._nick = "" + self._setup_has_been_called = False + + def _setup_config_file(self, config_file_path): + self._config_file_path = config_file_path + self._collaboration_config_values = ConfigFile(self._config_file_path) + self._valid_config_values = { + 'nick': {'type': 'text'}, + 'account_id': {'type': 'text'}, + 'password': {'type': 'text'}, + 'server': {'type': 'text'}, + 'port': {'type': 'integer'}, + 'register': {'type': 'boolean'}, + 'colors': {'type': 'text'} + } + + def _connect_cb(self, button): + """ Enable connection """ + self._collaboration_config_values.set_valid_keys( + self._valid_config_values) + self._collaboration_config_values.connect( + 'configuration-loaded', self._connect_to_neighborhood) + self._collaboration_config_values.connect( + 'configuration-saved', self._connect_to_neighborhood) + self._collaboration_config_values.load() + self.setup() + + def setup(self): + self._collaboration = Collaboration(self.tw, self) + self._collaboration.setup() + # Do we know if we were successful? + self._setup_has_been_called = True + # TODO: + # use set_sensitive to enable Share and Configuration menuitems + + def set_tw(self, turtleart_window): + self.tw = turtleart_window + self.tw.nick = self._get_nick() + self._setup_config_file(self._parent.get_config_home()) + + def get_menu(self): + menu = gtk.Menu() + + MenuBuilder.make_menu_item(menu, _('Enable collaboration'), + self._connect_cb) + + self._activities_submenu = gtk.Menu() + activities_menu = MenuBuilder.make_sub_menu(self._activities_submenu, + _('Activities')) + menu.append(activities_menu) + + self._buddies_submenu = gtk.Menu() + buddies_menu = MenuBuilder.make_sub_menu(self._buddies_submenu, + _('Buddies')) + menu.append(buddies_menu) + + MenuBuilder.make_menu_item(menu, _('Share'), self._share_cb) + MenuBuilder.make_menu_item(menu, _('Configuration'), + self._config_neighborhood_cb) + + neighborhood_menu = MenuBuilder.make_sub_menu(menu, _('Neighborhood')) + + return neighborhood_menu + + def get_colors(self): + return self._colors + + def _get_nick(self): + return self._nick + + def _get_activity_id(self): + return self._activity_id + + def _get_bundle_id(self): + return self._bundle_id + + def _get_title(self): + return self._title + + def _connect_to_neighborhood(self, config_file_obj): + if self._neighborhood is not None: + return + + params = {} + params['nickname'] = self._collaboration_config_values.get('nick') + params['account_id'] = self._collaboration_config_values.get( + 'account_id') + params['server'] = self._collaboration_config_values.get('server') + params['port'] = self._collaboration_config_values.get('port') + params['password'] = self._collaboration_config_values.get('password') + params['register'] = self._collaboration_config_values.get('register') + if params['server'] == '': + raise RuntimeError('Invalid server address') + + self._nick = self._collaboration_config_values.get('nick') + # Tell the parent activity that the nick may have changed + self._parent.nick_changed(self._nick) + + self._colors = self._collaboration_config_values.get('colors') + # Tell the parent activity that the colors may have changed + self._parent.color_changed(self._colors) + + self._activities = {} + self._buddies = {} + + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + self._client_handler = telepathyclient.get_instance() + if self._client_handler == None: + raise RuntimeError('Telepathy client unavailable') + self._neighborhood = get_neighborhood(params) + self._neighborhood.connect('activity-added', self._activity_added_cb) + self._neighborhood.connect('activity-removed', + self._activity_removed_cb) + self._neighborhood.connect('buddy-added', self._buddy_added_cb) + self._neighborhood.connect('buddy-removed', self._buddy_removed_cb) + + # TODO: + # - show nick of sharer + # - show icon with color of sharer + def _activity_added_cb(self, model, activity_model): + self._activities[activity_model.props.name] = activity_model + self._recreate_available_activities_menu() + + def _activity_removed_cb(self, model, activity_model): + try: + self._activities.pop(activity_model.props.name) + except: + print 'Failed to remove activity %s' % activity_model.props.name + + self._recreate_available_activities_menu() + + def _buddy_added_cb(self, activity, buddy): + self._buddies[buddy.get_key()] = buddy + self._recreate_available_buddies_menu() + + def _buddy_removed_cb(self, activity, buddy): + try: + self._buddies.pop(buddy.get_key()) + except: + print "Couldn't remove buddy %s" % buddy.get_key() + self._recreate_available_buddies_menu() + + # TODO: we should have a list of available actions over + # a given buddy. I.e.: a) chat with him b) make friend + # c) invite to current activity + # + def _recreate_available_buddies_menu(self): + for child in self._buddies_submenu.get_children(): + self._buddies_submenu.remove(child) + + for buddy in self._buddies.values(): + key = buddy.get_key() + if key is None: + key = '' + n = buddy.get_nick() + '|' + key[0:15] + MenuBuilder.make_menu_item(self._buddies_submenu, n, + self._buddy_actions_cb, buddy) + + def _buddy_actions_cb(self, widget, buddy): + print 'do something with %s' % buddy.get_nick() + + # TODO: + # we need an extra menu branch with a) 'Join' button b) List of buddies + def _recreate_available_activities_menu(self): + for child in self._activities_submenu.get_children(): + self._activities_submenu.remove(child) + + for activity in self._activities.values(): + n = activity.props.name + MenuBuilder.make_menu_item(self._activities_submenu, n, + self._join_activity_cb, activity) + + def _join_activity_cb(self, widget, activity): + print 'Lets try to join...' + + connection_manager = get_connection_manager() + account_path, connection = \ + connection_manager.get_preferred_connection() + if connection is None: + print('No active connection available') + return + + properties = {} + properties['id'] = activity.activity_id + properties['color'] = activity.get_color() + print 'room handle according to activity %s' % activity.room_handle + properties['private'] = True + + try: + room_handle = connection.GetActivity(activity.activity_id, + dbus_interface=CONNECTION_INTERFACE_ACTIVITY_PROPERTIES) + print('room_handle = %s' % str(room_handle)) + self._joined_activity = Activity( + account_path, connection, room_handle, properties=properties) + # FIXME: this should be unified, no need to keep 2 references + self._shared_activity = self._joined_activity + except: + traceback.print_exc(file=sys.stdout) + + if self._joined_activity.props.joined: + raise RuntimeError('Activity %s is already shared.' % + activity.activity_id) + + _join_id = self._joined_activity.connect('joined', self.__joined_cb) + self._joined_activity.join() + + def __joined_cb(self, activity, success, err): + print "We've joined an activity" + self.emit('joined') + + def _config_neighborhood_cb(self, widget): + if not self._setup_has_been_called: + return + config_w = ConfigWizard(self._config_file_path) + config_items = [ + {'item_label': _('Nickname'), 'item_type': 'text', + 'item_name': 'nick'}, + {'item_label': _('Account ID'), 'item_type': 'text', + 'item_name': 'account_id'}, + {'item_label': _('Server'), 'item_type': 'text', + 'item_name': 'server'}, + {'item_label': _('Port'), 'item_type': 'text', + 'item_name': 'port'}, + {'item_label': _('Password'), 'item_type': 'text', + 'item_name': 'password'}, + {'item_label': _('Register'), 'item_type': 'boolean', + 'item_name': 'register'}, + {'item_label': _('Colors'), 'item_type': 'text', + 'item_name': 'colors'} + ] + config_w.set_config_items(config_items) + config_w.set_config_file_obj(self._collaboration_config_values) + config_w.show() + + def _share_cb(self, button): + if not self._setup_has_been_called: + return + properties = {} + properties['id'] = self._get_activity_id() + properties['type'] = self._get_bundle_id() + properties['name'] = self._get_title() + properties['color'] = self.get_colors() + properties['private'] = False + + connection_manager = get_connection_manager() + account_path, connection = \ + connection_manager.get_preferred_connection() + + if connection is None: + print('No active connection available') + return + + try: + self._parent._shared_activity = Activity(account_path, + connection, + properties=properties) + # FIXME: this should be unified, no need to keep 2 references + self._shared_activity = self._parent._shared_activity + except: + traceback.print_exc(file=sys.stdout) + + if self._parent._shared_parent.props.joined: + raise RuntimeError('Activity %s is already shared.' % + self._parent._get_activity_id()) + + self._parent._shared_parent.share(self.__share_activity_cb, + self.__share_activity_error_cb) + + def __share_activity_cb(self, activity): + """Finish sharing the activity""" + self.emit('shared') + + def __share_activity_error_cb(self, activity, error): + """Notify with GObject event of unsuccessful sharing of activity""" + print '%s got error: %s' % (activity, error) + +if __name__ == '__main__': + print 'testing collaboration' diff --git a/gnome_plugins/plugin.py b/gnome_plugins/plugin.py new file mode 100644 index 0000000..590c9bc --- /dev/null +++ b/gnome_plugins/plugin.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender +#Copyright (c) 2011 Collabora Ltd. + +#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 gobject + + +class Plugin(gobject.GObject): + def __init__(self): + gobject.GObject.__init__(self) + + def get_menu(self): + raise RuntimeError("You need to define get__menu for your plugin.") + + def set_tw(self, turtle_window=None): + raise RuntimeError("You need to define set_tw for your plugin.") diff --git a/gnome_plugins/uploader_plugin.py b/gnome_plugins/uploader_plugin.py new file mode 100644 index 0000000..06224fa --- /dev/null +++ b/gnome_plugins/uploader_plugin.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender +#Copyright (c) 2010 Jamie Boisture +#Copyright (c) 2011 Collabora Ltd. + +#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. + +#!/usr/bin/python + +try: + import pycurl + import xmlrpclib + _UPLOAD_AVAILABLE = True +except ImportError, e: + print "Import Error: %s. Project upload is disabled." % (e) + _UPLOAD_AVAILABLE = False + +import os +import gtk + +from plugin import Plugin +from util.menubuilder import MenuBuilder + +from gettext import gettext as _ + + +class Uploader_plugin(Plugin): + MAX_FILE_SIZE = 950000 + UPLOAD_SERVER = 'http://turtleartsite.appspot.com' + + def __init__(self, parent, upload_server=None, max_file_size=None): + self._parent = parent + self.uploading = False + + if upload_server is None: + self._upload_server = self.UPLOAD_SERVER + + if max_file_size is None: + self._max_file_size = self.MAX_FILE_SIZE + else: + self._max_file_size = max_file_size + + def set_tw(self, turtleart_window): + self.tw = turtleart_window + + def get_menu(self): + menu = gtk.Menu() + MenuBuilder.make_menu_item(menu, _('Upload to Web'), + self.do_upload_to_web) + upload_menu = MenuBuilder.make_sub_menu(menu, _('Upload')) + return upload_menu + + def enabled(self): + return _UPLOAD_AVAILABLE + + def do_upload_to_web(self, widget=None): + if self.uploading: + return + + self.uploading = False + self.pop_up = gtk.Window() + self.pop_up.set_default_size(600, 400) + self.pop_up.connect('delete_event', self._stop_uploading) + table = gtk.Table(8, 1, False) + self.pop_up.add(table) + + login_label = gtk.Label(_('You must have an account at \ +http://turtleartsite.sugarlabs.org to upload your project.')) + table.attach(login_label, 0, 1, 0, 1) + self.login_message = gtk.Label('') + table.attach(self.login_message, 0, 1, 1, 2) + + self.Hbox1 = gtk.HBox() + table.attach(self.Hbox1, 0, 1, 2, 3, xpadding=5, ypadding=3) + self.username_entry = gtk.Entry() + username_label = gtk.Label(_('Username:') + ' ') + username_label.set_size_request(150, 25) + username_label.set_alignment(1.0, 0.5) + self.username_entry.set_size_request(450, 25) + self.Hbox1.add(username_label) + self.Hbox1.add(self.username_entry) + + self.Hbox2 = gtk.HBox() + table.attach(self.Hbox2, 0, 1, 3, 4, xpadding=5, ypadding=3) + self.password_entry = gtk.Entry() + password_label = gtk.Label(_('Password:') + ' ') + self.password_entry.set_visibility(False) + password_label.set_size_request(150, 25) + password_label.set_alignment(1.0, 0.5) + self.password_entry.set_size_request(450, 25) + self.Hbox2.add(password_label) + self.Hbox2.add(self.password_entry) + + self.Hbox3 = gtk.HBox() + table.attach(self.Hbox3, 0, 1, 4, 5, xpadding=5, ypadding=3) + self.title_entry = gtk.Entry() + title_label = gtk.Label(_('Title:') + ' ') + title_label.set_size_request(150, 25) + title_label.set_alignment(1.0, 0.5) + self.title_entry.set_size_request(450, 25) + self.Hbox3.add(title_label) + self.Hbox3.add(self.title_entry) + + self.Hbox4 = gtk.HBox() + table.attach(self.Hbox4, 0, 1, 5, 6, xpadding=5, ypadding=3) + self.description_entry = gtk.TextView() + description_label = gtk.Label(_('Description:') + ' ') + description_label.set_size_request(150, 25) + description_label.set_alignment(1.0, 0.5) + self.description_entry.set_wrap_mode(gtk.WRAP_WORD) + self.description_entry.set_size_request(450, 50) + self.Hbox4.add(description_label) + self.Hbox4.add(self.description_entry) + + self.Hbox5 = gtk.HBox() + table.attach(self.Hbox5, 0, 1, 6, 7, xpadding=5, ypadding=3) + self.submit_button = gtk.Button(_('Submit to Web')) + self.submit_button.set_size_request(300, 25) + self.submit_button.connect('pressed', self._do_remote_logon) + self.Hbox5.add(self.submit_button) + self.cancel_button = gtk.Button(_('Cancel')) + self.cancel_button.set_size_request(300, 25) + self.cancel_button.connect('pressed', self._stop_uploading) + self.Hbox5.add(self.cancel_button) + + self.pop_up.show_all() + + def _stop_uploading(self, widget, event=None): + """ Hide the popup when the upload is complte """ + self.uploading = False + self.pop_up.hide() + + def _do_remote_logon(self, widget): + """ Log into the upload server """ + username = self.username_entry.get_text() + password = self.password_entry.get_text() + server = xmlrpclib.ServerProxy(self._upload_server + '/call/xmlrpc') + logged_in = server.login_remote(username, password) + if logged_in: + upload_key = logged_in + self._do_submit_to_web(upload_key) + else: + self.login_message.set_text(_('Login failed')) + + def _do_submit_to_web(self, key): + """ Submit project to the server """ + title = self.title_entry.get_text() + description = self.description_entry.get_buffer().get_text( + *self.description_entry.get_buffer().get_bounds()) + tafile, imagefile = self.tw.save_for_upload(title) + + # Set a maximum file size for image to be uploaded. + if int(os.path.getsize(imagefile)) > self._max_file_size: + import Image + while int(os.path.getsize(imagefile)) > self._max_file_size: + big_file = Image.open(imagefile) + smaller_file = big_file.resize(int(0.9 * big_file.size[0]), + int(0.9 * big_file.size[1]), + Image.ANTIALIAS) + smaller_file.save(imagefile, quality=100) + + c = pycurl.Curl() + c.setopt(c.POST, 1) + c.setopt(c.FOLLOWLOCATION, 1) + c.setopt(c.URL, self._upload_server + '/upload') + c.setopt(c.HTTPHEADER, ["Expect:"]) + c.setopt(c.HTTPPOST, [('file', (c.FORM_FILE, tafile)), + ('newimage', (c.FORM_FILE, imagefile)), + ('small_image', (c.FORM_FILE, imagefile)), + ('title', title), + ('description', description), + ('upload_key', key), ('_formname', + 'image_create')]) + c.perform() + error_code = c.getinfo(c.HTTP_CODE) + c.close + os.remove(imagefile) + os.remove(tafile) + if error_code == 400: + self.login_message.set_text(_('Failed to upload!')) + else: + self.pop_up.hide() + self.uploading = False + +if __name__ == "__main__": + # TODO: create test data... + u = Uploader(None) + if u.enabled(): + print "Uploader is enabled... trying to upload" + u.do_upload_to_web() + gtk.main() + else: + print "Uploader is not enabled... exiting" -- cgit v0.9.1