diff --git a/src/xpra/client/client_base.py b/src/xpra/client/client_base.py index 791dc76d87..d42a938e27 100644 --- a/src/xpra/client/client_base.py +++ b/src/xpra/client/client_base.py @@ -135,7 +135,7 @@ def init(self, opts): self.speed = opts.speed self.min_speed = opts.min_speed #printing and file transfer: - FileTransferHandler.init(self, opts) + FileTransferHandler.init_opts(self, opts) if DETECT_LEAKS: from xpra.util import detect_leaks @@ -643,6 +643,10 @@ def server_connection_established(self): return False self.parse_printing_capabilities() self.parse_logging_capabilities() + self.parse_file_transfer_caps(self.server_capabilities) + #raise packet size if required: + if self.file_transfer: + self._protocol.max_packet_size = max(self._protocol.max_packet_size, self.file_size_limit*1024*1024) netlog("server_connection_established() adding authenticated packet handlers") self.init_authenticated_packet_handlers() return True diff --git a/src/xpra/client/gtk_base/gtk_client_base.py b/src/xpra/client/gtk_base/gtk_client_base.py index fa21db89cb..4bd3d6d8c8 100644 --- a/src/xpra/client/gtk_base/gtk_client_base.py +++ b/src/xpra/client/gtk_base/gtk_client_base.py @@ -150,9 +150,9 @@ def run_command_cb(command, sharing=True): return self.start_new_command def show_file_upload(self, *args): - filelog("show_file_upload%s can open=%s", args, self.server_open_files) + filelog("show_file_upload%s can open=%s", args, self.remote_open_files) buttons = [gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL] - if self.server_open_files: + if self.remote_open_files: buttons += [gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT] buttons += [gtk.STOCK_OK, gtk.RESPONSE_OK] dialog = gtk.FileChooserDialog("File to upload", parent=None, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=tuple(buttons)) @@ -167,7 +167,7 @@ def show_file_upload(self, *args): data, filesize, entity = gfile.load_contents() filelog("load_contents: filename=%s, %i bytes, entity=%s, response=%s", filename, filesize, entity, v) dialog.destroy() - self.send_file(filename, data, filesize, openit=(v==gtk.RESPONSE_ACCEPT)) + self.send_file(filename, "", data, filesize=filesize, openit=(v==gtk.RESPONSE_ACCEPT)) def show_about(self, *args): diff --git a/src/xpra/client/gtk_base/gtk_tray_menu_base.py b/src/xpra/client/gtk_base/gtk_tray_menu_base.py index 6c59aa457a..c36af6b3ae 100644 --- a/src/xpra/client/gtk_base/gtk_tray_menu_base.py +++ b/src/xpra/client/gtk_base/gtk_tray_menu_base.py @@ -1091,9 +1091,9 @@ def enable_start_new_command(*args): def make_uploadmenuitem(self): self.upload = self.menuitem("Upload File", "upload.png", "Send a file to the server", self.client.show_file_upload) def enable_upload(*args): - log("enable_upload%s server_file_transfer=%s", args, self.client.server_file_transfer) - set_sensitive(self.upload, self.client.server_file_transfer) - if not self.client.server_file_transfer: + log("enable_upload%s server_file_transfer=%s", args, self.client.remote_file_transfer) + set_sensitive(self.upload, self.client.remote_file_transfer) + if not self.client.remote_file_transfer: self.upload.set_tooltip_text("Not supported by the server") self.client.after_handshake(enable_upload) return self.upload diff --git a/src/xpra/client/ui_client_base.py b/src/xpra/client/ui_client_base.py index 86e1b2b0de..0a7e4002f1 100644 --- a/src/xpra/client/ui_client_base.py +++ b/src/xpra/client/ui_client_base.py @@ -12,7 +12,6 @@ import datetime import traceback import logging -import hashlib from collections import deque from threading import RLock @@ -254,9 +253,6 @@ def __init__(self): self.server_is_shadow = False self.server_supports_sharing = False self.server_supports_window_filters = False - self.server_file_transfer = False - self.server_file_size_limit = 10 - self.server_open_files = False #what we told the server about our encoding defaults: self.encoding_defaults = {} @@ -1190,36 +1186,6 @@ def send_start_command(self, name, command, ignore, sharing=True): log("send_start_command(%s, %s, %s, %s)", name, command, ignore, sharing) self.send("start-command", name, command, ignore, sharing) - def send_file(self, filename, data, filesize, openit): - if not self.file_transfer: - filelog.warn("Warning: file transfers are not enabled for %s", self) - return False - assert len(data)>=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.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) @@ -1775,9 +1741,6 @@ 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_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") diff --git a/src/xpra/net/file_transfer.py b/src/xpra/net/file_transfer.py index ec0ecf48a4..1b7a2a7b4e 100644 --- a/src/xpra/net/file_transfer.py +++ b/src/xpra/net/file_transfer.py @@ -1,10 +1,13 @@ # This file is part of Xpra. -# Copyright (C) 2010-2015 Antoine Martin +# Copyright (C) 2010-2016 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 +import time +import subprocess, shlex +import hashlib from xpra.log import Logger printlog = Logger("printing") @@ -12,47 +15,77 @@ from xpra.child_reaper import getChildReaper from xpra.util import typedict, csv +from xpra.simple_stats import std_unit 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 - """ +class FileTransferAttributes(object): + def __init__(self, opts=None): + if opts: + self.init_opts(opts) + else: + self.init_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_opts(self, opts): + #get the settings from a config object + self.init_attributes(opts.file_transfer, opts.file_size_limit, opts.printing, opts.open_files, opts.open_command) - def init(self, opts): + def init_attributes(self, file_transfer=False, file_size_limit=10, printing=False, open_files=False, open_command=None): #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 - + self.file_transfer = file_transfer + self.file_size_limit = file_size_limit + self.printing = printing + self.open_files = open_files + self.open_command = open_command def get_file_transfer_features(self): + #used in hello packets return { - "file-transfer" : self.file_transfer, - "file-size-limit" : self.file_size_limit, - "open-files" : self.open_files, - "printing" : self.printing, - } + "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): + def get_info(self): #slightly different from above... for legacy reasons - #this one is used in a proper "file." namespace from server_base.py + #this one is used for get_info() in a proper "file." namespace from server_base.py return { - "transfer" : self.file_transfer, - "size-limit" : self.file_size_limit, - "open" : self.open_files, - } + "enabled" : self.file_transfer, + "size-limit" : self.file_size_limit, + "open" : self.open_files, + } + + +class FileTransferHandler(FileTransferAttributes): + """ + Utility class for receiving files and optionally printing them, + used by both clients and server to share the common code and attributes + """ + + def init_attributes(self, *args): + FileTransferAttributes.init_attributes(self, *args) + self.remote_file_transfer = False + self.remote_printing = False + self.remote_open_files = False + self.remote_file_size_limit = 0 + + def parse_file_transfer_caps(self, c): + self.remote_file_transfer = c.boolget("file-transfer") + self.remote_printing = c.boolget("printing") + self.remote_open_files = c.boolget("open-files") + self.remote_file_size_limit = c.boolget("file-size-limit") + + def get_info(self): + info = FileTransferAttributes.get_info(self) + info["remote"] = { + "file-transfer" : self.remote_file_transfer, + "printing" : self.remote_printing, + "open-files" : self.remote_open_files, + "file-size-limit" : self.remote_file_size_limit, + } + return info def _process_send_file(self, packet): @@ -78,7 +111,6 @@ def _process_send_file(self, packet): 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: @@ -124,11 +156,15 @@ def check_digest(algo="sha1", libfn=hashlib.sha1): 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) + l.info("downloaded %s bytes to %s file%s:", filesize, (mimetype or "temporary"), ["", " for printing"][int(printit)]) + l.info(" '%s'", filename) if printit: printer = options.strget("printer") title = options.strget("title") + if title: + l.info(" sending '%s' to printer '%s'", title, printer) + else: + l.info(" sending to printer '%s'", printer) print_options = options.dictget("options") #TODO: how do we print multiple copies? #copies = options.intget("copies") @@ -141,13 +177,8 @@ def check_digest(algo="sha1", libfn=hashlib.sha1): 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 @@ -156,6 +187,16 @@ def delfile(): except: printlog("failed to delete print job file '%s'", filename) return False + if not printer: + printlog.error("Error: the printer name is missing") + printlog.error(" printers available: %s", csv(printers.keys()) or "none") + delfile() + return + if printer not in printers: + printlog.error("Error: printer '%s' does not exist!", printer) + printlog.error(" printers available: %s", csv(printers.keys()) or "none") + delfile() + return job = print_files(printer, [filename], title, options) printlog("printing %s, job=%s", filename, job) if job<=0: @@ -183,7 +224,6 @@ def _open_file(self, filename): 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() @@ -194,3 +234,51 @@ def open_done(*args): 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) + + def send_file(self, filename, mimetype, data, filesize=0, printit=False, openit=False, options={}): + if printit: + if not self.printing: + printlog.warn("Warning: printing is not enabled for %s", self) + return False + if not self.remote_printing: + printlog.warn("Warning: remote end does not support printing") + return False + action = "print" + l = printlog + else: + if not self.file_transfer: + filelog.warn("Warning: file transfers are not enabled for %s", self) + return False + if not self.remote_file_transfer: + printlog.warn("Warning: remote end does not support file transfers") + return False + action = "upload" + l = filelog + l("send_file%s", (filename, mimetype, "%s bytes" % len(data), printit, openit, options)) + if not printit and openit and not self.remote_open_files: + l.warn("Warning: opening the file after transfer is disabled on the remote end") + openit = False + assert len(data)>=filesize, "data is smaller then the given file size!" + 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 %s the file '%s'", action, basefilename) + filelog.warn(" this file is too large: %sB", std_unit(filesize, unit=1024)) + filelog.warn(" the file size limit is %iMB", self.file_size_limit) + return False + if filesize>self.remote_file_size_limit*1024*1024: + filelog.warn("Warning: cannot %s the file '%s'", action, basefilename) + filelog.warn(" this file is too large: %sB", std_unit(filesize, unit=1024)) + filelog.warn(" the remote file size limit is %iMB", self.remote_file_size_limit) + return 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 diff --git a/src/xpra/platform/darwin/osx_menu.py b/src/xpra/platform/darwin/osx_menu.py index d8b6d6661d..74b3f2354f 100644 --- a/src/xpra/platform/darwin/osx_menu.py +++ b/src/xpra/platform/darwin/osx_menu.py @@ -223,7 +223,7 @@ def set_encodings_menu(*args): def add_ah(*args): if self.client.start_new_commands: actions_menu.add(self.make_startnewcommandmenuitem()) - if self.client.server_file_transfer: + if self.client.remote_file_transfer: actions_menu.add(self.make_uploadmenuitem()) self.client.after_handshake(add_ah) menus.append(("Actions", actions_menu)) diff --git a/src/xpra/server/server_base.py b/src/xpra/server/server_base.py index da411a2534..558465a8b6 100644 --- a/src/xpra/server/server_base.py +++ b/src/xpra/server/server_base.py @@ -21,6 +21,7 @@ clientlog = Logger("client") screenlog = Logger("screen") printlog = Logger("printing") +filelog = Logger("file") netlog = Logger("network") metalog = Logger("metadata") windowlog = Logger("window") @@ -45,7 +46,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 FileTransferHandler +from xpra.net.file_transfer import FileTransferAttributes if sys.version > '3': unicode = str #@ReservedAssignment @@ -76,7 +77,7 @@ def parse_env(env): return d -class ServerBase(ServerCore, FileTransferHandler): +class ServerBase(ServerCore): """ This is the base class for servers. It provides all the generic functions but is not tied @@ -86,7 +87,6 @@ class ServerBase(ServerCore, FileTransferHandler): def __init__(self): ServerCore.__init__(self) - FileTransferHandler.__init__(self) log("ServerBase.__init__()") self.init_uuid() @@ -132,6 +132,7 @@ def __init__(self): self.dbus_server = None self.lpadmin = "" self.lpinfo = "" + self.file_transfer = FileTransferAttributes() #starting child commands: self.child_display = None self.start_commands = [] @@ -242,13 +243,12 @@ def init_options(self, opts): self.env = parse_env(opts.env) self.send_pings = opts.pings #printing and file transfer: - FileTransferHandler.init(self, opts) + self.file_transfer.init_opts(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: - self.printing = os.name=="posix" and opts.printing self.postscript_printer = opts.postscript_printer self.pdf_printer = opts.pdf_printer self.notifications_forwarder = None @@ -353,13 +353,14 @@ def init_sockets(self, sockets): #verify we have a local socket for printing: nontcpsockets = [info for socktype, _, info in sockets if socktype not in ("tcp", "vsock")] printlog("local sockets we can use for printing: %s", nontcpsockets) - if not nontcpsockets: + if not nontcpsockets and self.file_transfer.printing: log.warn("Warning: no local sockets defined, cannot enable printing") - self.printing = False + self.file_transfer.printing = False def init_printing(self): - if not self.printing: + printing = self.file_transfer.printing + if not printing: return try: from xpra.platform import pycups_printing @@ -370,22 +371,24 @@ def init_printing(self): if self.pdf_printer: pycups_printing.add_printer_def("application/pdf", self.pdf_printer) printer_definitions = pycups_printing.validate_setup() - self.printing = bool(printer_definitions) - if self.printing: + printing = bool(printer_definitions) + if printing: printlog.info("printer forwarding enabled using %s", " and ".join([x.replace("application/", "") for x in printer_definitions.keys()])) else: printlog.warn("Warning: no printer definitions found, cannot enable printing") except ImportError as e: printlog("printing module is not installed: %s", e) - self.printing = False + printing = False except Exception: printlog.error("Error: failed to set lpadmin and lpinfo commands", exc_info=True) - self.printing = False + printing = False #verify that we can talk to the socket: - if self.printing and self.auth_class and self.auth_class!="none": + if printing and self.auth_class and self.auth_class!="none": log.warn("Warning: printing conflicts with socket authentication module '%s'", getattr(self.auth_class, "auth_name", self.auth_class)) - self.printing = False - printlog("init_printing() printing=%s", self.printing) + printing = False + #update file transfer attributes since printing nay have been disabled here + self.file_transfer.printing = printing + printlog("init_printing() printing=%s", printing) def init_webcam(self): if not self.webcam_forwarding: @@ -1054,7 +1057,7 @@ def get_window_id(wid): self.get_transient_for, self.get_focus, self.get_cursor_data, get_window_id, self.window_filters, - self.printing, + self.file_transfer, self.supports_mmap, self.av_sync, self.core_encodings, self.encodings, self.default_encoding, self.scaling_control, self.sound_properties, @@ -1066,7 +1069,6 @@ def get_window_id(wid): log("process_hello serversource=%s", ss) try: ss.parse_hello(c, self.min_mmap_size) - proto.max_packet_size = max(proto.max_packet_size, int(ss.file_transfer) * (1024 + ss.file_size_limit*1024*1024)) except: #close it already ss.close() @@ -1076,11 +1078,6 @@ def get_window_id(wid): send_ui = ui_client and not is_request self.idle_add(self.parse_hello_ui, ss, c, auth_caps, send_ui, share_count) - def accept_client(self, proto, c): - ServerCore.accept_client(self, proto, c) - #may need to bump file size limit for file transfers: - proto.max_packet_size = max(proto.max_packet_size, int(self.file_transfer) * (1024 + self.file_size_limit*1024*1024)) - def get_server_source_class(self): from xpra.server.source import ServerSource @@ -1295,7 +1292,7 @@ def make_hello(self, source): "webcam.encodings" : self.webcam_encodings, "virtual-video-devices" : self.virtual_video_devices, }) - capabilities.update(self.get_file_transfer_features()) + capabilities.update(self.file_transfer.get_file_transfer_features()) capabilities.update(flatten_dict(self.get_server_features())) #this is a feature, but we would need the hello request #to know if it is really needed.. so always include it: @@ -1347,7 +1344,7 @@ def _process_logging(self, proto, packet): clientlog.log(level, prefix+x) def _process_printers(self, proto, packet): - if not self.printing: + if not self.file_transfer.printing: log.error("Error: received printer definitions but this server does not support printing") return ss = self._server_sources.get(proto) @@ -1435,53 +1432,50 @@ def _control_get_sources(self, client_uuids_str, attr=None): return sources def control_command_send_file(self, filename, openit, client_uuids, maxbitrate=0): - actual_filename = os.path.abspath(os.path.expanduser(filename)) - if not os.path.exists(actual_filename): - raise ControlError("file '%s' does not exist" % filename) openit = str(openit).lower() in ("open", "true", "1") - #find the client uuid specified: + return self.do_control_file_command("send file", client_uuids, filename, "file_tranfer", (False, openit)) + + def control_command_print(self, filename, printer, client_uuids, maxbitrate=0, title="", *options_strs): + #parse options into a dict: + options = {} + for arg in options_strs: + argp = arg.split("=", 1) + if len(argp)==2 and len(argp[0])>0: + options[argp[0]] = argp[1] + return self.do_control_file_command("print", client_uuids, filename, "printing", (True, True, options)) + + def do_control_file_command(self, command_type, client_uuids, filename, source_flag_name, send_file_args): + #find the clients: sources = self._control_get_sources(client_uuids) if not sources: raise ControlError("no clients found matching: %s" % client_uuids) - data = load_binary_file(actual_filename) - file_size_MB = len(data)//1024//1024 - if file_size_MB>self.file_size_limit: - raise ControlError("file '%s' is too large: %iMB (limit is %iMB)" % (filename, file_size_MB, self.file_size_limit)) - for ss in sources: - if ss.file_transfer: - ss.send_file(filename, "", data, False, openit) - else: - log.warn("cannot send file, client %s does not support file transfers!", ss) - return "file transfer of '%s' to %s initiated" % (filename, client_uuids) - - def control_command_print(self, filename, printer, client_uuids, maxbitrate=0, title="", *options_strs): + #find the file and load it: actual_filename = os.path.abspath(os.path.expanduser(filename)) try: stat = os.stat(actual_filename) - printlog("os.stat(%s)=%s", actual_filename, stat) + filelog("os.stat(%s)=%s", actual_filename, stat) except os.error: - printlog("os.stat(%s)", actual_filename, exc_info=True) + filelog("os.stat(%s)", actual_filename, exc_info=True) if not os.path.exists(actual_filename): raise ControlError("file '%s' does not exist" % filename) - sources = self._control_get_sources(client_uuids) - if not sources: - raise ControlError("no clients found matching: %s" % client_uuids) - #parse options into a dict: - options = {} - for arg in options_strs: - argp = arg.split("=", 1) - if len(argp)==2 and len(argp[0])>0: - options[argp[0]] = argp[1] data = load_binary_file(actual_filename) + #verify size: file_size_MB = len(data)//1024//1024 if file_size_MB>self.file_size_limit: raise ControlError("file '%s' is too large: %iMB (limit is %iMB)" % (filename, file_size_MB, self.file_size_limit)) + return sources, data + #send it to each client: for ss in sources: - if ss.printing: - ss.send_file(filename, "", data, True, True, options) + if not getattr(ss, source_flag_name): #ie: ServerSource.file_transfer + log.warn("Warning: cannot %s '%s'", command_type, filename) + log.warn(" client %s does not support this feature", ss) + elif file_size_MB>ss.file_size_limit: + log.warn("Warning: cannot %s '%s'", command_type, filename) + log.warn(" client %s file size limit is %iMB (file is %iMB)", ss, ss.file_size_limit, file_size_MB) else: - printlog.warn("client %s does not support printing!", ss) - return "printing to %s initiated" % client_uuids + ss.send_file(filename, "", data, *send_file_args) + return "%s of '%s' to %s initiated" % (command_type, client_uuids) + def control_command_compression(self, compression): c = compression.lower() @@ -1707,13 +1701,16 @@ def send_screenshot(self, proto): def _process_send_file(self, proto, packet): - #superclass does not take the protocol as argument: - FileTransferHandler._process_send_file(self, packet) + ss = self._server_sources.get(proto) + if not ss: + log.warn("Warning: invalid client source for file packet") + return + ss._process_send_file(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] - assert self.printing + assert self.file_transfer.printing #printlog("_process_print(%s, %s)", proto, packet) if len(packet)<9: log.error("Error: invalid print packet, only %i arguments", len(packet)) @@ -1760,8 +1757,8 @@ def _process_print(self, proto, packet): if printer not in ss.printers: printlog.warn("Warning: client %s does not have a '%s' printer", ss.uuid, printer) continue - printlog("sending file to %s for printing on %s", ss, printer) - if ss.send_file(filename, mimetype, file_data, True, True, options): + printlog("'%s' sent to %s for printing on '%s'", title or filename, ss, printer) + if ss.send_file(filename, mimetype, file_data, len(file_data), True, True, options): sent += 1 #warn if not sent: if sent==0: @@ -1769,7 +1766,7 @@ def _process_print(self, proto, packet): else: l = printlog.info unit_str, v = to_std_unit(len(file_data), unit=1024) - l("file %s (%i%sB) sent to %i client%s", filename, v, unit_str, sent, engs(sent)) + l("'%s' (%i%sB) sent to %i client%s for printing", title or filename, v, unit_str, sent, engs(sent)) def _process_start_command(self, proto, packet): @@ -1844,9 +1841,11 @@ def get_info(self, proto=None, client_uuids=None, wids=None, *args): def get_printing_info(self): - d = {"enabled" : self.printing, - "lpadmin" : self.lpadmin} - if self.printing: + d = { + "lpadmin" : self.lpadmin, + "lpinfo" : self.lpinfo, + } + if self.file_transfer.printing: from xpra.platform.printing import get_info d.update(get_info()) return d @@ -1879,7 +1878,6 @@ def get_features_info(self): "rpc-types" : self.rpc_handlers.keys(), "clipboard" : self.supports_clipboard, "idle_timeout" : self.idle_timeout, - "file-size-limit" : self.file_size_limit, } i.update(self.get_server_features()) return i @@ -1927,8 +1925,8 @@ def do_get_info(self, proto, server_sources=None, window_ids=None): def up(prefix, d): info[prefix] = d + up("file", self.file_transfer.get_info()) up("webcam", self.get_webcam_info()) - up("file", self.get_file_transfer_info()) up("printing", self.get_printing_info()) up("commands", self.get_commands_info()) up("features", self.get_features_info()) diff --git a/src/xpra/server/server_core.py b/src/xpra/server/server_core.py index c481bb8768..de9a72bac6 100644 --- a/src/xpra/server/server_core.py +++ b/src/xpra/server/server_core.py @@ -444,7 +444,7 @@ def print_run_info(self): def print_screen_info(self): display = os.environ.get("DISPLAY") if display and display.startswith(":"): - log.info(" on display %s", display) + log.info(" connected to X11 display %s", display) def server_is_ready(self): diff --git a/src/xpra/server/source.py b/src/xpra/server/source.py index aa499abfe2..7443cc951e 100644 --- a/src/xpra/server/source.py +++ b/src/xpra/server/source.py @@ -38,6 +38,7 @@ from xpra.codecs.codec_constants import video_spec from xpra.net import compression from xpra.net.compression import compressed_wrapper, Compressed, Uncompressed +from xpra.net.file_transfer import FileTransferHandler from xpra.make_thread import make_thread from xpra.os_util import platform_name, Queue, get_machine_id, get_user_uuid, BytesIOClass from xpra.server.background_worker import add_work_item @@ -200,7 +201,7 @@ def evaluate(self, window_value): return not(WindowPropertyIn.evaluate(window_value)) -class ServerSource(object): +class ServerSource(FileTransferHandler): """ A ServerSource represents a client connection. It mediates between the server class (which only knows about actual window objects and display server events) @@ -226,7 +227,7 @@ def __init__(self, protocol, disconnect_cb, idle_add, timeout_add, source_remove get_transient_for, get_focus, get_cursor_data_cb, get_window_id, window_filters, - printing, + file_transfer, supports_mmap, av_sync, core_encodings, encodings, default_encoding, scaling_control, sound_properties, @@ -241,7 +242,7 @@ def __init__(self, protocol, disconnect_cb, idle_add, timeout_add, source_remove get_transient_for, get_focus, get_window_id, window_filters, - printing, + file_transfer, supports_mmap, av_sync, core_encodings, encodings, default_encoding, scaling_control, sound_properties, @@ -250,6 +251,7 @@ def __init__(self, protocol, disconnect_cb, idle_add, timeout_add, source_remove speaker_codecs, microphone_codecs, default_quality, default_min_quality, default_speed, default_min_speed)) + FileTransferHandler.__init__(self, file_transfer) global counter self.counter = counter.increase() self.close_event = Event() @@ -302,8 +304,6 @@ def __init__(self, protocol, disconnect_cb, idle_add, timeout_add, source_remove self.av_sync_delay_total = 0 self.av_sync_delta = AV_SYNC_DELTA - self.printing = printing - self.server_core_encodings = core_encodings self.server_encodings = encodings self.default_encoding = default_encoding @@ -416,8 +416,6 @@ def init_vars(self): self.metadata_supported = [] self.show_desktop_allowed = False self.supports_transparency = False - self.file_transfer = False - self.file_size_limit = 10 self.printers = {} self.vrefresh = -1 self.double_click_time = -1 @@ -680,6 +678,8 @@ def parse_batch_int(value, varname): self.client_revision = c.strget("build.revision") self.client_proxy = c.boolget("proxy") self.client_wm_name = c.strget("wm_name") + #file transfers and printing: + self.parse_file_transfer_caps(c) #general features: self.zlib = c.boolget("zlib", True) self.lz4 = c.boolget("lz4", False) and compression.use_lz4 @@ -698,9 +698,6 @@ def parse_batch_int(value, varname): self.clipboard_notifications = c.boolget("clipboard.notifications") self.clipboard_set_enabled = c.boolget("clipboard.set_enabled") self.share = c.boolget("share") - self.file_transfer = c.boolget("file-transfer") - self.file_size_limit = c.intget("file-size-limit") - self.printing = self.printing and c.boolget("printing") self.window_initiate_moveresize = c.boolget("window.initiate-moveresize") self.system_tray = c.boolget("system_tray") self.control_commands = c.strlistget("control_commands") @@ -794,6 +791,9 @@ def parse_batch_int(value, varname): self.add_window_filter(object_name, property_name, operator, value) except Exception as e: log.error("Error parsing window-filters: %s", e) + #adjust max packet size if file transfers are enabled: + if self.file_transfer: + self.protocol.max_packet_size = max(self.protocol.max_packet_size, self.file_size_limit*1024*1024) def parse_encoding_caps(self, c, min_mmap_size): @@ -1464,7 +1464,8 @@ def addattr(k, name): finfo[i] = str(f) i += 1 info["window-filter"] = finfo - info.update(self.get_sound_info()) + info["file-transfers"] = FileTransferHandler.get_info(self) + info["sound"] = self.get_sound_info() info.update(self.get_features_info()) info.update(self.get_screen_info()) return info @@ -1478,8 +1479,7 @@ def battr(k, prop): info[k] = bool(getattr(self, prop)) for prop in ("share", "randr_notify", "clipboard_notifications", "system_tray", - "lz4", "lzo", - "printing", "file_transfer"): + "lz4", "lzo"): battr(prop, prop) for prop, name in {"clipboard_enabled" : "clipboard", "send_windows" : "windows", @@ -1804,38 +1804,6 @@ def remove_printers(self): remove_printer(k) - def send_file(self, filename, mimetype, data, printit, openit, options={}): - if printit: - if not self.printing: - printlog.warn("Warning: printing is not enabled for %s", self) - return False - action = "print" - l = printlog - else: - if not self.file_transfer: - filelog.warn("Warning: file transfers are not enabled for %s", self) - return False - action = "transfer" - l = filelog - l("send_file%s", (filename, mimetype, "%s bytes" % len(data), printit, openit, options)) - #TODO: - # * client ACK - # * stream it (don't load the whole file!) - # * timeouts - # * rate control - # * xpra info with queue and progress - basefilename = os.path.basename(filename) - filesize = len(data) - 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: - 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 - self.send("send-file", basefilename, mimetype, printit, openit, filesize, cdata, options) - return True - def send_client_command(self, *args): self.send("control", *args)