xpra icon
Bug tracker and wiki

Ticket #2678: file-transfer-progress-v2.patch

File file-transfer-progress-v2.patch, 24.3 KB (added by Antoine Martin, 6 months ago)

better patch

  • css/10_header_bar.css

     
     1/*
     2 * This file is part of Xpra.
     3 * Copyright (C) 2020 Antoine Martin <antoine@xpra.org>
     4 * Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     5 * later version. See the file COPYING for details.
     6 *
     7 * Try to make the header bar use less vertical space
     8 * (why is it so hard?)
     9 */
     10
     11headerbar entry,
     12headerbar spinbutton,
     13headerbar button,
     14headerbar separator {
     15    margin-top: 0px; /* same as headerbar side padding for nicer proportions */
     16    margin-bottom: 0px;
     17}
     18
     19headerbar {
     20    min-height: 24px;
     21    padding-left: 2px; /* same as childrens vertical margins for nicer proportions */
     22    padding-right: 2px;
     23    margin: 0px; /* same as headerbar side padding for nicer proportions */
     24    padding: 0px;
     25}
  • css/20_progress_bar.css

     
     1/*
     2 * This file is part of Xpra.
     3 * Copyright (C) 2020 Antoine Martin <antoine@xpra.org>
     4 * Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     5 * later version. See the file COPYING for details.
     6 *
     7 * Make the progress bar more visible
     8 * (but we can't set the min-width this way...)
     9 */
     10 
     11progress, trough {
     12  min-height: 30px;
     13}
  • setup.py

     
    16821682    add_data_files("%s/icons" % share_xpra,          glob.glob("icons/*png"))
    16831683    add_data_files("%s/content-type" % share_xpra,   glob.glob("content-type/*"))
    16841684    add_data_files("%s/content-categories" % share_xpra, glob.glob("content-categories/*"))
     1685    add_data_files("%s/css" % share_xpra,            glob.glob("./css/*"))
    16851686
    16861687
    16871688if html5_ENABLED:
  • xpra/client/gtk_base/css_overrides.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2020 Antoine Martin <antoine@xpra.org>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6# load xpra's custom css overrides
     7
     8import os.path
     9
     10from xpra.util import envbool
     11from xpra.platform.paths import get_resources_dir
     12from xpra.log import Logger
     13
     14log = Logger("gtk", "util")
     15
     16CSS_OVERRIDES = envbool("XPRA_CSS_OVERRIDES", True)
     17
     18
     19_done = False
     20def inject_css_overrides():
     21    global _done
     22    if _done or not CSS_OVERRIDES:
     23        return
     24    _done = True
     25
     26    css_dir = os.path.join(get_resources_dir(), "css")
     27    log("inject_css_overrides() css_dir=%s", css_dir)
     28    from gi.repository import Gtk, Gdk
     29    style_provider = Gtk.CssProvider()
     30    filename = None
     31    def parsing_error(_css_provider, _section, error):
     32        log.error("Error: CSS parsing error on")
     33        log.error(" '%s'", filename)
     34        log.error(" %s", error)
     35    style_provider.connect("parsing-error", parsing_error)
     36    for f in sorted(os.listdir(css_dir)):
     37        filename = os.path.join(css_dir, f)
     38        try:
     39            style_provider.load_from_path(filename)
     40            log(" - loaded '%s'", filename)
     41        except Exception as e:
     42            log("load_from_path(%s)", filename, exc_info=True)
     43            log.error("Error: CSS loading error on")
     44            log.error(" '%s'", filename)
     45            log.error(" %s", e)
     46
     47    Gtk.StyleContext.add_provider_for_screen(
     48        Gdk.Screen.get_default(),
     49        style_provider,
     50        Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
     51    )
  • xpra/client/gtk_base/gtk_client_base.py

     
    3535from xpra.client.ui_client_base import UIXpraClient
    3636from xpra.client.gobject_client_base import GObjectXpraClient
    3737from xpra.client.gtk_base.gtk_keyboard_helper import GTKKeyboardHelper
     38from xpra.client.gtk_base.css_overrides import inject_css_overrides
    3839from xpra.client.mixins.window_manager import WindowClient
    3940from xpra.platform.paths import get_icon_filename
    4041from xpra.platform.gui import (
     
    6667NO_OPENGL_WINDOW_TYPES = os.environ.get("XPRA_NO_OPENGL_WINDOW_TYPES",
    6768                                        "DOCK,TOOLBAR,MENU,UTILITY,SPLASH,DROPDOWN_MENU,POPUP_MENU,TOOLTIP,NOTIFICATION,COMBO,DND").split(",")
    6869
     70inject_css_overrides()
    6971
     72
    7073class GTKXpraClient(GObjectXpraClient, UIXpraClient):
    7174    __gsignals__ = {}
    7275    #add signals from super classes (all no-arg signals)
     
    393396                #record our response, so we will accept the file
    394397                self.data_send_requests[send_id] = (dtype, url, printit, newopenit)
    395398            cb_answer(accept)
    396         self.file_ask_dialog = getOpenRequestsWindow(self.show_file_upload)
     399        self.file_ask_dialog = getOpenRequestsWindow(self.show_file_upload, self.cancel_download)
    397400        self.file_ask_dialog.add_request(rec_answer, send_id, dtype, url, filesize, printit, openit, timeout)
    398401        self.file_ask_dialog.show()
    399402
     
    405408
    406409    def show_ask_data_dialog(self, *_args):
    407410        from xpra.client.gtk_base.open_requests import getOpenRequestsWindow
    408         self.file_ask_dialog = getOpenRequestsWindow(self.show_file_upload)
     411        self.file_ask_dialog = getOpenRequestsWindow(self.show_file_upload, self.cancel_download)
    409412        self.file_ask_dialog.show()
    410413
     414    def transfer_progress_update(self, send=True, transfer_id=0, elapsed=0, position=0, total=0):
     415        fad = self.file_ask_dialog
     416        if fad:
     417            self.idle_add(fad.transfer_progress_update, send, transfer_id, elapsed, position, total)
    411418
     419
    412420    def accept_data(self, send_id, dtype, url, printit, openit):
    413421        #check if we have accepted this file via the GUI:
    414422        r = self.data_send_requests.pop(send_id, None)
  • xpra/client/gtk_base/open_requests.py

     
    1010
    1111import gi
    1212gi.require_version("Gtk", "3.0")
     13gi.require_version("Gdk", "3.0")
    1314gi.require_version("Pango", "1.0")
    1415from gi.repository import GLib, Gtk, GdkPixbuf, Pango
    1516
     17from xpra.util import envint
     18from xpra.os_util import monotonic_time, bytestostr, WIN32, OSX
    1619from xpra.gtk_common.gobject_compat import register_os_signals
    17 from xpra.util import envint
    18 from xpra.os_util import monotonic_time, bytestostr, get_util_logger, WIN32, OSX
     20from xpra.client.gtk_base.css_overrides import inject_css_overrides
    1921from xpra.child_reaper import getChildReaper
    2022from xpra.net.file_transfer import ACCEPT, OPEN, DENY
    21 from xpra.simple_stats import std_unit_dec
     23from xpra.simple_stats import std_unit, std_unit_dec
    2224from xpra.gtk_common.gtk_util import (
    2325    add_close_accel, scaled_image,
    2426    TableBuilder,
    2527    )
    2628from xpra.platform.paths import get_icon_dir, get_download_dir
     29from xpra.log import Logger
    2730
    28 log = get_util_logger()
     31log = Logger("gtk", "file")
    2932
    3033URI_MAX_WIDTH = envint("XPRA_URI_MAX_WIDTH", 320)
    3134
     35inject_css_overrides()
    3236
     37
    3338_instance = None
    34 def getOpenRequestsWindow(show_file_upload_cb=None):
     39def getOpenRequestsWindow(show_file_upload_cb=None, cancel_download=None):
    3540    global _instance
    3641    if _instance is None:
    37         _instance = OpenRequestsWindow(show_file_upload_cb)
     42        _instance = OpenRequestsWindow(show_file_upload_cb, cancel_download)
    3843    return _instance
    3944
    4045
    4146class OpenRequestsWindow:
    4247
    43     def __init__(self, show_file_upload_cb=None):
     48    def __init__(self, show_file_upload_cb=None, cancel_download=None):
    4449        self.show_file_upload_cb = show_file_upload_cb
     50        self.cancel_download = cancel_download
    4551        self.populate_timer = None
    4652        self.table = None
    4753        self.requests = []
    4854        self.expire_labels = {}
     55        self.progress_bars = {}
    4956        self.window = Gtk.Window()
    5057        self.window.set_border_width(20)
    5158        self.window.connect("delete-event", self.close)
     
    106113
    107114    def update_expires_label(self):
    108115        expired = 0
    109         for label, expiry in self.expire_labels.items():
     116        for label, expiry in self.expire_labels.values():
    110117            seconds = max(0, expiry-monotonic_time())
    111118            label.set_text("%i" % seconds)
    112119            if seconds==0:
     
    141148                if dtype==b"file" and filesize>0:
    142149                    details = "%sB" % std_unit_dec(filesize)
    143150                expires_label = l()
    144                 self.expire_labels[expires_label] = expires
     151                self.expire_labels[send_id] = (expires_label, expires)
    145152                buttons = self.action_buttons(cb_answer, send_id, dtype, printit, openit)
    146153                s = bytestostr(url)
    147154                main_label = l(s)
     
    158165        self.alignment.add(self.table)
    159166        self.table.show_all()
    160167
     168    def remove_entry(self, send_id, can_close=True):
     169        self.expire_labels.pop(send_id, None)
     170        self.requests = [x for x in self.requests if x[1]!=send_id]
     171        self.progress_bars.pop(send_id, None)
     172        if not self.requests and can_close:
     173            self.close()
     174        else:
     175            self.populate_table()
     176            self.window.resize(1, 1)
     177
    161178    def action_buttons(self, cb_answer, send_id, dtype, printit, openit):
    162179        hbox = Gtk.HBox()
    163180        def remove_entry(can_close=False):
    164             self.requests = [x for x in self.requests if x[1]!=send_id]
    165             if not self.requests and can_close:
    166                 self.close()
    167             else:
    168                 self.populate_table()
    169                 self.window.resize(1, 1)
     181            self.remove_entry(send_id, can_close)
     182        def show_progressbar():
     183            expire = self.expire_labels.pop(send_id, None)
     184            if expire:
     185                expire_label = expire[0]
     186                expire_label.set_text("")
     187            for b in hbox.get_children():
     188                hbox.remove(b)
     189            def stop(*_args):
     190                self.remove_entry(True)
     191                self.cancel_download(send_id, "User cancelled")
     192            stop_btn = self.btn("Stop", None, stop, "close.png")
     193            hbox.pack_start(stop_btn)
     194            pb = Gtk.ProgressBar()
     195            hbox.set_spacing(20)
     196            hbox.pack_start(pb)
     197            hbox.show_all()
     198            pb.set_size_request(420, 30)
     199            self.progress_bars[send_id] = (pb, stop_btn)
     200        def cancel(*_args):
     201            remove_entry(True)
     202            cb_answer(DENY)
    170203        def ok(*_args):
    171204            remove_entry(False)
    172205            cb_answer(ACCEPT, False)
    173         def okopen(*_args):
    174             remove_entry(True)
    175             cb_answer(ACCEPT, True)
    176206        def remote(*_args):
    177207            remove_entry(True)
    178208            cb_answer(OPEN)
    179         def cancel(*_args):
    180             remove_entry(True)
    181             cb_answer(DENY)
    182         hbox.pack_start(self.btn("Cancel", None, cancel, "close.png"))
     209        def progress(*_args):
     210            cb_answer(ACCEPT)
     211            show_progressbar()
     212        def progressopen(*_args):
     213            cb_answer(OPEN)
     214            show_progressbar()
     215        cancel_btn = self.btn("Cancel", None, cancel, "close.png")
     216        hbox.pack_start(cancel_btn)
    183217        if bytestostr(dtype)=="url":
    184218            hbox.pack_start(self.btn("Open Locally", None, ok, "open.png"))
    185219            hbox.pack_start(self.btn("Open on server", None, remote))
    186220        elif printit:
    187             hbox.pack_start(self.btn("Print", None, ok, "printer.png"))
     221            hbox.pack_start(self.btn("Print", None, progress, "printer.png"))
    188222        else:
    189             hbox.pack_start(self.btn("Download", None, ok, "download.png"))
     223            hbox.pack_start(self.btn("Download", None, progress, "download.png"))
    190224            if openit:
    191                 hbox.pack_start(self.btn("Download and Open", None, okopen, "open.png"))
     225                hbox.pack_start(self.btn("Download and Open", None, progressopen, "open.png"))
    192226                hbox.pack_start(self.btn("Open on server", None, remote))
    193227        return hbox
    194228
     
    203237            self.populate_timer = 0
    204238
    205239
     240    def transfer_progress_update(self, send=True, transfer_id=0, elapsed=0, position=0, total=0):
     241        buttons = self.progress_bars.get(transfer_id)
     242        if not buttons:
     243            #we're not tracking this transfer: no progress bar
     244            return
     245        pb, stop_btn = buttons
     246        log("transfer_progress_update%s pb=%s", (send, transfer_id, elapsed, position, total), pb)
     247        if position<0:
     248            stop_btn.hide()
     249            pb.set_text("Error: file transfer aborted")
     250            GLib.timeout_add(5000, self.remove_entry, transfer_id)
     251            return
     252        if pb:
     253            pb.set_fraction(position/total)
     254            pb.set_text("%sB of %s" % (std_unit(position), std_unit(total)))
     255            pb.set_show_text(True)
     256        if position==total:
     257            stop_btn.hide()
     258            pb.set_text("Complete: %i bytes" % total)
     259            pb.set_show_text(True)
     260            GLib.timeout_add(5000, self.remove_entry, transfer_id)
     261
     262
    206263    def show(self):
    207264        log("show()")
    208265        self.window.show_all()
     
    235292            cmd = ["open", downloads]
    236293        else:
    237294            cmd = ["xdg-open", downloads]
    238         proc = subprocess.Popen(cmd)
    239         getChildReaper().add_process(proc, "show-downloads", cmd, ignore=True, forget=True)
     295        try:
     296            proc = subprocess.Popen(cmd)
     297        except Exception as e:
     298            log("show_downloads()", exc_info=True)
     299            log.error("Error: failed to open 'Downloads' folder:")
     300            log.error(" %s", e)
     301        else:
     302            getChildReaper().add_process(proc, "show-downloads", cmd, ignore=True, forget=True)
    240303
    241304
    242305    def run(self):
  • xpra/net/file_transfer.py

     
    3737ACCEPT = 1
    3838OPEN = 2
    3939
     40def osclose(fd):
     41    try:
     42        os.close(fd)
     43    except OSError as e:
     44        filelog("os.close(%s)", fd, exc_info=True)
     45        filelog.error("Error closing file download:")
     46        filelog.error(" %s", e)
     47
    4048def basename(filename):
    4149    #we can't use os.path.basename,
    4250    #because the remote end may have sent us a filename
     
    242250            }
    243251        return info
    244252
    245     def check_digest(self, filename, digest, expected_digest, algo="sha1"):
    246         if digest!=expected_digest:
    247             filelog.error("Error: data does not match, invalid %s file digest for '%s'", algo, filename)
    248             filelog.error(" received %s, expected %s", digest, expected_digest)
    249             raise Exception("failed %s digest verification" % algo)
    250         else:
    251             filelog("%s digest matches: %s", algo, digest)
    252253
     254    def digest_mismatch(self, filename, digest, expected_digest, algo="sha1"):
     255        filelog.error("Error: data does not match, invalid %s file digest for '%s'", algo, filename)
     256        filelog.error(" received %s, expected %s", digest, expected_digest)
     257        raise Exception("failed %s digest verification" % algo)
    253258
     259
    254260    def _check_chunk_receiving(self, chunk_id, chunk_no):
    255261        chunk_state = self.receive_chunks_in_progress.get(chunk_id)
    256262        filelog("_check_chunk_receiving(%s, %s) chunk_state=%s", chunk_id, chunk_no, chunk_state)
    257263        if chunk_state:
     264            if chunk_state[-4]:
     265                #transfer has been cancelled
     266                return
    258267            chunk_state[-2] = 0     #this timer has been used
    259268            if chunk_state[-1]==0:
    260269                filelog.error("Error: chunked file transfer timed out")
    261                 del self.receive_chunks_in_progress[chunk_id]
     270                self.receive_chunks_in_progress.pop(chunk_id, None)
    262271
     272    def cancel_download(self, send_id, message="Cancelled"):
     273        filelog("cancel_download(%s, %s)", send_id, message)
     274        for chunk_id, chunk_state in dict(self.receive_chunks_in_progress).items():
     275            if chunk_state[-3]==send_id:
     276                self.cancel_file(chunk_id, message)
     277                return
     278        filelog.error("Error: cannot cancel download %s, entry not found!", bytestostr(send_id))
     279
     280    def cancel_file(self, chunk_id, message, chunk=0):
     281        filelog("cancel_file%s", (chunk_id, message, chunk))
     282        chunk_state = self.receive_chunks_in_progress.get(chunk_id)
     283        if chunk_state:
     284            #mark it as cancelled:
     285            chunk_state[-4] = True
     286            #remove this transfer after a little while,
     287            #so in-flight packets won't cause errors
     288            def clean_receive_state():
     289                self.receive_chunks_in_progress.pop(chunk_id, None)
     290                return False
     291            self.timeout_add(20000, clean_receive_state)
     292        self.send("ack-file-chunk", chunk_id, False, message, chunk)
     293
    263294    def _process_send_file_chunk(self, packet):
    264295        chunk_id, chunk, file_data, has_more = packet[1:5]
    265296        filelog("_process_send_file_chunk%s", (chunk_id, chunk, "%i bytes" % len(file_data), has_more))
     
    266297        chunk_state = self.receive_chunks_in_progress.get(chunk_id)
    267298        if not chunk_state:
    268299            filelog.error("Error: cannot find the file transfer id '%s'", nonl(bytestostr(chunk_id)))
    269             self.send("ack-file-chunk", chunk_id, False, "file transfer id not found", chunk)
     300            self.cancel_file(chunk_id, "file transfer id %s not found" % chunk_id, chunk)
    270301            return
     302        if chunk_state[-4]:
     303            filelog("got chunk for a cancelled file transfer, ignoring it")
     304            return
     305        def progress(position):
     306            start = chunk_state[0]
     307            send_id = chunk_state[-3]
     308            filesize = chunk_state[6]
     309            self.transfer_progress_update(False, send_id, monotonic_time()-start, position, filesize)
    271310        fd = chunk_state[1]
    272311        if chunk_state[-1]+1!=chunk:
    273312            filelog.error("Error: chunk number mismatch, expected %i but got %i", chunk_state[-1]+1, chunk)
    274             self.send("ack-file-chunk", chunk_id, False, "chunk number mismatch", chunk)
    275             del self.receive_chunks_in_progress[chunk_id]
    276             os.close(fd)
     313            self.cancel_file(chunk_id, "chunk number mismatch", chunk)
     314            osclose(fd)
     315            progress(-1)
    277316            return
    278317        #update chunk number:
    279318        chunk_state[-1] = chunk
     
    287326        except OSError as e:
    288327            filelog.error("Error: cannot write file chunk")
    289328            filelog.error(" %s", e)
    290             self.send("ack-file-chunk", chunk_id, False, "write error: %s" % e, chunk)
    291             del self.receive_chunks_in_progress[chunk_id]
    292             try:
    293                 os.close(fd)
    294             except OSError:
    295                 pass
     329            self.cancel_file(chunk_id, "write error: %s" % e, chunk)
     330            osclose(fd)
     331            progress(-1)
    296332            return
    297333        self.send("ack-file-chunk", chunk_id, True, "", chunk)
    298334        if has_more:
     335            progress(written)
    299336            timer = chunk_state[-2]
    300337            if timer:
    301338                self.source_remove(timer)
     
    303340            timer = self.timeout_add(CHUNK_TIMEOUT, self._check_chunk_receiving, chunk_id, chunk)
    304341            chunk_state[-2] = timer
    305342            return
    306         del self.receive_chunks_in_progress[chunk_id]
    307         os.close(fd)
     343        self.receive_chunks_in_progress.pop(chunk_id, None)
     344        osclose(fd)
    308345        #check file size and digest then process it:
    309346        filename, mimetype, printit, openit, filesize, options = chunk_state[2:8]
    310347        if written!=filesize:
    311348            filelog.error("Error: expected a file of %i bytes, got %i", filesize, written)
     349            progress(-1)
    312350            return
    313351        expected_digest = options.get("sha1")
    314         if expected_digest:
    315             self.check_digest(filename, digest.hexdigest(), expected_digest)
     352        if expected_digest and digest.hexdigest()!=expected_digest:
     353            progress(-1)
     354            self.digest_mismatch(filename, digest, expected_digest, "sha1")
     355            return
     356
     357        progress(written)
    316358        start_time = chunk_state[0]
    317359        elapsed = monotonic_time()-start_time
    318360        mimetype = bytestostr(mimetype)
     
    397439                monotonic_time(),
    398440                fd, filename, mimetype,
    399441                printit, openit, filesize,
    400                 options, digest, 0, timer, chunk,
     442                options, digest, 0, False, send_id,
     443                timer, chunk,
    401444                ]
    402445            self.receive_chunks_in_progress[chunk_id] = chunk_state
    403446            self.send("ack-file-chunk", chunk_id, True, "", chunk)
     
    415458                u = libfn()
    416459                u.update(file_data)
    417460                l("%s digest: %s - expected: %s", algo, u.hexdigest(), digest)
    418                 self.check_digest(basefilename, u.hexdigest(), digest, algo)
     461                if digest!=u.hexdigest():
     462                    self.digest_mismatch(filename, digest, u.hexdigest(), algo)
    419463        check_digest("sha1", hashlib.sha1)
    420464        check_digest("md5", hashlib.md5)
    421465        try:
     
    790834        chunk_state = self.send_chunks_in_progress.get(chunk_id)
    791835        filelog("_check_chunk_sending(%s, %s) chunk_state found: %s", chunk_id, chunk_no, bool(chunk_state))
    792836        if chunk_state:
    793             chunk_state[-2] = 0         #timer has fired
     837            chunk_state[3] = 0         #timer has fired
    794838            if chunk_state[-1]==chunk_no:
    795839                filelog.error("Error: chunked file transfer timed out on chunk %i", chunk_no)
    796                 try:
    797                     del self.send_chunks_in_progress[chunk_id]
    798                 except KeyError:
    799                     pass
     840                self.cancel_sending(chunk_id)
    800841
     842    def cancel_sending(self, chunk_id):
     843        chunk_state = self.send_chunks_in_progress.pop(chunk_id, None)
     844        if chunk_state:
     845            timer = chunk_state[3]
     846            if timer:
     847                self.source_remove(timer)
     848                chunk_state[3] = 0
     849
    801850    def _process_ack_file_chunk(self, packet):
    802851        #the other end received our send-file or send-file-chunk,
    803852        #send some more file data
     
    804853        filelog("ack-file-chunk: %s", packet[1:])
    805854        chunk_id, state, error_message, chunk = packet[1:5]
    806855        if not state:
    807             filelog.error("Error: remote end is cancelling the file transfer:")
    808             filelog.error(" %s", error_message)
    809             del self.send_chunks_in_progress[chunk_id]
     856            filelog.info("the remote end is cancelling the file transfer:")
     857            filelog.info(" %s", bytestostr(error_message))
     858            self.cancel_sending(chunk_id)
    810859            return
    811860        chunk_id = bytestostr(chunk_id)
    812861        chunk_state = self.send_chunks_in_progress.get(chunk_id)
     
    815864            return
    816865        if chunk_state[-1]!=chunk:
    817866            filelog.error("Error: chunk number mismatch (%i vs %i)", chunk_state, chunk)
    818             del self.send_chunks_in_progress[chunk_id]
     867            self.cancel_sending(chunk_id)
    819868            return
    820869        start_time, data, chunk_size, timer, chunk = chunk_state
    821870        if not data:
     
    823872            elapsed = monotonic_time()-start_time
    824873            filelog("%i chunks of %i bytes sent in %ims (%sB/s)",
    825874                    chunk, chunk_size, elapsed*1000, std_unit(chunk*chunk_size/elapsed))
    826             del self.send_chunks_in_progress[chunk_id]
     875            self.cancel_sending(chunk_id)
    827876            return
    828877        assert chunk_size>0
    829878        #carve out another chunk:
     
    841890
    842891    def compressed_wrapper(self, datatype, data, level=5):
    843892        raise NotImplementedError()
     893
     894    def transfer_progress_update(self, send=True, transfer_id=0, elapsed=0, position=0, total=0):
     895        #this method is overriden in the gtk client:
     896        filelog("transfer_progress_update%s", (send, transfer_id, elapsed, position, total))