Merge lp:~adreeve/backintime/samba_access into lp:backintime/1.0

Proposed by Adam Reeve
Status: Needs review
Proposed branch: lp:~adreeve/backintime/samba_access
Merge into: lp:backintime/1.0
Diff against target: 1000 lines (+586/-73)
10 files modified
common/config.py (+107/-1)
common/debian_specific/control (+1/-1)
common/network.py (+105/-0)
common/snapshots.py (+41/-10)
common/tools.py (+1/-3)
gnome/app.py (+10/-2)
gnome/settingsdialog.glade (+160/-35)
gnome/settingsdialog.py (+79/-15)
kde4/app.py (+4/-0)
kde4/settingsdialog.py (+78/-6)
To merge this branch: bzr merge lp:~adreeve/backintime/samba_access
Reviewer Review Type Date Requested Status
Back In Time Team Pending
Review via email: mp+22283@code.launchpad.net

Description of the change

Adds support for backing up to network shares. It currently implements samba shares but more protocols can be added by specifying commands to mount and unmount the share. This can be configured in both the Gnome and KDE GUIs.

To post a comment you must log in.
lp:~adreeve/backintime/samba_access updated
724. By Adam Reeve

Fix problems when updating snapshot locations to new version when using samba access.

725. By Adam Reeve

Merge from trunk

726. By Adam Reeve

Merge from trunk

Unmerged revisions

726. By Adam Reeve

Merge from trunk

725. By Adam Reeve

Merge from trunk

724. By Adam Reeve

Fix problems when updating snapshot locations to new version when using samba access.

723. By Adam Reeve

Removed all my vim modelines

722. By Adam Reeve

Changed mount commands to use full path as otherwise get a command not found error when running as a cron job

721. By Adam Reeve

Work on KDE interface for network access

720. By Adam Reeve

Fixed crash when config file doesn't exist

719. By Adam Reeve

Work on samba access, GTK interface should be finished now

718. By Adam Reeve

Merged changes in trunk

717. By Adam Reeve

