correctly set transient window for muc error dialogs. Fixes #6943
[gajim.git] / plugins / ftp_manager / ftp_manager.py
blobba1aad6e4f77e6516579cd545de32121236477b1
1 # -*- coding: utf-8 -*-
3 ## plugins/ftp_manager/ftp_manager.py
4 ##
5 ## Copyright (C) 2010 Denis Fomin <fominde AT gmail.com>
6 ##
7 ## This file is part of Gajim.
8 ##
9 ## Gajim is free software; you can redistribute it and/or modify
10 ## it under the terms of the GNU General Public License as published
11 ## by the Free Software Foundation; version 3 only.
13 ## Gajim is distributed in the hope that it will be useful,
14 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ## GNU General Public License for more details.
18 ## You should have received a copy of the GNU General Public License
19 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
21 import gtk
22 import pango
23 import gobject
24 import ftplib
25 import io
26 import threading
27 import ConfigParser
28 import os
29 import fnmatch
30 import sys
32 from common import gajim
33 from plugins import GajimPlugin
34 from plugins.helpers import log_calls, log
35 from dialogs import WarningDialog, HigDialog
36 from plugins.gui import GajimPluginConfigDialog
37 from common import i18n
40 class FtpManager(GajimPlugin):
42 @log_calls('FtpManagerPlugin')
43 def init(self):
44 self.config_dialog = FtpManagerPluginConfigDialog(self)
45 self.config_default_values = {'ftp_server': ('ftp.gajim.org', '')}
47 @log_calls('FtpManagerPlugin')
48 def activate(self):
49 self.pl_menuitem = gajim.interface.roster.xml.get_object(
50 'plugins_menuitem')
51 self.id_ = self.pl_menuitem.connect_after('activate', self.on_activate)
52 if 'plugins' in gajim.interface.instances:
53 self.on_activate(None)
55 @log_calls('FtpManagerPlugin')
56 def deactivate(self):
57 self.pl_menuitem.disconnect(self.id_)
58 if hasattr(self, 'page_num'):
59 self.notebook.remove_page(self.page_num)
60 self.notebook.set_current_page(0)
61 if hasattr(self, 'ftp'):
62 del self.ftp
64 def on_activate(self, widget):
65 if 'plugins' not in gajim.interface.instances:
66 return
67 self.installed_plugins_model = gajim.interface.instances[
68 'plugins'].installed_plugins_model
69 self.notebook = gajim.interface.instances['plugins'].plugins_notebook
70 self.id_n = self.notebook.connect('switch-page',
71 self.on_notebook_switch_page)
72 self.window = gajim.interface.instances['plugins'].window
73 self.window.connect('destroy', self.on_win_destroy)
74 self.GTK_BUILDER_FILE_PATH = self.local_file_path(
75 'config_dialog.ui')
76 self.xml = gtk.Builder()
77 self.xml.set_translation_domain(i18n.APP)
78 self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
79 ['hpaned2'])
80 hpaned = self.xml.get_object('hpaned2')
81 self.page_num = self.notebook.append_page(hpaned,
82 gtk.Label('Ftp Manager'))
84 widgets_to_extract = ('plugin_name_label1',
85 'available_treeview', 'progressbar', 'inslall_upgrade_button',
86 'plugin_authors_label1', 'plugin_authors_label1',
87 'plugin_homepage_linkbutton1', 'plugin_description_textview1')
89 for widget_name in widgets_to_extract:
90 setattr(self, widget_name, self.xml.get_object(widget_name))
92 attr_list = pango.AttrList()
93 attr_list.insert(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1))
94 self.plugin_name_label1.set_attributes(attr_list)
96 self.available_plugins_model = gtk.ListStore(gobject.TYPE_PYOBJECT,
97 gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING,
98 gobject.TYPE_BOOLEAN, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
99 gobject.TYPE_PYOBJECT)
100 self.available_treeview.set_model(self.available_plugins_model)
102 self.progressbar.set_property('no-show-all', True)
103 renderer = gtk.CellRendererText()
104 col = gtk.TreeViewColumn(_('Plugin'), renderer, text=1)
105 col.set_resizable(True)
106 col.set_property('expand', True)
107 col.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
108 self.available_treeview.append_column(col)
109 col = gtk.TreeViewColumn(_('Installed\nversion'), renderer, text=2)
110 self.available_treeview.append_column(col)
111 col = gtk.TreeViewColumn(_('Available\nversion'), renderer, text=3)
112 col.set_property('expand', False)
113 self.available_treeview.append_column(col)
115 renderer = gtk.CellRendererToggle()
116 renderer.set_property('activatable', True)
117 renderer.connect('toggled', self.available_plugins_toggled_cb)
118 col = gtk.TreeViewColumn(_('Install /\nUpgrade'), renderer, active=4)
119 self.available_treeview.append_column(col)
121 if gobject.signal_lookup('error_signal', self.window) is 0:
122 gobject.signal_new('error_signal', self.window,
123 gobject.SIGNAL_RUN_LAST, gobject.TYPE_STRING,
124 (gobject.TYPE_STRING,))
125 gobject.signal_new('plugin_downloaded', self.window,
126 gobject.SIGNAL_RUN_LAST, gobject.TYPE_STRING,
127 (gobject.TYPE_PYOBJECT,))
128 self.window.connect('error_signal', self.on_some_ftp_error)
129 self.window.connect('plugin_downloaded', self.on_plugin_downloaded)
131 selection = self.available_treeview.get_selection()
132 selection.connect('changed',
133 self.available_plugins_treeview_selection_changed)
134 selection.set_mode(gtk.SELECTION_SINGLE)
136 self._clear_available_plugin_info()
137 self.xml.connect_signals(self)
138 self.window.show_all()
140 def on_win_destroy(self, widget):
141 if hasattr(self, 'ftp'):
142 del self.ftp
144 def available_plugins_toggled_cb(self, cell, path):
145 is_active = self.available_plugins_model[path][4]
146 self.available_plugins_model[path][4] = not is_active
147 dir_list = []
148 for i in xrange(len(self.available_plugins_model)):
149 if self.available_plugins_model[i][4]:
150 dir_list.append(self.available_plugins_model[i][0])
151 if not dir_list:
152 self.inslall_upgrade_button.set_property('sensitive', False)
153 else:
154 self.inslall_upgrade_button.set_property('sensitive', True)
156 def on_notebook_switch_page(self, widget, page, page_num,):
157 if not hasattr(self, 'ftp') and self.page_num == page_num:
158 self.available_plugins_model.clear()
159 self.progressbar.show()
160 self.ftp = Ftp(self)
161 self.ftp.remote_dirs = None
162 self.ftp.start()
164 def on_inslall_upgrade_clicked(self, widget):
165 self.inslall_upgrade_button.set_property('sensitive', False)
166 dir_list = []
167 for i in xrange(len(self.available_plugins_model)):
168 if self.available_plugins_model[i][4]:
169 dir_list.append(self.available_plugins_model[i][0])
171 ftp = Ftp(self)
172 ftp.remote_dirs = dir_list
173 ftp.start()
175 def on_some_ftp_error(self, widget, error_text):
176 for i in xrange(len(self.available_plugins_model)):
177 self.available_plugins_model[i][4] = False
178 self.progressbar.hide()
179 WarningDialog('Ftp error', error_text, self.window)
181 def on_plugin_downloaded(self, widget, plugin_dirs):
182 for _dir in plugin_dirs:
183 is_active = False
184 plugins = None
185 plugin_dir = os.path.join(gajim.PLUGINS_DIRS[1], _dir)
186 plugin = gajim.plugin_manager.get_plugin_by_path(plugin_dir)
187 if plugin:
188 if plugin.active and plugin.name != self.name:
189 is_active = True
190 gobject.idle_add(gajim.plugin_manager.deactivate_plugin,
191 plugin)
192 gajim.plugin_manager.plugins.remove(plugin)
194 model = self.installed_plugins_model
195 for row in xrange(len(model)):
196 if plugin == model[row][0]:
197 model.remove(model.get_iter((row, 0)))
198 break
200 plugins = self.scan_dir_for_plugin(plugin_dir)
201 if not plugins:
202 continue
203 gajim.plugin_manager.add_plugin(plugins[0])
204 plugin = gajim.plugin_manager.plugins[-1]
205 for row in xrange(len(self.available_plugins_model)):
206 if plugin.name == self.available_plugins_model[row][1]:
207 self.available_plugins_model[row][2] = plugin.version
208 self.available_plugins_model[row][4] = False
209 continue
210 if is_active and plugin.name != self.name:
211 gobject.idle_add(gajim.plugin_manager.activate_plugin, plugin)
212 if plugin.name != 'Ftp Manager':
213 self.installed_plugins_model.append([plugin, plugin.name,
214 is_active])
215 dialog = HigDialog(None, gtk.MESSAGE_INFO, gtk.BUTTONS_OK,
216 '', 'All selected plugins downloaded')
217 dialog.set_modal(False)
218 dialog.set_transient_for(self.window)
219 dialog.popup()
221 def available_plugins_treeview_selection_changed(self, treeview_selection):
222 model, iter = treeview_selection.get_selected()
223 if iter:
224 self.plugin_name_label1.set_text(model.get_value(iter, 1))
225 self.plugin_authors_label1.set_text(model.get_value(iter, 6))
226 self.plugin_homepage_linkbutton1.set_uri(model.get_value(iter, 7))
227 self.plugin_homepage_linkbutton1.set_label(model.get_value(iter, 7))
228 label = self.plugin_homepage_linkbutton1.get_children()[0]
229 label.set_ellipsize(pango.ELLIPSIZE_END)
230 self.plugin_homepage_linkbutton1.set_property('sensitive', True)
231 desc_textbuffer = self.plugin_description_textview1.get_buffer()
232 desc_textbuffer.set_text(model.get_value(iter, 5))
233 self.plugin_description_textview1.set_property('sensitive', True)
234 else:
235 self._clear_available_plugin_info()
237 def _clear_available_plugin_info(self):
238 self.plugin_name_label1.set_text('')
239 self.plugin_authors_label1.set_text('')
240 self.plugin_homepage_linkbutton1.set_uri('')
241 self.plugin_homepage_linkbutton1.set_label('')
242 self.plugin_homepage_linkbutton1.set_property('sensitive', False)
244 desc_textbuffer = self.plugin_description_textview1.get_buffer()
245 desc_textbuffer.set_text('')
246 self.plugin_description_textview1.set_property('sensitive', False)
248 def scan_dir_for_plugin(self, path):
249 plugins_found = []
250 conf = ConfigParser.ConfigParser()
251 fields = ('name', 'short_name', 'version', 'description', 'authors',
252 'homepage')
253 if not os.path.isdir(path):
254 return plugins_found
256 dir_list = os.listdir(path)
257 dir_, mod = os.path.split(path)
258 sys.path.insert(0, dir_)
260 manifest_path = os.path.join(path, 'manifest.ini')
261 if not os.path.isfile(manifest_path):
262 return plugins_found
264 for elem_name in dir_list:
265 file_path = os.path.join(path, elem_name)
266 module = None
268 if os.path.isfile(file_path) and fnmatch.fnmatch(file_path, '*.py'):
269 module_name = os.path.splitext(elem_name)[0]
270 if module_name == '__init__':
271 continue
272 try:
273 module = __import__('%s.%s' % (mod, module_name))
274 except ValueError, value_error:
275 pass
276 except ImportError, import_error:
277 pass
278 except AttributeError, attribute_error:
279 pass
280 if module is None:
281 continue
283 for module_attr_name in [attr_name for attr_name in dir(module)
284 if not (attr_name.startswith('__') or attr_name.endswith('__'))]:
285 module_attr = getattr(module, module_attr_name)
286 try:
287 if not issubclass(module_attr, GajimPlugin) or \
288 module_attr is GajimPlugin:
289 continue
290 module_attr.__path__ = os.path.abspath(os.path.dirname(
291 file_path))
293 # read metadata from manifest.ini
294 conf.readfp(open(manifest_path, 'r'))
295 for option in fields:
296 if conf.get('info', option) is '':
297 raise ConfigParser.NoOptionError, 'field empty'
298 setattr(module_attr, option, conf.get('info', option))
299 conf.remove_section('info')
300 plugins_found.append(module_attr)
302 except TypeError, type_error:
303 pass
304 except ConfigParser.NoOptionError, type_error:
305 # all fields are required
306 pass
307 return plugins_found
310 class Ftp(threading.Thread):
311 def __init__(self, plugin):
312 super(Ftp, self).__init__()
313 self.window = plugin.window
314 self.server = plugin.config['ftp_server']
315 self.progressbar = plugin.progressbar
316 self.model = plugin.available_plugins_model
317 self.config = ConfigParser.ConfigParser()
318 self.buffer_ = io.BytesIO()
319 self.remote_dirs = None
320 self.append_to_model = True
322 def model_append(self, row):
323 self.model.append(row)
324 return False
326 def progressbar_pulse(self):
327 self.progressbar.pulse()
328 return True
330 def get_plugin_version(self, plugin_name):
331 for plugin in gajim.plugin_manager.plugins:
332 if plugin.name == plugin_name:
333 return plugin.version
335 def run(self):
336 try:
337 gobject.idle_add(self.progressbar.set_text,
338 'Connecting to server')
339 self.ftp = ftplib.FTP(self.server)
340 self.ftp.login()
341 self.ftp.cwd('plugins')
342 if not self.remote_dirs:
343 self.plugins_dirs = self.ftp.nlst()
344 progress_step = 1.0 / len(self.plugins_dirs)
345 gobject.idle_add(self.progressbar.set_text,
346 'Scan files on the server')
347 for dir_ in self.plugins_dirs:
348 fract = self.progressbar.get_fraction() + progress_step
349 gobject.idle_add(self.progressbar.set_fraction, fract)
350 gobject.idle_add(self.progressbar.set_text,
351 'Read "%s"' % dir_)
352 try:
353 self.ftp.retrbinary('RETR %s/manifest.ini' % dir_,
354 self.handleDownload)
355 except Exception, error:
356 if str(error).startswith('550'):
357 continue
358 self.config.readfp(io.BytesIO(self.buffer_.getvalue()))
359 local_version = self.get_plugin_version(
360 self.config.get('info', 'name'))
361 gobject.idle_add(self.model_append, [dir_,
362 self.config.get('info', 'name'), local_version,
363 self.config.get('info', 'version'), False,
364 self.config.get('info', 'description'),
365 self.config.get('info', 'authors'),
366 self.config.get('info', 'homepage'), ])
367 self.plugins_dirs = None
368 self.ftp.quit()
369 gobject.idle_add(self.progressbar.set_fraction, 0)
370 if self.remote_dirs:
371 self.download_plugin()
372 gobject.idle_add(self.progressbar.hide)
373 except Exception, e:
374 self.window.emit('error_signal', str(e))
376 def handleDownload(self, block):
377 self.buffer_.write(block)
379 def download_plugin(self):
380 gobject.idle_add(self.progressbar.show)
381 self.pulse = gobject.timeout_add(150, self.progressbar_pulse)
382 gobject.idle_add(self.progressbar.set_text,
383 'Create a list of files')
384 for remote_dir in self.remote_dirs:
386 def nlstr(dir_, subdir=None):
387 if subdir:
388 dir_ = dir_ + '/' + subdir
389 list_ = self.ftp.nlst(dir_)
390 for i in list_:
391 name = i.split('/')[-1]
392 if '.' not in name:
393 try:
394 if i == self.ftp.nlst(i)[0]:
395 files.append(i[1:])
396 del dirs[i[1:]]
397 except Exception, e:
398 # empty dir or file
399 continue
400 dirs.append(i[1:])
401 subdirs = name
402 nlstr(dir_, subdirs)
403 else:
404 files.append(i[1:])
405 dirs, files = [], []
406 nlstr('/plugins/' + remote_dir)
408 if not os.path.isdir(gajim.PLUGINS_DIRS[1]):
409 os.mkdir(gajim.PLUGINS_DIRS[1])
410 local_dir = ld = os.path.join(gajim.PLUGINS_DIRS[1], remote_dir)
411 if not os.path.isdir(local_dir):
412 os.mkdir(local_dir)
413 local_dir = os.path.split(gajim.PLUGINS_DIRS[1])[0]
415 # creating dirs
416 for dir_ in dirs:
417 try:
418 os.mkdir(os.path.join(local_dir, dir_))
419 except OSError, e:
420 if str(e).startswith('[Errno 17]'):
421 continue
422 raise
424 # downloading files
425 for filename in files:
426 gobject.idle_add(self.progressbar.set_text,
427 'Downloading "%s"' % filename)
428 full_filename = os.path.join(local_dir, filename)
429 try:
430 self.ftp.retrbinary('RETR /%s' % filename,
431 open(full_filename, 'wb').write)
432 #full_filename.close()
433 except ftplib.error_perm:
434 print 'ERROR: cannot read file "%s"' % filename
435 os.unlink(filename)
436 self.ftp.quit()
437 self.window.emit('plugin_downloaded', self.remote_dirs)
438 gobject.source_remove(self.pulse)
441 class FtpManagerPluginConfigDialog(GajimPluginConfigDialog):
442 def init(self):
443 self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
444 'config_dialog.ui')
445 self.xml = gtk.Builder()
446 self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
447 ['hbox111'])
448 hbox = self.xml.get_object('hbox111')
449 self.child.pack_start(hbox)
451 self.xml.connect_signals(self)
452 self.connect('hide', self.on_hide)
454 def on_run(self):
455 widget = self.xml.get_object('ftp_server')
456 widget.set_text(str(self.plugin.config['ftp_server']))
458 def on_hide(self, widget):
459 widget = self.xml.get_object('ftp_server')
460 self.plugin.config['ftp_server'] = widget.get_text()