#! /usr/bin/python3 # coding=utf-8 from __future__ import print_function from __future__ import division from __future__ import absolute_import from __future__ import unicode_literals import os, sys, signal from blueman.bluez.obex.Base import ObexdNotFoundError #support running uninstalled _dirname = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) if os.path.exists(os.path.join(_dirname, "CHANGELOG.md")): sys.path.insert(0, _dirname) from dbus.mainloop.glib import DBusGMainLoop from optparse import OptionParser import gettext import urllib import time from blueman.Constants import * import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk from gi.repository import GLib from blueman.bluez.Adapter import Adapter from blueman.bluez.obex.ObjectPush import ObjectPush from blueman.main.Device import Device from blueman.main.FakeDevice import FakeDevice from blueman.bluez.Manager import Manager from blueman.Functions import * from blueman.Constants import * from blueman.gui.DeviceSelectorDialog import DeviceSelectorDialog from blueman.main.SpeedCalc import SpeedCalc from blueman.main.AppletService import AppletService from blueman.bluez import obex DBusGMainLoop(set_as_default=True) # Workaround introspection bug, gnome bug 622084 signal.signal(signal.SIGINT, signal.SIG_DFL) enable_rgba_colormap() class Sender(GObject.GObject): __gsignals__ = { str('result'): (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_BOOLEAN,)), } def __init__(self, device, adapter, files): GObject.GObject.__init__(self) self.Builder = Gtk.Builder() self.Builder.set_translation_domain("blueman") self.Builder.add_from_file(UI_PATH + "/send-dialog.ui") self.window = self.Builder.get_object("window") 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.b_cancel = self.Builder.get_object("b_cancel") self.b_cancel.connect("clicked", self.on_cancel) self.pb.props.text = _("Connecting") self.device = device self.adapter = Adapter(adapter) self.files = files self.object_push = None self.transfer = None self.total_bytes = 0 self.total_transferred = 0 self._last_bytes = 0 self._last_update = 0 self.error_dialog = None self.cancelling = False #bytes transferred on a current transfer self.transferred = 0 self.speed = SpeedCalc(6) for i in range(len(self.files) - 1, -1, -1): f = self.files[i] match = re.match("file://(.*)", f) if match: f = self.files[i] = urllib.unquote(match.groups(1)[0]) if os.path.exists(f) and not os.path.isdir(f): f = os.path.abspath(f) self.total_bytes += os.path.getsize(f) else: self.files.remove(f) self.num_files = len(self.files) try: self.client = obex.Client() except ObexdNotFoundError: d = Gtk.MessageDialog(self.window, type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK) d.props.text = _("obexd not available") d.props.secondary_text = _("obexd is probably not installed") d.run() d.destroy() exit(1) if self.num_files == 0: exit(1) self.l_file.props.label = os.path.basename(self.files[-1]) self.client.connect('session-created', self.on_session_created) self.client.connect('session-failed', self.on_session_failed) self.client.connect('session-removed', self.on_session_removed) print("Sending to", device.Address) self.l_dest.props.label = device.Alias self.create_session() self.window.show() def create_session(self): def check(): if self.adapter.get_properties()['Discovering']: return True else: self._do_create_session() return False GLib.timeout_add(1000, check) def _do_create_session(self): dprint("Creating session") props = self.adapter.get_properties() self.client.create_session(self.device.Address, props["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()) else: 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 = obex.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]) else: self.emit("result", True) def send_file(self, file_path): dprint(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 = Gtk.MessageDialog(self.window, type=Gtk.MessageType.ERROR) d.props.text = msg d.props.modal = True d.props.secondary_text = _("Error occurred while sending file %s") % os.path.basename(self.files[-1]) d.props.icon_name = "blueman" 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) def on_response(dialog, resp): dialog.destroy() self.error_dialog = None if resp == "_Cancel": self.on_cancel(None) elif resp == Gtk.ResponseType.NO: self.total_bytes -= os.path.getsize(self.files[-1]) 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_created(self, _client, 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_failed(self, _client, msg): d = Gtk.MessageDialog(self.window, type=Gtk.MessageType.ERROR, buttons=(Gtk.ButtonsType.CLOSE)) d.props.text = _("Error occurred") d.props.icon_name = "blueman" d.props.secondary_text = str(msg).split(":")[1].strip(" ") d.run() d.destroy() exit(1) def on_session_removed(self, _client): self.emit("result", False) class SendTo: def __init__(self): setup_icon_path() usage = "Usage: %prog [options] file1 file2 ... fileN" parser = OptionParser(usage) parser.add_option("-d", "--device", dest="device", action="store", help=_("Send files to this device"), metavar="ADDRESS") parser.add_option("", "--dest", dest="device", action="store", help="Same as --device", metavar="ADDRESS") parser.add_option("-s", "--source", dest="source", action="store", help=_("Source adapter. Takes address or adapter's name eg. hci0"), metavar="PATTERN") (options, args) = parser.parse_args() check_bluetooth_status(_("Bluetooth needs to be turned on for file sending to work"), lambda: exit()) self.options = options self.files = [] if not args: self.files = self.select_files() else: self.files = [os.path.abspath(f) for f in args] self.device = None self.adapter = None if options.device is None: if not self.select_device(): exit() self.do_send() else: m = Manager() try: if options.source is not None: try: adapter = m.get_adapter(options.source) except: adapter = m.get_adapter() else: adapter = m.get_adapter() except: print("Error: No Adapters present") exit() try: d = adapter.find_device(options.device) except: info = {} info["Address"] = options.device info["Name"] = info["Address"].replace(":", "-") info["Alias"] = info["Name"] info["Fake"] = True d = FakeDevice(info) self.device = Device(d) self.adapter = adapter.get_object_path() self.do_send() Gtk.main() def do_send(self): if not self.files: dprint("No files to send") exit() sender = Sender(self.device, self.adapter, self.files) def on_result(sender, res): Gtk.main_quit() sender.connect("result", on_result) def select_files(self): d = Gtk.FileChooserDialog(_("Select files to send"), buttons=("_Cancel", Gtk.ResponseType.REJECT, "_OK", Gtk.ResponseType.ACCEPT)) d.props.icon_name = "blueman-send-file" d.set_select_multiple(True) resp = d.run() if resp == Gtk.ResponseType.ACCEPT: files = d.get_filenames() d.destroy() return files else: d.destroy() return [] def select_device(self): d = DeviceSelectorDialog() resp = d.run() if resp == Gtk.ResponseType.ACCEPT: sel = d.GetSelection() if sel: self.device = sel[1] self.adapter = sel[0] return True else: return False else: return False set_proc_title() SendTo()