Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bin/sugar-session15
-rw-r--r--extensions/deviceicon/volume.py95
-rw-r--r--src/jarabe/journal/journalactivity.py10
-rw-r--r--src/jarabe/journal/journaltoolbox.py21
-rw-r--r--src/jarabe/journal/model.py95
-rw-r--r--src/jarabe/journal/volumestoolbar.py159
-rw-r--r--src/jarabe/model/Makefile.am3
-rw-r--r--src/jarabe/model/volume.py253
8 files changed, 234 insertions, 417 deletions
diff --git a/bin/sugar-session b/bin/sugar-session
index 12e1f9e..7118c21 100644
--- a/bin/sugar-session
+++ b/bin/sugar-session
@@ -77,20 +77,6 @@ def cleanup_logs():
dest_path = os.path.join(backup_dir, log)
os.rename(source_path, dest_path)
-def start_datastore():
- from sugar.datastore import datastore
-
- # Mount the datastore in internal flash
- ds_path = env.get_profile_path('datastore')
- try:
- datastore.mount(ds_path, [], timeout=120)
- except Exception, e:
- # Don't explode if there's corruption; move the data out of the way
- # and attempt to create a store from scratch.
- logging.error(e)
- shutil.move(ds_path, os.path.abspath(ds_path) + str(time.time()))
- datastore.mount(ds_path, [], timeout=120)
-
def start_ui_service():
from jarabe.view.service import UIService
@@ -146,7 +132,6 @@ def main():
if timezone is not '':
os.environ['TZ'] = timezone
- start_datastore()
start_ui_service()
start_session_manager()
diff --git a/extensions/deviceicon/volume.py b/extensions/deviceicon/volume.py
index 8342cf9..80cf242 100644
--- a/extensions/deviceicon/volume.py
+++ b/extensions/deviceicon/volume.py
@@ -17,15 +17,19 @@
import os
import statvfs
from gettext import gettext as _
+import logging
+import gobject
+import gio
import gtk
+import gconf
from sugar.graphics.tray import TrayIcon
from sugar.graphics.palette import Palette
from sugar.graphics.menuitem import MenuItem
from sugar.graphics.icon import Icon
+from sugar.graphics.xocolor import XoColor
-from jarabe.model import volume
from jarabe.journal import journalactivity
_icons = {}
@@ -34,26 +38,35 @@ class DeviceView(TrayIcon):
FRAME_POSITION_RELATIVE = 800
- def __init__(self, model):
- TrayIcon.__init__(self, icon_name=model.icon_name,
- xo_color=model.icon_color)
- self._model = model
+ def __init__(self, mount):
+ TrayIcon.__init__(self)
+ self._mount = mount
+
+ # TODO: fallback to the more generic icons when needed
+ self.get_icon().props.icon_name = self._mount.get_icon().props.names[0]
+
+ # TODO: retrieve the colors from the owner of the device
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ self.get_icon().props.xo_color = color
+
self.connect('button-release-event', self.__button_release_event_cb)
def create_palette(self):
- return VolumePalette(self._model)
+ return VolumePalette(self._mount)
def __button_release_event_cb(self, widget, event):
journal = journalactivity.get_journal()
- journal.set_active_volume(self._model.mount_point)
+ journal.set_active_volume(self._mount)
journal.present()
return True
class VolumePalette(Palette):
- def __init__(self, model):
- Palette.__init__(self, label=model.name,
- secondary_text=model.mount_point)
- self._model = model
+ def __init__(self, mount):
+ Palette.__init__(self, label=mount.get_name())
+ self._mount = mount
+
+ self.props.secondary_text = mount.get_root().get_path()
vbox = gtk.VBox()
self.set_content(vbox)
@@ -81,10 +94,14 @@ class VolumePalette(Palette):
menu_item.show()
def __unmount_activate_cb(self, menu_item):
- self._model.unmount()
+ self._mount.unmount(self.__unmount_cb)
+
+ def __unmount_cb(self, source, result):
+ logging.debug('__unmount_cb %r %r' % (source, result))
def __popup_cb(self, palette):
- stat = os.statvfs(self._model.mount_point)
+ mount_point = self._mount.get_root().get_path()
+ stat = os.statvfs(mount_point)
free_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL]
total_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BLOCKS]
@@ -94,28 +111,44 @@ class VolumePalette(Palette):
{'free_space': free_space / (1024 * 1024)}
def setup(tray):
- volumes_manager = volume.get_volumes_manager()
+ gobject.idle_add(_setup_volumes, tray)
- for vol in volumes_manager.get_volumes():
- if vol.mount_point != '/':
- _add_device(vol, tray)
+def _setup_volumes(tray):
+ volume_monitor = gio.volume_monitor_get()
- volumes_manager.connect('volume-added', _volume_added_cb, tray)
- volumes_manager.connect('volume-removed', _volume_removed_cb, tray)
+ for volume in volume_monitor.get_volumes():
+ _mount(volume, tray)
-def _volume_added_cb(volumes_manager, vol, tray):
- if vol.mount_point != '/':
- _add_device(vol, tray)
+ for mount in volume_monitor.get_mounts():
+ _add_device(mount, tray)
-def _volume_removed_cb(volumes_manager, vol, tray):
- _remove_device(vol, tray)
+ #volume_monitor.connect('volume-added', _volume_added_cb, tray)
+ volume_monitor.connect('mount-added', _mount_added_cb, tray)
+ volume_monitor.connect('mount-removed', _mount_removed_cb, tray)
-def _add_device(vol, tray):
- icon = DeviceView(vol)
- _icons[vol] = icon
- tray.add_device(icon)
+def _volume_added_cb(volume_monitor, volume, tray):
+ _mount(volume, tray)
-def _remove_device(vol, tray):
- icon = _icons[vol]
+def _mount(volume, tray):
+ #TODO: this should be done by some other process, like gvfs-hal-volume-monitor
+ #TODO: use volume.should_automount() when it gets into pygtk
+ if volume.get_mount() is None and volume.can_mount():
+ #TODO: pass None as mount_operation, or better, SugarMountOperation
+ volume.mount(gtk.MountOperation(tray.get_toplevel()), _mount_cb)
+
+def _mount_cb(source, result):
+ logging.debug('mount finished %r %r' % (source, result))
+
+def _mount_added_cb(volume_monitor, mount, tray):
+ _add_device(mount, tray)
+
+def _mount_removed_cb(volume_monitor, mount, tray):
+ icon = _icons[mount]
tray.remove_device(icon)
- del _icons[vol]
+ del _icons[mount]
+
+def _add_device(mount, tray):
+ icon = DeviceView(mount)
+ _icons[mount] = icon
+ tray.add_device(icon)
+
diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py
index 5ab99ef..e948162 100644
--- a/src/jarabe/journal/journalactivity.py
+++ b/src/jarabe/journal/journalactivity.py
@@ -239,9 +239,9 @@ class JournalActivity(Window):
self._show_secondary_view(metadata)
return True
- 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)
+ def __volume_changed_cb(self, volume_toolbar, mount_point):
+ logging.debug('Selected volume: %r.' % mount_point)
+ self._main_toolbox.search_toolbar.set_mount_point(mount_point)
self._main_toolbox.set_current_toolbar(0)
def __model_created_cb(self, object_id):
@@ -320,8 +320,8 @@ 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)
+ def set_active_volume(self, mount):
+ self._volumes_toolbar.set_active_volume(mount)
_journal = None
diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py
index beda184..f8052eb 100644
--- a/src/jarabe/journal/journaltoolbox.py
+++ b/src/jarabe/journal/journaltoolbox.py
@@ -21,6 +21,7 @@ import os
import gconf
import gobject
+import gio
import gtk
from sugar.graphics.toolbox import Toolbox
@@ -35,7 +36,6 @@ from sugar.graphics import style
from sugar import mime
from jarabe.model import bundleregistry
-from jarabe.model import volume
from jarabe.journal import misc
from jarabe.journal import model
@@ -373,8 +373,8 @@ class EntryToolbar(gtk.Toolbar):
def _resume_menu_item_activate_cb(self, menu_item, service_name):
misc.resume(self._metadata, service_name)
- def _copy_menu_item_activate_cb(self, menu_item, vol):
- model.copy(self._metadata, vol.mount_point)
+ def _copy_menu_item_activate_cb(self, menu_item, mount):
+ model.copy(self._metadata, mount.get_root().get_path())
def _refresh_copy_palette(self):
palette = self._copy.get_palette()
@@ -383,16 +383,19 @@ class EntryToolbar(gtk.Toolbar):
palette.menu.remove(menu_item)
menu_item.destroy()
- volumes_manager = volume.get_volumes_manager()
- for vol in volumes_manager.get_volumes():
- if self._metadata['mountpoint'] == vol.mount_point:
+ volume_monitor = gio.volume_monitor_get()
+ for mount in volume_monitor.get_mounts():
+ if self._metadata['mountpoint'] == mount.get_root().get_path():
continue
- menu_item = MenuItem(vol.name)
- menu_item.set_image(Icon(icon_name=vol.icon_name,
+ menu_item = MenuItem(mount.get_name())
+
+ # TODO: fallback to the more generic icons when needed
+ menu_item.set_image(Icon(icon_name=mount.get_icon().props.names[0],
icon_size=gtk.ICON_SIZE_MENU))
+
menu_item.connect('activate',
self._copy_menu_item_activate_cb,
- vol)
+ mount)
palette.menu.append(menu_item)
menu_item.show()
diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py
index 19068f2..375345a 100644
--- a/src/jarabe/journal/model.py
+++ b/src/jarabe/journal/model.py
@@ -18,6 +18,7 @@ import logging
import os
from datetime import datetime
import time
+import shutil
import dbus
import gconf
@@ -95,14 +96,54 @@ class ResultSet(object):
length = property(get_length)
+ def _get_all_files(self, dir_path):
+ files = []
+ for entry in os.listdir(dir_path):
+ full_path = os.path.join(dir_path, entry)
+ if os.path.isdir(full_path):
+ files.extend(self._get_all_files(full_path))
+ elif os.path.isfile(full_path):
+ stat = os.stat(full_path)
+ files.append((full_path, stat.st_mtime))
+ return files
+
+ def _query_mount_point(self, mount_point, query):
+ t = time.time()
+
+ files = self._get_all_files(mount_point)
+ offset = int(query.get('offset', 0))
+ limit = int(query.get('limit', len(files)))
+
+ total_count = len(files)
+ files.sort(lambda a, b: int(b[1] - a[1]))
+ files = files[offset:offset + limit]
+
+ result = []
+ for file_path, timestamp_ in files:
+ metadata = _get_file_metadata(file_path)
+ result.append(metadata)
+
+ logging.debug('_query_mount_point took %f s.' % (time.time() - t))
+
+ return result, total_count
+
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)
+ data_store = _get_datastore()
+ entries, total_count = _get_datastore().find(query, PROPERTIES,
+ byte_arrays=True)
else:
- return _query_mount_point(mount_points[0], query)
+ entries, total_count = self._query_mount_point(mount_points[0],
+ query)
+
+ for entry in entries:
+ entry['mountpoint'] = mount_points[0]
+
+ return entries, total_count
def seek(self, position):
self._position = position
@@ -206,38 +247,6 @@ def _get_file_metadata(path):
'activity_id': '',
'icon-color': client.get_string('/desktop/sugar/user/color')}
-def _get_all_files(dir_path):
- 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))
- elif os.path.isfile(full_path):
- stat = os.stat(full_path)
- files.append((full_path, stat.st_mtime))
- return files
-
-def _query_mount_point(mount_point, query):
- t = time.time()
-
- files = _get_all_files(mount_point)
- offset = int(query.get('offset', 0))
- limit = int(query.get('limit', len(files)))
-
- total_count = len(files)
- files.sort(lambda a, b: int(b[1] - a[1]))
- files = files[offset:offset + limit]
-
- result = []
- for file_path, timestamp in files:
- metadata = _get_file_metadata(file_path)
- metadata['mountpoint'] = mount_point
- result.append(metadata)
-
- logging.debug('_query_mount_point took %f s.' % (time.time() - t))
-
- return result, total_count
-
_datastore = None
def _get_datastore():
global _datastore
@@ -278,8 +287,10 @@ def get_file(object_id):
"""Returns the file for an object
"""
if os.path.exists(object_id):
+ logging.debug('get_file asked for file with path %r' % object_id)
return object_id
else:
+ logging.debug('get_file asked for entry with id %r' % object_id)
return _get_datastore().get_filename(object_id)
def get_unique_values(key):
@@ -307,11 +318,13 @@ def copy(metadata, mount_point):
metadata['mountpoint'] = mount_point
del metadata['uid']
+ #TODO: should we transfer ownership?
return write(metadata, file_path)
def write(metadata, file_path='', update_mtime=True):
"""Creates or updates an entry for that id
"""
+ logging.debug('model.write %r %r %r' % (metadata, file_path, update_mtime))
if update_mtime:
metadata['mtime'] = datetime.now().isoformat()
metadata['timestamp'] = int(time.time())
@@ -327,10 +340,22 @@ def write(metadata, file_path='', update_mtime=True):
file_path,
True)
else:
- pass
+ if not os.path.exists(file_path):
+ raise ValueError('Entries without a file cannot be copied to '
+ 'removable devices')
+ file_name = _get_file_name(metadata['title'], metadata['mime_type'])
+ destination_path = os.path.join(metadata['mountpoint'], file_name)
+ shutil.copy(file_path, destination_path)
+ object_id = destination_path
return object_id
+def _get_file_name(title, mime_type):
+ # TODO: sanitize title for common filesystems
+ # TODO: make as robust as possible, this function should never fail.
+ # TODO: don't append the same extension again and again
+ return '%s.%s' % (title, mime.get_primary_extension(mime_type))
+
created = dispatch.Signal()
updated = dispatch.Signal()
deleted = dispatch.Signal()
diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py
index 50b9aa7..bb27ab7 100644
--- a/src/jarabe/journal/volumestoolbar.py
+++ b/src/jarabe/journal/volumestoolbar.py
@@ -18,12 +18,14 @@ import logging
from gettext import gettext as _
import gobject
+import gio
import gtk
+import gconf
from sugar.graphics.radiotoolbutton import RadioToolButton
from sugar.graphics.palette import Palette
+from sugar.graphics.xocolor import XoColor
-from jarabe.model import volume
from jarabe.journal import model
class VolumesToolbar(gtk.Toolbar):
@@ -32,108 +34,108 @@ class VolumesToolbar(gtk.Toolbar):
__gsignals__ = {
'volume-changed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
- ([object]))
+ ([str]))
}
def __init__(self):
gtk.Toolbar.__init__(self)
- self._volume_buttons = []
- self._volume_added_hid = None
- self._volume_removed_hid = None
+ self._mount_added_hid = None
+ self._mount_removed_hid = None
+
+ button = JournalButton()
+ button.set_palette(Palette(_('Journal')))
+ button.connect('toggled', self._button_toggled_cb)
+ self.insert(button, 0)
+ button.show()
+ self._volume_buttons = [button]
self.connect('destroy', self.__destroy_cb)
gobject.idle_add(self._set_up_volumes)
def __destroy_cb(self, widget):
- volumes_manager = volume.get_volumes_manager()
- volumes_manager.disconnect(self._volume_added_hid)
- volumes_manager.disconnect(self._volume_removed_hid)
+ volume_monitor = gio.volume_monitor_get()
+ volume_monitor.disconnect(self._mount_added_hid)
+ volume_monitor.disconnect(self._mount_removed_hid)
def _set_up_volumes(self):
- volumes_manager = volume.get_volumes_manager()
- self._volume_added_hid = \
- volumes_manager.connect('volume-added', self._volume_added_cb)
- self._volume_removed_hid = \
- volumes_manager.connect('volume-removed',
- self._volume_removed_cb)
-
- for vol in volumes_manager.get_volumes():
- self._add_button(vol)
+ volume_monitor = gio.volume_monitor_get()
+ self._mount_added_hid = \
+ volume_monitor.connect('mount-added', self.__mount_added_cb)
+ self._mount_removed_hid = \
+ volume_monitor.connect('mount-removed', self.__mount_removed_cb)
- def _volume_added_cb(self, volumes_manager, vol):
- self._add_button(vol)
+ for mount in volume_monitor.get_mounts():
+ self._add_button(mount)
- def _volume_removed_cb(self, volumes_manager, vol):
- self._remove_button(vol)
+ def __mount_added_cb(self, volume_monitor, mount):
+ self._add_button(mount)
- def _add_button(self, vol):
- logging.debug('VolumeToolbar._add_button: %r' % vol.name)
+ def __mount_removed_cb(self, volume_monitor, mount):
+ self._remove_button(mount)
- if self._volume_buttons:
- group = self._volume_buttons[0]
- else:
- group = None
+ def _add_button(self, mount):
+ logging.debug('VolumeToolbar._add_button: %r' % mount.get_name())
- palette = Palette(vol.name)
+ palette = Palette(mount.get_name())
- button = VolumeButton(vol, group)
+ button = VolumeButton(mount)
+ button.props.group = self._volume_buttons[0]
button.set_palette(palette)
- button.connect('toggled', self._button_toggled_cb, vol)
- if self._volume_buttons:
- position = self.get_item_index(self._volume_buttons[-1]) + 1
- else:
- position = 0
+ button.connect('toggled', self._button_toggled_cb)
+ position = self.get_item_index(self._volume_buttons[-1]) + 1
self.insert(button, position)
button.show()
self._volume_buttons.append(button)
- if vol.can_eject:
+ if mount.can_unmount():
menu_item = gtk.MenuItem(_('Unmount'))
- menu_item.connect('activate', self._unmount_activated_cb, vol)
+ menu_item.connect('activate', self._unmount_activated_cb, mount)
palette.menu.append(menu_item)
menu_item.show()
if len(self.get_children()) > 1:
self.show()
- def _button_toggled_cb(self, button, vol):
+ def _button_toggled_cb(self, button):
if button.props.active:
- self.emit('volume-changed', vol)
+ self.emit('volume-changed', button.mount_point)
- def _unmount_activated_cb(self, menu_item, vol):
- logging.debug('VolumesToolbar._unmount_activated_cb: %r', vol.udi)
- vol.unmount()
+ def _unmount_activated_cb(self, menu_item, mount):
+ logging.debug('VolumesToolbar._unmount_activated_cb: %r', mount)
+ mount.unmount(self.__unmount_cb)
- def _remove_button(self, vol):
- for button in self.get_children():
- if button.volume.udi == vol.udi:
- self._volume_buttons.remove(button)
- self.remove(button)
- self.get_children()[0].props.active = True
-
- 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):
+ def __unmount_cb(self, source, result):
+ logging.debug('__unmount_cb %r %r' % (source, result))
+
+ def _get_button_for_mount(self, mount):
+ mount_point = mount.get_root().get_path()
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):
+ if button.mount_point == mount_point:
+ return button
+ logging.error('Couldnt find button with mount_point %r' % mount_point)
+ return None
+
+ def _remove_button(self, mount):
+ button = self._get_button_for_mount(mount)
+ self._volume_buttons.remove(button)
+ self.remove(button)
+ self.get_children()[0].props.active = True
+
+ if len(self.get_children()) < 2:
+ self.hide()
+
+ def set_active_volume(self, mount):
+ button = self._get_button_for_mount(mount)
+ button.props.active = True
+
+class BaseButton(RadioToolButton):
+ def __init__(self, mount_point):
RadioToolButton.__init__(self)
- self.props.named_icon = vol.icon_name
- self.props.xo_color = vol.icon_color
- self.props.group = group
- self.volume = vol
+ self.mount_point = mount_point
+
self.drag_dest_set(gtk.DEST_DEFAULT_ALL,
[('journal-object-id', 0, 0)],
gtk.gdk.ACTION_COPY)
@@ -143,5 +145,28 @@ class VolumeButton(RadioToolButton):
info, timestamp):
object_id = selection_data.data
metadata = model.get(object_id)
- model.copy(metadata, self.volume.mount_point)
+ model.copy(metadata, self.mount_point)
+
+class VolumeButton(BaseButton):
+ def __init__(self, mount):
+ mount_point = mount.get_root().get_path()
+ BaseButton.__init__(self, mount_point)
+
+ # TODO: fallback to the more generic icons when needed
+ self.props.named_icon = mount.get_icon().props.names[0]
+
+ # TODO: retrieve the colors from the owner of the device
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ self.props.xo_color = color
+
+class JournalButton(BaseButton):
+ def __init__(self):
+ BaseButton.__init__(self, mount_point='/')
+
+ self.props.named_icon = 'computer-xo'
+
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ self.props.xo_color = color
diff --git a/src/jarabe/model/Makefile.am b/src/jarabe/model/Makefile.am
index 71ba988..91d9a3e 100644
--- a/src/jarabe/model/Makefile.am
+++ b/src/jarabe/model/Makefile.am
@@ -12,5 +12,4 @@ sugar_PYTHON = \
shell.py \
screen.py \
session.py \
- sound.py \
- volume.py
+ sound.py
diff --git a/src/jarabe/model/volume.py b/src/jarabe/model/volume.py
deleted file mode 100644
index 0d3d9a5..0000000
--- a/src/jarabe/model/volume.py
+++ /dev/null
@@ -1,253 +0,0 @@
-# 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 gobject
-import dbus
-
-from sugar import profile
-
-HAL_SERVICE_NAME = 'org.freedesktop.Hal'
-HAL_MANAGER_PATH = '/org/freedesktop/Hal/Manager'
-HAL_MANAGER_IFACE = 'org.freedesktop.Hal.Manager'
-HAL_DEVICE_IFACE = 'org.freedesktop.Hal.Device'
-HAL_VOLUME_IFACE = 'org.freedesktop.Hal.Device.Volume'
-
-MOUNT_OPTION_UID = 500
-MOUNT_OPTION_UMASK = 000
-
-_volumes_manager = None
-
-class VolumesManager(gobject.GObject):
-
- __gtype_name__ = 'VolumesManager'
-
- __gsignals__ = {
- 'volume-added': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([object])),
- 'volume-removed': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([object]))
- }
-
- def __init__(self):
- gobject.GObject.__init__(self)
-
- self._volumes = {}
-
- bus = dbus.SystemBus()
- proxy = bus.get_object(HAL_SERVICE_NAME, HAL_MANAGER_PATH)
- self._hal_manager = dbus.Interface(proxy, HAL_MANAGER_IFACE)
- self._hal_manager.connect_to_signal('DeviceAdded',
- self._hal_device_added_cb)
-
- for udi in self._hal_manager.FindDeviceByCapability('volume'):
- if self._is_device_relevant(udi):
- try:
- self._add_hal_device(udi)
- except Exception, e:
- logging.error('Exception when mounting device %r: %r' % \
- (udi, e))
-
- def get_volumes(self):
- return self._volumes.values()
-
- def _hal_device_added_cb(self, udi):
- bus = dbus.SystemBus()
- device_object = bus.get_object(HAL_SERVICE_NAME, udi)
- device = dbus.Interface(device_object, HAL_DEVICE_IFACE)
- if device.QueryCapability('volume'):
- logging.debug('VolumesManager._hal_device_added_cb: %r', udi)
- if self._is_device_relevant(udi):
- self._add_hal_device(udi)
-
- def _is_device_relevant(self, udi):
- bus = dbus.SystemBus()
- device_object = bus.get_object(HAL_SERVICE_NAME, udi)
- device = dbus.Interface(device_object, HAL_DEVICE_IFACE)
-
- # Ignore volumes without a filesystem.
- if device.GetProperty('volume.fsusage') != 'filesystem':
- return False
-
- return True
-
- def _add_hal_device(self, udi):
- logging.debug('VolumeToolbar._add_hal_device: %r' % udi)
-
- bus = dbus.SystemBus()
- device_object = bus.get_object(HAL_SERVICE_NAME, udi)
- device = dbus.Interface(device_object, HAL_DEVICE_IFACE)
-
- # listen to mount/unmount
- device.connect_to_signal('PropertyModified',
- lambda *args: self._hal_device_property_modified_cb(udi, *args))
-
- bus.add_signal_receiver(self._hal_device_removed_cb,
- 'DeviceRemoved',
- HAL_MANAGER_IFACE, HAL_SERVICE_NAME,
- HAL_MANAGER_PATH, arg0=udi)
-
- if device.GetProperty('volume.is_mounted'):
- self._add_volume(udi)
- return
-
- label = device.GetProperty('volume.label')
- fs_type = device.GetProperty('volume.fstype')
- valid_options = device.GetProperty('volume.mount.valid_options')
- options = []
-
- if 'uid=' in valid_options:
- options.append('uid=%i' % MOUNT_OPTION_UID)
-
- if 'umask=' in valid_options:
- options.append('umask=%i' % MOUNT_OPTION_UMASK)
-
- if 'noatime' in valid_options:
- options.append('noatime')
-
- if 'utf8' in valid_options:
- options.append('utf8')
-
- if 'iocharset=' in valid_options:
- options.append('iocharset=utf8')
-
- mount_point = label
- if not mount_point:
- mount_point = device.GetProperty('volume.uuid')
-
- volume = dbus.Interface(device_object, HAL_VOLUME_IFACE)
-
- # Try 100 times to get a mount point
- mounted = False
- i = 0
- while not mounted:
- try:
- if i > 0:
- volume.Mount('%s_%d' % (mount_point, i), fs_type, options)
- else:
- volume.Mount(mount_point, fs_type, options)
- mounted = True
- except dbus.DBusException, e:
- s = 'org.freedesktop.Hal.Device.Volume.MountPointNotAvailable'
- if i < 100 and e.get_dbus_name() == s:
- i += 1
- else:
- raise
-
- def _hal_device_property_modified_cb(self, udi, count, changes):
- if 'volume.is_mounted' in [change[0] for change in changes]:
- logging.debug('VolumesManager._hal_device_property_modified: %r' % \
- (udi))
- bus = dbus.SystemBus()
- #proxy = bus.get_object(HAL_SERVICE_NAME, HAL_MANAGER_PATH)
- #hal_manager = dbus.Interface(proxy, HAL_MANAGER_IFACE)
- # TODO: Why this doesn't work?
- #if not hal_manager.DeviceExists(udi):
- # return
-
- proxy = bus.get_object(HAL_SERVICE_NAME, udi)
- device = dbus.Interface(proxy, HAL_DEVICE_IFACE)
- try:
- is_mounted = device.GetProperty('volume.is_mounted')
- except dbus.DBusException, e:
- logging.debug('e: %s' % e)
- return
-
- if is_mounted:
- if udi not in self._volumes:
- self._add_volume(udi)
- else:
- if udi in self._volumes:
- 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)
-
- volume_name = device.GetProperty('volume.label')
- if not volume_name:
- volume_name = device.GetProperty('volume.uuid')
-
- 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,
- can_eject)
- self._volumes[udi] = volume
-
- logging.debug('mounted volume %s' % udi)
- self.emit('volume-added', volume)
-
- def _remove_volume(self, udi):
- volume = self._volumes[udi]
- del self._volumes[udi]
- self.emit('volume-removed', volume)
-
- def _hal_device_removed_cb(self, udi):
- logging.debug('VolumesManager._hal_device_removed_cb: %r', udi)
- if udi in self._volumes:
- self._remove_volume(udi)
-
- def _get_icon_for_volume(self, device):
- bus = dbus.SystemBus()
- storage_udi = device.GetProperty('block.storage_device')
- obj = bus.get_object(HAL_SERVICE_NAME, storage_udi)
- storage_device = dbus.Interface(obj, HAL_DEVICE_IFACE)
-
- 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,
- 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)
- bus = dbus.SystemBus()
- device_object = bus.get_object(HAL_SERVICE_NAME, self.udi)
- volume = dbus.Interface(device_object, HAL_VOLUME_IFACE)
- volume.Unmount([])
-
-def get_volumes_manager():
- global _volumes_manager
- if _volumes_manager is None:
- _volumes_manager = VolumesManager()
- return _volumes_manager
-