From ba4fadc5ccd7fcbea435753b092c998f8f488a7e Mon Sep 17 00:00:00 2001 From: Dimitur Kirov Date: Thu, 8 Sep 2005 22:12:14 +0000 Subject: [PATCH] add estimated time and speed to FT --- src/common/socks5.py | 35 +++++++++-- src/filetransfers_window.py | 113 ++++++++++++++++++++++++++++-------- src/gajim.py | 2 +- 3 files changed, 122 insertions(+), 28 deletions(-) diff --git a/src/common/socks5.py b/src/common/socks5.py index 074ed78f6..c439607f5 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -24,6 +24,7 @@ import select import os import struct import sha +import time from errno import EWOULDBLOCK from errno import ENOBUFS @@ -169,6 +170,8 @@ running instance of Gajim. \nFile Transfer will be canceled.\n================== reader.file_props['completed'] = False reader.file_props['paused'] = False reader.file_props['stalled'] = False + reader.file_props['elapsed-time'] = 0 + reader.file_props['last-time'] = time.time() reader.file_props['received-len'] = 0 reader.pauses = 0 reader.send_raw(reader._get_nl_byte()) @@ -186,6 +189,8 @@ running instance of Gajim. \nFile Transfer will be canceled.\n================== result = sender.send_file() self.process_result(result, sender) else: + file_props['elapsed-time'] = 0 + file_props['last-time'] = time.time() file_props['received-len'] = 0 sender.file_props = file_props @@ -248,7 +253,9 @@ running instance of Gajim. \nFile Transfer will be canceled.\n================== sender.file_props['type'] == 'r': result = sender.get_file_contents(0) self.process_result(result, sender) - elif sender.state == 7 and not sender.file_props['paused']: + elif sender.state == 7: + if sender.file_props['paused']: + break if not sender.connected: self.process_result(-1, sender) break @@ -354,6 +361,7 @@ class Socks5: def open_file_for_reading(self): self.fd = open(self.file_props['file-name'],'rb') self.fd.seek(self.size) + def close_file(self): try: self.fd.close() @@ -369,6 +377,8 @@ class Socks5: else: fd = open(self.file_props['file-name'],'wb') self.file_props['fd'] = fd + self.file_props['elapsed-time'] = 0 + self.file_props['last-time'] = time.time() self.file_props['received-len'] = 0 return fd @@ -442,6 +452,10 @@ class Socks5: self.file_props['error'] = -1 return -1 self.size += lenn + current_time = time.time() + self.file_props['elapsed-time'] += current_time - \ + self.file_props['last-time'] + self.file_props['last-time'] = current_time self.file_props['received-len'] = self.size if self.size == int(self.file_props['size']): self.state = 8 # end connection @@ -483,6 +497,10 @@ class Socks5: fd = self.get_fd() fd.write(self.remaining_buff) lenn = len(self.remaining_buff) + current_time = time.time() + self.file_props['elapsed-time'] += current_time - \ + self.file_props['last-time'] + self.file_props['last-time'] = current_time self.file_props['received-len'] += lenn self.remaining_buff = '' if self.file_props['received-len'] == int(self.file_props['size']): @@ -505,6 +523,11 @@ class Socks5: if ord(buff[0]) == 0xD: first_byte = True buff = buff[1:] + + current_time = time.time() + self.file_props['elapsed-time'] += current_time - \ + self.file_props['last-time'] + self.file_props['last-time'] = current_time self.file_props['received-len'] += len(buff) fd.write(buff) if len(buff) == 0 and first_byte is False: @@ -667,6 +690,8 @@ class Socks5Sender(Socks5): self.file_props['paused'] = False self.file_props['stalled'] = False self.file_props['connected'] = True + self.file_props['elapsed-time'] = 0 + self.file_props['last-time'] = time.time() self.file_props['received-len'] = 0 self.pauses = 0 return self.write_next(initial = True) # initial for nl byte @@ -692,7 +717,7 @@ class Socks5Sender(Socks5): self.state += 1 # go to the next step return None - def pending_data(self,timeout=0.01): + def pending_data(self,timeout=0): ''' return true if there is a data ready to be read ''' if self._sock is None: return False @@ -752,7 +777,7 @@ class Socks5Listener: _sock[0].setblocking(False) return _sock - def pending_connection(self,timeout=0.005): + def pending_connection(self,timeout=0): ''' Returns true if there is a data ready to be read. ''' if self._serv is None: return False @@ -872,6 +897,8 @@ class Socks5Receiver(Socks5): self.file_props['completed'] = False self.file_props['paused'] = False self.file_props['stalled'] = False + self.file_props['elapsed-time'] = 0 + self.file_props['last-time'] = time.time() self.file_props['received-len'] = 0 self.pauses = 0 self.send_raw(self._get_nl_byte()) @@ -887,7 +914,7 @@ class Socks5Receiver(Socks5): return False try: if self.state in [2, 4, 6]: # auth response, connect, file data - return self.pending_read(0.01) + return self.pending_read(0) elif self.state in [1, 3, 5]: # auth types, connect request return True except Exception, e: diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py index e3e8b9bb2..ad481fcf8 100644 --- a/src/filetransfers_window.py +++ b/src/filetransfers_window.py @@ -24,6 +24,7 @@ import gobject import pango import os import sys +import time import gtkgui_helpers import tooltips @@ -39,7 +40,16 @@ gtk.glade.bindtextdomain (APP, i18n.DIR) gtk.glade.textdomain (APP) GTKGUI_GLADE = 'gtkgui.glade' -SID_INDEX = 5 + +C_IMAGE = 0 +C_LABELS = 1 +C_FILE = 2 +C_PROGRESS = 3 +C_PERCENT = 4 +C_TIME = 5 +C_SID = 6 + + class FileTransfersWindow: def __init__(self, plugin): self.files_props = {'r' : {}, 's': {}} @@ -58,7 +68,7 @@ class FileTransfersWindow: self.notify_ft_checkbox.set_active(True) else: self.notify_ft_checkbox.set_active(False) - self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, int, str) + self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, int, str, str) self.tree.set_model(self.model) col = gtk.TreeViewColumn() @@ -74,11 +84,11 @@ class FileTransfersWindow: col = gtk.TreeViewColumn(_('File')) renderer = gtk.CellRendererText() col.pack_start(renderer, expand=False) - col.add_attribute(renderer, 'markup' , 1) + col.add_attribute(renderer, 'markup' , C_LABELS) renderer.set_property('yalign', 0.) renderer = gtk.CellRendererText() col.pack_start(renderer, expand=True) - col.add_attribute(renderer, 'markup' , 2) + col.add_attribute(renderer, 'markup' , C_FILE) renderer.set_property('xalign', 0.) renderer.set_property('yalign', 0.) renderer.set_property('ellipsize', pango.ELLIPSIZE_END) @@ -92,9 +102,24 @@ class FileTransfersWindow: renderer.set_property('xalign', 0.5) col.pack_start(renderer, expand = False) col.set_expand(False) - col.add_attribute(renderer, 'text' , 3) - col.add_attribute(renderer, 'value' , 4) + col.add_attribute(renderer, 'text' , C_PROGRESS) + col.add_attribute(renderer, 'value' , C_PERCENT) + col.set_resizable(True) self.tree.append_column(col) + + col = gtk.TreeViewColumn(_('Time')) + renderer = gtk.CellRendererText() + col.pack_start(renderer, expand=False) + col.add_attribute(renderer, 'markup' , C_TIME) + renderer.set_property('yalign', 0.5) + renderer.set_property('xalign', 0.5) + renderer = gtk.CellRendererText() + renderer.set_property('ellipsize', pango.ELLIPSIZE_END) + col.set_resizable(True) + col.set_expand(False) + self.tree.append_column(col) + + self.set_images() self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE) self.tree.get_selection().connect('changed', self.selection_changed) @@ -348,14 +373,15 @@ _('Connection with peer cannot be established.')) iter = self.get_iter_by_sid(typ, sid) if iter is None: return - sid = self.model[iter][SID_INDEX].decode('utf-8') + sid = self.model[iter][C_SID].decode('utf-8') file_props = self.files_props[sid[0]][sid[1:]] if status == 'stop': file_props['stopped'] = True elif status == 'ok': file_props['completed'] = True - self.model.set(iter, 0, self.images[status]) - def format_percent(self, percent): + self.model.set(iter, C_IMAGE, self.images[status]) + + def _format_percent(self, percent): ''' add extra spaces from both sides of the percent, so that progress string has always a fixed size''' _str = ' ' @@ -366,6 +392,32 @@ _('Connection with peer cannot be established.')) _str += unicode(percent) + '% \n' return _str + def _format_time(self, _time): + + times = { 'hours': 0, 'minutes': 0, 'seconds': 0 } + _time = int(_time) + times['seconds'] = _time % 60 + if _time >= 60: + _time /= 60 + times['minutes'] = _time % 60 + if _time >= 60: + times['hours'] = _time / 60 + + #Print remaining time in format 00:00:00 + #You can change the places of hours, minutes, seconds - + #they are not translatable. + return _('%(hours)02.d:%(minutes)02.d:%(seconds)02.d') % times + + def _get_eta_and_speed(self, full_size, transfered_size, elapsed_time): + if elapsed_time == 0: + return 0., 0. + speed = round(float(transfered_size) / elapsed_time) + if speed == 0.: + return 0., 0. + remaining_size = full_size - transfered_size + eta = remaining_size / speed + return eta, speed + def set_progress(self, typ, sid, transfered_size, iter = None): ''' change the progress of a transfer with new transfered size''' if not self.files_props[typ].has_key(sid): @@ -379,14 +431,27 @@ _('Connection with peer cannot be established.')) if iter is None: iter = self.get_iter_by_sid(typ, sid) if iter is not None: - text = self.format_percent(percent) + text = self._format_percent(percent) if transfered_size == 0: text += '0' else: text += helpers.convert_bytes(transfered_size) text += '/' + helpers.convert_bytes(full_size) - self.model.set(iter, 3, text) - self.model.set(iter, 4, int(percent)) + # Kb/s + + # remaining time + eta, speed = self._get_eta_and_speed(full_size, transfered_size, + file_props['elapsed-time']) + + #This should make the string Kb/s, + #where 'Kb' part is taken from %s. + #Only the last 's' should be translated. + text += _(' (%s/s)') % helpers.convert_bytes(speed) + self.model.set(iter, C_PROGRESS, text) + self.model.set(iter, C_PERCENT, int(percent)) + + self.model.set(iter, C_TIME, + self._format_time(eta)) if file_props['type'] == 'r': status = 'download' else: @@ -406,7 +471,7 @@ _('Connection with peer cannot be established.')) session id''' iter = self.model.get_iter_root() while iter: - if typ + sid == self.model[iter][SID_INDEX].decode('utf-8'): + if typ + sid == self.model[iter][C_SID].decode('utf-8'): return iter iter = self.model.iter_next(iter) @@ -427,6 +492,7 @@ _('Connection with peer cannot be established.')) if os.path.exists(file_path) and os.path.isfile(file_path): stat = os.stat(file_path) os.stat(file_path) + file_props['elapsed-time'] = 0 file_props['size'] = unicode(stat[6]) file_props['sid'] = self.get_sid() file_props['completed'] = False @@ -441,6 +507,7 @@ _('Connection with peer cannot be established.')) self.on_transfers_list_leave_notify_event(None) if file_props is None: return + file_props['elapsed-time'] = 0 self.files_props[file_props['type']][file_props['sid']] = file_props iter = self.model.append() text_labels = '' + _('Name: ') + '\n' @@ -455,9 +522,8 @@ _('Connection with peer cannot be established.')) file_name = file_props['name'] text_props = gtkgui_helpers.escape_for_pango_markup(file_name) + '\n' text_props += gtkgui_helpers.escape_for_pango_markup(contact.name) - self.model.set(iter, 1, text_labels, 2, text_props, SID_INDEX, \ + self.model.set(iter, 1, text_labels, 2, text_props, C_SID, \ file_props['type'] + file_props['sid']) - #~ self.model.set(iter, 4, 40) self.set_progress(file_props['type'], file_props['sid'], 0, iter) if file_props.has_key('started') and file_props['started'] is False: status = 'waiting' @@ -486,7 +552,7 @@ _('Connection with peer cannot be established.')) except: self.tooltip.hide_tooltip() return - sid = self.model[iter][SID_INDEX].decode('utf-8') + sid = self.model[iter][C_SID].decode('utf-8') file_props = self.files_props[sid[0]][sid[1:]] if file_props is not None: if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: @@ -567,7 +633,7 @@ _('Connection with peer cannot be established.')) self.set_all_insensitive() return current_iter = self.model.get_iter(path) - sid = self.model[current_iter][SID_INDEX].decode('utf-8') + sid = self.model[current_iter][C_SID].decode('utf-8') file_props = self.files_props[sid[0]][sid[1:]] self.remove_menuitem.set_sensitive(is_row_selected) self.open_folder_menuitem.set_sensitive(is_row_selected) @@ -624,7 +690,7 @@ _('Connection with peer cannot be established.')) i = len(self.model) - 1 while i >= 0: iter = self.model.get_iter((i)) - sid = self.model[iter][SID_INDEX].decode('utf-8') + sid = self.model[iter][C_SID].decode('utf-8') file_props = self.files_props[sid[0]][sid[1:]] if file_props.has_key('completed') and file_props['completed']: self.model.remove(iter) @@ -661,9 +727,10 @@ _('Connection with peer cannot be established.')) if selected is None or selected[1] is None: return s_iter = selected[1] - sid = self.model[s_iter][SID_INDEX].decode('utf-8') + sid = self.model[s_iter][C_SID].decode('utf-8') file_props = self.files_props[sid[0]][sid[1:]] if self.is_transfer_paused(file_props): + file_props['last-time'] = time.time() file_props['paused'] = False types = {'r' : 'download', 's' : 'upload'} self.set_status(file_props['type'], file_props['sid'], types[sid[0]]) @@ -678,7 +745,7 @@ _('Connection with peer cannot be established.')) if selected is None or selected[1] is None: return s_iter = selected[1] - sid = self.model[s_iter][SID_INDEX].decode('utf-8') + sid = self.model[s_iter][C_SID].decode('utf-8') file_props = self.files_props[sid[0]][sid[1:]] if not file_props.has_key('tt_account'): return @@ -699,7 +766,7 @@ _('Connection with peer cannot be established.')) # check if the current pointer is at the same path # as it was before setting the timeout iter = self.model.get_iter(props[0]) - sid = self.model[iter][SID_INDEX].decode('utf-8') + sid = self.model[iter][C_SID].decode('utf-8') file_props = self.files_props[sid[0]][sid[1:]] rect = self.tree.get_cell_area(props[0],props[1]) position = widget.window.get_origin() @@ -795,7 +862,7 @@ _('Connection with peer cannot be established.')) if selected is None or selected[1] is None: return s_iter = selected[1] - sid = self.model[s_iter][SID_INDEX].decode('utf-8') + sid = self.model[s_iter][C_SID].decode('utf-8') file_props = self.files_props[sid[0]][sid[1:]] if not file_props.has_key('file-name'): return @@ -818,7 +885,7 @@ _('Connection with peer cannot be established.')) if selected is None or selected[1] is None: return s_iter = selected[1] - sid = self.model[s_iter][SID_INDEX].decode('utf-8') + sid = self.model[s_iter][C_SID].decode('utf-8') file_props = self.files_props[sid[0]][sid[1:]] if not file_props.has_key('tt_account'): # file transfer is not set yet diff --git a/src/gajim.py b/src/gajim.py index e317e9e08..49d97e35f 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -1012,7 +1012,7 @@ class Interface: if gajim.connections[account].connected: gajim.connections[account].process(0.01) if gajim.socks5queue.connected: - gajim.socks5queue.process(0.01) + gajim.socks5queue.process(0) for account in gajim.events_for_ui: #when we create a new account we don't have gajim.connection while len(gajim.events_for_ui[account]): gajim.mutex_events_for_ui.lock(self.exec_event, account)