Work on gnome interface for network shares

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'common/config.py'
--- common/config.py 2010-04-01 17:57:51 +0000
+++ common/config.py 2010-04-16 06:46:25 +0000
@@ -22,10 +22,12 @@
22import gettext22import gettext
23import socket23import socket
24import random24import random
25import re
2526
26import configfile27import configfile
27import tools28import tools
28import logger29import logger
30import network
2931
30_=gettext.gettext32_=gettext.gettext
3133
@@ -175,10 +177,41 @@
175 self.remove_profile_key( 'snapshots.include_folders', profile_id )177 self.remove_profile_key( 'snapshots.include_folders', profile_id )
176 self.remove_profile_key( 'snapshots.exclude_patterns', profile_id )178 self.remove_profile_key( 'snapshots.exclude_patterns', profile_id )
177179
178 self.set_int_value( 'config.version', self.CONFIG_VERSION )180 # make sure config version is set
181 self.set_int_value( 'config.version', self.CONFIG_VERSION )
182
183 # mount required network shares
184 self.mount_shares()
185
186 def mount_shares( self, profile=None ):
187 self.network_mounts = {}
188 if profile is None:
189 profiles = self.get_profiles()
190 else:
191 profiles = [profile]
192 for profile_id in profiles:
193 if (self.get_snapshot_access_mode(profile_id) in network.NETWORK_ACCESS_MODES):
194 # mount network share now if the snapshots are stored on a network share
195 path = self.get_profile_str_value('snapshots.network_path','',profile_id)
196 protocol = self.get_snapshot_access_mode(profile_id)
197 username = self.get_profile_str_value('snapshots.username','',profile_id)
198 password = network.password_decode(self.get_profile_str_value('snapshots.password','',profile_id))
199 self.network_mounts[profile_id] = network.NetworkMount(path,protocol,username,password)
200
201 def unmount_shares( self ):
202 for mount in self.network_mounts.values():
203 mount.unmount()
204 self.network_mounts.clear()
205
206 def update_shares( self ):
207 self.unmount_shares()
208 self.mount_shares()
179209
180 def save( self ):210 def save( self ):
181 configfile.ConfigFile.save( self, self._LOCAL_CONFIG_PATH )211 configfile.ConfigFile.save( self, self._LOCAL_CONFIG_PATH )
212 # make sure no other users can read the config file
213 # as it may contain encoded passwords for network shares
214 os.chmod(self._LOCAL_CONFIG_PATH,0600)
182215
183 def check_config( self ):216 def check_config( self ):
184 profiles = self.get_profiles()217 profiles = self.get_profiles()
@@ -252,8 +285,27 @@
252 return user285 return user
253286
254 def get_snapshots_path( self, profile_id = None ):287 def get_snapshots_path( self, profile_id = None ):
288 if (self.get_snapshot_access_mode(profile_id) in network.NETWORK_ACCESS_MODES):
289 if profile_id is None:
290 profile_id = self.get_current_profile()
291 if profile_id not in self.network_mounts.keys():
292 self.mount_shares(profile_id)
293 return self.network_mounts[profile_id].path()
294 else:
295 return self.get_snapshots_local_path( profile_id )
296
297 def get_snapshots_local_path( self, profile_id = None ):
255 return self.get_profile_str_value( 'snapshots.path', '', profile_id )298 return self.get_profile_str_value( 'snapshots.path', '', profile_id )
256299
300 def get_snapshots_username( self, profile_id = None ):
301 return self.get_profile_str_value( 'snapshots.username', '', profile_id )
302
303 def get_snapshots_password( self, profile_id = None ):
304 return network.password_decode(self.get_profile_str_value( 'snapshots.password', '', profile_id ))
305
306 def get_snapshots_network_path( self, profile_id = None ):
307 return self.get_profile_str_value( 'snapshots.network_path', '', profile_id )
308
257 def get_snapshots_full_path( self, profile_id = None, version = None ):309 def get_snapshots_full_path( self, profile_id = None, version = None ):
258 '''Returns the full path for the snapshots: .../backintime/machine/user/profile_id/'''310 '''Returns the full path for the snapshots: .../backintime/machine/user/profile_id/'''
259 if version is None:311 if version is None:
@@ -304,6 +356,54 @@
304 356
305 os.rmdir( check_path )357 os.rmdir( check_path )
306 self.set_profile_str_value( 'snapshots.path', value, profile_id )358 self.set_profile_str_value( 'snapshots.path', value, profile_id )
359 self.set_profile_int_value( 'snapshots.access', network.LOCAL, profile_id )
360 return True
361
362 def set_snapshots_network_details( self, location, protocol, username, password, profile_id = None ):
363 """Sets network access details and checks that they work"""
364 # remove anything like smb: at the start
365 match = re.compile(r"^([a-zA-Z]+:)?(//)?(.+)$").match(location)
366 if not match: # only occurs if location is empty
367 self.notify_error( _( 'Invalid network location, %s' ) % location )
368 return False
369 location = "//"+match.group(3)
370
371 if profile_id == None:
372 profile_id = self.get_current_profile()
373
374 test_mount = network.NetworkMount(location, protocol, username, password)
375 if not test_mount.mounted():
376 test_mount.unmount() #removes directory
377 self.notify_error( _( 'Can\'t mount network share: %s\nAre you sure you sure your settings are correct?' % location ) )
378 return False
379
380 #Initialize the snapshots folder
381 print "Check snapshot mount: %s" % location
382 machine = socket.gethostname()
383 user = self.get_user()
384 full_path = os.path.join( test_mount.path(), 'backintime', machine, user, profile_id )
385 if not os.path.isdir( full_path ):
386 print "Create folder: %s" % full_path
387 tools.make_dirs( full_path )
388 if not os.path.isdir( full_path ):
389 self.notify_error( _( 'Can\'t write to: %s\nAre you sure you have write access ?' % location ) )
390 return False
391
392 #Test write access for the folder
393 check_path = os.path.join( full_path, 'check' )
394 tools.make_dirs( check_path )
395 if not os.path.isdir( check_path ):
396 self.notify_error( _( 'Can\'t write to: %s\nAre you sure you have write access ?' % full_path.replace(test_mount.path(),location) ) )
397 return False
398 os.rmdir( check_path )
399
400 test_mount.unmount()
401
402 encoded_pw = network.password_encode(password)
403 self.set_profile_int_value( 'snapshots.access', protocol, profile_id )
404 self.set_profile_str_value( 'snapshots.network_path', location, profile_id )
405 self.set_profile_str_value( 'snapshots.username', username, profile_id )
406 self.set_profile_str_value( 'snapshots.password', encoded_pw, profile_id )
307 return True407 return True
308408
309 def get_other_folders_paths( self, profile_id = None ):409 def get_other_folders_paths( self, profile_id = None ):
@@ -418,6 +518,12 @@
418 def get_tag( self, profile_id = None ):518 def get_tag( self, profile_id = None ):
419 return self.get_profile_str_value( 'snapshots.tag', str(random.randint(100, 999)), profile_id )519 return self.get_profile_str_value( 'snapshots.tag', str(random.randint(100, 999)), profile_id )
420520
521 def get_snapshot_access_mode( self, profile_id = None ):
522 return self.get_profile_int_value( 'snapshots.access', network.LOCAL, profile_id )
523
524 def set_snapshot_access_mode( self, value, profile_id = None ):
525 self.set_profile_int_value( 'snapshots.access', value, profile_id )
526
421 def get_automatic_backup_mode( self, profile_id = None ):527 def get_automatic_backup_mode( self, profile_id = None ):
422 return self.get_profile_int_value( 'snapshots.automatic_backup_mode', self.NONE, profile_id )528 return self.get_profile_int_value( 'snapshots.automatic_backup_mode', self.NONE, profile_id )
423529
424530
=== modified file 'common/debian_specific/control'
--- common/debian_specific/control 2010-04-01 17:57:51 +0000
+++ common/debian_specific/control 2010-04-16 06:46:25 +0000
@@ -7,7 +7,7 @@
7Homepage: http://backintime.le-web.org7Homepage: http://backintime.le-web.org
8Architecture: all8Architecture: all
9Depends: python, rsync, cron9Depends: python, rsync, cron
10Recommends: powermgmt-base10Recommends: powermgmt-base, smbfs
11Conflicts: backintime11Conflicts: backintime
12Replaces: backintime12Replaces: backintime
13Description: Simple backup system (common)13Description: Simple backup system (common)
1414
=== added file 'common/network.py'
--- common/network.py 1970-01-01 00:00:00 +0000
+++ common/network.py 2010-04-16 06:46:25 +0000
@@ -0,0 +1,105 @@
1# Back In Time
2# Copyright (C) 2008-2009 Oprea Dan, Bart de Koning, Richard Bailey
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18import os.path
19import os
20import tempfile
21import base64
22import gettext
23
24import tools
25
26_=gettext.gettext
27
28LOCAL = 0
29SAMBA = 1
30
31NETWORK_ACCESS_MODES = [SAMBA]
32
33SNAPSHOT_ACCESS = {
34 LOCAL : _('Local folder'),
35 SAMBA : _('Windows network share (Samba)')
36 }
37
38def password_encode(password):
39 """encode a password using base64 but remove extra
40 equals signs from the end so they can be stored
41 in the config file"""
42 # this is not secure at all but means that the password
43 # isn't human readable in the config file. A more secure
44 # alternative would be to use the python keyring module
45 # to store the password using the KDE or Gnome keyring manager
46 # but this isn't available in most distros yet
47 return base64.urlsafe_b64encode(password).strip("=")
48
49def password_decode(password):
50 """return the base64 decoded password and pad with equals
51 signs first so that the string is a multiple of 4 long"""
52 return base64.urlsafe_b64decode(password+'='*(4-len(password)%4))
53
54class NetworkMount:
55 def __init__( self, location, protocol, username="", password="" ):
56 self.location = location
57 self.username = username
58 self.password = password
59 supported_protocols = [SAMBA]
60 if protocol in supported_protocols:
61 self.protocol = protocol
62 else:
63 raise RuntimeError, "Unsupported network protocol, "+protocol
64 self.mount()
65
66 def __del__( self ):
67 self.unmount()
68
69 def mount( self ):
70 self.mount_dir = tempfile.mkdtemp(prefix="backintime_mount_")
71 # need to use full paths to mount and unmount commands so that they work in cron
72 if (self.protocol == SAMBA):
73 cmd = "/sbin/mount.cifs "+self.location+" "+self.mount_dir
74 if (self.username != "" and self.password != ""):
75 cmd += " -o username="+self.username+",password="+self.password
76 ret = _execute(cmd)
77 if (ret != 0):
78 print "Error mounting network share "+self.location
79
80 def unmount( self ):
81 """Unmount a network share and delete the mount directory.
82 You can't rely on python to delete the NetworkMount object so
83 this should be called manually when finished with a network share"""
84 if self.mounted():
85 if (self.protocol == SAMBA):
86 cmd = "/sbin/umount.cifs "+self.mount_dir
87 _execute(cmd)
88 if os.path.isdir(self.mount_dir):
89 os.rmdir(self.mount_dir)
90
91 def path( self ):
92 return self.mount_dir
93
94 def mounted( self ):
95 if not os.path.isdir(self.mount_dir):
96 return False
97 ret = _execute("mountpoint "+self.mount_dir)
98 if (ret == 0):
99 return True
100 else:
101 return False
102
103def _execute( cmd ):
104 ret_val = os.system( cmd )
105 return ret_val
0106
=== modified file 'common/snapshots.py'
--- common/snapshots.py 2010-03-29 14:27:12 +0000
+++ common/snapshots.py 2010-04-16 06:46:25 +0000
@@ -34,6 +34,7 @@
34import applicationinstance34import applicationinstance
35import tools35import tools
36import pluginmanager36import pluginmanager
37import network
3738
3839
39_=gettext.gettext40_=gettext.gettext
@@ -514,26 +515,45 @@
514 #print answer_same515 #print answer_same
515 profile_id = profiles[0]516 profile_id = profiles[0]
516 #print profile_id517 #print profile_id
517 #old_folder = self.get_snapshots_path( profile_id )518 main_snapshot_access = self.config.get_snapshot_access_mode(profile_id)
518 #print old_folder519 if main_snapshot_access in network.NETWORK_ACCESS_MODES:
520 main_network_path=self.config.get_snapshots_network_path(profile_id)
521 main_username=self.config.get_snapshots_username(profile_id)
522 main_password=self.config.get_snapshots_password(profile_id)
523
519 main_folder = self.config.get_snapshots_path( profile_id )524 main_folder = self.config.get_snapshots_path( profile_id )
520 old_snapshots_paths=[]525 old_snapshots_paths = []
526 old_snapshot_access = []
527 old_network_path = {}
528 old_username = {}
529 old_password = {}
521 counter = 0530 counter = 0
522 success = []531 success = []
523 532
524 for profile_id in profiles:533 for profile_id in profiles:
525 #print counter534 #print counter
535 old_snapshot_access.append(self.config.get_snapshot_access_mode(profile_id))
536 if old_snapshot_access[counter] in network.NETWORK_ACCESS_MODES:
537 old_network_path[counter] = self.config.get_snapshots_network_path(profile_id)
538 old_username[counter] = self.config.get_snapshots_username(profile_id)
539 old_password[counter] = self.config.get_snapshots_password(profile_id)
526 old_snapshots_paths.append( self.config.get_snapshots_path( profile_id ) )540 old_snapshots_paths.append( self.config.get_snapshots_path( profile_id ) )
527 #print old_snapshots_paths541 #print old_snapshots_paths
528 old_folder = os.path.join( self.config.get_snapshots_path( profile_id ), 'backintime' )542 old_folder = os.path.join( self.config.get_snapshots_path( profile_id ), 'backintime' )
529 #print old_folder543 #print old_folder
530 if profile_id != "1" and answer_same == True:544 if profile_id != "1" and answer_same == True:
531 #print 'profile_id != 1, answer = True'545 #print 'profile_id != 1, answer = True'
532 self.config.set_snapshots_path( main_folder, profile_id )546 if main_snapshot_access in network.NETWORK_ACCESS_MODES:
547 self.config.set_snapshots_network_details( main_network_path, main_snapshot_access, main_username, main_password, profile_id )
548 else:
549 self.config.set_snapshots_path( main_folder, profile_id )
533 logger.info( 'Folder of profile %s is set to %s' %( profile_id, main_folder ) )550 logger.info( 'Folder of profile %s is set to %s' %( profile_id, main_folder ) )
534 else:551 else:
535 self.config.set_snapshots_path( self.config.get_snapshots_path( profile_id ), profile_id )552 if old_snapshot_access[counter] in network.NETWORK_ACCESS_MODES:
536 logger.info( 'Folder of profile %s is set to %s' %( profile_id, main_folder ) )553 self.config.set_snapshots_network_details( old_network_path[counter], old_snapshot_access[counter], old_username[counter], old_password[counter], profile_id )
554 else:
555 self.config.set_snapshots_path( self.config.get_snapshots_path( profile_id ), profile_id )
556 logger.info( 'Folder of profile %s is set to %s' %( profile_id, old_folder ) )
537 new_folder = self.config.get_snapshots_full_path( profile_id )557 new_folder = self.config.get_snapshots_full_path( profile_id )
538 #print new_folder558 #print new_folder
539 #snapshots_to_move = tools.get_snapshots_list_in_folder( old_folder )559 #snapshots_to_move = tools.get_snapshots_list_in_folder( old_folder )
@@ -560,7 +580,10 @@
560 success.append( False )580 success.append( False )
561 # restore581 # restore
562 logger.info( 'Restore former settings' )582 logger.info( 'Restore former settings' )
563 self.config.set_snapshots_path( old_snapshots_paths[counter], profile_id )583 if old_snapshot_access[counter] in network.NETWORK_ACCESS_MODES:
584 self.config.set_snapshots_network_details( old_network_path[counter], old_snapshot_access[counter], old_username[counter], old_password[counter], profile_id )
585 else:
586 self.config.set_snapshots_path( old_snapshots_paths[counter], profile_id )
564 #print self.get_snapshots_path( profile_id )587 #print self.get_snapshots_path( profile_id )
565 self.config.error_handler( _('Former settings of profile %s are restored.\nBackinTime cannot continue taking new snapshots.\n\nYou can manually move the snapshots, \nif you are done restart BackinTime to proceed' %profile_id ) )588 self.config.error_handler( _('Former settings of profile %s are restored.\nBackinTime cannot continue taking new snapshots.\n\nYou can manually move the snapshots, \nif you are done restart BackinTime to proceed' %profile_id ) )
566 589
@@ -584,9 +607,16 @@
584 if answer_continue == True:607 if answer_continue == True:
585 #self.set_update_other_folders( False )608 #self.set_update_other_folders( False )
586 for profile_id in profiles:609 for profile_id in profiles:
587 old_folder = self.config.get_snapshots_path( profile_id )610 old_snapshot_access = self.config.get_snapshot_access_mode(profile_id)
588 self.config.set_snapshots_path( old_folder, profile_id )611 if old_snapshot_access in network.NETWORK_ACCESS_MODES:
589 logger.info( 'Folder of profile %s is set to %s' %( profile_id, self.get_snapshots_path( profile_id ) ) )612 old_network_path = self.config.get_snapshots_network_path(profile_id)
613 old_username = self.config.get_snapshots_username(profile_id)
614 old_password = self.config.get_snapshots_password(profile_id)
615 self.config.set_snapshots_network_details( old_network_path, old_snapshot_access, old_username, old_password, profile_id )
616 else:
617 old_folder = self.config.get_snapshots_path( profile_id )
618 self.config.set_snapshots_path( old_folder, profile_id )
619 logger.info( 'Folder of profile %s is set to %s' %( profile_id, self.get_snapshot_path( profile_id ) ) )
590 620
591 logger.info( 'BackinTime will be able to make new snapshots again!' )621 logger.info( 'BackinTime will be able to make new snapshots again!' )
592 self.config.error_handler( _('BackinTime will continue taking snapshots again as scheduled' ) )622 self.config.error_handler( _('BackinTime will continue taking snapshots again as scheduled' ) )
@@ -1276,4 +1306,5 @@
1276 config = config.Config()1306 config = config.Config()
1277 snapshots = Snapshots( config )1307 snapshots = Snapshots( config )
1278 snapshots.take_snapshot()1308 snapshots.take_snapshot()
1309 config.unmount_shares()
12791310
12801311
=== modified file 'common/tools.py'
--- common/tools.py 2010-02-14 13:47:14 +0000
+++ common/tools.py 2010-04-16 06:46:25 +0000
@@ -21,12 +21,10 @@
21import sys21import sys
22import subprocess22import subprocess
2323
24
25ON_AC = 024ON_AC = 0
26ON_BATTERY = 125ON_BATTERY = 1
27POWER_ERROR = 25526POWER_ERROR = 255
2827
29
30def get_backintime_path( path ):28def get_backintime_path( path ):
31 return os.path.join( os.path.dirname( os.path.abspath( os.path.dirname( __file__ ) ) ), path )29 return os.path.join( os.path.dirname( os.path.abspath( os.path.dirname( __file__ ) ) ), path )
3230
@@ -268,7 +266,7 @@
268 return False266 return False
269267
270268
271def _execute( cmd, callback = None, user_data = None ):269def _execute( cmd, callback = None, user_data = None):
272 ret_val = 0270 ret_val = 0
273271
274 if callback is None:272 if callback is None:
275273
=== modified file 'gnome/app.py'
--- gnome/app.py 2010-03-23 09:34:13 +0000
+++ gnome/app.py 2010-04-16 06:46:25 +0000
@@ -452,6 +452,7 @@
452 #fill lists452 #fill lists
453 selected_file = None453 selected_file = None
454 show_snapshots = False454 show_snapshots = False
455 self.config.update_shares()
455 if init:456 if init:
456 self.folder_path, selected_file, show_snapshots = self.get_startup_folder_and_file()457 self.folder_path, selected_file, show_snapshots = self.get_startup_folder_and_file()
457 self.snapshot_id = '/'458 self.snapshot_id = '/'
@@ -925,12 +926,17 @@
925 self.on_help()926 self.on_help()
926927
927 def on_btn_settings_clicked( self, button ):928 def on_btn_settings_clicked( self, button ):
928 snapshots_path = self.config.get_snapshots_path()929 snapshots_local_path = self.config.get_snapshots_local_path()
930 snapshots_network_path = self.config.get_snapshots_network_path()
931 snapshots_access = self.config.get_snapshot_access_mode()
929 include_folders = self.config.get_include()932 include_folders = self.config.get_include()
930933
931 settingsdialog.SettingsDialog( self.config, self.snapshots, self ).run()934 settingsdialog.SettingsDialog( self.config, self.snapshots, self ).run()
932935
933 if snapshots_path != self.config.get_snapshots_path() or include_folders != self.config.get_include():936 if (snapshots_local_path != self.config.get_snapshots_local_path() or
937 snapshots_network_path != self.config.get_snapshots_network_path() or
938 snapshots_access != self.config.get_snapshot_access_mode() or
939 include_folders != self.config.get_include()):
934 self.update_all( False )940 self.update_all( False )
935 self.update_profiles()941 self.update_profiles()
936942
@@ -1173,5 +1179,7 @@
1173 gtk.main()1179 gtk.main()
1174 logger.closelog()1180 logger.closelog()
11751181
1182 cfg.unmount_shares()
1183
1176 app_instance.exit_application()1184 app_instance.exit_application()
11771185
11781186
=== modified file 'gnome/settingsdialog.glade'
--- gnome/settingsdialog.glade 2010-03-05 20:41:59 +0000
+++ gnome/settingsdialog.glade 2010-04-16 06:46:25 +0000
@@ -123,42 +123,167 @@
123 <property name="label_xalign">0</property>123 <property name="label_xalign">0</property>
124 <property name="shadow_type">none</property>124 <property name="shadow_type">none</property>
125 <child>125 <child>
126 <object class="GtkAlignment" id="alignment1">126 <object class="GtkVBox" id="vbox1">
127 <property name="visible">True</property>127 <property name="visible">True</property>
128 <property name="left_padding">12</property>128 <property name="orientation">vertical</property>
129 <child>129 <property name="spacing">3</property>
130 <object class="GtkHBox" id="hbox4">130 <child>
131 <property name="visible">True</property>131 <object class="GtkAlignment" id="alignment3">
132 <child>132 <property name="visible">True</property>
133 <object class="GtkEntry" id="edit_where">133 <property name="left_padding">12</property>
134 <property name="visible">True</property>134 <child>
135 <property name="can_focus">True</property>135 <object class="GtkHBox" id="hbox8">
136 <property name="editable">False</property>136 <property name="visible">True</property>
137 <property name="invisible_char">&#x25CF;</property>137 <property name="homogeneous">True</property>
138 </object>138 <child>
139 <packing>139 <object class="GtkComboBox" id="cb_snapshot_access">
140 <property name="position">0</property>140 <property name="visible">True</property>
141 </packing>141 <signal name="changed" handler="on_cb_snapshot_access_changed"/>
142 </child>142 </object>
143 <child>143 <packing>
144 <object class="GtkButton" id="btn_where">144 <property name="position">0</property>
145 <property name="visible">True</property>145 </packing>
146 <property name="can_focus">True</property>146 </child>
147 <property name="receives_default">True</property>147 </object>
148 <signal name="clicked" handler="on_btn_where_clicked"/>148 </child>
149 <child>149 </object>
150 <object class="GtkImage" id="image1">150 <packing>
151 <property name="visible">True</property>151 <property name="position">0</property>
152 <property name="stock">gtk-directory</property>152 </packing>
153 </object>153 </child>
154 </child>154 <child>
155 </object>155 <object class="GtkAlignment" id="alignment_where">
156 <packing>156 <property name="visible">True</property>
157 <property name="expand">False</property>157 <property name="left_padding">12</property>
158 <property name="position">1</property>158 <child>
159 </packing>159 <object class="GtkHBox" id="hbox4">
160 </child>160 <property name="visible">True</property>
161 </object>161 <child>
162 <object class="GtkEntry" id="edit_where">
163 <property name="visible">True</property>
164 <property name="can_focus">True</property>
165 <property name="editable">False</property>
166 <property name="invisible_char">&#x2022;</property>
167 </object>
168 <packing>
169 <property name="position">0</property>
170 </packing>
171 </child>
172 <child>
173 <object class="GtkButton" id="btn_where">
174 <property name="visible">True</property>
175 <property name="can_focus">True</property>
176 <property name="receives_default">True</property>
177 <signal name="clicked" handler="on_btn_where_clicked"/>
178 <child>
179 <object class="GtkImage" id="image1">
180 <property name="visible">True</property>
181 <property name="stock">gtk-directory</property>
182 </object>
183 </child>
184 </object>
185 <packing>
186 <property name="expand">False</property>
187 <property name="position">1</property>
188 </packing>
189 </child>
190 </object>
191 </child>
192 </object>
193 <packing>
194 <property name="position">1</property>
195 </packing>
196 </child>
197 <child>
198 <object class="GtkAlignment" id="alignment_network">
199 <property name="left_padding">12</property>
200 <child>
201 <object class="GtkTable" id="table1">
202 <property name="visible">True</property>
203 <property name="n_rows">3</property>
204 <property name="n_columns">2</property>
205 <property name="column_spacing">25</property>
206 <child>
207 <object class="GtkEntry" id="edit_where_network">
208 <property name="visible">True</property>
209 <property name="can_focus">True</property>
210 <property name="invisible_char">&#x2022;</property>
211 <property name="caps_lock_warning">False</property>
212 </object>
213 <packing>
214 <property name="left_attach">1</property>
215 <property name="right_attach">2</property>
216 </packing>
217 </child>
218 <child>
219 <object class="GtkLabel" id="label7">
220 <property name="visible">True</property>
221 <property name="xalign">0</property>
222 <property name="label" translatable="yes">Network location</property>
223 </object>
224 <packing>
225 <property name="x_options">GTK_FILL</property>
226 </packing>
227 </child>
228 <child>
229 <object class="GtkLabel" id="label8">
230 <property name="visible">True</property>
231 <property name="xalign">0</property>
232 <property name="label" translatable="yes">Username</property>
233 </object>
234 <packing>
235 <property name="top_attach">1</property>
236 <property name="bottom_attach">2</property>
237 <property name="x_options">GTK_FILL</property>
238 </packing>
239 </child>
240 <child>
241 <object class="GtkEntry" id="edit_username">
242 <property name="visible">True</property>
243 <property name="can_focus">True</property>
244 <property name="invisible_char">&#x2022;</property>
245 <property name="caps_lock_warning">False</property>
246 </object>
247 <packing>
248 <property name="left_attach">1</property>
249 <property name="right_attach">2</property>
250 <property name="top_attach">1</property>
251 <property name="bottom_attach">2</property>
252 </packing>
253 </child>
254 <child>
255 <object class="GtkLabel" id="label9">
256 <property name="visible">True</property>
257 <property name="xalign">0</property>
258 <property name="label" translatable="yes">Password</property>
259 </object>
260 <packing>
261 <property name="top_attach">2</property>
262 <property name="bottom_attach">3</property>
263 <property name="x_options">GTK_FILL</property>
264 <property name="y_options">GTK_FILL</property>
265 </packing>
266 </child>
267 <child>
268 <object class="GtkEntry" id="edit_password">
269 <property name="visible">True</property>
270 <property name="can_focus">True</property>
271 <property name="visibility">False</property>
272 <property name="invisible_char">&#x2022;</property>
273 </object>
274 <packing>
275 <property name="left_attach">1</property>
276 <property name="right_attach">2</property>
277 <property name="top_attach">2</property>
278 <property name="bottom_attach">3</property>
279 </packing>
280 </child>
281 </object>
282 </child>
283 </object>
284 <packing>
285 <property name="position">2</property>
286 </packing>
162 </child>287 </child>
163 </object>288 </object>
164 </child>289 </child>
165290
=== modified file 'gnome/settingsdialog.py'
--- gnome/settingsdialog.py 2010-03-05 20:41:59 +0000
+++ gnome/settingsdialog.py 2010-04-16 06:46:25 +0000
@@ -30,6 +30,7 @@
30import config30import config
31import messagebox31import messagebox
32import tools32import tools
33import network
3334
3435
35_=gettext.gettext36_=gettext.gettext
@@ -74,6 +75,7 @@
74 'on_combo_profiles_changed': self.on_combo_profiles_changed,75 'on_combo_profiles_changed': self.on_combo_profiles_changed,
75 'on_btn_where_clicked': self.on_btn_where_clicked,76 'on_btn_where_clicked': self.on_btn_where_clicked,
76 'on_cb_backup_mode_changed': self.on_cb_backup_mode_changed,77 'on_cb_backup_mode_changed': self.on_cb_backup_mode_changed,
78 'on_cb_snapshot_access_changed': self.on_cb_snapshot_access_changed,
77 }79 }
78 80
79 builder.connect_signals(signals)81 builder.connect_signals(signals)
@@ -102,6 +104,19 @@
102 #self.fcb_where = get( 'fcb_where' )104 #self.fcb_where = get( 'fcb_where' )
103 #self.fcb_where.set_show_hidden( self.parent.show_hidden_files )105 #self.fcb_where.set_show_hidden( self.parent.show_hidden_files )
104 self.edit_where = get( 'edit_where' )106 self.edit_where = get( 'edit_where' )
107
108 #snapshot location access
109 self.store_snapshot_access_mode = gtk.ListStore( str, int )
110 map = network.SNAPSHOT_ACCESS
111 self.rev_snapshot_access_modes = {}
112 keys = map.keys()
113 keys.sort()
114 for key in keys:
115 self.rev_snapshot_access_modes[ map[key] ] = key
116 self.store_snapshot_access_mode.append( [ map[key], key ] )
117 self.edit_where_network = get( 'edit_where_network' )
118 self.edit_username = get( 'edit_username' )
119 self.edit_password = get( 'edit_password' )
105 120
106 #automatic backup mode store121 #automatic backup mode store
107 self.store_backup_mode = gtk.ListStore( str, int )122 self.store_backup_mode = gtk.ListStore( str, int )
@@ -165,6 +180,18 @@
165 180
166 self.store_exclude = gtk.ListStore( str, str )181 self.store_exclude = gtk.ListStore( str, str )
167 self.list_exclude.set_model( self.store_exclude )182 self.list_exclude.set_model( self.store_exclude )
183
184 #setup snapshot access mode
185 self.cb_snapshot_access = get( 'cb_snapshot_access' )
186 self.cb_snapshot_access.set_model( self.store_snapshot_access_mode)
187
188 self.cb_snapshot_access.clear()
189 renderer = gtk.CellRendererText()
190 self.cb_snapshot_access.pack_start( renderer, True )
191 self.cb_snapshot_access.add_attribute( renderer, 'text', 0 )
192
193 self.alignment_where = get( 'alignment_where' )
194 self.alignment_network = get( 'alignment_network' )
168 195
169 #setup automatic backup mode196 #setup automatic backup mode
170 self.cb_backup_mode = get( 'cb_backup_mode' )197 self.cb_backup_mode = get( 'cb_backup_mode' )
@@ -306,6 +333,17 @@
306 self.profile_id = profile_id333 self.profile_id = profile_id
307 334
308 self.update_profile()335 self.update_profile()
336
337 def on_cb_snapshot_access_changed( self, *params ):
338 iter = self.cb_snapshot_access.get_active_iter()
339 access = self.store_snapshot_access_mode.get_value( iter, 1 )
340
341 if access in network.NETWORK_ACCESS_MODES:
342 self.alignment_where.hide()
343 self.alignment_network.show()
344 else:
345 self.alignment_where.show()
346 self.alignment_network.hide()
309 347
310 def update_profiles( self ):348 def update_profiles( self ):
311 self.disable_combo_changed = True349 self.disable_combo_changed = True
@@ -335,7 +373,12 @@
335 373
336 #set current folder374 #set current folder
337 #self.fcb_where.set_filename( self.config.get_snapshots_path() )375 #self.fcb_where.set_filename( self.config.get_snapshots_path() )
338 self.edit_where.set_text( self.config.get_snapshots_path( self.profile_id ) )376 self.edit_where.set_text( self.config.get_snapshots_local_path( self.profile_id ) )
377
378 #setup network paths
379 self.edit_where_network.set_text( self.config.get_snapshots_network_path( self.profile_id ) )
380 self.edit_username.set_text( self.config.get_snapshots_username( self.profile_id ) )
381 self.edit_password.set_text( self.config.get_snapshots_password( self.profile_id ) )
339 382
340 #per directory schedule383 #per directory schedule
341 #self.cb_per_directory_schedule.set_active( self.config.get_per_directory_schedule() )384 #self.cb_per_directory_schedule.set_active( self.config.get_per_directory_schedule() )
@@ -358,6 +401,17 @@
358 if len( exclude_patterns ) > 0:401 if len( exclude_patterns ) > 0:
359 for exclude_pattern in exclude_patterns:402 for exclude_pattern in exclude_patterns:
360 self.store_exclude.append( [exclude_pattern, gtk.STOCK_DELETE] )403 self.store_exclude.append( [exclude_pattern, gtk.STOCK_DELETE] )
404
405 #setup snapshot access mode
406 i = 0
407 iter = self.store_snapshot_access_mode.get_iter_first()
408 default_mode = self.config.get_snapshot_access_mode( self.profile_id )
409 while not iter is None:
410 if self.store_snapshot_access_mode.get_value( iter, 1 ) == default_mode:
411 self.cb_snapshot_access.set_active( i )
412 break
413 iter = self.store_snapshot_access_mode.iter_next( iter )
414 i = i + 1
361 415
362 #setup automatic backup mode416 #setup automatic backup mode
363 i = 0417 i = 0
@@ -443,13 +497,26 @@
443 497
444 def save_profile( self ):498 def save_profile( self ):
445 #profile_id = self.config.get_current_profile()499 #profile_id = self.config.get_current_profile()
446 #snapshots path500
447 snapshots_path = self.edit_where.get_text()501 iter = self.cb_snapshot_access.get_active_iter()
448 502 access = self.store_snapshot_access_mode.get_value( iter, 1 )
449 #hack503 if access in network.NETWORK_ACCESS_MODES:
450 if snapshots_path.startswith( '//' ):504 network_path = self.edit_where_network.get_text()
451 snapshots_path = snapshots_path[ 1 : ]505 username = self.edit_username.get_text()
452 506 password = self.edit_password.get_text()
507 if not self.config.set_snapshots_network_details(network_path,access,username,password):
508 return False
509 self.config.update_shares()
510 else:
511 #snapshots path
512 snapshots_path = self.edit_where.get_text()
513 #hack
514 if snapshots_path.startswith( '//' ):
515 snapshots_path = snapshots_path[ 1 : ]
516 #ok let's save to config
517 if not self.config.set_snapshots_path( snapshots_path, self.profile_id ):
518 return False
519
453 #include list 520 #include list
454 include_list = []521 include_list = []
455 iter = self.store_include.get_iter_first()522 iter = self.store_include.get_iter_first()
@@ -471,12 +538,6 @@
471 #if len( self.config.get_snapshots_path() ) > 0 and self.config.get_snapshots_path() != snapshots_path:538 #if len( self.config.get_snapshots_path() ) > 0 and self.config.get_snapshots_path() != snapshots_path:
472 # if gtk.RESPONSE_YES != messagebox.show_question( self.dialog, self.config, _('Are you sure you want to change snapshots folder ?') ):539 # if gtk.RESPONSE_YES != messagebox.show_question( self.dialog, self.config, _('Are you sure you want to change snapshots folder ?') ):
473 # return False 540 # return False
474
475 #ok let's save to config
476 self.config.set_snapshots_path( snapshots_path, self.profile_id )
477 #if not msg is None:
478 # messagebox.show_error( self.dialog, self.config, msg )
479 # return False
480541
481 self.config.set_include( include_list, self.profile_id )542 self.config.set_include( include_list, self.profile_id )
482 self.config.set_exclude( exclude_list, self.profile_id )543 self.config.set_exclude( exclude_list, self.profile_id )
@@ -509,6 +570,8 @@
509 self.config.set_run_ionice_from_cron_enabled( self.cb_run_ionice_from_cron.get_active(), self.profile_id )570 self.config.set_run_ionice_from_cron_enabled( self.cb_run_ionice_from_cron.get_active(), self.profile_id )
510 self.config.set_run_ionice_from_user_enabled( self.cb_run_ionice_from_user.get_active(), self.profile_id )571 self.config.set_run_ionice_from_user_enabled( self.cb_run_ionice_from_user.get_active(), self.profile_id )
511 self.config.set_no_on_battery_enabled( self.cb_no_on_battery.get_active(), self.profile_id )572 self.config.set_no_on_battery_enabled( self.cb_no_on_battery.get_active(), self.profile_id )
573
574 return True
512 575
513 def update_remove_old_backups( self, button ):576 def update_remove_old_backups( self, button ):
514 enabled = self.cb_remove_old_backup.get_active()577 enabled = self.cb_remove_old_backup.get_active()
@@ -687,7 +750,8 @@
687 self.dialog.destroy()750 self.dialog.destroy()
688 751
689 def validate( self ):752 def validate( self ):
690 self.save_profile()753 if not self.save_profile():
754 return False
691 755
692 if not self.config.check_config():756 if not self.config.check_config():
693 return False757 return False
694758
=== modified file 'kde4/app.py'
--- kde4/app.py 2010-04-01 17:57:51 +0000
+++ kde4/app.py 2010-04-16 06:46:25 +0000
@@ -389,6 +389,8 @@
389389
390 self.disable_profile_changed = False390 self.disable_profile_changed = False
391391
392 self.config.update_shares()
393
392 def update_profile( self ):394 def update_profile( self ):
393 self.update_time_line()395 self.update_time_line()
394 self.update_places()396 self.update_places()
@@ -1098,5 +1100,7 @@
10981100
1099 logger.closelog()1101 logger.closelog()
11001102
1103 cfg.unmount_shares()
1104
1101 app_instance.exit_application()1105 app_instance.exit_application()
11021106
11031107
=== modified file 'kde4/settingsdialog.py'
--- kde4/settingsdialog.py 2010-03-05 20:41:59 +0000
+++ kde4/settingsdialog.py 2010-04-16 06:46:25 +0000
@@ -32,6 +32,7 @@
32import config32import config
33import tools33import tools
34import kde4tools34import kde4tools
35import network
3536
3637
37_=gettext.gettext38_=gettext.gettext
@@ -108,8 +109,15 @@
108 group_box.setTitle( QString.fromUtf8( _( 'Where to save snapshots' ) ) )109 group_box.setTitle( QString.fromUtf8( _( 'Where to save snapshots' ) ) )
109 layout.addWidget( group_box )110 layout.addWidget( group_box )
110111
111 hlayout = QHBoxLayout( group_box )112 vlayout = QVBoxLayout( group_box )
112113
114 self.combo_snapshot_access = KComboBox( self )
115 vlayout.addWidget(self.combo_snapshot_access)
116 self.fill_combo( self.combo_snapshot_access, network.SNAPSHOT_ACCESS )
117 QObject.connect( self.combo_snapshot_access, SIGNAL('currentIndexChanged(int)'), self.current_snapshot_access_changed )
118
119 # - In a local folder
120 hlayout = QHBoxLayout()
113 self.edit_snapshots_path = KLineEdit( self )121 self.edit_snapshots_path = KLineEdit( self )
114 self.edit_snapshots_path.setReadOnly( True )122 self.edit_snapshots_path.setReadOnly( True )
115 hlayout.addWidget( self.edit_snapshots_path )123 hlayout.addWidget( self.edit_snapshots_path )
@@ -118,6 +126,38 @@
118 hlayout.addWidget( self.btn_snapshots_path )126 hlayout.addWidget( self.btn_snapshots_path )
119 QObject.connect( self.btn_snapshots_path, SIGNAL('clicked()'), self.on_btn_snapshots_path_clicked )127 QObject.connect( self.btn_snapshots_path, SIGNAL('clicked()'), self.on_btn_snapshots_path_clicked )
120128
129 self.local_path_widgets = [self.edit_snapshots_path, self.btn_snapshots_path]
130
131 vlayout.addLayout(hlayout)
132
133 # - Over the network
134 glayout = QGridLayout()
135 self.edit_snapshots_network_path = KLineEdit( self )
136 self.label_snapshots_network_path = QLabel(QString.fromUtf8( _( 'Network location')), self )
137 self.label_snapshots_network_path.setBuddy(self.edit_snapshots_network_path)
138 glayout.addWidget(self.label_snapshots_network_path,0,0)
139 glayout.addWidget(self.edit_snapshots_network_path,0,1)
140
141 self.edit_snapshots_username = KLineEdit( self )
142 self.label_snapshots_username = QLabel(QString.fromUtf8( _( 'Username')), self )
143 self.label_snapshots_username.setBuddy(self.edit_snapshots_username)
144 glayout.addWidget(self.label_snapshots_username,1,0)
145 glayout.addWidget(self.edit_snapshots_username,1,1)
146
147 self.edit_snapshots_password = KLineEdit( self )
148 self.edit_snapshots_password.setEchoMode(KLineEdit.EchoMode(KLineEdit.Password))
149 self.label_snapshots_password = QLabel(QString.fromUtf8( _( 'Password')), self )
150 self.label_snapshots_password.setBuddy(self.edit_snapshots_password)
151 glayout.addWidget(self.label_snapshots_password,2,0)
152 glayout.addWidget(self.edit_snapshots_password,2,1)
153
154 self.network_widgets = [self.edit_snapshots_network_path, self.label_snapshots_network_path,
155 self.edit_snapshots_username, self.label_snapshots_username,
156 self.edit_snapshots_password, self.label_snapshots_password]
157
158 glayout.setHorizontalSpacing(35)
159 vlayout.addLayout(glayout)
160
121 #Schedule161 #Schedule
122 group_box = QGroupBox( self )162 group_box = QGroupBox( self )
123 self.global_schedule_group_box = group_box163 self.global_schedule_group_box = group_box
@@ -130,7 +170,7 @@
130 hlayout.addWidget( self.combo_automatic_snapshots, 2 )170 hlayout.addWidget( self.combo_automatic_snapshots, 2 )
131 self.fill_combo( self.combo_automatic_snapshots, self.config.AUTOMATIC_BACKUP_MODES )171 self.fill_combo( self.combo_automatic_snapshots, self.config.AUTOMATIC_BACKUP_MODES )
132172
133 hlayout_time = QHBoxLayout( group_box )173 hlayout_time = QHBoxLayout()
134 hlayout.addLayout( hlayout_time )174 hlayout.addLayout( hlayout_time )
135175
136 self.lbl_automatic_snapshots_time = QLabel( QString.fromUtf8( _( 'Hour:' ) ), self )176 self.lbl_automatic_snapshots_time = QLabel( QString.fromUtf8( _( 'Hour:' ) ), self )
@@ -356,6 +396,17 @@
356 def current_automatic_snapshot_changed( self, index ):396 def current_automatic_snapshot_changed( self, index ):
357 backup_mode = self.combo_automatic_snapshots.itemData( index ).toInt()[0]397 backup_mode = self.combo_automatic_snapshots.itemData( index ).toInt()[0]
358 self.update_automatic_snapshot_time( backup_mode )398 self.update_automatic_snapshot_time( backup_mode )
399
400 def update_snapshot_access_mode( self, access_mode ):
401 network_access = (access_mode in network.NETWORK_ACCESS_MODES)
402 for widget in self.local_path_widgets:
403 widget.setVisible(not network_access)
404 for widget in self.network_widgets:
405 widget.setVisible(network_access)
406
407 def current_snapshot_access_changed( self, index ):
408 access = self.combo_snapshot_access.itemData( index ).toInt()[0]
409 self.update_snapshot_access_mode( access )
359410
360 def current_profile_changed( self, index ):411 def current_profile_changed( self, index ):
361 if self.disable_profile_changed:412 if self.disable_profile_changed:
@@ -395,7 +446,15 @@
395 self.btn_remove_profile.setEnabled( True )446 self.btn_remove_profile.setEnabled( True )
396447
397 #TAB: General448 #TAB: General
398 self.edit_snapshots_path.setText( QString.fromUtf8( self.config.get_snapshots_path() ) )449 self.set_combo_value( self.combo_snapshot_access, self.config.get_snapshot_access_mode() )
450 self.update_snapshot_access_mode( self.config.get_snapshot_access_mode() )
451
452 self.edit_snapshots_path.setText( QString.fromUtf8( self.config.get_snapshots_local_path() ) )
453
454 self.edit_snapshots_network_path.setText( QString.fromUtf8( self.config.get_snapshots_network_path() ))
455 self.edit_snapshots_username.setText( QString.fromUtf8( self.config.get_snapshots_username() ))
456 self.edit_snapshots_password.setText( QString.fromUtf8( self.config.get_snapshots_password() ))
457
399 self.set_combo_value( self.combo_automatic_snapshots, self.config.get_automatic_backup_mode() )458 self.set_combo_value( self.combo_automatic_snapshots, self.config.get_automatic_backup_mode() )
400 self.set_combo_value( self.combo_automatic_snapshots_time, self.config.get_automatic_backup_time() )459 self.set_combo_value( self.combo_automatic_snapshots_time, self.config.get_automatic_backup_time() )
401 self.update_automatic_snapshot_time( self.config.get_automatic_backup_mode() )460 self.update_automatic_snapshot_time( self.config.get_automatic_backup_mode() )
@@ -450,7 +509,17 @@
450509
451 def save_profile( self ):510 def save_profile( self ):
452 #snapshots path511 #snapshots path
453 self.config.set_snapshots_path( str( self.edit_snapshots_path.text().toUtf8() ) )512 access = self.combo_snapshot_access.itemData( self.combo_snapshot_access.currentIndex() ).toInt()[0]
513 if access in network.NETWORK_ACCESS_MODES:
514 network_path = str( self.edit_snapshots_network_path.text().toUtf8() )
515 username = str( self.edit_snapshots_username.text().toUtf8() )
516 password = str( self.edit_snapshots_password.text().toUtf8() )
517 if not self.config.set_snapshots_network_details(network_path,access,username,password):
518 return False
519 self.config.update_shares()
520 else:
521 if not self.config.set_snapshots_path( str( self.edit_snapshots_path.text().toUtf8() ) ):
522 return False
454 523
455 #include list 524 #include list
456 include_list = []525 include_list = []
@@ -495,6 +564,8 @@
495 self.config.set_run_ionice_from_user_enabled( self.cb_run_ionice_from_user.isChecked() )564 self.config.set_run_ionice_from_user_enabled( self.cb_run_ionice_from_user.isChecked() )
496 self.config.set_no_on_battery_enabled( self.cb_no_on_battery.isChecked() )565 self.config.set_no_on_battery_enabled( self.cb_no_on_battery.isChecked() )
497566
567 return True
568
498 def error_handler( self, message ):569 def error_handler( self, message ):
499 KMessageBox.error( self, QString.fromUtf8( message ) )570 KMessageBox.error( self, QString.fromUtf8( message ) )
500571
@@ -594,7 +665,8 @@
594 break665 break
595666
596 def validate( self ):667 def validate( self ):
597 self.save_profile()668 if not self.save_profile():
669 return False
598670
599 if not self.config.check_config():671 if not self.config.check_config():
600 return False672 return False

Subscribers

People subscribed via source and target branches