404 lines
13 KiB
Plaintext
404 lines
13 KiB
Plaintext
|
|
#! /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()
|