Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTomeu Vizoso <tomeu@tomeuvizoso.net>2008-11-28 18:42:57 (GMT)
committer Tomeu Vizoso <tomeu@tomeuvizoso.net>2008-11-28 18:42:57 (GMT)
commitb006cfdd12d5f22ccea35ab4f716350058cdf107 (patch)
tree5f633159b0d6dfdd3390da42a54576a4c7cb2ba2 /src
parent5ee998c245a05656b527eacb57cebe124a73dddf (diff)
First try at restoring removable devices support in the journal
Diffstat (limited to 'src')
-rw-r--r--src/jarabe/journal/Makefile.am2
-rw-r--r--src/jarabe/journal/collapsedentry.py107
-rw-r--r--src/jarabe/journal/detailview.py41
-rw-r--r--src/jarabe/journal/expandedentry.py86
-rw-r--r--src/jarabe/journal/journalactivity.py95
-rw-r--r--src/jarabe/journal/journalentrybundle.py27
-rw-r--r--src/jarabe/journal/journaltoolbox.py58
-rw-r--r--src/jarabe/journal/listview.py37
-rw-r--r--src/jarabe/journal/misc.py93
-rw-r--r--src/jarabe/journal/model.py325
-rw-r--r--src/jarabe/journal/objectchooser.py5
-rw-r--r--src/jarabe/journal/palettes.py32
-rw-r--r--src/jarabe/journal/query.py266
-rw-r--r--src/jarabe/journal/volumestoolbar.py29
-rw-r--r--src/jarabe/model/volume.py26
15 files changed, 640 insertions, 589 deletions
diff --git a/src/jarabe/journal/Makefile.am b/src/jarabe/journal/Makefile.am
index a8ef90f..5f66480 100644
--- a/src/jarabe/journal/Makefile.am
+++ b/src/jarabe/journal/Makefile.am
@@ -11,7 +11,7 @@ sugar_PYTHON = \
listview.py \
misc.py \
modalalert.py \
+ model.py \
objectchooser.py \
palettes.py \
- query.py \
volumestoolbar.py
diff --git a/src/jarabe/journal/collapsedentry.py b/src/jarabe/journal/collapsedentry.py
index c2cc9c8..bf29199 100644
--- a/src/jarabe/journal/collapsedentry.py
+++ b/src/jarabe/journal/collapsedentry.py
@@ -25,12 +25,12 @@ import cjson
from sugar.graphics.icon import CanvasIcon
from sugar.graphics.xocolor import XoColor
from sugar.graphics import style
-from sugar.datastore import datastore
from sugar.graphics.entry import CanvasEntry
from jarabe.journal.keepicon import KeepIcon
from jarabe.journal.palettes import ObjectPalette, BuddyPalette
from jarabe.journal import misc
+from jarabe.journal import model
class BuddyIcon(CanvasIcon):
def __init__(self, buddy, **kwargs):
@@ -41,18 +41,18 @@ class BuddyIcon(CanvasIcon):
return BuddyPalette(self._buddy)
class BuddyList(hippo.CanvasBox):
- def __init__(self, model, width):
+ def __init__(self, buddies, width):
hippo.CanvasBox.__init__(self,
orientation=hippo.ORIENTATION_HORIZONTAL,
box_width=width,
xalign=hippo.ALIGNMENT_START)
- self.set_model(model)
+ self.set_buddies(buddies)
- def set_model(self, model):
+ def set_buddies(self, buddies):
for item in self.get_children():
self.remove(item)
- for buddy in model[0:3]:
+ for buddy in buddies[0:3]:
nick_, color = buddy
icon = BuddyIcon(buddy,
icon_name='computer-xo',
@@ -63,16 +63,16 @@ class BuddyList(hippo.CanvasBox):
class EntryIcon(CanvasIcon):
def __init__(self, **kwargs):
CanvasIcon.__init__(self, **kwargs)
- self._jobject = None
+ self._metadata = None
- def set_jobject(self, jobject):
- self._jobject = jobject
- self.props.file_name = misc.get_icon_name(jobject)
+ def set_metadata(self, metadata):
+ self._metadata = metadata
+ self.props.file_name = misc.get_icon_name(metadata)
self.palette = None
def create_palette(self):
if self.show_palette:
- return ObjectPalette(self._jobject)
+ return ObjectPalette(self._metadata)
else:
return None
@@ -95,7 +95,7 @@ class BaseCollapsedEntry(hippo.CanvasBox):
box_height=style.GRID_CELL_SIZE,
orientation=hippo.ORIENTATION_HORIZONTAL)
- self._jobject = None
+ self._metadata = None
self._is_selected = False
self.keep_icon = self._create_keep_icon()
@@ -163,9 +163,9 @@ class BaseCollapsedEntry(hippo.CanvasBox):
return button
def _decode_buddies(self):
- if self.jobject.metadata.has_key('buddies') and \
- self.jobject.metadata['buddies']:
- buddies = cjson.decode(self._jobject.metadata['buddies']).values()
+ if self.metadata.has_key('buddies') and \
+ self.metadata['buddies']:
+ buddies = cjson.decode(self.metadata['buddies']).values()
else:
buddies = []
return buddies
@@ -187,24 +187,21 @@ class BaseCollapsedEntry(hippo.CanvasBox):
self.props.background_color = style.COLOR_WHITE.get_int()
def is_in_progress(self):
- return self._jobject.metadata.has_key('progress') and \
- int(self._jobject.metadata['progress']) < 100
+ return self.metadata.has_key('progress') and \
+ int(self.metadata['progress']) < 100
def get_keep(self):
- keep = int(self._jobject.metadata.get('keep', 0))
+ keep = int(self.metadata.get('keep', 0))
return keep == 1
def __keep_icon_button_release_event_cb(self, button, event):
logging.debug('__keep_icon_button_release_event_cb')
- jobject = datastore.get(self._jobject.object_id)
- try:
- if self.get_keep():
- jobject.metadata['keep'] = 0
- else:
- jobject.metadata['keep'] = 1
- datastore.write(jobject, update_mtime=False)
- finally:
- jobject.destroy()
+ metadata = model.get(self._metadata['uid'])
+ if self.get_keep():
+ metadata['keep'] = 0
+ else:
+ metadata['keep'] = 1
+ model.write(metadata, update_mtime=False)
self.keep_icon.props.keep = self.get_keep()
self._update_color()
@@ -213,55 +210,55 @@ class BaseCollapsedEntry(hippo.CanvasBox):
def _cancel_button_release_event_cb(self, button, event):
logging.debug('_cancel_button_release_event_cb')
- datastore.delete(self._jobject.object_id)
+ model.delete(self._metadata['uid'])
return True
def set_selected(self, is_selected):
self._is_selected = is_selected
self._update_color()
- def set_jobject(self, jobject):
- self._jobject = jobject
+ def set_metadata(self, metadata):
+ self._metadata = metadata
self._is_selected = False
self.keep_icon.props.keep = self.get_keep()
- self.date.props.text = misc.get_date(jobject)
+ self.date.props.text = misc.get_date(metadata)
- self.icon.set_jobject(jobject)
- if jobject.is_activity_bundle():
+ self.icon.set_metadata(metadata)
+ if misc.is_activity_bundle(metadata):
self.icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
self.icon.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
else:
- if jobject.metadata.has_key('icon-color') and \
- jobject.metadata['icon-color']:
+ if metadata.has_key('icon-color') and \
+ metadata['icon-color']:
self.icon.props.xo_color = XoColor( \
- jobject.metadata['icon-color'])
+ metadata['icon-color'])
else:
self.icon.props.xo_color = None
- if jobject.metadata.get('title', ''):
- title_text = jobject.metadata['title']
+ if metadata.get('title', ''):
+ title_text = metadata['title']
else:
title_text = _('Untitled')
self.title.props.text = title_text
- self.buddies_list.set_model(self._decode_buddies())
+ self.buddies_list.set_buddies(self._decode_buddies())
- if jobject.metadata.has_key('progress'):
+ if metadata.has_key('progress'):
self.progress_bar.props.widget.props.fraction = \
- int(jobject.metadata['progress']) / 100.0
+ int(metadata['progress']) / 100.0
self.update_visibility()
self._update_color()
- def get_jobject(self):
- return self._jobject
+ def get_metadata(self):
+ return self._metadata
- jobject = property(get_jobject, set_jobject)
+ metadata = property(get_metadata, set_metadata)
def update_date(self):
- self.date.props.text = misc.get_date(self._jobject)
+ self.date.props.text = misc.get_date(self._metadata)
class CollapsedEntry(BaseCollapsedEntry):
__gtype_name__ = 'CollapsedEntry'
@@ -318,11 +315,11 @@ class CollapsedEntry(BaseCollapsedEntry):
BaseCollapsedEntry.update_visibility(self)
self._detail_button.set_visible(not self.is_in_progress())
- def set_jobject(self, jobject):
- BaseCollapsedEntry.set_jobject(self, jobject)
+ def set_metadata(self, metadata):
+ BaseCollapsedEntry.set_metadata(self, metadata)
self._title_entry.props.text = self.title.props.text
- jobject = property(BaseCollapsedEntry.get_jobject, set_jobject)
+ metadata = property(BaseCollapsedEntry.get_metadata, set_metadata)
def __detail_button_release_event_cb(self, button, event):
logging.debug('_detail_button_release_event_cb')
@@ -338,7 +335,7 @@ class CollapsedEntry(BaseCollapsedEntry):
def __icon_button_release_event_cb(self, button, event):
logging.debug('__icon_button_release_event_cb')
- misc.resume(self.jobject)
+ misc.resume(self.metadata)
return True
def __title_button_release_event_cb(self, button, event):
@@ -364,20 +361,12 @@ class CollapsedEntry(BaseCollapsedEntry):
self._cancel_title_change()
elif self.title.props.text != title:
self.title.props.text = title
- self._jobject.metadata['title'] = title
- self._jobject.metadata['title_set_by_user'] = '1'
- datastore.write(self._jobject, update_mtime=False,
- reply_handler=self._datastore_write_cb,
- error_handler=self._datastore_write_error_cb)
+ self._metadata['title'] = title
+ self._metadata['title_set_by_user'] = '1'
+ model.write(self._metadata, update_mtime=False)
def _cancel_title_change(self):
self._title_entry.props.text = self.title.props.text
self._title_entry.set_visible(False)
self.title.set_visible(True)
- def _datastore_write_cb(self):
- pass
-
- def _datastore_write_error_cb(self, error):
- logging.error('CollapsedEntry._datastore_write_error_cb: %r' % error)
-
diff --git a/src/jarabe/journal/detailview.py b/src/jarabe/journal/detailview.py
index 5748d6f..363e152 100644
--- a/src/jarabe/journal/detailview.py
+++ b/src/jarabe/journal/detailview.py
@@ -23,24 +23,19 @@ import hippo
from sugar.graphics import style
from sugar.graphics.icon import CanvasIcon
-from sugar.datastore import datastore
from jarabe.journal.expandedentry import ExpandedEntry
+from jarabe.journal import model
class DetailView(gtk.VBox):
__gtype_name__ = 'DetailView'
- __gproperties__ = {
- 'jobject' : (object, None, None,
- gobject.PARAM_READWRITE)
- }
-
__gsignals__ = {
'go-back-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
}
def __init__(self, **kwargs):
- self._jobject = None
+ self._metadata = None
self._expanded_entry = None
canvas = hippo.Canvas()
@@ -76,29 +71,23 @@ class DetailView(gtk.VBox):
self._expanded_entry.remove_all()
import gc
gc.collect()
- if self._jobject:
- self._expanded_entry = ExpandedEntry(self._jobject.object_id)
- self._root.append(self._expanded_entry, hippo.PACK_EXPAND)
+ self._expanded_entry = ExpandedEntry(self._metadata)
+ self._root.append(self._expanded_entry, hippo.PACK_EXPAND)
def refresh(self):
logging.debug('DetailView.refresh')
- if self._jobject:
- self._jobject = datastore.get(self._jobject.object_id)
- self._update_view()
-
- def do_set_property(self, pspec, value):
- if pspec.name == 'jobject':
- self._jobject = value
- self._update_view()
- else:
- raise AssertionError
-
- def do_get_property(self, pspec):
- if pspec.name == 'jobject':
- return self._jobject
- else:
- raise AssertionError
+ self._metadata = model.get(self._metadata['uid'])
+ self._update_view()
+
+ def get_metadata(self):
+ return self._metadata
+
+ def set_metadata(self, metadata):
+ self._metadata = metadata
+ self._update_view()
+ metadata = gobject.property(
+ type=object, getter=get_metadata, setter=set_metadata)
class BackBar(hippo.CanvasBox):
def __init__(self):
diff --git a/src/jarabe/journal/expandedentry.py b/src/jarabe/journal/expandedentry.py
index 9f99d3a..1e0b890 100644
--- a/src/jarabe/journal/expandedentry.py
+++ b/src/jarabe/journal/expandedentry.py
@@ -28,11 +28,11 @@ from sugar.graphics import style
from sugar.graphics.icon import CanvasIcon
from sugar.graphics.xocolor import XoColor
from sugar.graphics.entry import CanvasEntry
-from sugar.datastore import datastore
from jarabe.journal.keepicon import KeepIcon
from jarabe.journal.palettes import ObjectPalette, BuddyPalette
from jarabe.journal import misc
+from jarabe.journal import model
class Separator(hippo.CanvasBox, hippo.CanvasItem):
def __init__(self, orientation):
@@ -63,11 +63,11 @@ class CanvasTextView(hippo.CanvasWidget):
self.props.widget = scrolled_window
class BuddyList(hippo.CanvasBox):
- def __init__(self, model):
+ def __init__(self, buddies):
hippo.CanvasBox.__init__(self, xalign=hippo.ALIGNMENT_START,
orientation=hippo.ORIENTATION_HORIZONTAL)
- for buddy in model:
+ for buddy in buddies:
nick_, color = buddy
hbox = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL)
icon = CanvasIcon(icon_name='computer-xo',
@@ -78,13 +78,13 @@ class BuddyList(hippo.CanvasBox):
self.append(hbox)
class ExpandedEntry(hippo.CanvasBox):
- def __init__(self, object_id):
+ def __init__(self, metadata):
hippo.CanvasBox.__init__(self)
self.props.orientation = hippo.ORIENTATION_VERTICAL
self.props.background_color = style.COLOR_WHITE.get_int()
self.props.padding_top = style.DEFAULT_SPACING * 3
- self._jobject = datastore.get(object_id)
+ self._metadata = metadata
self._update_title_sid = None
# Create header
@@ -147,33 +147,33 @@ class ExpandedEntry(hippo.CanvasBox):
second_column.append(self._buddy_list)
def _create_keep_icon(self):
- keep = int(self._jobject.metadata.get('keep', 0)) == 1
+ keep = int(self._metadata.get('keep', 0)) == 1
keep_icon = KeepIcon(keep)
keep_icon.connect('activated', self._keep_icon_activated_cb)
return keep_icon
def _create_icon(self):
- icon = CanvasIcon(file_name=misc.get_icon_name(self._jobject))
+ icon = CanvasIcon(file_name=misc.get_icon_name(self._metadata))
icon.connect_after('button-release-event',
self._icon_button_release_event_cb)
- if self._jobject.is_activity_bundle():
+ if misc.is_activity_bundle(self._metadata):
icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
icon.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
else:
- if self._jobject.metadata.has_key('icon-color') and \
- self._jobject.metadata['icon-color']:
+ if self._metadata.has_key('icon-color') and \
+ self._metadata['icon-color']:
icon.props.xo_color = XoColor( \
- self._jobject.metadata['icon-color'])
+ self._metadata['icon-color'])
- icon.set_palette(ObjectPalette(self._jobject))
+ icon.set_palette(ObjectPalette(self._metadata))
return icon
def _create_title(self):
title = CanvasEntry()
title.set_background(style.COLOR_WHITE.get_html())
- title.props.text = self._jobject.metadata.get('title', _('Untitled'))
+ title.props.text = self._metadata.get('title', _('Untitled'))
title.props.widget.connect('focus-out-event',
self._title_focus_out_event_cb)
return title
@@ -181,7 +181,7 @@ class ExpandedEntry(hippo.CanvasBox):
def _create_date(self):
date = hippo.CanvasText(xalign=hippo.ALIGNMENT_START,
font_desc=style.FONT_NORMAL.get_pango_desc(),
- text = misc.get_date(self._jobject))
+ text = misc.get_date(self._metadata))
return date
def _create_preview(self):
@@ -189,16 +189,16 @@ class ExpandedEntry(hippo.CanvasBox):
height = style.zoom(240)
box = hippo.CanvasBox()
- if self._jobject.metadata.has_key('preview') and \
- len(self._jobject.metadata['preview']) > 4:
+ if self._metadata.has_key('preview') and \
+ len(self._metadata['preview']) > 4:
- if self._jobject.metadata['preview'][1:4] == 'PNG':
- preview_data = self._jobject.metadata['preview']
+ if self._metadata['preview'][1:4] == 'PNG':
+ preview_data = self._metadata['preview']
else:
# TODO: We are close to be able to drop this.
import base64
preview_data = base64.b64decode(
- self._jobject.metadata['preview'])
+ self._metadata['preview'])
png_file = StringIO.StringIO(preview_data)
try:
@@ -249,9 +249,9 @@ class ExpandedEntry(hippo.CanvasBox):
vbox.append(text)
- if self._jobject.metadata.has_key('buddies') and \
- self._jobject.metadata['buddies']:
- buddies = cjson.decode(self._jobject.metadata['buddies']).values()
+ if self._metadata.has_key('buddies') and \
+ self._metadata['buddies']:
+ buddies = cjson.decode(self._metadata['buddies']).values()
vbox.append(BuddyList(buddies))
return vbox
else:
@@ -272,7 +272,7 @@ class ExpandedEntry(hippo.CanvasBox):
vbox.append(text)
- description = self._jobject.metadata.get('description', '')
+ description = self._metadata.get('description', '')
text_view = CanvasTextView(description,
box_height=style.GRID_CELL_SIZE * 2)
vbox.append(text_view, hippo.PACK_EXPAND)
@@ -298,7 +298,7 @@ class ExpandedEntry(hippo.CanvasBox):
vbox.append(text)
- tags = self._jobject.metadata.get('tags', '')
+ tags = self._metadata.get('tags', '')
text_view = CanvasTextView(tags, box_height=style.GRID_CELL_SIZE * 2)
vbox.append(text_view, hippo.PACK_EXPAND)
@@ -313,12 +313,6 @@ class ExpandedEntry(hippo.CanvasBox):
self._update_title_sid = gobject.timeout_add(1000,
self._update_title_cb)
- def _datastore_write_cb(self):
- pass
-
- def _datastore_write_error_cb(self, error):
- logging.error('ExpandedEntry._datastore_write_error_cb: %r' % error)
-
def _title_focus_out_event_cb(self, entry, event):
self._update_entry()
@@ -331,53 +325,51 @@ class ExpandedEntry(hippo.CanvasBox):
def _update_entry(self):
needs_update = False
- old_title = self._jobject.metadata.get('title', None)
+ old_title = self._metadata.get('title', None)
if old_title != self._title.props.text:
self._icon.palette.props.primary_text = self._title.props.text
- self._jobject.metadata['title'] = self._title.props.text
- self._jobject.metadata['title_set_by_user'] = '1'
+ self._metadata['title'] = self._title.props.text
+ self._metadata['title_set_by_user'] = '1'
needs_update = True
- old_tags = self._jobject.metadata.get('tags', None)
+ old_tags = self._metadata.get('tags', None)
new_tags = self._tags.text_view_widget.props.buffer.props.text
if old_tags != new_tags:
- self._jobject.metadata['tags'] = new_tags
+ self._metadata['tags'] = new_tags
needs_update = True
- old_description = self._jobject.metadata.get('description', None)
+ old_description = self._metadata.get('description', None)
new_description = \
self._description.text_view_widget.props.buffer.props.text
if old_description != new_description:
- self._jobject.metadata['description'] = new_description
+ self._metadata['description'] = new_description
needs_update = True
if needs_update:
- datastore.write(self._jobject, update_mtime=False,
- reply_handler=self._datastore_write_cb,
- error_handler=self._datastore_write_error_cb)
+ model.write(self._metadata, update_mtime=False)
self._update_title_sid = None
def get_keep(self):
- return self._jobject.metadata.has_key('keep') and \
- self._jobject.metadata['keep'] == 1
+ return self._metadata.has_key('keep') and \
+ self._metadata['keep'] == 1
def _keep_icon_activated_cb(self, keep_icon):
if self.get_keep():
- self._jobject.metadata['keep'] = 0
+ self._metadata['keep'] = 0
else:
- self._jobject.metadata['keep'] = 1
- datastore.write(self._jobject, update_mtime=False)
+ self._metadata['keep'] = 1
+ model.write(self._metadata, update_mtime=False)
keep_icon.props.keep = self.get_keep()
def _icon_button_release_event_cb(self, button, event):
logging.debug('_icon_button_release_event_cb')
- misc.resume(self._jobject)
+ misc.resume(self._metadata)
return True
def _preview_box_button_release_event_cb(self, button, event):
logging.debug('_preview_box_button_release_event_cb')
- misc.resume(self._jobject)
+ misc.resume(self._metadata)
return True
diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py
index 0513382..5ab99ef 100644
--- a/src/jarabe/journal/journalactivity.py
+++ b/src/jarabe/journal/journalactivity.py
@@ -28,7 +28,6 @@ import os
from sugar.graphics.window import Window
from sugar.bundle.bundle import ZipExtractException, RegistrationException
-from sugar.datastore import datastore
from sugar import env
from sugar.activity import activityfactory
from sugar import wm
@@ -42,10 +41,7 @@ from jarabe.journal import misc
from jarabe.journal.journalentrybundle import JournalEntryBundle
from jarabe.journal.objectchooser import ObjectChooser
from jarabe.journal.modalalert import ModalAlert
-
-DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
-DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
-DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
+from jarabe.journal import model
J_DBUS_SERVICE = 'org.laptop.Journal'
J_DBUS_INTERFACE = 'org.laptop.Journal'
@@ -126,6 +122,7 @@ class JournalActivity(Window):
self._detail_view = None
self._main_toolbox = None
self._detail_toolbox = None
+ self._volumes_toolbar = None
self._setup_main_view()
self._setup_secondary_view()
@@ -139,12 +136,9 @@ class JournalActivity(Window):
self.connect('key-press-event', self._key_press_event_cb)
self.connect('focus-in-event', self._focus_in_event_cb)
- bus = dbus.SessionBus()
- data_store = dbus.Interface(
- bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH), DS_DBUS_INTERFACE)
- data_store.connect_to_signal('Created', self.__data_store_created_cb)
- data_store.connect_to_signal('Updated', self.__data_store_updated_cb)
- data_store.connect_to_signal('Deleted', self.__data_store_deleted_cb)
+ model.created.connect(self.__model_created_cb)
+ model.updated.connect(self.__model_updated_cb)
+ model.deleted.connect(self.__model_deleted_cb)
self._dbus_service = JournalActivityDBusService(self)
@@ -172,13 +166,14 @@ class JournalActivity(Window):
self._main_view.pack_start(self._list_view)
self._list_view.show()
- volumes_toolbar = VolumesToolbar()
- volumes_toolbar.connect('volume-changed', self._volume_changed_cb)
- self._main_view.pack_start(volumes_toolbar, expand=False)
+ self._volumes_toolbar = VolumesToolbar()
+ self._volumes_toolbar.connect('volume-changed',
+ self.__volume_changed_cb)
+ self._main_view.pack_start(self._volumes_toolbar, expand=False)
search_toolbar = self._main_toolbox.search_toolbar
search_toolbar.connect('query-changed', self._query_changed_cb)
- search_toolbar.set_volume_id(datastore.mounts()[0]['id'])
+ search_toolbar.set_mount_point('/')
def _setup_secondary_view(self):
self._secondary_view = gtk.VBox()
@@ -199,7 +194,7 @@ class JournalActivity(Window):
self.show_main_view()
def __detail_clicked_cb(self, list_view, entry):
- self._show_secondary_view(entry.jobject)
+ self._show_secondary_view(entry.metadata)
def __go_back_clicked_cb(self, detail_view):
self.show_main_view()
@@ -217,9 +212,9 @@ class JournalActivity(Window):
self.set_canvas(self._main_view)
self._main_view.show()
- def _show_secondary_view(self, jobject):
+ def _show_secondary_view(self, metadata):
try:
- self._detail_toolbox.entry_toolbar.set_jobject(jobject)
+ self._detail_toolbox.entry_toolbar.set_metadata(metadata)
except Exception:
logging.error('Exception while displaying entry:\n' + \
''.join(traceback.format_exception(*sys.exc_info())))
@@ -228,7 +223,7 @@ class JournalActivity(Window):
self._detail_toolbox.show()
try:
- self._detail_view.props.jobject = jobject
+ self._detail_view.props.metadata = metadata
except Exception:
logging.error('Exception while displaying entry:\n' + \
''.join(traceback.format_exception(*sys.exc_info())))
@@ -237,52 +232,40 @@ class JournalActivity(Window):
self._secondary_view.show()
def show_object(self, object_id):
- jobject = datastore.get(object_id)
- if jobject is None:
+ metadata = model.get(object_id)
+ if metadata is None:
return False
else:
- self._show_secondary_view(jobject)
+ self._show_secondary_view(metadata)
return True
- def _volume_changed_cb(self, volume_toolbar, volume_id):
- logging.debug('Selected volume: %r.' % volume_id)
- self._main_toolbox.search_toolbar.set_volume_id(volume_id)
+ def __volume_changed_cb(self, volume_toolbar, volume):
+ logging.debug('Selected volume: %r.' % volume.udi)
+ self._main_toolbox.search_toolbar.set_mount_point(volume.mount_point)
self._main_toolbox.set_current_toolbar(0)
- def __data_store_created_cb(self, uid):
- jobject = datastore.get(uid)
- if jobject is None:
- return
- try:
- self._check_for_bundle(jobject)
- finally:
- jobject.destroy()
+ def __model_created_cb(self, object_id):
+ self._check_for_bundle(object_id)
self._main_toolbox.search_toolbar.refresh_filters()
self._check_available_space()
- def __data_store_updated_cb(self, uid):
- jobject = datastore.get(uid)
- if jobject is None:
- return
- try:
- self._check_for_bundle(jobject)
- finally:
- jobject.destroy()
+ def __model_updated_cb(self, object_id):
+ self._check_for_bundle(object_id)
self._check_available_space()
- def __data_store_deleted_cb(self, uid):
+ def __model_deleted_cb(self, object_id):
if self.canvas == self._secondary_view and \
- uid == self._detail_view.props.jobject.object_id:
+ object_id == self._detail_view.props.metadata['uid']:
self.show_main_view()
def _focus_in_event_cb(self, window, event):
self.search_grab_focus()
self._list_view.update_dates()
- def _check_for_bundle(self, jobject):
+ def _check_for_bundle(self, object_id):
registry = bundleregistry.get_registry()
- bundle = misc.get_bundle(jobject)
+ bundle = misc.get_bundle(object_id)
if bundle is None:
return
@@ -292,11 +275,12 @@ class JournalActivity(Window):
registry.install(bundle)
except (ZipExtractException, RegistrationException), e:
logging.warning('Could not install bundle %s: %r' %
- (jobject.file_path, e))
+ (bundle.get_path(), e))
return
- if jobject.metadata['mime_type'] == JournalEntryBundle.MIME_TYPE:
- datastore.delete(jobject.object_id)
+ metadata = model.get(object_id)
+ if metadata['mime_type'] == JournalEntryBundle.MIME_TYPE:
+ model.delete(object_id)
def search_grab_focus(self):
search_toolbar = self._main_toolbox.search_toolbar
@@ -336,7 +320,18 @@ class JournalActivity(Window):
self.present()
self._critical_space_alert = None
+ def set_active_volume(self, mount_point):
+ self._volumes_toolbar.set_active_volume(mount_point)
+
+_journal = None
+
+def get_journal():
+ global _journal
+ if _journal is None:
+ _journal = JournalActivity()
+ _journal.show()
+ return _journal
+
def start():
- journal = JournalActivity()
- journal.show()
+ get_journal()
diff --git a/src/jarabe/journal/journalentrybundle.py b/src/jarabe/journal/journalentrybundle.py
index b3efe92..5d4086c 100644
--- a/src/jarabe/journal/journalentrybundle.py
+++ b/src/jarabe/journal/journalentrybundle.py
@@ -19,11 +19,12 @@ import tempfile
import shutil
import cjson
-
import dbus
-from sugar.datastore import datastore
+
from sugar.bundle.bundle import Bundle, MalformedBundleException
+from jarabe.journal import model
+
class JournalEntryBundle(Bundle):
"""A Journal entry bundle
@@ -50,20 +51,14 @@ class JournalEntryBundle(Bundle):
self._unzip(install_dir)
try:
metadata = self._read_metadata(bundle_dir)
- jobject = datastore.create()
- try:
- for key, value in metadata.iteritems():
- jobject.metadata[key] = value
-
- preview = self._read_preview(uid, bundle_dir)
- if preview is not None:
- jobject.metadata['preview'] = dbus.ByteArray(preview)
-
- jobject.metadata['uid'] = ''
- jobject.file_path = os.path.join(bundle_dir, uid)
- datastore.write(jobject)
- finally:
- jobject.destroy()
+ metadata['uid'] = ''
+
+ preview = self._read_preview(uid, bundle_dir)
+ if preview is not None:
+ metadata['preview'] = dbus.ByteArray(preview)
+
+ file_path = os.path.join(bundle_dir, uid)
+ model.write(metadata, file_path)
finally:
shutil.rmtree(bundle_dir, ignore_errors=True)
diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py
index 637965f..beda184 100644
--- a/src/jarabe/journal/journaltoolbox.py
+++ b/src/jarabe/journal/journaltoolbox.py
@@ -33,11 +33,11 @@ from sugar.graphics.xocolor import XoColor
from sugar.graphics import iconentry
from sugar.graphics import style
from sugar import mime
-from sugar.datastore import datastore
from jarabe.model import bundleregistry
from jarabe.model import volume
from jarabe.journal import misc
+from jarabe.journal import model
_AUTOSEARCH_TIMEOUT = 1000
@@ -79,7 +79,7 @@ class SearchToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
- self._volume_id = None
+ self._mount_point = None
self._search_entry = iconentry.IconEntry()
self._search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
@@ -159,8 +159,8 @@ class SearchToolbar(gtk.Toolbar):
def _build_query(self):
query = {}
- if self._volume_id:
- query['mountpoints'] = [self._volume_id]
+ if self._mount_point:
+ query['mountpoints'] = [self._mount_point]
if self._what_search_combo.props.value:
value = self._what_search_combo.props.value
generic_type = mime.get_generic_type(value)
@@ -238,8 +238,8 @@ class SearchToolbar(gtk.Toolbar):
self._search_entry.activate()
return False
- def set_volume_id(self, volume_id):
- self._volume_id = volume_id
+ def set_mount_point(self, mount_point):
+ self._mount_point = mount_point
new_query = self._build_query()
if self._query != new_query:
self._query = new_query
@@ -257,7 +257,7 @@ class SearchToolbar(gtk.Toolbar):
registry = bundleregistry.get_registry()
appended_separator = False
- for service_name in datastore.get_unique_values('activity'):
+ for service_name in model.get_unique_values('activity'):
activity_info = registry.get_bundle(service_name)
if not activity_info is None:
if not appended_separator:
@@ -310,7 +310,7 @@ class EntryToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
- self._jobject = None
+ self._metadata = None
self._resume = ToolButton('activity-start')
self._resume.connect('clicked', self._resume_clicked_cb)
@@ -340,43 +340,41 @@ class EntryToolbar(gtk.Toolbar):
self.add(erase_button)
erase_button.show()
- def set_jobject(self, jobject):
- self._jobject = jobject
+ def set_metadata(self, metadata):
+ self._metadata = metadata
self._refresh_copy_palette()
self._refresh_resume_palette()
def _resume_clicked_cb(self, button):
- if self._jobject:
- misc.resume(self._jobject)
+ misc.resume(self._metadata)
def _copy_clicked_cb(self, button):
clipboard = gtk.Clipboard()
clipboard.set_with_data([('text/uri-list', 0, 0)],
- self._clipboard_get_func_cb,
- self._clipboard_clear_func_cb)
+ self.__clipboard_get_func_cb,
+ self.__clipboard_clear_func_cb)
- def _clipboard_get_func_cb(self, clipboard, selection_data, info, data):
- selection_data.set_uris(['file://' + self._jobject.file_path])
+ def __clipboard_get_func_cb(self, clipboard, selection_data, info, data):
+ file_path = model.get_file(self._metadata['uid'])
+ selection_data.set_uris(['file://' + file_path])
- def _clipboard_clear_func_cb(self, clipboard, data):
+ def __clipboard_clear_func_cb(self, clipboard, data):
+ #TODO: should we remove here the temp file created before?
pass
def _erase_button_clicked_cb(self, button):
registry = bundleregistry.get_registry()
- if self._jobject:
- bundle = misc.get_bundle(self._jobject)
- if bundle is not None and registry.is_installed(bundle):
- registry.uninstall(bundle)
- datastore.delete(self._jobject.object_id)
+ bundle = misc.get_bundle(self._metadata['uid'])
+ if bundle is not None and registry.is_installed(bundle):
+ registry.uninstall(bundle)
+ model.delete(self._metadata['uid'])
def _resume_menu_item_activate_cb(self, menu_item, service_name):
- if self._jobject:
- misc.resume(self._jobject, service_name)
+ misc.resume(self._metadata, service_name)
def _copy_menu_item_activate_cb(self, menu_item, vol):
- if self._jobject:
- datastore.copy(self._jobject, vol.id)
+ model.copy(self._metadata, vol.mount_point)
def _refresh_copy_palette(self):
palette = self._copy.get_palette()
@@ -387,7 +385,7 @@ class EntryToolbar(gtk.Toolbar):
volumes_manager = volume.get_volumes_manager()
for vol in volumes_manager.get_volumes():
- if self._jobject.metadata['mountpoint'] == vol.id:
+ if self._metadata['mountpoint'] == vol.mount_point:
continue
menu_item = MenuItem(vol.name)
menu_item.set_image(Icon(icon_name=vol.icon_name,
@@ -397,9 +395,9 @@ class EntryToolbar(gtk.Toolbar):
vol)
palette.menu.append(menu_item)
menu_item.show()
-
+
def _refresh_resume_palette(self):
- if self._jobject.metadata.get('activity_id', ''):
+ if self._metadata.get('activity_id', ''):
# TRANS: Action label for resuming an activity.
self._resume.set_tooltip(_('Resume'))
else:
@@ -412,7 +410,7 @@ class EntryToolbar(gtk.Toolbar):
palette.menu.remove(menu_item)
menu_item.destroy()
- for activity_info in misc.get_activities(self._jobject):
+ for activity_info in misc.get_activities(self._metadata):
menu_item = MenuItem(activity_info.get_name())
menu_item.set_image(Icon(file=activity_info.get_icon(),
icon_size=gtk.ICON_SIZE_MENU))
diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py
index befc7f4..a34184a 100644
--- a/src/jarabe/journal/listview.py
+++ b/src/jarabe/journal/listview.py
@@ -28,7 +28,7 @@ from sugar.graphics import style
from sugar.graphics.icon import CanvasIcon
from jarabe.journal.collapsedentry import CollapsedEntry
-from jarabe.journal import query
+from jarabe.journal import model
DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
@@ -118,9 +118,6 @@ class BaseListView(gtk.HBox):
self._datastore_updated_handler.remove()
self._datastore_deleted_handler.remove()
- if self._result_set:
- self._result_set.destroy()
-
def _vadjustment_changed_cb(self, vadjustment):
if vadjustment.props.upper > self._page_size:
self._vscrollbar.show()
@@ -141,38 +138,38 @@ class BaseListView(gtk.HBox):
self._last_value = value
self._result_set.seek(value)
- jobjects = self._result_set.read(self._page_size)
+ metadata_list = self._result_set.read(self._page_size)
if self._result_set.length != self._vadjustment.props.upper:
self._vadjustment.props.upper = self._result_set.length
self._vadjustment.changed()
- self._refresh_view(jobjects)
+ self._refresh_view(metadata_list)
self._dirty = False
logging.debug('_do_scroll %r %r\n' % (value, (time.time() - t)))
return False
- def _refresh_view(self, jobjects):
+ def _refresh_view(self, metadata_list):
logging.debug('ListView %r' % self)
# Indicate when the Journal is empty
- if len(jobjects) == 0:
+ if len(metadata_list) == 0:
self._show_message(EMPTY_JOURNAL)
return
# Refresh view and create the entries if they don't exist yet.
for i in range(0, self._page_size):
try:
- if i < len(jobjects):
+ if i < len(metadata_list):
if i >= len(self._entries):
entry = self.create_entry()
self._box.append(entry)
self._entries.append(entry)
- entry.jobject = jobjects[i]
+ entry.metadata = metadata_list[i]
else:
entry = self._entries[i]
- entry.jobject = jobjects[i]
+ entry.metadata = metadata_list[i]
entry.set_visible(True)
elif i < len(self._entries):
entry = self._entries[i]
@@ -193,9 +190,8 @@ class BaseListView(gtk.HBox):
self.refresh()
def refresh(self):
- if self._result_set:
- self._result_set.destroy()
- self._result_set = query.find(self._query)
+ logging.debug('ListView.refresh query %r' % self._query)
+ self._result_set = model.find(self._query)
self._vadjustment.props.upper = self._result_set.length
self._vadjustment.changed()
@@ -283,7 +279,7 @@ class BaseListView(gtk.HBox):
self._vadjustment.changed()
if self._result_set is None:
- self._result_set = query.find(self._query)
+ self._result_set = model.find(self._query)
max_value = max(0, self._result_set.length - self._page_size)
if self._vadjustment.props.value > max_value:
@@ -353,11 +349,13 @@ class BaseListView(gtk.HBox):
event_time):
logging.debug("drag_data_get_cb: requested target " + selection.target)
- jobject = self._last_clicked_entry.jobject
+ metadata = self._last_clicked_entry.metadata
if selection.target == 'text/uri-list':
- selection.set(selection.target, 8, jobject.file_path)
+ #TODO: figure out the best place to get rid of that temp file
+ file_path = model.get_file(metadata)
+ selection.set(selection.target, 8, file_path)
elif selection.target == 'journal-object-id':
- selection.set(selection.target, 8, jobject.object_id)
+ selection.set(selection.target, 8, metadata['uid'])
def _canvas_button_press_event_cb(self, widget, event):
logging.debug("button_press_event_cb")
@@ -385,7 +383,8 @@ class BaseListView(gtk.HBox):
def update_dates(self):
logging.debug('ListView.update_dates')
for entry in self._entries:
- entry.update_date()
+ if entry.get_visible():
+ entry.update_date()
def __datastore_created_cb(self, uid):
self._set_dirty()
diff --git a/src/jarabe/journal/misc.py b/src/jarabe/journal/misc.py
index 19322ad..3c9f83a 100644
--- a/src/jarabe/journal/misc.py
+++ b/src/jarabe/journal/misc.py
@@ -33,6 +33,7 @@ from sugar import util
from jarabe.model import bundleregistry
from jarabe.journal.journalentrybundle import JournalEntryBundle
+from jarabe.journal import model
def _get_icon_file_name(icon_name):
icon_theme = gtk.icon_theme_get_default()
@@ -47,30 +48,31 @@ def _get_icon_file_name(icon_name):
_icon_cache = util.LRU(50)
-def get_icon_name(jobject):
-
- cache_key = (jobject.object_id, jobject.metadata.get('timestamp', None))
+def get_icon_name(metadata):
+ cache_key = (metadata['uid'], metadata.get('timestamp', None))
if cache_key in _icon_cache:
return _icon_cache[cache_key]
file_name = None
- if jobject.is_activity_bundle() and jobject.file_path:
+ #TODO: figure out the best place to get rid of that temp file
+ file_path = model.get_file(metadata['uid'])
+ if is_activity_bundle(metadata) and os.path.exists(file_path):
try:
- bundle = ActivityBundle(jobject.file_path)
+ bundle = ActivityBundle(file_path)
file_name = bundle.get_icon()
except Exception:
logging.warning('Could not read bundle:\n' + \
''.join(traceback.format_exception(*sys.exc_info())))
file_name = _get_icon_file_name('application-octet-stream')
- if not file_name and jobject.metadata['activity']:
- service_name = jobject.metadata['activity']
+ if not file_name and metadata['activity']:
+ service_name = metadata['activity']
activity_info = bundleregistry.get_registry().get_bundle(service_name)
if activity_info:
file_name = activity_info.get_icon()
- mime_type = jobject.metadata['mime_type']
+ mime_type = metadata['mime_type']
if not file_name and mime_type:
icon_name = mime.get_mime_icon(mime_type)
if icon_name:
@@ -83,26 +85,27 @@ def get_icon_name(jobject):
return file_name
-def get_date(jobject):
+def get_date(metadata):
""" Convert from a string in iso format to a more human-like format. """
- if jobject.metadata.has_key('timestamp'):
- timestamp = float(jobject.metadata['timestamp'])
+ if metadata.has_key('timestamp'):
+ timestamp = float(metadata['timestamp'])
return util.timestamp_to_elapsed_string(timestamp)
- elif jobject.metadata.has_key('mtime'):
- ti = time.strptime(jobject.metadata['mtime'], "%Y-%m-%dT%H:%M:%S")
+ elif metadata.has_key('mtime'):
+ ti = time.strptime(metadata['mtime'], "%Y-%m-%dT%H:%M:%S")
return util.timestamp_to_elapsed_string(time.mktime(ti))
else:
return _('No date')
-def get_bundle(jobject):
+def get_bundle(metadata):
try:
- if jobject.is_activity_bundle() and jobject.file_path:
- return ActivityBundle(jobject.file_path)
- elif jobject.is_content_bundle() and jobject.file_path:
- return ContentBundle(jobject.file_path)
- elif jobject.metadata['mime_type'] == JournalEntryBundle.MIME_TYPE \
- and jobject.file_path:
- return JournalEntryBundle(jobject.file_path)
+ #TODO: figure out the best place to get rid of that temp file
+ file_path = model.get_file(metadata['uid'])
+ if is_activity_bundle(metadata) and os.path.exists(file_path):
+ return ActivityBundle(file_path)
+ elif is_content_bundle(metadata) and os.path.exists(file_path):
+ return ContentBundle(file_path)
+ elif is_journal_bundle(metadata) and os.path.exists(file_path):
+ return JournalEntryBundle(file_path)
else:
return None
except MalformedBundleException, e:
@@ -117,16 +120,16 @@ def _get_activities_for_mime(mime_type):
result.extend(registry.get_activities_for_type(parent_mime))
return result
-def get_activities(jobject):
+def get_activities(metadata):
activities = []
- bundle_id = jobject.metadata.get('activity', '')
+ bundle_id = metadata.get('activity', '')
if bundle_id:
activity_info = bundleregistry.get_registry().get_bundle(bundle_id)
if activity_info:
activities.append(activity_info)
- mime_type = jobject.metadata.get('mime_type', '')
+ mime_type = metadata.get('mime_type', '')
if mime_type:
activities_info = _get_activities_for_mime(mime_type)
for activity_info in activities_info:
@@ -135,13 +138,15 @@ def get_activities(jobject):
return activities
-def resume(jobject, bundle_id=None):
+def resume(metadata, bundle_id=None):
registry = bundleregistry.get_registry()
- if jobject.is_activity_bundle() and not bundle_id:
+ if is_activity_bundle(metadata) and bundle_id is None:
logging.debug('Creating activity bundle')
- bundle = ActivityBundle(jobject.file_path)
+ #TODO: figure out the best place to get rid of that temp file
+ file_path = model.get_file(metadata['uid'])
+ bundle = ActivityBundle(file_path)
if not registry.is_installed(bundle):
logging.debug('Installing activity bundle')
registry.install(bundle)
@@ -158,10 +163,12 @@ def resume(jobject, bundle_id=None):
logging.error('Bundle %r is not installed.',
bundle.get_bundle_id())
- elif jobject.is_content_bundle() and not bundle_id:
+ elif is_content_bundle(metadata) and bundle_id is None:
logging.debug('Creating content bundle')
- bundle = ContentBundle(jobject.file_path)
+ #TODO: figure out the best place to get rid of that temp file
+ file_path = model.get_file(metadata['uid'])
+ bundle = ContentBundle(file_path)
if not bundle.is_installed():
logging.debug('Installing content bundle')
bundle.install()
@@ -178,22 +185,40 @@ def resume(jobject, bundle_id=None):
activityfactory.create_with_uri(activity_bundle, bundle.get_start_uri())
else:
if bundle_id is None:
- activities = get_activities(jobject)
+ activities = get_activities(metadata)
if not activities:
logging.warning('No activity can open this object, %s.' %
- jobject.metadata.get('mime_type', None))
+ metadata.get('mime_type', None))
return
bundle_id = activities[0].get_bundle_id()
bundle = registry.get_bundle(bundle_id)
- activity_id = jobject.metadata['activity_id']
- object_id = jobject.object_id
+ activity_id = metadata['activity_id']
+
+ if metadata['mountpoint'] == '/':
+ object_id = metadata['uid']
+ else:
+ object_id = model.copy(metadata, '/')
- if activity_id:
+ if activity_id is None:
handle = ActivityHandle(object_id=object_id,
activity_id=activity_id)
activityfactory.create(bundle, handle)
else:
activityfactory.create_with_object_id(bundle, object_id)
+def is_activity_bundle(metadata):
+ return metadata['mime_type'] in \
+ [ActivityBundle.MIME_TYPE, ActivityBundle.DEPRECATED_MIME_TYPE]
+
+def is_content_bundle(metadata):
+ return metadata['mime_type'] == ContentBundle.MIME_TYPE
+
+def is_journal_bundle(metadata):
+ return metadata['mime_type'] == JournalEntryBundle.MIME_TYPE
+
+def is_bundle(metadata):
+ return is_activity_bundle(metadata) or is_content_bundle(metadata) or \
+ is_journal_bundle(metadata)
+
diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py
new file mode 100644
index 0000000..4e23533
--- /dev/null
+++ b/src/jarabe/journal/model.py
@@ -0,0 +1,325 @@
+# Copyright (C) 2007-2008, One Laptop Per Child
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+import os
+from datetime import datetime
+import time
+
+import dbus
+import gconf
+
+from sugar import dispatch
+from sugar import mime
+
+DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
+DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
+DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
+
+# Properties the journal cares about.
+PROPERTIES = ['uid', 'title', 'mtime', 'timestamp', 'keep', 'buddies',
+ 'icon-color', 'mime_type', 'progress', 'activity', 'mountpoint',
+ 'activity_id']
+
+class _Cache(object):
+
+ __gtype_name__ = 'model_Cache'
+
+ def __init__(self, entries=None):
+ self._array = []
+ self._dict = {}
+ if entries is not None:
+ self.append_all(entries)
+
+ def prepend_all(self, entries):
+ for entry in entries[::-1]:
+ self._array.insert(0, entry)
+ self._dict[entry['uid']] = entry
+
+ def append_all(self, entries):
+ for entry in entries:
+ self._array.append(entry)
+ self._dict[entry['uid']] = entry
+
+ def remove_all(self, entries):
+ entries = entries[:]
+ for entry in entries:
+ obj = self._dict[entry['uid']]
+ self._array.remove(obj)
+ del self._dict[entry['uid']]
+
+ def __len__(self):
+ return len(self._array)
+
+ def __getitem__(self, key):
+ if isinstance(key, basestring):
+ return self._dict[key]
+ else:
+ return self._array[key]
+
+class ResultSet(object):
+ """Encapsulates the result of a query
+ """
+
+ _CACHE_LIMIT = 80
+
+ def __init__(self, query):
+ self._total_count = -1
+ self._position = -1
+ self._query = query
+
+ self._offset = 0
+ self._cache = _Cache()
+
+ def get_length(self):
+ if self._total_count == -1:
+ query = self._query.copy()
+ query['limit'] = ResultSet._CACHE_LIMIT
+ entries, self._total_count = self._find(query)
+ self._cache.append_all(entries)
+ self._offset = 0
+ return self._total_count
+
+ length = property(get_length)
+
+ def _find(self, query):
+ mount_points = query.get('mountpoints', ['/'])
+ if mount_points is None or len(mount_points) != 1:
+ raise ValueError('Exactly one mount point must be specified')
+ if mount_points[0] == '/':
+ return _get_datastore().find(query, PROPERTIES, byte_arrays=True)
+ else:
+ return _query_mount_point(mount_points[0], query)
+
+ def seek(self, position):
+ self._position = position
+
+ def read(self, max_count):
+ logging.debug('ResultSet.read position: %r' % self._position)
+
+ if max_count * 5 > ResultSet._CACHE_LIMIT:
+ raise RuntimeError(
+ 'max_count (%i) too big for ResultSet._CACHE_LIMIT'
+ ' (%i).' % (max_count, ResultSet._CACHE_LIMIT))
+
+ if self._position == -1:
+ self.seek(0)
+
+ if self._position < self._offset:
+ remaining_forward_entries = 0
+ else:
+ remaining_forward_entries = self._offset + len(self._cache) - \
+ self._position
+
+ if self._position > self._offset + len(self._cache):
+ remaining_backwards_entries = 0
+ else:
+ remaining_backwards_entries = self._position - self._offset
+
+ last_cached_entry = self._offset + len(self._cache)
+
+ if (remaining_forward_entries <= 0 and
+ remaining_backwards_entries <= 0) or \
+ max_count > ResultSet._CACHE_LIMIT:
+
+ # Total cache miss: remake it
+ offset = max(0, self._position - max_count)
+ logging.debug('remaking cache, offset: %r limit: %r' % \
+ (offset, max_count * 2))
+ query = self._query.copy()
+ query['limit'] = ResultSet._CACHE_LIMIT
+ query['offset'] = offset
+ entries, self._total_count = self._find(query)
+
+ self._cache.remove_all(self._cache)
+ self._cache.append_all(entries)
+ self._offset = offset
+
+ elif remaining_forward_entries < 2 * max_count and \
+ last_cached_entry < self._total_count:
+
+ # Add one page to the end of cache
+ logging.debug('appending one more page, offset: %r' % \
+ last_cached_entry)
+ query = self._query.copy()
+ query['limit'] = max_count
+ query['offset'] = last_cached_entry
+ entries, self._total_count = self._find(query)
+
+ # update cache
+ self._cache.append_all(entries)
+
+ # apply the cache limit
+ objects_excess = len(self._cache) - ResultSet._CACHE_LIMIT
+ if objects_excess > 0:
+ self._offset += objects_excess
+ self._cache.remove_all(self._cache[:objects_excess])
+
+ elif remaining_backwards_entries < 2 * max_count and self._offset > 0:
+
+ # Add one page to the beginning of cache
+ limit = min(self._offset, max_count)
+ self._offset = max(0, self._offset - max_count)
+
+ logging.debug('prepending one more page, offset: %r limit: %r' %
+ (self._offset, limit))
+ query = self._query.copy()
+ query['limit'] = limit
+ query['offset'] = self._offset
+ entries, self._total_count = self._find(query)
+
+ # update cache
+ self._cache.prepend_all(entries)
+
+ # apply the cache limit
+ objects_excess = len(self._cache) - ResultSet._CACHE_LIMIT
+ if objects_excess > 0:
+ self._cache.remove_all(self._cache[-objects_excess:])
+ else:
+ logging.debug('cache hit and no need to grow the cache')
+
+ first_pos = self._position - self._offset
+ last_pos = self._position - self._offset + max_count
+ return self._cache[first_pos:last_pos]
+
+def _get_file_metadata(path):
+ stat = os.stat(path)
+ client = gconf.client_get_default()
+ return {'uid': path,
+ 'title': os.path.basename(path),
+ 'timestamp': stat.st_mtime,
+ 'mime_type': mime.get_for_file(path),
+ 'activity': '',
+ 'activity_id': '',
+ 'icon-color': client.get_string('/desktop/sugar/user/color')}
+
+def _get_all_files(dir_path, mount_point):
+ files = []
+ for entry in os.listdir(dir_path):
+ full_path = os.path.join(dir_path, entry)
+ if os.path.isdir(full_path):
+ files.extend(_get_all_files(full_path, mount_point))
+ elif os.path.isfile(full_path):
+ metadata = _get_file_metadata(full_path)
+ metadata['mountpoint'] = mount_point
+ files.append(metadata)
+ return files
+
+def _query_mount_point(mount_point, query):
+ t = time.time()
+
+ files = _get_all_files(mount_point, mount_point)
+ offset = int(query.get('offset', 0))
+ limit = int(query.get('limit', len(files)))
+ result = files[offset:offset + limit], len(files)
+
+ logging.debug('_query_mount_point took %f s.' % (time.time() - t))
+
+ return result
+
+_datastore = None
+def _get_datastore():
+ global _datastore
+ if _datastore is None:
+ bus = dbus.SessionBus()
+ remote_object = bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH)
+ _datastore = dbus.Interface(remote_object, DS_DBUS_INTERFACE)
+
+ return _datastore
+
+def find(query):
+ """Returns a ResultSet
+ """
+ if 'order_by' not in query:
+ query['order_by'] = ['-mtime']
+ return ResultSet(query)
+
+def _get_mount_point(path):
+ dir_path = os.path.dirname(path)
+ while True:
+ if os.path.ismount(dir_path):
+ return dir_path
+ else:
+ dir_path = dir_path.rsplit(os.sep, 1)[0]
+
+def get(object_id):
+ """Returns the metadata for an object
+ """
+ if os.path.exists(object_id):
+ metadata = _get_file_metadata(object_id)
+ metadata['mountpoint'] = _get_mount_point(object_id)
+ else:
+ metadata = _get_datastore().get_properties(object_id, byte_arrays=True)
+ metadata['mountpoint'] = '/'
+ return metadata
+
+def get_file(object_id):
+ """Returns the file for an object
+ """
+ if os.path.exists(object_id):
+ return object_id
+ else:
+ return _get_datastore().get_filename(object_id)
+
+def get_unique_values(key):
+ """Returns a list with the different values a property has taken
+ """
+ return []
+
+def delete(object_id):
+ """Removes an object from persistent storage
+ """
+ pass
+
+def copy(metadata, mount_point):
+ """Copies an object to another mount point
+ """
+ metadata = get(metadata['uid'])
+
+ #TODO: figure out the best place to get rid of that temp file
+ file_path = get_file(metadata['uid'])
+
+ metadata['mountpoint'] = mount_point
+ del metadata['uid']
+
+ return write(metadata, file_path)
+
+def write(metadata, file_path='', update_mtime=True):
+ """Creates or updates an entry for that id
+ """
+ if update_mtime:
+ metadata['mtime'] = datetime.now().isoformat()
+ metadata['timestamp'] = int(time.time())
+
+ if metadata['mountpoint'] == '/':
+ if metadata.get('uid', ''):
+ object_id = _get_datastore().update(metadata['uid'],
+ dbus.Dictionary(metadata),
+ file_path,
+ True)
+ else:
+ object_id = _get_datastore().create(dbus.Dictionary(metadata),
+ file_path,
+ True)
+ else:
+ pass
+
+ return object_id
+
+created = dispatch.Signal()
+updated = dispatch.Signal()
+deleted = dispatch.Signal()
+
diff --git a/src/jarabe/journal/objectchooser.py b/src/jarabe/journal/objectchooser.py
index 947141d..fcaed55 100644
--- a/src/jarabe/journal/objectchooser.py
+++ b/src/jarabe/journal/objectchooser.py
@@ -23,7 +23,6 @@ import hippo
from sugar.graphics import style
from sugar.graphics.toolbutton import ToolButton
-from sugar.datastore import datastore
from jarabe.journal.listview import ListView
from jarabe.journal.collapsedentry import BaseCollapsedEntry
@@ -84,7 +83,7 @@ class ObjectChooser(gtk.Window):
vbox.pack_start(self._list_view)
self._list_view.show()
- self._toolbar.set_volume_id(datastore.mounts()[0]['id'])
+ self._toolbar.set_mount_point('/')
width = gtk.gdk.screen_width() - style.GRID_CELL_SIZE * 2
height = gtk.gdk.screen_height() - style.GRID_CELL_SIZE * 2
@@ -95,7 +94,7 @@ class ObjectChooser(gtk.Window):
# TODO: Should we disconnect the signal here?
def __entry_activated_cb(self, list_view, entry):
- self._selected_object_id = entry.jobject.object_id
+ self._selected_object_id = entry.metadata['uid']
self.emit('response', gtk.RESPONSE_ACCEPT)
def __delete_event_cb(self, chooser, event):
diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py
index 9ca1190..1f41032 100644
--- a/src/jarabe/journal/palettes.py
+++ b/src/jarabe/journal/palettes.py
@@ -24,37 +24,37 @@ from sugar.graphics import style
from sugar.graphics.palette import Palette
from sugar.graphics.menuitem import MenuItem
from sugar.graphics.icon import Icon
-from sugar.datastore import datastore
from sugar.graphics.xocolor import XoColor
from jarabe.model import bundleregistry
from jarabe.journal import misc
+from jarabe.journal import model
class ObjectPalette(Palette):
- def __init__(self, jobject):
+ def __init__(self, metadata):
- self._jobject = jobject
+ self._metadata = metadata
activity_icon = Icon(icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
- activity_icon.props.file = misc.get_icon_name(jobject)
- if jobject.metadata.has_key('icon-color') and \
- jobject.metadata['icon-color']:
+ activity_icon.props.file = misc.get_icon_name(metadata)
+ if metadata.has_key('icon-color') and \
+ metadata['icon-color']:
activity_icon.props.xo_color = \
- XoColor(jobject.metadata['icon-color'])
+ XoColor(metadata['icon-color'])
else:
activity_icon.props.xo_color = \
XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
style.COLOR_TRANSPARENT.get_svg()))
- if jobject.metadata.has_key('title'):
- title = jobject.metadata['title']
+ if metadata.has_key('title'):
+ title = metadata['title']
else:
title = _('Untitled')
Palette.__init__(self, primary_text=title,
icon=activity_icon)
- if jobject.metadata.get('activity_id', ''):
+ if metadata.get('activity_id', ''):
resume_label = _('Resume')
else:
resume_label = _('Start')
@@ -81,7 +81,7 @@ class ObjectPalette(Palette):
menu_item.show()
def __start_activate_cb(self, menu_item):
- misc.resume(self._jobject)
+ misc.resume(self._metadata)
def __copy_activate_cb(self, menu_item):
clipboard = gtk.Clipboard()
@@ -90,19 +90,21 @@ class ObjectPalette(Palette):
self.__clipboard_clear_func_cb)
def __clipboard_get_func_cb(self, clipboard, selection_data, info, data):
- logging.debug('__clipboard_get_func_cb %r' % self._jobject.file_path)
- selection_data.set_uris(['file://' + self._jobject.file_path])
+ file_path = model.get_file(self._metadata['uid'])
+ logging.debug('__clipboard_get_func_cb %r' % file_path)
+ selection_data.set_uris(['file://' + file_path])
def __clipboard_clear_func_cb(self, clipboard, data):
+ #TODO: should we remove here the temp file created before?
pass
def __erase_activate_cb(self, menu_item):
registry = bundleregistry.get_registry()
- bundle = misc.get_bundle(self._jobject)
+ bundle = misc.get_bundle(self._metadata)
if bundle is not None and registry.is_installed(bundle):
registry.uninstall(bundle)
- datastore.delete(self._jobject.object_id)
+ model.delete(self._metadata['uid'])
class BuddyPalette(Palette):
def __init__(self, buddy):
diff --git a/src/jarabe/journal/query.py b/src/jarabe/journal/query.py
deleted file mode 100644
index 04d9b16..0000000
--- a/src/jarabe/journal/query.py
+++ /dev/null
@@ -1,266 +0,0 @@
-# Copyright (C) 2007, One Laptop Per Child
-#
-# 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 2 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, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-import logging
-
-from sugar.datastore import datastore
-
-# Properties the journal cares about.
-PROPERTIES = ['uid', 'title', 'mtime', 'timestamp', 'keep', 'buddies',
- 'icon-color', 'mime_type', 'progress', 'activity', 'mountpoint',
- 'activity_id']
-
-class _Cache(object):
-
- __gtype_name__ = 'query_Cache'
-
- def __init__(self, jobjects=None):
- self._array = []
- self._dict = {}
- if jobjects is not None:
- self.append_all(jobjects)
-
- def prepend_all(self, jobjects):
- for jobject in jobjects[::-1]:
- self._array.insert(0, jobject)
- self._dict[jobject.object_id] = jobject
-
- def append_all(self, jobjects):
- for jobject in jobjects:
- self._array.append(jobject)
- self._dict[jobject.object_id] = jobject
-
- def remove_all(self, jobjects):
- jobjects = jobjects[:]
- for jobject in jobjects:
- obj = self._dict[jobject.object_id]
- self._array.remove(obj)
- del self._dict[obj.object_id]
- obj.destroy()
-
- def __len__(self):
- return len(self._array)
-
- def __getitem__(self, key):
- if isinstance(key, basestring):
- return self._dict[key]
- else:
- return self._array[key]
-
- def destroy(self):
- self._destroy_jobjects(self._array)
- self._array = []
- self._dict = {}
-
- def _destroy_jobjects(self, jobjects):
- for jobject in jobjects:
- jobject.destroy()
-
-class ResultSet(object):
-
- _CACHE_LIMIT = 80
-
- def __init__(self, query, sorting):
- self._total_count = -1
- self._position = -1
- self._query = query
- self._sorting = sorting
-
- self._offset = 0
- self._cache = _Cache()
-
- def destroy(self):
- self._cache.destroy()
-
- def get_length(self):
- if self._total_count == -1:
- jobjects, self._total_count = datastore.find(self._query,
- sorting=self._sorting,
- limit=ResultSet._CACHE_LIMIT,
- properties=PROPERTIES)
- self._cache.append_all(jobjects)
- self._offset = 0
- return self._total_count
-
- length = property(get_length)
-
- def seek(self, position):
- self._position = position
-
- def read(self, max_count):
- logging.debug('ResultSet.read position: %r' % self._position)
-
- if max_count * 5 > ResultSet._CACHE_LIMIT:
- raise RuntimeError(
- 'max_count (%i) too big for ResultSet._CACHE_LIMIT'
- ' (%i).' % (max_count, ResultSet._CACHE_LIMIT))
-
- if self._position == -1:
- self.seek(0)
-
- if self._position < self._offset:
- remaining_forward_entries = 0
- else:
- remaining_forward_entries = self._offset + len(self._cache) - \
- self._position
-
- if self._position > self._offset + len(self._cache):
- remaining_backwards_entries = 0
- else:
- remaining_backwards_entries = self._position - self._offset
-
- last_cached_entry = self._offset + len(self._cache)
-
- if (remaining_forward_entries <= 0 and
- remaining_backwards_entries <= 0) or \
- max_count > ResultSet._CACHE_LIMIT:
-
- # Total cache miss: remake it
- offset = max(0, self._position - max_count)
- logging.debug('remaking cache, offset: %r limit: %r' % \
- (offset, max_count * 2))
- jobjects, self._total_count = datastore.find(self._query,
- sorting=self._sorting,
- offset=offset,
- limit=ResultSet._CACHE_LIMIT,
- properties=PROPERTIES)
-
- self._cache.remove_all(self._cache)
- self._cache.append_all(jobjects)
- self._offset = offset
-
- elif remaining_forward_entries < 2 * max_count and \
- last_cached_entry < self._total_count:
-
- # Add one page to the end of cache
- logging.debug('appending one more page, offset: %r' % \
- last_cached_entry)
- jobjects, self._total_count = datastore.find(self._query,
- sorting=self._sorting,
- offset=last_cached_entry,
- limit=max_count,
- properties=PROPERTIES)
- # update cache
- self._cache.append_all(jobjects)
-
- # apply the cache limit
- objects_excess = len(self._cache) - ResultSet._CACHE_LIMIT
- if objects_excess > 0:
- self._offset += objects_excess
- self._cache.remove_all(self._cache[:objects_excess])
-
- elif remaining_backwards_entries < 2 * max_count and self._offset > 0:
-
- # Add one page to the beginning of cache
- limit = min(self._offset, max_count)
- self._offset = max(0, self._offset - max_count)
-
- logging.debug('prepending one more page, offset: %r limit: %r' %
- (self._offset, limit))
- jobjects, self._total_count = datastore.find(self._query,
- sorting=self._sorting,
- offset=self._offset,
- limit=limit,
- properties=PROPERTIES)
-
- # update cache
- self._cache.prepend_all(jobjects)
-
- # apply the cache limit
- objects_excess = len(self._cache) - ResultSet._CACHE_LIMIT
- if objects_excess > 0:
- self._cache.remove_all(self._cache[-objects_excess:])
- else:
- logging.debug('cache hit and no need to grow the cache')
-
- first_pos = self._position - self._offset
- last_pos = self._position - self._offset + max_count
- return self._cache[first_pos:last_pos]
-
-def find(query, sorting=None):
- if sorting is None:
- sorting = ['-mtime']
- result_set = ResultSet(query, sorting)
- return result_set
-
-def test():
- TOTAL_ITEMS = 1000
- SCREEN_SIZE = 10
-
- def mock_debug(string):
- print "\tDEBUG: %s" % string
- logging.debug = mock_debug
-
- def mock_find(query, sorting=None, limit=None, offset=None,
- properties=None):
- if properties is None:
- properties = []
-
- print "mock_find %r %r" % (offset, (offset + limit))
-
- if limit is None or offset is None:
- raise RuntimeError("Unimplemented test.")
-
- result = []
- for index in range(offset, offset + limit):
- obj = datastore.DSObject(index, datastore.DSMetadata({}), '')
- result.append(obj)
-
- return result, TOTAL_ITEMS
- datastore.find = mock_find
-
- result_set = find({})
-
- print "Get first page"
- objects = result_set.read(SCREEN_SIZE)
- print [obj.object_id for obj in objects]
- assert range(0, SCREEN_SIZE) == [obj.object_id for obj in objects]
- print ""
-
- print "Scroll to 5th item"
- result_set.seek(5)
- objects = result_set.read(SCREEN_SIZE)
- print [obj.object_id for obj in objects]
- assert range(5, SCREEN_SIZE + 5) == [obj.object_id for obj in objects]
- print ""
-
- print "Scroll back to beginning"
- result_set.seek(0)
- objects = result_set.read(SCREEN_SIZE)
- print [obj.object_id for obj in objects]
- assert range(0, SCREEN_SIZE) == [obj.object_id for obj in objects]
- print ""
-
- print "Hit PgDn five times"
- for i in range(0, 5):
- result_set.seek((i + 1) * SCREEN_SIZE)
- objects = result_set.read(SCREEN_SIZE)
- print [obj.object_id for obj in objects]
- assert range((i + 1) * SCREEN_SIZE, (i + 2) * SCREEN_SIZE) == \
- [obj.object_id for obj in objects]
- print ""
-
- print "Hit PgUp five times"
- for i in range(0, 5)[::-1]:
- result_set.seek(i * SCREEN_SIZE)
- objects = result_set.read(SCREEN_SIZE)
- print [obj.object_id for obj in objects]
- assert range(i * SCREEN_SIZE, (i + 1) * SCREEN_SIZE) == \
- [obj.object_id for obj in objects]
- print ""
-
-if __name__ == "__main__":
- test()
diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py
index b29f325..50b9aa7 100644
--- a/src/jarabe/journal/volumestoolbar.py
+++ b/src/jarabe/journal/volumestoolbar.py
@@ -20,11 +20,11 @@ from gettext import gettext as _
import gobject
import gtk
-from sugar.datastore import datastore
from sugar.graphics.radiotoolbutton import RadioToolButton
from sugar.graphics.palette import Palette
from jarabe.model import volume
+from jarabe.journal import model
class VolumesToolbar(gtk.Toolbar):
__gtype_name__ = 'VolumesToolbar'
@@ -32,7 +32,7 @@ class VolumesToolbar(gtk.Toolbar):
__gsignals__ = {
'volume-changed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
- ([str]))
+ ([object]))
}
def __init__(self):
@@ -43,9 +43,7 @@ class VolumesToolbar(gtk.Toolbar):
self.connect('destroy', self.__destroy_cb)
- # TODO: It's unclear now how removable devices will be handled in the
- # Journal. Disable for now.
- #gobject.idle_add(self._set_up_volumes)
+ gobject.idle_add(self._set_up_volumes)
def __destroy_cb(self, widget):
volumes_manager = volume.get_volumes_manager()
@@ -91,7 +89,7 @@ class VolumesToolbar(gtk.Toolbar):
self._volume_buttons.append(button)
- if vol.can_unmount:
+ if vol.can_eject:
menu_item = gtk.MenuItem(_('Unmount'))
menu_item.connect('activate', self._unmount_activated_cb, vol)
palette.menu.append(menu_item)
@@ -102,7 +100,7 @@ class VolumesToolbar(gtk.Toolbar):
def _button_toggled_cb(self, button, vol):
if button.props.active:
- self.emit('volume-changed', vol.id)
+ self.emit('volume-changed', vol)
def _unmount_activated_cb(self, menu_item, vol):
logging.debug('VolumesToolbar._unmount_activated_cb: %r', vol.udi)
@@ -110,7 +108,7 @@ class VolumesToolbar(gtk.Toolbar):
def _remove_button(self, vol):
for button in self.get_children():
- if button.volume.id == vol.id:
+ if button.volume.udi == vol.udi:
self._volume_buttons.remove(button)
self.remove(button)
self.get_children()[0].props.active = True
@@ -118,6 +116,15 @@ class VolumesToolbar(gtk.Toolbar):
if len(self.get_children()) < 2:
self.hide()
return
+ logging.error('Couldnt find volume with udi %r' % vol.udi)
+
+ def set_active_volume(self, mount_point):
+ for button in self.get_children():
+ logging.error('udi %r' % button.volume.mount_point)
+ if button.volume.mount_point == mount_point:
+ button.props.active = True
+ return
+ logging.error('Couldnt find volume with mount_point %r' % mount_point)
class VolumeButton(RadioToolButton):
def __init__(self, vol, group):
@@ -134,5 +141,7 @@ class VolumeButton(RadioToolButton):
def _drag_data_received_cb(self, widget, drag_context, x, y, selection_data,
info, timestamp):
- jobject = datastore.get(selection_data.data)
- datastore.copy(jobject, self.volume.id)
+ object_id = selection_data.data
+ metadata = model.get(object_id)
+ model.copy(metadata, self.volume.mount_point)
+
diff --git a/src/jarabe/model/volume.py b/src/jarabe/model/volume.py
index 6afa6a6..0d3d9a5 100644
--- a/src/jarabe/model/volume.py
+++ b/src/jarabe/model/volume.py
@@ -84,17 +84,6 @@ class VolumesManager(gobject.GObject):
# Ignore volumes without a filesystem.
if device.GetProperty('volume.fsusage') != 'filesystem':
return False
- # Ignore root.
- if device.GetProperty('volume.mount_point') == '/':
- return False
-
- storage_udi = device.GetProperty('block.storage_device')
- obj = bus.get_object(HAL_SERVICE_NAME, storage_udi)
- storage_device = dbus.Interface(obj, HAL_DEVICE_IFACE)
-
- # Ignore non-removable storage.
- if not storage_device.GetProperty('storage.hotpluggable'):
- return False
return True
@@ -188,6 +177,7 @@ class VolumesManager(gobject.GObject):
self._remove_volume(udi)
def _add_volume(self, udi):
+ logging.debug('_add_volume %r' % udi)
bus = dbus.SystemBus()
device_object = bus.get_object(HAL_SERVICE_NAME, udi)
device = dbus.Interface(device_object, HAL_DEVICE_IFACE)
@@ -198,11 +188,17 @@ class VolumesManager(gobject.GObject):
mount_point = device.GetProperty('volume.mount_point')
+ storage_udi = device.GetProperty('block.storage_device')
+ obj = bus.get_object(HAL_SERVICE_NAME, storage_udi)
+ storage_device = dbus.Interface(obj, HAL_DEVICE_IFACE)
+ can_eject = storage_device.GetProperty('storage.hotpluggable')
+
volume = Volume(volume_name,
self._get_icon_for_volume(device),
profile.get_color(),
udi,
- mount_point)
+ mount_point,
+ can_eject)
self._volumes[udi] = volume
logging.debug('mounted volume %s' % udi)
@@ -227,16 +223,20 @@ class VolumesManager(gobject.GObject):
storage_drive_type = storage_device.GetProperty('storage.drive_type')
if storage_drive_type == 'sd_mmc':
return 'media-flash-sd-mmc'
+ elif device.GetProperty('volume.mount_point') == '/':
+ return 'computer-xo'
else:
return 'media-flash-usb'
class Volume(object):
- def __init__(self, name, icon_name, icon_color, udi, mount_point):
+ def __init__(self, name, icon_name, icon_color, udi, mount_point,
+ can_eject):
self.name = name
self.icon_name = icon_name
self.icon_color = icon_color
self.udi = udi
self.mount_point = mount_point
+ self.can_eject = can_eject
def unmount(self):
logging.debug('Volumes.unmount: %r', self.udi)