1
# Ubuntu Tweak - magic tool to configure Ubuntu
3
# Copyright (C) 2010 TualatriX <tualatrix@gmail.com>
5
# Ubuntu Tweak is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
10
# Ubuntu Tweak is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with Ubuntu Tweak; if not, write to the Free Software Foundation, Inc.,
17
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
27
from subprocess import Popen, PIPE
29
from ubuntutweak.utils import icon
30
from ubuntutweak.common.consts import CONFIG_ROOT
31
from ubuntutweak.common.gui import GuiWorker
32
from ubuntutweak.modules import TweakModule
33
from ubuntutweak.ui.dialogs import InfoDialog, QuestionDialog, ErrorDialog
34
from ubuntutweak.ui.dialogs import ProcessDialog
36
log = logging.getLogger('DesktopRecovery')
38
def build_backup_prefix(dir):
39
name_prefix = os.path.join(CONFIG_ROOT, 'desktoprecovery', dir[1:]) + '/'
41
log.debug("build_backup_prefix: %s" % name_prefix)
43
if not os.path.exists(name_prefix):
44
os.makedirs(name_prefix)
47
def build_backup_path(dir, name):
48
name_prefix = build_backup_prefix(dir)
49
return name_prefix + name + '.xml'
51
def do_backup_task(dir, name):
52
backup_name = build_backup_path(dir, name)
53
log.debug("the backup path is %s" % backup_name)
54
backup_file = open(backup_name, 'w')
55
process = Popen(['gconftool-2', '--dump', dir], stdout=backup_file)
56
return process.communicate()
58
def do_recover_task(path):
59
process = Popen(['gconftool-2', '--load', path])
60
log.debug('Start setting recovery: %s' % path)
61
return process.communicate()
63
def do_reset_task(dir):
64
process = Popen(['gconftool-2', '--recursive-unset', dir])
65
log.debug('Start setting reset: %s' % dir)
66
return process.communicate()
68
class CateView(gtk.TreeView):
75
'/apps': _('Applications'),
76
'/desktop': _('Desktop'),
77
'/system': _('System'),
81
gtk.TreeView.__init__(self)
83
self.set_rules_hint(True)
84
self.model = self.__create_model()
85
self.set_model(self.model)
89
selection = self.get_selection()
90
selection.select_iter(self.model.get_iter_first())
92
def __create_model(self):
93
'''The model is icon, title and the list reference'''
94
model = gtk.ListStore(
101
def __add_columns(self):
102
column = gtk.TreeViewColumn(_('Category'))
104
renderer = gtk.CellRendererPixbuf()
105
column.pack_start(renderer, False)
106
column.set_attributes(renderer, pixbuf=self.COLUMN_ICON)
108
renderer = gtk.CellRendererText()
109
column.pack_start(renderer, True)
110
column.set_sort_column_id(self.COLUMN_TITLE)
111
column.set_attributes(renderer, text=self.COLUMN_TITLE)
113
self.append_column(column)
115
def update_model(self):
116
for path, title in self.path_dict.items():
117
pixbuf = icon.get_from_name('folder')
118
iter = self.model.append(None)
120
self.COLUMN_ICON, pixbuf,
121
self.COLUMN_DIR, path,
122
self.COLUMN_TITLE, title)
124
class SettingView(gtk.TreeView):
131
gtk.TreeView.__init__(self)
133
self.model = self.__create_model()
134
self.set_model(self.model)
137
def __create_model(self):
138
''' The first is for icon, second is for real path, second is for title (if available)'''
139
model = gtk.ListStore(gtk.gdk.Pixbuf,
145
def __add_columns(self):
146
column = gtk.TreeViewColumn(_('Setting'))
148
renderer = gtk.CellRendererPixbuf()
149
column.pack_start(renderer, False)
150
column.set_attributes(renderer, pixbuf=self.COLUMN_ICON)
152
renderer = gtk.CellRendererText()
153
column.pack_start(renderer, True)
154
column.set_sort_column_id(self.COLUMN_TITLE)
155
column.set_attributes(renderer, text=self.COLUMN_TITLE)
157
self.append_column(column)
159
def update_model(self, dir):
162
process = Popen(['gconftool-2', '--all-dirs', dir], stdout=PIPE)
163
stdout, stderr = process.communicate()
166
#TODO raise error or others
169
dirlist = stdout.split()
172
title = dir.split('/')[-1]
174
pixbuf = icon.get_from_name(title, alter='folder')
175
iter = self.model.append(None)
177
self.COLUMN_ICON, pixbuf,
178
self.COLUMN_DIR, dir,
179
self.COLUMN_TITLE, title)
181
class GetTextDialog(QuestionDialog):
182
def __init__(self, title='', message='', text=''):
183
super(GetTextDialog, self).__init__(title=title, message=message)
189
hbox = gtk.HBox(False, 12)
190
label = gtk.Label(_('Backup Name:'))
191
hbox.pack_start(label, False, False, 0)
193
self.entry = gtk.Entry()
195
self.entry.set_text(text)
196
hbox.pack_start(self.entry)
198
vbox.pack_start(hbox)
202
self.text = self.entry.get_text()
203
super(GetTextDialog, self).destroy()
205
def set_text(self, text):
206
self.entry.set_text(text)
211
class BackupProgressDialog(ProcessDialog):
212
def __init__(self, parent, name, dir):
213
self.file_name = name
218
super(BackupProgressDialog, self).__init__(parent=parent)
220
def process_data(self):
222
name = self.file_name
224
process = Popen(['gconftool-2', '--all-dirs', dir], stdout=PIPE)
225
stdout, stderr = process.communicate()
228
#TODO raise error or others
233
dirlist = stdout.split()
237
for subdir in dirlist:
238
self.set_progress_text(_('Backing up...%s') % subdir)
239
stdout, stderr = do_backup_task(subdir, name)
240
if stderr is not None:
245
totol_backuped.append(build_backup_path(subdir, name))
248
backup_name = build_backup_path(dir, name)
249
sum_file = open(backup_name, 'w')
250
sum_file.write('\n'.join(totol_backuped))
255
def on_timeout(self):
263
class DesktopRecovery(TweakModule):
264
__title__ = _('Desktop Recovery')
265
__desc__ = _('Backup and recover your desktop and application settings with ease.\n'
266
'You can also use "Reset" to reset to the system default settings.')
267
__icon__ = 'gnome-control-center'
268
__category__ = 'desktop'
269
__desktop__ = ['gnome']
272
TweakModule.__init__(self, 'desktoprecovery.ui')
274
self.setup_backup_model()
276
hbox = gtk.HBox(False, 5)
279
self.cateview = CateView()
280
#FIXME it will cause two callback for cateview changed
281
self.cateview.connect('button_press_event', self.on_cateview_button_press_event)
282
self.cate_selection = self.cateview.get_selection()
283
self.cate_selection.connect('changed', self.on_cateview_changed)
284
hbox.pack_start(self.cateview, False, False, 0)
286
vpaned = gtk.VPaned()
287
hbox.pack_start(vpaned)
289
self.settingview = SettingView()
290
self.setting_selection = self.settingview.get_selection()
291
self.setting_selection.connect('changed', self.on_settingview_changed)
292
sw = gtk.ScrolledWindow()
293
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
294
sw.add(self.settingview)
295
vpaned.pack1(sw, True, False)
297
self.window1.remove(self.recover_box)
298
vpaned.pack2(self.recover_box, False, False)
300
self.on_cateview_changed(self.cate_selection)
303
def setup_backup_model(self):
304
model = gtk.ListStore(gobject.TYPE_STRING,
307
self.backup_combobox.set_model(model)
309
cell = gtk.CellRendererText()
310
self.backup_combobox.pack_start(cell, True)
311
self.backup_combobox.add_attribute(cell, 'text', 0)
313
def update_backup_model(self, dir):
314
def file_cmp(f1, f2):
315
return cmp(os.stat(f1).st_ctime, os.stat(f2).st_ctime)
317
model = self.backup_combobox.get_model()
320
name_prefix = build_backup_prefix(dir)
322
file_lsit = glob.glob(name_prefix + '*.xml')
323
file_lsit.sort(cmp=file_cmp, reverse=True)
324
log.debug('Use glob to find the name_prefix: %s with result: %s' % (name_prefix, str(file_lsit)))
327
for file in file_lsit:
328
iter = model.append(None)
329
if first_iter == None:
332
0, os.path.basename(file)[:-4],
334
self.backup_combobox.set_active_iter(first_iter)
335
self.delete_button.set_sensitive(True)
336
self.edit_button.set_sensitive(True)
337
self.recover_button.set_sensitive(True)
339
iter = model.append(None)
340
model.set(iter, 0, _('No Backup Yet'), 1, '')
341
self.backup_combobox.set_active_iter(iter)
342
self.delete_button.set_sensitive(False)
343
self.edit_button.set_sensitive(False)
344
self.recover_button.set_sensitive(False)
346
def on_cateview_changed(self, widget):
347
model, iter = widget.get_selected()
349
dir = model.get_value(iter, self.cateview.COLUMN_DIR)
350
self.settingview.update_model(dir)
352
self.dir_label.set_text(dir)
353
self.update_backup_model(dir)
355
def on_cateview_button_press_event(self, widget, event):
356
if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1:
357
self.on_cateview_changed(self.cate_selection)
359
def on_settingview_changed(self, widget):
360
model, iter = widget.get_selected()
362
dir = model.get_value(iter, self.settingview.COLUMN_DIR)
363
self.dir_label.set_text(dir)
364
self.update_backup_model(dir)
366
def on_backup_button_clicked(self, widget):
367
def get_time_stamp():
368
return time.strftime('%Y-%m-%d-%H-%M', time.localtime(time.time()))
370
dir = self.dir_label.get_text()
371
log.debug("Start backing up the dir: %s" % dir)
373
# if 1, then root dir
374
if dir.count('/') == 1:
375
dialog = GetTextDialog(message=_('Backup all settings under: <b>%s</b>\nWould you like to continue?') % dir,
376
text=get_time_stamp())
378
response = dialog.run()
380
name = dialog.get_text()
382
if response == gtk.RESPONSE_YES and name:
383
dialog = BackupProgressDialog(self.get_toplevel(), name, dir)
388
if dialog.error == False:
389
self.show_backup_successful_dialog()
390
self.update_backup_model(dir)
392
self.show_backup_failed_dialog()
394
dialog = GetTextDialog(message=_('Backup settings under: <b>%s</b>\nWould you like to continue?') % dir,
395
text=get_time_stamp())
396
response = dialog.run()
398
name = dialog.get_text()
400
if response == gtk.RESPONSE_YES and name:
401
stdout, stderr = do_backup_task(dir, name)
404
self.show_backup_successful_dialog()
405
self.update_backup_model(dir)
407
self.show_backup_failed_dialog()
408
log.debug("Backup error: %s" % stderr)
410
def on_delete_button_clicked(self, widget):
411
def try_remove_record_in_root_backup(dir, path):
412
rootpath = build_backup_prefix('/'.join(dir.split('/')[:2])) + os.path.basename(path)
413
if os.path.exists(rootpath):
414
lines = open(rootpath).read().split()
420
new = open(rootpath, 'w')
421
new.write('\n'.join(lines))
424
def try_remove_all_subback(path):
425
for line in open(path):
426
os.remove(line.strip())
428
iter = self.backup_combobox.get_active_iter()
429
model = self.backup_combobox.get_model()
431
dir = self.dir_label.get_text()
433
path = model.get_value(iter, 1)
434
if dir.count('/') == 2:
435
dialog = QuestionDialog(_('Would you like to delete the backup: <b>%s/%s</b>?') % (dir, os.path.basename(path)[:-4]))
437
dialog = QuestionDialog(_('Would you like to delete the backup of all <b>%s</b> settings named <b>%s</b>?') % (dir, os.path.basename(path)[:-4]))
438
response = dialog.run()
440
if response == gtk.RESPONSE_YES:
441
if dir.count('/') == 2:
442
try_remove_record_in_root_backup(dir, path)
444
try_remove_all_subback(path)
447
self.update_backup_model(dir)
449
def on_recover_button_clicked(self, widget):
450
iter = self.backup_combobox.get_active_iter()
451
model = self.backup_combobox.get_model()
452
dir = self.dir_label.get_text()
453
path = model.get_value(iter, 1)
455
if dir.count('/') == 2:
456
message = _('Would you like to recover the backup: <b>%s/%s</b>?') % (dir, os.path.basename(path)[:-4])
458
message = _('Would you like to recover the backup of all <b>%s</b> settings named <b>%s</b>?') % (dir, os.path.basename(path)[:-4])
460
addon_message = _('<b>NOTES</b>: While recovering, your desktop may be unresponsive for a moment.')
462
dialog = QuestionDialog(message + '\n\n' + addon_message)
463
response = dialog.run()
466
if response == gtk.RESPONSE_YES:
467
if dir.count('/') == 1:
468
for line in open(path):
469
stdout, stderr = do_recover_task(line.strip())
471
stdout, stderr = do_recover_task(path)
475
#TODO raise error or others
477
self.__show_successful_with_logout_button(_('Recovery Successful!\nYou may need to restart your desktop for changes to take effect'))
479
def __show_successful_with_logout_button(self, message):
480
dialog = InfoDialog(message)
482
button = gtk.Button(_('_Logout'))
483
button.connect('clicked', self.on_logout_button_clicked, dialog)
484
dialog.add_option_button(button)
488
def on_reset_button_clicked(self, widget):
489
iter = self.backup_combobox.get_active_iter()
490
model = self.backup_combobox.get_model()
491
dir = self.dir_label.get_text()
493
if dir.count('/') == 2:
494
message = _('Would you like to reset settings for: <b>%s</b>?') % dir
496
message = _('Would you like to reset all settings under: <b>%s</b>?') % dir
498
addon_message = _('<b>NOTES</b>: Whilst resetting, your desktop may be unresponsive for a moment.')
500
dialog = QuestionDialog(message + '\n\n' + addon_message)
501
response = dialog.run()
504
if response == gtk.RESPONSE_YES:
505
stdout, stderr = do_reset_task(dir)
509
#TODO raise error or others
511
self.__show_successful_with_logout_button(_('Reset Successful!\nYou may need to restart your desktop for changes to take effect'))
513
def on_logout_button_clicked(self, widget, dialog):
514
bus = dbus.SessionBus()
515
object = bus.get_object('org.gnome.SessionManager', '/org/gnome/SessionManager')
516
object.get_dbus_method('Logout', 'org.gnome.SessionManager')(True)
518
self.emit('call', 'mainwindow', 'destroy', {})
520
def on_edit_button_clicked(self, widget):
521
def try_rename_record_in_root_backup(dir, old_path, new_path):
522
rootpath = build_backup_prefix('/'.join(dir.split('/')[:2])) + os.path.basename(path)
523
if os.path.exists(rootpath):
524
lines = open(rootpath).read().split()
525
lines.remove(old_path)
526
lines.append(new_path)
528
new = open(rootpath, 'w')
529
new.write('\n'.join(lines))
531
iter = self.backup_combobox.get_active_iter()
532
model = self.backup_combobox.get_model()
533
dir = self.dir_label.get_text()
534
path = model.get_value(iter, 1)
536
dialog = GetTextDialog(message=_('Please enter a new name for your backup:'))
538
dialog.set_text(os.path.basename(path)[:-4])
541
new_name = dialog.get_text()
542
log.debug('Get the new backup name: %s' % new_name)
544
if res == gtk.RESPONSE_YES and new_name:
545
# If is root, try to rename all the subdir, then rename itself
546
if dir.count('/') == 1:
548
for line in open(path):
550
dirname = os.path.dirname(line)
551
new_path = os.path.join(dirname, new_name + '.xml')
552
log.debug('Rename backup file from "%s" to "%s"' % (line, new_path))
553
os.rename(line, new_path)
554
totol_renamed.append(new_path)
555
sum_file = open(path, 'w')
556
sum_file.write('\n'.join(totol_renamed))
559
dirname = os.path.dirname(path)
560
new_path = os.path.join(dirname, new_name + '.xml')
561
log.debug('Rename backup file from "%s" to "%s"' % (path, new_path))
562
os.rename(path, new_path)
563
try_rename_record_in_root_backup(dir, path, new_path)
565
self.update_backup_model(dir)
567
def show_backup_successful_dialog(self):
568
InfoDialog(_("Backup Successful!")).launch()
570
def show_backup_failed_dialog(self):
571
ErrorDialog(_("Backup Failed!")).launch()