From 34e9d30a9cd87e5b3fa665a0a4a9a954a5edb88e Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sat, 8 Sep 2007 12:10:35 +0200 Subject: [PATCH 1/3] Improved icon caching logic --- shell/view/home/FriendsBox.py | 2 +- shell/view/home/HomeBox.py | 2 +- shell/view/home/MeshBox.py | 6 +- shell/view/home/activitiesdonut.py | 6 +- sugar/graphics/icon.py | 124 +++++++---------------------- sugar/util.py | 78 ++++++++++++++++++ 6 files changed, 113 insertions(+), 105 deletions(-) diff --git a/shell/view/home/FriendsBox.py b/shell/view/home/FriendsBox.py index 2d832076..9a9ca9b3 100644 --- a/shell/view/home/FriendsBox.py +++ b/shell/view/home/FriendsBox.py @@ -38,7 +38,7 @@ class FriendsBox(hippo.CanvasBox): self._layout = SpreadLayout() self.set_layout(self._layout) - self._owner_icon = CanvasIcon(icon_name='computer-xo', + self._owner_icon = CanvasIcon(icon_name='computer-xo', cache=True, xo_color=profile.get_color()) self._owner_icon.props.size = style.LARGE_ICON_SIZE palette = Palette(profile.get_nick_name()) diff --git a/shell/view/home/HomeBox.py b/shell/view/home/HomeBox.py index 9eb57162..8a655797 100644 --- a/shell/view/home/HomeBox.py +++ b/shell/view/home/HomeBox.py @@ -41,7 +41,7 @@ class HomeBox(hippo.CanvasBox, hippo.CanvasItem): def __init__(self, shell): hippo.CanvasBox.__init__(self, background_color=0xe2e2e2ff, yalign=2) - self._donut = ActivitiesDonut(shell,box_width=style.zoom(450), + self._donut = ActivitiesDonut(shell, box_width=style.zoom(450), box_height=style.zoom(450)) self.append(self._donut) diff --git a/shell/view/home/MeshBox.py b/shell/view/home/MeshBox.py index 4824cce8..2209bdeb 100644 --- a/shell/view/home/MeshBox.py +++ b/shell/view/home/MeshBox.py @@ -43,7 +43,7 @@ _ICON_NAME = 'network-wireless' class AccessPointView(PulsingIcon): def __init__(self, model): - PulsingIcon.__init__(self, size=style.MEDIUM_ICON_SIZE) + PulsingIcon.__init__(self, size=style.MEDIUM_ICON_SIZE, cache=True) self._model = model self.connect('activated', self._activate_cb) @@ -124,7 +124,7 @@ _MESH_ICON_NAME = 'network-mesh' class MeshDeviceView(PulsingIcon): def __init__(self, nm_device): PulsingIcon.__init__(self, size=style.MEDIUM_ICON_SIZE, - icon_name=_MESH_ICON_NAME) + icon_name=_MESH_ICON_NAME, cache=True) self._nm_device = nm_device self.set_tooltip(_("Mesh Network")) @@ -180,7 +180,7 @@ class ActivityView(hippo.CanvasBox): self._layout = SnowflakeLayout() self.set_layout(self._layout) - self._icon = CanvasIcon(file_name=model.get_icon_name(), + self._icon = CanvasIcon(file_name=model.get_icon_name(), cache=True, xo_color=model.get_color(), box_width=80) self._icon.connect('activated', self._clicked_cb) self._icon.set_tooltip(self._model.activity.props.name) diff --git a/shell/view/home/activitiesdonut.py b/shell/view/home/activitiesdonut.py index de3f8bd7..aa0adde5 100644 --- a/shell/view/home/activitiesdonut.py +++ b/shell/view/home/activitiesdonut.py @@ -73,8 +73,8 @@ class ActivityIcon(CanvasIcon): self._level = self._level_max color = self._icon_colors[self._level] - CanvasIcon.__init__(self, file_name=icon_name, xo_color=color, - size=style.MEDIUM_ICON_SIZE) + CanvasIcon.__init__(self, file_name=icon_name, xo_color=color, + size=style.MEDIUM_ICON_SIZE, cache=True) self._activity = activity self._pulse_id = 0 @@ -159,7 +159,6 @@ class ActivityIcon(CanvasIcon): if self._pulse_id: return - self.props.cache_size = self._level_max self._pulse_id = gobject.timeout_add(self._INTERVAL, self._pulse_cb) def _stop_pulsing(self): @@ -168,7 +167,6 @@ class ActivityIcon(CanvasIcon): self._cleanup() self._level = 100.0 - self.props.cache_size = 1 self.props.xo_color = self._orig_color def _resume_activate_cb(self, menuitem): diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py index 31af35b2..4cb3044f 100644 --- a/sugar/graphics/icon.py +++ b/sugar/graphics/icon.py @@ -31,87 +31,30 @@ from sugar.graphics.style import Color from sugar.graphics.xocolor import XoColor from sugar.graphics import style from sugar.graphics.palette import Palette, CanvasInvoker +from sugar.util import LRU _BADGE_SIZE = 0.45 -_svg_loader = None - -def _get_svg_loader(): - global _svg_loader - if _svg_loader == None: - _svg_loader = _SVGLoader() - return _svg_loader - -class _SVGIcon(object): - def __init__(self, data): - self.data = data - self.data_size = len(data) - self.last_used = time.time() - class _SVGLoader(object): - CACHE_MAX_SIZE = 50000 - CACHE_MAX_ICON_SIZE = 5000 - def __init__(self): - self._cache = {} - self._cache_size = 0 + self._cache = LRU(50) - def load(self, file_name, entities): - icon = self._get_icon(file_name) + def load(self, file_name, entities, cache): + if file_name in self._cache: + icon = self._cache[file_name] + else: + icon_file = open(file_name, 'r') + icon = icon_file.read() + icon_file.close() + + if cache: + self._cache[file_name] = icon for entity, value in entities.items(): xml = '' % (entity, value) - icon.data = re.sub('' % entity, xml, icon.data) + icon = re.sub('' % entity, xml, icon) - return rsvg.Handle(data=icon.data) - - def _get_icon(self, file_name): - if self._cache.has_key(file_name): - data = self._cache[file_name] - data.last_used = time.time() - return data - - icon_file = open(file_name, 'r') - icon = _SVGIcon(icon_file.read()) - icon_file.close() - - self._cache[file_name] = icon - self._cleanup_cache() - - return icon - - def _cleanup_cache(self): - now = time.time() - while self._cache_size > self.CACHE_MAX_SIZE: - evict_key = None - oldest_key = None - oldest_time = now - - for icon_key, icon in self._cache.items(): - # evict large icons first - if icon.data_size > self.CACHE_MAX_ICON_SIZE: - evict_key = icon_key - break - - # evict older icons next; those used over 2 minutes ago - if icon.last_used < now - 120: - evict_key = icon_key - break - - # otherwise, evict the oldest - if oldest_time > icon.last_used: - oldest_time = icon.last_used - oldest_key = icon_key - - # If there's nothing specific to evict, try evicting - # the oldest thing - if not evict_key: - if not oldest_key: - break - evict_key = oldest_key - - self._cache_size -= self._cache[evict_key].data_size - del self._cache[evict_key] + return rsvg.Handle(data=icon) class _IconInfo(object): def __init__(self): @@ -127,11 +70,10 @@ class _BadgeInfo(object): self.icon_padding = 0 class _IconBuffer(object): - def __init__(self): - self._svg_loader = _get_svg_loader() - self._surface_cache = {} - self._cache_size = 1 + _surface_cache = LRU(50) + _loader = _SVGLoader() + def __init__(self): self.icon_name = None self.file_name = None self.fill_color = None @@ -139,6 +81,7 @@ class _IconBuffer(object): self.badge_name = None self.width = None self.height = None + self.cache = False def _get_cache_key(self): return (self.icon_name, self.file_name, self.fill_color, @@ -151,7 +94,7 @@ class _IconBuffer(object): if self.stroke_color: entities['stroke_color'] = self.stroke_color - return self._svg_loader.load(file_name, entities) + return self._loader.load(file_name, entities, self.cache) def _get_attach_points(self, info, size_request): attach_points = info.get_attach_points() @@ -184,7 +127,8 @@ class _IconBuffer(object): icon_info.attach_x = attach_x icon_info.attach_y = attach_y else: - logging.warning('No icon with the name %s was found in the theme.' % self.icon_name) + logging.warning('No icon with the name %s ' + 'was found in the theme.' % self.icon_name) return icon_info @@ -194,7 +138,7 @@ class _IconBuffer(object): if badge_info: badge_file_name = badge_info.get_filename() if badge_file_name.endswith('.svg'): - handle = self._svg_loader.load(badge_file_name, {}) + handle = self._loader.load(badge_file_name, {}, self.cache) handle.render_cairo(context) else: pixbuf = gtk.gdk.pixbuf_new_from_file(badge_file_name) @@ -233,7 +177,7 @@ class _IconBuffer(object): def get_surface(self): cache_key = self._get_cache_key() - if self._surface_cache.has_key(cache_key): + if cache_key in self._surface_cache: return self._surface_cache[cache_key] icon_info = self._get_icon_info() @@ -276,22 +220,10 @@ class _IconBuffer(object): context.translate(badge_info.attach_x, badge_info.attach_y) self._draw_badge(context, badge_info.size) - if (len(self._surface_cache) == self._cache_size): - self._surface_cache.popitem() self._surface_cache[cache_key] = surface return surface - def get_cache_size(self): - return self._cache_size - - def set_cache_size(self, cache_size): - while len(self._surface_cache) > cache_size: - self._surface_cache.popitem() - self._cache_size = cache_size - - cache_size = property(get_cache_size, set_cache_size) - class Icon(gtk.Image): __gtype_name__ = 'SugarIcon' @@ -416,7 +348,7 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): gobject.PARAM_READWRITE), 'scale' : (float, None, None, -1024.0, 1024.0, 1.0, gobject.PARAM_READWRITE), - 'cache-size' : (int, None, None, 0, 1024, 0, + 'cache' : (bool, None, None, False, gobject.PARAM_READWRITE), 'badge-name' : (str, None, None, None, gobject.PARAM_READWRITE) @@ -460,8 +392,8 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): elif pspec.name == 'scale': self._buffer.scale = value self.emit_request_changed() - elif pspec.name == 'cache-size': - self._buffer.cache_size = value + elif pspec.name == 'cache': + self._buffer.cache = value elif pspec.name == 'badge-name': self._buffer.badge_name = value self.emit_paint_needed(0, 0, -1, -1) @@ -477,8 +409,8 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): return self._buffer.fill_color elif pspec.name == 'stroke-color': return self._buffer.stroke_color - elif pspec.name == 'cache-size': - return self._buffer.cache_size + elif pspec.name == 'cache': + return self._buffer.cache elif pspec.name == 'badge-name': return self._buffer.badge_name elif pspec.name == 'scale': diff --git a/sugar/util.py b/sugar/util.py index 814a7283..5f018483 100644 --- a/sugar/util.py +++ b/sugar/util.py @@ -128,3 +128,81 @@ def set_proc_title(title): except: return False +class Node(object): + __slots__ = ['prev', 'next', 'me'] + def __init__(self, prev, me): + self.prev = prev + self.me = me + self.next = None + +class LRU: + """ + Implementation of a length-limited O(1) LRU queue. + Built for and used by PyPE: + http://pype.sourceforge.net + Copyright 2003 Josiah Carlson. + """ + def __init__(self, count, pairs=[]): + self.count = max(count, 1) + self.d = {} + self.first = None + self.last = None + for key, value in pairs: + self[key] = value + def __contains__(self, obj): + return obj in self.d + def __getitem__(self, obj): + a = self.d[obj].me + self[a[0]] = a[1] + return a[1] + def __setitem__(self, obj, val): + if obj in self.d: + del self[obj] + nobj = Node(self.last, (obj, val)) + if self.first is None: + self.first = nobj + if self.last: + self.last.next = nobj + self.last = nobj + self.d[obj] = nobj + if len(self.d) > self.count: + if self.first == self.last: + self.first = None + self.last = None + return + a = self.first + a.next.prev = None + self.first = a.next + a.next = None + del self.d[a.me[0]] + del a + def __delitem__(self, obj): + nobj = self.d[obj] + if nobj.prev: + nobj.prev.next = nobj.next + else: + self.first = nobj.next + if nobj.next: + nobj.next.prev = nobj.prev + else: + self.last = nobj.prev + del self.d[obj] + def __iter__(self): + cur = self.first + while cur != None: + cur2 = cur.next + yield cur.me[1] + cur = cur2 + def iteritems(self): + cur = self.first + while cur != None: + cur2 = cur.next + yield cur.me + cur = cur2 + def iterkeys(self): + return iter(self.d) + def itervalues(self): + for i,j in self.iteritems(): + yield j + def keys(self): + return self.d.keys() From f73132de9130aeba285ae91089a2c0baa5f5a853 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Sat, 8 Sep 2007 18:41:59 +0200 Subject: [PATCH 2/3] Don't try to resume a DSObject if no activity can open it. --- sugar/datastore/datastore.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sugar/datastore/datastore.py b/sugar/datastore/datastore.py index 0be6a2fa..d4340feb 100644 --- a/sugar/datastore/datastore.py +++ b/sugar/datastore/datastore.py @@ -138,6 +138,8 @@ class DSObject(object): activityfactory.create(bundle.get_service_name()) else: + if not self.get_activities(): + return if service_name is None: service_name = self.get_activities()[0].service_name From 8ced2dccbeea399a299f58527cba7de70db50afa Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Sat, 8 Sep 2007 19:40:22 +0200 Subject: [PATCH 3/3] Generate context comments in .po files. --- sugar/activity/bundlebuilder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sugar/activity/bundlebuilder.py b/sugar/activity/bundlebuilder.py index b0e46ab5..d2b1a89b 100644 --- a/sugar/activity/bundlebuilder.py +++ b/sugar/activity/bundlebuilder.py @@ -230,7 +230,7 @@ def cmd_genpot(bundle_name, manifest): f.close() args = [ 'xgettext', '--join-existing', '--language=Python', - '--keyword=_', '--output=%s' % pot_file ] + '--keyword=_', '--add-comments=TRANS:', '--output=%s' % pot_file ] args += python_files retcode = subprocess.call(args)