root/branches/gajim_0.8/src/filetransfers_window.py

Revision 3083, 27.1 kB (checked in by dkirov, 3 years ago)

added comments on some methods

Line 
1##      filetransfers_window.py
2##
3## Gajim Team:
4##      - Yann Le Boulanger <asterix@lagaule.org>
5##      - Vincent Hanquez <tab@snarc.org>
6##      - Nikos Kouremenos <kourem@gmail.com>
7##      - Dimitur Kirov <dkirov@gmail.com>
8##
9##      Copyright (C) 2003-2005 Gajim Team
10##
11## This program is free software; you can redistribute it and/or modify
12## it under the terms of the GNU General Public License as published
13## by the Free Software Foundation; version 2 only.
14##
15## This program is distributed in the hope that it will be useful,
16## but WITHOUT ANY WARRANTY; without even the implied warranty of
17## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18## GNU General Public License for more details.
19##
20
21import gtk
22import gtk.glade
23import gobject
24import os
25
26import gtkgui_helpers
27import tooltips
28import dialogs
29
30from common import gajim
31from common import helpers
32from common import i18n
33
34_ = i18n._
35APP = i18n.APP
36gtk.glade.bindtextdomain (APP, i18n.DIR)
37gtk.glade.textdomain (APP)
38
39GTKGUI_GLADE = 'gtkgui.glade'
40
41class FileTransfersWindow:
42        def __init__(self, plugin):
43                self.files_props = {'r' : {}, 's': {}}
44                self.plugin = plugin
45                self.height_diff = 0
46                self.last_save_dir = None
47                self.xml = gtk.glade.XML(GTKGUI_GLADE, 'file_transfers_window', APP)
48                self.window = self.xml.get_widget('file_transfers_window')
49                self.tree = self.xml.get_widget('transfers_list')
50                self.cancel_button = self.xml.get_widget('cancel_button')
51                self.pause_button = self.xml.get_widget('pause_restore_button')
52                self.remove_button = self.xml.get_widget('remove_button')
53                self.notify_ft_checkbox = self.xml.get_widget(
54                        'notify_ft_complete_checkbox')
55                notify = gajim.config.get('notify_on_file_complete')
56                if notify:
57                        self.notify_ft_checkbox.set_active(True)
58                else:
59                        self.notify_ft_checkbox.set_active(False)
60                self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, str, str)
61                self.tree.set_model(self.model)
62                col = gtk.TreeViewColumn()
63               
64                render_pixbuf = gtk.CellRendererPixbuf()
65               
66                col.pack_start(render_pixbuf, expand = True)
67                render_pixbuf.set_property('xpad', 3)
68                render_pixbuf.set_property('ypad', 3)
69                render_pixbuf.set_property('yalign', .0)
70                col.add_attribute(render_pixbuf, 'pixbuf', 0)
71                self.tree.append_column(col)
72               
73                col = gtk.TreeViewColumn(_('File'))
74                renderer = gtk.CellRendererText()
75                col.pack_start(renderer, expand=False)
76                col.add_attribute(renderer, 'markup' , 1)
77                renderer.set_property('yalign', 0.)
78                renderer = gtk.CellRendererText()
79                col.pack_start(renderer, expand=True)
80                col.add_attribute(renderer, 'markup' , 2)
81                renderer.set_property('xalign', 0.)
82                renderer.set_property('yalign', 0.)
83                col.set_resizable(True)
84                col.set_expand(True)
85                self.tree.append_column(col)
86               
87                col = gtk.TreeViewColumn(_('Progress'))
88                renderer = gtk.CellRendererText()
89                renderer.set_property('yalign', 0.)
90                renderer.set_property('xalign', 0.)
91                col.pack_start(renderer, expand = True)
92                col.set_expand(False)
93                col.add_attribute(renderer, 'text' , 3)
94                self.tree.append_column(col)
95                self.set_images()
96                self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
97                self.tree.get_selection().connect('changed', self.selection_changed)
98                self.tooltip = tooltips.FileTransfersTooltip()
99                self.xml.signal_autoconnect(self)
100                popup_xml = gtk.glade.XML(GTKGUI_GLADE, 'file_transfers_menu',
101                        APP)
102                self.file_transfers_menu = popup_xml.get_widget('file_transfers_menu')
103                self.open_folder_menuitem = popup_xml.get_widget('open_folder_menuitem')
104                self.cancel_menuitem = popup_xml.get_widget('cancel_menuitem')
105                self.pause_menuitem = popup_xml.get_widget('pause_menuitem')
106                self.continue_menuitem = popup_xml.get_widget('continue_menuitem')
107                self.remove_menuitem = popup_xml.get_widget('remove_menuitem')
108                self.clean_up_menuitem = popup_xml.get_widget('clean_up_menuitem')
109                if gtk.gtk_version >= (2, 6, 0) and gtk.pygtk_version >= (2, 6, 0):
110                        self.pause_button.set_image(gtk.image_new_from_stock(
111                gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU))
112                popup_xml.signal_autoconnect(self)
113               
114        def show_completed(self, jid, file_props):
115                ''' show a dialog saying that file (file_props) has been transferred'''
116                self.window.present()
117                self.window.window.focus()
118                sectext = '\t' + _('Filename: %s') % \
119                        gtkgui_helpers.escape_for_pango_markup(file_props['name'])
120                sectext += '\n\t' + _('Size: %s') % \
121                helpers.convert_bytes(file_props['size'])
122                if file_props['type'] == 'r':
123                        jid = str(file_props['sender']).split('/')[0]
124                        sender_name = gajim.get_first_contact_instance_from_jid( 
125                                file_props['tt_account'], jid).name
126                        sender = gtkgui_helpers.escape_for_pango_markup(sender_name)
127                else:
128                        if float(gajim.version) > 0.8:
129                                sender = 'You' # FIXME _(gettext this)
130                        else:
131                                sender = file_props['tt_account']
132                sectext += '\n\t' +_('Sender: %s') % sender
133                sectext += '\n\t' +_('Recipient: ')
134                if file_props['type'] == 's':
135                        jid = str(file_props['receiver']).split('/')[0]
136                        receiver_name = gajim.get_first_contact_instance_from_jid( 
137                                file_props['tt_account'], jid).name
138                        recipient = gtkgui_helpers.escape_for_pango_markup(receiver_name)
139                else:
140                        if float(gajim.version) > 0.8:
141                                recipient = 'You' # FIXME _(gettext this)
142                        else:
143                                recipient = file_props['tt_account']
144                sectext += recipient
145                if file_props['type'] == 'r':
146                        (path, file) = os.path.split(file_props['file-name'])
147                        sectext += '\n\t' +_('Saved in: %s') % \
148                                gtkgui_helpers.escape_for_pango_markup(path)
149                dialog = dialogs.HigDialog(None, _('File transfer completed'), sectext, 
150                        gtk.STOCK_DIALOG_INFO, [[_('_Open Containing Folder'), gtk.RESPONSE_ACCEPT], 
151                                [ gtk.STOCK_OK, gtk.RESPONSE_OK ]])
152                button = dialog.get_button(1)
153                if gtk.gtk_version >= (2, 6, 0) and gtk.pygtk_version >= (2, 6, 0):
154                        button.set_image(gtk.image_new_from_stock(
155                gtk.STOCK_DIRECTORY, gtk.ICON_SIZE_BUTTON))
156                dialog.show_all()
157                if file_props['type'] == 's':
158                        button.hide()
159                response = dialog.run()
160                dialog.destroy()
161                if response == gtk.RESPONSE_ACCEPT:
162                        if not file_props.has_key('file-name'):
163                                return
164                        (path, file) = os.path.split(file_props['file-name'])
165                        if os.path.exists(path) and os.path.isdir(path):
166                                helpers.launch_file_manager(path)
167                        self.tree.get_selection().unselect_all()
168               
169        def show_request_error(self, file_props):
170                ''' show error dialog to the recipient saying that transfer
171                has been canceled'''
172                self.window.present()
173                self.window.window.focus()
174                dialogs.InformationDialog(_('File transfer canceled'), _('Connection with peer cannot be established.'))
175                self.tree.get_selection().unselect_all()
176               
177        def show_send_error(self, file_props):
178                ''' show error dialog to the sender saying that transfer
179                has been canceled'''
180                self.window.present()
181                self.window.window.focus()
182                dialogs.InformationDialog(_('File transfer canceled'),
183_('Connection with peer cannot be established.'))
184                self.tree.get_selection().unselect_all()
185       
186        def show_stopped(self, jid, file_props):
187                self.window.present()
188                self.window.window.focus()
189                sectext = '\t' + _('Filename: %s') % \
190                        gtkgui_helpers.escape_for_pango_markup(file_props['name'])
191                sectext += '\n\t' + _('Sender: %s') % \
192                        gtkgui_helpers.escape_for_pango_markup(jid)
193                dialogs.ErrorDialog(_('File transfer stopped by the contact of the other side'), \
194                        sectext).get_response()
195                self.tree.get_selection().unselect_all()
196               
197        def show_file_send_request(self, account, contact):
198                #FIXME: user better name for this function
199                #atm it's like it shows popup for incoming file transfer request
200                dialog = gtk.FileChooserDialog(title=_('Choose File to Send...'), 
201                        action=gtk.FILE_CHOOSER_ACTION_OPEN, 
202                        buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
203                butt = dialog.add_button(_('Send'), gtk.RESPONSE_OK)
204                butt.set_use_stock(True)
205                dialog.set_default_response(gtk.RESPONSE_OK)
206                if self.last_save_dir and os.path.exists(self.last_save_dir) \
207                        and os.path.isdir(self.last_save_dir):
208                        dialog.set_current_folder(self.last_save_dir)
209                file_props = {}
210                response = dialog.run()
211                if response == gtk.RESPONSE_OK:
212                        file_path =  unicode(dialog.get_filename(), 'utf-8')
213                        (file_dir, file_name) = os.path.split(file_path)
214                        if file_dir:
215                                self.last_save_dir = file_dir
216                        dialog.destroy()
217                        self.send_file(account, contact, file_path)
218                else:
219                        dialog.destroy()
220
221        def send_file(self, account, contact, file_path):
222                ''' start the real transfer(upload) of the file '''
223                (file_dir, file_name) = os.path.split(file_path)
224                file_props = self.get_send_file_props(account, contact, 
225                                file_path, file_name)
226                self.add_transfer(account, contact, file_props)
227                gajim.connections[account].send_file_request(file_props)
228       
229        def show_file_request(self, account, contact, file_props):
230                ''' show dialog asking for comfirmation and store location of new
231                file requested by a contact'''
232                if file_props is None or not file_props.has_key('name'):
233                        return
234                sec_text = '\t' + _('File: %s') % \
235                        gtkgui_helpers.escape_for_pango_markup(file_props['name'])
236                if file_props.has_key('size'):
237                        sec_text += '\n\t' + _('Size: %s') % \
238                                helpers.convert_bytes(file_props['size'])
239                if file_props.has_key('mime-type'):
240                        sec_text += '\n\t' + _('Type: %s') % \
241                                gtkgui_helpers.escape_for_pango_markup(file_props['mime-type'])
242                if file_props.has_key('desc'):
243                        sec_text += '\n\t' + _('Description: %s') % \
244                                gtkgui_helpers.escape_for_pango_markup(file_props['desc'])
245                prim_text = _('%s wants to send you a file:') % contact.jid
246                dialog = dialogs.ConfirmationDialog(prim_text, sec_text)
247                if dialog.get_response() == gtk.RESPONSE_OK:
248                        dialog = gtk.FileChooserDialog(title=_('Save File as...'), 
249                                action=gtk.FILE_CHOOSER_ACTION_SAVE, 
250                                buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, 
251                                gtk.STOCK_SAVE, gtk.RESPONSE_OK))
252                        dialog.set_current_name(file_props['name'])
253                        dialog.set_default_response(gtk.RESPONSE_OK)
254                        if self.last_save_dir and os.path.exists(self.last_save_dir) \
255                                and os.path.isdir(self.last_save_dir):
256                                dialog.set_current_folder(self.last_save_dir)
257                        while True:
258                                response = dialog.run()
259                                if response == gtk.RESPONSE_OK:
260                                        file_path = dialog.get_filename()
261                                        if os.path.exists(file_path):
262                                                primtext = _('This file already exists')
263                                                sectext = _('Would you like to overwrite it?')
264                                                dialog2 = dialogs.ConfirmationDialog(primtext, sectext)
265                                                if dialog2.get_response() != gtk.RESPONSE_OK:
266                                                        continue
267                                        (file_dir, file_name) = os.path.split(file_path)
268                                        if file_dir:
269                                                self.last_save_dir = file_dir
270                                        file_props['file-name'] = file_path.decode('utf-8')
271                                        self.add_transfer(account, contact, file_props)
272                                        gajim.connections[account].send_file_approval(file_props)
273                                else:
274                                        gajim.connections[account].send_file_rejection(file_props)
275                                dialog.destroy()
276                                break
277                else:
278                        gajim.connections[account].send_file_rejection(file_props)
279       
280        def set_images(self):
281                ''' create pixbufs for status images in transfer rows'''
282                self.images = {}
283                self.images['upload'] = self.window.render_icon(gtk.STOCK_GO_UP, 
284                        gtk.ICON_SIZE_MENU)
285                self.images['download'] = self.window.render_icon(gtk.STOCK_GO_DOWN, 
286                        gtk.ICON_SIZE_MENU)
287                self.images['stop'] = self.window.render_icon(gtk.STOCK_STOP, 
288                        gtk.ICON_SIZE_MENU)
289                self.images['waiting'] = self.window.render_icon(gtk.STOCK_REFRESH, 
290                        gtk.ICON_SIZE_MENU)
291                self.images['pause'] = self.window.render_icon(gtk.STOCK_MEDIA_PAUSE, 
292                        gtk.ICON_SIZE_MENU)
293                self.images['continue'] = self.window.render_icon(gtk.STOCK_MEDIA_PLAY, 
294                        gtk.ICON_SIZE_MENU)
295                self.images['ok'] = self.window.render_icon(gtk.STOCK_APPLY, 
296                        gtk.ICON_SIZE_MENU)
297                       
298        def set_status(self, typ, sid, status):
299                ''' change the status of a transfer to state 'status' '''
300                iter = self.get_iter_by_sid(typ, sid)
301                if iter is None:
302                        return
303                sid = self.model[iter][4]
304                file_props = self.files_props[sid[0]][sid[1:]]
305                if status == 'stop':
306                        file_props['stopped'] = True
307                elif status == 'ok':
308                        file_props['completed'] = True
309                self.model.set(iter, 0, self.images[status])
310       
311        def set_progress(self, typ, sid, transfered_size, iter = None):
312                ''' change the progress of a transfer with new transfered size'''
313                if not self.files_props[typ].has_key(sid):
314                        return
315                file_props = self.files_props[typ][sid]
316                full_size = int(file_props['size'])
317                if full_size == 0:
318                        percent = 0
319                else:
320                        percent = round(float(transfered_size) / full_size * 100)
321                if iter is None:
322                        iter = self.get_iter_by_sid(typ, sid)
323                if iter is not None:
324                        text = str(percent) + '%\n' 
325                        if transfered_size == 0:
326                                text += '0'
327                        else:
328                                text += helpers.convert_bytes(transfered_size)
329                        text += '/' + helpers.convert_bytes(full_size)
330                        self.model.set(iter, 3, text)
331                        if file_props['type'] == 'r':
332                                status = 'download'
333                        else:
334                                status = 'upload'
335                        if file_props.has_key('paused') and file_props['paused'] == True:
336                                status = 'pause'
337                        elif file_props.has_key('stalled') and file_props['stalled'] == True:
338                                status = 'waiting'
339                        if file_props.has_key('connected') and file_props['connected'] == False:
340                                status = 'stop'
341                        self.model.set(iter, 0, self.images[status])
342                        if percent == 100:
343                                self.set_status(typ, sid, 'ok')
344       
345        def get_iter_by_sid(self, typ, sid):
346                '''returns iter to the row, which holds file transfer, identified by the
347                session id'''
348                iter = self.model.get_iter_root()
349                while iter:
350                        if typ + sid == self.model[iter][4]:
351                                return iter
352                        iter = self.model.iter_next(iter)
353       
354        def get_sid(self):
355                ''' create random string of length 16'''
356                rng = range(65, 90)
357                rng.extend(range(48, 57))
358                char_sequence = map(lambda e:chr(e), rng)
359                from random import sample
360                return reduce(lambda e1, e2: e1 + e2, 
361                                sample(char_sequence, 16))
362       
363        def get_send_file_props(self, account, contact, file_path, file_name):
364                ''' create new file_props dict and set initial file transfer
365                properties in it'''
366                file_props = {'file-name' : file_path, 'name' : file_name, 
367                        'type' : 's'}
368                if os.path.exists(file_path) and os.path.isfile(file_path):
369                        stat = os.stat(file_path)
370                os.stat(file_path)
371                file_props['size'] = str(stat[6])
372                file_props['sid'] = self.get_sid()
373                file_props['completed'] = False
374                file_props['started'] = False
375                file_props['sender'] = account
376                file_props['receiver'] = contact
377                file_props['tt_account'] = account
378                return file_props
379               
380        def add_transfer(self, account, contact, file_props):
381                ''' add new transfer to FT window and show the FT window '''
382                self.on_transfers_list_leave_notify_event(None)
383                if file_props is None:
384                        return
385                self.files_props[file_props['type']][file_props['sid']] = file_props
386                iter = self.model.append()
387                text_labels = '<b>' + _('Name: ') + '</b>\n' 
388                if file_props['type'] == 'r':
389                        text_labels += '<b>' + _('Sender: ') + '</b>' 
390                else:
391                        text_labels += '<b>' + _('Recipient: ') + '</b>' 
392                text_props = gtkgui_helpers.escape_for_pango_markup(file_props['name']) + '\n'
393                text_props += gtkgui_helpers.escape_for_pango_markup(contact.name)
394                self.model.set(iter, 1, text_labels, 2, text_props, 4, \
395                        file_props['type'] + file_props['sid'])
396                self.set_progress(file_props['type'], file_props['sid'], 0, iter)
397                if file_props.has_key('started') and file_props['started'] is False:
398                        status = 'waiting'
399                elif file_props['type'] == 'r':
400                        status = 'download'
401                else:
402                        status = 'upload'
403                file_props['tt_account'] = account
404                self.set_status(file_props['type'], file_props['sid'], status)
405                self.window.show_all()
406       
407        def on_transfers_list_motion_notify_event(self, widget, event):
408                pointer = self.tree.get_pointer()
409                orig = widget.window.get_origin()
410                props = widget.get_path_at_pos(int(event.x), int(event.y))
411                self.height_diff = pointer[1] - int(event.y)
412                if self.tooltip.timeout > 0:
413                        if not props or self.tooltip.id != props[0]:
414                                self.tooltip.hide_tooltip()
415                if props:
416                        [row, col, x, y] = props
417                        iter = None
418                        try:
419                                iter = self.model.get_iter(row)
420                        except:
421                                self.tooltip.hide_tooltip()
422                                return
423                        sid = self.model[iter][4]
424                        file_props = self.files_props[sid[0]][sid[1:]]
425                        if file_props is not None:
426                                if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
427                                        self.tooltip.id = row
428                                        self.tooltip.timeout = gobject.timeout_add(500,
429                                                self.show_tooltip, widget)
430       
431        def on_transfers_list_leave_notify_event(self, widget = None, event = None):
432                if event is not None:
433                        self.height_diff = int(event.y)
434                elif self.height_diff is 0:
435                        return
436                pointer = self.tree.get_pointer()
437                props = self.tree.get_path_at_pos(pointer[0], 
438                        pointer[1] - self.height_diff)
439                if self.tooltip.timeout > 0:
440                        if not props or self.tooltip.id == props[0]:
441                                self.tooltip.hide_tooltip()
442       
443        def on_transfers_list_row_activated(self, widget, path, col):
444                # try to open the containing folder
445                self.on_open_folder_menuitem_activate(widget)
446               
447        def is_transfer_paused(self, file_props):
448                if file_props.has_key('stopped') and file_props['stopped']:
449                        return False
450                if file_props.has_key('completed') and file_props['completed']:
451                        return False
452                if not file_props.has_key('disconnect_cb'):
453                        return False
454                return file_props['paused']
455               
456        def is_transfer_active(self, file_props):
457                if file_props.has_key('stopped') and file_props['stopped']:
458                        return False
459                if file_props.has_key('completed') and file_props['completed']:
460                        return False
461                if not file_props.has_key('started') or not file_props['started']:
462                        return False
463                if not file_props.has_key('paused'):
464                        return True
465                return not file_props['paused']
466               
467        def is_transfer_stoped(self, file_props):
468                if file_props.has_key('error') and file_props['error'] != 0:
469                        return True
470                if file_props.has_key('completed') and file_props['completed']:
471                        return True
472                if not file_props.has_key('stopped') or not \
473                        file_props['stopped']:
474                        return False
475                return True
476               
477        def set_all_insensitive(self):
478                ''' make all buttons/menuitems insensitive '''
479                self.pause_button.set_sensitive(False)
480                self.pause_menuitem.set_sensitive(False)
481                self.continue_menuitem.set_sensitive(False)
482                self.remove_button.set_sensitive(False)
483                self.remove_menuitem.set_sensitive(False)
484                self.cancel_button.set_sensitive(False)
485                self.cancel_menuitem.set_sensitive(False)
486                self.open_folder_menuitem.set_sensitive(False)
487       
488        def set_buttons_sensitive(self, path, is_row_selected):
489                ''' make buttons/menuitems sensitive as appropriate to
490                the state of file transfer located at path 'path' '''
491                if path is None:
492                        self.set_all_insensitive()
493                        return
494                current_iter = self.model.get_iter(path)
495                sid = self.model[current_iter][4]
496                file_props = self.files_props[sid[0]][sid[1:]]
497                self.remove_button.set_sensitive(is_row_selected)
498                self.remove_menuitem.set_sensitive(is_row_selected)
499                self.open_folder_menuitem.set_sensitive(is_row_selected)
500                is_stopped = False
501                if self.is_transfer_stoped(file_props):
502                        is_stopped = True
503                self.cancel_button.set_sensitive(not is_stopped)
504                self.cancel_menuitem.set_sensitive(not is_stopped)
505                if not is_row_selected:
506                        # no selection, disable the buttons
507                        self.set_all_insensitive()
508                elif not is_stopped:
509                        if self.is_transfer_active(file_props):
510                                # file transfer is active
511                                self.toggle_pause_continue(True)
512                                self.pause_button.set_sensitive(True)
513                        elif self.is_transfer_paused(file_props):
514                                # file transfer is paused
515                                self.toggle_pause_continue(False)
516                                self.pause_button.set_sensitive(True)
517                        else:
518                                self.pause_button.set_sensitive(False)