From 5343c4c09cd0f24cc98e93b0e53b06d685b9fbc9 Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Wed, 15 Aug 2012 12:31:16 +0000 Subject: Make activities monitor less curly; register mime types and icons on activity checkin --- 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 . 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 . - -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 . + +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 . - -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', [ + '', + '', + '', + 'foo-bar', + '', + '', + '', + ])) 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 -- cgit v0.9.1