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

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

remove a bad decode

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                        for file_path in files_path_list:
245                                file_path = file_path.decode(sys.getfilesystemencoding())
246                                if os.path.isfile(file_path):
247                                        file_dir = os.path.dirname(file_path)
248                                        self.send_file(account, contact, file_path)
249                        if file_dir:
250                                gajim.config.set('last_send_dir', file_dir)
251                else:
252                        dialog.destroy()
253
254        def send_file(self, account, contact, file_path):
255                ''' start the real transfer(upload) of the file '''
256                if type(contact) == str:
257                        if contact.find('/') == -1:
258                                return
259                        (jid, resource) = contact.split("/", 1)
260                        contact = gajim.Contact(jid = jid, resource = resource)
261                (file_dir, file_name) = os.path.split(file_path)
262                file_props = self.get_send_file_props(account, contact, 
263                                file_path, file_name)
264                self.add_transfer(account, contact, file_props)
265                gajim.connections[account].send_file_request(file_props)
266       
267        def show_file_request(self, account, contact, file_props):
268                ''' show dialog asking for comfirmation and store location of new
269                file requested by a contact'''
270                if file_props is None or not file_props.has_key('name'):
271                        return
272                last_save_dir = gajim.config.get('last_save_dir')
273                sec_text = '\t' + _('File: %s') % \
274                        gtkgui_helpers.escape_for_pango_markup(file_props['name'])
275                if file_props.has_key('size'):
276                        sec_text += '\n\t' + _('Size: %s') % \
277                                helpers.convert_bytes(file_props['size'])
278                if file_props.has_key('mime-type'):
279                        sec_text += '\n\t' + _('Type: %s') % \
280                                gtkgui_helpers.escape_for_pango_markup(file_props['mime-type'])
281                if file_props.has_key('desc'):
282                        sec_text += '\n\t' + _('Description: %s') % \
283                                gtkgui_helpers.escape_for_pango_markup(file_props['desc'])
284                prim_text = _('%s wants to send you a file:') % contact.jid
285                dialog = dialogs.ConfirmationDialog(prim_text, sec_text)
286                if dialog.get_response() == gtk.RESPONSE_OK:
287                        dialog = gtk.FileChooserDialog(title=_('Save File as...'), 
288                                action=gtk.FILE_CHOOSER_ACTION_SAVE, 
289                                buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, 
290                                gtk.STOCK_SAVE, gtk.RESPONSE_OK))
291                        dialog.set_current_name(file_props['name'])
292                        dialog.set_default_response(gtk.RESPONSE_OK)
293                        if last_save_dir and os.path.isdir(last_save_dir):
294                                dialog.set_current_folder(last_save_dir)
295                        else:
296                                home_dir = os.path.expanduser('~')
297                                dialog.set_current_folder(home_dir)
298                        while True:
299                                response = dialog.run()
300                                if response == gtk.RESPONSE_OK:
301                                        file_path = dialog.get_filename()
302                                        file_path = file_path.decode('utf-8')
303                                        if os.path.exists(file_path):
304                                                #FIXME: pango does not work here.
305                                                #FIXME: if gtk2.8 do this via signal
306#http://developer.gnome.org/doc/API/2.0/gtk/GtkFileChooser.html#GtkFileChooser--do-overwrite-confirmation
307                                                primtext = _('This file already exists')
308                                                sectext = _('Would you like to overwrite it?')
309                                                dialog2 = dialogs.ConfirmationDialog(primtext, sectext)
310                                                if dialog2.get_response() != gtk.RESPONSE_OK:
311                                                        continue
312                                        file_dir = os.path.dirname(file_path)
313                                        if file_dir:
314                                                gajim.config.set('last_save_dir', file_dir)
315                                        file_props['file-name'] = file_path
316                                        self.add_transfer(account, contact, file_props)
317                                        gajim.connections[account].send_file_approval(file_props)
318                                else:
319                                        gajim.connections[account].send_file_rejection(file_props)
320                                dialog.destroy()
321                                break
322                else:
323                        gajim.connections[account].send_file_rejection(file_props)
324       
325        def set_images(self):
326                ''' create pixbufs for status images in transfer rows'''
327                self.images = {}
328                self.images['upload'] = self.window.render_icon(gtk.STOCK_GO_UP, 
329                        gtk.ICON_SIZE_MENU)
330                self.images['download'] = self.window.render_icon(gtk.STOCK_GO_DOWN, 
331                        gtk.ICON_SIZE_MENU)
332                self.images['stop'] = self.window.render_icon(gtk.STOCK_STOP, 
333                        gtk.ICON_SIZE_MENU)
334                self.images['waiting'] = self.window.render_icon(gtk.STOCK_REFRESH, 
335                        gtk.ICON_SIZE_MENU)
336                self.images['pause'] = self.window.render_icon(gtk.STOCK_MEDIA_PAUSE, 
337                        gtk.ICON_SIZE_MENU)
338                self.images['continue'] = self.window.render_icon(gtk.STOCK_MEDIA_PLAY, 
339                        gtk.ICON_SIZE_MENU)
340                self.images['ok'] = self.window.render_icon(gtk.STOCK_APPLY, 
341                        gtk.ICON_SIZE_MENU)
342                       
343        def set_status(self, typ, sid, status):
344                ''' change the status of a transfer to state 'status' '''
345                iter = self.get_iter_by_sid(typ, sid)
346                if iter is None:
347                        return
348                sid = self.model[iter][4].decode('utf-8')
349                file_props = self.files_props[sid[0]][sid[1:]]
350                if status == 'stop':
351                        file_props['stopped'] = True
352                elif status == 'ok':
353                        file_props['completed'] = True
354                self.model.set(iter, 0, self.images[status])
355       
356        def set_progress(self, typ, sid, transfered_size, iter = None):
357                ''' change the progress of a transfer with new transfered size'''
358                if not self.files_props[typ].has_key(sid):
359                        return
360                file_props = self.files_props[typ][sid]
361                full_size = int(file_props['size'])
362                if full_size == 0:
363                        percent = 0
364                else:
365                        percent = round(float(transfered_size) / full_size * 100)
366                if iter is None:
367                        iter = self.get_iter_by_sid(typ, sid)
368                if iter is not None:
369                        text = unicode(percent) + '%\n' 
370                        if transfered_size == 0:
371                                text += '0'
372                        else:
373                                text += helpers.convert_bytes(transfered_size)
374                        text += '/' + helpers.convert_bytes(full_size)
375                        self.model.set(iter, 3, text)
376                        if file_props['type'] == 'r':
377                                status = 'download'
378                        else:
379                                status = 'upload'
380                        if file_props.has_key('paused') and file_props['paused'] == True:
381                                status = 'pause'
382                        elif file_props.has_key('stalled') and file_props['stalled'] == True:
383                                status = 'waiting'
384                        if file_props.has_key('connected') and file_props['connected'] == False:
385                                status = 'stop'
386                        self.model.set(iter, 0, self.images[status])
387                        if percent == 100:
388                                self.set_status(typ, sid, 'ok')
389       
390        def get_iter_by_sid(self, typ, sid):
391                '''returns iter to the row, which holds file transfer, identified by the
392                session id'''
393                iter = self.model.get_iter_root()
394                while iter:
395                        if typ + sid == self.model[iter][4].decode('utf-8'):
396                                return iter
397                        iter = self.model.iter_next(iter)
398       
399        def get_sid(self):
400                ''' create random string of length 16'''
401                rng = range(65, 90)
402                rng.extend(range(48, 57))
403                char_sequence = map(lambda e:chr(e), rng)
404                from random import sample
405                return reduce(lambda e1, e2: e1 + e2, 
406                                sample(char_sequence, 16))
407       
408        def get_send_file_props(self, account, contact, file_path, file_name):
409                ''' create new file_props dict and set initial file transfer
410                properties in it'''
411                file_props = {'file-name' : file_path, 'name' : file_name, 
412                        'type' : 's'}
413                if os.path.exists(file_path) and os.path.isfile(file_path):
414                        stat = os.stat(file_path)
415                os.stat(file_path)
416                file_props['size'] = unicode(stat[6])
417                file_props['sid'] = self.get_sid()
418                file_props['completed'] = False
419                file_props['started'] = False
420                file_props['sender'] = account
421                file_props['receiver'] = contact
422                file_props['tt_account'] = account
423                return file_props
424               
425        def add_transfer(self, account, contact, file_props):
426                ''' add new transfer to FT window and show the FT window '''
427                self.on_transfers_list_leave_notify_event(None)
428                if file_props is None:
429                        return
430                self.files_props[file_props['type']][file_props['sid']] = file_props
431                iter = self.model.append()
432                text_labels = '<b>' + _('Name: ') + '</b>\n' 
433                if file_props['type'] == 'r':
434                        text_labels += '<b>' + _('Sender: ') + '</b>' 
435                else:
436                        text_labels += '<b>' + _('Recipient: ') + '</b>' 
437                       
438                if file_props['type'] == 'r':
439                        (file_path, file_name) = os.path.split(file_props['file-name'])
440                else:
441                        file_name = file_props['name']
442                text_props = gtkgui_helpers.escape_for_pango_markup(file_name) + '\n'
443                text_props += gtkgui_helpers.escape_for_pango_markup(contact.name)
444                self.model.set(iter, 1, text_labels, 2, text_props, 4, \
445                        file_props['type'] + file_props['sid'])
446                self.set_progress(file_props['type'], file_props['sid'], 0, iter)
447                if file_props.has_key('started') and file_props['started'] is False:
448                        status = 'waiting'
449                elif file_props['type'] == 'r':
450                        status = 'download'
451                else:
452                        status = 'upload'
453                file_props['tt_account'] = account
454                self.set_status(file_props['type'], file_props['sid'], status)
455                self.window.show_all()
456       
457        def on_transfers_list_motion_notify_event(self, widget, event):
458                pointer = self.tree.get_pointer()
459                orig = widget.window.get_origin()
460                props = widget.get_path_at_pos(int(event.x), int(event.y))
461                self.height_diff = pointer[1] - int(event.y)
462                if self.tooltip.timeout > 0:
463                        if not props or self.tooltip.id != props[0]:
464                                self.tooltip.hide_tooltip()
465                if props:
466                        [row, col, x, y] = props
467                        iter = None
468                        try:
469                                iter = self.model.get_iter(row)
470                        except:
471                                self.tooltip.hide_tooltip()
472                                return
473                        sid = self.model[iter][4].decode('utf-8')
474                        file_props = self.files_props[sid[0]][sid[1:]]
475                        if file_props is not None:
476                                if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
477                                        self.tooltip.id = row
478                                        self.tooltip.timeout = gobject.timeout_add(500,
479                                                self.show_tooltip, widget)
480       
481        def on_transfers_list_leave_notify_event(self, widget = None, event = None):
482                if event is not None:
483                        self.height_diff = int(event.y)
484                elif self.height_diff is 0:
485                        return
486                pointer = self.tree.get_pointer()
487                props = self.tree.get_path_at_pos(pointer[0], 
488                        pointer[1] - self.height_diff)
489                if self.tooltip.timeout > 0:
490                        if not props or self.tooltip.id == props[0]:
491                                self.tooltip.hide_tooltip()
492       
493        def on_transfers_list_row_activated(self, widget, path, col):
494                # try to open the containing folder
495                self.on_open_folder_menuitem_activate(widget)
496               
497        def is_transfer_paused(self, file_props):
498                if file_props.has_key('stopped') and file_props['stopped']:
499                        return False
500                if file_props.has_key('completed') and file_props['completed']:
501                        return False
502                if not file_props.has_key('disconnect_cb'):
503                        return False
504                return file_props['paused']
505               
506        def is_transfer_active(self, file_props):
507                if file_props.has_key('stopped') and file_props['stopped']:
508                        return False
509                if file_props.has_key('completed') and file_props['completed']:
510                        return False
511                if not file_props.has_key('started') or not file_props['started']:
512                        return False
513                if not file_props.has_key('paused'):
514                        return True
515                return not file_props['paused']
516               
517        def is_transfer_stoped(self, file_props):
518                if file_props.has_key('error') and file_props['error'] != 0:
519