From df98fd8a1000e93d812d090e42b67c4dbad2e6b9 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Mon, 15 Dec 2008 16:34:31 +0000 Subject: First go at adding file transfer to the journal --- (limited to 'src/jarabe/frame/activitiestray.py') diff --git a/src/jarabe/frame/activitiestray.py b/src/jarabe/frame/activitiestray.py index 8821236..1ff044a 100644 --- a/src/jarabe/frame/activitiestray.py +++ b/src/jarabe/frame/activitiestray.py @@ -17,8 +17,11 @@ import logging from gettext import gettext as _ -import gconf +import tempfile +import os +import gconf +import dbus import gtk from sugar.graphics import style @@ -31,17 +34,23 @@ from sugar.graphics.palette import Palette, WidgetInvoker from sugar.graphics.menuitem import MenuItem from sugar.activity.activityhandle import ActivityHandle from sugar.activity import activityfactory +from sugar import mime from jarabe.model import shell from jarabe.model import neighborhood from jarabe.model import owner from jarabe.model import bundleregistry +from jarabe.model import filetransfer from jarabe.view.palettes import JournalPalette, CurrentActivityPalette from jarabe.view.pulsingicon import PulsingIcon from jarabe.frame.frameinvoker import FrameWidgetInvoker from jarabe.frame.notification import NotificationIcon import jarabe.frame +DS_DBUS_SERVICE = "org.laptop.sugar.DataStore" +DS_DBUS_INTERFACE = "org.laptop.sugar.DataStore" +DS_DBUS_PATH = "/org/laptop/sugar/DataStore" + class ActivityButton(RadioToolButton): def __init__(self, home_activity, group): RadioToolButton.__init__(self, group=group) @@ -83,12 +92,15 @@ class ActivityButton(RadioToolButton): self._icon.props.pulsing = False home_activity.disconnect(self._notify_launching_hid) - class BaseInviteButton(ToolButton): def __init__(self, invite): ToolButton.__init__(self) self._invite = invite + self._icon = Icon() + self.set_icon_widget(self._icon) + self._icon.show() + self.connect('clicked', self.__clicked_cb) self.connect('destroy', self.__destroy_cb) self._notif_icon = NotificationIcon() @@ -127,8 +139,6 @@ class ActivityInviteButton(BaseInviteButton): self._icon.props.file = activity_model.get_icon_name() else: self._icon.props.icon_name = 'image-missing' - self.set_icon_widget(self._icon) - self._icon.show() palette = ActivityInvitePalette(invite) palette.props.invoker = FrameWidgetInvoker(self) @@ -177,8 +187,6 @@ class PrivateInviteButton(BaseInviteButton): self._icon.props.file = self._bundle.get_icon() else: self._icon.props.icon_name = 'image-missing' - self.set_icon_widget(self._icon) - self._icon.show() palette = PrivateInvitePalette(invite) palette.props.invoker = FrameWidgetInvoker(self) @@ -310,6 +318,8 @@ class ActivitiesTray(HTray): self._invites.connect('invite-added', self.__invite_added_cb) self._invites.connect('invite-removed', self.__invite_removed_cb) + filetransfer.new_file_transfer.connect(self.__new_file_transfer_cb) + def __activity_added_cb(self, home_model, home_activity): logging.debug('__activity_added_cb: %r' % home_activity) if self.get_children(): @@ -370,7 +380,7 @@ class ActivitiesTray(HTray): self._invites.remove_invite(invite) else: self._invites.remove_private_invite(invite) - + def __invite_added_cb(self, invites, invite): self._add_invite(invite) @@ -398,3 +408,403 @@ class ActivitiesTray(HTray): self._invite_to_item[invite].destroy() del self._invite_to_item[invite] + def __new_file_transfer_cb(self, **kwargs): + file_transfer = kwargs['file_transfer'] + logging.debug('__new_file_transfer_cb %r' % file_transfer) + + if isinstance(file_transfer, filetransfer.IncomingFileTransfer): + button = IncomingTransferButton(file_transfer) + elif isinstance(file_transfer, filetransfer.OutgoingFileTransfer): + button = OutgoingTransferButton(file_transfer) + + self.add_item(button) + button.show() + +class BaseTransferButton(ToolButton): + """Button with a notification attached + """ + def __init__(self): + ToolButton.__init__(self) + icon = Icon() + self.props.icon_widget = icon + icon.show() + + self.notif_icon = NotificationIcon() + self.notif_icon.connect('button-release-event', + self.__button_release_event_cb) + + def __button_release_event_cb(self, icon, event): + if self.notif_icon is not None: + frame = jarabe.frame.get_view() + frame.remove_notification(self.notif_icon) + self.notif_icon = None + +class IncomingTransferButton(BaseTransferButton): + """UI element representing an ongoing incoming file transfer + """ + def __init__(self, file_transfer): + BaseTransferButton.__init__(self) + + self._object_id = None + self._metadata = {} + self._file_transfer = file_transfer + self._file_transfer.connect('notify::state', self.__notify_state_cb) + self._file_transfer.connect('notify::transferred-bytes', + self.__notify_transferred_bytes_cb) + + icon_name = mime.get_mime_icon(file_transfer.mime_type) + icon_theme = gtk.icon_theme_get_default() + info = icon_theme.lookup_icon(icon_name, gtk.ICON_SIZE_LARGE_TOOLBAR, 0) + if not info: + # display standard icon when icon for mime type is not found + icon_name = 'application-octet-stream' + + icon_color = XoColor(file_transfer.buddy.props.color) + + self.props.icon_widget.props.icon_name = icon_name + self.props.icon_widget.props.xo_color = icon_color + + self.notif_icon.props.icon_name = icon_name + self.notif_icon.props.xo_color = icon_color + + frame = jarabe.frame.get_view() + frame.add_notification(self.notif_icon, + gtk.CORNER_TOP_LEFT) + + def create_palette(self): + palette = IncomingTransferPalette(self._file_transfer) + palette.props.invoker = FrameWidgetInvoker(self) + palette.set_group_id('frame') + return palette + + def __notify_state_cb(self, file_transfer, pspec): + if file_transfer.props.state == filetransfer.FT_STATE_OPEN: + logging.debug('__notify_state_cb OPEN') + self._metadata['title'] = file_transfer.title + self._metadata['description'] = file_transfer.description + self._metadata['progress'] = '0' + self._metadata['keep'] = '0' + self._metadata['buddies'] = '' + self._metadata['preview'] = '' + self._metadata['icon-color'] = file_transfer.buddy.props.color + self._metadata['mime_type'] = file_transfer.mime_type + + datastore = self._get_datastore() + file_path = '' + transfer_ownership = True + self._object_id = datastore.create(self._metadata, file_path, + transfer_ownership) + + elif file_transfer.props.state == filetransfer.FT_STATE_COMPLETED: + logging.debug('__notify_state_cb COMPLETED') + self._metadata['progress'] = '100' + + datastore = self._get_datastore() + file_path = file_transfer.destination_path + transfer_ownership = True + datastore.update(self._object_id, self._metadata, file_path, + transfer_ownership, + reply_handler=self.__reply_handler_cb, + error_handler=self.__error_handler_cb) + + elif file_transfer.props.state == filetransfer.FT_STATE_CANCELLED: + logging.debug('__notify_state_cb CANCELLED') + if self._object_id is not None: + datastore.delete(self._object_id, + reply_handler=self.__reply_handler_cb, + error_handler=self.__error_handler_cb) + self._object_id = None + + def __notify_transferred_bytes_cb(self, file_transfer, pspec): + progress = file_transfer.props.transferred_bytes / \ + file_transfer.file_size + self._metadata['progress'] = str(progress * 100) + + datastore = self._get_datastore() + file_path = '' + transfer_ownership = True + datastore.update(self._object_id, self._metadata, file_path, + transfer_ownership, + reply_handler=self.__reply_handler_cb, + error_handler=self.__error_handler_cb) + + def _get_datastore(self): + bus = dbus.SessionBus() + remote_object = bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH) + return dbus.Interface(remote_object, DS_DBUS_INTERFACE) + + def __reply_handler_cb(self): + logging.debug('__reply_handler_cb %r' % self._object_id) + + def __error_handler_cb(self, error): + logging.debug('__error_handler_cb %r %s' % (self._object_id, error)) + +class OutgoingTransferButton(BaseTransferButton): + """UI element representing an ongoing outgoing file transfer + """ + def __init__(self, file_transfer): + BaseTransferButton.__init__(self) + + self._file_transfer = file_transfer + + icon_name = mime.get_mime_icon(file_transfer.mime_type) + icon_theme = gtk.icon_theme_get_default() + info = icon_theme.lookup_icon(icon_name, gtk.ICON_SIZE_LARGE_TOOLBAR, 0) + if not info: + # display standard icon when icon for mime type is not found + icon_name = 'application-octet-stream' + + client = gconf.client_get_default() + icon_color = XoColor(client.get_string("/desktop/sugar/user/color")) + + self.props.icon_widget.props.icon_name = icon_name + self.props.icon_widget.props.xo_color = icon_color + + self.notif_icon.props.icon_name = icon_name + self.notif_icon.props.xo_color = icon_color + + frame = jarabe.frame.get_view() + frame.add_notification(self.notif_icon, + gtk.CORNER_TOP_LEFT) + + def create_palette(self): + palette = OutgoingTransferPalette(self._file_transfer) + palette.props.invoker = FrameWidgetInvoker(self) + palette.set_group_id('frame') + return palette + +class BaseTransferPalette(Palette): + """Base palette class for frame or notification icon for file transfers + """ + def __init__(self, file_transfer): + Palette.__init__(self, file_transfer.title) + + self.file_transfer = file_transfer + + self.progress_bar = None + self.progress_label = None + self._notify_transferred_bytes_handler = None + + self.connect('popup', self.__popup_cb) + self.connect('popdown', self.__popdown_cb) + + def __popup_cb(self, palette): + self.update_progress() + self._notify_transferred_bytes_handler = \ + self.file_transfer.connect('notify::transferred_bytes', + self.__notify_transferred_bytes_cb) + + def __popdown_cb(self, palette): + if self._notify_transferred_bytes_handler is not None: + self.file_transfer.disconnect( + self._notify_transferred_bytes_handler) + self._notify_transferred_bytes_handler = None + + def __notify_transferred_bytes_cb(self, file_transfer, pspec): + self.update_progress() + + def _format_size(self, size): + if size < 1024: + return _('%dB') % size + elif size < 1048576: + return _('%dKB') % (size / 1024) + else: + return _('%dMB') % (size / 1048576) + + def update_progress(self): + logging.debug('update_progress: %r' % self.file_transfer.props.transferred_bytes) + if self.progress_bar is None: + return + + self.progress_bar.props.fraction = \ + self.file_transfer.props.transferred_bytes / \ + float(self.file_transfer.file_size) + logging.debug('update_progress: %r' % self.progress_bar.props.fraction) + + transferred = self._format_size( + self.file_transfer.props.transferred_bytes) + total = self._format_size(self.file_transfer.file_size) + self.progress_label.props.label = _('%s of %s') % (transferred, total) + +class IncomingTransferPalette(BaseTransferPalette): + """Palette for frame or notification icon for incoming file transfers + """ + def __init__(self, file_transfer): + BaseTransferPalette.__init__(self, file_transfer) + + self.file_transfer.connect('notify::state', self.__notify_state_cb) + + nick = self.file_transfer.buddy.props.nick + self.props.secondary_text = _('Transfer from %r') % nick + + self._update() + + def __notify_state_cb(self, file_transfer, pspec): + self._update() + + def _update(self): + logging.debug('_update state: %r' % self.file_transfer.props.state) + if self.file_transfer.props.state == filetransfer.FT_STATE_PENDING: + menu_item = MenuItem(_('Accept'), icon_name='dialog-ok') + menu_item.connect('activate', self.__accept_activate_cb) + self.menu.append(menu_item) + menu_item.show() + + menu_item = MenuItem(_('Decline'), icon_name='dialog-cancel') + menu_item.connect('activate', self.__decline_activate_cb) + self.menu.append(menu_item) + menu_item.show() + + vbox = gtk.VBox() + self.set_content(vbox) + vbox.show() + + if self.file_transfer.description: + label = gtk.Label(self.file_transfer.description) + vbox.add(label) + label.show() + + mime_type = self.file_transfer.mime_type + type_description = mime.get_mime_description(mime_type) + + size = self._format_size(self.file_transfer.file_size) + label = gtk.Label(_('%s (%s)') % (size, type_description)) + vbox.add(label) + label.show() + + elif self.file_transfer.props.state in \ + [filetransfer.FT_STATE_ACCEPTED, filetransfer.FT_STATE_OPEN]: + + for item in self.menu.get_children(): + self.menu.remove(item) + + menu_item = MenuItem(_('Cancel'), icon_name='dialog-cancel') + menu_item.connect('activate', self.__cancel_activate_cb) + self.menu.append(menu_item) + menu_item.show() + + vbox = gtk.VBox() + self.set_content(vbox) + vbox.show() + + self.progress_bar = gtk.ProgressBar() + vbox.add(self.progress_bar) + self.progress_bar.show() + + self.progress_label = gtk.Label('') + vbox.add(self.progress_label) + self.progress_label.show() + + self.update_progress() + + elif self.file_transfer.props.state == filetransfer.FT_STATE_COMPLETED: + # TODO: What to do here? + self.update_progress() + elif self.file_transfer.props.state == filetransfer.FT_STATE_CANCELLED: + # TODO: What to do here? + self.update_progress() + + def __accept_activate_cb(self, menu_item): + #TODO: figure out the best place to get rid of that temp file + extension = mime.get_primary_extension(self.file_transfer.mime_type) + fd, file_path = tempfile.mkstemp(suffix=extension, + prefix=self._sanitize(self.file_transfer.title)) + os.close(fd) + os.unlink(file_path) + + self.file_transfer.accept(file_path) + + def _sanitize(self, file_name): + file_name = file_name.replace('/', '_') + file_name = file_name.replace('.', '_') + file_name = file_name.replace('?', '_') + return file_name + + def __decline_activate_cb(self, menu_item): + self.file_transfer.decline() + + def __cancel_activate_cb(self, menu_item): + self.file_transfer.cancel() + +class OutgoingTransferPalette(Palette): + """Palette for frame or notification icon for outgoing file transfers + """ + def __init__(self, file_transfer): + BaseTransferPalette.__init__(self, file_transfer) + + self.file_transfer.connect('notify::state', self.__notify_state_cb) + + nick = file_transfer.buddy.props.nick + self.props.secondary_text = _('Transfer to %r') % nick + + menu_item = MenuItem(_('Cancel'), icon_name='dialog-cancel') + menu_item.connect('activate', self.__cancel_activate_cb) + self.menu.append(menu_item) + menu_item.show() + + self._update() + + def __notify_state_cb(self, file_transfer, pspec): + self._update() + + def _update(self): + logging.debug('_update state: %r' % self.file_transfer.props.state) + if self.file_transfer.props.state == filetransfer.FT_STATE_PENDING: + + menu_item = MenuItem(_('Cancel'), icon_name='dialog-cancel') + menu_item.connect('activate', self.__cancel_activate_cb) + self.menu.append(menu_item) + menu_item.show() + + vbox = gtk.VBox() + self.set_content(vbox) + vbox.show() + + if self.file_transfer.description: + label = gtk.Label(self.file_transfer.description) + vbox.add(label) + label.show() + + mime_type = self.file_transfer.mime_type + type_description = mime.get_mime_description(mime_type) + + size = self._format_size(self.file_transfer.file_size) + label = gtk.Label(_('%s (%s)') % (size, type_description)) + vbox.add(label) + label.show() + + elif self.file_transfer.props.state in \ + [filetransfer.FT_STATE_ACCEPTED, filetransfer.FT_STATE_OPEN]: + + for item in self.menu.get_children(): + self.menu.remove(item) + + menu_item = MenuItem(_('Cancel'), icon_name='dialog-cancel') + menu_item.connect('activate', self.__cancel_activate_cb) + self.menu.append(menu_item) + menu_item.show() + + vbox = gtk.VBox() + self.set_content(vbox) + vbox.show() + + self.progress_bar = gtk.ProgressBar() + vbox.add(self.progress_bar) + self.progress_bar.show() + + self.progress_label = gtk.Label('') + vbox.add(self.progress_label) + self.progress_label.show() + + self.update_progress() + + elif self.file_transfer.props.state == filetransfer.FT_STATE_COMPLETED: + # TODO: What to do here? + self.update_progress() + elif self.file_transfer.props.state == filetransfer.FT_STATE_CANCELLED: + # TODO: What to do here? + self.update_progress() + + def __cancel_activate_cb(self, menu_item): + self.file_transfer.cancel() + -- cgit v0.9.1