Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/sugar/activity
diff options
context:
space:
mode:
authorMarco Pesenti Gritti <marco@localhost.localdomain>2008-04-29 12:58:34 (GMT)
committer Marco Pesenti Gritti <marco@localhost.localdomain>2008-04-29 12:58:34 (GMT)
commit72c2a1d770aac5af9df899cbfd25a15cbc2956ea (patch)
tree8909999bda47a3dfe9abc7befcd38e5278d6716f /sugar/activity
parent6efdf259b62d7af47b1e06601eaee17b74dbeb58 (diff)
Use a src directory consistently with base and shell.
Diffstat (limited to 'sugar/activity')
-rw-r--r--sugar/activity/Makefile.am9
-rw-r--r--sugar/activity/__init__.py58
-rw-r--r--sugar/activity/__init__py0
-rw-r--r--sugar/activity/activity.py945
-rw-r--r--sugar/activity/activityfactory.py316
-rw-r--r--sugar/activity/activityhandle.py66
-rw-r--r--sugar/activity/activityservice.py70
-rw-r--r--sugar/activity/bundlebuilder.py377
-rw-r--r--sugar/activity/registry.py182
9 files changed, 0 insertions, 2023 deletions
diff --git a/sugar/activity/Makefile.am b/sugar/activity/Makefile.am
deleted file mode 100644
index 9dfc8de..0000000
--- a/sugar/activity/Makefile.am
+++ /dev/null
@@ -1,9 +0,0 @@
-sugardir = $(pythondir)/sugar/activity
-sugar_PYTHON = \
- __init__.py \
- activity.py \
- activityfactory.py \
- activityhandle.py \
- activityservice.py \
- bundlebuilder.py \
- registry.py
diff --git a/sugar/activity/__init__.py b/sugar/activity/__init__.py
deleted file mode 100644
index 8a984ad..0000000
--- a/sugar/activity/__init__.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright (C) 2006-2007, Red Hat, Inc.
-#
-# 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 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.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the
-# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-# Boston, MA 02111-1307, USA.
-
-"""Activity implementation code for Sugar-based activities
-
-Each activity within the OLPC environment must provide two
-dbus services. The first, patterned after the
-
- sugar.activity.activityfactory.ActivityFactory
-
-class is responsible for providing a "create" method which
-takes a small dictionary with values corresponding to a
-
- sugar.activity.activityhandle.ActivityHandle
-
-describing an individual instance of the activity.
-
-Each activity so registered is described by a
-
- sugar.activity.bundle.Bundle
-
-instance, which parses a specially formatted activity.info
-file (stored in the activity directory's ./activity
-subdirectory). The
-
- sugar.activity.bundlebuilder
-
-module provides facilities for the standard setup.py module
-which produces and registers bundles from activity source
-directories.
-
-Once instantiated by the ActivityFactory's create method,
-each activity must provide an introspection API patterned
-after the
-
- sugar.activity.activityservice.ActivityService
-
-class. This class allows for querying the ID of the root
-window, requesting sharing across the network, and basic
-"what type of application are you" queries.
-"""
-from sugar.activity.registry import ActivityRegistry
-from sugar.activity.registry import get_registry
-from sugar.activity.registry import ActivityInfo
diff --git a/sugar/activity/__init__py b/sugar/activity/__init__py
deleted file mode 100644
index e69de29..0000000
--- a/sugar/activity/__init__py
+++ /dev/null
diff --git a/sugar/activity/activity.py b/sugar/activity/activity.py
deleted file mode 100644
index 6204354..0000000
--- a/sugar/activity/activity.py
+++ /dev/null
@@ -1,945 +0,0 @@
-"""Base class for activities written in Python
-
-This is currently the only definitive reference for what an
-activity must do to participate in the Sugar desktop.
-
- A Basic Activity
-
-All activities must implement a class derived from 'Activity' in this class.
-The convention is to call it ActivitynameActivity, but this is not required as
-the activity.info file associated with your activity will tell the sugar-shell
-which class to start.
-
-For example the most minimal Activity:
-
-
- from sugar.activity import activity
-
- class ReadActivity(activity.Activity):
- pass
-
-To get a real, working activity, you will at least have to implement:
- __init__(), read_file() and write_file()
-
-Aditionally, you will probably need a at least a Toolbar so you can have some
-interesting buttons for the user, like for example 'exit activity'
-
-See the methods of the Activity class below for more information on what you
-will need for a real activity.
-"""
-# Copyright (C) 2006-2007 Red Hat, Inc.
-# Copyright (C) 2007-2008 One Laptop Per Child
-#
-# 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 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.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the
-# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-# Boston, MA 02111-1307, USA.
-
-import gettext
-import logging
-import os
-import time
-import tempfile
-from hashlib import sha1
-import traceback
-
-import gtk, gobject
-import dbus
-import dbus.service
-import json
-
-from sugar import util
-from sugar.presence import presenceservice
-from sugar.activity.activityservice import ActivityService
-from sugar.graphics import style
-from sugar.graphics.window import Window
-from sugar.graphics.toolbox import Toolbox
-from sugar.graphics.toolbutton import ToolButton
-from sugar.graphics.toolcombobox import ToolComboBox
-from sugar.graphics.alert import Alert
-from sugar.graphics.icon import Icon
-from sugar.datastore import datastore
-from sugar import wm
-from sugar import profile
-from sugar import _sugarext
-
-_ = lambda msg: gettext.dgettext('sugar', msg)
-
-SCOPE_PRIVATE = "private"
-SCOPE_INVITE_ONLY = "invite" # shouldn't be shown in UI, it's implicit
-SCOPE_NEIGHBORHOOD = "public"
-
-J_DBUS_SERVICE = 'org.laptop.Journal'
-J_DBUS_PATH = '/org/laptop/Journal'
-J_DBUS_INTERFACE = 'org.laptop.Journal'
-
-class ActivityToolbar(gtk.Toolbar):
- """The Activity toolbar with the Journal entry title, sharing,
- Keep and Stop buttons
-
- All activities should have this toolbar. It is easiest to add it to your
- Activity by using the ActivityToolbox.
- """
- def __init__(self, activity):
- gtk.Toolbar.__init__(self)
-
- self._activity = activity
- self._updating_share = False
-
- activity.connect('shared', self.__activity_shared_cb)
- activity.connect('joined', self.__activity_shared_cb)
- activity.connect('notify::max_participants',
- self.__max_participants_changed_cb)
-
- if activity.metadata:
- self.title = gtk.Entry()
- self.title.set_size_request(int(gtk.gdk.screen_width() / 6), -1)
- self.title.set_text(activity.metadata['title'])
- self.title.connect('changed', self.__title_changed_cb)
- self._add_widget(self.title)
-
- activity.metadata.connect('updated', self.__jobject_updated_cb)
-
- separator = gtk.SeparatorToolItem()
- separator.props.draw = False
- separator.set_expand(True)
- self.insert(separator, -1)
- separator.show()
-
- self.share = ToolComboBox(label_text=_('Share with:'))
- self.share.combo.connect('changed', self.__share_changed_cb)
- self.share.combo.append_item(SCOPE_PRIVATE, _('Private'), 'zoom-home')
- self.share.combo.append_item(SCOPE_NEIGHBORHOOD, _('My Neighborhood'),
- 'zoom-neighborhood')
- self.insert(self.share, -1)
- self.share.show()
-
- self._update_share()
-
- self.keep = ToolButton('document-save', tooltip=_('Keep'))
- self.keep.props.accelerator = '<Ctrl>S'
- self.keep.connect('clicked', self.__keep_clicked_cb)
- self.insert(self.keep, -1)
- self.keep.show()
-
- self.stop = ToolButton('activity-stop', tooltip=_('Stop'))
- self.stop.props.accelerator = '<Ctrl>Q'
- self.stop.connect('clicked', self.__stop_clicked_cb)
- self.insert(self.stop, -1)
- self.stop.show()
-
- self._update_title_sid = None
-
- def _update_share(self):
- self._updating_share = True
-
- if self._activity.props.max_participants == 1:
- self.share.hide()
-
- if self._activity.get_shared():
- self.share.set_sensitive(False)
- self.share.combo.set_active(1)
- else:
- self.share.set_sensitive(True)
- self.share.combo.set_active(0)
-
- self._updating_share = False
-
- def __share_changed_cb(self, combo):
- if self._updating_share:
- return
-
- model = self.share.combo.get_model()
- it = self.share.combo.get_active_iter()
- (scope, ) = model.get(it, 0)
- if scope == SCOPE_NEIGHBORHOOD:
- self._activity.share()
-
- def __keep_clicked_cb(self, button):
- self._activity.copy()
-
- def __stop_clicked_cb(self, button):
- self._activity.take_screenshot()
- self._activity.close()
-
- def __jobject_updated_cb(self, jobject):
- self.title.set_text(jobject['title'])
-
- def __title_changed_cb(self, entry):
- if not self._update_title_sid:
- self._update_title_sid = gobject.timeout_add(
- 1000, self.__update_title_cb)
-
- def __update_title_cb(self):
- title = self.title.get_text()
-
- self._activity.metadata['title'] = title
- self._activity.metadata['title_set_by_user'] = '1'
- self._activity.save()
-
- shared_activity = self._activity._shared_activity
- if shared_activity:
- shared_activity.props.name = title
-
- self._update_title_sid = None
- return False
-
- def _add_widget(self, widget, expand=False):
- tool_item = gtk.ToolItem()
- tool_item.set_expand(expand)
-
- tool_item.add(widget)
- widget.show()
-
- self.insert(tool_item, -1)
- tool_item.show()
-
- def __activity_shared_cb(self, activity):
- self._update_share()
-
- def __max_participants_changed_cb(self, activity, pspec):
- self._update_share()
-
-class EditToolbar(gtk.Toolbar):
- """Provides the standard edit toolbar for Activities.
-
- Members:
- undo -- the undo button
- redo -- the redo button
- copy -- the copy button
- paste -- the paste button
- separator -- A separator between undo/redo and copy/paste
-
- This class only provides the 'edit' buttons in a standard layout,
- your activity will need to either hide buttons which make no sense for your
- Activity, or you need to connect the button events to your own callbacks:
-
- ## Example from Read.activity:
- # Create the edit toolbar:
- self._edit_toolbar = EditToolbar(self._view)
- # Hide undo and redo, they're not needed
- self._edit_toolbar.undo.props.visible = False
- self._edit_toolbar.redo.props.visible = False
- # Hide the separator too:
- self._edit_toolbar.separator.props.visible = False
-
- # As long as nothing is selected, copy needs to be insensitive:
- self._edit_toolbar.copy.set_sensitive(False)
- # When the user clicks the button, call _edit_toolbar_copy_cb()
- self._edit_toolbar.copy.connect('clicked', self._edit_toolbar_copy_cb)
-
- # Add the edit toolbar:
- toolbox.add_toolbar(_('Edit'), self._edit_toolbar)
- # And make it visible:
- self._edit_toolbar.show()
- """
- def __init__(self):
- gtk.Toolbar.__init__(self)
-
- self.undo = ToolButton('edit-undo')
- self.undo.set_tooltip(_('Undo'))
- self.insert(self.undo, -1)
- self.undo.show()
-
- self.redo = ToolButton('edit-redo')
- self.redo.set_tooltip(_('Redo'))
- self.insert(self.redo, -1)
- self.redo.show()
-
- self.separator = gtk.SeparatorToolItem()
- self.separator.set_draw(True)
- self.insert(self.separator, -1)
- self.separator.show()
-
- self.copy = ToolButton('edit-copy')
- self.copy.set_tooltip(_('Copy'))
- self.insert(self.copy, -1)
- self.copy.show()
-
- self.paste = ToolButton('edit-paste')
- self.paste.set_tooltip(_('Paste'))
- self.insert(self.paste, -1)
- self.paste.show()
-
-class ActivityToolbox(Toolbox):
- """Creates the Toolbox for the Activity
-
- By default, the toolbox contains only the ActivityToolbar. After creating
- the toolbox, you can add your activity specific toolbars, for example the
- EditToolbar.
-
- To add the ActivityToolbox to your Activity in MyActivity.__init__() do:
-
- # Create the Toolbar with the ActivityToolbar:
- toolbox = activity.ActivityToolbox(self)
- ... your code, inserting all other toolbars you need, like EditToolbar
-
- # Add the toolbox to the activity frame:
- self.set_toolbox(toolbox)
- # And make it visible:
- toolbox.show()
- """
- def __init__(self, activity):
- Toolbox.__init__(self)
-
- self._activity_toolbar = ActivityToolbar(activity)
- self.add_toolbar(_('Activity'), self._activity_toolbar)
- self._activity_toolbar.show()
-
- def get_activity_toolbar(self):
- return self._activity_toolbar
-
-class Activity(Window, gtk.Container):
- """This is the base Activity class that all other Activities derive from.
- This is where your activity starts.
-
- To get a working Activity:
- 0. Derive your Activity from this class:
- class MyActivity(activity.Activity):
- ...
-
- 1. implement an __init__() method for your Activity class.
-
- Use your init method to create your own ActivityToolbar which will
- contain some standard buttons:
- toolbox = activity.ActivityToolbox(self)
-
- Add extra Toolbars to your toolbox.
-
- You should setup Activity sharing here too.
-
- Finaly, your Activity may need some resources which you can claim
- here too.
-
- The __init__() method is also used to make the distinction between
- being resumed from the Journal, or starting with a blank document.
-
- 2. Implement read_file() and write_file()
- Most activities revolve around creating and storing Journal entries.
- For example, Write: You create a document, it is saved to the Journal
- and then later you resume working on the document.
-
- read_file() and write_file() will be called by sugar to tell your
- Activity that it should load or save the document the user is working
- on.
-
- 3. Implement our Activity Toolbars.
- The Toolbars are added to your Activity in step 1 (the toolbox), but
- you need to implement them somewhere. Now is a good time.
-
- There are a number of standard Toolbars. The most basic one, the one
- your almost absolutely MUST have is the ActivityToolbar. Without
- this, you're not really making a proper Sugar Activity (which may be
- okay, but you should really stop and think about why not!) You do
- this with the ActivityToolbox(self) call in step 1.
-
- Usually, you will also need the standard EditToolbar. This is the one
- which has the standard copy and paste buttons. You need to derive
- your own EditToolbar class from sugar.EditToolbar:
- class EditToolbar(activity.EditToolbar):
- ...
-
- See EditToolbar for the methods you should implement in your class.
-
- Finaly, your Activity will very likely need some activity specific
- buttons and options you can create your own toolbars by deriving a
- class from gtk.Toolbar:
- class MySpecialToolbar(gtk.Toolbar):
- ...
-
- 4. Use your creativity. Make your Activity something special and share
- it with your friends!
-
- Read through the methods of the Activity class below, to learn more about
- how to make an Activity work.
-
- Hint: A good and simple Activity to learn from is the Read activity. To
- create your own activity, you may want to copy it and use it as a template.
- """
- __gtype_name__ = 'SugarActivity'
-
- __gsignals__ = {
- 'shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
- 'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
- }
-
- __gproperties__ = {
- 'active' : (bool, None, None, False,
- gobject.PARAM_READWRITE),
- 'max-participants': (int, None, None, 0, 1000, 0,
- gobject.PARAM_READWRITE)
- }
-
- def __init__(self, handle, create_jobject=True):
- """Initialise the Activity
-
- handle -- sugar.activity.activityhandle.ActivityHandle
- instance providing the activity id and access to the
- presence service which *may* provide sharing for this
- application
-
- create_jobject -- boolean
- define if it should create a journal object if we are
- not resuming
-
- Side effects:
-
- Sets the gdk screen DPI setting (resolution) to the
- Sugar screen resolution.
-
- Connects our "destroy" message to our _destroy_cb
- method.
-
- Creates a base gtk.Window within this window.
-
- Creates an ActivityService (self._bus) servicing
- this application.
-
- Usage:
- If your Activity implements __init__(), it should call
- the base class __init()__ before doing Activity specific things.
-
- """
- Window.__init__(self)
-
- # process titles will only show 15 characters
- # but they get truncated anyway so if more characters
- # are supported in the future we will get a better view
- # of the processes
- proc_title = "%s <%s>" % (get_bundle_name(), handle.activity_id)
- util.set_proc_title(proc_title)
-
- self.connect('realize', self.__realize_cb)
- self.connect('delete-event', self.__delete_event_cb)
-
- self._active = False
- self._activity_id = handle.activity_id
- self._pservice = presenceservice.get_instance()
- self._shared_activity = None
- self._share_id = None
- self._join_id = None
- self._preview = _sugarext.Preview()
- self._updating_jobject = False
- self._closing = False
- self._deleting = False
- self._max_participants = 0
- self._invites_queue = []
-
- accel_group = gtk.AccelGroup()
- self.set_data('sugar-accel-group', accel_group)
- self.add_accel_group(accel_group)
-
- self._bus = ActivityService(self)
- self._owns_file = False
-
- share_scope = SCOPE_PRIVATE
-
- if handle.object_id:
- self._jobject = datastore.get(handle.object_id)
- # TODO: Don't create so many objects until we have versioning
- # support in the datastore
- #self._jobject.object_id = ''
- #del self._jobject.metadata['ctime']
- del self._jobject.metadata['mtime']
-
- self.set_title(self._jobject.metadata['title'])
-
- if self._jobject.metadata.has_key('share-scope'):
- share_scope = self._jobject.metadata['share-scope']
-
- elif create_jobject:
- logging.debug('Creating a jobject.')
- self._jobject = datastore.create()
- title = _('%s Activity') % get_bundle_name()
- self._jobject.metadata['title'] = title
- self.set_title(self._jobject.metadata['title'])
- self._jobject.metadata['title_set_by_user'] = '0'
- self._jobject.metadata['activity'] = self.get_bundle_id()
- self._jobject.metadata['activity_id'] = self.get_id()
- self._jobject.metadata['keep'] = '0'
- self._jobject.metadata['preview'] = ''
- self._jobject.metadata['share-scope'] = SCOPE_PRIVATE
-
- if self._shared_activity is not None:
- icon_color = self._shared_activity.props.color
- else:
- icon_color = profile.get_color().to_string()
-
- self._jobject.metadata['icon-color'] = icon_color
-
- self._jobject.file_path = ''
- # Cannot call datastore.write async for creates:
- # https://dev.laptop.org/ticket/3071
- datastore.write(self._jobject)
- else:
- self._jobject = None
-
- # handle activity share/join
- mesh_instance = self._pservice.get_activity(self._activity_id,
- warn_if_none=False)
- logging.debug("*** Act %s, mesh instance %r, scope %s",
- self._activity_id, mesh_instance, share_scope)
- if mesh_instance is not None:
- # There's already an instance on the mesh, join it
- logging.debug("*** Act %s joining existing mesh instance %r",
- self._activity_id, mesh_instance)
- self._shared_activity = mesh_instance
- self._shared_activity.connect('notify::private',
- self.__privacy_changed_cb)
- self._join_id = self._shared_activity.connect(
- "joined", self.__joined_cb)
- if not self._shared_activity.props.joined:
- self._shared_activity.join()
- else:
- self.__joined_cb(self._shared_activity, True, None)
- elif share_scope != SCOPE_PRIVATE:
- logging.debug("*** Act %s no existing mesh instance, but used to " \
- "be shared, will share" % self._activity_id)
- # no existing mesh instance, but activity used to be shared, so
- # restart the share
- if share_scope == SCOPE_INVITE_ONLY:
- self.share(private=True)
- elif share_scope == SCOPE_NEIGHBORHOOD:
- self.share(private=False)
- else:
- logging.debug("Unknown share scope %r" % share_scope)
-
- def do_set_property(self, pspec, value):
- if pspec.name == 'active':
- if self._active != value:
- self._active = value
- if not self._active and self._jobject:
- self.save()
- elif pspec.name == 'max-participants':
- self._max_participants = value
- else:
- Window.do_set_property(self, pspec, value)
-
- def do_get_property(self, pspec):
- if pspec.name == 'active':
- return self._active
- elif pspec.name == 'max-participants':
- return self._max_participants
- else:
- return Window.do_get_property(self, pspec)
-
- def get_id(self):
- """Returns the activity id of the current instance of your activity.
-
- The activity id is sort-of-like the unix process id (PID). However,
- unlike PIDs it is only different for each new instance (with
- create_jobject = True set) and stays the same everytime a user
- resumes an activity. This is also the identity of your Activity to other
- XOs for use when sharing.
- """
- return self._activity_id
-
- def get_bundle_id(self):
- """Returns the bundle_id from the activity.info file"""
- return os.environ['SUGAR_BUNDLE_ID']
-
- def set_canvas(self, canvas):
- """Sets the 'work area' of your activity with the canvas of your choice.
-
- One commonly used canvas is gtk.ScrolledWindow
- """
- Window.set_canvas(self, canvas)
- canvas.connect('map', self.__canvas_map_cb)
-
- def __canvas_map_cb(self, canvas):
- if self._jobject and self._jobject.file_path:
- self.read_file(self._jobject.file_path)
-
- def __jobject_create_cb(self):
- pass
-
- def __jobject_error_cb(self, err):
- logging.debug("Error creating activity datastore object: %s" % err)
-
- def get_activity_root(self):
- """ FIXME: Deprecated. This part of the API has been moved
- out of this class to the module itself
-
- Returns a path for saving Activity specific preferences, etc.
-
- Returns a path to the location in the filesystem where the activity can
- store activity related data that doesn't pertain to the current
- execution of the activity and thus cannot go into the DataStore.
-
- Currently, this will return something like
- ~/.sugar/default/MyActivityName/
-
- Activities should ONLY save settings, user preferences and other data
- which isn't specific to a journal item here. If (meta-)data is in anyway
- specific to a journal entry, it MUST be stored in the DataStore.
- """
- if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \
- os.environ['SUGAR_ACTIVITY_ROOT']:
- return os.environ['SUGAR_ACTIVITY_ROOT']
- else:
- return '/'
-
- def read_file(self, file_path):
- """
- Subclasses implement this method if they support resuming objects from
- the journal. 'file_path' is the file to read from.
-
- You should immediately open the file from the file_path, because the
- file_name will be deleted immediately after returning from read_file().
- Once the file has been opened, you do not have to read it immediately:
- After you have opened it, the file will only be really gone when you
- close it.
-
- Although not required, this is also a good time to read all meta-data:
- the file itself cannot be changed externally, but the title, description
- and other metadata['tags'] may change. So if it is important for you to
- notice changes, this is the time to record the originals.
- """
- raise NotImplementedError
-
- def write_file(self, file_path):
- """
- Subclasses implement this method if they support saving data to objects
- in the journal. 'file_path' is the file to write to.
-
- If the user did make changes, you should create the file_path and save
- all document data to it.
-
- Additionally, you should also write any metadata needed to resume your
- activity. For example, the Read activity saves the current page and zoom
- level, so it can display the page.
-
- Note: Currently, the file_path *WILL* be different from the one you
- received in file_read(). Even if you kept the file_path from file_read()
- open until now, you must still write the entire file to this file_path.
- """
- raise NotImplementedError
-
- def __save_cb(self):
- logging.debug('Activity.__save_cb')
- self._updating_jobject = False
- if self._closing:
- self._cleanup_jobject()
- self.destroy()
-
- def __save_error_cb(self, err):
- logging.debug('Activity.__save_error_cb')
- self._updating_jobject = False
- if self._closing:
- self._cleanup_jobject()
- self.destroy()
- logging.debug("Error saving activity object to datastore: %s" % err)
-
- def _cleanup_jobject(self):
- if self._jobject:
- if self._owns_file and os.path.isfile(self._jobject.file_path):
- logging.debug('_cleanup_jobject: removing %r' %
- self._jobject.file_path)
- os.remove(self._jobject.file_path)
- self._owns_file = False
- self._jobject.destroy()
- self._jobject = None
-
- def _get_preview(self):
- pixbuf = self._preview.get_pixbuf()
- if pixbuf is None:
- return None
-
- pixbuf = pixbuf.scale_simple(style.zoom(300), style.zoom(225),
- gtk.gdk.INTERP_BILINEAR)
-
- # TODO: Find a way of taking a png out of the pixbuf without saving
- # to a temp file. Impementing gtk.gdk.Pixbuf.save_to_buffer in pygtk
- # would solve this.
- fd, file_path = tempfile.mkstemp('.png')
- os.close(fd)
-
- pixbuf.save(file_path, 'png')
- f = open(file_path)
- try:
- preview_data = f.read()
- finally:
- f.close()
- os.remove(file_path)
-
- self._preview.clear()
-
- return preview_data
-
- def _get_buddies(self):
- if self._shared_activity is not None:
- buddies = {}
- for buddy in self._shared_activity.get_joined_buddies():
- if not buddy.props.owner:
- buddy_id = sha1(buddy.props.key).hexdigest()
- buddies[buddy_id] = [buddy.props.nick, buddy.props.color]
- return buddies
- else:
- return {}
-
- def take_screenshot(self):
- if self.canvas and self.canvas.window:
- self._preview.take_screenshot(self.canvas.window)
-
- def save(self):
- """Request that the activity is saved to the Journal.
-
- This method is called by the close() method below. In general,
- activities should not override this method. This method is part of the
- public API of an Acivity, and should behave in standard ways. Use your
- own implementation of write_file() to save your Activity specific data.
- """
-
- logging.debug('Activity.save: %r' % self._jobject.object_id)
-
- if self._updating_jobject:
- logging.info('Activity.save: still processing a previous request.')
- return
-
- buddies_dict = self._get_buddies()
- if buddies_dict:
- self.metadata['buddies_id'] = json.write(buddies_dict.keys())
- self.metadata['buddies'] = json.write(self._get_buddies())
-
- preview = self._get_preview()
- if self._preview:
- self.metadata['preview'] = dbus.ByteArray(preview)
-
- try:
- file_path = os.path.join(self.get_activity_root(), 'instance',
- '%i' % time.time())
- self.write_file(file_path)
- self._owns_file = True
- self._jobject.file_path = file_path
- except NotImplementedError:
- logging.debug('Activity.write_file is not implemented.')
-
- # Cannot call datastore.write async for creates:
- # https://dev.laptop.org/ticket/3071
- if self._jobject.object_id is None:
- datastore.write(self._jobject, transfer_ownership=True)
- else:
- self._updating_jobject = True
- datastore.write(self._jobject,
- transfer_ownership=True,
- reply_handler=self.__save_cb,
- error_handler=self.__save_error_cb)
-
- def copy(self):
- """Request that the activity 'Keep in Journal' the current state
- of the activity.
-
- Activities should not override this method. Instead, like save() do any
- copy work that needs to be done in write_file()
- """
- logging.debug('Activity.copy: %r' % self._jobject.object_id)
- self.save()
- self._jobject.object_id = None
-
- def __privacy_changed_cb(self, shared_activity, param_spec):
- if shared_activity.props.private:
- self._jobject.metadata['share-scope'] = SCOPE_INVITE_ONLY
- else:
- self._jobject.metadata['share-scope'] = SCOPE_NEIGHBORHOOD
-
- def __joined_cb(self, activity, success, err):
- """Callback when join has finished"""
- self._shared_activity.disconnect(self._join_id)
- self._join_id = None
- if not success:
- logging.debug("Failed to join activity: %s" % err)
- return
-
- self.present()
- self.emit('joined')
- self.__privacy_changed_cb(self._shared_activity, None)
-
- def get_shared(self):
- """Returns TRUE if the activity is shared on the mesh."""
- if not self._shared_activity:
- return False
- return self._shared_activity.props.joined
-
- def __share_cb(self, ps, success, activity, err):
- self._pservice.disconnect(self._share_id)
- self._share_id = None
- if not success:
- logging.debug('Share of activity %s failed: %s.' %
- (self._activity_id, err))
- return
-
- logging.debug('Share of activity %s successful, PS activity is %r.',
- self._activity_id, activity)
-
- activity.props.name = self._jobject.metadata['title']
-
- self._shared_activity = activity
- self._shared_activity.connect('notify::private',
- self.__privacy_changed_cb)
- self.emit('shared')
- self.__privacy_changed_cb(self._shared_activity, None)
-
- self._send_invites()
-
- def _invite_response_cb(self, error):
- if error:
- logging.error('Invite failed: %s' % error)
-
- def _send_invites(self):
- while self._invites_queue:
- buddy_key = self._invites_queue.pop()
- buddy = self._pservice.get_buddy(buddy_key)
- if buddy:
- self._shared_activity.invite(
- buddy, '', self._invite_response_cb)
- else:
- logging.error('Cannot invite %s, no such buddy.' % buddy_key)
-
- def invite(self, buddy_key):
- """Invite a buddy to join this Activity.
-
- Side Effects:
- Calls self.share(True) to privately share the activity if it wasn't
- shared before.
- """
- self._invites_queue.append(buddy_key)
-
- if (self._shared_activity is None
- or not self._shared_activity.props.joined):
- self.share(True)
- else:
- self._send_invites()
-
- def share(self, private=False):
- """Request that the activity be shared on the network.
-
- private -- bool: True to share by invitation only,
- False to advertise as shared to everyone.
-
- Once the activity is shared, its privacy can be changed by setting
- its 'private' property.
- """
- if self._shared_activity and self._shared_activity.props.joined:
- raise RuntimeError("Activity %s already shared." %
- self._activity_id)
- verb = private and 'private' or 'public'
- logging.debug('Requesting %s share of activity %s.' %
- (verb, self._activity_id))
- self._share_id = self._pservice.connect("activity-shared",
- self.__share_cb)
- self._pservice.share_activity(self, private=private)
-
- def _display_keep_failed_dialog(self):
- alert = Alert()
- alert.props.title = _('Keep error')
- alert.props.msg = _('Keep error: all changes will be lost')
-
- cancel_icon = Icon(icon_name='dialog-cancel')
- alert.add_button(gtk.RESPONSE_CANCEL, _('Don\'t stop'), cancel_icon)
-
- stop_icon = Icon(icon_name='dialog-ok')
- alert.add_button(gtk.RESPONSE_OK, _('Stop anyway'), stop_icon)
-
- self.add_alert(alert)
- alert.connect('response', self._keep_failed_dialog_response_cb)
-
- def _keep_failed_dialog_response_cb(self, alert, response_id):
- self.remove_alert(alert)
- if response_id == gtk.RESPONSE_OK:
- self.close(skip_save=True)
-
- def can_close(self):
- """Activities should override this function if they want to perform
- extra checks before actually closing."""
-
- return True
-
- def close(self, force=False, skip_save=False):
- """Request that the activity be stopped and saved to the Journal
-
- Activities should not override this method, but should implement
- write_file() to do any state saving instead. If the application wants
- to control wether it can close, it should override can_close().
- """
-
- if not force:
- if not self.can_close():
- return
-
- try:
- if not skip_save:
- self.save()
- except Exception:
- logging.info(traceback.format_exc())
- self._display_keep_failed_dialog()
- return
-
- if self._shared_activity:
- self._shared_activity.leave()
-
- if self._updating_jobject:
- self._closing = True
- else:
- self.destroy()
-
- # Make the exported object inaccessible
- dbus.service.Object.remove_from_connection(self._bus)
-
- def __realize_cb(self, window):
- wm.set_bundle_id(window.window, self.get_bundle_id())
- wm.set_activity_id(window.window, str(self._activity_id))
-
- def __delete_event_cb(self, widget, event):
- self.close()
- return True
-
- def get_metadata(self):
- """Returns the jobject metadata or None if there is no jobject.
-
- Activities can set metadata in write_file() using:
- self.metadata['MyKey'] = "Something"
-
- and retrieve metadata in read_file() using:
- self.metadata.get('MyKey', 'aDefaultValue')
-
- Note: Make sure your activity works properly if one or more of the
- metadata items is missing. Never assume they will all be present.
- """
- if self._jobject:
- return self._jobject.metadata
- else:
- return None
-
- metadata = property(get_metadata, None)
-
-def get_bundle_name():
- """Return the bundle name for the current process' bundle"""
- return os.environ['SUGAR_BUNDLE_NAME']
-
-def get_bundle_path():
- """Return the bundle path for the current process' bundle"""
- return os.environ['SUGAR_BUNDLE_PATH']
-
-def get_activity_root():
- """Returns a path for saving Activity specific preferences, etc."""
- if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \
- os.environ['SUGAR_ACTIVITY_ROOT']:
- return os.environ['SUGAR_ACTIVITY_ROOT']
- else:
- raise RuntimeError("No SUGAR_ACTIVITY_ROOT set.")
-
-def show_object_in_journal(object_id):
- bus = dbus.SessionBus()
- obj = bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH)
- journal = dbus.Interface(obj, J_DBUS_INTERFACE)
- journal.ShowObject(object_id)
diff --git a/sugar/activity/activityfactory.py b/sugar/activity/activityfactory.py
deleted file mode 100644
index 42b8c40..0000000
--- a/sugar/activity/activityfactory.py
+++ /dev/null
@@ -1,316 +0,0 @@
-"""Shell side object which manages request to start activity"""
-# Copyright (C) 2006-2007 Red Hat, Inc.
-#
-# 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 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.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the
-# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-# Boston, MA 02111-1307, USA.
-
-import logging
-import subprocess
-import signal
-
-import dbus
-import gobject
-
-from sugar.presence import presenceservice
-from sugar.activity.activityhandle import ActivityHandle
-from sugar.activity import registry
-from sugar import util
-from sugar import env
-
-from errno import EEXIST
-
-import os
-
-# #3903 - this constant can be removed and assumed to be 1 when dbus-python
-# 0.82.3 is the only version used
-if dbus.version >= (0, 82, 3):
- DBUS_PYTHON_TIMEOUT_UNITS_PER_SECOND = 1
-else:
- DBUS_PYTHON_TIMEOUT_UNITS_PER_SECOND = 1000
-
-_SHELL_SERVICE = "org.laptop.Shell"
-_SHELL_PATH = "/org/laptop/Shell"
-_SHELL_IFACE = "org.laptop.Shell"
-
-_DS_SERVICE = "org.laptop.sugar.DataStore"
-_DS_INTERFACE = "org.laptop.sugar.DataStore"
-_DS_PATH = "/org/laptop/sugar/DataStore"
-
-_ACTIVITY_FACTORY_INTERFACE = "org.laptop.ActivityFactory"
-
-_RAINBOW_SERVICE_NAME = "org.laptop.security.Rainbow"
-_RAINBOW_ACTIVITY_FACTORY_PATH = "/"
-_RAINBOW_ACTIVITY_FACTORY_INTERFACE = "org.laptop.security.Rainbow"
-
-_children_pid = []
-
-def _sigchild_handler(signum, frame):
- for child_pid in _children_pid:
- pid, status_ = os.waitpid(child_pid, os.WNOHANG)
- if pid > 0:
- _children_pid.remove(pid)
-
-signal.signal(signal.SIGCHLD, _sigchild_handler)
-
-def create_activity_id():
- """Generate a new, unique ID for this activity"""
- pservice = presenceservice.get_instance()
-
- # create a new unique activity ID
- i = 0
- act_id = None
- while i < 10:
- act_id = util.unique_id()
- i += 1
-
- # check through network activities
- found = False
- activities = pservice.get_activities()
- for act in activities:
- if act_id == act.props.id:
- found = True
- break
- if not found:
- return act_id
- raise RuntimeError("Cannot generate unique activity id.")
-
-def get_environment(activity):
- environ = os.environ.copy()
-
- bin_path = os.path.join(activity.path, 'bin')
-
- activity_root = env.get_profile_path(activity.bundle_id)
- if not os.path.exists(activity_root):
- os.mkdir(activity_root)
-
- data_dir = os.path.join(activity_root, 'instance')
- if not os.path.exists(data_dir):
- os.mkdir(data_dir)
-
- data_dir = os.path.join(activity_root, 'data')
- if not os.path.exists(data_dir):
- os.mkdir(data_dir)
-
- tmp_dir = os.path.join(activity_root, 'tmp')
- if not os.path.exists(tmp_dir):
- os.mkdir(tmp_dir)
-
- environ['SUGAR_BUNDLE_PATH'] = activity.path
- environ['SUGAR_BUNDLE_ID'] = activity.bundle_id
- environ['SUGAR_ACTIVITY_ROOT'] = activity_root
- environ['PATH'] = bin_path + ':' + environ['PATH']
- #environ['RAINBOW_STRACE_LOG'] = '1'
-
- if activity.bundle_id in [ 'org.laptop.WebActivity',
- 'org.laptop.GmailActivity',
- 'org.laptop.WikiBrowseActivity'
- ]:
- environ['RAINBOW_CONSTANT_UID'] = 'yes'
-
- return environ
-
-def get_command(activity, activity_id=None, object_id=None, uri=None):
- if not activity_id:
- activity_id = create_activity_id()
-
- command = activity.command.split(' ')
- command.extend(['-b', activity.bundle_id])
- command.extend(['-a', activity_id])
-
- if object_id is not None:
- command.extend(['-o', object_id])
- if uri is not None:
- command.extend(['-u', uri])
-
- print command
-
- return command
-
-def open_log_file(activity):
- i = 1
- while True:
- path = env.get_logs_path('%s-%s.log' % (activity.bundle_id, i))
- try:
- fd = os.open(path, os.O_EXCL | os.O_CREAT \
- | os.O_SYNC | os.O_WRONLY, 0644)
- f = os.fdopen(fd, 'w', 0)
- return (path, f)
- except OSError, e:
- if e.errno == EEXIST:
- i += 1
- else:
- raise e
-
-class ActivityCreationHandler(gobject.GObject):
- """Sugar-side activity creation interface
-
- This object uses a dbus method on the ActivityFactory
- service to create the new activity. It generates
- GObject events in response to the success/failure of
- activity startup using callbacks to the service's
- create call.
- """
-
- def __init__(self, service_name, handle):
- """Initialise the handler
-
- service_name -- the service name of the bundle factory
- activity_handle -- stores the values which are to
- be passed to the service to uniquely identify
- the activity to be created and the sharing
- service that may or may not be connected with it
-
- sugar.activity.activityhandle.ActivityHandle instance
-
- calls the "create" method on the service for this
- particular activity type and registers the
- _reply_handler and _error_handler methods on that
- call's results.
-
- The specific service which creates new instances of this
- particular type of activity is created during the activity
- registration process in shell bundle registry which creates
- service definition files for each registered bundle type.
-
- If the file '/etc/olpc-security' exists, then activity launching
- will be delegated to the prototype 'Rainbow' security service.
- """
- gobject.GObject.__init__(self)
-
- self._service_name = service_name
- self._handle = handle
-
- self._use_rainbow = os.path.exists('/etc/olpc-security')
- if service_name in [ 'org.laptop.JournalActivity',
- 'org.laptop.Terminal',
- 'org.laptop.LogViewer',
- 'org.laptop.Analyze'
- ]:
- self._use_rainbow = False
-
- bus = dbus.SessionBus()
-
- bus_object = bus.get_object(_SHELL_SERVICE, _SHELL_PATH)
- self._shell = dbus.Interface(bus_object, _SHELL_IFACE)
-
- if handle.activity_id is not None and \
- handle.object_id is None:
- datastore = dbus.Interface(
- bus.get_object(_DS_SERVICE, _DS_PATH), _DS_INTERFACE)
- datastore.find({ 'activity_id': self._handle.activity_id }, [],
- reply_handler=self._find_object_reply_handler,
- error_handler=self._find_object_error_handler)
- else:
- self._launch_activity()
-
- def _launch_activity(self):
- if self._handle.activity_id != None:
- self._shell.ActivateActivity(self._handle.activity_id,
- reply_handler=self._activate_reply_handler,
- error_handler=self._activate_error_handler)
- else:
- self._create_activity()
-
- def _create_activity(self):
- if self._handle.activity_id is None:
- self._handle.activity_id = create_activity_id()
-
- self._shell.NotifyLaunch(
- self._service_name, self._handle.activity_id,
- reply_handler=self._no_reply_handler,
- error_handler=self._notify_launch_error_handler)
-
- activity_registry = registry.get_registry()
- activity = activity_registry.get_activity(self._service_name)
- if activity:
- environ = get_environment(activity)
- (log_path, log_file) = open_log_file(activity)
- command = get_command(activity, self._handle.activity_id,
- self._handle.object_id,
- self._handle.uri)
-
- if not self._use_rainbow:
- p = subprocess.Popen(command, env=environ, cwd=activity.path,
- stdout=log_file, stderr=log_file)
- _children_pid.append(p.pid)
- else:
- log_file.close()
- system_bus = dbus.SystemBus()
- factory = system_bus.get_object(_RAINBOW_SERVICE_NAME,
- _RAINBOW_ACTIVITY_FACTORY_PATH)
- factory.CreateActivity(
- log_path,
- environ,
- command,
- environ['SUGAR_BUNDLE_PATH'],
- environ['SUGAR_BUNDLE_ID'],
- timeout=30 * DBUS_PYTHON_TIMEOUT_UNITS_PER_SECOND,
- reply_handler=self._create_reply_handler,
- error_handler=self._create_error_handler,
- dbus_interface=_RAINBOW_ACTIVITY_FACTORY_INTERFACE)
-
- def _no_reply_handler(self, *args):
- pass
-
- def _notify_launch_failure_error_handler(self, err):
- logging.error('Notify launch failure failed %s' % err)
-
- def _notify_launch_error_handler(self, err):
- logging.debug('Notify launch failed %s' % err)
-
- def _activate_reply_handler(self, activated):
- if not activated:
- self._create_activity()
-
- def _activate_error_handler(self, err):
- logging.error("Activity activation request failed %s" % err)
-
- def _create_reply_handler(self):
- logging.debug("Activity created %s (%s)." %
- (self._handle.activity_id, self._service_name))
-
- def _create_error_handler(self, err):
- logging.error("Couldn't create activity %s (%s): %s" %
- (self._handle.activity_id, self._service_name, err))
- self._shell.NotifyLaunchFailure(
- self._handle.activity_id, reply_handler=self._no_reply_handler,
- error_handler=self._notify_launch_failure_error_handler)
-
- def _find_object_reply_handler(self, jobjects, count):
- if count > 0:
- if count > 1:
- logging.debug("Multiple objects has the same activity_id.")
- self._handle.object_id = jobjects[0]['uid']
- self._create_activity()
-
- def _find_object_error_handler(self, err):
- logging.error("Datastore find failed %s" % err)
- self._create_activity()
-
-def create(service_name, activity_handle=None):
- """Create a new activity from its name."""
- if not activity_handle:
- activity_handle = ActivityHandle()
- return ActivityCreationHandler(service_name, activity_handle)
-
-def create_with_uri(service_name, uri):
- """Create a new activity and pass the uri as handle."""
- activity_handle = ActivityHandle(uri=uri)
- return ActivityCreationHandler(service_name, activity_handle)
-
-def create_with_object_id(service_name, object_id):
- """Create a new activity and pass the object id as handle."""
- activity_handle = ActivityHandle(object_id=object_id)
- return ActivityCreationHandler(service_name, activity_handle)
diff --git a/sugar/activity/activityhandle.py b/sugar/activity/activityhandle.py
deleted file mode 100644
index 3f4a44c..0000000
--- a/sugar/activity/activityhandle.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
-#
-# 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 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.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the
-# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-# Boston, MA 02111-1307, USA.
-
-class ActivityHandle(object):
- """Data structure storing simple activity metadata"""
- def __init__(
- self, activity_id=None, object_id=None, uri=None
- ):
- """Initialise the handle from activity_id
-
- activity_id -- unique id for the activity to be
- created
- object_id -- identity of the journal object
- associated with the activity. It was used by
- the journal prototype implementation, might
- change when we do the real one.
-
- When you resume an activity from the journal
- the object_id will be passed in. It's optional
- since new activities does not have an
- associated object (yet).
-
- XXX Not clear how this relates to the activity
- id yet, i.e. not sure we really need both. TBF
- uri -- URI associated with the activity. Used when
- opening an external file or resource in the
- activity, rather than a journal object
- (downloads stored on the file system for
- example or web pages)
- """
- self.activity_id = activity_id
- self.object_id = object_id
- self.uri = uri
-
- def get_dict(self):
- """Retrieve our settings as a dictionary"""
- result = { 'activity_id' : self.activity_id }
- if self.object_id:
- result['object_id'] = self.object_id
- if self.uri:
- result['uri'] = self.uri
-
- return result
-
-def create_from_dict(handle_dict):
- """Create a handle from a dictionary of parameters"""
- result = ActivityHandle(
- handle_dict['activity_id'],
- object_id = handle_dict.get('object_id'),
- uri = handle_dict.get('uri'),
- )
- return result
diff --git a/sugar/activity/activityservice.py b/sugar/activity/activityservice.py
deleted file mode 100644
index c884fcb..0000000
--- a/sugar/activity/activityservice.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
-#
-# 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 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.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the
-# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-# Boston, MA 02111-1307, USA.
-
-import logging
-
-import dbus
-import dbus.service
-
-_ACTIVITY_SERVICE_NAME = "org.laptop.Activity"
-_ACTIVITY_SERVICE_PATH = "/org/laptop/Activity"
-_ACTIVITY_INTERFACE = "org.laptop.Activity"
-
-class ActivityService(dbus.service.Object):
- """Base dbus service object that each Activity uses to export dbus methods.
-
- The dbus service is separate from the actual Activity object so that we can
- tightly control what stuff passes through the dbus python bindings."""
-
- def __init__(self, activity):
- """Initialise the service for the given activity
-
- activity -- sugar.activity.activity.Activity instance
-
- Creates dbus services that use the instance's activity_id
- as discriminants among all active services
- of this type. That is, the services are all available
- as names/paths derived from the instance's activity_id.
-
- The various methods exposed on dbus are just forwarded
- to the client Activity object's equally-named methods.
- """
- activity.realize()
-
- activity_id = activity.get_id()
- service_name = _ACTIVITY_SERVICE_NAME + activity_id
- object_path = _ACTIVITY_SERVICE_PATH + "/" + activity_id
-
- bus = dbus.SessionBus()
- bus_name = dbus.service.BusName(service_name, bus=bus)
- dbus.service.Object.__init__(self, bus_name, object_path)
-
- self._activity = activity
-
- @dbus.service.method(_ACTIVITY_INTERFACE)
- def SetActive(self, active):
- logging.debug('ActivityService.set_active: %s.' % active)
- self._activity.props.active = active
-
- @dbus.service.method(_ACTIVITY_INTERFACE)
- def Invite(self, buddy_key):
- self._activity.invite(buddy_key)
-
- @dbus.service.method(_ACTIVITY_INTERFACE)
- def TakeScreenshot(self):
- self._activity.take_screenshot()
-
diff --git a/sugar/activity/bundlebuilder.py b/sugar/activity/bundlebuilder.py
deleted file mode 100644
index 3e70b3c..0000000
--- a/sugar/activity/bundlebuilder.py
+++ /dev/null
@@ -1,377 +0,0 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
-#
-# 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 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.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the
-# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-# Boston, MA 02111-1307, USA.
-
-import sys
-import os
-import zipfile
-import shutil
-import subprocess
-import re
-import gettext
-
-from sugar import env
-from sugar.bundle.activitybundle import ActivityBundle
-
-class _SvnFileList(list):
- def __init__(self):
- f = os.popen('svn list -R')
- for line in f.readlines():
- filename = line.strip()
- if os.path.isfile(filename):
- self.append(filename)
- f.close()
-
-class _GitFileList(list):
- def __init__(self):
- f = os.popen('git-ls-files')
- for line in f.readlines():
- filename = line.strip()
- if not filename.startswith('.'):
- self.append(filename)
- f.close()
-
-class _DefaultFileList(list):
- def __init__(self):
- for name in os.listdir('activity'):
- if name.endswith('.svg'):
- self.append(os.path.join('activity', name))
-
- self.append('activity/activity.info')
-
- if os.path.isfile(_get_source_path('NEWS')):
- self.append('NEWS')
-
-class _ManifestFileList(_DefaultFileList):
- def __init__(self, manifest):
- _DefaultFileList.__init__(self)
- self.append(manifest)
-
- f = open(manifest,'r')
- for line in f.readlines():
- stripped_line = line.strip()
- if stripped_line and not stripped_line in self:
- self.append(stripped_line)
- f.close()
-
-def _extract_bundle(source_file, dest_dir):
- if not os.path.exists(dest_dir):
- os.mkdir(dest_dir)
-
- zf = zipfile.ZipFile(source_file)
-
- for name in zf.namelist():
- path = os.path.join(dest_dir, name)
- if not os.path.exists(os.path.dirname(path)):
- os.makedirs(os.path.dirname(path))
-
- outfile = open(path, 'wb')
- outfile.write(zf.read(name))
- outfile.flush()
- outfile.close()
-
-def _get_source_path(path=None):
- if path:
- return os.path.join(os.getcwd(), path)
- else:
- return os.getcwd()
-
-def _get_bundle_dir():
- bundle_name = os.path.basename(_get_source_path())
- return bundle_name + '.activity'
-
-def _get_package_name(bundle_name):
- bundle = ActivityBundle(_get_source_path())
- zipname = '%s-%d.xo' % (bundle_name, bundle.get_activity_version())
- return zipname
-
-def _delete_backups(arg, dirname, names):
- for name in names:
- if name.endswith('~') or name.endswith('pyc'):
- os.remove(os.path.join(dirname, name))
-
-def _get_bundle_id():
- bundle = ActivityBundle(_get_source_path())
- return bundle.get_bundle_id()
-
-def cmd_help():
- print 'Usage: \n\
-setup.py dev - setup for development \n\
-setup.py dist - create a bundle package \n\
-setup.py install [dirname] - install the bundle \n\
-setup.py uninstall [dirname] - uninstall the bundle \n\
-setup.py genpot - generate the gettext pot file \n\
-setup.py genl10n - generate localization files \n\
-setup.py clean - clean the directory \n\
-setup.py release - do a new release of the bundle \n\
-setup.py help - print this message \n\
-'
-
-def cmd_dev():
- bundle_path = env.get_user_activities_path()
- if not os.path.isdir(bundle_path):
- os.mkdir(bundle_path)
- bundle_path = os.path.join(bundle_path, _get_bundle_dir())
- try:
- os.symlink(_get_source_path(), bundle_path)
- except OSError:
- if os.path.islink(bundle_path):
- print 'ERROR - The bundle has been already setup for development.'
- else:
- print 'ERROR - A bundle with the same name is already installed.'
-
-def _get_file_list(manifest):
- if os.path.isfile(manifest):
- return _ManifestFileList(manifest)
- elif os.path.isdir('.git'):
- return _GitFileList()
- elif os.path.isdir('.svn'):
- return _SvnFileList()
- else:
- return _DefaultFileList()
-
-def _get_po_list(manifest):
- file_list = {}
-
- po_regex = re.compile("po/(.*)\.po$")
- for file_name in _get_file_list(manifest):
- match = po_regex.match(file_name)
- if match:
- file_list[match.group(1)] = file_name
-
- return file_list
-
-def _get_l10n_list(manifest):
- l10n_list = []
-
- for lang in _get_po_list(manifest).keys():
- filename = _get_bundle_id() + '.mo'
- l10n_list.append(os.path.join('locale', lang, 'LC_MESSAGES', filename))
- l10n_list.append(os.path.join('locale', lang, 'activity.linfo'))
-
- return l10n_list
-
-def _get_activity_name():
- info_path = os.path.join(_get_source_path(), 'activity', 'activity.info')
- f = open(info_path,'r')
- info = f.read()
- f.close()
- match = re.search('^name\s*=\s*(.*)$', info, flags = re.MULTILINE)
- return match.group(1)
-
-def cmd_dist(bundle_name, manifest):
- cmd_genl10n(bundle_name, manifest)
- file_list = _get_file_list(manifest)
-
- zipname = _get_package_name(bundle_name)
- bundle_zip = zipfile.ZipFile(zipname, 'w', zipfile.ZIP_DEFLATED)
- base_dir = bundle_name + '.activity'
-
- for filename in file_list:
- bundle_zip.write(filename, os.path.join(base_dir, filename))
-
- for filename in _get_l10n_list(manifest):
- bundle_zip.write(filename, os.path.join(base_dir, filename))
-
- bundle_zip.close()
-
-def cmd_install(bundle_name, manifest, path):
- cmd_dist(bundle_name, manifest)
- cmd_uninstall(path)
-
- _extract_bundle(_get_package_name(bundle_name), path)
-
-def cmd_uninstall(path):
- path = os.path.join(path, _get_bundle_dir())
- if os.path.isdir(path):
- shutil.rmtree(path)
-
-def cmd_genpot(bundle_name, manifest):
- po_path = os.path.join(_get_source_path(), 'po')
- if not os.path.isdir(po_path):
- os.mkdir(po_path)
-
- python_files = []
- file_list = _get_file_list(manifest)
- for file_name in file_list:
- if file_name.endswith('.py'):
- python_files.append(file_name)
-
- # First write out a stub .pot file containing just the translated
- # activity name, then have xgettext merge the rest of the
- # translations into that. (We can't just append the activity name
- # to the end of the .pot file afterwards, because that might
- # create a duplicate msgid.)
- pot_file = os.path.join('po', '%s.pot' % bundle_name)
- activity_name = _get_activity_name()
- escaped_name = re.sub('([\\\\"])', '\\\\\\1', activity_name)
- f = open(pot_file, 'w')
- f.write('#: activity/activity.info:2\n')
- f.write('msgid "%s"\n' % escaped_name)
- f.write('msgstr ""\n')
- f.close()
-
- args = [ 'xgettext', '--join-existing', '--language=Python',
- '--keyword=_', '--add-comments=TRANS:', '--output=%s' % pot_file ]
-
- args += python_files
- retcode = subprocess.call(args)
- if retcode:
- print 'ERROR - xgettext failed with return code %i.' % retcode
-
-
-def cmd_genl10n(bundle_name, manifest):
- source_path = _get_source_path()
- activity_name = _get_activity_name()
-
- po_list = _get_po_list(manifest)
- for lang in po_list.keys():
- file_name = po_list[lang]
-
- localedir = os.path.join(source_path, 'locale', lang)
- mo_path = os.path.join(localedir, 'LC_MESSAGES')
- if not os.path.isdir(mo_path):
- os.makedirs(mo_path)
-
- mo_file = os.path.join(mo_path, "%s.mo" % _get_bundle_id())
- args = ["msgfmt", "--output-file=%s" % mo_file, file_name]
- retcode = subprocess.call(args)
- if retcode:
- print 'ERROR - msgfmt failed with return code %i.' % retcode
-
- cat = gettext.GNUTranslations(open(mo_file, 'r'))
- translated_name = cat.gettext(activity_name)
- linfo_file = os.path.join(localedir, 'activity.linfo')
- f = open(linfo_file, 'w')
- f.write('[Activity]\nname = %s\n' % translated_name)
- f.close()
-
-def cmd_release(bundle_name, manifest):
- if not os.path.isdir('.git'):
- print 'ERROR - this command works only for git repositories'
-
- retcode = subprocess.call(['git', 'pull'])
- if retcode:
- print 'ERROR - cannot pull from git'
-
- print 'Bumping activity version...'
-
- info_path = os.path.join(_get_source_path(), 'activity', 'activity.info')
- f = open(info_path,'r')
- info = f.read()
- f.close()
-
- exp = re.compile('activity_version\s?=\s?([0-9]*)')
- match = re.search(exp, info)
- version = int(match.group(1)) + 1
- info = re.sub(exp, 'activity_version = %d' % version, info)
-
- f = open(info_path, 'w')
- f.write(info)
- f.close()
-
- news_path = os.path.join(_get_source_path(), 'NEWS')
-
- if os.environ.has_key('SUGAR_NEWS'):
- print 'Update NEWS.sugar...'
-
- sugar_news_path = os.environ['SUGAR_NEWS']
- if os.path.isfile(sugar_news_path):
- f = open(sugar_news_path,'r')
- sugar_news = f.read()
- f.close()
- else:
- sugar_news = ''
-
- sugar_news += '%s - %d\n\n' % (bundle_name, version)
-
- f = open(news_path,'r')
- for line in f.readlines():
- if len(line.strip()) > 0:
- sugar_news += line
- else:
- break
- f.close()
-
- sugar_news += '\n'
-
- f = open(sugar_news_path, 'w')
- f.write(sugar_news)
- f.close()
-
- print 'Update NEWS...'
-
- f = open(news_path,'r')
- news = f.read()
- f.close()
-
- news = '%d\n\n' % version + news
-
- f = open(news_path, 'w')
- f.write(news)
- f.close()
-
- print 'Committing to git...'
-
- changelog = 'Release version %d.' % version
- retcode = subprocess.call(['git', 'commit', '-a', '-m % s' % changelog])
- if retcode:
- print 'ERROR - cannot commit to git'
-
- retcode = subprocess.call(['git', 'push'])
- if retcode:
- print 'ERROR - cannot push to git'
-
- print 'Creating the bundle...'
- cmd_dist(bundle_name, manifest)
-
- print 'Done.'
-
-def cmd_clean():
- os.path.walk('.', _delete_backups, None)
-
-def sanity_check():
- if not os.path.isfile(_get_source_path('NEWS')):
- print 'WARNING: NEWS file is missing.'
-
-def start(bundle_name, manifest='MANIFEST'):
- sanity_check()
-
- if len(sys.argv) < 2:
- cmd_help()
- elif sys.argv[1] == 'build':
- pass
- elif sys.argv[1] == 'dev':
- cmd_dev()
- elif sys.argv[1] == 'dist':
- cmd_dist(bundle_name, manifest)
- elif sys.argv[1] == 'install' and len(sys.argv) == 3:
- cmd_install(bundle_name, manifest, sys.argv[2])
- elif sys.argv[1] == 'uninstall' and len(sys.argv) == 3:
- cmd_uninstall(sys.argv[2])
- elif sys.argv[1] == 'genpot':
- cmd_genpot(bundle_name, manifest)
- elif sys.argv[1] == 'genl10n':
- cmd_genl10n(bundle_name, manifest)
- elif sys.argv[1] == 'clean':
- cmd_clean()
- elif sys.argv[1] == 'release':
- cmd_release(bundle_name, manifest)
- else:
- cmd_help()
-
-if __name__ == '__main__':
- start()
diff --git a/sugar/activity/registry.py b/sugar/activity/registry.py
deleted file mode 100644
index 5f5aefc..0000000
--- a/sugar/activity/registry.py
+++ /dev/null
@@ -1,182 +0,0 @@
-# Copyright (C) 2006-2007 Red Hat, Inc.
-# Copyright (C) 2007 One Laptop Per Child
-#
-# 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 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.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the
-# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-# Boston, MA 02111-1307, USA.
-
-import logging
-
-import dbus
-import gobject
-
-_ACTIVITY_REGISTRY_SERVICE_NAME = 'org.laptop.ActivityRegistry'
-_ACTIVITY_REGISTRY_IFACE = 'org.laptop.ActivityRegistry'
-_ACTIVITY_REGISTRY_PATH = '/org/laptop/ActivityRegistry'
-
-def _activity_info_from_dict(info_dict):
- if not info_dict:
- return None
- return ActivityInfo(info_dict['name'], info_dict['icon'],
- info_dict['bundle_id'], info_dict['version'],
- info_dict['path'], info_dict['show_launcher'],
- info_dict['command'], info_dict['favorite'])
-
-class ActivityInfo(object):
- def __init__(self, name, icon, bundle_id, version,
- path, show_launcher, command, favorite):
- self.name = name
- self.icon = icon
- self.bundle_id = bundle_id
- self.version = version
- self.path = path
- self.command = command
- self.show_launcher = show_launcher
- self.favorite = favorite
-
-class ActivityRegistry(gobject.GObject):
- __gsignals__ = {
- 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT])),
- 'activity-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT])),
- 'activity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
- ([gobject.TYPE_PYOBJECT]))
- }
- def __init__(self):
- gobject.GObject.__init__(self)
-
- bus = dbus.SessionBus()
-
- # NOTE: We need to follow_name_owner_changes here
- # because we can not connect to a signal unless
- # we follow the changes or we start the service
- # before we connect. Starting the service here
- # causes a major bottleneck during startup
- bus_object = bus.get_object(_ACTIVITY_REGISTRY_SERVICE_NAME,
- _ACTIVITY_REGISTRY_PATH,
- follow_name_owner_changes = True)
- self._registry = dbus.Interface(bus_object, _ACTIVITY_REGISTRY_IFACE)
- self._registry.connect_to_signal('ActivityAdded',
- self._activity_added_cb)
- self._registry.connect_to_signal('ActivityRemoved',
- self._activity_removed_cb)
- self._registry.connect_to_signal('ActivityChanged',
- self._activity_changed_cb)
-
- # Two caches fo saving some travel across dbus.
- self._service_name_to_activity_info = {}
- self._mime_type_to_activities = {}
-
- def _convert_info_list(self, info_list):
- result = []
-
- for info_dict in info_list:
- result.append(_activity_info_from_dict(info_dict))
-
- return result
-
- def get_activities(self):
- info_list = self._registry.GetActivities()
- return self._convert_info_list(info_list)
-
- def _get_activities_cb(self, reply_handler, info_list):
- result = []
- for info_dict in info_list:
- result.append(_activity_info_from_dict(info_dict))
-
- reply_handler(result)
-
- def _get_activities_error_cb(self, error_handler, e):
- if error_handler:
- error_handler(e)
- else:
- logging.error('Error getting activities async: %s' % str(e))
-
- def get_activities_async(self, reply_handler=None, error_handler=None):
- if not reply_handler:
- logging.error('Function get_activities_async called' \
- 'without a reply handler. Can not run.')
- return
-
- self._registry.GetActivities(
- reply_handler=lambda info_list: \
- self._get_activities_cb(reply_handler, info_list),
- error_handler=lambda e: \
- self._get_activities_error_cb(error_handler, e))
-
- def get_activity(self, service_name):
- if self._service_name_to_activity_info.has_key(service_name):
- return self._service_name_to_activity_info[service_name]
-
- info_dict = self._registry.GetActivity(service_name)
- activity_info = _activity_info_from_dict(info_dict)
-
- self._service_name_to_activity_info[service_name] = activity_info
- return activity_info
-
- def find_activity(self, name):
- info_list = self._registry.FindActivity(name)
- return self._convert_info_list(info_list)
-
- def get_activities_for_type(self, mime_type):
- if self._mime_type_to_activities.has_key(mime_type):
- return self._mime_type_to_activities[mime_type]
-
- info_list = self._registry.GetActivitiesForType(mime_type)
- activities = self._convert_info_list(info_list)
-
- self._mime_type_to_activities[mime_type] = activities
- return activities
-
- def add_bundle(self, bundle_path):
- result = self._registry.AddBundle(bundle_path)
- # Need to invalidate here because get_activity could be called after
- # add_bundle and before we receive activity-added, causing a race.
- self._invalidate_cache()
- return result
-
- def _activity_added_cb(self, info_dict):
- logging.debug('ActivityRegistry._activity_added_cb: invalidating cache')
- self._invalidate_cache()
- self.emit('activity-added', _activity_info_from_dict(info_dict))
-
- def _invalidate_cache(self):
- self._service_name_to_activity_info.clear()
- self._mime_type_to_activities.clear()
-
- def remove_bundle(self, bundle_path):
- self._invalidate_cache()
- return self._registry.RemoveBundle(bundle_path)
-
- def _activity_removed_cb(self, info_dict):
- logging.debug('ActivityRegistry._activity_removed_cb: flushing caches')
- self._invalidate_cache()
- self.emit('activity-removed', _activity_info_from_dict(info_dict))
-
- def _activity_changed_cb(self, info_dict):
- logging.debug('ActivityRegistry._activity_changed_cb: flushing caches')
- self._invalidate_cache()
- self.emit('activity-changed', _activity_info_from_dict(info_dict))
-
- def set_activity_favorite(self, bundle_id, version, favorite):
- self._registry.SetActivityFavorite(bundle_id, version, favorite)
-
-_registry = None
-
-def get_registry():
- global _registry
- if not _registry:
- _registry = ActivityRegistry()
- return _registry