Merge lp:~germar/backintime/ssh into lp:backintime/1.0

Proposed by Germar
Status: Merged
Merged at revision: 808
Proposed branch: lp:~germar/backintime/ssh
Merge into: lp:backintime/1.0
Diff against target: 3394 lines (+2339/-271)
16 files modified
AUTHORS (+1/-0)
CHANGES (+5/-0)
common/backintime.py (+61/-0)
common/config.py (+153/-6)
common/configfile.py (+0/-1)
common/debian_specific/control (+1/-1)
common/dummytools.py (+105/-0)
common/mount.py (+421/-0)
common/snapshots.py (+154/-26)
common/sshtools.py (+309/-0)
common/tools.py (+21/-1)
gnome/app.py (+51/-10)
gnome/settingsdialog.glade (+608/-211)
gnome/settingsdialog.py (+202/-7)
kde4/app.py (+31/-0)
kde4/settingsdialog.py (+216/-8)
To merge this branch: bzr merge lp:~germar/backintime/ssh
Reviewer Review Type Date Requested Status
Back In Time Team Pending
Review via email: mp+131080@code.launchpad.net

Description of the change

* Add generic mount-framework which will make it easy to add new mount services to BIT
* Add mode 'SSH' for backups on remote host using ssh protocol
* Fix bug: wrong path if restore system root
* Add option to choose multiple custom hours for backup

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'AUTHORS'
--- AUTHORS 2009-11-06 13:00:42 +0000
+++ AUTHORS 2012-10-23 19:40:24 +0000
@@ -1,3 +1,4 @@
1Oprea Dan (<dan@le-web.org>)1Oprea Dan (<dan@le-web.org>)
2Bart de Koning (<bratdaking@gmail.com>)2Bart de Koning (<bratdaking@gmail.com>)
3Richard Bailey (<rmjb@mail.com>)3Richard Bailey (<rmjb@mail.com>)
4Germar Reitze (<germar.reitze@gmx.de>)
45
=== modified file 'CHANGES'
--- CHANGES 2012-02-22 09:55:08 +0000
+++ CHANGES 2012-10-23 19:40:24 +0000
@@ -1,5 +1,10 @@
1Back In Time1Back In Time
22
3Version 1.1.0
4* Add generic mount-framework
5* Add mode 'SSH' for backups on remote host using ssh protocol.
6* Fix bug: wrong path if restore system root
7
3Version 1.0.108Version 1.0.10
4* Add "Restore to ..." in replacement of copy (with or without drag & drop) because copy don't restore user/group/rights 9* Add "Restore to ..." in replacement of copy (with or without drag & drop) because copy don't restore user/group/rights
510
611
=== modified file 'common/backintime.py'
--- common/backintime.py 2010-11-08 21:11:52 +0000
+++ common/backintime.py 2012-10-23 19:40:24 +0000
@@ -26,6 +26,8 @@
26import logger26import logger
27import snapshots27import snapshots
28import tools28import tools
29import sshtools
30import mount
2931
30_=gettext.gettext32_=gettext.gettext
3133
@@ -44,9 +46,26 @@
4446
45def take_snapshot( cfg, force = True ):47def take_snapshot( cfg, force = True ):
46 logger.openlog()48 logger.openlog()
49 _mount(cfg)
47 snapshots.Snapshots( cfg ).take_snapshot( force )50 snapshots.Snapshots( cfg ).take_snapshot( force )
51 _umount(cfg)
48 logger.closelog()52 logger.closelog()
4953
54def _mount(cfg):
55 try:
56 hash_id = mount.Mount(cfg = cfg).mount()
57 except mount.MountException as ex:
58 logger.error(str(ex))
59 sys.exit(1)
60 else:
61 cfg.set_current_hash_id(hash_id)
62
63def _umount(cfg):
64 try:
65 mount.Mount(cfg = cfg).umount(cfg.current_hash_id)
66 except mount.MountException as ex:
67 logger.error(str(ex))
68
5069
51def print_version( cfg, app_name ):70def print_version( cfg, app_name ):
52 print ''71 print ''
@@ -79,6 +98,13 @@
79 print '\tShow the ID of the last snapshot (and exit)'98 print '\tShow the ID of the last snapshot (and exit)'
80 print '--last-snapshot-path'99 print '--last-snapshot-path'
81 print '\tShow the path to the last snapshot (and exit)'100 print '\tShow the path to the last snapshot (and exit)'
101 print '--keep-mount'
102 print '\tDon\'t unmount on exit. Only valid with'
103 print '\t--snapshots-list-path and --last-snapshot-path.'
104 print '--unmount'
105 print '\tUnmount the profile.'
106 print '--benchmark-cipher [file-size]'
107 print '\tShow a benchmark of all ciphers for ssh transfer (and exit)'
82 print '-v | --version'108 print '-v | --version'
83 print '\tShow version (and exit)'109 print '\tShow version (and exit)'
84 print '--license'110 print '--license'
@@ -94,6 +120,7 @@
94120
95 skip = False121 skip = False
96 index = 0122 index = 0
123 keep_mount = False
97 124
98 for arg in sys.argv[ 1 : ]:125 for arg in sys.argv[ 1 : ]:
99 index = index + 1126 index = index + 1
@@ -146,18 +173,21 @@
146 if not cfg.is_configured():173 if not cfg.is_configured():
147 print "The application is not configured !"174 print "The application is not configured !"
148 else:175 else:
176 _mount(cfg)
149 list = snapshots.Snapshots( cfg ).get_snapshots_list()177 list = snapshots.Snapshots( cfg ).get_snapshots_list()
150 if len( list ) <= 0:178 if len( list ) <= 0:
151 print "There are no snapshots"179 print "There are no snapshots"
152 else:180 else:
153 for snapshot_id in list:181 for snapshot_id in list:
154 print "SnapshotID: %s" % snapshot_id182 print "SnapshotID: %s" % snapshot_id
183 _umount(cfg)
155 sys.exit(0)184 sys.exit(0)
156185
157 if arg == '--snapshots-list-path':186 if arg == '--snapshots-list-path':
158 if not cfg.is_configured():187 if not cfg.is_configured():
159 print "The application is not configured !"188 print "The application is not configured !"
160 else:189 else:
190 _mount(cfg)
161 s = snapshots.Snapshots( cfg )191 s = snapshots.Snapshots( cfg )
162 list = s.get_snapshots_list()192 list = s.get_snapshots_list()
163 if len( list ) <= 0:193 if len( list ) <= 0:
@@ -165,29 +195,60 @@
165 else:195 else:
166 for snapshot_id in list:196 for snapshot_id in list:
167 print "SnapshotPath: %s" % s.get_snapshot_path( snapshot_id )197 print "SnapshotPath: %s" % s.get_snapshot_path( snapshot_id )
198 if not keep_mount:
199 _umount(cfg)
168 sys.exit(0)200 sys.exit(0)
169201
170 if arg == '--last-snapshot':202 if arg == '--last-snapshot':
171 if not cfg.is_configured():203 if not cfg.is_configured():
172 print "The application is not configured !"204 print "The application is not configured !"
173 else:205 else:
206 _mount(cfg)
174 list = snapshots.Snapshots( cfg ).get_snapshots_list()207 list = snapshots.Snapshots( cfg ).get_snapshots_list()
175 if len( list ) <= 0:208 if len( list ) <= 0:
176 print "There are no snapshots"209 print "There are no snapshots"
177 else:210 else:
178 print "SnapshotID: %s" % list[0]211 print "SnapshotID: %s" % list[0]
212 _umount(cfg)
179 sys.exit(0)213 sys.exit(0)
180214
181 if arg == '--last-snapshot-path':215 if arg == '--last-snapshot-path':
182 if not cfg.is_configured():216 if not cfg.is_configured():
183 print "The application is not configured !"217 print "The application is not configured !"
184 else:218 else:
219 _mount(cfg)
185 s = snapshots.Snapshots( cfg )220 s = snapshots.Snapshots( cfg )
186 list = s.get_snapshots_list()221 list = s.get_snapshots_list()
187 if len( list ) <= 0:222 if len( list ) <= 0:
188 print "There are no snapshots"223 print "There are no snapshots"
189 else:224 else:
190 print "SnapshotPath: %s" % s.get_snapshot_path( list[0] )225 print "SnapshotPath: %s" % s.get_snapshot_path( list[0] )
226 if not keep_mount:
227 _umount(cfg)
228 sys.exit(0)
229
230 if arg == '--keep-mount':
231 keep_mount = True
232 continue
233
234 if arg == '--unmount':
235 _mount(cfg)
236 _umount(cfg)
237 sys.exit(0)
238
239 if arg == '--benchmark-cipher':
240 if not cfg.is_configured():
241 print "The application is not configured !"
242 else:
243 try:
244 size = sys.argv[index + 1]
245 except IndexError:
246 size = '40'
247 if cfg.get_snapshots_mode() == 'ssh':
248 ssh = sshtools.SSH(cfg=cfg)
249 ssh.benchmark_cipher(size)
250 else:
251 print('ssh is not configured !')
191 sys.exit(0)252 sys.exit(0)
192253
193 if arg == '--snapshots' or arg == '-s':254 if arg == '--snapshots' or arg == '-s':
194255
=== modified file 'common/config.py'
--- common/config.py 2012-03-06 21:23:31 +0000
+++ common/config.py 2012-10-23 19:40:24 +0000
@@ -26,6 +26,9 @@
26import configfile26import configfile
27import tools27import tools
28import logger28import logger
29import mount
30import sshtools
31##import dummytools
2932
30_=gettext.gettext33_=gettext.gettext
3134
@@ -44,6 +47,7 @@
44 _5_MIN = 247 _5_MIN = 2
45 _10_MIN = 448 _10_MIN = 4
46 HOUR = 1049 HOUR = 10
50 CUSTOM_HOUR = 15
47 DAY = 2051 DAY = 20
48 WEEK = 3052 WEEK = 30
49 MONTH = 4053 MONTH = 40
@@ -58,6 +62,7 @@
58 _5_MIN: _('Every 5 minutes'), 62 _5_MIN: _('Every 5 minutes'),
59 _10_MIN: _('Every 10 minutes'), 63 _10_MIN: _('Every 10 minutes'),
60 HOUR : _('Every Hour'), 64 HOUR : _('Every Hour'),
65 CUSTOM_HOUR : _('Custom Hours'),
61 DAY : _('Every Day'), 66 DAY : _('Every Day'),
62 WEEK : _('Every Week'), 67 WEEK : _('Every Week'),
63 MONTH : _('Every Month')68 MONTH : _('Every Month')
@@ -71,7 +76,30 @@
7176
72 MIN_FREE_SPACE_UNITS = { DISK_UNIT_MB : 'Mb', DISK_UNIT_GB : 'Gb' }77 MIN_FREE_SPACE_UNITS = { DISK_UNIT_MB : 'Mb', DISK_UNIT_GB : 'Gb' }
7378
74 DEFAULT_EXCLUDE = [ '.gvfs', '.cache*', '[Cc]ache*', '.thumbnails*', '[Tt]rash*', '*.backup*', '*~', os.path.expanduser( '~/Ubuntu One' ), '.dropbox*', '/proc', '/sys', '/dev' ]79 DEFAULT_EXCLUDE = [ '.gvfs', '.cache*', '[Cc]ache*', '.thumbnails*', '[Tt]rash*', '*.backup*', '*~', os.path.expanduser( '~/Ubuntu One' ), '.dropbox*', '/proc', '/sys', '/dev' , '/tmp/backintime']
80
81 SNAPSHOT_MODES = {
82 #mode : (<mounttools>, _('ComboBox Text') ),
83 'local' : (None, _('Local') ),
84 'ssh' : (sshtools.SSH, _('SSH (without password)') )
85## 'dummy' : (dummytools.Dummy, _('Dummy') )
86 }
87
88 MOUNT_ROOT = '/tmp/backintime'
89
90 SSH_CIPHERS = {'default': _('Default'),
91 'aes128-ctr': _('AES128-CTR'),
92 'aes192-ctr': _('AES192-CTR'),
93 'aes256-ctr': _('AES256-CTR'),
94 'arcfour256': _('ARCFOUR256'),
95 'arcfour128': _('ARCFOUR128'),
96 'aes128-cbc': _('AES128-CBC'),
97 '3des-cbc': _('3DES-CBC'),
98 'blowfish-cbc': _('Blowfish-CBC'),
99 'cast128-cbc': _('Cast128-CBC'),
100 'aes192-cbc': _('AES192-CBC'),
101 'aes256-cbc': _('AES256-CBC'),
102 'arcfour': _('ARCFOUR') }
75103
76 def __init__( self ):104 def __init__( self ):
77 configfile.ConfigFileWithProfiles.__init__( self, _('Main profile') )105 configfile.ConfigFileWithProfiles.__init__( self, _('Main profile') )
@@ -179,6 +207,8 @@
179207
180 self.set_int_value( 'config.version', self.CONFIG_VERSION )208 self.set_int_value( 'config.version', self.CONFIG_VERSION )
181 self.save()209 self.save()
210
211 self.current_hash_id = 'local'
182212
183 def save( self ):213 def save( self ):
184 configfile.ConfigFile.save( self, self._LOCAL_CONFIG_PATH )214 configfile.ConfigFile.save( self, self._LOCAL_CONFIG_PATH )
@@ -254,8 +284,22 @@
254284
255 return user285 return user
256286
257 def get_snapshots_path( self, profile_id = None ):287 def get_pid(self):
258 return self.get_profile_str_value( 'snapshots.path', '', profile_id )288 return str(os.getpid())
289
290 def get_host(self):
291 return socket.gethostname()
292
293 def get_snapshots_path( self, profile_id = None, mode = None, tmp_mount = False ):
294 if mode is None:
295 mode = self.get_snapshots_mode(profile_id)
296 if self.SNAPSHOT_MODES[mode][0] == None:
297 #no mount needed
298 return self.get_profile_str_value( 'snapshots.path', '', profile_id )
299 else:
300 #mode need to be mounted; return mountpoint
301 symlink = self.get_snapshots_symlink(profile_id = profile_id, tmp_mount = tmp_mount)
302 return os.path.join(self.MOUNT_ROOT, self.get_user(), symlink)
259303
260 def get_snapshots_full_path( self, profile_id = None, version = None ):304 def get_snapshots_full_path( self, profile_id = None, version = None ):
261 '''Returns the full path for the snapshots: .../backintime/machine/user/profile_id/'''305 '''Returns the full path for the snapshots: .../backintime/machine/user/profile_id/'''
@@ -268,7 +312,7 @@
268 host, user, profile = self.get_host_user_profile( profile_id )312 host, user, profile = self.get_host_user_profile( profile_id )
269 return os.path.join( self.get_snapshots_path( profile_id ), 'backintime', host, user, profile ) 313 return os.path.join( self.get_snapshots_path( profile_id ), 'backintime', host, user, profile )
270314
271 def set_snapshots_path( self, value, profile_id = None ):315 def set_snapshots_path( self, value, profile_id = None, mode = None ):
272 """Sets the snapshot path to value, initializes, and checks it"""316 """Sets the snapshot path to value, initializes, and checks it"""
273 if len( value ) <= 0:317 if len( value ) <= 0:
274 return False318 return False
@@ -278,6 +322,9 @@
278 # tjoep322 # tjoep
279 # return False323 # return False
280 profile_id = self.get_current_profile()324 profile_id = self.get_current_profile()
325
326 if mode is None:
327 mode = self.get_snapshots_mode( profile_id )
281328
282 if not os.path.isdir( value ):329 if not os.path.isdir( value ):
283 self.notify_error( _( '%s is not a folder !' ) % value )330 self.notify_error( _( '%s is not a folder !' ) % value )
@@ -312,8 +359,100 @@
312 return False359 return False
313 360
314 os.rmdir( check_path )361 os.rmdir( check_path )
315 self.set_profile_str_value( 'snapshots.path', value, profile_id )362 if self.SNAPSHOT_MODES[mode][0] is None:
316 return True363 self.set_profile_str_value( 'snapshots.path', value, profile_id )
364 return True
365
366 def get_snapshots_mode( self, profile_id = None ):
367 return self.get_profile_str_value( 'snapshots.mode', 'local', profile_id )
368
369 def set_snapshots_mode( self, value, profile_id = None ):
370 self.set_profile_str_value( 'snapshots.mode', value, profile_id )
371
372 def get_snapshots_symlink(self, profile_id = None, tmp_mount = False):
373 if profile_id is None:
374 profile_id = self.current_profile_id
375 symlink = '%s_%s' % (profile_id, self.get_pid())
376 if tmp_mount:
377 symlink = 'tmp_%s' % symlink
378 return symlink
379
380 def set_current_hash_id(self, hash_id):
381 self.current_hash_id = hash_id
382
383 def get_hash_collision(self):
384 return self.get_int_value( 'global.hash_collision', 0 )
385
386 def increment_hash_collision(self):
387 value = self.get_hash_collision() + 1
388 self.set_int_value( 'global.hash_collision', value )
389
390 def get_snapshots_path_ssh( self, profile_id = None ):
391 return self.get_profile_str_value( 'snapshots.ssh.path', './', profile_id )
392
393 def get_snapshots_full_path_ssh( self, profile_id = None, version = None ):
394 '''Returns the full path for the snapshots: .../backintime/machine/user/profile_id/'''
395 if version is None:
396 version = self.get_int_value( 'config.version', self.CONFIG_VERSION )
397
398 if version < 4:
399 return os.path.join( self.get_snapshots_path_ssh( profile_id ), 'backintime' )
400 else:
401 host, user, profile = self.get_host_user_profile( profile_id )
402 return os.path.join( self.get_snapshots_path_ssh( profile_id ), 'backintime', host, user, profile )
403
404 def set_snapshots_path_ssh( self, value, profile_id = None ):
405 self.set_profile_str_value( 'snapshots.ssh.path', value, profile_id )
406 return True
407
408 def get_ssh_host( self, profile_id = None ):
409 return self.get_profile_str_value( 'snapshots.ssh.host', '', profile_id )
410
411 def set_ssh_host( self, value, profile_id = None ):
412 self.set_profile_str_value( 'snapshots.ssh.host', value, profile_id )
413
414 def get_ssh_port( self, profile_id = None ):
415 return self.get_profile_int_value( 'snapshots.ssh.port', '22', profile_id )
416
417 def set_ssh_port( self, value, profile_id = None ):
418 self.set_profile_int_value( 'snapshots.ssh.port', value, profile_id )
419
420 def get_ssh_cipher( self, profile_id = None ):
421 return self.get_profile_str_value( 'snapshots.ssh.cipher', 'default', profile_id )
422
423 def set_ssh_cipher( self, value, profile_id = None ):
424 self.set_profile_str_value( 'snapshots.ssh.cipher', value, profile_id )
425
426 def get_ssh_user( self, profile_id = None ):
427 return self.get_profile_str_value( 'snapshots.ssh.user', self.get_user(), profile_id )
428
429 def set_ssh_user( self, value, profile_id = None ):
430 self.set_profile_str_value( 'snapshots.ssh.user', value, profile_id )
431
432 def get_ssh_host_port_user_path(self, profile_id = None ):
433 host = self.get_ssh_host(profile_id)
434 port = self.get_ssh_port(profile_id)
435 user = self.get_ssh_user(profile_id)
436 path = self.get_snapshots_path_ssh(profile_id)
437 return (host, port, user, path)
438
439## def get_dummy_host( self, profile_id = None ):
440## return self.get_profile_str_value( 'snapshots.dummy.host', '', profile_id )
441##
442## def set_dummy_host( self, value, profile_id = None ):
443## self.set_profile_str_value( 'snapshots.dummy.host', value, profile_id )
444##
445## def get_dummy_port( self, profile_id = None ):
446## return self.get_profile_int_value( 'snapshots.dummy.port', '22', profile_id )
447##
448## def set_dummy_port( self, value, profile_id = None ):
449## self.set_profile_int_value( 'snapshots.dummy.port', value, profile_id )
450##
451## def get_dummy_user( self, profile_id = None ):
452## return self.get_profile_str_value( 'snapshots.dummy.user', self.get_user(), profile_id )
453##
454## def set_dummy_user( self, value, profile_id = None ):
455## self.set_profile_str_value( 'snapshots.dummy.user', value, profile_id )
317456
318 def get_auto_host_user_profile( self, profile_id = None ):457 def get_auto_host_user_profile( self, profile_id = None ):
319 return self.get_profile_bool_value( 'snapshots.path.auto', True, profile_id )458 return self.get_profile_bool_value( 'snapshots.path.auto', True, profile_id )
@@ -494,6 +633,12 @@
494 def set_automatic_backup_weekday( self, value, profile_id = None ):633 def set_automatic_backup_weekday( self, value, profile_id = None ):
495 self.set_profile_int_value( 'snapshots.automatic_backup_weekday', value, profile_id )634 self.set_profile_int_value( 'snapshots.automatic_backup_weekday', value, profile_id )
496635
636 def get_custom_backup_time( self, profile_id = None ):
637 return self.get_profile_str_value( 'snapshots.custom_backup_time', '8,12,18,23', profile_id )
638
639 def set_custom_backup_time( self, value, profile_id = None ):
640 self.set_profile_str_value( 'snapshots.custom_backup_time', value, profile_id )
641
497 #def get_per_directory_schedule( self, profile_id = None ):642 #def get_per_directory_schedule( self, profile_id = None ):
498 # return self.get_profile_bool_value( 'snapshots.expert.per_directory_schedule', False, profile_id )643 # return self.get_profile_bool_value( 'snapshots.expert.per_directory_schedule', False, profile_id )
499644
@@ -816,6 +961,8 @@
816 cron_line = 'echo "{msg}\n*/10 * * * * {cmd}"'961 cron_line = 'echo "{msg}\n*/10 * * * * {cmd}"'
817 if self.HOUR == backup_mode:962 if self.HOUR == backup_mode:
818 cron_line = 'echo "{msg}\n0 * * * * {cmd}"'963 cron_line = 'echo "{msg}\n0 * * * * {cmd}"'
964 if self.CUSTOM_HOUR == backup_mode:
965 cron_line = 'echo "{msg}\n0 ' + self.get_custom_backup_time( profile_id ) + ' * * * {cmd}"'
819 elif self.DAY == backup_mode:966 elif self.DAY == backup_mode:
820 cron_line = "echo \"{msg}\n%s %s * * * {cmd}\"" % (minute, hour)967 cron_line = "echo \"{msg}\n%s %s * * * {cmd}\"" % (minute, hour)
821 elif self.WEEK == backup_mode:968 elif self.WEEK == backup_mode:
822969
=== modified file 'common/configfile.py'
--- common/configfile.py 2010-03-05 20:23:32 +0000
+++ common/configfile.py 2012-10-23 19:40:24 +0000
@@ -347,4 +347,3 @@
347347
348 def set_profile_bool_value( self, key, value, profile_id = None ):348 def set_profile_bool_value( self, key, value, profile_id = None ):
349 self.set_bool_value( self._get_profile_key_( key, profile_id ), value )349 self.set_bool_value( self._get_profile_key_( key, profile_id ), value )
350
351350
=== modified file 'common/debian_specific/control'
--- common/debian_specific/control 2012-03-06 21:23:31 +0000
+++ common/debian_specific/control 2012-10-23 19:40:24 +0000
@@ -6,7 +6,7 @@
6Maintainer: BIT Team <bit-team@lists.launchpad.net>6Maintainer: BIT Team <bit-team@lists.launchpad.net>
7Homepage: http://backintime.le-web.org7Homepage: http://backintime.le-web.org
8Architecture: all8Architecture: all
9Depends: python, rsync, cron9Depends: python, rsync, cron, openssh-client, sshfs
10Recommends: powermgmt-base10Recommends: powermgmt-base
11Conflicts: backintime11Conflicts: backintime
12Replaces: backintime12Replaces: backintime
1313
=== added file 'common/dummytools.py'
--- common/dummytools.py 1970-01-01 00:00:00 +0000
+++ common/dummytools.py 2012-10-23 19:40:24 +0000
@@ -0,0 +1,105 @@
1# Copyright (c) 2012 Germar Reitze
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16
17import gettext
18
19import config
20import mount
21
22_=gettext.gettext
23
24class Dummy(mount.MountControl):
25 """
26 This is a template for mounting services. For simple mount services
27 all you need to do is:
28 - add your settings in gnome|kde/settingsdialog.py (search for the dummy examples)
29 - add settings in gnome/settingsdialog.glade (copy GtkFrame 'mode_dummy')
30 - add settings in common/config.py (search for the dummy examples)
31 - modify a copy of this file
32
33 Please use self.mountpoint as your local mountpoint.
34 This class inherit from mount.MountControl. All methodes from MountControl can
35 be used exactly like they were in this class.
36 Methodes from MountControl also can be overriden in here if you need
37 something different."""
38 def __init__(self, cfg = None, profile_id = None, hash_id = None, tmp_mount = False, **kwargs):
39 self.config = cfg
40 if self.config is None:
41 self.config = config.Config()
42
43 self.profile_id = profile_id
44 if not self.profile_id:
45 self.profile_id = self.config.get_current_profile()
46
47 self.tmp_mount = tmp_mount
48 self.hash_id = hash_id
49
50 #init MountControl
51 mount.MountControl.__init__(self)
52
53 self.all_kwargs = {}
54
55 #First we need to map the settings.
56 #If <arg> is in kwargs (e.g. if this class is called with dummytools.Dummy(<arg> = <value>)
57 #this will map self.<arg> to kwargs[<arg>]; else self.<arg> = <default> from config
58 #e.g. self.setattr_kwargs(<arg>, <default>, **kwargs)
59 self.setattr_kwargs('mode', self.config.get_snapshots_mode(self.profile_id), **kwargs)
60 self.setattr_kwargs('hash_collision', self.config.get_hash_collision(), **kwargs)
61 #start editing from here---------------------------------------------------------
62 self.setattr_kwargs('user', self.config.get_dummy_user(self.profile_id), **kwargs)
63 self.setattr_kwargs('host', self.config.get_dummy_host(self.profile_id), **kwargs)
64 self.setattr_kwargs('port', self.config.get_dummy_port(self.profile_id), **kwargs)
65
66 self.set_default_args()
67
68 #if self.mountpoint is not the remote snapshot path you can specify
69 #a subfolder of self.mountpoint for the symlink
70 self.symlink_subfolder = None
71
72 self.log_command = '%s: %s@%s' % (self.mode, self.user, self.host)
73
74 def _mount(self):
75 """mount the service"""
76 #implement your mountprocess here
77 pass
78
79 def _umount(self):
80 """umount the service"""
81 #implement your unmountprocess here
82 pass
83
84 def pre_mount_check(self, first_run = False):
85 """check what ever conditions must be given for the mount to be done successful
86 raise MountException( _('Error discription') ) if service can not mount
87 return True if everything is okay
88 all pre|post_[u]mount_check can also be used to prepare things or clean up"""
89 return True
90
91 def post_mount_check(self):
92 """check if mount was successful
93 raise MountException( _('Error discription') ) if not"""
94 return True
95
96 def pre_umount_check(self):
97 """check if service is safe to umount
98 raise MountException( _('Error discription') ) if not"""
99 return True
100
101 def post_umount_check(self):
102 """check if umount successful
103 raise MountException( _('Error discription') ) if not"""
104 return True
105
0\ No newline at end of file106\ No newline at end of file
1107
=== modified file 'common/man/C/backintime.1.gz'
2Binary files common/man/C/backintime.1.gz 2012-03-06 21:23:31 +0000 and common/man/C/backintime.1.gz 2012-10-23 19:40:24 +0000 differ108Binary files common/man/C/backintime.1.gz 2012-03-06 21:23:31 +0000 and common/man/C/backintime.1.gz 2012-10-23 19:40:24 +0000 differ
=== added file 'common/mount.py'
--- common/mount.py 1970-01-01 00:00:00 +0000
+++ common/mount.py 2012-10-23 19:40:24 +0000
@@ -0,0 +1,421 @@
1# Copyright (c) 2012 Germar Reitze
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16
17import os
18import subprocess
19import socket
20import json
21import gettext
22from zlib import crc32
23from time import sleep
24
25import config
26import logger
27
28_=gettext.gettext
29
30class MountException(Exception):
31 pass
32
33class HashCollision(Exception):
34 pass
35
36class Mount(object):
37 def __init__(self, cfg = None, profile_id = None, tmp_mount = False):
38 self.config = cfg
39 if self.config is None:
40 self.config = config.Config()
41
42 self.profile_id = profile_id
43 if self.profile_id is None:
44 self.profile_id = self.config.get_current_profile()
45
46 self.tmp_mount = tmp_mount
47
48 def mount(self, mode = None, check = True, **kwargs):
49 if mode is None:
50 mode = self.config.get_snapshots_mode(self.profile_id)
51
52 if self.config.SNAPSHOT_MODES[mode][0] is None:
53 #mode doesn't need to mount
54 return 'local'
55 else:
56 while True:
57 try:
58 mounttools = self.config.SNAPSHOT_MODES[mode][0]
59 tools = mounttools(cfg = self.config, profile_id = self.profile_id, tmp_mount = self.tmp_mount, mode = mode, **kwargs)
60 return tools.mount(check = check)
61 except HashCollision as ex:
62 logger.warning(str(ex))
63 del tools
64 check = False
65 continue
66 break
67
68 def umount(self, hash_id = None):
69 if hash_id is None:
70 hash_id = self.config.current_hash_id
71 if hash_id == 'local':
72 #mode doesn't need to umount
73 return
74 else:
75 umount_info = os.path.join(self.config.MOUNT_ROOT, self.config.get_user(), 'mnt', hash_id, 'umount')
76 with open(umount_info, 'r') as f:
77 data_string = f.read()
78 f.close()
79 kwargs = json.loads(data_string)
80 mode = kwargs.pop('mode')
81 mounttools = self.config.SNAPSHOT_MODES[mode][0]
82 tools = mounttools(cfg = self.config, profile_id = self.profile_id, tmp_mount = self.tmp_mount, mode = mode, hash_id = hash_id, **kwargs)
83 tools.umount()
84
85 def pre_mount_check(self, mode = None, first_run = False, **kwargs):
86 """called by SettingsDialog.save_profile() to check
87 if settings are correct before saving"""
88 if mode is None:
89 mode = self.config.get_snapshots_mode(self.profile_id)
90
91 if self.config.SNAPSHOT_MODES[mode][0] is None:
92 #mode doesn't need to mount
93 return True
94 else:
95 mounttools = self.config.SNAPSHOT_MODES[mode][0]
96 tools = mounttools(cfg = self.config, profile_id = self.profile_id, tmp_mount = self.tmp_mount, mode = mode, **kwargs)
97 return tools.pre_mount_check(first_run)
98
99 def remount(self, new_profile_id, mode = None, hash_id = None, **kwargs):
100 """mode <= new profile
101 kwargs <= new profile
102 hash_id <= old profile
103 self.profile_id <= old profile"""
104 if mode is None:
105 mode = self.config.get_snapshots_mode(new_profile_id)
106 if hash_id is None:
107 hash_id = self.config.current_hash_id
108
109 if self.config.SNAPSHOT_MODES[mode][0] is None:
110 #new profile don't need to mount.
111 self.umount(hash_id = hash_id)
112 return 'local'
113
114 if hash_id == 'local':
115 #old profile don't need to umount.
116 self.profile_id = new_profile_id
117 return self.mount(mode = mode, **kwargs)
118
119 mounttools = self.config.SNAPSHOT_MODES[mode][0]
120 tools = mounttools(cfg = self.config, profile_id = new_profile_id, tmp_mount = self.tmp_mount, mode = mode, **kwargs)
121 if tools.compare_remount(hash_id):
122 #profiles uses the same settings. just swap the symlinks
123 tools.remove_symlink(profile_id = self.profile_id)
124 tools.set_symlink(profile_id = new_profile_id, hash_id = hash_id)
125 return hash_id
126 else:
127 #profiles are different. we need to umount and mount again
128 self.umount(hash_id = hash_id)
129 self.profile_id = new_profile_id
130 return self.mount(mode = mode, **kwargs)
131
132class MountControl(object):
133 def __init__(self):
134 self.local_host = self.config.get_host()
135 self.local_user = self.config.get_user()
136 self.pid = self.config.get_pid()
137
138 def set_default_args(self):
139 #self.destination should contain all arguments that are nessesary for mount.
140 args = self.all_kwargs.keys()
141 self.destination = '%s:' % self.all_kwargs['mode']
142 args.remove('mode')
143 args.sort()
144 for arg in args:
145 self.destination += ' %s' % self.all_kwargs[arg]
146
147 #unique id for every different mount settings. Similar settings even in
148 #different profiles will generate the same hash_id and so share the same
149 #mountpoint
150 if self.hash_id is None:
151 self.hash_id = self.hash(self.destination)
152
153 self.mount_root = self.config.MOUNT_ROOT
154 self.mount_user_path = os.path.join(self.mount_root, self.local_user)
155 self.snapshots_path = self.config.get_snapshots_path(profile_id = self.profile_id, mode = self.mode, tmp_mount = self.tmp_mount)
156
157 self.hash_id_path = self.get_hash_id_path()
158 self.mountpoint = self.get_mountpoint()
159 self.lock_path = self.get_lock_path()
160 self.umount_info = self.get_umount_info()
161
162 def mount(self, check = True):
163 self.create_mountstructure()
164 self.mountprocess_lock_acquire()
165 try:
166 if self.is_mounted():
167 if not self.compare_umount_info():
168 #We probably have a hash collision
169 self.config.increment_hash_collision()
170 raise HashCollision( _('Hash collision occurred in hash_id %s. Incrementing global value hash_collision and try again.') % self.hash_id)
171 logger.info('Mountpoint %s is already mounted' % self.mountpoint)
172 else:
173 if check:
174 self.pre_mount_check()
175 self._mount()
176 self.post_mount_check()
177 logger.info('mount %s on %s' % (self.log_command, self.mountpoint))
178 self.write_umount_info()
179 except Exception:
180 raise
181 else:
182 self.set_mount_lock()
183 self.set_symlink()
184 finally:
185 self.mountprocess_lock_release()
186 return self.hash_id
187
188 def umount(self):
189 self.mountprocess_lock_acquire()
190 try:
191 if not os.path.isdir(self.hash_id_path):
192 logger.info('Mountpoint %s does not exist.' % self.hash_id_path)
193 else:
194 if not self.is_mounted():
195 logger.info('Mountpoint %s is not mounted' % self.hash_id_path)
196 else:
197 if self.check_mount_lock():
198 logger.info('Mountpoint %s still in use. Keep mounted' % self.mountpoint)
199 else:
200 self.pre_umount_check()
201 self._umount()
202 self.post_umount_check()
203 if len(os.listdir(self.mountpoint)) > 0:
204 logger.warning('Mountpoint %s not empty after unmount' % self.mountpoint)
205 else:
206 logger.info('unmount %s from %s' % (self.log_command, self.mountpoint))
207 except Exception:
208 raise
209 else:
210 self.del_mount_lock()
211 self.remove_symlink()
212 finally:
213 self.mountprocess_lock_release()
214
215 def is_mounted(self):
216 """return True if path is is already mounted"""
217 try:
218 subprocess.check_call(['mountpoint', self.mountpoint], stdout=open(os.devnull, 'w'))
219 except subprocess.CalledProcessError:
220 if len(os.listdir(self.mountpoint)) > 0:
221 raise MountException( _('mountpoint %s not empty.') % self.mountpoint)
222 return False
223 else:
224 return True
225
226 def create_mountstructure(self):
227 """ folder structure in /tmp/backintime/<user>/:
228 mnt/ <= used for mount points
229 <pid>.lock <= mountprocess lock that will prevent different
230 processes modifying mountpoints at one time
231 <hash_id>/ <= will be shared by all profiles with the same mount settings
232 mountpoint/ <= real mountpoint
233 umount <= json file with all nessesary args for unmount
234 locks/ <= for each process you have a <pid>.lock file
235 <profile id>_<pid>/ <= sym-link to the right path. return by config.get_snapshots_path
236 (can be ../mnt/<hash_id>/mount_point for ssh or
237 ../mnt/<hash_id>/<HOST>/<SHARE> for fusesmb ...)
238 tmp_<profile id>_<pid>/ <= sym-link for testing mountpoints in settingsdialog
239 """
240 self.mkdir(self.mount_root, 0777, force_chmod = True)
241 self.mkdir(self.mount_user_path, 0700)
242 self.mkdir(os.path.join(self.mount_user_path, 'mnt'), 0700)
243 self.mkdir(self.hash_id_path, 0700)
244 self.mkdir(self.mountpoint, 0700)
245 self.mkdir(self.lock_path, 0700)
246
247 def mkdir(self, path, mode = 0777, force_chmod = False):
248 if not os.path.isdir(path):
249 os.mkdir(path, mode)
250 if force_chmod:
251 #hack: debian and ubuntu won't set go+w on mkdir in tmp
252 os.chmod(path, mode)
253
254 def mountprocess_lock_acquire(self, timeout = 60):
255 """block while an other process is mounting or unmounting"""
256 lock_path = os.path.join(self.mount_user_path, 'mnt')
257 lock_suffix = '.lock'
258 lock = self.pid + lock_suffix
259 count = 0
260 while self.check_locks(lock_path, lock_suffix):
261 count += 1
262 if count == timeout:
263 raise MountException( _('Mountprocess lock timeout') )
264 sleep(1)
265
266 with open(os.path.join(lock_path, lock), 'w') as f:
267 f.write(self.pid)
268 f.close()
269
270 def mountprocess_lock_release(self):
271 lock_path = os.path.join(self.mount_user_path, 'mnt')
272 lock_suffix = '.lock'
273 lock = os.path.join(lock_path, self.pid + lock_suffix)
274 if os.path.exists(lock):
275 os.remove(lock)
276
277 def set_mount_lock(self):
278 """lock mount for this process"""
279 if self.tmp_mount:
280 lock_suffix = '.tmp.lock'
281 else:
282 lock_suffix = '.lock'
283 lock = self.pid + lock_suffix
284 with open(os.path.join(self.lock_path, lock), 'w') as f:
285 f.write(self.pid)
286 f.close()
287
288 def check_mount_lock(self):
289 """return True if mount is locked by other processes"""
290 lock_suffix = '.lock'
291 return self.check_locks(self.lock_path, lock_suffix)
292
293 def del_mount_lock(self):
294 """remove mount lock for this process"""
295 if self.tmp_mount:
296 lock_suffix = '.tmp.lock'
297 else:
298 lock_suffix = '.lock'
299 lock = os.path.join(self.lock_path, self.pid + lock_suffix)
300 if os.path.exists(lock):
301 os.remove(lock)
302
303 def check_process_alive(self, pid):
304 """check if process is still alive"""
305 if os.path.exists(os.path.join('/proc', pid)):
306 return True
307 return False
308
309 def check_locks(self, path, lock_suffix):
310 """return True if there are active locks"""
311 for file in os.listdir(path):
312 if not file[-len(lock_suffix):] == lock_suffix:
313 continue
314 is_tmp = False
315 if os.path.basename(file)[-len(lock_suffix)-len('.tmp'):-len(lock_suffix)] == '.tmp':
316 is_tmp = True
317 if is_tmp:
318 lock_pid = os.path.basename(file)[:-len('.tmp')-len(lock_suffix)]
319 else:
320 lock_pid = os.path.basename(file)[:-len(lock_suffix)]
321 if lock_pid == self.pid:
322 if is_tmp == self.tmp_mount:
323 continue
324 if self.check_process_alive(lock_pid):
325 return True
326 else:
327 #clean up
328 os.remove(os.path.join(path, file))
329 for symlink in os.listdir(self.mount_user_path):
330 if symlink.endswith('_%s' % lock_pid):
331 os.remove(os.path.join(self.mount_user_path, symlink))
332 return False
333
334 def setattr_kwargs(self, arg, default, **kwargs):
335 """if kwargs[arg] exist set self.<arg> to kwargs[arg]
336 else set self.<arg> to default which should be the value from config"""
337 if arg in kwargs:
338 value = kwargs[arg]
339 else:
340 value = default
341 setattr(self, arg, value)
342 #make dictionary with all used args for umount
343 self.all_kwargs[arg] = value
344
345 def write_umount_info(self):
346 """dump dictionary self.all_kwargs to umount_info file"""
347 data_string = json.dumps(self.all_kwargs)
348 with open(self.umount_info, 'w') as f:
349 f.write(data_string)
350 f.close
351
352 def read_umount_info(self, umount_info = None):
353 """load dictionary kwargs from umount_info file"""
354 if umount_info is None:
355 umount_info = self.umount_info
356 with open(umount_info, 'r') as f:
357 data_string = f.read()
358 f.close()
359 return json.loads(data_string)
360
361 def compare_umount_info(self, umount_info = None):
362 """just in case of hash collisions in <hash_id> we compare self.all_kwargs
363 with the old saved in umount_info file.
364 return True if both are identical"""
365 #run self.all_kwargs through json first
366 current_kwargs = json.loads(json.dumps(self.all_kwargs))
367 saved_kwargs = self.read_umount_info(umount_info)
368 if not len(current_kwargs) == len(saved_kwargs):
369 return False
370 for arg in current_kwargs.keys():
371 if not arg in saved_kwargs.keys():
372 return False
373 if not current_kwargs[arg] == saved_kwargs[arg]:
374 return False
375 return True
376
377 def compare_remount(self, old_hash_id):
378 """return True if profiles are identiacal and we don't need to remount"""
379 if old_hash_id == self.hash_id:
380 return self.compare_umount_info(self.get_umount_info(old_hash_id))
381 return False
382
383 def set_symlink(self, profile_id = None, hash_id = None, tmp_mount = None):
384 if profile_id is None:
385 profile_id = self.profile_id
386 if hash_id is None:
387 hash_id = self.hash_id
388 if tmp_mount is None:
389 tmp_mount = self.tmp_mount
390 dst = self.config.get_snapshots_path(profile_id = profile_id, mode = self.mode, tmp_mount = tmp_mount)
391 mountpoint = self.get_mountpoint(hash_id)
392 if self.symlink_subfolder is None:
393 src = mountpoint
394 else:
395 src = os.path.join(mountpoint, self.symlink_subfolder)
396 os.symlink(src, dst)
397
398 def remove_symlink(self, profile_id = None, tmp_mount = None):
399 if profile_id is None:
400 profile_id = self.profile_id
401 if tmp_mount is None:
402 tmp_mount = self.tmp_mount
403 os.remove(self.config.get_snapshots_path(profile_id = profile_id, mode = self.mode, tmp_mount = tmp_mount))
404
405 def hash(self, str):
406 """return a hex crc32 hash of str"""
407 return('%X' % (crc32(str) & 0xFFFFFFFF))
408
409 def get_hash_id_path(self, hash_id = None):
410 if hash_id is None:
411 hash_id = self.hash_id
412 return os.path.join(self.mount_user_path, 'mnt', self.hash_id)
413
414 def get_mountpoint(self, hash_id = None):
415 return os.path.join(self.get_hash_id_path(hash_id), 'mountpoint')
416
417 def get_lock_path(self, hash_id = None):
418 return os.path.join(self.get_hash_id_path(hash_id), 'locks')
419
420 def get_umount_info(self, hash_id = None):
421 return os.path.join(self.get_hash_id_path(hash_id), 'umount')
0422
=== modified file 'common/snapshots.py'
--- common/snapshots.py 2012-03-05 10:06:01 +0000
+++ common/snapshots.py 2012-10-23 19:40:24 +0000
@@ -27,6 +27,7 @@
27import pwd27import pwd
28import grp28import grp
29import socket29import socket
30import subprocess
3031
31import config32import config
32import configfile33import configfile
@@ -112,6 +113,11 @@
112 #print path113 #print path
113 return path114 return path
114115
116 def get_snapshot_path_ssh( self, date ):
117 profile_id = self.config.get_current_profile()
118 path = os.path.join( self.config.get_snapshots_full_path_ssh( profile_id ), self.get_snapshot_id( date ) )
119 return path
120
115 def get_snapshot_info_path( self, date ):121 def get_snapshot_info_path( self, date ):
116 return os.path.join( self.get_snapshot_path( date ), 'info' )122 return os.path.join( self.get_snapshot_path( date ), 'info' )
117123
@@ -132,6 +138,14 @@
132 def get_snapshot_path_to( self, snapshot_id, toPath = '/' ):138 def get_snapshot_path_to( self, snapshot_id, toPath = '/' ):
133 return os.path.join( self._get_snapshot_data_path( snapshot_id ), toPath[ 1 : ] )139 return os.path.join( self._get_snapshot_data_path( snapshot_id ), toPath[ 1 : ] )
134140
141 def _get_snapshot_data_path_ssh( self, snapshot_id ):
142 if len( snapshot_id ) <= 1:
143 return '/';
144 return os.path.join( self.get_snapshot_path_ssh( snapshot_id ), 'backup' )
145
146 def get_snapshot_path_to_ssh( self, snapshot_id, toPath = '/' ):
147 return os.path.join( self._get_snapshot_data_path_ssh( snapshot_id ), toPath[ 1 : ] )
148
135 def can_open_path( self, snapshot_id, full_path ):149 def can_open_path( self, snapshot_id, full_path ):
136 #full_path = self.get_snapshot_path_to( snapshot_id, path )150 #full_path = self.get_snapshot_path_to( snapshot_id, path )
137 if not os.path.exists( full_path ):151 if not os.path.exists( full_path ):
@@ -452,6 +466,18 @@
452 def restore( self, snapshot_id, path, callback = None, restore_to = '' ):466 def restore( self, snapshot_id, path, callback = None, restore_to = '' ):
453 if restore_to.endswith('/'):467 if restore_to.endswith('/'):
454 restore_to = restore_to[ : -1 ]468 restore_to = restore_to[ : -1 ]
469
470 #ssh
471 ssh = False
472 if self.config.get_snapshots_mode() == 'ssh':
473 ssh = True
474 (ssh_host, ssh_port, ssh_user, ssh_path) = self.config.get_ssh_host_port_user_path()
475 ssh_cipher = self.config.get_ssh_cipher()
476 if ssh_cipher == 'default':
477 ssh_cipher_suffix = ''
478 else:
479 ssh_cipher_suffix = '-c %s' % ssh_cipher
480 rsync_ssh_suffix = '--rsh="ssh -p %s %s" "%s@%s:' % ( str(ssh_port), ssh_cipher_suffix, ssh_user, ssh_host )
455481
456 logger.info( "Restore: %s to: %s" % (path, restore_to) )482 logger.info( "Restore: %s to: %s" % (path, restore_to) )
457483
@@ -466,6 +492,8 @@
466 cmd = cmd + "--backup --suffix=%s " % backup_suffix492 cmd = cmd + "--backup --suffix=%s " % backup_suffix
467 #cmd = cmd + '--chmod=+w '493 #cmd = cmd + '--chmod=+w '
468 src_base = self.get_snapshot_path_to( snapshot_id )494 src_base = self.get_snapshot_path_to( snapshot_id )
495 if ssh:
496 src_base = self.get_snapshot_path_to_ssh( snapshot_id )
469 src_path = path497 src_path = path
470 src_delta = 0498 src_delta = 0
471 if len(restore_to) > 0:499 if len(restore_to) > 0:
@@ -475,17 +503,24 @@
475 items = os.path.split(src_path)503 items = os.path.split(src_path)
476 aux = items[0]504 aux = items[0]
477 if aux.startswith('/'):505 if aux.startswith('/'):
478 aux = aux[1:] 506 aux = aux[1:]
479 src_base = os.path.join(src_base, aux) + '/'507 if len(aux) > 0: #bugfix: restore system root ended in <src_base>//.<src_path>
508 src_base = os.path.join(src_base, aux) + '/'
480 src_path = '/' + items[1]509 src_path = '/' + items[1]
481 src_delta = len(items[0])510 if items[0] == '/':
511 src_delta = 0
512 else:
513 src_delta = len(items[0])
482 514
483 #print "src_base: %s" % src_base515 #print "src_base: %s" % src_base
484 #print "src_path: %s" % src_path516 #print "src_path: %s" % src_path
485 #print "src_delta: %s" % src_delta517 #print "src_delta: %s" % src_delta
486 #print "snapshot_id: %s" % snapshot_id 518 #print "snapshot_id: %s" % snapshot_id
487 519
488 cmd = cmd + "\"%s.%s\" %s" % ( src_base, src_path, restore_to + '/' )520 if ssh:
521 cmd = cmd + rsync_ssh_suffix + "%s.%s\" %s" % ( src_base, src_path, restore_to + '/' )
522 else:
523 cmd = cmd + "\"%s.%s\" %s" % ( src_base, src_path, restore_to + '/' )
489 self.restore_callback( callback, True, cmd )524 self.restore_callback( callback, True, cmd )
490 self._execute( cmd, callback )525 self._execute( cmd, callback )
491526
@@ -600,13 +635,33 @@
600 def remove_snapshot( self, snapshot_id ):635 def remove_snapshot( self, snapshot_id ):
601 if len( snapshot_id ) <= 1:636 if len( snapshot_id ) <= 1:
602 return637 return
603638 #ssh
604 path = self.get_snapshot_path( snapshot_id )639 profile_id = self.config.get_current_profile()
605 #cmd = "chmod -R u+rwx \"%s\"" % path640 ssh = False
606 cmd = "find \"%s\" -type d -exec chmod u+wx {} \\;" % path #Debian patch641 if self.config.get_snapshots_mode() == 'ssh':
607 self._execute( cmd )642 ssh = True
608 cmd = "rm -rfv \"%s\"" % path643 (ssh_host, ssh_port, ssh_user, ssh_path) = self.config.get_ssh_host_port_user_path(profile_id)
609 self._execute( cmd )644 ssh_cipher = self.config.get_ssh_cipher(profile_id)
645 if ssh_cipher == 'default':
646 ssh_cipher_suffix = ''
647 else:
648 ssh_cipher_suffix = '-c %s' % ssh_cipher
649 cmd_ssh = 'ssh -p %s %s %s@%s ' % ( ssh_port, ssh_cipher_suffix, ssh_user, ssh_host )
650
651 if not ssh:
652 path = self.get_snapshot_path( snapshot_id )
653 #cmd = "chmod -R u+rwx \"%s\"" % path
654 cmd = "find \"%s\" -type d -exec chmod u+wx {} \\;" % path #Debian patch
655 self._execute( cmd )
656 cmd = "rm -rf \"%s\"" % path
657 self._execute( cmd )
658 else:
659 path = self.get_snapshot_path_ssh( snapshot_id )
660 #cmd = "chmod -R u+rwx \"%s\"" % path
661 cmd = cmd_ssh + '\'find \"%s\" -type d -exec chmod u+wx \"{}\" \\;\'' % path #Debian patch
662 self._execute( cmd )
663 cmd = cmd_ssh + "rm -rf \"%s\"" % path
664 self._execute( cmd )
610665
611 def copy_snapshot( self, snapshot_id, new_folder ):666 def copy_snapshot( self, snapshot_id, new_folder ):
612 '''Copies a known snapshot to a new location'''667 '''Copies a known snapshot to a new location'''
@@ -1016,6 +1071,22 @@
1016 new_snapshot_id = 'new_snapshot'1071 new_snapshot_id = 'new_snapshot'
1017 new_snapshot_path = self.get_snapshot_path( new_snapshot_id )1072 new_snapshot_path = self.get_snapshot_path( new_snapshot_id )
1018 1073
1074 #ssh
1075 profile_id = self.config.get_current_profile()
1076 ssh = False
1077 if self.config.get_snapshots_mode() == 'ssh':
1078 ssh = True
1079 (ssh_host, ssh_port, ssh_user, ssh_path) = self.config.get_ssh_host_port_user_path(profile_id)
1080 ssh_cipher = self.config.get_ssh_cipher(profile_id)
1081 if ssh_cipher == 'default':
1082 ssh_cipher_suffix = ''
1083 else:
1084 ssh_cipher_suffix = '-c %s' % ssh_cipher
1085 rsync_ssh_suffix = '--rsh="ssh -p %s %s" "%s@%s:' % ( str(ssh_port), ssh_cipher_suffix, ssh_user, ssh_host )
1086 cmd_ssh = 'ssh -p %s %s %s@%s ' % ( str(ssh_port), ssh_cipher_suffix, ssh_user, ssh_host )
1087 new_snapshot_path_ssh = self.get_snapshot_path_ssh( new_snapshot_id )
1088 new_snapshot_path_to_ssh = self.get_snapshot_path_to_ssh( new_snapshot_id )
1089
1019 if os.path.exists( new_snapshot_path ):1090 if os.path.exists( new_snapshot_path ):
1020 #self._execute( "find \"%s\" -type d -exec chmod +w {} \;" % new_snapshot_path )1091 #self._execute( "find \"%s\" -type d -exec chmod +w {} \;" % new_snapshot_path )
1021 #self._execute( "chmod -R u+rwx \"%s\"" % new_snapshot_path )1092 #self._execute( "chmod -R u+rwx \"%s\"" % new_snapshot_path )
@@ -1101,7 +1172,11 @@
1101 logger.info( "Compare with old snapshot: %s" % prev_snapshot_id )1172 logger.info( "Compare with old snapshot: %s" % prev_snapshot_id )
1102 1173
1103 prev_snapshot_folder = self.get_snapshot_path_to( prev_snapshot_id )1174 prev_snapshot_folder = self.get_snapshot_path_to( prev_snapshot_id )
1175 prev_snapshot_folder_ssh = self.get_snapshot_path_to_ssh( prev_snapshot_id )
1104 cmd = rsync_prefix + ' -i --dry-run --out-format="BACKINTIME: %i %n%L"' + rsync_suffix + '"' + prev_snapshot_folder + '"'1176 cmd = rsync_prefix + ' -i --dry-run --out-format="BACKINTIME: %i %n%L"' + rsync_suffix + '"' + prev_snapshot_folder + '"'
1177 if ssh:
1178 cmd = rsync_prefix + ' -i --dry-run --out-format="BACKINTIME: %i %n%L"' + rsync_suffix
1179 cmd += rsync_ssh_suffix + prev_snapshot_folder_ssh + '"'
1105 params = [ prev_snapshot_folder, False ]1180 params = [ prev_snapshot_folder, False ]
1106 #try_cmd = self._execute_output( cmd, self._exec_rsync_compare_callback, prev_snapshot_name )1181 #try_cmd = self._execute_output( cmd, self._exec_rsync_compare_callback, prev_snapshot_name )
1107 self.append_to_take_snapshot_log( '[I] ' + cmd, 3 )1182 self.append_to_take_snapshot_log( '[I] ' + cmd, 3 )
@@ -1135,21 +1210,33 @@
1135 #if force or len( ignore_folders ) == 0:1210 #if force or len( ignore_folders ) == 0:
11361211
1137 prev_snapshot_path = self.get_snapshot_path_to( prev_snapshot_id )1212 prev_snapshot_path = self.get_snapshot_path_to( prev_snapshot_id )
1213 prev_snapshot_path_ssh = self.get_snapshot_path_to_ssh( prev_snapshot_id )
11381214
1139 #make source snapshot folders rw to allow cp -al1215 #make source snapshot folders rw to allow cp -al
1140 self._execute( "find \"%s\" -type d -exec chmod u+wx {} \\;" % prev_snapshot_path ) #Debian patch1216 if not ssh:
1217 self._execute( "find \"%s\" -type d -exec chmod u+wx {} \\;" % prev_snapshot_path ) #Debian patch
1218 else:
1219 self._execute( cmd_ssh + '\'find \"%s\" -type d -exec chmod u+wx \"{}\" \\;\'' % prev_snapshot_path_ssh ) #Debian patch
11411220
1142 #clone snapshot1221 #clone snapshot
1143 cmd = "cp -aRl \"%s\"* \"%s\"" % ( prev_snapshot_path, new_snapshot_path_to )1222 cmd = "cp -aRl \"%s\"* \"%s\"" % ( prev_snapshot_path, new_snapshot_path_to )
1223 if ssh:
1224 cmd = cmd_ssh + "cp -aRl \"%s\"* \"%s\"" % ( prev_snapshot_path_ssh, new_snapshot_path_to_ssh )
1144 self.append_to_take_snapshot_log( '[I] ' + cmd, 3 )1225 self.append_to_take_snapshot_log( '[I] ' + cmd, 3 )
1145 cmd_ret_val = self._execute( cmd )1226 cmd_ret_val = self._execute( cmd )
1146 self.append_to_take_snapshot_log( "[I] returns: %s" % cmd_ret_val, 3 )1227 self.append_to_take_snapshot_log( "[I] returns: %s" % cmd_ret_val, 3 )
11471228
1148 #make source snapshot folders read-only1229 #make source snapshot folders read-only
1149 self._execute( "find \"%s\" -type d -exec chmod a-w {} \\;" % prev_snapshot_path ) #Debian patch1230 if not ssh:
1231 self._execute( "find \"%s\" -type d -exec chmod a-w {} \\;" % prev_snapshot_path ) #Debian patch
1232 else:
1233 self._execute( cmd_ssh + '\'find \"%s\" -type d -exec chmod a-w \"{}\" \\;\'' % prev_snapshot_path_ssh ) #Debian patch
11501234
1151 #make snapshot items rw to allow xopy xattr1235 #make snapshot items rw to allow xopy xattr
1152 self._execute( "chmod -R a+w \"%s\"" % new_snapshot_path )1236 if not ssh:
1237 self._execute( "chmod -R a+w \"%s\"" % new_snapshot_path )
1238 else:
1239 self._execute( cmd_ssh + "chmod -R a+w \"%s\"" % new_snapshot_path_ssh )
11531240
1154 #else:1241 #else:
1155 # for folder in include_folders:1242 # for folder in include_folders:
@@ -1165,6 +1252,8 @@
1165 #sync changed folders1252 #sync changed folders
1166 logger.info( "Call rsync to take the snapshot" )1253 logger.info( "Call rsync to take the snapshot" )
1167 cmd = rsync_prefix + ' -v ' + rsync_suffix + '"' + new_snapshot_path_to + '"'1254 cmd = rsync_prefix + ' -v ' + rsync_suffix + '"' + new_snapshot_path_to + '"'
1255 if ssh:
1256 cmd = rsync_prefix + ' -v ' + rsync_suffix + rsync_ssh_suffix + new_snapshot_path_to_ssh + '"'
1168 self.set_take_snapshot_message( 0, _('Take snapshot') )1257 self.set_take_snapshot_message( 0, _('Take snapshot') )
11691258
1170 params = [False]1259 params = [False]
@@ -1174,12 +1263,19 @@
1174 has_errors = False1263 has_errors = False
1175 if params[0]:1264 if params[0]:
1176 if not self.config.continue_on_errors():1265 if not self.config.continue_on_errors():
1177 self._execute( "find \"%s\" -type d -exec chmod u+wx {} \\;" % new_snapshot_path ) #Debian patch1266 if not ssh:
1178 self._execute( "rm -rf \"%s\"" % new_snapshot_path )1267 self._execute( "find \"%s\" -type d -exec chmod u+wx {} \\;" % new_snapshot_path ) #Debian patch
1268 self._execute( "rm -rf \"%s\"" % new_snapshot_path )
1269 else:
1270 self._execute( cmd_ssh + '\'find \"%s\" -type d -exec chmod u+wx \"{}\" \\;\'' % new_snapshot_path_ssh ) #Debian patch
1271 self._execute( cmd_ssh + "rm -rf \"%s\"" % new_snapshot_path_ssh )
11791272
1180 #fix previous snapshot: make read-only again1273 #fix previous snapshot: make read-only again
1181 if len( prev_snapshot_id ) > 0:1274 if len( prev_snapshot_id ) > 0:
1182 self._execute( "chmod -R a-w \"%s\"" % self.get_snapshot_path_to( prev_snapshot_id ) )1275 if not ssh:
1276 self._execute( "chmod -R a-w \"%s\"" % self.get_snapshot_path_to( prev_snapshot_id ) )
1277 else:
1278 self._execute( cmd_ssh + "chmod -R a-w \"%s\"" % self.get_snapshot_path_to_ssh( prev_snapshot_id ) )
11831279
1184 return [ False, True ]1280 return [ False, True ]
11851281
@@ -1199,12 +1295,34 @@
1199 path_to_explore = self.get_snapshot_path_to( new_snapshot_id ).rstrip( '/' )1295 path_to_explore = self.get_snapshot_path_to( new_snapshot_id ).rstrip( '/' )
1200 fileinfo_dict = {}1296 fileinfo_dict = {}
12011297
1202 for path, dirs, files in os.walk( path_to_explore ):1298 permission_done = False
1203 dirs.extend( files )1299 if ssh:
1204 for item in dirs:1300 path_to_explore_ssh = self.get_snapshot_path_to_ssh( new_snapshot_id ).rstrip( '/' )
1205 item_path = os.path.join( path, item )[ len( path_to_explore ) : ]1301 cmd = ['ssh', '-p', str(ssh_port)]
1206 fileinfo_dict[item_path] = 11302 if not ssh_cipher == 'default':
1207 self._save_path_info( fileinfo, item_path )1303 cmd.extend(['-c', ssh_cipher])
1304 cmd.extend(['%s@%s' % (ssh_user, ssh_host)])
1305 cmd.extend(['find', path_to_explore_ssh, '-name', '\*', '-print'])
1306
1307 find = subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
1308 output, err = find.communicate()
1309 if len(err) > 0:
1310 logger.warning('Save permission over ssh failed. Retry normal methode')
1311 else:
1312 for line in output.split('\n'):
1313 if not len(line) == 0:
1314 item_path = line[ len( path_to_explore_ssh ) : ]
1315 fileinfo_dict[item_path] = 1
1316 self._save_path_info( fileinfo, item_path )
1317 permission_done = True
1318
1319 if not permission_done:
1320 for path, dirs, files in os.walk( path_to_explore ):
1321 dirs.extend( files )
1322 for item in dirs:
1323 item_path = os.path.join( path, item )[ len( path_to_explore ) : ]
1324 fileinfo_dict[item_path] = 1
1325 self._save_path_info( fileinfo, item_path )
12081326
1209 # We now copy on forehand, so copying afterwards is not necessary anymore1327 # We now copy on forehand, so copying afterwards is not necessary anymore
1210 ##copy ignored folders1328 ##copy ignored folders
@@ -1267,7 +1385,11 @@
12671385
1268 #rename snapshot1386 #rename snapshot
1269 snapshot_path = self.get_snapshot_path( snapshot_id )1387 snapshot_path = self.get_snapshot_path( snapshot_id )
1270 os.system( "mv \"%s\" \"%s\"" % ( new_snapshot_path, snapshot_path ) )1388 snapshot_path_ssh = self.get_snapshot_path_ssh( snapshot_id )
1389 if not ssh:
1390 os.system( "mv \"%s\" \"%s\"" % ( new_snapshot_path, snapshot_path ) )
1391 else:
1392 os.system( cmd_ssh + "mv \"%s\" \"%s\"" % ( new_snapshot_path_ssh, snapshot_path_ssh ) )
1271 if not os.path.exists( snapshot_path ):1393 if not os.path.exists( snapshot_path ):
1272 logger.error( "Can't rename %s to %s" % ( new_snapshot_path, snapshot_path ) )1394 logger.error( "Can't rename %s to %s" % ( new_snapshot_path, snapshot_path ) )
1273 self.set_take_snapshot_message( 1, _('Can\'t rename %s to %s') % ( new_snapshot_path, snapshot_path ) )1395 self.set_take_snapshot_message( 1, _('Can\'t rename %s to %s') % ( new_snapshot_path, snapshot_path ) )
@@ -1275,11 +1397,17 @@
1275 return [ False, True ]1397 return [ False, True ]
12761398
1277 #make new snapshot read-only1399 #make new snapshot read-only
1278 self._execute( "chmod -R a-w \"%s\"" % snapshot_path )1400 if not ssh:
1401 self._execute( "chmod -R a-w \"%s\"" % snapshot_path )
1402 else:
1403 self._execute( cmd_ssh + "chmod -R a-w \"%s\"" % snapshot_path_ssh )
12791404
1280 #fix previous snapshot: make read-only again1405 #fix previous snapshot: make read-only again
1281 if len( prev_snapshot_id ) > 0:1406 if len( prev_snapshot_id ) > 0:
1282 self._execute( "chmod -R a-w \"%s\"" % self.get_snapshot_path_to( prev_snapshot_id ) )1407 if not ssh:
1408 self._execute( "chmod -R a-w \"%s\"" % self.get_snapshot_path_to( prev_snapshot_id ) )
1409 else:
1410 self._execute( cmd_ssh + "chmod -R a-w \"%s\"" % self.get_snapshot_path_to_ssh( prev_snapshot_id ) )
12831411
1284 return [ True, has_errors ]1412 return [ True, has_errors ]
12851413
12861414
=== added file 'common/sshtools.py'
--- common/sshtools.py 1970-01-01 00:00:00 +0000
+++ common/sshtools.py 2012-10-23 19:40:24 +0000
@@ -0,0 +1,309 @@
1# Copyright (c) 2012 Germar Reitze
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with this program; if not, write to the Free Software Foundation, Inc.,
15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16
17import os
18import grp
19import subprocess
20import gettext
21import string
22import random
23import tempfile
24from time import sleep
25
26import config
27import mount
28import logger
29import tools
30
31_=gettext.gettext
32
33class SSH(mount.MountControl):
34 """
35 Mount remote path with sshfs. The real take_snapshot process will use
36 rsync over ssh. Other commands run remote over ssh.
37 """
38 def __init__(self, cfg = None, profile_id = None, hash_id = None, tmp_mount = False, **kwargs):
39 self.config = cfg
40 if self.config is None:
41 self.config = config.Config()
42
43 self.profile_id = profile_id
44 if not self.profile_id:
45 self.profile_id = self.config.get_current_profile()
46
47 self.tmp_mount = tmp_mount
48 self.hash_id = hash_id
49
50 #init MountControl
51 mount.MountControl.__init__(self)
52
53 self.all_kwargs = {}
54
55 #First we need to map the settings.
56 self.setattr_kwargs('mode', self.config.get_snapshots_mode(self.profile_id), **kwargs)
57 self.setattr_kwargs('hash_collision', self.config.get_hash_collision(), **kwargs)
58 #start editing from here---------------------------------------------------------
59 self.setattr_kwargs('user', self.config.get_ssh_user(self.profile_id), **kwargs)
60 self.setattr_kwargs('host', self.config.get_ssh_host(self.profile_id), **kwargs)
61 self.setattr_kwargs('port', self.config.get_ssh_port(self.profile_id), **kwargs)
62 self.setattr_kwargs('path', self.config.get_snapshots_path_ssh(self.profile_id), **kwargs)
63 self.setattr_kwargs('cipher', self.config.get_ssh_cipher(self.profile_id), **kwargs)
64
65 self.set_default_args()
66
67 self.symlink_subfolder = None
68 self.user_host_path = '%s@%s:%s' % (self.user, self.host, self.path)
69 self.log_command = '%s: %s' % (self.mode, self.user_host_path)
70
71 def _mount(self):
72 """mount the service"""
73 sshfs = ['sshfs', '-p', str(self.port)]
74 if not self.cipher == 'default':
75 sshfs.extend(['-o', 'Ciphers=%s' % self.cipher])
76 sshfs.extend([self.user_host_path, self.mountpoint])
77 #bugfix: sshfs doesn't mount if locale in LC_ALL is not available on remote host
78 #LANG or other envirnoment variable are no problem.
79 env = os.environ.copy()
80 if 'LC_ALL' in env.keys():
81 env['LC_ALL'] = 'C'
82 try:
83 subprocess.check_call(sshfs, env = env)
84 except subprocess.CalledProcessError as ex:
85 raise mount.MountException( _('Can\'t mount %s') % ' '.join(sshfs))
86
87 def _umount(self):
88 """umount the service"""
89 try:
90 subprocess.check_call(['fusermount', '-u', self.mountpoint])
91 except subprocess.CalledProcessError as ex:
92 raise mount.MountException( _('Can\'t unmount sshfs %s') % self.mountpoint)
93
94 def pre_mount_check(self, first_run = False):
95 """check what ever conditions must be given for the mount to be done successful
96 raise MountException( _('Error discription') ) if service can not mount
97 return True if everything is okay
98 all pre|post_[u]mount_check can also be used to prepare things or clean up"""
99 self.check_ping_host()
100 if first_run:
101 self.check_fuse()
102 self.check_known_hosts()
103 self.check_login()
104 if first_run:
105 self.check_cipher()
106 self.check_remote_folder()
107 if first_run:
108 self.check_remote_commands()
109 return True
110
111 def post_mount_check(self):
112 """check if mount was successful
113 raise MountException( _('Error discription') ) if not"""
114 return True
115
116 def pre_umount_check(self):
117 """check if service is safe to umount
118 raise MountException( _('Error discription') ) if not"""
119 return True
120
121 def post_umount_check(self):
122 """check if umount successful
123 raise MountException( _('Error discription') ) if not"""
124 return True
125
126 def check_fuse(self):
127 """check if sshfs is installed and user is part of group fuse"""
128 if not self.pathexists('sshfs'):
129 raise mount.MountException( _('sshfs not found. Please install e.g. \'apt-get install sshfs\'') )
130 user = self.config.get_user()
131 fuse_grp_members = grp.getgrnam('fuse')[3]
132 if not user in fuse_grp_members:
133 raise mount.MountException( _('%s is not member of group \'fuse\'.\n Run \'sudo adduser %s fuse\'. To apply changes logout and login again.\nLook at \'man backintime\' for further instructions.') % (user, user))
134
135 def pathexists(self, filename):
136 """Checks if 'filename' is present in the system PATH.
137 In other words, it checks if os.execvp(filename, ...) will work.
138 shameless stolen from GnuPGInterface;)"""
139 pathenv = os.getenv("PATH")
140 path = pathenv.split(":")
141 for directory in path:
142 fullpath = os.path.join(directory, filename)
143 if (os.path.exists(fullpath)):
144 return True
145 return False
146
147 def check_login(self):
148 """check passwordless authentication to host"""
149 ssh = ['ssh', '-o', 'PreferredAuthentications=publickey']
150 ssh.extend(['-p', str(self.port), self.user + '@' + self.host])
151 ssh.extend(['echo', '"Hello"'])
152 try:
153 subprocess.check_call(ssh, stdout=open(os.devnull, 'w'))
154 except subprocess.CalledProcessError:
155 raise mount.MountException( _('Password-less authentication for %s@%s failed. Look at \'man backintime\' for further instructions.') % (self.user, self.host))
156
157 def check_cipher(self):
158 """check if both host and localhost support cipher"""
159 if not self.cipher == 'default':
160 ssh = ['ssh']
161 ssh.extend(['-o', 'Ciphers=%s' % self.cipher])
162 ssh.extend(['-p', str(self.port), self.user + '@' + self.host, 'echo', '"Hello"'])
163 err = subprocess.Popen(ssh, stdout=open(os.devnull, 'w'), stderr=subprocess.PIPE).communicate()[1]
164 if err:
165 raise mount.MountException( _('Cipher %s failed for %s:\n%s') % (self.cipher, self.host, err))
166
167 def benchmark_cipher(self, size = '40'):
168 import tempfile
169 temp = tempfile.mkstemp()[1]
170 print('create random data file')
171 subprocess.call(['dd', 'if=/dev/urandom', 'of=%s' % temp, 'bs=1M', 'count=%s' % size])
172 keys = self.config.SSH_CIPHERS.keys()
173 keys.sort()
174 for cipher in keys:
175 if cipher == 'default':
176 continue
177 print('%s:' % cipher)
178 for i in range(2):
179 subprocess.call(['scp', '-p', str(self.port), '-c', cipher, temp, self.user_host_path])
180 subprocess.call(['ssh', '%s@%s' % (self.user, self.host), 'rm', os.path.join(self.path, os.path.basename(temp))])
181 os.remove(temp)
182
183 def check_known_hosts(self):
184 """check ssh_known_hosts"""
185 output = subprocess.Popen(['ssh-keygen', '-F', self.host], stdout=subprocess.PIPE).communicate()[0] #subprocess.check_output doesn't exist in Python 2.6 (Debian squeeze default)
186 if output.find('Host %s found' % self.host) < 0:
187 raise mount.MountException( _('%s not found in ssh_known_hosts.') % self.host)
188
189 def check_remote_folder(self):
190 """check if remote folder exists and is write- and executable.
191 Create folder if it doesn't exist."""
192 cmd = 'd=0;'
193 cmd += '[[ -e %s ]] || d=1;' % self.path #path doesn't exist. set d=1 to indicate
194 cmd += '[[ $d -eq 1 ]] && mkdir %s; err=$?;' % self.path #create path, get errorcode from mkdir
195 cmd += '[[ $d -eq 1 ]] && exit $err;' #return errorcode from mkdir
196 cmd += '[[ -d %s ]] || exit 11;' % self.path #path is no directory
197 cmd += '[[ -w %s ]] || exit 12;' % self.path #path is not writeable
198 cmd += '[[ -x %s ]] || exit 13;' % self.path #path is not executable
199 cmd += 'exit 20' #everything is fine
200 try:
201 subprocess.check_call(['ssh', '-p', str(self.port), self.user + '@' + self.host, cmd], stdout=open(os.devnull, 'w'))
202 except subprocess.CalledProcessError as ex:
203 if ex.returncode == 20:
204 #clean exit
205 pass
206 elif ex.returncode == 11:
207 raise mount.MountException( _('Remote path exists but is not a directory:\n %s') % self.path)
208 elif ex.returncode == 12:
209 raise mount.MountException( _('Remote path is not writeable:\n %s') % self.path)
210 elif ex.returncode == 13:
211 raise mount.MountException( _('Remote path is not executable:\n %s') % self.path)
212 else:
213 raise mount.MountException( _('Couldn\'t create remote path:\n %s') % self.path)
214 else:
215 #returncode is 0
216 logger.info('Create remote folder %s' % self.path)
217
218 def check_ping_host(self):
219 try:
220 subprocess.check_call(['ping', '-q', '-c3', '-l3', self.host], stdout = open(os.devnull, 'w') )
221 except subprocess.CalledProcessError:
222 raise mount.MountException( _('Ping %s failed. Host is down or wrong address.') % self.host)
223
224 def check_remote_commands(self):
225 """try all relevant commands for take_snapshot on remote host.
226 specialy embedded Linux devices using 'BusyBox' sometimes doesn't
227 support everything that is need to run backintime.
228 also check for hardlink-support on remote host.
229 """
230 #check rsync
231 tmp_file = tempfile.mkstemp()[1]
232 rsync = tools.get_rsync_prefix( self.config ) + ' --dry-run --chmod=Du+wx %s ' % tmp_file
233
234 if self.cipher == 'default':
235 ssh_cipher_suffix = ''
236 else:
237 ssh_cipher_suffix = '-c %s' % self.cipher
238 rsync += '--rsh="ssh -p %s %s" ' % ( str(self.port), ssh_cipher_suffix)
239 rsync += '"%s@%s:%s"' % (self.user, self.host, self.path)
240
241 #use os.system for compatiblity with snapshots.py
242 err = os.system(rsync)
243 if err:
244 os.remove(tmp_file)
245 raise mount.MountException( _('Remote host %s doesn\'t support \'%s\':\n%s\nLook at \'man backintime\' for further instructions') % (self.host, rsync, err))
246 os.remove(tmp_file)
247
248 #check cp chmod find and rm
249 remote_tmp_dir = os.path.join(self.path, 'tmp_%s' % self.random_id())
250 cmd = 'tmp=%s ; ' % remote_tmp_dir
251 #first define a function to clean up and exit
252 cmd += 'cleanup(){ '
253 cmd += '[[ -e $tmp/a ]] && rm $tmp/a >/dev/null 2>&1; '
254 cmd += '[[ -e $tmp/b ]] && rm $tmp/b >/dev/null 2>&1; '
255 cmd += '[[ -e $tmp ]] && rmdir $tmp >/dev/null 2>&1; '
256 cmd += 'exit $1; }; '
257 #create tmp_RANDOM dir and file a
258 cmd += '[[ -e $tmp ]] || mkdir $tmp; touch $tmp/a; '
259 #try to create hardlink b from a
260 cmd += 'echo \"cp -aRl SOURCE DEST\"; cp -aRl $tmp/a $tmp/b >/dev/null; err_cp=$?; '
261 cmd += '[[ $err_cp -ne 0 ]] && cleanup $err_cp; '
262 #list inodes of a and b
263 cmd += 'ls -i $tmp/a; ls -i $tmp/b; '
264 #try to chmod
265 cmd += 'echo \"chmod u+rw FILE\"; chmod u+rw $tmp/a >/dev/null; err_chmod=$?; '
266 cmd += '[[ $err_chmod -ne 0 ]] && cleanup $err_chmod; '
267 #try to find and chmod
268 cmd += 'echo \"find PATH -type f -exec chmod u-wx \"{}\" \\;\"; '
269 cmd += 'find $tmp -type f -exec chmod u-wx \"{}\" \\; >/dev/null; err_find=$?; '
270 cmd += '[[ $err_find -ne 0 ]] && cleanup $err_find; '
271 #try to rm -rf
272 cmd += 'echo \"rm -rf PATH\"; rm -rf $tmp >/dev/null; err_rm=$?; '
273 cmd += '[[ $err_rm -ne 0 ]] && cleanup $err_rm; '
274 #if we end up here, everything should be fine
275 cmd += 'echo \"done\"'
276 output, err = subprocess.Popen(['ssh', '-p', str(self.port), self.user + '@' + self.host, cmd],
277 stdout=subprocess.PIPE,
278 stderr=subprocess.PIPE).communicate()
279
280## print('ERROR: %s' % err)
281## print('OUTPUT: %s' % output)
282 output_split = output.split('\n')
283 while True:
284 if len(output_split) > 0 and len(output_split[-1]) == 0:
285 output_split = output_split[:-1]
286 else:
287 break
288 if err or not output_split[-1].startswith('done'):
289 for command in ('cp', 'chmod', 'find', 'rm'):
290 if output_split[-1].startswith(command):
291 raise mount.MountException( _('Remote host %s doesn\'t support \'%s\':\n%s\nLook at \'man backintime\' for further instructions') % (self.host, output_split[-1], err))
292 raise mount.MountException( _('Check commands on host %s returned unknown error:\n%s\nLook at \'man backintime\' for further instructions') % (self.host, err))
293
294 i = 1
295 inode1 = 'ABC'
296 inode2 = 'DEF'
297 for line in output_split:
298 if line.startswith('cp'):
299 try:
300 inode1 = output_split[i].split(' ')[0]
301 inode2 = output_split[i+1].split(' ')[0]
302 except IndexError:
303 pass
304 if not inode1 == inode2:
305 raise mount.MountException( _('Remote host %s doesn\'t support hardlinks') % self.host)
306 i += 1
307
308 def random_id(self, size=6, chars=string.ascii_uppercase + string.digits):
309 return ''.join(random.choice(chars) for x in range(size))
0310
=== modified file 'common/tools.py'
--- common/tools.py 2012-02-20 13:12:41 +0000
+++ common/tools.py 2012-10-23 19:40:24 +0000
@@ -399,7 +399,27 @@
399 obj = os.stat(path)399 obj = os.stat(path)
400 unique_key = (obj.st_size, int(obj.st_mtime)) 400 unique_key = (obj.st_size, int(obj.st_mtime))
401 return unique_key401 return unique_key
402 402
403def check_cron_pattern(str):
404 '''check if str look like '0,10,13,15,17,20,23' or '*/6' '''
405 if str.find(' ') >= 0:
406 return False
407 try:
408 if str.startswith('*/'):
409 if int(str[2:]) <= 24:
410 return True
411 else:
412 return False
413 list = str.split(',')
414 for s in list:
415 if int(s) <= 24:
416 continue
417 else:
418 return False
419 return True
420 except ValueError:
421 return False
422
403#423#
404#424#
405class UniquenessSet:425class UniquenessSet:
406426
=== modified file 'gnome/app.py'
--- gnome/app.py 2012-02-22 09:55:08 +0000
+++ gnome/app.py 2012-10-23 19:40:24 +0000
@@ -44,6 +44,7 @@
44import snapshots44import snapshots
45import guiapplicationinstance45import guiapplicationinstance
46import tools46import tools
47import mount
4748
48import settingsdialog49import settingsdialog
49import logviewdialog50import logviewdialog
@@ -309,20 +310,31 @@
309310
310 if not self.config.is_configured():311 if not self.config.is_configured():
311 return 312 return
312 313
313 if self.snapshots.has_old_snapshots():314 if self.snapshots.has_old_snapshots():
314 settingsdialog.SettingsDialog( self.config, self.snapshots, self ).update_snapshots_location()315 settingsdialog.SettingsDialog( self.config, self.snapshots, self ).update_snapshots_location()
315 316
316 profile_id = self.config.get_current_profile()317 profile_id = self.config.get_current_profile()
317 if not self.config.can_backup( profile_id ):318
318 messagebox.show_error( self.window, self.config, _('Can\'t find snapshots folder.\nIf it is on a removable drive please plug it and then press OK') )319 #mount
320 try:
321 mnt = mount.Mount(cfg = self.config, profile_id = profile_id)
322 hash_id = mnt.mount()
323 except mount.MountException as ex:
324 messagebox.show_error( self.window, self.config, str(ex) )
325 sys.exit(1)
326 else:
327 self.config.set_current_hash_id(hash_id)
328
329 if not self.config.can_backup( profile_id ):
330 messagebox.show_error( self.window, self.config, _('Can\'t find snapshots folder.\nIf it is on a removable drive please plug it and then press OK') )
319331
320 self.update_profiles()332 self.update_profiles()
321 self.update_backup_info()333 self.update_backup_info()
322 gobject.timeout_add( 1000, self.update_backup_info )334 gobject.timeout_add( 1000, self.update_backup_info )
323335
324 def on_combo_profiles_changed( self, *params ):336 def on_combo_profiles_changed( self, *params ):
325 if self.disable_combo_changed:337 if self.disable_combo_changed:
326 return338 return
327339
328 iter = self.combo_profiles.get_active_iter()340 iter = self.combo_profiles.get_active_iter()
@@ -335,6 +347,7 @@
335 if not first_update_all and profile_id == self.config.get_current_profile():347 if not first_update_all and profile_id == self.config.get_current_profile():
336 return348 return
337349
350 self.remount(profile_id, self.config.get_current_profile())
338 self.config.set_current_profile( profile_id )351 self.config.set_current_profile( profile_id )
339 self.first_update_all = False352 self.first_update_all = False
340 self.update_all( first_update_all )353 self.update_all( first_update_all )
@@ -352,7 +365,7 @@
352 iter = self.store_profiles.append( [ self.config.get_profile_name( profile_id ), profile_id ] )365 iter = self.store_profiles.append( [ self.config.get_profile_name( profile_id ), profile_id ] )
353 if profile_id == self.config.get_current_profile():366 if profile_id == self.config.get_current_profile():
354 select_iter = iter367 select_iter = iter
355 368
356 self.disable_combo_changed = False369 self.disable_combo_changed = False
357370
358 if not select_iter is None:371 if not select_iter is None:
@@ -467,6 +480,15 @@
467 self.fill_places()480 self.fill_places()
468 self.fill_time_line( False )481 self.fill_time_line( False )
469 self.update_folder_view( 1, selected_file, show_snapshots )482 self.update_folder_view( 1, selected_file, show_snapshots )
483
484 def remount( self, new_profile_id, old_profile_id):
485 try:
486 mnt = mount.Mount(cfg = self.config, profile_id = old_profile_id)
487 hash_id = mnt.remount(new_profile_id)
488 except mount.MountException as ex:
489 messagebox.show_error( self.window, self.config, str(ex) )
490 else:
491 self.config.set_current_hash_id(hash_id)
470492
471 def places_pix_renderer_function( self, column, renderer, model, iter, user_data ):493 def places_pix_renderer_function( self, column, renderer, model, iter, user_data ):
472 if len( model.get_value( iter, 1 ) ) == 0:494 if len( model.get_value( iter, 1 ) ) == 0:
@@ -731,6 +753,13 @@
731 self.config.set_int_value( 'gnome.main_window.hpaned2', main_window_hpaned2 )753 self.config.set_int_value( 'gnome.main_window.hpaned2', main_window_hpaned2 )
732 self.config.set_str_value( 'gnome.last_path', self.folder_path )754 self.config.set_str_value( 'gnome.last_path', self.folder_path )
733 self.config.set_bool_value( 'gnome.show_hidden_files', self.show_hidden_files )755 self.config.set_bool_value( 'gnome.show_hidden_files', self.show_hidden_files )
756
757 #mount
758 try:
759 mnt = mount.Mount(cfg = self.config)
760 mnt.umount(self.config.current_hash_id)
761 except mount.MountException as ex:
762 messagebox.show_error( self.window, self.config, str(ex) )
734763
735 self.config.save()764 self.config.save()
736 self.window.destroy()765 self.window.destroy()
@@ -966,10 +995,22 @@
966 def on_btn_settings_clicked( self, button ):995 def on_btn_settings_clicked( self, button ):
967 snapshots_full_path = self.config.get_snapshots_full_path()996 snapshots_full_path = self.config.get_snapshots_full_path()
968 include_folders = self.config.get_include()997 include_folders = self.config.get_include()
998 hash_id = self.config.current_hash_id
969999
970 settingsdialog.SettingsDialog( self.config, self.snapshots, self ).run()1000 settingsdialog.SettingsDialog( self.config, self.snapshots, self ).run()
1001
1002 #mount
1003 try:
1004 mnt = mount.Mount(cfg = self.config)
1005 new_hash_id = mnt.remount(self.config.get_current_profile())
1006 except mount.MountException as ex:
1007 messagebox.show_error( self.window, self.config, str(ex) )
1008 else:
1009 self.config.set_current_hash_id(new_hash_id)
9711010
972 if snapshots_full_path != self.config.get_snapshots_full_path() or include_folders != self.config.get_include():1011 if snapshots_full_path != self.config.get_snapshots_full_path() \
1012 or include_folders != self.config.get_include() \
1013 or hash_id != self.config.current_hash_id:
973 self.update_all( False )1014 self.update_all( False )
974 self.update_profiles()1015 self.update_profiles()
9751016
9761017
=== modified file 'gnome/man/C/backintime-gnome.1.gz'
977Binary files gnome/man/C/backintime-gnome.1.gz 2012-03-06 21:23:31 +0000 and gnome/man/C/backintime-gnome.1.gz 2012-10-23 19:40:24 +0000 differ1018Binary files gnome/man/C/backintime-gnome.1.gz 2012-03-06 21:23:31 +0000 and gnome/man/C/backintime-gnome.1.gz 2012-10-23 19:40:24 +0000 differ
=== modified file 'gnome/settingsdialog.glade'
--- gnome/settingsdialog.glade 2012-02-20 11:30:09 +0000
+++ gnome/settingsdialog.glade 2012-10-23 19:40:24 +0000
@@ -177,239 +177,607 @@
177 <property name="border_width">5</property>177 <property name="border_width">5</property>
178 <property name="spacing">5</property>178 <property name="spacing">5</property>
179 <child>179 <child>
180 <object class="GtkFrame" id="frame1">180 <object class="GtkFrame" id="select_modes">
181 <property name="visible">True</property>181 <property name="visible">True</property>
182 <property name="can_focus">False</property>182 <property name="can_focus">False</property>
183 <property name="label_xalign">0</property>183 <property name="label_xalign">0</property>
184 <property name="shadow_type">none</property>184 <property name="shadow_type">none</property>
185 <child>185 <child>
186 <object class="GtkHBox" id="hbox_select_modes">
187 <property name="visible">True</property>
188 <property name="can_focus">False</property>
189 <child>
190 <object class="GtkLabel" id="lbl_select_modes">
191 <property name="visible">True</property>
192 <property name="can_focus">False</property>
193 <property name="xalign">1</property>
194 <property name="label" translatable="yes">Mode:</property>
195 </object>
196 <packing>
197 <property name="expand">False</property>
198 <property name="fill">True</property>
199 <property name="position">0</property>
200 </packing>
201 </child>
202 <child>
203 <object class="GtkComboBox" id="combo_modes">
204 <property name="visible">True</property>
205 <property name="can_focus">False</property>
206 <signal name="changed" handler="on_combo_modes_changed" swapped="no"/>
207 </object>
208 <packing>
209 <property name="expand">True</property>
210 <property name="fill">True</property>
211 <property name="position">1</property>
212 </packing>
213 </child>
214 </object>
215 </child>
216 </object>
217 <packing>
218 <property name="expand">False</property>
219 <property name="fill">True</property>
220 <property name="position">0</property>
221 </packing>
222 </child>
223 <child>
224 <object class="GtkFrame" id="mode_local">
225 <property name="can_focus">False</property>
226 <property name="label_xalign">0</property>
227 <property name="shadow_type">none</property>
228 <child>
186 <object class="GtkAlignment" id="alignment1">229 <object class="GtkAlignment" id="alignment1">
187 <property name="visible">True</property>230 <property name="visible">True</property>
188 <property name="can_focus">False</property>231 <property name="can_focus">False</property>
189 <property name="left_padding">12</property>232 <property name="left_padding">12</property>
190 <child>233 <child>
191 <object class="GtkVBox" id="vbox1">234 <object class="GtkHBox" id="hbox4">
192 <property name="visible">True</property>235 <property name="visible">True</property>
193 <property name="can_focus">False</property>236 <property name="can_focus">False</property>
194 <property name="spacing">2</property>237 <child>
195 <child>238 <object class="GtkEntry" id="edit_where">
196 <object class="GtkHBox" id="hbox4">239 <property name="visible">True</property>
197 <property name="visible">True</property>240 <property name="can_focus">True</property>
198 <property name="can_focus">False</property>241 <property name="editable">False</property>
199 <child>242 <property name="invisible_char">•</property>
200 <object class="GtkEntry" id="edit_where">243 <property name="primary_icon_activatable">False</property>
201 <property name="visible">True</property>244 <property name="secondary_icon_activatable">False</property>
202 <property name="can_focus">True</property>245 <property name="primary_icon_sensitive">True</property>
203 <property name="editable">False</property>246 <property name="secondary_icon_sensitive">True</property>
204 <property name="invisible_char">•</property>247 </object>
205 <property name="primary_icon_activatable">False</property>248 <packing>
206 <property name="secondary_icon_activatable">False</property>249 <property name="expand">True</property>
207 <property name="primary_icon_sensitive">True</property>250 <property name="fill">True</property>
208 <property name="secondary_icon_sensitive">True</property>251 <property name="position">0</property>
209 </object>252 </packing>
210 <packing>253 </child>
211 <property name="expand">True</property>254 <child>
212 <property name="fill">True</property>255 <object class="GtkButton" id="btn_where">
213 <property name="position">0</property>256 <property name="visible">True</property>
214 </packing>257 <property name="can_focus">True</property>
215 </child>258 <property name="receives_default">True</property>
216 <child>259 <property name="use_action_appearance">False</property>
217 <object class="GtkButton" id="btn_where">260 <signal name="clicked" handler="on_btn_where_clicked" swapped="no"/>
218 <property name="visible">True</property>261 <child>
219 <property name="can_focus">True</property>262 <object class="GtkImage" id="image1">
220 <property name="receives_default">True</property>263 <property name="visible">True</property>
264 <property name="can_focus">False</property>
265 <property name="stock">gtk-directory</property>
266 </object>
267 </child>
268 </object>
269 <packing>
270 <property name="expand">False</property>
271 <property name="fill">True</property>
272 <property name="position">1</property>
273 </packing>
274 </child>
275 </object>
276 </child>
277 </object>
278 </child>
279 <child type="label">
280 <object class="GtkLabel" id="lbl_where">
281 <property name="visible">True</property>
282 <property name="can_focus">False</property>
283 <property name="label" translatable="yes">&lt;b&gt;Where to save snapshots&lt;/b&gt;</property>
284 <property name="use_markup">True</property>
285 </object>
286 </child>
287 </object>
288 <packing>
289 <property name="expand">False</property>
290 <property name="fill">True</property>
291 <property name="position">10</property>
292 </packing>
293 </child>
294 <child>
295 <object class="GtkFrame" id="mode_ssh">
296 <property name="can_focus">False</property>
297 <property name="label_xalign">0</property>
298 <property name="shadow_type">none</property>
299 <child>
300 <object class="GtkAlignment" id="alignment_ssh">
301 <property name="visible">True</property>
302 <property name="can_focus">False</property>
303 <property name="left_padding">12</property>
304 <child>
305 <object class="GtkVBox" id="vbox_ssh">
306 <property name="visible">True</property>
307 <property name="can_focus">False</property>
308 <property name="spacing">2</property>
309 <child>
310 <object class="GtkHBox" id="hbox_ssh1">
311 <property name="visible">True</property>
312 <property name="can_focus">False</property>
313 <property name="spacing">2</property>
314 <child>
315 <object class="GtkLabel" id="lbl_ssh_host">
316 <property name="visible">True</property>
317 <property name="can_focus">False</property>
318 <property name="xalign">1</property>
319 <property name="label" translatable="yes">Host:</property>
320 <property name="width_chars">8</property>
321 </object>
322 <packing>
323 <property name="expand">False</property>
324 <property name="fill">True</property>
325 <property name="position">0</property>
326 </packing>
327 </child>
328 <child>
329 <object class="GtkEntry" id="txt_ssh_host">
330 <property name="visible">True</property>
331 <property name="can_focus">True</property>
332 <property name="invisible_char">•</property>
333 <property name="primary_icon_activatable">False</property>
334 <property name="secondary_icon_activatable">False</property>
335 <property name="primary_icon_sensitive">True</property>
336 <property name="secondary_icon_sensitive">True</property>
337 </object>
338 <packing>
339 <property name="expand">True</property>
340 <property name="fill">True</property>
341 <property name="position">1</property>
342 </packing>
343 </child>
344 <child>
345 <object class="GtkLabel" id="lbl_ssh_port">
346 <property name="visible">True</property>
347 <property name="can_focus">False</property>
348 <property name="xalign">1</property>
349 <property name="label" translatable="yes">Port:</property>
350 <property name="width_chars">8</property>
351 </object>
352 <packing>
353 <property name="expand">False</property>
354 <property name="fill">True</property>
355 <property name="position">2</property>
356 </packing>
357 </child>
358 <child>
359 <object class="GtkEntry" id="txt_ssh_port">
360 <property name="visible">True</property>
361 <property name="can_focus">True</property>
362 <property name="invisible_char">•</property>
363 <property name="primary_icon_activatable">False</property>
364 <property name="secondary_icon_activatable">False</property>
365 <property name="primary_icon_sensitive">True</property>
366 <property name="secondary_icon_sensitive">True</property>
367 </object>
368 <packing>
369 <property name="expand">True</property>
370 <property name="fill">True</property>
371 <property name="position">3</property>
372 </packing>
373 </child>
374 <child>
375 <object class="GtkLabel" id="lbl_ssh_user">
376 <property name="visible">True</property>
377 <property name="can_focus">False</property>
378 <property name="xalign">1</property>
379 <property name="label" translatable="yes">User:</property>
380 <property name="width_chars">8</property>
381 </object>
382 <packing>
383 <property name="expand">False</property>
384 <property name="fill">True</property>
385 <property name="position">4</property>
386 </packing>
387 </child>
388 <child>
389 <object class="GtkEntry" id="txt_ssh_user">
390 <property name="visible">True</property>
391 <property name="can_focus">True</property>
392 <property name="invisible_char">•</property>
393 <property name="primary_icon_activatable">False</property>
394 <property name="secondary_icon_activatable">False</property>
395 <property name="primary_icon_sensitive">True</property>
396 <property name="secondary_icon_sensitive">True</property>
397 </object>
398 <packing>
399 <property name="expand">True</property>
400 <property name="fill">True</property>
401 <property name="position">5</property>
402 </packing>
403 </child>
404 </object>
405 <packing>
406 <property name="expand">True</property>
407 <property name="fill">True</property>
408 <property name="position">0</property>
409 </packing>
410 </child>
411 <child>
412 <object class="GtkHBox" id="hbox_ssh2">
413 <property name="visible">True</property>
414 <property name="can_focus">False</property>
415 <property name="spacing">2</property>
416 <child>
417 <object class="GtkLabel" id="lbl_ssh_path">
418 <property name="visible">True</property>
419 <property name="can_focus">False</property>
420 <property name="xalign">1</property>
421 <property name="label" translatable="yes">Path:</property>
422 <property name="width_chars">8</property>
423 </object>
424 <packing>
425 <property name="expand">False</property>
426 <property name="fill">True</property>
427 <property name="position">0</property>
428 </packing>
429 </child>
430 <child>
431 <object class="GtkEntry" id="txt_ssh_path">
432 <property name="visible">True</property>
433 <property name="can_focus">True</property>
434 <property name="invisible_char">•</property>
435 <property name="primary_icon_activatable">False</property>
436 <property name="secondary_icon_activatable">False</property>
437 <property name="primary_icon_sensitive">True</property>
438 <property name="secondary_icon_sensitive">True</property>
439 </object>
440 <packing>
441 <property name="expand">True</property>
442 <property name="fill">True</property>
443 <property name="position">1</property>
444 </packing>
445 </child>
446 <child>
447 <object class="GtkLabel" id="lbl_ssh_cipher">
448 <property name="visible">True</property>
449 <property name="can_focus">False</property>
450 <property name="xalign">1</property>
451 <property name="label" translatable="yes">Cipher:</property>
452 <property name="width_chars">8</property>
453 </object>
454 <packing>
455 <property name="expand">False</property>
456 <property name="fill">True</property>
457 <property name="position">2</property>
458 </packing>
459 </child>
460 <child>
461 <object class="GtkComboBox" id="combo_ssh_cipher">
462 <property name="visible">True</property>
463 <property name="can_focus">False</property>
464 </object>
465 <packing>
466 <property name="expand">True</property>
467 <property name="fill">True</property>
468 <property name="position">3</property>
469 </packing>
470 </child>
471 </object>
472 <packing>
473 <property name="expand">True</property>
474 <property name="fill">True</property>
475 <property name="position">1</property>
476 </packing>
477 </child>
478 </object>
479 </child>
480 </object>
481 </child>
482 <child type="label">
483 <object class="GtkLabel" id="lbl_ssh">
484 <property name="visible">True</property>
485 <property name="can_focus">False</property>
486 <property name="label" translatable="yes">&lt;b&gt;SSH Settings&lt;/b&gt;</property>
487 <property name="use_markup">True</property>
488 </object>
489 </child>
490 </object>
491 <packing>
492 <property name="expand">False</property>
493 <property name="fill">True</property>
494 <property name="position">20</property>
495 </packing>
496 </child>
497 <child>
498 <object class="GtkFrame" id="mode_dummy">
499 <property name="can_focus">False</property>
500 <property name="label_xalign">0</property>
501 <property name="shadow_type">none</property>
502 <child>
503 <object class="GtkAlignment" id="alignment_dummy">
504 <property name="visible">True</property>
505 <property name="can_focus">False</property>
506 <property name="left_padding">12</property>
507 <child>
508 <object class="GtkHBox" id="hbox_dummy">
509 <property name="visible">True</property>
510 <property name="can_focus">False</property>
511 <property name="spacing">2</property>
512 <child>
513 <object class="GtkLabel" id="lbl_dummy_host">
514 <property name="visible">True</property>
515 <property name="can_focus">False</property>
516 <property name="xalign">1</property>
517 <property name="label" translatable="yes">Host:</property>
518 <property name="width_chars">8</property>
519 </object>
520 <packing>
521 <property name="expand">False</property>
522 <property name="fill">True</property>
523 <property name="position">0</property>
524 </packing>
525 </child>
526 <child>
527 <object class="GtkEntry" id="txt_dummy_host">
528 <property name="visible">True</property>
529 <property name="can_focus">True</property>
530 <property name="invisible_char">•</property>
531 <property name="primary_icon_activatable">False</property>
532 <property name="secondary_icon_activatable">False</property>
533 <property name="primary_icon_sensitive">True</property>
534 <property name="secondary_icon_sensitive">True</property>
535 </object>
536 <packing>
537 <property name="expand">True</property>
538 <property name="fill">True</property>
539 <property name="position">1</property>
540 </packing>
541 </child>
542 <child>
543 <object class="GtkLabel" id="lbl_dummy_port">
544 <property name="visible">True</property>
545 <property name="can_focus">False</property>
546 <property name="xalign">1</property>
547 <property name="label" translatable="yes">Port:</property>
548 <property name="width_chars">8</property>
549 </object>
550 <packing>
551 <property name="expand">False</property>
552 <property name="fill">True</property>
553 <property name="position">2</property>
554 </packing>
555 </child>
556 <child>
557 <object class="GtkEntry" id="txt_dummy_port">
558 <property name="visible">True</property>
559 <property name="can_focus">True</property>
560 <property name="invisible_char">•</property>
561 <property name="primary_icon_activatable">False</property>
562 <property name="secondary_icon_activatable">False</property>
563 <property name="primary_icon_sensitive">True</property>
564 <property name="secondary_icon_sensitive">True</property>
565 </object>
566 <packing>
567 <property name="expand">True</property>
568 <property name="fill">True</property>
569 <property name="position">3</property>
570 </packing>
571 </child>
572 <child>
573 <object class="GtkLabel" id="lbl_dummy_user">
574 <property name="visible">True</property>
575 <property name="can_focus">False</property>
576 <property name="xalign">1</property>
577 <property name="label" translatable="yes">User:</property>
578 <property name="width_chars">8</property>
579 </object>
580 <packing>
581 <property name="expand">False</property>
582 <property name="fill">True</property>
583 <property name="position">4</property>
584 </packing>
585 </child>
586 <child>
587 <object class="GtkEntry" id="txt_dummy_user">
588 <property name="visible">True</property>
589 <property name="can_focus">True</property>
590 <property name="invisible_char">•</property>
591 <property name="primary_icon_activatable">False</property>
592 <property name="secondary_icon_activatable">False</property>
593 <property name="primary_icon_sensitive">True</property>
594 <property name="secondary_icon_sensitive">True</property>
595 </object>
596 <packing>
597 <property name="expand">True</property>
598 <property name="fill">True</property>
599 <property name="position">5</property>
600 </packing>
601 </child>
602 </object>
603 </child>
604 </object>
605 </child>
606 <child type="label">
607 <object class="GtkLabel" id="lbl_dummy">
608 <property name="visible">True</property>
609 <property name="can_focus">False</property>
610 <property name="label" translatable="yes">&lt;b&gt;Dummy Settings&lt;/b&gt;</property>
611 <property name="use_markup">True</property>
612 </object>
613 </child>
614 </object>
615 <packing>
616 <property name="expand">False</property>
617 <property name="fill">True</property>
618 <property name="position">800</property>
619 </packing>
620 </child>
621 <child>
622 <object class="GtkFrame" id="advanced">
623 <property name="visible">True</property>
624 <property name="can_focus">False</property>
625 <property name="label_xalign">0</property>
626 <property name="shadow_type">none</property>
627 <child>
628 <object class="GtkExpander" id="expander1">
629 <property name="visible">True</property>
630 <property name="can_focus">True</property>
631 <child>
632 <object class="GtkAlignment" id="alignment3">
633 <property name="visible">True</property>
634 <property name="can_focus">False</property>
635 <property name="left_padding">12</property>
636 <child>
637 <object class="GtkVBox" id="vbox3">
638 <property name="visible">True</property>
639 <property name="can_focus">False</property>
640 <property name="spacing">2</property>
641 <child>
642 <object class="GtkCheckButton" id="cb_auto_host_user_profile">
643 <property name="label" translatable="yes">Auto Host / User / Profile Id</property>
644 <property name="visible">True</property>
645 <property name="can_focus">True</property>
646 <property name="receives_default">False</property>
221 <property name="use_action_appearance">False</property>647 <property name="use_action_appearance">False</property>
222 <signal name="clicked" handler="on_btn_where_clicked" swapped="no"/>648 <property name="draw_indicator">True</property>
223 <child>649 <signal name="toggled" handler="on_cb_auto_host_user_profile_toggled" swapped="no"/>
224 <object class="GtkImage" id="image1">650 </object>
225 <property name="visible">True</property>651 <packing>
226 <property name="can_focus">False</property>652 <property name="expand">True</property>
227 <property name="stock">gtk-directory</property>653 <property name="fill">True</property>
228 </object>654 <property name="position">0</property>
229 </child>655 </packing>
230 </object>656 </child>
231 <packing>657 <child>
232 <property name="expand">False</property>658 <object class="GtkHBox" id="hbox8">
659 <property name="visible">True</property>
660 <property name="can_focus">False</property>
661 <property name="spacing">2</property>
662 <child>
663 <object class="GtkLabel" id="lbl_host">
664 <property name="visible">True</property>
665 <property name="can_focus">False</property>
666 <property name="xalign">1</property>
667 <property name="label" translatable="yes">Host:</property>
668 <property name="width_chars">8</property>
669 </object>
670 <packing>
671 <property name="expand">False</property>
672 <property name="fill">True</property>
673 <property name="position">0</property>
674 </packing>
675 </child>
676 <child>
677 <object class="GtkEntry" id="txt_host">
678 <property name="visible">True</property>
679 <property name="can_focus">True</property>
680 <property name="invisible_char">•</property>
681 <property name="primary_icon_activatable">False</property>
682 <property name="secondary_icon_activatable">False</property>
683 <property name="primary_icon_sensitive">True</property>
684 <property name="secondary_icon_sensitive">True</property>
685 </object>
686 <packing>
687 <property name="expand">True</property>
688 <property name="fill">True</property>
689 <property name="position">1</property>
690 </packing>
691 </child>
692 <child>
693 <object class="GtkLabel" id="lbl_user">
694 <property name="visible">True</property>
695 <property name="can_focus">False</property>
696 <property name="xalign">1</property>
697 <property name="label" translatable="yes">User:</property>
698 <property name="width_chars">8</property>
699 </object>
700 <packing>
701 <property name="expand">False</property>
702 <property name="fill">True</property>
703 <property name="position">2</property>
704 </packing>
705 </child>
706 <child>
707 <object class="GtkEntry" id="txt_user">
708 <property name="visible">True</property>
709 <property name="can_focus">True</property>
710 <property name="invisible_char">•</property>
711 <property name="primary_icon_activatable">False</property>
712 <property name="secondary_icon_activatable">False</property>
713 <property name="primary_icon_sensitive">True</property>
714 <property name="secondary_icon_sensitive">True</property>
715 </object>
716 <packing>
717 <property name="expand">True</property>
718 <property name="fill">True</property>
719 <property name="position">3</property>
720 </packing>
721 </child>
722 <child>
723 <object class="GtkLabel" id="lbl_profile">
724 <property name="visible">True</property>
725 <property name="can_focus">False</property>
726 <property name="xalign">1</property>
727 <property name="label" translatable="yes">Profile Id:</property>
728 <property name="width_chars">8</property>
729 </object>
730 <packing>
731 <property name="expand">False</property>
732 <property name="fill">True</property>
733 <property name="position">4</property>
734 </packing>
735 </child>
736 <child>
737 <object class="GtkEntry" id="txt_profile">
738 <property name="visible">True</property>
739 <property name="can_focus">True</property>
740 <property name="invisible_char">•</property>
741 <property name="primary_icon_activatable">False</property>
742 <property name="secondary_icon_activatable">False</property>
743 <property name="primary_icon_sensitive">True</property>
744 <property name="secondary_icon_sensitive">True</property>
745 </object>
746 <packing>
747 <property name="expand">True</property>
748 <property name="fill">True</property>
749 <property name="position">5</property>
750 </packing>
751 </child>
752 </object>
753 <packing>
754 <property name="expand">True</property>
233 <property name="fill">True</property>755 <property name="fill">True</property>
234 <property name="position">1</property>756 <property name="position">1</property>
235 </packing>757 </packing>
236 </child>758 </child>
237 </object>759 </object>
238 <packing>760 </child>
239 <property name="expand">True</property>761 </object>
240 <property name="fill">True</property>762 </child>
241 <property name="position">0</property>763 <child type="label">
242 </packing>764 <object class="GtkLabel" id="label7">
243 </child>765 <property name="visible">True</property>
244 <child>766 <property name="can_focus">False</property>
245 <object class="GtkExpander" id="expander1">767 <property name="label" translatable="yes">Advanced</property>
246 <property name="visible">True</property>768 </object>
247 <property name="can_focus">True</property>769 </child>
248 <child>
249 <object class="GtkAlignment" id="alignment3">
250 <property name="visible">True</property>
251 <property name="can_focus">False</property>
252 <property name="left_padding">12</property>
253 <child>
254 <object class="GtkVBox" id="vbox3">
255 <property name="visible">True</property>
256 <property name="can_focus">False</property>
257 <property name="spacing">2</property>
258 <child>
259 <object class="GtkCheckButton" id="cb_auto_host_user_profile">
260 <property name="label" translatable="yes">Auto Host / User / Profile Id</property>
261 <property name="visible">True</property>
262 <property name="can_focus">True</property>
263 <property name="receives_default">False</property>
264 <property name="use_action_appearance">False</property>
265 <property name="draw_indicator">True</property>
266 <signal name="toggled" handler="on_cb_auto_host_user_profile_toggled" swapped="no"/>
267 </object>
268 <packing>
269 <property name="expand">True</property>
270 <property name="fill">True</property>
271 <property name="position">0</property>
272 </packing>
273 </child>
274 <child>
275 <object class="GtkHBox" id="hbox8">
276 <property name="visible">True</property>
277 <property name="can_focus">False</property>
278 <property name="spacing">2</property>
279 <child>
280 <object class="GtkLabel" id="lbl_host">
281 <property name="visible">True</property>
282 <property name="can_focus">False</property>
283 <property name="xalign">1</property>
284 <property name="yalign">0.5899999737739563</property>
285 <property name="label" translatable="yes">Host:</property>
286 </object>
287 <packing>
288 <property name="expand">True</property>
289 <property name="fill">True</property>
290 <property name="position">0</property>
291 </packing>
292 </child>
293 <child>
294 <object class="GtkEntry" id="txt_host">
295 <property name="visible">True</property>
296 <property name="can_focus">True</property>
297 <property name="invisible_char">•</property>
298 <property name="primary_icon_activatable">False</property>
299 <property name="secondary_icon_activatable">False</property>
300 <property name="primary_icon_sensitive">True</property>
301 <property name="secondary_icon_sensitive">True</property>
302 </object>
303 <packing>
304 <property name="expand">True</property>
305 <property name="fill">True</property>
306 <property name="position">1</property>
307 </packing>
308 </child>
309 <child>
310 <object class="GtkLabel" id="lbl_user">
311 <property name="visible">True</property>
312 <property name="can_focus">False</property>
313 <property name="xalign">1</property>
314 <property name="label" translatable="yes">User:</property>
315 </object>
316 <packing>
317 <property name="expand">True</property>
318 <property name="fill">True</property>
319 <property name="position">2</property>
320 </packing>
321 </child>
322 <child>
323 <object class="GtkEntry" id="txt_user">
324 <property name="visible">True</property>
325 <property name="can_focus">True</property>
326 <property name="invisible_char">•</property>
327 <property name="primary_icon_activatable">False</property>
328 <property name="secondary_icon_activatable">False</property>
329 <property name="primary_icon_sensitive">True</property>
330 <property name="secondary_icon_sensitive">True</property>
331 </object>
332 <packing>
333 <property name="expand">True</property>
334 <property name="fill">True</property>
335 <property name="position">3</property>
336 </packing>
337 </child>
338 <child>
339 <object class="GtkLabel" id="lbl_profile">
340 <property name="visible">True</property>
341 <property name="can_focus">False</property>
342 <property name="xalign">1</property>
343 <property name="label" translatable="yes">Profile Id:</property>
344 </object>
345 <packing>
346 <property name="expand">True</property>
347 <property name="fill">True</property>
348 <property name="position">4</property>
349 </packing>
350 </child>
351 <child>
352 <object class="GtkEntry" id="txt_profile">
353 <property name="visible">True</property>
354 <property name="can_focus">True</property>
355 <property name="invisible_char">•</property>
356 <property name="primary_icon_activatable">False</property>
357 <property name="secondary_icon_activatable">False</property>
358 <property name="primary_icon_sensitive">True</property>
359 <property name="secondary_icon_sensitive">True</property>
360 </object>
361 <packing>
362 <property name="expand">True</property>
363 <property name="fill">True</property>
364 <property name="position">5</property>
365 </packing>
366 </child>
367 </object>
368 <packing>
369 <property name="expand">True</property>
370 <property name="fill">True</property>
371 <property name="position">1</property>
372 </packing>
373 </child>
374 </object>
375 </child>
376 </object>
377 </child>
378 <child type="label">
379 <object class="GtkLabel" id="label7">
380 <property name="visible">True</property>
381 <property name="can_focus">False</property>
382 <property name="label" translatable="yes">Advanced</property>
383 </object>
384 </child>
385 </object>
386 <packing>
387 <property name="expand">True</property>
388 <property name="fill">True</property>
389 <property name="position">1</property>
390 </packing>
391 </child>
392 </object>
393 </child>
394 </object>
395 </child>
396 <child type="label">
397 <object class="GtkLabel" id="label4">
398 <property name="visible">True</property>
399 <property name="can_focus">False</property>
400 <property name="label" translatable="yes">&lt;b&gt;Where to save snapshots&lt;/b&gt;</property>
401 <property name="use_markup">True</property>
402 </object>770 </object>
403 </child>771 </child>
404 </object>772 </object>
405 <packing>773 <packing>
406 <property name="expand">False</property>774 <property name="expand">False</property>
407 <property name="fill">True</property>775 <property name="fill">True</property>
408 <property name="position">0</property>776 <property name="position">900</property>
409 </packing>777 </packing>
410 </child>778 </child>
411 <child>779 <child>
412 <object class="GtkFrame" id="frame2">780 <object class="GtkFrame" id="shedule">
413 <property name="visible">True</property>781 <property name="visible">True</property>
414 <property name="can_focus">False</property>782 <property name="can_focus">False</property>
415 <property name="label_xalign">0</property>783 <property name="label_xalign">0</property>
@@ -423,7 +791,7 @@
423 <object class="GtkTable" id="grid1">791 <object class="GtkTable" id="grid1">
424 <property name="visible">True</property>792 <property name="visible">True</property>
425 <property name="can_focus">False</property>793 <property name="can_focus">False</property>
426 <property name="n_rows">4</property>794 <property name="n_rows">5</property>
427 <property name="n_columns">2</property>795 <property name="n_columns">2</property>
428 <property name="column_spacing">5</property>796 <property name="column_spacing">5</property>
429 <property name="row_spacing">4</property>797 <property name="row_spacing">4</property>
@@ -438,6 +806,19 @@
438 </packing>806 </packing>
439 </child>807 </child>
440 <child>808 <child>
809 <object class="GtkLabel" id="lbl_backup_time_custom">
810 <property name="visible">True</property>
811 <property name="can_focus">False</property>
812 <property name="xalign">1</property>
813 <property name="ypad">6</property>
814 <property name="label" translatable="yes">Hours:</property>
815 </object>
816 <packing>
817 <property name="top_attach">4</property>
818 <property name="x_options">GTK_FILL</property>
819 </packing>
820 </child>
821 <child>
441 <object class="GtkLabel" id="lbl_backup_time">822 <object class="GtkLabel" id="lbl_backup_time">
442 <property name="visible">True</property>823 <property name="visible">True</property>
443 <property name="can_focus">False</property>824 <property name="can_focus">False</property>
@@ -477,6 +858,22 @@
477 </packing>858 </packing>
478 </child>859 </child>
479 <child>860 <child>
861 <object class="GtkEntry" id="txt_backup_time_custom">
862 <property name="visible">True</property>
863 <property name="can_focus">True</property>
864 <property name="tooltip_text" translatable="yes">hours seperate by comma ( e.g. 8,12,18,23) or */3 for periodic backups every 3 hours (cron style)</property>
865 <property name="invisible_char">•</property>
866 <property name="primary_icon_activatable">False</property>
867 <property name="secondary_icon_activatable">False</property>
868 <property name="primary_icon_sensitive">True</property>
869 <property name="secondary_icon_sensitive">True</property>
870 </object>
871 <packing>
872 <property name="left_attach">1</property>
873 <property name="top_attach">4</property>
874 </packing>
875 </child>
876 <child>
480 <object class="GtkComboBox" id="cb_backup_time">877 <object class="GtkComboBox" id="cb_backup_time">
481 <property name="visible">True</property>878 <property name="visible">True</property>
482 <property name="can_focus">False</property>879 <property name="can_focus">False</property>
@@ -522,7 +919,7 @@
522 <packing>919 <packing>
523 <property name="expand">False</property>920 <property name="expand">False</property>
524 <property name="fill">True</property>921 <property name="fill">True</property>
525 <property name="position">1</property>922 <property name="position">950</property>
526 </packing>923 </packing>
527 </child>924 </child>
528 <child>925 <child>
@@ -549,7 +946,7 @@
549 <packing>946 <packing>
550 <property name="expand">True</property>947 <property name="expand">True</property>
551 <property name="fill">True</property>948 <property name="fill">True</property>
552 <property name="position">2</property>949 <property name="position">1000</property>
553 </packing>950 </packing>
554 </child>951 </child>
555 </object>952 </object>
556953
=== modified file 'gnome/settingsdialog.py'
--- gnome/settingsdialog.py 2012-02-20 11:18:31 +0000
+++ gnome/settingsdialog.py 2012-10-23 19:40:24 +0000
@@ -30,6 +30,7 @@
30import config30import config
31import messagebox31import messagebox
32import tools32import tools
33import mount
3334
3435
35_=gettext.gettext36_=gettext.gettext
@@ -74,7 +75,8 @@
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,
77 'on_cb_auto_host_user_profile_toggled': self.update_host_user_profile78 'on_cb_auto_host_user_profile_toggled': self.update_host_user_profile,
79 'on_combo_modes_changed': self.on_combo_modes_changed
78 }80 }
79 81
80 builder.connect_signals(signals)82 builder.connect_signals(signals)
@@ -98,6 +100,26 @@
98 100
99 self.disable_combo_changed = False101 self.disable_combo_changed = False
100 102
103 #snapshots mode (local, ssh, ...)
104 self.store_modes = gtk.ListStore(str, str)
105 keys = self.config.SNAPSHOT_MODES.keys()
106 keys.sort()
107 for key in keys:
108 self.store_modes.append([self.config.SNAPSHOT_MODES[key][1], key])
109
110 self.combo_modes = get( 'combo_modes' )
111 self.combo_modes.set_model( self.store_modes )
112
113 self.combo_modes.clear()
114 text_renderer = gtk.CellRendererText()
115 self.combo_modes.pack_start( text_renderer, True )
116 self.combo_modes.add_attribute( text_renderer, 'text', 0 )
117
118 self.mode = None
119 self.mode_local = get('mode_local')
120 self.mode_ssh = get('mode_ssh')
121## self.mode_dummy = get('mode_dummy')
122
101 #set current folder123 #set current folder
102 #self.fcb_where = get( 'fcb_where' )124 #self.fcb_where = get( 'fcb_where' )
103 #self.fcb_where.set_show_hidden( self.parent.show_hidden_files )125 #self.fcb_where.set_show_hidden( self.parent.show_hidden_files )
@@ -110,6 +132,31 @@
110 self.txt_user = get('txt_user')132 self.txt_user = get('txt_user')
111 self.lbl_profile = get('lbl_profile')133 self.lbl_profile = get('lbl_profile')
112 self.txt_profile = get('txt_profile')134 self.txt_profile = get('txt_profile')
135
136 #ssh
137 self.txt_ssh_host = get('txt_ssh_host')
138 self.txt_ssh_port = get('txt_ssh_port')
139 self.txt_ssh_user = get('txt_ssh_user')
140 self.txt_ssh_path = get('txt_ssh_path')
141
142 self.store_ssh_cipher = gtk.ListStore(str, str)
143 keys = self.config.SSH_CIPHERS.keys()
144 keys.sort()
145 for key in keys:
146 self.store_ssh_cipher.append([self.config.SSH_CIPHERS[key], key])
147
148 self.combo_ssh_cipher = get( 'combo_ssh_cipher' )
149 self.combo_ssh_cipher.set_model( self.store_ssh_cipher )
150
151 self.combo_ssh_cipher.clear()
152 text_renderer = gtk.CellRendererText()
153 self.combo_ssh_cipher.pack_start( text_renderer, True )
154 self.combo_ssh_cipher.add_attribute( text_renderer, 'text', 0 )
155
156## #dummy
157## self.txt_dummy_host = get('txt_dummy_host')
158## self.txt_dummy_port = get('txt_dummy_port')
159## self.txt_dummy_user = get('txt_dummy_user')
113160
114 #automatic backup mode store161 #automatic backup mode store
115 self.store_backup_mode = gtk.ListStore( str, int )162 self.store_backup_mode = gtk.ListStore( str, int )
@@ -136,6 +183,10 @@
136 for t in xrange( 1, 8 ):183 for t in xrange( 1, 8 ):
137 self.store_backup_weekday.append( [ datetime.date(2011, 11, 6 + t).strftime("%A"), t ] )184 self.store_backup_weekday.append( [ datetime.date(2011, 11, 6 + t).strftime("%A"), t ] )
138185
186 #custom backup time
187 self.txt_backup_time_custom = get('txt_backup_time_custom')
188 self.lbl_backup_time_custom = get('lbl_backup_time_custom')
189
139 #per directory schedule190 #per directory schedule
140 #self.cb_per_directory_schedule = get( 'cb_per_directory_schedule' )191 #self.cb_per_directory_schedule = get( 'cb_per_directory_schedule' )
141 #self.lbl_schedule = get( 'lbl_schedule' )192 #self.lbl_schedule = get( 'lbl_schedule' )
@@ -184,7 +235,17 @@
184 self.store_exclude = gtk.ListStore( str, str )235 self.store_exclude = gtk.ListStore( str, str )
185 self.list_exclude.set_model( self.store_exclude )236 self.list_exclude.set_model( self.store_exclude )
186237
187 get( 'lbl_highly_recommended_excluded' ).set_text( ', '.join(self.config.DEFAULT_EXCLUDE) )238 exclude = ''
239 i = 1
240 prev_lines = 0
241 for ex in self.config.DEFAULT_EXCLUDE:
242 exclude += ex
243 if i < len(self.config.DEFAULT_EXCLUDE):
244 exclude += ', '
245 if len(exclude)-prev_lines > 80:
246 exclude += '\n'
247 prev_lines += len(exclude)
248 get( 'lbl_highly_recommended_excluded' ).set_text( exclude )
188249
189 #setup automatic backup mode250 #setup automatic backup mode
190 self.cb_backup_mode = get( 'cb_backup_mode' )251 self.cb_backup_mode = get( 'cb_backup_mode' )
@@ -228,6 +289,8 @@
228 289
229 self.lbl_backup_weekday = get( 'lbl_backup_weekday' )290 self.lbl_backup_weekday = get( 'lbl_backup_weekday' )
230291
292 self.hbox_backup_time = get( 'hbox_backup_time' )
293
231 #setup remove old backups older than294 #setup remove old backups older than
232 self.edit_remove_old_backup_value = get( 'edit_remove_old_backup_value' )295 self.edit_remove_old_backup_value = get( 'edit_remove_old_backup_value' )
233 self.cb_remove_old_backup_unit = get( 'cb_remove_old_backup_unit' )296 self.cb_remove_old_backup_unit = get( 'cb_remove_old_backup_unit' )
@@ -376,6 +439,15 @@
376 self.lbl_backup_day.hide()439 self.lbl_backup_day.hide()
377 self.cb_backup_day.hide()440 self.cb_backup_day.hide()
378441
442 if backup_mode == self.config.CUSTOM_HOUR:
443 self.lbl_backup_time_custom.show()
444 self.txt_backup_time_custom.show()
445 self.txt_backup_time_custom.set_sensitive( True )
446 self.txt_backup_time_custom.set_text( self.config.get_custom_backup_time( self.profile_id ) )
447 else:
448 self.lbl_backup_time_custom.hide()
449 self.txt_backup_time_custom.hide()
450
379 def update_host_user_profile( self, *params ):451 def update_host_user_profile( self, *params ):
380 value = not self.cb_auto_host_user_profile.get_active()452 value = not self.cb_auto_host_user_profile.get_active()
381 self.lbl_host.set_sensitive( value )453 self.lbl_host.set_sensitive( value )
@@ -384,6 +456,20 @@
384 self.txt_user.set_sensitive( value )456 self.txt_user.set_sensitive( value )
385 self.lbl_profile.set_sensitive( value )457 self.lbl_profile.set_sensitive( value )
386 self.txt_profile.set_sensitive( value )458 self.txt_profile.set_sensitive( value )
459
460 def on_combo_modes_changed(self, *params):
461 iter = self.combo_modes.get_active_iter()
462 if iter is None:
463 return
464
465 active_mode = self.store_modes.get_value( iter, 1 )
466 if active_mode != self.mode:
467 for mode in self.config.SNAPSHOT_MODES.keys():
468 if active_mode == mode:
469 getattr(self, 'mode_%s' % mode).show()
470 else:
471 getattr(self, 'mode_%s' % mode).hide()
472 self.mode = active_mode
387473
388 def on_combo_profiles_changed( self, *params ):474 def on_combo_profiles_changed( self, *params ):
389 if self.disable_combo_changed:475 if self.disable_combo_changed:
@@ -425,10 +511,22 @@
425 else:511 else:
426 self.btn_edit_profile.set_sensitive( True )512 self.btn_edit_profile.set_sensitive( True )
427 self.btn_remove_profile.set_sensitive( True )513 self.btn_remove_profile.set_sensitive( True )
514
515 #set mode
516 i = 0
517 iter = self.store_modes.get_iter_first()
518 default_mode = self.config.get_snapshots_mode(self.profile_id)
519 while not iter is None:
520 if self.store_modes.get_value( iter, 1 ) == default_mode:
521 self.combo_modes.set_active( i )
522 break
523 iter = self.store_modes.iter_next( iter )
524 i = i + 1
525 self.on_combo_modes_changed()
428 526
429 #set current folder527 #set current folder
430 #self.fcb_where.set_filename( self.config.get_snapshots_path() )528 #self.fcb_where.set_filename( self.config.get_snapshots_path() )
431 self.edit_where.set_text( self.config.get_snapshots_path( self.profile_id ) )529 self.edit_where.set_text( self.config.get_snapshots_path( self.profile_id, mode = 'local' ) )
432 self.cb_auto_host_user_profile.set_active( self.config.get_auto_host_user_profile( self.profile_id ) )530 self.cb_auto_host_user_profile.set_active( self.config.get_auto_host_user_profile( self.profile_id ) )
433 host, user, profile = self.config.get_host_user_profile( self.profile_id )531 host, user, profile = self.config.get_host_user_profile( self.profile_id )
434 self.txt_host.set_text( host )532 self.txt_host.set_text( host )
@@ -436,6 +534,27 @@
436 self.txt_profile.set_text( profile )534 self.txt_profile.set_text( profile )
437 self.update_host_user_profile()535 self.update_host_user_profile()
438 536
537 #ssh
538 self.txt_ssh_host.set_text( self.config.get_ssh_host( self.profile_id ) )
539 self.txt_ssh_port.set_text( str(self.config.get_ssh_port( self.profile_id )) )
540 self.txt_ssh_user.set_text( self.config.get_ssh_user( self.profile_id ) )
541 self.txt_ssh_path.set_text( self.config.get_snapshots_path_ssh( self.profile_id ) )
542 #set chipher
543 i = 0
544 iter = self.store_ssh_cipher.get_iter_first()
545 default_mode = self.config.get_ssh_cipher(self.profile_id)
546 while not iter is None:
547 if self.store_ssh_cipher.get_value( iter, 1 ) == default_mode:
548 self.combo_ssh_cipher.set_active( i )
549 break
550 iter = self.store_ssh_cipher.iter_next( iter )
551 i = i + 1
552
553## #dummy
554## self.txt_dummy_host.set_text( self.config.get_dummy_host( self.profile_id ) )
555## self.txt_dummy_port.set_text( str(self.config.get_dummy_port( self.profile_id )) )
556## self.txt_dummy_user.set_text( self.config.get_dummy_user( self.profile_id ) )
557
439 #per directory schedule558 #per directory schedule
440 #self.cb_per_directory_schedule.set_active( self.config.get_per_directory_schedule() )559 #self.cb_per_directory_schedule.set_active( self.config.get_per_directory_schedule() )
441 560
@@ -504,6 +623,9 @@
504 623
505 self.on_cb_backup_mode_changed()624 self.on_cb_backup_mode_changed()
506625
626 #setup custom backup time
627 self.txt_backup_time_custom.set_text( self.config.get_custom_backup_time( self.profile_id ) )
628
507 #setup remove old backups older than629 #setup remove old backups older than
508 enabled, value, unit = self.config.get_remove_old_snapshots( self.profile_id )630 enabled, value, unit = self.config.get_remove_old_snapshots( self.profile_id )
509 631
@@ -589,7 +711,12 @@
589 def save_profile( self ):711 def save_profile( self ):
590 #profile_id = self.config.get_current_profile()712 #profile_id = self.config.get_current_profile()
591 #snapshots path713 #snapshots path
592 snapshots_path = self.edit_where.get_text()714 iter = self.combo_modes.get_active_iter()
715 mode = self.store_modes.get_value( iter, 1 )
716 if self.config.SNAPSHOT_MODES[mode][0] is None:
717 snapshots_path = self.edit_where.get_text()
718 else:
719 snapshots_path = self.config.get_snapshots_path(self.profile_id, mode = mode, tmp_mount = True)
593 720
594 #hack721 #hack
595 if snapshots_path.startswith( '//' ):722 if snapshots_path.startswith( '//' ):
@@ -611,6 +738,49 @@
611 while not iter is None:738 while not iter is None:
612 exclude_list.append( self.store_exclude.get_value( iter, 0 ) )739 exclude_list.append( self.store_exclude.get_value( iter, 0 ) )
613 iter = self.store_exclude.iter_next( iter )740 iter = self.store_exclude.iter_next( iter )
741
742 if self.store_backup_mode.get_value( self.cb_backup_mode.get_active_iter(), 1 ) == self.config.CUSTOM_HOUR:
743 if not tools.check_cron_pattern(self.txt_backup_time_custom.get_text()):
744 self.error_handler( _('Custom Hours can only be a comma seperate list of hours (e.g. 8,12,18,23) or */3 for periodic backups every 3 hours') )
745 return False
746
747 mount_kwargs = {}
748
749 #ssh settings
750 ssh_host = self.txt_ssh_host.get_text()
751 ssh_port = self.txt_ssh_port.get_text()
752 ssh_user = self.txt_ssh_user.get_text()
753 ssh_path = self.txt_ssh_path.get_text()
754 iter = self.combo_ssh_cipher.get_active_iter()
755 ssh_cipher = self.store_ssh_cipher.get_value( iter, 1 )
756 if mode == 'ssh':
757 mount_kwargs = { 'host': ssh_host, 'port': int(ssh_port), 'user': ssh_user, 'path': ssh_path, 'cipher': ssh_cipher }
758
759## #dummy settings
760## dummy_host = self.txt_dummy_host.get_text()
761## dummy_port = self.txt_dummy_port.get_text()
762## dummy_user = self.txt_dummy_user.get_text()
763## if mode == 'dummy':
764## #values must have exactly the same Type (str, int or bool)
765## #as they are set in config or you will run into false-positive
766## #HashCollision warnings
767## mount_kwargs = { 'host': dummy_host, 'port': int(dummy_port), 'user': dummy_user }
768
769 if not self.config.SNAPSHOT_MODES[mode][0] is None:
770 #pre_mount_check
771 mnt = mount.Mount(cfg = self.config, profile_id = self.profile_id, tmp_mount = True)
772 try:
773 mnt.pre_mount_check(mode = mode, first_run = True, **mount_kwargs)
774 except mount.MountException as ex:
775 self.error_handler(str(ex))
776 return False
777
778 #okay, lets try to mount
779 try:
780 hash_id = mnt.mount(mode = mode, check = False, **mount_kwargs)
781 except mount.MountException as ex:
782 self.error_handler(str(ex))
783 return False
614 784
615 #check if back folder changed785 #check if back folder changed
616 #if len( self.config.get_snapshots_path() ) > 0 and self.config.get_snapshots_path() != snapshots_path:786 #if len( self.config.get_snapshots_path() ) > 0 and self.config.get_snapshots_path() != snapshots_path:
@@ -620,7 +790,21 @@
620 #ok let's save to config790 #ok let's save to config
621 self.config.set_auto_host_user_profile( self.cb_auto_host_user_profile.get_active(), self.profile_id )791 self.config.set_auto_host_user_profile( self.cb_auto_host_user_profile.get_active(), self.profile_id )
622 self.config.set_host_user_profile( self.txt_host.get_text(), self.txt_user.get_text(), self.txt_profile.get_text(), self.profile_id )792 self.config.set_host_user_profile( self.txt_host.get_text(), self.txt_user.get_text(), self.txt_profile.get_text(), self.profile_id )
623 self.config.set_snapshots_path( snapshots_path, self.profile_id )793 self.config.set_snapshots_path( snapshots_path, self.profile_id , mode)
794
795 self.config.set_snapshots_mode(mode, self.profile_id)
796
797 #save ssh
798 self.config.set_ssh_host(ssh_host, self.profile_id)
799 self.config.set_ssh_port(ssh_port, self.profile_id)
800 self.config.set_ssh_user(ssh_user, self.profile_id)
801 self.config.set_snapshots_path_ssh(ssh_path, self.profile_id)
802 self.config.set_ssh_cipher(ssh_cipher, self.profile_id)
803
804## #save dummy
805## self.config.set_dummy_host(dummy_host, self.profile_id)
806## self.config.set_dummy_port(dummy_port, self.profile_id)
807## self.config.set_dummy_user(dummy_user, self.profile_id)
624808
625 #if not msg is None:809 #if not msg is None:
626 # messagebox.show_error( self.dialog, self.config, msg )810 # messagebox.show_error( self.dialog, self.config, msg )
@@ -634,6 +818,7 @@
634 self.config.set_automatic_backup_time( self.store_backup_time.get_value( self.cb_backup_time.get_active_iter(), 1 ), self.profile_id )818 self.config.set_automatic_backup_time( self.store_backup_time.get_value( self.cb_backup_time.get_active_iter(), 1 ), self.profile_id )
635 self.config.set_automatic_backup_day( self.store_backup_day.get_value( self.cb_backup_day.get_active_iter(), 1 ), self.profile_id )819 self.config.set_automatic_backup_day( self.store_backup_day.get_value( self.cb_backup_day.get_active_iter(), 1 ), self.profile_id )
636 self.config.set_automatic_backup_weekday( self.store_backup_weekday.get_value( self.cb_backup_weekday.get_active_iter(), 1 ), self.profile_id )820 self.config.set_automatic_backup_weekday( self.store_backup_weekday.get_value( self.cb_backup_weekday.get_active_iter(), 1 ), self.profile_id )
821 self.config.set_custom_backup_time( self.txt_backup_time_custom.get_text(), self.profile_id )
637 822
638 #auto-remove snapshots823 #auto-remove snapshots
639 self.config.set_remove_old_snapshots( 824 self.config.set_remove_old_snapshots(
@@ -674,6 +859,15 @@
674 self.config.set_preserve_xattr( self.cb_preserve_xattr.get_active(), self.profile_id )859 self.config.set_preserve_xattr( self.cb_preserve_xattr.get_active(), self.profile_id )
675 self.config.set_copy_unsafe_links( self.cb_copy_unsafe_links.get_active(), self.profile_id )860 self.config.set_copy_unsafe_links( self.cb_copy_unsafe_links.get_active(), self.profile_id )
676 self.config.set_copy_links( self.cb_copy_links.get_active(), self.profile_id )861 self.config.set_copy_links( self.cb_copy_links.get_active(), self.profile_id )
862
863 #umount
864 if not self.config.SNAPSHOT_MODES[mode][0] is None:
865 try:
866 mnt.umount(hash_id = hash_id)
867 except mount.MountException as ex:
868 self.error_handler(str(ex))
869 return False
870 return True
677 871
678 def update_remove_old_backups( self, button ):872 def update_remove_old_backups( self, button ):
679 enabled = self.cb_remove_old_backup.get_active()873 enabled = self.cb_remove_old_backup.get_active()
@@ -716,7 +910,7 @@
716 910
717 self.config.clear_handlers()911 self.config.clear_handlers()
718 self.dialog.destroy()912 self.dialog.destroy()
719 913
720 def update_snapshots_location( self ):914 def update_snapshots_location( self ):
721 '''Update snapshot location dialog'''915 '''Update snapshot location dialog'''
722 self.config.set_question_handler( self.question_handler )916 self.config.set_question_handler( self.question_handler )
@@ -852,7 +1046,8 @@
852 self.dialog.destroy()1046 self.dialog.destroy()
853 1047
854 def validate( self ):1048 def validate( self ):
855 self.save_profile()1049 if not self.save_profile():
1050 return False
856 1051
857 if not self.config.check_config():1052 if not self.config.check_config():
858 return False1053 return False
8591054
=== modified file 'kde4/app.py'
--- kde4/app.py 2012-03-05 16:04:13 +0000
+++ kde4/app.py 2012-10-23 19:40:24 +0000
@@ -37,6 +37,7 @@
37import logger37import logger
38import snapshots38import snapshots
39import guiapplicationinstance39import guiapplicationinstance
40import mount
4041
41from PyQt4.QtGui import *42from PyQt4.QtGui import *
42from PyQt4.QtCore import *43from PyQt4.QtCore import *
@@ -318,6 +319,17 @@
318 settingsdialog.SettingsDialog( self ).update_snapshots_location()319 settingsdialog.SettingsDialog( self ).update_snapshots_location()
319320
320 profile_id = cfg.get_current_profile()321 profile_id = cfg.get_current_profile()
322
323 #mount
324 try:
325 mnt = mount.Mount(cfg = self.config, profile_id = profile_id)
326 hash_id = mnt.mount()
327 except mount.MountException as ex:
328 KMessageBox.error( self, QString.fromUtf8( str(ex) ) )
329 sys.exit(1)
330 else:
331 self.config.set_current_hash_id(hash_id)
332
321 if not cfg.can_backup( profile_id ):333 if not cfg.can_backup( profile_id ):
322 KMessageBox.error( self, QString.fromUtf8( _('Can\'t find snapshots folder.\nIf it is on a removable drive please plug it and then press OK') ) )334 KMessageBox.error( self, QString.fromUtf8( _('Can\'t find snapshots folder.\nIf it is on a removable drive please plug it and then press OK') ) )
323335
@@ -378,6 +390,13 @@
378 self.config.set_int_value( 'kde4.main_window.files_view.sort.column', self.list_files_view.header().sortIndicatorSection() )390 self.config.set_int_value( 'kde4.main_window.files_view.sort.column', self.list_files_view.header().sortIndicatorSection() )
379 self.config.set_bool_value( 'kde4.main_window.files_view.sort.ascending', self.list_files_view.header().sortIndicatorOrder() == Qt.AscendingOrder )391 self.config.set_bool_value( 'kde4.main_window.files_view.sort.ascending', self.list_files_view.header().sortIndicatorOrder() == Qt.AscendingOrder )
380 392
393 #umount
394 try:
395 mnt = mount.Mount(cfg = self.config)
396 mnt.umount(self.config.current_hash_id)
397 except mount.MountException as ex:
398 KMessageBox.error( self, QString.fromUtf8( str(ex) ) )
399
381 self.config.save()400 self.config.save()
382401
383 event.accept()402 event.accept()
@@ -419,8 +438,18 @@
419 return438 return
420 439
421 if profile_id != self.config.get_current_profile():440 if profile_id != self.config.get_current_profile():
441 self.remount(profile_id, self.config.get_current_profile())
422 self.config.set_current_profile( profile_id )442 self.config.set_current_profile( profile_id )
423 self.update_profile()443 self.update_profile()
444
445 def remount( self, new_profile_id, old_profile_id):
446 try:
447 mnt = mount.Mount(cfg = self.config, profile_id = old_profile_id)
448 hash_id = mnt.remount(new_profile_id)
449 except mount.MountException as ex:
450 KMessageBox.error( self, QString.fromUtf8( str(ex) ) )
451 else:
452 self.config.set_current_hash_id(hash_id)
424453
425 def get_default_startup_folder_and_file( self ):454 def get_default_startup_folder_and_file( self ):
426 last_path = self.config.get_str_value( 'gnome.last_path', '' )455 last_path = self.config.get_str_value( 'gnome.last_path', '' )
@@ -779,6 +808,8 @@
779808
780 def on_btn_settings_clicked( self ):809 def on_btn_settings_clicked( self ):
781 if QDialog.Accepted == settingsdialog.SettingsDialog( self ).exec_():810 if QDialog.Accepted == settingsdialog.SettingsDialog( self ).exec_():
811 profile_id = self.config.get_current_profile()
812 self.remount(profile_id, profile_id)
782 self.update_profiles()813 self.update_profiles()
783814
784 def on_btn_about_clicked( self ):815 def on_btn_about_clicked( self ):
785816
=== modified file 'kde4/man/C/backintime-kde4.1.gz'
786Binary files kde4/man/C/backintime-kde4.1.gz 2012-03-06 21:23:31 +0000 and kde4/man/C/backintime-kde4.1.gz 2012-10-23 19:40:24 +0000 differ817Binary files kde4/man/C/backintime-kde4.1.gz 2012-03-06 21:23:31 +0000 and kde4/man/C/backintime-kde4.1.gz 2012-10-23 19:40:24 +0000 differ
=== modified file 'kde4/settingsdialog.py'
--- kde4/settingsdialog.py 2012-02-20 11:30:09 +0000
+++ kde4/settingsdialog.py 2012-10-23 19:40:24 +0000
@@ -32,6 +32,7 @@
32import config32import config
33import tools33import tools
34import kde4tools34import kde4tools
35import mount
3536
3637
37_=gettext.gettext38_=gettext.gettext
@@ -102,9 +103,25 @@
102 tab_widget = QWidget( self )103 tab_widget = QWidget( self )
103 self.tabs_widget.addTab( tab_widget, QString.fromUtf8( _( 'General' ) ) )104 self.tabs_widget.addTab( tab_widget, QString.fromUtf8( _( 'General' ) ) )
104 layout = QVBoxLayout( tab_widget )105 layout = QVBoxLayout( tab_widget )
105106
107 #select mode
108 self.mode = None
109 vlayout = QVBoxLayout()
110 layout.addLayout( vlayout )
111
112 self.lbl_modes = QLabel( QString.fromUtf8( _( 'Mode:' ) ), self )
113 vlayout.addWidget( self.lbl_modes )
114
115 self.combo_modes = KComboBox( self )
116 vlayout.addWidget( self.combo_modes )
117 store_modes = {}
118 for key in self.config.SNAPSHOT_MODES.keys():
119 store_modes[key] = self.config.SNAPSHOT_MODES[key][1]
120 self.fill_combo( self.combo_modes, store_modes )
121
106 #Where to save snapshots122 #Where to save snapshots
107 group_box = QGroupBox( self )123 group_box = QGroupBox( self )
124 self.mode_local = group_box
108 group_box.setTitle( QString.fromUtf8( _( 'Where to save snapshots' ) ) )125 group_box.setTitle( QString.fromUtf8( _( 'Where to save snapshots' ) ) )
109 layout.addWidget( group_box )126 layout.addWidget( group_box )
110127
@@ -120,7 +137,75 @@
120 self.btn_snapshots_path = KPushButton( KIcon( 'folder' ), '', self )137 self.btn_snapshots_path = KPushButton( KIcon( 'folder' ), '', self )
121 hlayout.addWidget( self.btn_snapshots_path )138 hlayout.addWidget( self.btn_snapshots_path )
122 QObject.connect( self.btn_snapshots_path, SIGNAL('clicked()'), self.on_btn_snapshots_path_clicked )139 QObject.connect( self.btn_snapshots_path, SIGNAL('clicked()'), self.on_btn_snapshots_path_clicked )
123140
141 #SSH
142 group_box = QGroupBox( self )
143 self.mode_ssh = group_box
144 group_box.setTitle( QString.fromUtf8( _( 'SSH Settings' ) ) )
145 layout.addWidget( group_box )
146
147 vlayout = QVBoxLayout( group_box )
148
149 hlayout1 = QHBoxLayout()
150 vlayout.addLayout( hlayout1 )
151 hlayout2 = QHBoxLayout()
152 vlayout.addLayout( hlayout2 )
153
154 self.lbl_ssh_host = QLabel( QString.fromUtf8( _( 'Host:' ) ), self )
155 hlayout1.addWidget( self.lbl_ssh_host )
156 self.txt_ssh_host = KLineEdit( self )
157 hlayout1.addWidget( self.txt_ssh_host )
158
159 self.lbl_ssh_port = QLabel( QString.fromUtf8( _( 'Port:' ) ), self )
160 hlayout1.addWidget( self.lbl_ssh_port )
161 self.txt_ssh_port = KLineEdit( self )
162 hlayout1.addWidget( self.txt_ssh_port )
163
164 self.lbl_ssh_user = QLabel( QString.fromUtf8( _( 'User:' ) ), self )
165 hlayout1.addWidget( self.lbl_ssh_user )
166 self.txt_ssh_user = KLineEdit( self )
167 hlayout1.addWidget( self.txt_ssh_user )
168
169 self.lbl_ssh_path = QLabel( QString.fromUtf8( _( 'Path:' ) ), self )
170 hlayout2.addWidget( self.lbl_ssh_path )
171 self.txt_ssh_path = KLineEdit( self )
172 hlayout2.addWidget( self.txt_ssh_path )
173
174 self.lbl_ssh_cipher = QLabel( QString.fromUtf8( _( 'Cipher:' ) ), self )
175 hlayout2.addWidget( self.lbl_ssh_cipher )
176 self.combo_ssh_cipher = KComboBox( self )
177 hlayout2.addWidget( self.combo_ssh_cipher )
178 self.fill_combo( self.combo_ssh_cipher, self.config.SSH_CIPHERS )
179
180## #Dummy
181## group_box = QGroupBox( self )
182## self.mode_dummy = group_box
183## group_box.setTitle( QString.fromUtf8( _( 'Dummy Settings' ) ) )
184## layout.addWidget( group_box )
185##
186## vlayout = QVBoxLayout( group_box )
187##
188## hlayout = QHBoxLayout()
189## vlayout.addLayout( hlayout )
190##
191## self.lbl_dummy_host = QLabel( QString.fromUtf8( _( 'Host:' ) ), self )
192## hlayout.addWidget( self.lbl_dummy_host )
193## self.txt_dummy_host = KLineEdit( self )
194## hlayout.addWidget( self.txt_dummy_host )
195##
196## self.lbl_dummy_port = QLabel( QString.fromUtf8( _( 'Port:' ) ), self )
197## hlayout.addWidget( self.lbl_dummy_port )
198## self.txt_dummy_port = KLineEdit( self )
199## hlayout.addWidget( self.txt_dummy_port )
200##
201## self.lbl_dummy_user = QLabel( QString.fromUtf8( _( 'User:' ) ), self )
202## hlayout.addWidget( self.lbl_dummy_user )
203## self.txt_dummy_user = KLineEdit( self )
204## hlayout.addWidget( self.txt_dummy_user )
205
206 QObject.connect( self.combo_modes, SIGNAL('currentIndexChanged(int)'), self.on_combo_modes_changed )
207 self.on_combo_modes_changed()
208
124 #host, user, profile id209 #host, user, profile id
125 hlayout = QHBoxLayout()210 hlayout = QHBoxLayout()
126 layout.addLayout( hlayout )211 layout.addLayout( hlayout )
@@ -200,6 +285,14 @@
200 for t in xrange( 0, 2300, 100 ):285 for t in xrange( 0, 2300, 100 ):
201 self.combo_automatic_snapshots_time.addItem( QIcon(), QString.fromUtf8( datetime.time( t/100, t%100 ).strftime("%H:%M") ), QVariant( t ) )286 self.combo_automatic_snapshots_time.addItem( QIcon(), QString.fromUtf8( datetime.time( t/100, t%100 ).strftime("%H:%M") ), QVariant( t ) )
202287
288 self.lbl_automatic_snapshots_time_custom = QLabel( QString.fromUtf8( _( 'Hours:' ) ), self )
289 self.lbl_automatic_snapshots_time_custom.setContentsMargins( 5, 0, 0, 0 )
290 self.lbl_automatic_snapshots_time_custom.setAlignment( Qt.AlignRight | Qt.AlignVCenter )
291 glayout.addWidget( self.lbl_automatic_snapshots_time_custom, 4, 0 )
292
293 self.txt_automatic_snapshots_time_custom = KLineEdit( self )
294 glayout.addWidget( self.txt_automatic_snapshots_time_custom, 4, 1 )
295
203 QObject.connect( self.combo_automatic_snapshots, SIGNAL('currentIndexChanged(int)'), self.current_automatic_snapshot_changed )296 QObject.connect( self.combo_automatic_snapshots, SIGNAL('currentIndexChanged(int)'), self.current_automatic_snapshot_changed )
204297
205 #298 #
@@ -470,6 +563,13 @@
470 self.update_profiles()563 self.update_profiles()
471564
472 def update_automatic_snapshot_time( self, backup_mode ):565 def update_automatic_snapshot_time( self, backup_mode ):
566 if backup_mode == self.config.CUSTOM_HOUR:
567 self.lbl_automatic_snapshots_time_custom.show()
568 self.txt_automatic_snapshots_time_custom.show()
569 else:
570 self.lbl_automatic_snapshots_time_custom.hide()
571 self.txt_automatic_snapshots_time_custom.hide()
572
473 if backup_mode == self.config.WEEK:573 if backup_mode == self.config.WEEK:
474 self.lbl_automatic_snapshots_weekday.show()574 self.lbl_automatic_snapshots_weekday.show()
475 self.combo_automatic_snapshots_weekday.show()575 self.combo_automatic_snapshots_weekday.show()
@@ -542,7 +642,23 @@
542 self.btn_remove_profile.setEnabled( True )642 self.btn_remove_profile.setEnabled( True )
543643
544 #TAB: General644 #TAB: General
545 self.edit_snapshots_path.setText( QString.fromUtf8( self.config.get_snapshots_path() ) )645 #mode
646 self.set_combo_value( self.combo_modes, self.config.get_snapshots_mode(), type = 'str' )
647
648 #local
649 self.edit_snapshots_path.setText( QString.fromUtf8( self.config.get_snapshots_path( mode = 'local') ) )
650
651 #ssh
652 self.txt_ssh_host.setText( QString.fromUtf8( self.config.get_ssh_host() ) )
653 self.txt_ssh_port.setText( QString.fromUtf8( str(self.config.get_ssh_port()) ) )
654 self.txt_ssh_user.setText( QString.fromUtf8( self.config.get_ssh_user() ) )
655 self.txt_ssh_path.setText( QString.fromUtf8( self.config.get_snapshots_path_ssh() ) )
656 self.set_combo_value( self.combo_ssh_cipher, self.config.get_ssh_cipher(), type = 'str' )
657
658## #dummy
659## self.txt_dummy_host.setText( QString.fromUtf8( self.config.get_dummy_host() ) )
660## self.txt_dummy_port.setText( QString.fromUtf8( self.config.get_dummy_port() ) )
661## self.txt_dummy_user.setText( QString.fromUtf8( self.config.get_dummy_user() ) )
546662
547 self.cb_auto_host_user_profile.setChecked( self.config.get_auto_host_user_profile() )663 self.cb_auto_host_user_profile.setChecked( self.config.get_auto_host_user_profile() )
548 host, user, profile = self.config.get_host_user_profile()664 host, user, profile = self.config.get_host_user_profile()
@@ -555,6 +671,7 @@
555 self.set_combo_value( self.combo_automatic_snapshots_time, self.config.get_automatic_backup_time() )671 self.set_combo_value( self.combo_automatic_snapshots_time, self.config.get_automatic_backup_time() )
556 self.set_combo_value( self.combo_automatic_snapshots_day, self.config.get_automatic_backup_day() )672 self.set_combo_value( self.combo_automatic_snapshots_day, self.config.get_automatic_backup_day() )
557 self.set_combo_value( self.combo_automatic_snapshots_weekday, self.config.get_automatic_backup_weekday() )673 self.set_combo_value( self.combo_automatic_snapshots_weekday, self.config.get_automatic_backup_weekday() )
674 self.txt_automatic_snapshots_time_custom.setText( self.config.get_custom_backup_time() )
558 self.update_automatic_snapshot_time( self.config.get_automatic_backup_mode() )675 self.update_automatic_snapshot_time( self.config.get_automatic_backup_mode() )
559676
560 #TAB: Include677 #TAB: Include
@@ -619,13 +736,76 @@
619 self.update_min_free_space()736 self.update_min_free_space()
620737
621 def save_profile( self ):738 def save_profile( self ):
739 if self.combo_automatic_snapshots.itemData( self.combo_automatic_snapshots.currentIndex() ).toInt()[0] == self.config.CUSTOM_HOUR:
740 if not tools.check_cron_pattern(str( self.txt_automatic_snapshots_time_custom.text().toUtf8() ) ):
741 self.error_handler( _('Custom Hours can only be a comma seperate list of hours (e.g. 8,12,18,23) or */3 for periodic backups every 3 hours') )
742 return False
743
744 #mode
745 mode = str( self.combo_modes.itemData( self.combo_modes.currentIndex() ).toString().toUtf8() )
746 self.config.set_snapshots_mode( mode )
747 mount_kwargs = {}
748
749 #ssh
750 ssh_host = str( self.txt_ssh_host.text().toUtf8() )
751 ssh_port = str( self.txt_ssh_port.text().toUtf8() )
752 ssh_user = str( self.txt_ssh_user.text().toUtf8() )
753 ssh_path = str( self.txt_ssh_path.text().toUtf8() )
754 ssh_cipher = str( self.combo_ssh_cipher.itemData( self.combo_ssh_cipher.currentIndex() ).toString().toUtf8() )
755 if mode == 'ssh':
756 mount_kwargs = { 'host': ssh_host, 'port': int(ssh_port), 'user': ssh_user, 'path': ssh_path, 'cipher': ssh_cipher }
757
758## #dummy
759## dummy_host = str( self.txt_dummy_host.text().toUtf8() )
760## dummy_port = str( self.txt_dummy_port.text().toUtf8() )
761## dummy_user = str( self.txt_dummy_user.text().toUtf8() )
762## if mode == 'dummy':
763## #values must have exactly the same Type (str, int or bool)
764## #as they are set in config or you will run into false-positive
765## #HashCollision warnings
766## mount_kwargs = { 'host': dummy_host, 'port': int(dummy_port), 'user': dummy_user }
767
768 if not self.config.SNAPSHOT_MODES[mode][0] is None:
769 #pre_mount_check
770 mnt = mount.Mount(cfg = self.config, tmp_mount = True)
771 try:
772 mnt.pre_mount_check(mode = mode, first_run = True, **mount_kwargs)
773 except mount.MountException as ex:
774 self.error_handler(str(ex))
775 return False
776
777 #okay, lets try to mount
778 try:
779 hash_id = mnt.mount(mode = mode, check = False, **mount_kwargs)
780 except mount.MountException as ex:
781 self.error_handler(str(ex))
782 return False
783
622 #snapshots path784 #snapshots path
623 self.config.set_auto_host_user_profile( self.cb_auto_host_user_profile.isChecked() )785 self.config.set_auto_host_user_profile( self.cb_auto_host_user_profile.isChecked() )
624 self.config.set_host_user_profile(786 self.config.set_host_user_profile(
625 str( self.txt_host.text().toUtf8() ),787 str( self.txt_host.text().toUtf8() ),
626 str( self.txt_user.text().toUtf8() ),788 str( self.txt_user.text().toUtf8() ),
627 str( self.txt_profile.text().toUtf8() ) )789 str( self.txt_profile.text().toUtf8() ) )
628 self.config.set_snapshots_path( str( self.edit_snapshots_path.text().toUtf8() ) )790
791 if self.config.SNAPSHOT_MODES[mode][0] is None:
792 snapshots_path = str( self.edit_snapshots_path.text().toUtf8() )
793 else:
794 snapshots_path = self.config.get_snapshots_path(mode = mode, tmp_mount = True)
795
796 self.config.set_snapshots_path( snapshots_path, mode = mode )
797
798 #save ssh
799 self.config.set_ssh_host(ssh_host)
800 self.config.set_ssh_port(ssh_port)
801 self.config.set_ssh_user(ssh_user)
802 self.config.set_snapshots_path_ssh(ssh_path)
803 self.config.set_ssh_cipher(ssh_cipher)
804
805## #save dummy
806## self.config.set_dummy_host(dummy_host)
807## self.config.set_dummy_port(dummy_port)
808## self.config.set_dummy_user(dummy_user)
629809
630 #include list 810 #include list
631 include_list = []811 include_list = []
@@ -648,6 +828,7 @@
648 self.config.set_automatic_backup_time( self.combo_automatic_snapshots_time.itemData( self.combo_automatic_snapshots_time.currentIndex() ).toInt()[0] )828 self.config.set_automatic_backup_time( self.combo_automatic_snapshots_time.itemData( self.combo_automatic_snapshots_time.currentIndex() ).toInt()[0] )
649 self.config.set_automatic_backup_weekday( self.combo_automatic_snapshots_weekday.itemData( self.combo_automatic_snapshots_weekday.currentIndex() ).toInt()[0] )829 self.config.set_automatic_backup_weekday( self.combo_automatic_snapshots_weekday.itemData( self.combo_automatic_snapshots_weekday.currentIndex() ).toInt()[0] )
650 self.config.set_automatic_backup_day( self.combo_automatic_snapshots_day.itemData( self.combo_automatic_snapshots_day.currentIndex() ).toInt()[0] )830 self.config.set_automatic_backup_day( self.combo_automatic_snapshots_day.itemData( self.combo_automatic_snapshots_day.currentIndex() ).toInt()[0] )
831 self.config.set_custom_backup_time( str( self.txt_automatic_snapshots_time_custom.text().toUtf8() ) )
651832
652 #auto-remove833 #auto-remove
653 self.config.set_remove_old_snapshots( 834 self.config.set_remove_old_snapshots(
@@ -684,6 +865,15 @@
684 self.config.set_preserve_xattr( self.cb_preserve_xattr.isChecked() )865 self.config.set_preserve_xattr( self.cb_preserve_xattr.isChecked() )
685 self.config.set_copy_unsafe_links( self.cb_copy_unsafe_links.isChecked() )866 self.config.set_copy_unsafe_links( self.cb_copy_unsafe_links.isChecked() )
686 self.config.set_copy_links( self.cb_copy_links.isChecked() )867 self.config.set_copy_links( self.cb_copy_links.isChecked() )
868
869 #umount
870 if not self.config.SNAPSHOT_MODES[mode][0] is None:
871 try:
872 mnt.umount(hash_id = hash_id)
873 except mount.MountException as ex:
874 self.error_handler(str(ex))
875 return False
876 return True
687877
688 def error_handler( self, message ):878 def error_handler( self, message ):
689 KMessageBox.error( self, QString.fromUtf8( message ) )879 KMessageBox.error( self, QString.fromUtf8( message ) )
@@ -777,14 +967,18 @@
777 for key in keys:967 for key in keys:
778 combo.addItem( QIcon(), QString.fromUtf8( dict[ key ] ), QVariant( key ) )968 combo.addItem( QIcon(), QString.fromUtf8( dict[ key ] ), QVariant( key ) )
779969
780 def set_combo_value( self, combo, value ):970 def set_combo_value( self, combo, value, type = 'int' ):
781 for i in xrange( combo.count() ):971 for i in xrange( combo.count() ):
782 if value == combo.itemData( i ).toInt()[0]:972 if type == 'int' and value == combo.itemData( i ).toInt()[0]:
973 combo.setCurrentIndex( i )
974 break
975 if type == 'str' and value == combo.itemData( i ).toString():
783 combo.setCurrentIndex( i )976 combo.setCurrentIndex( i )
784 break977 break
785978
786 def validate( self ):979 def validate( self ):
787 self.save_profile()980 if not self.save_profile():
981 return False
788982
789 if not self.config.check_config():983 if not self.config.check_config():
790 return False984 return False
@@ -886,7 +1080,21 @@
886 if not self.question_handler( _('Are you sure you want to change snapshots folder ?') ):1080 if not self.question_handler( _('Are you sure you want to change snapshots folder ?') ):
887 return1081 return
888 self.edit_snapshots_path.setText( QString.fromUtf8( self.config.prepare_path( path ) ) )1082 self.edit_snapshots_path.setText( QString.fromUtf8( self.config.prepare_path( path ) ) )
8891083
1084 def on_combo_modes_changed(self, *params):
1085 if len(params) == 0:
1086 index = self.combo_modes.currentIndex()
1087 else:
1088 index = params[0]
1089 active_mode = str( self.combo_modes.itemData( index ).toString().toUtf8() )
1090 if active_mode != self.mode:
1091 for mode in self.config.SNAPSHOT_MODES.keys():
1092 if active_mode == mode:
1093 getattr(self, 'mode_%s' % mode).show()
1094 else:
1095 getattr(self, 'mode_%s' % mode).hide()
1096 self.mode = active_mode
1097
890 def accept( self ):1098 def accept( self ):
891 if self.validate():1099 if self.validate():
892 KDialog.accept( self )1100 KDialog.accept( self )

Subscribers

People subscribed via source and target branches

to all changes: