Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksey Lim <alsroot@sugarlabs.org>2012-08-15 12:31:16 (GMT)
committer Aleksey Lim <alsroot@sugarlabs.org>2012-08-15 12:31:16 (GMT)
commit5343c4c09cd0f24cc98e93b0e53b06d685b9fbc9 (patch)
tree05fbd80cb14a4f507f9ace4741be9bc2bb794c20
parent94791e3854b4c201b69302448c6e48307e4ed733 (diff)
Make activities monitor less curly; register mime types and icons on activity checkin
-rwxr-xr-xsugar-network-service16
-rw-r--r--sugar_network/local/activities.py271
-rw-r--r--sugar_network/local/activities_crawler.py199
-rw-r--r--sugar_network/toolkit/__init__.py67
-rw-r--r--sugar_network/toolkit/crypto.py40
-rw-r--r--tests/__init__.py1
-rw-r--r--tests/units/__main__.py1
-rwxr-xr-xtests/units/activities.py151
-rwxr-xr-xtests/units/activities_crawler.py154
-rwxr-xr-xtests/units/mountset.py1
10 files changed, 460 insertions, 441 deletions
diff --git a/sugar-network-service b/sugar-network-service
index e525ab8..5df3b53 100755
--- a/sugar-network-service
+++ b/sugar-network-service
@@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import signal
import locale
import logging
from contextlib import contextmanager
@@ -24,9 +25,9 @@ from os.path import join, abspath, exists
import active_document as ad
import sugar_network_webui as webui
-from sugar_network import local, node, Client
+from sugar_network import toolkit, local, node, Client
from sugar_network.toolkit import sugar, sneakernet
-from sugar_network.toolkit import crypto, dbus_thread, mounts_monitor
+from sugar_network.toolkit import dbus_thread, mounts_monitor
from sugar_network.local import activities, datastore
from sugar_network.local.dbus_datastore import Datastore
from sugar_network.local.dbus_network import Network
@@ -65,6 +66,7 @@ class Application(application.Application):
'inotify',
'netlink',
'sneakernet',
+ 'toolkit',
):
logger = logging.getLogger(log_name)
logger.propagate = False
@@ -83,6 +85,8 @@ class Application(application.Application):
os.makedirs(local.tmpdir.value)
sneakernet.TMPDIR = local.tmpdir.value
+ coroutine.signal(signal.SIGCHLD, self.__SIGCHLD_cb)
+
@application.command(
'[PREFIX-PATH [CONTEXT-GUID ..]]\n'
'Index local Sugar Network database; if CONTEXT-GUIDs '
@@ -146,7 +150,7 @@ class Application(application.Application):
name='start', keep_stdout=True)
def _start(self):
# In case if it is new Sugar Shell profile
- crypto.ensure_dsa_pubkey(sugar.profile_path('owner.key'))
+ toolkit.ensure_dsa_pubkey(sugar.profile_path('owner.key'))
jobs = coroutine.Pool()
@@ -245,6 +249,12 @@ class Application(application.Application):
def _db_path(self):
return join(local.local_root.value, 'local')
+ def __SIGCHLD_cb(self):
+ while True:
+ pid, __ = os.waitpid(-1, os.WNOHANG)
+ if not pid:
+ break
+
locale.setlocale(locale.LC_ALL, '')
diff --git a/sugar_network/local/activities.py b/sugar_network/local/activities.py
index 5da6f28..d750dd7 100644
--- a/sugar_network/local/activities.py
+++ b/sugar_network/local/activities.py
@@ -17,14 +17,17 @@ import os
import hashlib
import logging
import tempfile
-from os.path import join, exists, lexists, relpath, dirname, basename
+from os.path import join, exists, lexists, relpath, dirname, basename, isdir
+from os.path import abspath
-import sweets_recipe
+from sweets_recipe import Spec
+from sugar_network.toolkit.inotify import Inotify, \
+ IN_DELETE_SELF, IN_CREATE, IN_DELETE, IN_CLOSE_WRITE, \
+ IN_MOVED_TO, IN_MOVED_FROM
from active_document import DEFAULT_LANG
from sugar_network.toolkit import sugar
-from sugar_network import local
-from sugar_network.local import activities_crawler
-from active_toolkit import util
+from sugar_network import toolkit, local
+from active_toolkit import coroutine, util
_logger = logging.getLogger('local.activities')
@@ -70,29 +73,57 @@ def ensure_checkins(context):
def monitor(contexts, paths):
- _Monitor(contexts, paths).serve_forever()
+ inotify = _Inotify(contexts)
+ inotify.setup(paths)
+ inotify.serve_forever()
def populate(contexts, paths, prefix):
- _Monitor(contexts, paths, prefix).populate()
+ inotify = _Inotify(contexts)
+ inotify.add_watch = lambda *args: None
+ inotify.setup(paths)
-class _Monitor(object):
+class _Inotify(Inotify):
+
+ def __init__(self, contexts):
+ Inotify.__init__(self)
- def __init__(self, contexts, paths, prefix=''):
self._contexts = contexts
- self._paths = paths
- self._prefix = prefix
+ self._roots = []
+ self._jobs = coroutine.Pool()
+
+ xdg_data_home = os.environ.get('XDG_DATA_HOME') or \
+ join(os.environ['HOME'], '.local', 'share')
+ self._icons_dir = join(xdg_data_home,
+ 'icons', 'sugar', 'scalable', 'mimetypes')
+ self._mime_dir = join(xdg_data_home, 'mime')
+
+ def setup(self, paths):
+ for path in paths:
+ path = abspath(path)
+ if not exists(path):
+ if not os.access(dirname(path), os.W_OK):
+ _logger.warning('No permissions to create %s '
+ 'directory, do not monitor it', path)
+ continue
+ os.makedirs(path)
+ self._roots.append(_Root(self, path))
def serve_forever(self):
- activities_crawler.dispatch(self._paths,
- self.__found_cb, self.__lost_cb)
-
- def populate(self):
- activities_crawler.populate(self._paths,
- self.__found_cb, self.__lost_cb)
-
- def __found_cb(self, impl_path):
+ while True:
+ coroutine.select([self.fileno()], [], [])
+ if self.closed:
+ break
+ for filename, event, cb in self.read():
+ try:
+ cb(filename, event)
+ except Exception:
+ util.exception('Cannot dispatch 0x%X event for %r',
+ event, filename)
+ coroutine.dispatch()
+
+ def found(self, impl_path):
hashed_path, checkin_path = _checkin_path(impl_path)
if exists(checkin_path):
return
@@ -100,10 +131,9 @@ class _Monitor(object):
_logger.debug('Checking in activity from %r', impl_path)
try:
- spec = sweets_recipe.Spec(root=impl_path)
- except Exception, error:
- util.exception(_logger, 'Cannot read %r spec: %s',
- impl_path, error)
+ spec = Spec(root=impl_path)
+ except Exception:
+ util.exception(_logger, 'Cannot read %r spec', impl_path)
return
context = spec['Activity', 'bundle_id']
@@ -112,7 +142,7 @@ class _Monitor(object):
else:
_logger.debug('Register unknown local activity, %r', context)
- mtime = os.stat(impl_path).st_mtime
+ mtime = os.stat(spec.root).st_mtime
self._contexts.create(guid=context, type='activity',
title={DEFAULT_LANG: spec['name']},
summary={DEFAULT_LANG: spec['summary']},
@@ -124,20 +154,35 @@ class _Monitor(object):
if exists(icon_path):
self._contexts.set_blob(context, 'artifact_icon', icon_path)
with tempfile.NamedTemporaryFile() as f:
- _svg_to_png(icon_path, f.name, 32, 32)
+ toolkit.svg_to_png(icon_path, f.name, 32, 32)
self._contexts.set_blob(context, 'icon', f.name)
+ self._checkin_activity(spec)
+
context_path = _ensure_context_path(context, hashed_path)
if lexists(context_path):
os.unlink(context_path)
- os.symlink(impl_path[len(self._prefix):], context_path)
+ os.symlink(impl_path, context_path)
if lexists(checkin_path):
os.unlink(checkin_path)
local.ensure_path(checkin_path)
os.symlink(relpath(context_path, dirname(checkin_path)), checkin_path)
- def __lost_cb(self, impl_path):
+ def found_mimetypes(self, impl_path):
+ hashed_path, __ = _checkin_path(impl_path)
+ src_path = join(impl_path, 'activity', 'mimetypes.xml')
+ dst_path = join(self._mime_dir, 'packages', hashed_path + '.xml')
+
+ if exists(dst_path):
+ return
+
+ _logger.debug('Update MIME database to process found %r', src_path)
+
+ toolkit.symlink(src_path, dst_path)
+ toolkit.spawn('update-mime-database', self._mime_dir)
+
+ def lost(self, impl_path):
__, checkin_path = _checkin_path(impl_path)
if not lexists(checkin_path):
return
@@ -157,6 +202,163 @@ class _Monitor(object):
os.unlink(context_path)
os.unlink(checkin_path)
+ def lost_mimetypes(self, impl_path):
+ hashed_path, __ = _checkin_path(impl_path)
+ dst_path = join(self._mime_dir, 'packages', hashed_path + '.xml')
+
+ if not exists(dst_path):
+ return
+
+ _logger.debug('Update MIME database to process lost %r', impl_path)
+
+ os.unlink(dst_path)
+ toolkit.spawn('update-mime-database', self._mime_dir)
+
+ def _checkin_activity(self, spec):
+ icon_path = join(spec.root, spec['icon'])
+ if spec['mime_types'] and exists(icon_path):
+ _logger.debug('Register %r icons for %r',
+ spec['mime_types'], spec)
+ if not exists(self._icons_dir):
+ os.makedirs(self._icons_dir)
+ for mime_type in spec['mime_types']:
+ toolkit.symlink(icon_path,
+ join(self._icons_dir,
+ mime_type.replace('/', '-') + '.svg'))
+
+
+class _Root(object):
+
+ def __init__(self, monitor_, path):
+ self.path = path
+ self._monitor = monitor_
+ self._nodes = {}
+
+ _logger.info('Start monitoring %r activities root', self.path)
+
+ self._monitor.add_watch(self.path,
+ IN_DELETE_SELF | IN_CREATE | IN_DELETE |
+ IN_MOVED_TO | IN_MOVED_FROM,
+ self.__watch_cb)
+
+ for filename in os.listdir(self.path):
+ path = join(self.path, filename)
+ if isdir(path):
+ self._nodes[filename] = _Node(self._monitor, path)
+
+ def __watch_cb(self, filename, event):
+ if event & IN_DELETE_SELF:
+ _logger.warning('Lost ourselves, cannot monitor anymore')
+ self._nodes.clear()
+ return
+
+ if event & (IN_CREATE | IN_MOVED_TO):
+ path = join(self.path, filename)
+ if isdir(path):
+ self._nodes[filename] = _Node(self._monitor, path)
+ elif event & (IN_DELETE | IN_MOVED_FROM):
+ node = self._nodes.get(filename)
+ if node is not None:
+ node.unlink()
+ del self._nodes[filename]
+
+
+class _Node(object):
+
+ def __init__(self, monitor_, path):
+ self._path = path
+ self._monitor = monitor_
+ self._activity_path = join(path, 'activity')
+ self._activity_dir = None
+
+ _logger.debug('Start monitoring %r root activity directory', path)
+
+ self._wd = self._monitor.add_watch(path,
+ IN_CREATE | IN_DELETE | IN_MOVED_TO | IN_MOVED_FROM,
+ self.__watch_cb)
+
+ if exists(self._activity_path):
+ self._activity_dir = \
+ _ActivityDir(self._monitor, self._activity_path)
+
+ def unlink(self):
+ if self._activity_dir is not None:
+ self._activity_dir.unlink()
+ self._activity_dir = None
+ _logger.debug('Stop monitoring %r root activity directory', self._path)
+ self._monitor.rm_watch(self._wd)
+
+ def __watch_cb(self, filename, event):
+ if filename != 'activity':
+ return
+ if event & (IN_CREATE | IN_MOVED_TO):
+ self._activity_dir = \
+ _ActivityDir(self._monitor, self._activity_path)
+ elif event & (IN_DELETE | IN_MOVED_FROM):
+ self._activity_dir.unlink()
+ self._activity_dir = None
+
+
+class _ActivityDir(object):
+
+ def __init__(self, monitor_, path):
+ self._path = path
+ self._monitor = monitor_
+ self._found = False
+ self._node_path = dirname(path)
+
+ _logger.debug('Start monitoring %r activity directory', path)
+
+ self._wd = self._monitor.add_watch(path,
+ IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_MOVED_TO |
+ IN_MOVED_FROM,
+ self.__watch_cb)
+
+ for filename in ('activity.info', 'mimetypes.xml'):
+ if exists(join(path, filename)):
+ self.found(filename)
+
+ def unlink(self):
+ self.lost('activity.info')
+ _logger.debug('Stop monitoring %r activity directory', self._path)
+ self._monitor.rm_watch(self._wd)
+
+ def found(self, filename):
+ if filename == 'mimetypes.xml':
+ self._monitor.found_mimetypes(self._node_path)
+ return
+ if self._found:
+ return
+ _logger.debug('Found %r', self._node_path)
+ self._found = True
+ self._monitor.found(self._node_path)
+ if exists(join(self._path, 'mimetypes.xml')):
+ self._monitor.found_mimetypes(self._node_path)
+
+ def lost(self, filename):
+ if filename == 'mimetypes.xml':
+ self._monitor.lost_mimetypes(self._node_path)
+ return
+ if not self._found:
+ return
+ _logger.debug('Lost %r', self._node_path)
+ self._found = False
+ self._monitor.lost(self._node_path)
+
+ def __watch_cb(self, filename, event):
+ if filename not in ('activity.info', 'mimetypes.xml'):
+ return
+ if event & IN_CREATE:
+ # There is only one case when newly created file can be read,
+ # if number of hardlinks is bigger than one, i.e., its content
+ # already populated
+ if os.stat(join(self._path, filename)).st_nlink > 1:
+ self.found(filename)
+ elif event & (IN_CLOSE_WRITE | IN_MOVED_TO):
+ self.found(filename)
+ elif event & (IN_DELETE | IN_MOVED_FROM):
+ self.lost(filename)
+
def _checkin_path(impl_path):
hashed_path = path_to_guid(impl_path)
@@ -173,18 +375,3 @@ def _context_path(context, hashed_path):
def _ensure_context_path(context, hashed_path):
return local.ensure_path('activities', 'context', context, hashed_path)
-
-
-def _svg_to_png(src_path, dst_path, w, h):
- import rsvg
- import cairo
-
- svg = rsvg.Handle(src_path)
-
- surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
- context = cairo.Context(surface)
- scale = min(float(w) / svg.props.width, float(h) / svg.props.height)
- context.scale(scale, scale)
- svg.render_cairo(context)
-
- surface.write_to_png(dst_path)
diff --git a/sugar_network/local/activities_crawler.py b/sugar_network/local/activities_crawler.py
deleted file mode 100644
index e2f5b0f..0000000
--- a/sugar_network/local/activities_crawler.py
+++ /dev/null
@@ -1,199 +0,0 @@
-# Copyright (C) 2012 Aleksey Lim
-#
-# 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 3 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, see <http://www.gnu.org/licenses/>.
-
-import os
-import logging
-from os.path import join, exists, isdir, dirname, abspath
-
-from active_toolkit import coroutine, util
-from sugar_network.toolkit.inotify import Inotify, \
- IN_DELETE_SELF, IN_CREATE, IN_DELETE, IN_CLOSE_WRITE, \
- IN_MOVED_TO, IN_MOVED_FROM
-
-
-_logger = logging.getLogger('local.activities_crawler')
-
-
-def populate(paths, found_cb, lost_cb):
-
- class FakeMonitor(object):
-
- def add_watch(self, *args):
- pass
-
- def found_cb(self, path):
- found_cb(path)
-
- def lost_cb(self, path):
- lost_cb(path)
-
- for path in paths:
- if exists(path):
- _Root(FakeMonitor(), path)
-
-
-def dispatch(paths, found_cb, lost_cb):
- with _Inotify(found_cb, lost_cb) as monitor:
- roots = []
- for path in paths:
- path = abspath(path)
- if not exists(path):
- if not os.access(dirname(path), os.W_OK):
- _logger.warning('No permissions to create %s '
- 'directory, do not monitor it', path)
- continue
- os.makedirs(path)
- roots.append(_Root(monitor, path))
-
- while True:
- coroutine.select([monitor.fileno()], [], [])
- if monitor.closed:
- break
- for filename, event, cb in monitor.read():
- try:
- cb(filename, event)
- except Exception:
- util.exception('Cannot dispatch 0x%X event for %r',
- event, filename)
- coroutine.dispatch()
-
-
-class _Inotify(Inotify):
-
- def __init__(self, found_cb, lost_cb):
- Inotify.__init__(self)
- self.found_cb = found_cb
- self.lost_cb = lost_cb
-
-
-class _Root(object):
-
- def __init__(self, monitor, path):
- self.path = path
- self._monitor = monitor
- self._nodes = {}
-
- _logger.info('Start monitoring %r activities root', self.path)
-
- monitor.add_watch(self.path,
- IN_DELETE_SELF | IN_CREATE | IN_DELETE |
- IN_MOVED_TO | IN_MOVED_FROM,
- self.__watch_cb)
-
- for filename in os.listdir(self.path):
- path = join(self.path, filename)
- if isdir(path):
- self._nodes[filename] = _Node(monitor, path)
-
- def __watch_cb(self, filename, event):
- if event & IN_DELETE_SELF:
- _logger.warning('Lost ourselves, cannot monitor anymore')
- self._nodes.clear()
- return
-
- if event & (IN_CREATE | IN_MOVED_TO):
- path = join(self.path, filename)
- if isdir(path):
- self._nodes[filename] = _Node(self._monitor, path)
- elif event & (IN_DELETE | IN_MOVED_FROM):
- node = self._nodes.get(filename)
- if node is not None:
- node.unlink()
- del self._nodes[filename]
-
-
-class _Node(object):
-
- def __init__(self, monitor, path):
- self._path = path
- self._monitor = monitor
- self._activity_path = join(path, 'activity')
- self._activity_dir = None
-
- _logger.debug('Start monitoring %r root activity directory', path)
-
- self._wd = monitor.add_watch(path,
- IN_CREATE | IN_DELETE | IN_MOVED_TO | IN_MOVED_FROM,
- self.__watch_cb)
-
- if exists(self._activity_path):
- self._activity_dir = _ActivityDir(monitor, self._activity_path)
-
- def unlink(self):
- if self._activity_dir is not None:
- self._activity_dir.unlink()
- self._activity_dir = None
- _logger.debug('Stop monitoring %r root activity directory', self._path)
- self._monitor.rm_watch(self._wd)
-
- def __watch_cb(self, filename, event):
- if filename != 'activity':
- return
- if event & (IN_CREATE | IN_MOVED_TO):
- self._activity_dir = \
- _ActivityDir(self._monitor, self._activity_path)
- elif event & (IN_DELETE | IN_MOVED_FROM):
- self._activity_dir.unlink()
- self._activity_dir = None
-
-
-class _ActivityDir(object):
-
- def __init__(self, monitor, path):
- self._path = path
- self._monitor = monitor
- self._found = False
- self._node_path = dirname(path)
- self._info_path = join(path, 'activity.info')
-
- _logger.debug('Start monitoring %r activity directory', path)
-
- self._wd = monitor.add_watch(path,
- IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_MOVED_TO |
- IN_MOVED_FROM,
- self.__watch_cb)
-
- if exists(self._info_path):
- self.found()
-
- def unlink(self):
- self.lost()
- _logger.debug('Stop monitoring %r activity directory', self._path)
- self._monitor.rm_watch(self._wd)
-
- def found(self):
- if self._found:
- return
- _logger.debug('Found %r', self._node_path)
- self._found = True
- self._monitor.found_cb(self._node_path)
-
- def lost(self):
- if not self._found:
- return
- _logger.debug('Lost %r', self._node_path)
- self._found = False
- self._monitor.lost_cb(self._node_path)
-
- def __watch_cb(self, filename, event):
- if filename != 'activity.info':
- return
- if event & IN_CREATE:
- if os.stat(join(self._path, filename)).st_nlink > 1:
- self.found()
- elif event & (IN_CLOSE_WRITE | IN_MOVED_TO):
- self.found()
- elif event & (IN_DELETE | IN_MOVED_FROM):
- self.lost()
diff --git a/sugar_network/toolkit/__init__.py b/sugar_network/toolkit/__init__.py
index ee29047..e1f7949 100644
--- a/sugar_network/toolkit/__init__.py
+++ b/sugar_network/toolkit/__init__.py
@@ -12,3 +12,70 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import logging
+import hashlib
+from os.path import isfile, lexists, exists, dirname
+
+from active_toolkit import util
+
+
+_logger = logging.getLogger('toolkit')
+
+
+def spawn(cmd_filename, *args):
+ _logger.debug('Spawn %s%r', cmd_filename, args)
+
+ if os.fork():
+ return
+
+ os.execvp(cmd_filename, (cmd_filename,) + args)
+
+
+def symlink(src, dst):
+ if not isfile(src):
+ _logger.debug('Cannot link %r to %r, source file is absent', src, dst)
+ return
+
+ _logger.debug('Link %r to %r', src, dst)
+
+ if lexists(dst):
+ os.unlink(dst)
+ elif not exists(dirname(dst)):
+ os.makedirs(dirname(dst))
+ os.symlink(src, dst)
+
+
+def ensure_dsa_pubkey(path):
+ if not exists(path):
+ _logger.info('Create DSA server key')
+ util.assert_call([
+ '/usr/bin/ssh-keygen', '-q', '-t', 'dsa', '-f', path,
+ '-C', '', '-N', ''])
+
+ with file(path + '.pub') as f:
+ for line in f:
+ line = line.strip()
+ if line.startswith('ssh-'):
+ key = line.split()[1]
+ return str(hashlib.sha1(key).hexdigest())
+
+ raise RuntimeError('No valid DSA public key in %r' % path)
+
+
+def svg_to_png(src_path, dst_path, width, height):
+ import rsvg
+ import cairo
+
+ svg = rsvg.Handle(src_path)
+
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+ context = cairo.Context(surface)
+ scale = min(
+ float(width) / svg.props.width,
+ float(height) / svg.props.height)
+ context.scale(scale, scale)
+ svg.render_cairo(context)
+
+ surface.write_to_png(dst_path)
diff --git a/sugar_network/toolkit/crypto.py b/sugar_network/toolkit/crypto.py
deleted file mode 100644
index 8beb224..0000000
--- a/sugar_network/toolkit/crypto.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright (C) 2012 Aleksey Lim
-#
-# 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 3 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, see <http://www.gnu.org/licenses/>.
-
-import hashlib
-import logging
-from os.path import exists
-
-from active_toolkit import util
-
-
-_logger = logging.getLogger('crypto')
-
-
-def ensure_dsa_pubkey(path):
- if not exists(path):
- _logger.info('Create DSA server key')
- util.assert_call([
- '/usr/bin/ssh-keygen', '-q', '-t', 'dsa', '-f', path,
- '-C', '', '-N', ''])
-
- with file(path + '.pub') as f:
- for line in f:
- line = line.strip()
- if line.startswith('ssh-'):
- key = line.split()[1]
- return str(hashlib.sha1(key).hexdigest())
-
- raise RuntimeError('No valid DSA public key in %r' % path)
diff --git a/tests/__init__.py b/tests/__init__.py
index 0fdc6c3..6235904 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -62,6 +62,7 @@ class Test(unittest.TestCase):
if exists(self.logfile):
os.unlink(self.logfile)
+ os.environ['XDG_DATA_HOME'] = tmpdir + '/share'
os.environ['SUGAR_LOGGER_LEVEL'] = 'all'
os.environ['HOME'] = tmpdir
profile_dir = join(tmpdir, '.sugar', 'default')
diff --git a/tests/units/__main__.py b/tests/units/__main__.py
index 4624277..23c08f8 100644
--- a/tests/units/__main__.py
+++ b/tests/units/__main__.py
@@ -5,7 +5,6 @@ from __init__ import tests
from collection import *
from sneakernet import *
from bus import *
-from activities_crawler import *
from activities import *
from ipc_client import *
from injector import *
diff --git a/tests/units/activities.py b/tests/units/activities.py
index 9bd1152..707e05b 100755
--- a/tests/units/activities.py
+++ b/tests/units/activities.py
@@ -35,6 +35,135 @@ class ActivitiesTest(tests.Test):
self.mounts.close()
tests.Test.tearDown(self)
+ def test_Inotify_NoPermissions(self):
+ assert not exists('/foo/bar')
+ inotify = activities._Inotify(self.mounts.home_volume['context'])
+ inotify.setup(['/foo/bar'])
+ assert not exists('/foo/bar')
+
+ def test_Inotify_Walkthrough(self):
+ self.touch('file')
+ os.makedirs('activity-1')
+ os.makedirs('activity-2/activity')
+ self.touch('activity-3/activity/activity.info')
+ self.touch('activity-4/activity/activity.info')
+ self.touch('activity-5/activity/activity.info')
+
+ found = []
+ lost = []
+
+ inotify = activities._Inotify(self.mounts.home_volume['context'])
+ inotify.found = found.append
+ inotify.lost = lost.append
+ inotify.setup(['.'])
+ self.job = coroutine.spawn(inotify.serve_forever)
+ coroutine.sleep(1)
+
+ self.assertEqual(
+ sorted([
+ tests.tmpdir + '/activity-3',
+ tests.tmpdir + '/activity-4',
+ tests.tmpdir + '/activity-5',
+ ]),
+ sorted(found))
+ self.assertEqual([], lost)
+ del found[:]
+
+ with file('activity-4/activity/activity.info', 'w') as f:
+ f.close()
+ coroutine.sleep(.1)
+ self.assertEqual([], found)
+ self.assertEqual([], lost)
+
+ with file('activity-2/activity/activity.info', 'w') as f:
+ f.close()
+ coroutine.sleep(.1)
+ self.assertEqual([tests.tmpdir + '/activity-2'], found)
+ self.assertEqual([], lost)
+ del found[:]
+
+ os.makedirs('activity-6/activity')
+ coroutine.sleep(.1)
+ self.assertEqual([], found)
+ self.assertEqual([], lost)
+
+ with file('activity-6/activity/activity.info', 'w') as f:
+ f.close()
+ coroutine.sleep(.1)
+ self.assertEqual([tests.tmpdir + '/activity-6'], found)
+ self.assertEqual([], lost)
+ del found[:]
+
+ os.unlink('activity-5/activity/activity.info')
+ coroutine.sleep(.1)
+ self.assertEqual([], found)
+ self.assertEqual([tests.tmpdir + '/activity-5'], lost)
+ del lost[:]
+
+ shutil.rmtree('activity-5')
+ coroutine.sleep(.1)
+ self.assertEqual([], found)
+ self.assertEqual([], lost)
+
+ shutil.rmtree('activity-4')
+ coroutine.sleep(.1)
+ coroutine.sleep(.1)
+ self.assertEqual([], found)
+ self.assertEqual([tests.tmpdir + '/activity-4'], lost)
+ del lost[:]
+
+ def test_Inotify_Moves(self):
+ self.touch('Activities/activity/activity/activity.info')
+
+ found = []
+ lost = []
+
+ inotify = activities._Inotify(self.mounts.home_volume['context'])
+ inotify.found = found.append
+ inotify.lost = lost.append
+ inotify.setup(['Activities'])
+ self.job = coroutine.spawn(inotify.serve_forever)
+ coroutine.sleep(.1)
+
+ shutil.move('Activities/activity', '.')
+ coroutine.sleep(.1)
+ self.assertEqual([tests.tmpdir + '/Activities/activity'], found)
+ self.assertEqual([tests.tmpdir + '/Activities/activity'], lost)
+ del found[:]
+ del lost[:]
+ shutil.move('activity', 'Activities/')
+ coroutine.sleep(.1)
+ self.assertEqual([tests.tmpdir + '/Activities/activity'], found)
+ self.assertEqual([], lost)
+ del found[:]
+ del lost[:]
+
+ shutil.move('Activities/activity/activity', 'Activities/activity/activity2')
+ coroutine.sleep(.1)
+ self.assertEqual([], found)
+ self.assertEqual([tests.tmpdir + '/Activities/activity'], lost)
+ del found[:]
+ del lost[:]
+ shutil.move('Activities/activity/activity2', 'Activities/activity/activity')
+ coroutine.sleep(.1)
+ self.assertEqual([tests.tmpdir + '/Activities/activity'], found)
+ self.assertEqual([], lost)
+ del found[:]
+ del lost[:]
+
+ shutil.move('Activities/activity/activity/activity.info', 'Activities/activity/activity/activity.info2')
+ coroutine.sleep(.1)
+ self.assertEqual([], found)
+ self.assertEqual([tests.tmpdir + '/Activities/activity'], lost)
+ del found[:]
+ del lost[:]
+ shutil.move('Activities/activity/activity/activity.info2', 'Activities/activity/activity/activity.info')
+ coroutine.sleep(.1)
+ self.assertEqual([tests.tmpdir + '/Activities/activity'], found)
+ self.assertEqual([], lost)
+ del found[:]
+ del lost[:]
+
def test_Checkin_Create(self):
self.job = coroutine.spawn(activities.monitor,
self.mounts.home_volume['context'], ['Activities'])
@@ -47,13 +176,24 @@ class ActivitiesTest(tests.Test):
os.makedirs('Activities/activity/activity')
coroutine.sleep(1)
+ self.touch('Activities/activity/activity/icon.svg')
+ self.touch(('Activities/activity/activity/mimetypes.xml', [
+ '<?xml version="1.0" encoding="UTF-8"?>',
+ '<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">',
+ '<mime-type type="application/x-foo-bar">',
+ '<comment xml:lang="en">foo-bar</comment>',
+ '<glob pattern="*.foo"/>',
+ '</mime-type>',
+ '</mime-info>',
+ ]))
spec = ['[Activity]',
'name = HelloWorld',
'activity_version = 1',
'bundle_id = org.sugarlabs.HelloWorld',
'exec = sugar-activity activity.HelloWorldActivity',
- 'icon = activity-helloworld',
+ 'icon = icon',
'license = GPLv2+',
+ 'mime_types = foo/bar',
]
with file('Activities/activity/activity/activity.info', 'w') as f:
coroutine.sleep(1)
@@ -68,6 +208,15 @@ class ActivitiesTest(tests.Test):
self.assertEqual(
{'guid': 'org.sugarlabs.HelloWorld', 'title': {'en': 'title'}, 'keep': False, 'keep_impl': 2},
self.mounts.home_volume['context'].get('org.sugarlabs.HelloWorld').properties(['guid', 'title', 'keep', 'keep_impl']))
+ assert exists('share/icons/sugar/scalable/mimetypes/foo-bar.svg')
+ self.assertEqual(
+ tests.tmpdir + '/Activities/activity/activity/icon.svg',
+ os.readlink('share/icons/sugar/scalable/mimetypes/foo-bar.svg'))
+ assert exists('share/mime/packages/%s.xml' % hashed_path)
+ self.assertEqual(
+ tests.tmpdir + '/Activities/activity/activity/mimetypes.xml',
+ os.readlink('share/mime/packages/%s.xml' % hashed_path))
+ assert exists('share/mime/application/x-foo-bar.xml')
def test_Checkin_Copy(self):
self.job = coroutine.spawn(activities.monitor,
diff --git a/tests/units/activities_crawler.py b/tests/units/activities_crawler.py
deleted file mode 100755
index 4ab98df..0000000
--- a/tests/units/activities_crawler.py
+++ /dev/null
@@ -1,154 +0,0 @@
-#!/usr/bin/env python
-# sugar-lint: disable
-
-import os
-import shutil
-from os.path import exists
-
-from __init__ import tests
-
-from active_toolkit import coroutine
-from sugar_network.local import activities_crawler
-
-
-class ActivitiesCrawlerTest(tests.Test):
-
- def setUp(self):
- tests.Test.setUp(self)
-
- self.job = None
- self.found = []
- self.lost = []
-
- def tearDown(self):
- if self.job is not None:
- self.job.kill()
- tests.Test.tearDown(self)
-
- def found_cb(self, path):
- self.found.append(path)
-
- def lost_cb(self, path):
- self.lost.append(path)
-
- def test_Walkthrough(self):
- self.touch('file')
- os.makedirs('activity-1')
- os.makedirs('activity-2/activity')
- self.touch('activity-3/activity/activity.info')
- self.touch('activity-4/activity/activity.info')
- self.touch('activity-5/activity/activity.info')
-
- self.job = coroutine.spawn(activities_crawler.dispatch, ['.'],
- self.found_cb, self.lost_cb)
- coroutine.sleep(1)
-
- self.assertEqual(
- sorted([
- tests.tmpdir + '/activity-3',
- tests.tmpdir + '/activity-4',
- tests.tmpdir + '/activity-5',
- ]),
- sorted(self.found))
- self.assertEqual([], self.lost)
- del self.found[:]
-
- with file('activity-4/activity/activity.info', 'w') as f:
- f.close()
- coroutine.sleep(.1)
- self.assertEqual([], self.found)
- self.assertEqual([], self.lost)
-
- with file('activity-2/activity/activity.info', 'w') as f:
- f.close()
- coroutine.sleep(.1)
- self.assertEqual([tests.tmpdir + '/activity-2'], self.found)
- self.assertEqual([], self.lost)
- del self.found[:]
-
- os.makedirs('activity-6/activity')
- coroutine.sleep(.1)
- self.assertEqual([], self.found)
- self.assertEqual([], self.lost)
-
- with file('activity-6/activity/activity.info', 'w') as f:
- f.close()
- coroutine.sleep(.1)
- self.assertEqual([tests.tmpdir + '/activity-6'], self.found)
- self.assertEqual([], self.lost)
- del self.found[:]
-
- os.unlink('activity-5/activity/activity.info')
- coroutine.sleep(.1)
- self.assertEqual([], self.found)
- self.assertEqual([tests.tmpdir + '/activity-5'], self.lost)
- del self.lost[:]
-
- shutil.rmtree('activity-5')
- coroutine.sleep(.1)
- self.assertEqual([], self.found)
- self.assertEqual([], self.lost)
-
- shutil.rmtree('activity-4')
- coroutine.sleep(.1)
- coroutine.sleep(.1)
- self.assertEqual([], self.found)
- self.assertEqual([tests.tmpdir + '/activity-4'], self.lost)
- del self.lost[:]
-
- def test_Moves(self):
- self.touch('Activities/activity/activity/activity.info')
-
- self.job = coroutine.spawn(activities_crawler.dispatch, ['Activities'],
- self.found_cb, self.lost_cb)
- coroutine.sleep()
- del self.found[:]
- del self.lost[:]
-
- shutil.move('Activities/activity', '.')
- coroutine.sleep(.1)
- self.assertEqual([], self.found)
- self.assertEqual([tests.tmpdir + '/Activities/activity'], self.lost)
- del self.found[:]
- del self.lost[:]
- shutil.move('activity', 'Activities/')
- coroutine.sleep(.1)
- self.assertEqual([tests.tmpdir + '/Activities/activity'], self.found)
- self.assertEqual([], self.lost)
- del self.found[:]
- del self.lost[:]
-
- shutil.move('Activities/activity/activity', 'Activities/activity/activity2')
- coroutine.sleep(.1)
- self.assertEqual([], self.found)
- self.assertEqual([tests.tmpdir + '/Activities/activity'], self.lost)
- del self.found[:]
- del self.lost[:]
- shutil.move('Activities/activity/activity2', 'Activities/activity/activity')
- coroutine.sleep(.1)
- self.assertEqual([tests.tmpdir + '/Activities/activity'], self.found)
- self.assertEqual([], self.lost)
- del self.found[:]
- del self.lost[:]
-
- shutil.move('Activities/activity/activity/activity.info', 'Activities/activity/activity/activity.info2')
- coroutine.sleep(.1)
- self.assertEqual([], self.found)
- self.assertEqual([tests.tmpdir + '/Activities/activity'], self.lost)
- del self.found[:]
- del self.lost[:]
- shutil.move('Activities/activity/activity/activity.info2', 'Activities/activity/activity/activity.info')
- coroutine.sleep(.1)
- self.assertEqual([tests.tmpdir + '/Activities/activity'], self.found)
- self.assertEqual([], self.lost)
- del self.found[:]
- del self.lost[:]
-
- def test_NoPermissions(self):
- assert not exists('/foo/bar')
- activities_crawler.populate(['/foo/bar'], None, None)
- assert not exists('/foo/bar')
-
-
-if __name__ == '__main__':
- tests.main()
diff --git a/tests/units/mountset.py b/tests/units/mountset.py
index 1073e7c..7fddceb 100755
--- a/tests/units/mountset.py
+++ b/tests/units/mountset.py
@@ -10,7 +10,6 @@ from __init__ import tests
import active_document as ad
from active_toolkit import coroutine, sockets
-from sugar_network.local import activities_crawler
from sugar_network.local.mountset import Mountset
from sugar_network.local.bus import IPCServer
from sugar_network.resources.user import User