diff --git a/src/icons/upload.png b/src/icons/upload.png new file mode 100644 index 0000000000..64dd550f15 Binary files /dev/null and b/src/icons/upload.png differ diff --git a/src/xpra/client/client_base.py b/src/xpra/client/client_base.py index 9e798b9de7..0641b36fc3 100644 --- a/src/xpra/client/client_base.py +++ b/src/xpra/client/client_base.py @@ -29,14 +29,8 @@ from xpra.version_util import version_compat_check, get_version_info, local_version from xpra.platform.info import get_name from xpra.os_util import get_hex_uuid, get_machine_id, get_user_uuid, load_binary_file, SIGNAMES, strtobytes, bytestostr -from xpra.util import typedict, updict, xor, repr_ellipsized, nonl, disconnect_is_an_error, csv - -try: - import xpra.platform.printing - assert xpra.platform.printing - HAS_PRINTING = True -except: - HAS_PRINTING = False +from xpra.util import typedict, updict, xor, repr_ellipsized, nonl, disconnect_is_an_error +from xpra.net.file_transfer import HAS_PRINTING, FileTransferHandler EXIT_OK = 0 EXIT_CONNECTION_LOST = 1 @@ -62,7 +56,7 @@ DELETE_PRINTER_FILE = os.environ.get("XPRA_DELETE_PRINTER_FILE", "1")=="1" -class XpraClientBase(object): +class XpraClientBase(FileTransferHandler): """ Base class for Xpra clients. Provides the glue code for: * sending packets via Protocol @@ -74,6 +68,7 @@ class XpraClientBase(object): """ def __init__(self): + FileTransferHandler.__init__(self) #this may be called more than once, #skip doing internal init again: if not hasattr(self, "exit_code"): @@ -100,14 +95,9 @@ def defaults_init(self): self.min_quality = 0 self.speed = 0 self.min_speed = -1 - self.file_transfer = False - self.file_size_limit = 10 - self.printing = False self.printer_attributes = [] self.send_printers_pending = False self.exported_printers = None - self.open_files = False - self.open_command = None #protocol stuff: self._protocol = None self._priority_packets = [] @@ -140,11 +130,8 @@ def init(self, opts): self.min_quality = opts.min_quality self.speed = opts.speed self.min_speed = opts.min_speed - self.file_transfer = opts.file_transfer - self.file_size_limit = opts.file_size_limit - self.printing = opts.printing - self.open_command = opts.open_command - self.open_files = opts.open_files + #printing and file transfer: + FileTransferHandler.init(self, opts) if DETECT_LEAKS: from xpra.util import detect_leaks @@ -249,21 +236,39 @@ def setup_connection(self, conn): self.timeout_add((conn.timeout + EXTRA_TIMEOUT) * 1000, self.verify_connected) netlog("setup_connection(%s) protocol=%s", conn, self._protocol) + + def set_packet_handlers(self, to, defs): + """ configures the given packet handlers, + and make sure we remove any existing ones with the same key + (which can be useful for subclasses, not here) + """ + log("set_packet_handlers(%s, %s)", to, defs) + def delhandler(k): + #remove any existing mappings: + for d in (self._packet_handlers, self._ui_packet_handlers): + try: + del d[k] + except: + pass + for k,v in defs.items(): + delhandler(k) + to[k] = v + def init_packet_handlers(self): - self._packet_handlers = { - "hello" : self._process_hello, - } - self._ui_packet_handlers = { + self._packet_handlers = {} + self._ui_packet_handlers = {} + self.set_packet_handlers(self._packet_handlers, {"hello" : self._process_hello}) + self.set_packet_handlers(self._ui_packet_handlers, { "challenge": self._process_challenge, "disconnect": self._process_disconnect, "set_deflate": self._process_set_deflate, Protocol.CONNECTION_LOST: self._process_connection_lost, Protocol.GIBBERISH: self._process_gibberish, Protocol.INVALID: self._process_invalid, - } + }) def init_authenticated_packet_handlers(self): - self._packet_handlers["send-file"] = self._process_send_file + self.set_packet_handlers(self._packet_handlers, {"send-file" : self._process_send_file}) def init_aliases(self): @@ -275,6 +280,7 @@ def init_aliases(self): self._reverse_aliases[key] = i i += 1 + def send_hello(self, challenge_response=None, client_salt=None): try: hello = self.make_hello_base() @@ -313,9 +319,6 @@ def make_hello_base(self): "version" : local_version, "encoding.generic" : True, "namespace" : True, - "file-transfer" : self.file_transfer, - "file-size-limit" : self.file_size_limit, - "printing" : self.printing, "hostname" : socket.gethostname(), "uuid" : self.uuid, "username" : self.username, @@ -325,6 +328,7 @@ def make_hello_base(self): "compression_level" : self.compression_level, "argv" : sys.argv, }) + capabilities.update(self.get_file_transfer_features()) if self.display: capabilities["display"] = self.display def up(prefix, d): @@ -446,6 +450,7 @@ def warn_and_quit(self, exit_code, message): log.warn(message) self.quit(exit_code) + def _process_disconnect(self, packet): #ie: ("disconnect", "version error", "incompatible version") reason = bytestostr(packet[1]) @@ -781,146 +786,6 @@ def _process_set_deflate(self, packet): pass - def _process_send_file(self, packet): - #send-file basefilename, printit, openit, filesize, 0, data) - from xpra.platform.paths import get_download_dir - basefilename, mimetype, printit, openit, filesize, file_data, options = packet[1:11] - options = typedict(options) - if printit: - l = printlog - assert self.printing - else: - l = filelog - assert self.file_transfer - l("received file: %s", [basefilename, mimetype, printit, openit, filesize, "%s bytes" % len(file_data), options]) - assert filesize>0, "invalid file size: %s" % filesize - assert file_data, "no data!" - if len(file_data)!=filesize: - l.error("Error: invalid data size for file '%s'", basefilename) - l.error(" received %i bytes, expected %i bytes", len(file_data), filesize) - return - if filesize>self.file_size_limit*1024*1024: - l.error("Error: file '%s' is too large:", basefilename) - l.error(" %iMB, the file size limit is %iMB", filesize//1024//1024, self.file_size_limit) - return - #check digest if present: - import hashlib - def check_digest(algo="sha1", libfn=hashlib.sha1): - digest = options.get(algo) - if not digest: - return - u = libfn() - u.update(file_data) - l("%s digest: %s - expected: %s", algo, u.hexdigest(), digest) - if digest!=u.hexdigest(): - l.error("Error: data does not match, invalid %s file digest for %s", algo, basefilename) - l.error(" received %s, expected %s", u.hexdigest(), digest) - return - check_digest("sha1", hashlib.sha1) - check_digest("md5", hashlib.md5) - - #make sure we use a filename that does not exist already: - dd = os.path.expanduser(get_download_dir()) - wanted_filename = os.path.abspath(os.path.join(dd, os.path.basename(basefilename))) - EXTS = {"application/postscript" : "ps", - "application/pdf" : "pdf", - "raw" : "raw", - } - ext = EXTS.get(mimetype) - if ext: - #on some platforms (win32), - #we want to force an extension - #so that the file manager can display them properly when you double-click on them - if not wanted_filename.endswith("."+ext): - wanted_filename += "."+ext - filename = wanted_filename - base = 0 - while os.path.exists(filename): - l("cannot save file as %s: file already exists", filename) - root, ext = os.path.splitext(wanted_filename) - base += 1 - filename = root+("-%s" % base)+ext - flags = os.O_CREAT | os.O_RDWR | os.O_EXCL - try: - flags |= os.O_BINARY #@UndefinedVariable (win32 only) - except: - pass - fd = os.open(filename, flags) - try: - os.write(fd, file_data) - finally: - os.close(fd) - l.info("downloaded %s bytes to %s file%s:", filesize, (mimetype or "unknown"), ["", " for printing"][int(printit)]) - l.info(" %s", filename) - if printit: - printer = options.strget("printer") - title = options.strget("title") - print_options = options.dictget("options") - #TODO: how do we print multiple copies? - #copies = options.intget("copies") - #whitelist of options we can forward: - safe_print_options = dict((k,v) for k,v in print_options.items() if k in ("PageSize", "Resolution")) - l("safe print options(%s) = %s", options, safe_print_options) - self._print_file(filename, mimetype, printer, title, safe_print_options) - return - elif openit: - self._open_file(filename) - - def _print_file(self, filename, mimetype, printer, title, options): - import time - from xpra.platform.printing import print_files, printing_finished, get_printers - printers = get_printers() - if printer not in printers: - printlog.error("Error: printer '%s' does not exist!", printer) - printlog.error(" printers available: %s", csv(printers.keys()) or "none") - return - def delfile(): - if not DELETE_PRINTER_FILE: - return - try: - os.unlink(filename) - except: - printlog("failed to delete print job file '%s'", filename) - return False - job = print_files(printer, [filename], title, options) - printlog("printing %s, job=%s", filename, job) - if job<=0: - printlog("printing failed and returned %i", job) - delfile() - return - start = time.time() - def check_printing_finished(): - done = printing_finished(job) - printlog("printing_finished(%s)=%s", job, done) - if done: - delfile() - return False - if time.time()-start>10*60: - printlog.warn("print job %s timed out", job) - delfile() - return False - return True #try again.. - if check_printing_finished(): - self.timeout_add(10000, check_printing_finished) - - def _open_file(self, filename): - if not self.open_files: - log.warn("Warning: opening files automatically is disabled,") - log.warn(" ignoring uploaded file:") - log.warn(" '%s'", filename) - return - import subprocess, shlex - command = shlex.split(self.open_command)+[filename] - proc = subprocess.Popen(command) - cr = getChildReaper() - def open_done(*args): - returncode = proc.poll() - log("open_done: command %s has ended, returncode=%s", command, returncode) - if returncode!=0: - log.warn("Warning: failed to open the downloaded file") - log.warn(" '%s %s' returned", self.open_command, filename, returncode) - cr.add_process(proc, "Open File %s" % filename, command, True, True, open_done) - def _process_gibberish(self, packet): (_, message, data) = packet p = self._protocol diff --git a/src/xpra/client/client_window_base.py b/src/xpra/client/client_window_base.py index 0651df1d5f..b51b9bf30e 100644 --- a/src/xpra/client/client_window_base.py +++ b/src/xpra/client/client_window_base.py @@ -576,6 +576,9 @@ def show_session_info(self, *args): def show_start_new_command(self, *args): self._client.show_start_new_command(*args) + def show_file_upload(self, *args): + self._client.show_file_upload(*args) + def log(self, message=""): log.info(message) diff --git a/src/xpra/client/gtk2/client.py b/src/xpra/client/gtk2/client.py index 7b4a56e7f8..4e84c556fa 100644 --- a/src/xpra/client/gtk2/client.py +++ b/src/xpra/client/gtk2/client.py @@ -306,7 +306,10 @@ def compressed_wrapper(self, datatype, data): lz4 = "lz4" in self.server_compressors and compression.use_lz4 lzo = "lzo" in self.server_compressors and compression.use_lzo if zlib or lz4 or lzo: - return compression.compressed_wrapper(datatype, data, zlib=zlib, lz4=lz4, lzo=lzo, can_inline=False) + cw = compression.compressed_wrapper(datatype, data, zlib=zlib, lz4=lz4, lzo=lzo, can_inline=False) + if len(cw)=filesize + data = data[:filesize] #gio may null terminate it + filelog("send_file%s", (filename, "%i bytes" % filesize, openit)) + absfile = os.path.abspath(filename) + basefilename = os.path.basename(filename) + cdata = self.compressed_wrapper("file-data", data) + assert len(cdata)<=filesize #compressed wrapper ensures this is true + if filesize>self.file_size_limit*1024*1024: + filelog.warn("Warning: cannot upload the file '%s'", basefilename) + filelog.warn(" this file is too large: %sB", std_unit(filesize, unit=1024)) + filelog.warn(" the file size limit is %iMB", self, self.file_size_limit) + return False + if filesize>self.server_file_size_limit*1024*1024: + filelog.warn("Warning: cannot upload the file '%s'", basefilename) + filelog.warn(" this file is too large: %sB", std_unit(filesize, unit=1024)) + filelog.warn(" the file size limit for %s is %iMB", self._protocol, self.server_file_size_limit) + return False + printit = False + mimetype = "" + u = hashlib.sha1() + u.update(data) + filelog("sha1 digest(%s)=%s", absfile, u.hexdigest()) + options = {"sha1" : u.hexdigest()} + self.send("send-file", basefilename, mimetype, printit, openit, filesize, cdata, options) + return True + def send_focus(self, wid): focuslog("send_focus(%s)", wid) @@ -1556,6 +1591,7 @@ def parse_server_capabilities(self): self.window_configure_pointer = c.boolget("window.configure.pointer") self.force_ungrab = c.boolget("force_ungrab") self.window_refresh_config = c.boolget("window_refresh_config") + self.server_window_frame_extents = c.boolget("window.frame-extents") self.suspend_resume = c.boolget("suspend-resume") self.server_supports_notifications = c.boolget("notifications") self.notifications_enabled = self.server_supports_notifications and self.client_supports_notifications @@ -1576,7 +1612,9 @@ def parse_server_capabilities(self): default_rpc_types = [] self.server_rpc_types = c.strlistget("rpc-types", default_rpc_types) self.start_new_commands = c.boolget("start-new-commands") - self.server_window_frame_extents = c.boolget("window.frame-extents") + self.server_file_transfer = c.boolget("file-transfer") + self.server_file_size_limit = c.intget("file-size-limit", 10) + self.server_open_files = c.boolget("open-files") self.mmap_enabled = self.supports_mmap and self.mmap_enabled and c.boolget("mmap_enabled") if self.mmap_enabled: mmap_token = c.intget("mmap_token") @@ -2676,14 +2714,7 @@ def set_max_packet_size(self): def init_authenticated_packet_handlers(self): log("init_authenticated_packet_handlers()") XpraClientBase.init_authenticated_packet_handlers(self) - def delhandler(k): - #remove any existing mappings: - for d in (self._packet_handlers, self._ui_packet_handlers): - try: - del d[k] - except: - pass - for k,v in { + self.set_packet_handlers(self._ui_packet_handlers, { "startup-complete": self._startup_complete, "new-window": self._process_new_window, "new-override-redirect":self._process_new_override_redirect, @@ -2708,19 +2739,16 @@ def delhandler(k): "control" : self._process_control, "draw": self._process_draw, # "clipboard-*" packets are handled by a special case below. - }.items(): - delhandler(k) - self._ui_packet_handlers[k] = v + }) #these handlers can run directly from the network thread: - for k,v in { + self.set_packet_handlers(self._packet_handlers, { "ping": self._process_ping, "ping_echo": self._process_ping_echo, "info-response": self._process_info_response, "sound-data": self._process_sound_data, "server-event": self._process_server_event, - }.items(): - delhandler(k) - self._packet_handlers[k] = v + }) + def process_clipboard_packet(self, packet): clipboardlog("process_clipboard_packet: %s", packet[0]) diff --git a/src/xpra/net/file_transfer.py b/src/xpra/net/file_transfer.py new file mode 100644 index 0000000000..daecc4c6e5 --- /dev/null +++ b/src/xpra/net/file_transfer.py @@ -0,0 +1,203 @@ +# This file is part of Xpra. +# Copyright (C) 2010-2015 Antoine Martin +# Copyright (C) 2008, 2010 Nathaniel Smith +# Xpra is released under the terms of the GNU GPL v2, or, at your option, any +# later version. See the file COPYING for details. + +import os + +from xpra.log import Logger +printlog = Logger("printing") +filelog = Logger("file") + +from xpra.child_reaper import getChildReaper +from xpra.util import typedict, csv + +try: + import xpra.platform.printing + assert xpra.platform.printing + HAS_PRINTING = True +except: + HAS_PRINTING = False + +DELETE_PRINTER_FILE = os.environ.get("XPRA_DELETE_PRINTER_FILE", "1")=="1" + + +class FileTransferHandler(object): + """ Utility class for receiving files and optionally printing them, + used by both clients and server to share the common code and attributes + """ + + def __init__(self): + self.file_transfer = False + self.file_size_limit = 10 + self.printing = False + self.open_files = False + self.open_command = None + + def init(self, opts): + #printing and file transfer: + self.file_transfer = opts.file_transfer + self.file_size_limit = opts.file_size_limit + self.printing = opts.printing + self.open_command = opts.open_command + self.open_files = opts.open_files + + + def get_file_transfer_features(self): + return { + "file-transfer" : self.file_transfer, + "file-size-limit" : self.file_size_limit, + "open-files" : self.open_files, + "printing" : self.printing, + } + + def get_file_transfer_info(self): + #slightly different from above... for legacy reasons + #this one is used in a proper "file." namespace from server_base.py + return { + "transfer" : self.file_transfer, + "size-limit" : self.file_size_limit, + "open" : self.open_files, + } + + + def _process_send_file(self, packet): + #send-file basefilename, printit, openit, filesize, 0, data) + from xpra.platform.paths import get_download_dir + basefilename, mimetype, printit, openit, filesize, file_data, options = packet[1:11] + options = typedict(options) + if printit: + l = printlog + assert self.printing + else: + l = filelog + assert self.file_transfer + l("received file: %s", [basefilename, mimetype, printit, openit, filesize, "%s bytes" % len(file_data), options]) + assert filesize>0, "invalid file size: %s" % filesize + assert file_data, "no data!" + if len(file_data)!=filesize: + l.error("Error: invalid data size for file '%s'", basefilename) + l.error(" received %i bytes, expected %i bytes", len(file_data), filesize) + return + if filesize>self.file_size_limit*1024*1024: + l.error("Error: file '%s' is too large:", basefilename) + l.error(" %iMB, the file size limit is %iMB", filesize//1024//1024, self.file_size_limit) + return + #check digest if present: + import hashlib + def check_digest(algo="sha1", libfn=hashlib.sha1): + digest = options.get(algo) + if not digest: + return + u = libfn() + u.update(file_data) + l("%s digest: %s - expected: %s", algo, u.hexdigest(), digest) + if digest!=u.hexdigest(): + l.error("Error: data does not match, invalid %s file digest for %s", algo, basefilename) + l.error(" received %s, expected %s", u.hexdigest(), digest) + return + check_digest("sha1", hashlib.sha1) + check_digest("md5", hashlib.md5) + + #make sure we use a filename that does not exist already: + dd = os.path.expanduser(get_download_dir()) + wanted_filename = os.path.abspath(os.path.join(dd, os.path.basename(basefilename))) + EXTS = {"application/postscript" : "ps", + "application/pdf" : "pdf", + "raw" : "raw", + } + ext = EXTS.get(mimetype) + if ext: + #on some platforms (win32), + #we want to force an extension + #so that the file manager can display them properly when you double-click on them + if not wanted_filename.endswith("."+ext): + wanted_filename += "."+ext + filename = wanted_filename + base = 0 + while os.path.exists(filename): + l("cannot save file as %s: file already exists", filename) + root, ext = os.path.splitext(wanted_filename) + base += 1 + filename = root+("-%s" % base)+ext + flags = os.O_CREAT | os.O_RDWR | os.O_EXCL + try: + flags |= os.O_BINARY #@UndefinedVariable (win32 only) + except: + pass + fd = os.open(filename, flags) + try: + os.write(fd, file_data) + finally: + os.close(fd) + l.info("downloaded %s bytes to %s file%s:", filesize, (mimetype or "unknown"), ["", " for printing"][int(printit)]) + l.info(" %s", filename) + if printit: + printer = options.strget("printer") + title = options.strget("title") + print_options = options.dictget("options") + #TODO: how do we print multiple copies? + #copies = options.intget("copies") + #whitelist of options we can forward: + safe_print_options = dict((k,v) for k,v in print_options.items() if k in ("PageSize", "Resolution")) + l("safe print options(%s) = %s", options, safe_print_options) + self._print_file(filename, mimetype, printer, title, safe_print_options) + return + elif openit: + self._open_file(filename) + + def _print_file(self, filename, mimetype, printer, title, options): + import time + from xpra.platform.printing import print_files, printing_finished, get_printers + printers = get_printers() + if printer not in printers: + printlog.error("Error: printer '%s' does not exist!", printer) + printlog.error(" printers available: %s", csv(printers.keys()) or "none") + return + def delfile(): + if not DELETE_PRINTER_FILE: + return + try: + os.unlink(filename) + except: + printlog("failed to delete print job file '%s'", filename) + return False + job = print_files(printer, [filename], title, options) + printlog("printing %s, job=%s", filename, job) + if job<=0: + printlog("printing failed and returned %i", job) + delfile() + return + start = time.time() + def check_printing_finished(): + done = printing_finished(job) + printlog("printing_finished(%s)=%s", job, done) + if done: + delfile() + return False + if time.time()-start>10*60: + printlog.warn("print job %s timed out", job) + delfile() + return False + return True #try again.. + if check_printing_finished(): + self.timeout_add(10000, check_printing_finished) + + def _open_file(self, filename): + if not self.open_files: + filelog.warn("Warning: opening files automatically is disabled,") + filelog.warn(" ignoring uploaded file:") + filelog.warn(" '%s'", filename) + return + import subprocess, shlex + command = shlex.split(self.open_command)+[filename] + proc = subprocess.Popen(command) + cr = getChildReaper() + def open_done(*args): + returncode = proc.poll() + filelog("open_done: command %s has ended, returncode=%s", command, returncode) + if returncode!=0: + filelog.warn("Warning: failed to open the downloaded file") + filelog.warn(" '%s %s' returned %s", self.open_command, filename, returncode) + cr.add_process(proc, "Open File %s" % filename, command, True, True, open_done) diff --git a/src/xpra/platform/darwin/osx_menu.py b/src/xpra/platform/darwin/osx_menu.py index d469d1a1b2..a97b98a95c 100644 --- a/src/xpra/platform/darwin/osx_menu.py +++ b/src/xpra/platform/darwin/osx_menu.py @@ -198,10 +198,12 @@ def set_encodings_menu(*args): actions_menu.add(self.make_raisewindowsmenuitem()) #set_sensitive(bool) does not work on OSX, #so we only add the menu item if it does something - def addsnc(*args): + def add_ah(*args): if self.client.start_new_commands: - actions_menu.add(self.make_startnewcommandmenuitem(True)) - self.client.after_handshake(addsnc) + actions_menu.add(self.make_startnewcommandmenuitem()) + if self.client.server_file_transfer: + actions_menu.add(self.make_uploadmenuitem()) + self.client.after_handshake(add_ah) menus.append(("Actions", actions_menu)) menus.append((SEPARATOR+"-EXTRAS", None)) return menus diff --git a/src/xpra/server/server_base.py b/src/xpra/server/server_base.py index c1f3813a1b..02797fca24 100644 --- a/src/xpra/server/server_base.py +++ b/src/xpra/server/server_base.py @@ -43,6 +43,7 @@ from xpra.scripts.main import sound_option from xpra.codecs.loader import PREFERED_ENCODING_ORDER, PROBLEMATIC_ENCODINGS, load_codecs, codec_versions, has_codec, get_codec from xpra.codecs.video_helper import getVideoHelper, ALL_VIDEO_ENCODER_OPTIONS, ALL_CSC_MODULE_OPTIONS +from xpra.net.file_transfer import HAS_PRINTING, FileTransferHandler if sys.version > '3': unicode = str #@ReservedAssignment @@ -73,7 +74,7 @@ def parse_env(env): return d -class ServerBase(ServerCore): +class ServerBase(ServerCore, FileTransferHandler): """ This is the base class for servers. It provides all the generic functions but is not tied @@ -83,6 +84,7 @@ class ServerBase(ServerCore): def __init__(self): ServerCore.__init__(self) + FileTransferHandler.__init__(self) log("ServerBase.__init__()") self.init_uuid() @@ -124,10 +126,8 @@ def __init__(self): self.dbus_helper = None self.dbus_control = False self.dbus_server = None - self.file_transfer = False - self.file_size_limit = 10 - self.printing = False self.lpadmin = "" + self.lpinfo = "" self.exit_with_children = False self.start_new_commands = False self.remote_logging = False @@ -213,14 +213,14 @@ def init(self, opts): self.remote_logging = opts.remote_logging self.env = parse_env(opts.env) self.send_pings = opts.pings - self.file_transfer = opts.file_transfer - self.file_size_limit = opts.file_size_limit + #printing and file transfer: + FileTransferHandler.init(self, opts) self.lpadmin = opts.lpadmin self.lpinfo = opts.lpinfo self.av_sync = opts.av_sync self.dbus_control = opts.dbus_control #server-side printer handling is only for posix via pycups for now: - if os.name=="posix" and opts.printing: + if os.name=="posix" and HAS_PRINTING and opts.printing: try: from xpra.platform import pycups_printing pycups_printing.set_lpadmin_command(self.lpadmin) @@ -539,6 +539,7 @@ def init_packet_handlers(self): "logging": self._process_logging, "command_request": self._process_command_request, "printers": self._process_printers, + "send-file": self._process_send_file, } self._authenticated_ui_packet_handlers = self._default_packet_handlers.copy() self._authenticated_ui_packet_handlers.update({ @@ -1083,16 +1084,13 @@ def make_hello(self, source): "cursors" : self.cursors, "dbus_proxy" : self.supports_dbus_proxy, "rpc-types" : self.rpc_handlers.keys(), - "file-transfer" : self.file_transfer, - #not exposed as this is currently unused by the client (we only transfer from server to client) - #"file-size-limit" : self.file_size_limit, - "printing" : self.printing, "sharing" : self.sharing, "printer.attributes" : ("printer-info", "device-uri"), "start-new-commands" : self.start_new_commands, "exit-with-children" : self.exit_with_children, "av-sync.enabled" : self.av_sync, }) + capabilities.update(self.get_file_transfer_features()) for x in self.get_server_features(): capabilities[x] = True #this is a feature, but we would need the hello request @@ -1444,6 +1442,10 @@ def send_screenshot(self, proto): self.send_disconnect(proto, "screenshot failed: %s" % e) + def _process_send_file(self, proto, packet): + #superclass does not take the protocol as argument: + FileTransferHandler._process_send_file(self, packet) + def _process_print(self, proto, packet): #ie: from the xpraforwarder we call this command: #command = ["xpra", "print", "socket:/path/tosocket", filename, mimetype, source, title, printer, no_copies, print_options] @@ -1631,6 +1633,7 @@ def do_get_info(self, proto, server_sources=None, window_ids=None): def up(prefix, d, suffix=""): updict(info, prefix, d, suffix) + up("file", self.get_file_transfer_info()) up("printing", self.get_printing_info()) up("features", self.get_features_info()) up("clipboard", self.get_clipboard_info()) diff --git a/src/xpra/server/source.py b/src/xpra/server/source.py index 9423980c98..add7e38eeb 100644 --- a/src/xpra/server/source.py +++ b/src/xpra/server/source.py @@ -1672,8 +1672,7 @@ def send_file(self, filename, mimetype, data, printit, openit, options={}): cdata = self.compressed_wrapper("file-data", data) assert len(cdata)<=filesize #compressed wrapper ensures this is true if filesize>self.file_size_limit*1024*1024: - b = os.path.basename(filename) - l.warn("Warning: cannot %s the file '%s'", action, b) + l.warn("Warning: cannot %s the file '%s'", action, basefilename) l.warn(" this file is too large: %sB", std_unit(filesize, unit=1024)) l.warn(" the file size limit for %s is %iMB", self, self.file_size_limit) return False