1 # -*- coding: utf-8 -*-
3 ## plugins/ftp_manager/ftp_manager.py
5 ## Copyright (C) 2010 Denis Fomin <fominde AT gmail.com>
7 ## This file is part of Gajim.
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/>.
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')
44 self
.config_dialog
= FtpManagerPluginConfigDialog(self
)
45 self
.config_default_values
= {'ftp_server': ('ftp.gajim.org', '')}
47 @log_calls('FtpManagerPlugin')
49 self
.pl_menuitem
= gajim
.interface
.roster
.xml
.get_object(
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')
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'):
64 def on_activate(self
, widget
):
65 if 'plugins' not in gajim
.interface
.instances
:
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(
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
,
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'):
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
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])
152 self
.inslall_upgrade_button
.set_property('sensitive', False)
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()
161 self
.ftp
.remote_dirs
= None
164 def on_inslall_upgrade_clicked(self
, widget
):
165 self
.inslall_upgrade_button
.set_property('sensitive', False)
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])
172 ftp
.remote_dirs
= dir_list
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
:
185 plugin_dir
= os
.path
.join(gajim
.PLUGINS_DIRS
[1], _dir
)
186 plugin
= gajim
.plugin_manager
.get_plugin_by_path(plugin_dir
)
188 if plugin
.active
and plugin
.name
!= self
.name
:
190 gobject
.idle_add(gajim
.plugin_manager
.deactivate_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)))
200 plugins
= self
.scan_dir_for_plugin(plugin_dir
)
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
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
,
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
)
221 def available_plugins_treeview_selection_changed(self
, treeview_selection
):
222 model
, iter = treeview_selection
.get_selected()
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)
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
):
250 conf
= ConfigParser
.ConfigParser()
251 fields
= ('name', 'short_name', 'version', 'description', 'authors',
253 if not os
.path
.isdir(path
):
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
):
264 for elem_name
in dir_list
:
265 file_path
= os
.path
.join(path
, elem_name
)
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__':
273 module
= __import__('%s.%s' % (mod
, module_name
))
274 except ValueError, value_error
:
276 except ImportError, import_error
:
278 except AttributeError, attribute_error
:
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
)
287 if not issubclass(module_attr
, GajimPlugin
) or \
288 module_attr
is GajimPlugin
:
290 module_attr
.__path
__ = os
.path
.abspath(os
.path
.dirname(
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
:
304 except ConfigParser
.NoOptionError
, type_error
:
305 # all fields are required
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
)
326 def progressbar_pulse(self
):
327 self
.progressbar
.pulse()
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
337 gobject
.idle_add(self
.progressbar
.set_text
,
338 'Connecting to server')
339 self
.ftp
= ftplib
.FTP(self
.server
)
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
,
353 self
.ftp
.retrbinary('RETR %s/manifest.ini' % dir_
,
355 except Exception, error
:
356 if str(error
).startswith('550'):
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
369 gobject
.idle_add(self
.progressbar
.set_fraction
, 0)
371 self
.download_plugin()
372 gobject
.idle_add(self
.progressbar
.hide
)
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):
388 dir_
= dir_
+ '/' + subdir
389 list_
= self
.ftp
.nlst(dir_
)
391 name
= i
.split('/')[-1]
394 if i
== self
.ftp
.nlst(i
)[0]:
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
):
413 local_dir
= os
.path
.split(gajim
.PLUGINS_DIRS
[1])[0]
418 os
.mkdir(os
.path
.join(local_dir
, dir_
))
420 if str(e
).startswith('[Errno 17]'):
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
)
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
437 self
.window
.emit('plugin_downloaded', self
.remote_dirs
)
438 gobject
.source_remove(self
.pulse
)
441 class FtpManagerPluginConfigDialog(GajimPluginConfigDialog
):
443 self
.GTK_BUILDER_FILE_PATH
= self
.plugin
.local_file_path(
445 self
.xml
= gtk
.Builder()
446 self
.xml
.add_objects_from_file(self
.GTK_BUILDER_FILE_PATH
,
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
)
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()