def _on_dbus_name_appeared(self, _connection, name, owner): logging.info(f"{name} {owner}") self._manager = Manager() self._handlerids.append(self._manager.connect("transfer-started", self._on_transfer_started)) self._handlerids.append(self._manager.connect("transfer-completed", self._on_transfer_completed)) self._handlerids.append(self._manager.connect('session-removed', self._on_session_removed)) self._register_agent()
def _on_dbus_name_appeared(self, _connection, name, owner): logging.info("%s %s" % (name, owner)) self._manager = Manager() self._manager.connect("transfer-started", self._on_transfer_started) self._manager.connect("transfer-completed", self._on_transfer_completed) self._manager.connect('session-removed', self._on_session_removed) self._register_agent()
def on_load(self) -> None: def on_reset(_action: str) -> None: self._notification = None self._config.reset('shared-path') logging.info('Reset share path') self._config = Config("org.blueman.transfer") share_path, invalid_share_path = self._make_share_path() if invalid_share_path: text = _('Configured directory for incoming files does not exist') secondary_text = _( 'Please make sure that directory "<b>%s</b>" exists or ' 'configure it with blueman-services. Until then the default "%s" will be used' ) self._notification = Notification( text, secondary_text % (self._config["shared-path"], share_path), icon_name='blueman', timeout=30000, actions=[('reset', 'Reset to default')], actions_cb=on_reset) self._notification.show() self._watch = Manager.watch_name_owner(self._on_dbus_name_appeared, self._on_dbus_name_vanished)
class TransferService(AppletPlugin): __author__ = "cschramm" __description__ = _("Provides OBEX file transfer capabilities") __icon__ = "blueman-send-symbolic" _silent_transfers = 0 _normal_transfers = 0 _manager = None _agent = None _watch = None _notification = None _handlerids: List[int] = [] def on_load(self) -> None: def on_reset(_action: str) -> None: self._notification = None self._config.reset('shared-path') logging.info('Reset share path') self._config = Config("org.blueman.transfer") share_path, invalid_share_path = self._make_share_path() if invalid_share_path: text = _('Configured directory for incoming files does not exist') secondary_text = _( 'Please make sure that directory "<b>%s</b>" exists or ' 'configure it with blueman-services. Until then the default "%s" will be used' ) self._notification = Notification( text, secondary_text % (self._config["shared-path"], share_path), icon_name='blueman', timeout=30000, actions=[('reset', 'Reset to default')], actions_cb=on_reset) self._notification.show() self._watch = Manager.watch_name_owner(self._on_dbus_name_appeared, self._on_dbus_name_vanished) def on_unload(self) -> None: if self._watch: Gio.bus_unwatch_name(self._watch) self._unregister_agent() def _make_share_path(self) -> Tuple[str, bool]: config_path = self._config["shared-path"] default_path = GLib.get_user_special_dir( GLib.UserDirectory.DIRECTORY_DOWNLOAD) path = None error = False if config_path == '': path = default_path elif not os.path.isdir(config_path): path = default_path error = True logging.warning('Invalid shared-path %s' % config_path) else: path = config_path if not path: path = os.path.expanduser("~") logging.warning('Failed to get Download dir from XDG') # We used to always store the full path which caused problems if config_path == default_path: logging.info('Reset stored path, identical to default path.') self._config["shared-path"] = '' return path, error def _register_agent(self) -> None: if not self._agent: self._agent = Agent(self.parent) self._agent.register_at_manager() def _unregister_agent(self) -> None: if self._agent: self._agent.unregister_from_manager() self._agent.unregister() self._agent = None def _on_dbus_name_appeared(self, _connection: Gio.DBusConnection, name: str, owner: str) -> None: logging.info(f"{name} {owner}") self._manager = Manager() self._handlerids.append( self._manager.connect("transfer-started", self._on_transfer_started)) self._handlerids.append( self._manager.connect("transfer-completed", self._on_transfer_completed)) self._handlerids.append( self._manager.connect('session-removed', self._on_session_removed)) self._register_agent() def _on_dbus_name_vanished(self, _connection: Gio.DBusConnection, name: str) -> None: logging.info(f"{name} not running or was stopped") if self._manager: for sigid in self._handlerids: self._manager.disconnect(sigid) self._manager = None self._handlerids = [] if self._agent: self._agent.unregister() self._agent = None def _on_transfer_started(self, _manager: Manager, transfer_path: str) -> None: if not self._agent or transfer_path not in self._agent.transfers: # This is not an incoming transfer we authorized return size = self._agent.transfers[transfer_path]['size'] assert size is not None if size > 350000: self._normal_transfers += 1 else: self._silent_transfers += 1 def _add_open(self, n: NotificationType, name: str, path: str) -> None: if n.actions_supported: logging.info("adding action") def on_open(_action: str) -> None: self._notification = None logging.info("open") launch("xdg-open", paths=[path], system=True) n.add_action("open", name, on_open) def _on_transfer_completed(self, _manager: Manager, transfer_path: str, success: bool) -> None: if not self._agent or transfer_path not in self._agent.transfers: logging.info( "This is probably not an incoming transfer we authorized") return attributes = self._agent.transfers[transfer_path] src = attributes['path'] dest_dir, ignored = self._make_share_path() filename = os.path.basename(src) dest = os.path.join(dest_dir, filename) if os.path.exists(dest): now = datetime.now() filename = f"{now.strftime('%Y%m%d%H%M%S')}_{filename}" logging.info(f"Destination file exists, renaming to: {filename}") try: shutil.move(src, dest) except (OSError, PermissionError): logging.error("Failed to move files", exc_info=True) success = False if success: self._notification = Notification( _("File received"), _("File %(0)s from %(1)s successfully received") % { "0": "<b>" + escape(filename) + "</b>", "1": "<b>" + escape(attributes['name']) + "</b>" }, icon_name="blueman") self._add_open(self._notification, _("Open"), dest) self._notification.show() elif not success: n = Notification( _("Transfer failed"), _("Transfer of file %(0)s failed") % { "0": "<b>" + escape(filename) + "</b>", "1": "<b>" + escape(attributes['name']) + "</b>" }, icon_name="blueman") n.show() assert attributes['size'] is not None if attributes['size'] > 350000: self._normal_transfers -= 1 else: self._silent_transfers -= 1 del self._agent.transfers[transfer_path] def _on_session_removed(self, _manager: Manager, _session_path: str) -> None: if self._silent_transfers == 0: return share_path, ignored = self._make_share_path() if self._normal_transfers == 0: self._notification = Notification( _("Files received"), ngettext("Received %(files)d file in the background", "Received %(files)d files in the background", self._silent_transfers) % {"files": self._silent_transfers}, icon_name="blueman") self._add_open(self._notification, _("Open Location"), share_path) self._notification.show() else: self._notification = Notification( _("Files received"), ngettext("Received %(files)d more file in the background", "Received %(files)d more files in the background", self._silent_transfers) % {"files": self._silent_transfers}, icon_name="blueman") self._add_open(self._notification, _("Open Location"), share_path) self._notification.show()
def __init__(self, device, adapter_path, files): super().__init__( title=_("Bluetooth File Transfer"), name="BluemanSendTo", icon_name="blueman", border_width=5, default_width=400, window_position=Gtk.WindowPosition.CENTER, type_hint=Gdk.WindowTypeHint.DIALOG ) self.b_cancel = self.add_button("_Stop", Gtk.ResponseType.CLOSE) self.b_cancel.props.receives_default = True self.b_cancel.props.use_underline = True self.b_cancel.connect("clicked", self.on_cancel) self.Builder = Gtk.Builder(translation_domain="blueman") bind_textdomain_codeset("blueman", "UTF-8") self.Builder.add_from_file(UI_PATH + "/send-dialog.ui") grid = self.Builder.get_object("sendto") content_area = self.get_content_area() content_area.add(grid) self.l_dest = self.Builder.get_object("l_dest") self.l_file = self.Builder.get_object("l_file") self.pb = self.Builder.get_object("pb") self.pb.props.text = _("Connecting") self.device = device self.adapter = Adapter(adapter_path) self.manager = Manager() self.files: List[Gio.File] = [] self.num_files = 0 self.object_push = None self.transfer = None self.total_bytes = 0 self.total_transferred = 0 self._last_bytes = 0 self._last_update = 0.0 self.error_dialog = None self.cancelling = False # bytes transferred on a current transfer self.transferred = 0 self.speed = SpeedCalc(6) for file_name in files: parsed_file = Gio.File.parse_name(file_name) if not parsed_file.query_exists(): logging.info("Skipping non existing file %s" % parsed_file.get_path()) continue file_info = parsed_file.query_info("standard::*", Gio.FileQueryInfoFlags.NONE) if file_info.get_file_type() == Gio.FileType.DIRECTORY: logging.info("Skipping directory %s" % parsed_file.get_path()) continue self.files.append(parsed_file) self.num_files += 1 self.total_bytes += file_info.get_size() if len(self.files) == 0: self.emit("result", False) try: self.client = Client() self.manager.connect_signal('session-added', self.on_session_added) self.manager.connect_signal('session-removed', self.on_session_removed) except GLib.Error as e: if 'StartServiceByName' in e.message: logging.debug(e.message) d = ErrorDialog(_("obexd not available"), _("Failed to autostart obex service. Make sure the obex " "daemon is running"), parent=self.get_toplevel()) d.run() d.destroy() self.emit("result", False) else: # Fail on anything else raise self.l_file.props.label = self.files[-1].get_basename() self.client.connect('session-failed', self.on_session_failed) logging.info("Sending to %s" % device['Address']) self.l_dest.props.label = device['Alias'] # Stop discovery if discovering and let adapter settle for a second if self.adapter["Discovering"]: self.adapter.stop_discovery() time.sleep(1) self.create_session() self.show()
class Sender(Gtk.Dialog): __gsignals__: GSignals = { 'result': (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_BOOLEAN,)), } def __init__(self, device, adapter_path, files): super().__init__( title=_("Bluetooth File Transfer"), name="BluemanSendTo", icon_name="blueman", border_width=5, default_width=400, window_position=Gtk.WindowPosition.CENTER, type_hint=Gdk.WindowTypeHint.DIALOG ) self.b_cancel = self.add_button("_Stop", Gtk.ResponseType.CLOSE) self.b_cancel.props.receives_default = True self.b_cancel.props.use_underline = True self.b_cancel.connect("clicked", self.on_cancel) self.Builder = Gtk.Builder(translation_domain="blueman") bind_textdomain_codeset("blueman", "UTF-8") self.Builder.add_from_file(UI_PATH + "/send-dialog.ui") grid = self.Builder.get_object("sendto") content_area = self.get_content_area() content_area.add(grid) self.l_dest = self.Builder.get_object("l_dest") self.l_file = self.Builder.get_object("l_file") self.pb = self.Builder.get_object("pb") self.pb.props.text = _("Connecting") self.device = device self.adapter = Adapter(adapter_path) self.manager = Manager() self.files: List[Gio.File] = [] self.num_files = 0 self.object_push = None self.transfer = None self.total_bytes = 0 self.total_transferred = 0 self._last_bytes = 0 self._last_update = 0.0 self.error_dialog = None self.cancelling = False # bytes transferred on a current transfer self.transferred = 0 self.speed = SpeedCalc(6) for file_name in files: parsed_file = Gio.File.parse_name(file_name) if not parsed_file.query_exists(): logging.info("Skipping non existing file %s" % parsed_file.get_path()) continue file_info = parsed_file.query_info("standard::*", Gio.FileQueryInfoFlags.NONE) if file_info.get_file_type() == Gio.FileType.DIRECTORY: logging.info("Skipping directory %s" % parsed_file.get_path()) continue self.files.append(parsed_file) self.num_files += 1 self.total_bytes += file_info.get_size() if len(self.files) == 0: self.emit("result", False) try: self.client = Client() self.manager.connect_signal('session-added', self.on_session_added) self.manager.connect_signal('session-removed', self.on_session_removed) except GLib.Error as e: if 'StartServiceByName' in e.message: logging.debug(e.message) d = ErrorDialog(_("obexd not available"), _("Failed to autostart obex service. Make sure the obex " "daemon is running"), parent=self.get_toplevel()) d.run() d.destroy() self.emit("result", False) else: # Fail on anything else raise self.l_file.props.label = self.files[-1].get_basename() self.client.connect('session-failed', self.on_session_failed) logging.info("Sending to %s" % device['Address']) self.l_dest.props.label = device['Alias'] # Stop discovery if discovering and let adapter settle for a second if self.adapter["Discovering"]: self.adapter.stop_discovery() time.sleep(1) self.create_session() self.show() def create_session(self): self.client.create_session(self.device['Address'], self.adapter["Address"]) def on_cancel(self, button): self.pb.props.text = _("Cancelling") if button: button.props.sensitive = False if self.object_push: self.client.remove_session(self.object_push.get_session_path()) self.emit("result", False) def on_transfer_started(self, _object_push, transfer_path, filename): if self.total_transferred == 0: self.pb.props.text = _("Sending File") + (" %(0)s/%(1)s (%(2).2f %(3)s/s) " + _("ETA:") + " %(4)s") % { "1": self.num_files, "0": (self.num_files - len(self.files) + 1), "2": 0.0, "3": "B/s", "4": "∞"} self.l_file.props.label = filename self._last_bytes = 0 self.transferred = 0 self.transfer = Transfer(transfer_path) self.transfer.connect("error", self.on_transfer_error) self.transfer.connect("progress", self.on_transfer_progress) self.transfer.connect("completed", self.on_transfer_completed) def on_transfer_failed(self, _object_push, error): self.on_transfer_error(None, str(error)) def on_transfer_progress(self, _transfer, progress): self.transferred = progress if self._last_bytes == 0: self.total_transferred += progress else: self.total_transferred += (progress - self._last_bytes) self._last_bytes = progress tm = time.time() if tm - self._last_update > 0.5: spd = self.speed.calc(self.total_transferred) (size, units) = format_bytes(spd) try: x = ((self.total_bytes - self.total_transferred) / spd) + 1 if x > 60: x /= 60 eta = ngettext("%.0f Minute", "%.0f Minutes", round(x)) % x else: eta = ngettext("%.0f Second", "%.0f Seconds", round(x)) % x except ZeroDivisionError: eta = "∞" self.pb.props.text = _("Sending File") + (" %(0)s/%(1)s (%(2).2f %(3)s/s) " + _("ETA:") + " %(4)s") % { "1": self.num_files, "0": (self.num_files - len(self.files) + 1), "2": size, "3": units, "4": eta} self._last_update = tm self.pb.props.fraction = float(self.total_transferred) / self.total_bytes def on_transfer_completed(self, _transfer): del self.files[-1] self.transfer = None self.process_queue() def process_queue(self): if len(self.files) > 0: self.send_file(self.files[-1].get_path()) else: self.emit("result", True) def send_file(self, file_path): logging.info(file_path) if self.object_push: self.object_push.send_file(file_path) def on_transfer_error(self, _transfer, msg=""): if not self.error_dialog: self.speed.reset() d = ErrorDialog(msg, _("Error occurred while sending file %s") % self.files[-1].get_basename(), modal=True, icon_name="blueman", parent=self.get_toplevel(), buttons=[]) if len(self.files) > 1: d.add_button(_("Skip"), Gtk.ResponseType.NO) d.add_button(_("Retry"), Gtk.ResponseType.YES) d.add_button("_Cancel", Gtk.ResponseType.CANCEL) if self.object_push: self.client.remove_session(self.object_push.get_object_path()) def on_response(dialog, resp): dialog.destroy() self.error_dialog = None if resp == Gtk.ResponseType.CANCEL: self.on_cancel(None) elif resp == Gtk.ResponseType.NO: finfo = self.files[-1].query_info('standard::*', Gio.FileQueryInfoFlags.NONE) self.total_bytes -= finfo.get_size() self.total_transferred -= self.transferred self.transferred = 0 del self.files[-1] if not self.object_push: self.create_session() self.process_queue() elif resp == Gtk.ResponseType.YES: self.total_transferred -= self.transferred self.transferred = 0 if not self.object_push: self.create_session() self.process_queue() else: self.on_cancel(None) d.connect("response", on_response) d.show() self.error_dialog = d def on_session_added(self, _manager, session_path): self.object_push = ObjectPush(session_path) self.object_push.connect("transfer-started", self.on_transfer_started) self.object_push.connect("transfer-failed", self.on_transfer_failed) self.process_queue() def on_session_removed(self, _manager, session_path): logging.debug('Session removed: %s' % session_path) if self.object_push: self.object_push.disconnect_by_func(self.on_transfer_started) self.object_push.disconnect_by_func(self.on_transfer_failed) self.object_push = None def on_session_failed(self, _client, msg): d = ErrorDialog(_("Error occurred"), msg.reason.split(None, 1)[1], icon_name="blueman", parent=self.get_toplevel()) d.run() d.destroy() self.emit("result", False)
class TransferService(AppletPlugin): __author__ = "cschramm" __description__ = _("Provides OBEX file transfer capabilities") __icon__ = "blueman-send-file" _silent_transfers = 0 _normal_transfers = 0 _manager = None _agent = None _watch = None _notification = None def on_load(self): def on_reset(*_args): self._notification = None self._config.reset('shared-path') logging.info('Reset share path') self._config = Config("org.blueman.transfer") share_path, invalid_share_path = self._make_share_path() if invalid_share_path: text = _('Configured directory for incoming files does not exist') secondary_text = _( 'Please make sure that directory "<b>%s</b>" exists or ' 'configure it with blueman-services. Until then the default "%s" will be used' ) self._notification = Notification( text, secondary_text % (self._config["shared-path"], share_path), icon_name='blueman', timeout=30000, actions=[['reset', 'Reset to default', 'blueman']], actions_cb=on_reset) self._notification.show() self._watch = Manager.watch_name_owner(self._on_dbus_name_appeared, self._on_dbus_name_vanished) def on_unload(self): if self._watch: Gio.bus_unwatch_name(self._watch) self._unregister_agent() def _make_share_path(self): config_path = self._config["shared-path"] default_path = GLib.get_user_special_dir( GLib.UserDirectory.DIRECTORY_DOWNLOAD) path = None error = False if config_path == '': path = default_path elif not os.path.isdir(config_path): path = default_path error = True logging.warning('Invalid shared-path %s' % config_path) else: path = config_path if not path: path = os.path.expanduser("~") logging.warning('Failed to get Download dir from XDG') # We used to always store the full path which caused problems if config_path == default_path: logging.info('Reset stored path, identical to default path.') self._config["shared-path"] = '' return path, error def _register_agent(self): if not self._agent: self._agent = Agent(self.parent) self._agent.register_at_manager() def _unregister_agent(self): if self._agent: self._agent.unregister_from_manager() self._agent.unregister() self._agent = None def _on_dbus_name_appeared(self, _connection, name, owner): logging.info("%s %s" % (name, owner)) self._manager = Manager() self._manager.connect("transfer-started", self._on_transfer_started) self._manager.connect("transfer-completed", self._on_transfer_completed) self._manager.connect('session-removed', self._on_session_removed) self._register_agent() def _on_dbus_name_vanished(self, _connection, name): logging.info("%s not running or was stopped" % name) if self._manager: self._manager.disconnect_by_func(self._on_transfer_started) self._manager.disconnect_by_func(self._on_transfer_completed) self._manager.disconnect_by_func(self._on_session_removed) self._manager = None if self._agent: self._agent.unregister() self._agent = None def _on_transfer_started(self, _manager, transfer_path): if not self._agent or transfer_path not in self._agent.transfers: # This is not an incoming transfer we authorized return if self._agent.transfers[transfer_path]['size'] > 350000: self._normal_transfers += 1 else: self._silent_transfers += 1 def _add_open(self, n, name, path): if n.actions_supported: logging.info("adding action") def on_open(*_args): self._notification = None logging.info("open") launch("xdg-open", [path], True) n.add_action("open", name, on_open) @property def _notify_kwargs(self): kwargs = {"icon_name": "blueman"} try: kwargs["pos_hint"] = self.parent.Plugins.StatusIcon.geometry except AttributeError: logging.error("No statusicon found") return kwargs def _on_transfer_completed(self, _manager, transfer_path, success): if not self._agent or transfer_path not in self._agent.transfers: logging.info( "This is probably not an incoming transfer we authorized") return attributes = self._agent.transfers[transfer_path] src = attributes['path'] dest_dir, ignored = self._make_share_path() filename = os.path.basename(src) dest = os.path.join(dest_dir, filename) if os.path.exists(dest): now = datetime.now() filename = "%s_%s" % (now.strftime("%Y%m%d%H%M%S"), filename) logging.info("Destination file exists, renaming to: %s" % filename) try: shutil.move(src, dest) except (OSError, PermissionError): logging.error("Failed to move files", exc_info=True) success = False if success: self._notification = Notification( _("File received"), _("File %(0)s from %(1)s successfully received") % { "0": "<b>" + escape(filename) + "</b>", "1": "<b>" + escape(attributes['name']) + "</b>" }, **self._notify_kwargs) self._add_open(self._notification, "Open", dest) self._notification.show() elif not success: n = Notification( _("Transfer failed"), _("Transfer of file %(0)s failed") % { "0": "<b>" + escape(filename) + "</b>", "1": "<b>" + escape(attributes['name']) + "</b>" }, **self._notify_kwargs) n.show() if attributes['size'] > 350000: self._normal_transfers -= 1 else: self._silent_transfers -= 1 del self._agent.transfers[transfer_path] def _on_session_removed(self, _manager, _session_path): if self._silent_transfers == 0: return share_path, ignored = self._make_share_path() if self._normal_transfers == 0: self._notification = Notification( _("Files received"), ngettext("Received %d file in the background", "Received %d files in the background", self._silent_transfers) % self._silent_transfers, **self._notify_kwargs) self._add_open(self._notification, "Open Location", share_path) self._notification.show() else: self._notification = Notification( _("Files received"), ngettext("Received %d more file in the background", "Received %d more files in the background", self._silent_transfers) % self._silent_transfers, **self._notify_kwargs) self._add_open(self._notification, "Open Location", share_path) self._notification.show()