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

Revision 3447, 29.1 kB (checked in by nk, 3 years ago)

overwite with http://trac.gajim.org/file/trunk/src/filetransfers_window.py?rev=3350

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
25import sys
26
27import gtkgui_helpers
28import tooltips
29import dialogs
30
31from common import gajim
32from common import helpers
33from common import i18n
34
35_ = i18n._
36APP = i18n.APP
37gtk.glade.bindtextdomain (APP, i18n.DIR)
38gtk.glade.textdomain (APP)
39
40GTKGUI_GLADE = 'gtkgui.glade'
41
42class FileTransfersWindow:
43        def __init__(self, plugin):
44                self.files_props = {'r' : {}, 's': {}}
45                self.plugin = plugin
46                self.height_diff = 0
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 find_transfer_by_jid(self, account, jid):
115                ''' find all transfers with peer 'jid' that belong to 'account' '''
116                active_transfers = [[],[]] # ['senders', 'receivers']
117               
118                # 'account' is the sender
119                for file_props in self.files_props['s'].values():
120                        if file_props['tt_account'] == account:
121                                receiver_jid = unicode(file_props['receiver']).split('/')[0]
122                                if jid == receiver_jid:
123                                        if not self.is_transfer_stoped(file_props):
124                                                active_transfers[0].append(file_props)
125               
126                # 'account' is the recipient
127                for file_props in self.files_props['r'].values():
128                        if file_props['tt_account'] == account:
129                                sender_jid = unicode(file_props['sender']).split('/')[0]
130                                if jid == sender_jid:
131                                        if not self.is_transfer_stoped(file_props):
132                                                active_transfers[1].append(file_props)
133                return active_transfers
134       
135        def show_completed(self, jid, file_props):
136                ''' show a dialog saying that file (file_props) has been transferred'''
137                self.window.present()
138                self.window.window.focus()
139                if file_props['type'] == 'r':
140                        # file path is used below in 'Save in'
141                        (file_path, file_name) = os.path.split(file_props['file-name'])
142                else:
143                        file_name = file_props['name']
144                sectext = '\t' + _('Filename: %s') % \
145                        gtkgui_helpers.escape_for_pango_markup(file_name)
146                sectext += '\n\t' + _('Size: %s') % \
147                helpers.convert_bytes(file_props['size'])
148                if file_props['type'] == 'r':
149                        jid = unicode(file_props['sender']).split('/')[0]
150                        sender_name = gajim.get_first_contact_instance_from_jid( 
151                                file_props['tt_account'], jid).name
152                        sender = gtkgui_helpers.escape_for_pango_markup(sender_name)
153                else:
154                        #You is a reply of who send a file
155                        sender = _('You')
156                sectext += '\n\t' +_('Sender: %s') % sender
157                sectext += '\n\t' +_('Recipient: ')
158                if file_props['type'] == 's':
159                        jid = unicode(file_props['receiver']).split('/')[0]
160                        receiver_name = gajim.get_first_contact_instance_from_jid( 
161                                file_props['tt_account'], jid).name
162                        recipient = gtkgui_helpers.escape_for_pango_markup(receiver_name)
163                else:
164                        #You is a reply of who received a file
165                        recipient = ('You')
166                sectext += recipient
167                if file_props['type'] == 'r':
168                        sectext += '\n\t' +_('Saved in: %s') % \
169                                gtkgui_helpers.escape_for_pango_markup(file_path)
170                dialog = dialogs.HigDialog(None, _('File transfer completed'), sectext, 
171                        gtk.STOCK_DIALOG_INFO, [
172                                [_('_Open Containing Folder'), gtk.RESPONSE_ACCEPT], 
173                                [ gtk.STOCK_OK, gtk.RESPONSE_OK ]])
174                button = dialog.get_button(1)
175                if gtk.gtk_version >= (2, 6, 0) and gtk.pygtk_version >= (2, 6, 0):
176                        button.set_image(gtk.image_new_from_stock(
177                gtk.STOCK_DIRECTORY, gtk.ICON_SIZE_BUTTON))
178                dialog.show_all()
179                if file_props['type'] == 's':
180                        button.hide()
181                response = dialog.run()
182                dialog.destroy()
183                if response == gtk.RESPONSE_ACCEPT:
184                        if not file_props.has_key('file-name'):
185                                return
186                        (path, file) = os.path.split(file_props['file-name'])
187                        if os.path.exists(path) and os.path.isdir(path):
188                                helpers.launch_file_manager(path)
189                        self.tree.get_selection().unselect_all()
190               
191        def show_request_error(self, file_props):
192                ''' show error dialog to the recipient saying that transfer
193                has been canceled'''
194                self.window.present()
195                self.window.window.focus()
196                dialogs.InformationDialog(_('File transfer canceled'), _('Connection with peer cannot be established.'))
197                self.tree.get_selection().unselect_all()
198               
199        def show_send_error(self, file_props):
200                ''' show error dialog to the sender saying that transfer
201                has been canceled'''
202                self.window.present()
203                self.window.window.focus()
204                dialogs.InformationDialog(_('File transfer canceled'),
205_('Connection with peer cannot be established.'))
206                self.tree.get_selection().unselect_all()
207       
208        def show_stopped(self, jid, file_props):
209                self.window.present()
210                self.window.window.focus()
211                if file_props['type'] == 'r':
212                        file_name = os.path.basename(file_props['file-name'])
213                else:
214                        file_name = file_props['name']
215                sectext = '\t' + _('Filename: %s') % \
216                        gtkgui_helpers.escape_for_pango_markup(file_name)
217                sectext += '\n\t' + _('Sender: %s') % \
218                        gtkgui_helpers.escape_for_pango_markup(jid)
219                dialogs.ErrorDialog(_('File transfer stopped by the contact of the other side'), \
220                        sectext).get_response()
221                self.tree.get_selection().unselect_all()
222               
223        def show_file_send_request(self, account, contact):
224                #FIXME: user better name for this function
225                #atm it's like it shows popup for incoming file transfer request
226                last_send_dir = gajim.config.get('last_send_dir')
227                dialog = gtk.FileChooserDialog(title=_('Choose File to Send...'), 
228                        action=gtk.FILE_CHOOSER_ACTION_OPEN, 
229                        buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
230                butt = dialog.add_button(_('Send'), gtk.RESPONSE_OK)
231                butt.set_use_stock(True)
232                dialog.set_default_response(gtk.RESPONSE_OK)
233                dialog.set_select_multiple(True) # we can select many files to send
234                if last_send_dir and os.path.isdir(last_send_dir):
235                        dialog.set_current_folder(last_send_dir)
236                else:
237                        home_dir = os.path.expanduser('~')
238                        dialog.set_current_folder(home_dir)
239                file_props = {}
240                response = dialog.run()
241                if response == gtk.RESPONSE_OK:
242                        files_path_list = dialog.get_filenames()
243                        dialog.destroy()
244                        file_dir = None
245                        for file_path in files_path_list:
246                                file_path = file_path.decode(sys.getfilesystemencoding())
247                                if os.path.isfile(file_path):
248                                        file_dir = os.path.dirname(file_path)
249                                        self.send_file(account, contact, file_path)
250                        if file_dir:
251                                gajim.config.set('last_send_dir', file_dir)
252                else:
253                        dialog.destroy()
254
255        def send_file(self, account, contact, file_path):
256                ''' start the real transfer(upload) of the file '''
257                if type(contact) == str:
258                        if contact.find('/') == -1:
259                                return
260                        (jid, resource) = contact.split("/", 1)
261                        contact = gajim.Contact(jid = jid, resource = resource)
262                (file_dir, file_name) = os.path.split(file_path)
263                file_props = self.get_send_file_props(account, contact, 
264                                file_path, file_name)
265                self.add_transfer(account, contact, file_props)
266                gajim.connections[account].send_file_request(file_props)
267       
268        def show_file_request(self, account, contact, file_props):
269                ''' show dialog asking for comfirmation and store location of new
270                file requested by a contact'''
271                if file_props is None or not file_props.has_key('name'):
272                        return
273                last_save_dir = gajim.config.get('last_save_dir')
274                sec_text = '\t' + _('File: %s') % \
275                        gtkgui_helpers.escape_for_pango_markup(file_props['name'])
276                if file_props.has_key('size'):
277                        sec_text += '\n\t' + _('Size: %s') % \
278                                helpers.convert_bytes(file_props['size'])
279                if file_props.has_key('mime-type'):
280                        sec_text += '\n\t' + _('Type: %s') % \
281                                gtkgui_helpers.escape_for_pango_markup(file_props['mime-type'])
282                if file_props.has_key('desc'):
283                        sec_text += '\n\t' + _('Description: %s') % \
284                                gtkgui_helpers.escape_for_pango_markup(file_props['desc'])
285                prim_text = _('%s wants to send you a file:') % contact.jid
286                dialog = dialogs.ConfirmationDialog(prim_text, sec_text)
287                if dialog.get_response() == gtk.RESPONSE_OK:
288                        dialog = gtk.FileChooserDialog(title=_('Save File as...'), 
289                                action=gtk.FILE_CHOOSER_ACTION_SAVE, 
290                                buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, 
291                                gtk.STOCK_SAVE, gtk.RESPONSE_OK))
292                        dialog.set_current_name(file_props['name'])
293                        dialog.set_default_response(gtk.RESPONSE_OK)
294                        if last_save_dir and os.path.isdir(last_save_dir):
295                                dialog.set_current_folder(last_save_dir)
296                        else:
297                                home_dir = os.path.expanduser('~')
298                                dialog.set_current_folder(home_dir)
299                        while True:
300                                response = dialog.run()
301                                if response == gtk.RESPONSE_OK:
302                                        file_path = dialog.get_filename()
303                                        file_path = file_path.decode('utf-8')
304                                        if os.path.exists(file_path):
305                                                #FIXME: pango does not work here.
306                                                #FIXME: if gtk2.8 do this via signal
307#http://developer.gnome.org/doc/API/2.0/gtk/GtkFileChooser.html#GtkFileChooser--do-overwrite-confirmation
308                                                primtext = _('This file already exists')
309                                                sectext = _('Would you like to overwrite it?')
310                                                dialog2 = dialogs.ConfirmationDialog(primtext, sectext)
311                                                if dialog2.get_response() != gtk.RESPONSE_OK:
312                                                        continue
313                                        file_dir = os.path.dirname(file_path)
314                                        if file_dir:
315                                                gajim.config.set('last_save_dir', file_dir)
316                                        file_props['file-name'] = file_path
317                                        self.add_transfer(account, contact, file_props)
318                                        gajim.connections[account].send_file_approval(file_props)
319                                else:
320                                        gajim.connections[account].send_file_rejection(file_props)
321                                dialog.destroy()
322                                break
323                else:
324                        gajim.connections[account].send_file_rejection(file_props)
325       
326        def set_images(self):
327                ''' create pixbufs for status images in transfer rows'''
328                self.images = {}
329                self.images['upload'] = self.window.render_icon(gtk.STOCK_GO_UP, 
330                        gtk.ICON_SIZE_MENU)
331                self.images['download'] = self.window.render_icon(gtk.STOCK_GO_DOWN, 
332                        gtk.ICON_SIZE_MENU)
333                self.images['stop'] = self.window.render_icon(gtk.STOCK_STOP, 
334                        gtk.ICON_SIZE_MENU)
335                self.images['waiting'] = self.window.render_icon(gtk.STOCK_REFRESH, 
336                        gtk.ICON_SIZE_MENU)
337                self.images['pause'] = self.window.render_icon(gtk.STOCK_MEDIA_PAUSE, 
338                        gtk.ICON_SIZE_MENU)
339                self.images['continue'] = self.window.render_icon(gtk.STOCK_MEDIA_PLAY, 
340                        gtk.ICON_SIZE_MENU)
341                self.images['ok'] = self.window.render_icon(gtk.STOCK_APPLY, 
342                        gtk.ICON_SIZE_MENU)
343                       
344        def set_status(self, typ, sid, status):
345                ''' change the status of a transfer to state 'status' '''
346                iter = self.get_iter_by_sid(typ, sid)
347                if iter is None:
348                        return
349                sid = self.model[iter][4].decode('utf-8')
350                file_props = self.files_props[sid[0]][sid[1:]]
351                if status == 'stop':
352                        file_props['stopped'] = True
353                elif status == 'ok':
354                        file_props['completed'] = True
355                self.model.set(iter, 0, self.images[status])
356       
357        def set_progress(self, typ, sid, transfered_size, iter = None):
358                ''' change the progress of a transfer with new transfered size'''
359                if not self.files_props[typ].has_key(sid):
360                        return
361                file_props = self.files_props[typ][sid]
362                full_size = int(file_props['size'])
363                if full_size == 0:
364                        percent = 0
365                else:
366                        percent = round(float(transfered_size) / full_size * 100)
367                if iter is None:
368                        iter = self.get_iter_by_sid(typ, sid)
369                if iter is not None:
370                        text = unicode(percent) + '%\n' 
371                        if transfered_size == 0:
372                                text += '0'
373                        else:
374                                text += helpers.convert_bytes(transfered_size)
375                        text += '/' + helpers.convert_bytes(full_size)
376                        self.model.set(iter, 3, text)
377                        if file_props['type'] == 'r':
378                                status = 'download'
379                        else:
380                                status = 'upload'
381                        if file_props.has_key('paused') and file_props['paused'] == True:
382                                status = 'pause'
383                        elif file_props.has_key('stalled') and file_props['stalled'] == True:
384                                status = 'waiting'
385                        if file_props.has_key('connected') and file_props['connected'] == False:
386                                status = 'stop'
387                        self.model.set(iter, 0, self.images[status])
388                        if percent == 100:
389                                self.set_status(typ, sid, 'ok')
390       
391        def get_iter_by_sid(self, typ, sid):
392                '''returns iter to the row, which holds file transfer, identified by the
393                session id'''
394                iter = self.model.get_iter_root()
395                while iter:
396                        if typ + sid == self.model[iter][4].decode('utf-8'):
397                                return iter
398                        iter = self.model.iter_next(iter)
399       
400        def get_sid(self):
401                ''' create random string of length 16'''
402                rng = range(65, 90)
403                rng.extend(range(48, 57))
404                char_sequence = map(lambda e:chr(e), rng)
405                from random import sample
406                return reduce(lambda e1, e2: e1 + e2, 
407                                sample(char_sequence, 16))
408       
409        def get_send_file_props(self, account, contact, file_path, file_name):
410                ''' create new file_props dict and set initial file transfer
411                properties in it'''
412                file_props = {'file-name' : file_path, 'name' : file_name, 
413                        'type' : 's'}
414                if os.path.exists(file_path) and os.path.isfile(file_path):
415                        stat = os.stat(file_path)
416                os.stat(file_path)
417                file_props['size'] = unicode(stat[6])
418                file_props['sid'] = self.get_sid()
419                file_props['completed'] = False
420                file_props['started'] = False
421                file_props['sender'] = account
422                file_props['receiver'] = contact
423                file_props['tt_account'] = account
424                return file_props
425               
426        def add_transfer(self, account, contact, file_props):
427                ''' add new transfer to FT window and show the FT window '''
428                self.on_transfers_list_leave_notify_event(None)
429                if file_props is None:
430                        return
431                self.files_props[file_props['type']][file_props['sid']] = file_props
432                iter = self.model.append()
433                text_labels = '<b>' + _('Name: ') + '</b>\n' 
434                if file_props['type'] == 'r':
435                        text_labels += '<b>' + _('Sender: ') + '</b>' 
436                else:
437                        text_labels += '<b>' + _('Recipient: ') + '</b>' 
438                       
439                if file_props['type'] == 'r':
440                        (file_path, file_name) = os.path.split(file_props['file-name'])
441                else:
442                        file_name = file_props['name']
443                text_props = gtkgui_helpers.escape_for_pango_markup(file_name) + '\n'
444                text_props += gtkgui_helpers.escape_for_pango_markup(contact.name)
445                self.model.set(iter, 1, text_labels, 2, text_props, 4, \
446                        file_props['type'] + file_props['sid'])
447                self.set_progress(file_props['type'], file_props['sid'], 0, iter)
448                if file_props.has_key('started') and file_props['started'] is False:
449                        status = 'waiting'
450                elif file_props['type'] == 'r':
451                        status = 'download'
452                else:
453                        status = 'upload'
454                file_props['tt_account'] = account
455                self.set_status(file_props['type'], file_props['sid'], status)
456                self.window.show_all()
457       
458        def on_transfers_list_motion_notify_event(self, widget, event):
459                pointer = self.tree.get_pointer()
460                orig = widget.window.get_origin()
461                props = widget.get_path_at_pos(int(event.x), int(event.y))
462                self.height_diff = pointer[1] - int(event.y)
463                if self.tooltip.timeout > 0:
464                        if not props or self.tooltip.id != props[0]:
465                                self.tooltip.hide_tooltip()
466                if props:
467                        [row, col, x, y] = props
468                        iter = None
469                        try:
470                                iter = self.model.get_iter(row)
471                        except:
472                                self.tooltip.hide_tooltip()
473                                return
474                        sid = self.model[iter][4].decode('utf-8')
475                        file_props = self.files_props[sid[0]][sid[1:]]
476                        if file_props is not None:
477                                if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
478                                        self.tooltip.id = row
479                                        self.tooltip.timeout = gobject.timeout_add(500,
480                                                self.show_tooltip, widget)
481       
482        def on_transfers_list_leave_notify_event(self, widget = None, event = None):
483                if event is not None:
484                        self.height_diff = int(event.y)
485                elif self.height_diff is 0:
486                        return
487                pointer = self.tree.get_pointer()
488                props = self.tree.get_path_at_pos(pointer[0], 
489                        pointer[1] - self.height_diff)
490                if self.tooltip.timeout > 0:
491                        if not props or self.tooltip.id == props[0]:
492                                self.tooltip.hide_tooltip()
493       
494        def on_transfers_list_row_activated(self, widget, path, col):
495                # try to open the containing folder
496                self.on_open_folder_menuitem_activate(widget)
497               
498        def is_transfer_paused(self, file_props):
499                if file_props.has_key('stopped') and file_props['stopped']:
500                        return False
501                if file_props.has_key('completed') and file_props['completed']:
502                        return False
503                if not file_props.has_key('disconnect_cb'):
504                        return False
505                return file_props['paused']
506               
507        def is_transfer_active(self, file_props):
508                if file_props.has_key('stopped') and file_props['stopped']:
509                        return False
510                if file_props.has_key('completed') and file_props['completed']:
511                        return False
512                if not file_props.has_key('started') or not file_props['started']:
513                        return False
514                if not file_props.has_key('paused'):
515                        return True
516                return not file_props['paused']
517               
518        def is_transfer_stoped(self, file_props):
519                if file