def _process_send_data_response(self, packet): send_id, accept = packet[1:3] send_id = net_utf8(send_id) filelog("process send-data-response: send_id=%s, accept=%s", send_id, accept) timer = self.pending_send_data_timers.pop(send_id, None) if timer: self.source_remove(timer) v = self.pending_send_data.pop(send_id, None) if v is None: filelog.warn("Warning: cannot find send-file entry") return dtype = net_utf8(v[0]) url = net_utf8(v[1]) if accept==DENY: filelog.info("the request to send %s '%s' has been denied", dtype, url) return assert accept in (ACCEPT, OPEN), "unknown value for send-data response: %s" % (accept,) if dtype=="file": mimetype, data, filesize, printit, openit, options = v[2:] if accept==ACCEPT: self.do_send_file(url, mimetype, data, filesize, printit, openit, options, send_id) else: assert openit and accept==OPEN #try to open at this end: self._open_file(url) elif dtype=="url": if accept==ACCEPT: self.do_send_open_url(url, send_id) else: assert accept==OPEN #open it at this end: self._open_url(url) else: filelog.error("Error: unknown datatype '%s'", dtype)
def _process_notify_show(self, packet): if not self.notifications_enabled: log("process_notify_show: ignoring packet, notifications are disabled") return self._ui_event() dbus_id, nid, app_name, replaces_nid, app_icon, summary, body, expire_timeout = packet[1:9] icon, actions, hints = None, [], {} if len(packet)>=10: icon = packet[9] if len(packet)>=12: actions, hints = packet[10], packet[11] #note: if the server doesn't support notification forwarding, #it can still send us the messages (via xpra control or the dbus interface) log("_process_notify_show(%s) notifier=%s, server_notifications=%s", repr_ellipsized(packet), self.notifier, self.server_notifications) log("notification actions=%s, hints=%s", actions, hints) assert self.notifier #this one of the few places where we actually do care about character encoding: summary = net_utf8(summary) body = net_utf8(body) app_name = net_utf8(app_name) tray = self.get_tray_window(app_name, hints) log("get_tray_window(%s)=%s", app_name, tray) self.notifier.show_notify(dbus_id, tray, nid, app_name, replaces_nid, app_icon, summary, body, actions, hints, expire_timeout, icon)
def _process_logging(self, proto, packet): assert self.remote_logging_receive ss = self.get_server_source(proto) if ss is None: return level, msg = packet[1:3] prefix = "client " counter = getattr(ss, "counter", 0) if counter>0: prefix += "%3i " % counter if len(packet)>=4: dtime = packet[3] prefix += "@%02i.%03i " % ((dtime//1000)%60, dtime%1000) try: if isinstance(msg, (tuple, list)): dmsg = " ".join(net_utf8(x) for x in msg) else: dmsg = net_utf8(msg) for l in dmsg.splitlines(): self.do_log(level, prefix+l) except Exception as e: log("log message decoding error", exc_info=True) log.error("Error: failed to parse logging message:") log.error(" %s", repr_ellipsized(msg)) log.error(" %s", e)
def _process_send_data_request(self, packet): dtype, send_id, url, _, filesize, printit, openit = packet[1:8] options = {} if len(packet)>=9: options = packet[8] #filenames and url are always sent encoded as utf8: url = net_utf8(url) dtype = net_utf8(dtype) send_id = net_utf8(send_id) self.do_process_send_data_request(dtype, send_id, url, _, filesize, printit, openit, typedict(options))
def _process_open_url(self, packet): send_id = net_utf8(packet[2]) url = net_utf8(packet[1]) if not self.open_url: filelog.warn("Warning: received a request to open URL '%s'", url) filelog.warn(" but opening of URLs is disabled") return if not self.open_url_ask or self.accept_data(send_id, "url", url, False, True): self._open_url(url) else: filelog("url '%s' not accepted", url)
def getvar(var): #"hostname" is magic: #we try harder to find a useful value to show: if var in ("hostname", "hostinfo"): if var == "hostinfo" and getattr(self._client, "mmap_enabled", False): #this is a local connection for sure server_display = getattr(self._client, "server_display", None) if server_display: return server_display #try to find the hostname: proto = getattr(self._client, "_protocol", None) if proto: conn = getattr(proto, "_conn", None) if conn: hostname = conn.info.get("host") or bytestostr( conn.target) if hostname: return hostname for m in ("client-machine", "server-machine"): value = getvar(m) if value not in ("localhost", "localhost.localdomain", "", None): return value return UNKNOWN_MACHINE value = metadata.get(var) or self._metadata.get(var) if value is None: return default_values.get(var, "<unknown %s>" % var) return net_utf8(value)
def control_command_send_file(self, filename, openit="open", client_uuids="*", maxbitrate=0): #we always get the values as strings from the command interface, #but those may actually be utf8 encoded binary strings, #so we may have to do an ugly roundtrip: filename = net_utf8(filename) openit = str(openit).lower() in ("open", "true", "1") return self.do_control_file_command("send file", client_uuids, filename, "file_transfer", (False, openit))
def got_token(self, targets, target_data=None, claim=True, synchronous_client=False): # the remote end now owns the clipboard self.cancel_emit_token() if not self._enabled: return self._got_token_events += 1 log( "got token, selection=%s, targets=%s, target data=%s, claim=%s, synchronous_client=%s, can-receive=%s", self._selection, targets, target_data, claim, synchronous_client, self._can_receive) if claim: self._have_token = True if not self._can_receive: return if target_data and claim: targets = target_data.keys() text_targets = tuple(x for x in targets if x in TEXT_TARGETS) for text_target in text_targets: dtype, dformat, data = target_data.get(text_target) if dformat != 8: continue text = net_utf8(data) log("setting text data %s / %s of size %i: %s", dtype, dformat, len(text), ellipsizer(text)) self._block_owner_change = monotonic() self.clipboard.set_text(text, len(text)) return
def udict(d): #with rencode, we may get bytes instead of strings: t = typedict() for k, v in d.items(): if isinstance(k, bytes): k = net_utf8(k) t[k] = v return t
def _process_ack_file_chunk(self, packet): #the other end received our send-file or send-file-chunk, #send some more file data filelog("ack-file-chunk: %s", packet[1:]) chunk_id, state, error_message, chunk = packet[1:5] chunk_id = net_utf8(chunk_id) if not state: filelog.info("the remote end is cancelling the file transfer:") filelog.info(" %s", net_utf8(error_message)) self.cancel_sending(chunk_id) return chunk_state = self.send_chunks_in_progress.get(chunk_id) if not chunk_state: filelog.error("Error: cannot find the file transfer id '%r'", chunk_id) return if chunk_state[-1] != chunk: filelog.error("Error: chunk number mismatch (%i vs %i)", chunk_state, chunk) self.cancel_sending(chunk_id) return start_time, data, chunk_size, timer, chunk = chunk_state if not data: #all sent! elapsed = monotonic() - start_time filelog("%i chunks of %i bytes sent in %ims (%sB/s)", chunk, chunk_size, elapsed * 1000, std_unit(chunk * chunk_size / elapsed)) self.cancel_sending(chunk_id) return assert chunk_size > 0 #carve out another chunk: cdata = self.compressed_wrapper("file-data", data[:chunk_size]) data = data[chunk_size:] chunk += 1 if timer: self.source_remove(timer) timer = self.timeout_add(CHUNK_TIMEOUT, self._check_chunk_sending, chunk_id, chunk) self.send_chunks_in_progress[chunk_id] = [ start_time, data, chunk_size, timer, chunk ] self.send("send-file-chunk", chunk_id, chunk, cdata, bool(data))
def _process_logging(self, packet): assert not self.local_logging, "cannot receive logging packets when forwarding logging!" level, msg = packet[1:3] prefix = "server: " if len(packet)>=4: dtime = packet[3] prefix += "@%02i.%03i " % ((dtime//1000)%60, dtime%1000) try: if isinstance(msg, (tuple, list)): dmsg = " ".join(net_utf8(x) for x in msg) else: dmsg = net_utf8(msg) for l in dmsg.splitlines(): self.do_log(level, prefix+l) except Exception as e: log("log message decoding error", exc_info=True) log.error("Error: failed to parse logging message:") log.error(" %s", repr_ellipsized(msg)) log.error(" %s", e)
def _process_key_action(self, proto, packet): if self.readonly: return wid, keyname, pressed, modifiers, keyval, keystr, client_keycode, group = packet[ 1:9] ss = self.get_server_source(proto) if ss is None: return keyname = net_utf8(keyname) keystr = net_utf8(keystr) modifiers = list(net_utf8(x) for x in modifiers) self.set_ui_driver(ss) keycode, group = self.get_keycode(ss, client_keycode, keyname, pressed, modifiers, keyval, keystr, group) keylog("process_key_action(%s) server keycode=%s, group=%i", packet, keycode, group) if group >= 0 and keycode >= 0: self.set_keyboard_layout_group(group) #currently unused: (group, is_modifier) = packet[8:10] self._focus(ss, wid, None) ss.make_keymask_match(modifiers, keycode, ignored_modifier_keynames=[keyname]) #negative keycodes are used for key events without a real keypress/unpress #for example, used by win32 to send Caps_Lock/Num_Lock changes if keycode >= 0: try: is_mod = ss.is_modifier(keyname, keycode) self._handle_key(wid, pressed, keyname, keyval, keycode, modifiers, is_mod, ss.keyboard_config.sync) except Exception as e: keylog("process_key_action%s", (proto, packet), exc_info=True) keylog.error("Error: failed to %s key", ["unpress", "press"][pressed]) keylog.error(" %s", e) keylog.error(" for keyname=%s, keyval=%i, keycode=%i", keyname, keyval, keycode) ss.user_event()
def remove_printer(self, name): printer = net_utf8(name) try: self.printers_added.remove(printer) except KeyError: log("not removing printer '%s' - since we didn't add it", name) else: try: from xpra.platform.pycups_printing import remove_printer remove_printer(printer) log.info("removed remote printer '%s'", printer) except Exception as e: log("remove_printer(%s)", printer, exc_info=True) log.error("Error: failed to remove printer '%s':", name) log.error(" %s", e)
def _process_shell_reply(self, packet): fd = packet[1] message = packet[2] if fd==1: stream = sys.stdout elif fd==2: stream = sys.stderr else: raise Exception("invalid file descriptor %i" % fd) s = net_utf8(message) if s.endswith("\n"): s = s[:-1] stream.write("%s" % s) stream.flush() if fd==2: stream.write("\n") self.print_prompt()
def got_contents(self, target, dtype=None, dformat=None, data=None): #if this is the special target 'TARGETS', cache the result: if target=="TARGETS" and dtype=="ATOM" and dformat==32: self.targets = _filter_targets(data) log("got_contents: tell OS we have %s", csv(self.targets)) image_types = tuple(t for t in IMAGE_FORMATS if t in self.targets) log("image_types=%s, dtype=%s (is text=%s)", image_types, dtype, dtype in TEXT_TARGETS) if image_types and dtype not in TEXT_TARGETS: #request image: self.send_clipboard_request_handler(self, self._selection, image_types[0]) return if dformat==8 and dtype in TEXT_TARGETS: log("we got a byte string: %s", data) self.set_clipboard_text(net_utf8(data)) if dformat==8 and dtype in IMAGE_FORMATS: log("we got a %s image", dtype) self.set_image_data(dtype, data)
def parse_client_caps(self, c: typedict): self.vrefresh = c.intget("vrefresh", -1) self.randr_notify = c.boolget("randr_notify") self.desktop_size = c.intpair("desktop_size") if self.desktop_size is not None: w, h = self.desktop_size if w <= 0 or h <= 0 or w >= 32768 or h >= 32768: log.warn("ignoring invalid desktop dimensions: %sx%s", w, h) self.desktop_size = None self.desktop_mode_size = c.intpair("desktop_mode_size") self.desktop_size_unscaled = c.intpair("desktop_size.unscaled") self.screen_resize_bigger = c.boolget("screen-resize-bigger", True) self.set_screen_sizes(c.tupleget("screen_sizes")) desktop_names = tuple(net_utf8(x) for x in c.tupleget("desktop.names")) self.set_desktops(c.intget("desktops", 1), desktop_names) self.show_desktop_allowed = c.boolget("show-desktop") self.icc = c.dictget("icc", {}) self.display_icc = c.dictget("display-icc", {}) self.opengl_props = c.dictget("opengl", {})
def got_contents(self, target, dtype=None, dformat=None, data=None): #if this is the special target 'TARGETS', cache the result: if target == "TARGETS" and dtype == "ATOM" and dformat == 32: self.targets = _filter_targets(data) #TODO: tell system what targets we have log("got_contents: tell OS we have %s", csv(self.targets)) image_formats = tuple(x for x in ("image/png", "image/jpeg") if x in self.targets) if image_formats: #request it: self.send_clipboard_request_handler(self, self._selection, image_formats[0]) elif dformat == 8 and dtype in TEXT_TARGETS: log("we got a byte string: %s", data) self.set_clipboard_text(net_utf8(data)) elif dformat == 8 and dtype.startswith("image/"): img_format = dtype.split("/")[-1] #ie: 'png' self.set_clipboard_image(img_format, data) else: log("no handling: target=%s, dtype=%s, dformat=%s, data=%s", target, dtype, dformat, ellipsizer(data))
def set_desktops(self, desktops, desktop_names): self.desktops = desktops or 1 self.desktop_names = tuple(net_utf8(d) for d in (desktop_names or ()))
def set_metadata(self, metadata): metalog("set_metadata(%s)", metadata) debug_props = [x for x in PROPERTIES_DEBUG if x in metadata.keys()] for x in debug_props: metalog.info("set_metadata: %s=%s", x, metadata.get(x)) #WARNING: "class-instance" needs to go first because others may realize the window #(and GTK doesn't set the "class-instance" once the window is realized) if "class-instance" in metadata: self.set_class_instance( *self._metadata.strtupleget("class-instance", ("xpra", "Xpra"), 2, 2)) self.reset_icon() if "title" in metadata: title = self._get_window_title(metadata) self.set_title(title) if "icon-title" in metadata: icon_title = metadata.strget("icon-title", "") self.set_icon_name(net_utf8(icon_title)) #the DE may have reset the icon now, #force it to use the one we really want: self.reset_icon() if "size-constraints" in metadata: sc = typedict(metadata.dictget("size-constraints", {})) self.size_constraints = sc self._set_initial_position = sc.boolget("set-initial-position", self._set_initial_position) self.set_size_constraints(sc, self.max_window_size) if "set-initial-position" in metadata: #this should be redundant - but we keep it here for consistency self._set_initial_position = metadata.boolget( "set-initial-position") if "transient-for" in metadata: wid = metadata.intget("transient-for", -1) self.apply_transient_for(wid) if "modal" in metadata: modal = metadata.boolget("modal") self.set_modal(modal) #apply window-type hint if window has not been mapped yet: if "window-type" in metadata and not self.get_mapped(): window_types = metadata.strtupleget("window-type") self.set_window_type(window_types) if "role" in metadata: role = metadata.strget("role") self.set_role(role) if "xid" in metadata: xid = metadata.strget("xid") self.set_xid(xid) if "opacity" in metadata: opacity = metadata.intget("opacity", -1) if opacity < 0: opacity = 1 else: opacity = min(1, opacity / 0xffffffff) #requires gtk>=2.12! if hasattr(self, "set_opacity"): self.set_opacity(opacity) if "has-alpha" in metadata: new_alpha = metadata.boolget("has-alpha") if new_alpha != self._has_alpha: l = alphalog if not WIN32: #win32 without opengl can't do transparency, #so it triggers too many warnings l = log.warn l("Warning: window %#x changed its transparency attribute", self._id) l(" from %s to %s, behaviour is undefined", self._has_alpha, new_alpha) self._has_alpha = new_alpha if "maximized" in metadata: maximized = metadata.boolget("maximized") if maximized != self._maximized: self._maximized = maximized if maximized: self.maximize() else: self.unmaximize() if "fullscreen" in metadata: fullscreen = metadata.boolget("fullscreen") if self._fullscreen is None or self._fullscreen != fullscreen: self._fullscreen = fullscreen self.set_fullscreen(fullscreen) if "iconic" in metadata: iconified = metadata.boolget("iconic") if self._iconified != iconified: self._iconified = iconified if iconified: self.iconify() else: self.deiconify() if "decorations" in metadata: decorated = metadata.boolget("decorations", True) was_decorated = self.get_decorated() if WIN32 and decorated != was_decorated: log.info( "decorations flag toggled, now %s, re-initializing window", decorated) self.idle_add(self._client.reinit_window, self._id, self) else: self.set_decorated(metadata.boolget("decorations")) self.apply_geometry_hints(self.geometry_hints) if "above" in metadata: above = metadata.boolget("above") if self._above != above: self._above = above self.set_keep_above(above) if "below" in metadata: below = metadata.boolget("below") if self._below != below: self._below = below self.set_keep_below(below) if "shaded" in metadata: shaded = metadata.boolget("shaded") if self._shaded != shaded: self._shaded = shaded self.set_shaded(shaded) if "sticky" in metadata: sticky = metadata.boolget("sticky") if self._sticky != sticky: self._sticky = sticky if sticky: self.stick() else: self.unstick() if "skip-taskbar" in metadata: skip_taskbar = metadata.boolget("skip-taskbar") if self._skip_taskbar != skip_taskbar: self._skip_taskbar = skip_taskbar self.set_skip_taskbar_hint(skip_taskbar) if "skip-pager" in metadata: skip_pager = metadata.boolget("skip-pager") if self._skip_pager != skip_pager: self._skip_pager = skip_pager self.set_skip_pager_hint(skip_pager) if "workspace" in metadata: self.set_workspace(metadata.intget("workspace")) if "bypass-compositor" in metadata: self.set_bypass_compositor(metadata.intget("bypass-compositor")) if "strut" in metadata: self.set_strut(metadata.dictget("strut", {})) if "fullscreen-monitors" in metadata: self.set_fullscreen_monitors( metadata.inttupleget("fullscreen-monitors")) if "shape" in metadata: self.set_shape(metadata.dictget("shape", {})) if "command" in metadata: self.set_command(metadata.strget("command")) if "x11-property" in metadata: self.set_x11_property(*metadata.tupleget("x11-property")) if "content-type" in metadata: self.content_type = metadata.strget("content-type")
def add_request(self, cb_answer, send_id, dtype, url, filesize, printit, openit, timeout): expires = monotonic()+timeout self.requests.append((cb_answer, net_utf8(send_id), net_utf8(dtype), net_utf8(url), filesize, printit, openit, expires)) self.populate_table() if not self.populate_timer: self.schedule_timer()
def set_printers(self, printers, password_file, auth, encryption, encryption_keyfile): log("set_printers%s for %s", (printers, password_file, auth, encryption, encryption_keyfile), self) if self.machine_id == get_machine_id() and not ADD_LOCAL_PRINTERS: self.printers = printers log("local client with identical machine id,") log(" not configuring local printers") return if not self.uuid: log.warn("Warning: client did not supply a UUID,") log.warn(" printer forwarding cannot be enabled") return #remove the printers no longer defined #or those whose definition has changed (and we will re-add them): for k in tuple(self.printers.keys()): cpd = self.printers.get(k) npd = printers.get(k) if cpd == npd: #unchanged: make sure we don't try adding it again: printers.pop(k, None) continue if npd is None: log("printer %s no longer exists", k) else: log("printer %s has been modified:", k) log(" was %s", cpd) log(" now %s", npd) #remove it: self.printers.pop(k, None) self.remove_printer(k) #expand it here so the xpraforwarder doesn't need to import anything xpra: attributes = { "display": os.environ.get("DISPLAY"), "source": self.uuid } def makeabs(filename): #convert to an absolute path since the backend may run as a different user: return os.path.abspath(os.path.expanduser(filename)) if auth: auth_password_file = None try: name, _, authclass, authoptions = auth auth_password_file = authoptions.get("file") log("file for %s / %s: '%s'", name, authclass, password_file) except Exception as e: log.error( "Error: cannot forward authentication attributes to printer backend:" ) log.error(" %s", e) if auth_password_file or password_file: attributes["password-file"] = makeabs(auth_password_file or password_file[0]) if encryption: if not encryption_keyfile: log.error("Error: no encryption keyfile found for printing") else: attributes["encryption"] = encryption attributes["encryption-keyfile"] = makeabs(encryption_keyfile) #if we can, tell it exactly where to connect: if self.unix_socket_paths: #prefer sockets in public paths: attributes["socket-path"] = self.choose_socket_path() log("printer attributes: %s", attributes) for name, props in printers.items(): printer = net_utf8(name) if printer not in self.printers: self.setup_printer(printer, props, attributes)
def _process_send_file(self, packet): #the remote end is sending us a file start = monotonic() basefilename, mimetype, printit, openit, filesize, file_data, options = packet[1:8] send_id = "" if len(packet)>=9: send_id = net_utf8(packet[8]) #basefilename should be utf8: basefilename = net_utf8(basefilename) mimetype = net_utf8(mimetype) if filesize<=0: filelog.error("Error: invalid file size: %s", filesize) filelog.error(" file transfer aborted for %r", basefilename) return args = (send_id, "file", basefilename, printit, openit) r = self.accept_data(*args) filelog("%s%s=%s", self.accept_data, args, r) if r is None: filelog.warn("Warning: %s rejected for file '%s'", ("transfer", "printing")[bool(printit)], basefilename) return #accept_data can override the flags: printit, openit = r options = typedict(options) if printit: l = printlog assert self.printing else: l = filelog assert self.file_transfer l("receiving file: %s", [basefilename, mimetype, printit, openit, filesize, "%s bytes" % len(file_data), options]) if filesize>self.file_size_limit: l.error("Error: file '%s' is too large:", basefilename) l.error(" %sB, the file size limit is %sB", std_unit(filesize), std_unit(self.file_size_limit)) return chunk_id = options.strget("file-chunk-id") try: filename, fd = safe_open_download_file(basefilename, mimetype) except OSError as e: filelog("cannot save file %s / %s", basefilename, mimetype, exc_info=True) filelog.error("Error: failed to save downloaded file") filelog.error(" %s", e) if chunk_id: self.send("ack-file-chunk", chunk_id, False, "failed to create file: %s" % e, 0) return self.file_descriptors.add(fd) if chunk_id: l = len(self.receive_chunks_in_progress) if l>=MAX_CONCURRENT_FILES: self.send("ack-file-chunk", chunk_id, False, "too many file transfers in progress: %i" % l, 0) os.close(fd) return digest = hashlib.sha256() chunk = 0 timer = self.timeout_add(CHUNK_TIMEOUT, self._check_chunk_receiving, chunk_id, chunk) chunk_state = [ monotonic(), fd, filename, mimetype, printit, openit, filesize, options, digest, 0, False, send_id, timer, chunk, ] self.receive_chunks_in_progress[chunk_id] = chunk_state self.send("ack-file-chunk", chunk_id, True, "", chunk) return #not chunked, full file: assert file_data, "no data, got %s" % (file_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 #check digest if present: def check_digest(algo="sha256", libfn=hashlib.sha256): digest = options.get(algo) if digest: h = libfn() h.update(file_data) l("%s digest: %s - expected: %s", algo, h.hexdigest(), digest) if digest!=h.hexdigest(): self.digest_mismatch(filename, digest, h.hexdigest(), algo) check_digest("sha256", hashlib.sha256) check_digest("sha1", hashlib.sha1) check_digest("md5", hashlib.md5) try: os.write(fd, file_data) finally: os.close(fd) self.transfer_progress_update(False, send_id, monotonic()-start, filesize, filesize, None) self.process_downloaded_file(filename, mimetype, printit, openit, filesize, options)
def _process_send_file_chunk(self, packet): chunk_id, chunk, file_data, has_more = packet[1:5] chunk_id = net_utf8(chunk_id) filelog("_process_send_file_chunk%s", (chunk_id, chunk, "%i bytes" % len(file_data), has_more)) chunk_state = self.receive_chunks_in_progress.get(chunk_id) if not chunk_state: filelog.error("Error: cannot find the file transfer id '%r'", chunk_id) self.cancel_file(chunk_id, "file transfer id %r not found" % chunk_id, chunk) return if chunk_state[-4]: filelog("got chunk for a cancelled file transfer, ignoring it") return def progress(position, error=None): start = chunk_state[0] send_id = chunk_state[-3] filesize = chunk_state[6] self.transfer_progress_update(False, send_id, monotonic()-start, position, filesize, error) fd = chunk_state[1] if chunk_state[-1]+1!=chunk: filelog.error("Error: chunk number mismatch, expected %i but got %i", chunk_state[-1]+1, chunk) self.cancel_file(chunk_id, "chunk number mismatch", chunk) osclose(fd) progress(-1, "chunk no mismatch") return file_data = strtobytes(file_data) #update chunk number: chunk_state[-1] = chunk digest = chunk_state[8] written = chunk_state[9] try: os.write(fd, file_data) digest.update(file_data) written += len(file_data) chunk_state[9] = written except OSError as e: filelog.error("Error: cannot write file chunk") filelog.error(" %s", e) self.cancel_file(chunk_id, "write error: %s" % e, chunk) osclose(fd) progress(-1, "write error (%s)" % e) return self.send("ack-file-chunk", chunk_id, True, "", chunk) if has_more: progress(written) timer = chunk_state[-2] if timer: self.source_remove(timer) #remote end will send more after receiving the ack timer = self.timeout_add(CHUNK_TIMEOUT, self._check_chunk_receiving, chunk_id, chunk) chunk_state[-2] = timer return self.receive_chunks_in_progress.pop(chunk_id, None) osclose(fd) #check file size and digest then process it: filename, mimetype, printit, openit, filesize, options = chunk_state[2:8] if written!=filesize: filelog.error("Error: expected a file of %i bytes, got %i", filesize, written) progress(-1, "file size mismatch") return expected_digest = options.strget("sha1") if expected_digest and digest.hexdigest()!=expected_digest: progress(-1, "checksum mismatch") self.digest_mismatch(filename, digest, expected_digest, "sha1") return progress(written) start_time = chunk_state[0] elapsed = monotonic()-start_time mimetype = bytestostr(mimetype) filelog("%i bytes received in %i chunks, took %ims", filesize, chunk, elapsed*1000) self.process_downloaded_file(filename, mimetype, printit, openit, filesize, options)