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
1=== modified file 'AUTHORS'
2--- AUTHORS 2009-11-06 13:00:42 +0000
3+++ AUTHORS 2012-10-23 19:40:24 +0000
4@@ -1,3 +1,4 @@
5 Oprea Dan (<dan@le-web.org>)
6 Bart de Koning (<bratdaking@gmail.com>)
7 Richard Bailey (<rmjb@mail.com>)
8+Germar Reitze (<germar.reitze@gmx.de>)
9
10=== modified file 'CHANGES'
11--- CHANGES 2012-02-22 09:55:08 +0000
12+++ CHANGES 2012-10-23 19:40:24 +0000
13@@ -1,5 +1,10 @@
14 Back In Time
15
16+Version 1.1.0
17+* Add generic mount-framework
18+* Add mode 'SSH' for backups on remote host using ssh protocol.
19+* Fix bug: wrong path if restore system root
20+
21 Version 1.0.10
22 * Add "Restore to ..." in replacement of copy (with or without drag & drop) because copy don't restore user/group/rights
23
24
25=== modified file 'common/backintime.py'
26--- common/backintime.py 2010-11-08 21:11:52 +0000
27+++ common/backintime.py 2012-10-23 19:40:24 +0000
28@@ -26,6 +26,8 @@
29 import logger
30 import snapshots
31 import tools
32+import sshtools
33+import mount
34
35 _=gettext.gettext
36
37@@ -44,9 +46,26 @@
38
39 def take_snapshot( cfg, force = True ):
40 logger.openlog()
41+ _mount(cfg)
42 snapshots.Snapshots( cfg ).take_snapshot( force )
43+ _umount(cfg)
44 logger.closelog()
45
46+def _mount(cfg):
47+ try:
48+ hash_id = mount.Mount(cfg = cfg).mount()
49+ except mount.MountException as ex:
50+ logger.error(str(ex))
51+ sys.exit(1)
52+ else:
53+ cfg.set_current_hash_id(hash_id)
54+
55+def _umount(cfg):
56+ try:
57+ mount.Mount(cfg = cfg).umount(cfg.current_hash_id)
58+ except mount.MountException as ex:
59+ logger.error(str(ex))
60+
61
62 def print_version( cfg, app_name ):
63 print ''
64@@ -79,6 +98,13 @@
65 print '\tShow the ID of the last snapshot (and exit)'
66 print '--last-snapshot-path'
67 print '\tShow the path to the last snapshot (and exit)'
68+ print '--keep-mount'
69+ print '\tDon\'t unmount on exit. Only valid with'
70+ print '\t--snapshots-list-path and --last-snapshot-path.'
71+ print '--unmount'
72+ print '\tUnmount the profile.'
73+ print '--benchmark-cipher [file-size]'
74+ print '\tShow a benchmark of all ciphers for ssh transfer (and exit)'
75 print '-v | --version'
76 print '\tShow version (and exit)'
77 print '--license'
78@@ -94,6 +120,7 @@
79
80 skip = False
81 index = 0
82+ keep_mount = False
83
84 for arg in sys.argv[ 1 : ]:
85 index = index + 1
86@@ -146,18 +173,21 @@
87 if not cfg.is_configured():
88 print "The application is not configured !"
89 else:
90+ _mount(cfg)
91 list = snapshots.Snapshots( cfg ).get_snapshots_list()
92 if len( list ) <= 0:
93 print "There are no snapshots"
94 else:
95 for snapshot_id in list:
96 print "SnapshotID: %s" % snapshot_id
97+ _umount(cfg)
98 sys.exit(0)
99
100 if arg == '--snapshots-list-path':
101 if not cfg.is_configured():
102 print "The application is not configured !"
103 else:
104+ _mount(cfg)
105 s = snapshots.Snapshots( cfg )
106 list = s.get_snapshots_list()
107 if len( list ) <= 0:
108@@ -165,29 +195,60 @@
109 else:
110 for snapshot_id in list:
111 print "SnapshotPath: %s" % s.get_snapshot_path( snapshot_id )
112+ if not keep_mount:
113+ _umount(cfg)
114 sys.exit(0)
115
116 if arg == '--last-snapshot':
117 if not cfg.is_configured():
118 print "The application is not configured !"
119 else:
120+ _mount(cfg)
121 list = snapshots.Snapshots( cfg ).get_snapshots_list()
122 if len( list ) <= 0:
123 print "There are no snapshots"
124 else:
125 print "SnapshotID: %s" % list[0]
126+ _umount(cfg)
127 sys.exit(0)
128
129 if arg == '--last-snapshot-path':
130 if not cfg.is_configured():
131 print "The application is not configured !"
132 else:
133+ _mount(cfg)
134 s = snapshots.Snapshots( cfg )
135 list = s.get_snapshots_list()
136 if len( list ) <= 0:
137 print "There are no snapshots"
138 else:
139 print "SnapshotPath: %s" % s.get_snapshot_path( list[0] )
140+ if not keep_mount:
141+ _umount(cfg)
142+ sys.exit(0)
143+
144+ if arg == '--keep-mount':
145+ keep_mount = True
146+ continue
147+
148+ if arg == '--unmount':
149+ _mount(cfg)
150+ _umount(cfg)
151+ sys.exit(0)
152+
153+ if arg == '--benchmark-cipher':
154+ if not cfg.is_configured():
155+ print "The application is not configured !"
156+ else:
157+ try:
158+ size = sys.argv[index + 1]
159+ except IndexError:
160+ size = '40'
161+ if cfg.get_snapshots_mode() == 'ssh':
162+ ssh = sshtools.SSH(cfg=cfg)
163+ ssh.benchmark_cipher(size)
164+ else:
165+ print('ssh is not configured !')
166 sys.exit(0)
167
168 if arg == '--snapshots' or arg == '-s':
169
170=== modified file 'common/config.py'
171--- common/config.py 2012-03-06 21:23:31 +0000
172+++ common/config.py 2012-10-23 19:40:24 +0000
173@@ -26,6 +26,9 @@
174 import configfile
175 import tools
176 import logger
177+import mount
178+import sshtools
179+##import dummytools
180
181 _=gettext.gettext
182
183@@ -44,6 +47,7 @@
184 _5_MIN = 2
185 _10_MIN = 4
186 HOUR = 10
187+ CUSTOM_HOUR = 15
188 DAY = 20
189 WEEK = 30
190 MONTH = 40
191@@ -58,6 +62,7 @@
192 _5_MIN: _('Every 5 minutes'),
193 _10_MIN: _('Every 10 minutes'),
194 HOUR : _('Every Hour'),
195+ CUSTOM_HOUR : _('Custom Hours'),
196 DAY : _('Every Day'),
197 WEEK : _('Every Week'),
198 MONTH : _('Every Month')
199@@ -71,7 +76,30 @@
200
201 MIN_FREE_SPACE_UNITS = { DISK_UNIT_MB : 'Mb', DISK_UNIT_GB : 'Gb' }
202
203- DEFAULT_EXCLUDE = [ '.gvfs', '.cache*', '[Cc]ache*', '.thumbnails*', '[Tt]rash*', '*.backup*', '*~', os.path.expanduser( '~/Ubuntu One' ), '.dropbox*', '/proc', '/sys', '/dev' ]
204+ DEFAULT_EXCLUDE = [ '.gvfs', '.cache*', '[Cc]ache*', '.thumbnails*', '[Tt]rash*', '*.backup*', '*~', os.path.expanduser( '~/Ubuntu One' ), '.dropbox*', '/proc', '/sys', '/dev' , '/tmp/backintime']
205+
206+ SNAPSHOT_MODES = {
207+ #mode : (<mounttools>, _('ComboBox Text') ),
208+ 'local' : (None, _('Local') ),
209+ 'ssh' : (sshtools.SSH, _('SSH (without password)') )
210+## 'dummy' : (dummytools.Dummy, _('Dummy') )
211+ }
212+
213+ MOUNT_ROOT = '/tmp/backintime'
214+
215+ SSH_CIPHERS = {'default': _('Default'),
216+ 'aes128-ctr': _('AES128-CTR'),
217+ 'aes192-ctr': _('AES192-CTR'),
218+ 'aes256-ctr': _('AES256-CTR'),
219+ 'arcfour256': _('ARCFOUR256'),
220+ 'arcfour128': _('ARCFOUR128'),
221+ 'aes128-cbc': _('AES128-CBC'),
222+ '3des-cbc': _('3DES-CBC'),
223+ 'blowfish-cbc': _('Blowfish-CBC'),
224+ 'cast128-cbc': _('Cast128-CBC'),
225+ 'aes192-cbc': _('AES192-CBC'),
226+ 'aes256-cbc': _('AES256-CBC'),
227+ 'arcfour': _('ARCFOUR') }
228
229 def __init__( self ):
230 configfile.ConfigFileWithProfiles.__init__( self, _('Main profile') )
231@@ -179,6 +207,8 @@
232
233 self.set_int_value( 'config.version', self.CONFIG_VERSION )
234 self.save()
235+
236+ self.current_hash_id = 'local'
237
238 def save( self ):
239 configfile.ConfigFile.save( self, self._LOCAL_CONFIG_PATH )
240@@ -254,8 +284,22 @@
241
242 return user
243
244- def get_snapshots_path( self, profile_id = None ):
245- return self.get_profile_str_value( 'snapshots.path', '', profile_id )
246+ def get_pid(self):
247+ return str(os.getpid())
248+
249+ def get_host(self):
250+ return socket.gethostname()
251+
252+ def get_snapshots_path( self, profile_id = None, mode = None, tmp_mount = False ):
253+ if mode is None:
254+ mode = self.get_snapshots_mode(profile_id)
255+ if self.SNAPSHOT_MODES[mode][0] == None:
256+ #no mount needed
257+ return self.get_profile_str_value( 'snapshots.path', '', profile_id )
258+ else:
259+ #mode need to be mounted; return mountpoint
260+ symlink = self.get_snapshots_symlink(profile_id = profile_id, tmp_mount = tmp_mount)
261+ return os.path.join(self.MOUNT_ROOT, self.get_user(), symlink)
262
263 def get_snapshots_full_path( self, profile_id = None, version = None ):
264 '''Returns the full path for the snapshots: .../backintime/machine/user/profile_id/'''
265@@ -268,7 +312,7 @@
266 host, user, profile = self.get_host_user_profile( profile_id )
267 return os.path.join( self.get_snapshots_path( profile_id ), 'backintime', host, user, profile )
268
269- def set_snapshots_path( self, value, profile_id = None ):
270+ def set_snapshots_path( self, value, profile_id = None, mode = None ):
271 """Sets the snapshot path to value, initializes, and checks it"""
272 if len( value ) <= 0:
273 return False
274@@ -278,6 +322,9 @@
275 # tjoep
276 # return False
277 profile_id = self.get_current_profile()
278+
279+ if mode is None:
280+ mode = self.get_snapshots_mode( profile_id )
281
282 if not os.path.isdir( value ):
283 self.notify_error( _( '%s is not a folder !' ) % value )
284@@ -312,8 +359,100 @@
285 return False
286
287 os.rmdir( check_path )
288- self.set_profile_str_value( 'snapshots.path', value, profile_id )
289- return True
290+ if self.SNAPSHOT_MODES[mode][0] is None:
291+ self.set_profile_str_value( 'snapshots.path', value, profile_id )
292+ return True
293+
294+ def get_snapshots_mode( self, profile_id = None ):
295+ return self.get_profile_str_value( 'snapshots.mode', 'local', profile_id )
296+
297+ def set_snapshots_mode( self, value, profile_id = None ):
298+ self.set_profile_str_value( 'snapshots.mode', value, profile_id )
299+
300+ def get_snapshots_symlink(self, profile_id = None, tmp_mount = False):
301+ if profile_id is None:
302+ profile_id = self.current_profile_id
303+ symlink = '%s_%s' % (profile_id, self.get_pid())
304+ if tmp_mount:
305+ symlink = 'tmp_%s' % symlink
306+ return symlink
307+
308+ def set_current_hash_id(self, hash_id):
309+ self.current_hash_id = hash_id
310+
311+ def get_hash_collision(self):
312+ return self.get_int_value( 'global.hash_collision', 0 )
313+
314+ def increment_hash_collision(self):
315+ value = self.get_hash_collision() + 1
316+ self.set_int_value( 'global.hash_collision', value )
317+
318+ def get_snapshots_path_ssh( self, profile_id = None ):
319+ return self.get_profile_str_value( 'snapshots.ssh.path', './', profile_id )
320+
321+ def get_snapshots_full_path_ssh( self, profile_id = None, version = None ):
322+ '''Returns the full path for the snapshots: .../backintime/machine/user/profile_id/'''
323+ if version is None:
324+ version = self.get_int_value( 'config.version', self.CONFIG_VERSION )
325+
326+ if version < 4:
327+ return os.path.join( self.get_snapshots_path_ssh( profile_id ), 'backintime' )
328+ else:
329+ host, user, profile = self.get_host_user_profile( profile_id )
330+ return os.path.join( self.get_snapshots_path_ssh( profile_id ), 'backintime', host, user, profile )
331+
332+ def set_snapshots_path_ssh( self, value, profile_id = None ):
333+ self.set_profile_str_value( 'snapshots.ssh.path', value, profile_id )
334+ return True
335+
336+ def get_ssh_host( self, profile_id = None ):
337+ return self.get_profile_str_value( 'snapshots.ssh.host', '', profile_id )
338+
339+ def set_ssh_host( self, value, profile_id = None ):
340+ self.set_profile_str_value( 'snapshots.ssh.host', value, profile_id )
341+
342+ def get_ssh_port( self, profile_id = None ):
343+ return self.get_profile_int_value( 'snapshots.ssh.port', '22', profile_id )
344+
345+ def set_ssh_port( self, value, profile_id = None ):
346+ self.set_profile_int_value( 'snapshots.ssh.port', value, profile_id )
347+
348+ def get_ssh_cipher( self, profile_id = None ):
349+ return self.get_profile_str_value( 'snapshots.ssh.cipher', 'default', profile_id )
350+
351+ def set_ssh_cipher( self, value, profile_id = None ):
352+ self.set_profile_str_value( 'snapshots.ssh.cipher', value, profile_id )
353+
354+ def get_ssh_user( self, profile_id = None ):
355+ return self.get_profile_str_value( 'snapshots.ssh.user', self.get_user(), profile_id )
356+
357+ def set_ssh_user( self, value, profile_id = None ):
358+ self.set_profile_str_value( 'snapshots.ssh.user', value, profile_id )
359+
360+ def get_ssh_host_port_user_path(self, profile_id = None ):
361+ host = self.get_ssh_host(profile_id)
362+ port = self.get_ssh_port(profile_id)
363+ user = self.get_ssh_user(profile_id)
364+ path = self.get_snapshots_path_ssh(profile_id)
365+ return (host, port, user, path)
366+
367+## def get_dummy_host( self, profile_id = None ):
368+## return self.get_profile_str_value( 'snapshots.dummy.host', '', profile_id )
369+##
370+## def set_dummy_host( self, value, profile_id = None ):
371+## self.set_profile_str_value( 'snapshots.dummy.host', value, profile_id )
372+##
373+## def get_dummy_port( self, profile_id = None ):
374+## return self.get_profile_int_value( 'snapshots.dummy.port', '22', profile_id )
375+##
376+## def set_dummy_port( self, value, profile_id = None ):
377+## self.set_profile_int_value( 'snapshots.dummy.port', value, profile_id )
378+##
379+## def get_dummy_user( self, profile_id = None ):
380+## return self.get_profile_str_value( 'snapshots.dummy.user', self.get_user(), profile_id )
381+##
382+## def set_dummy_user( self, value, profile_id = None ):
383+## self.set_profile_str_value( 'snapshots.dummy.user', value, profile_id )
384
385 def get_auto_host_user_profile( self, profile_id = None ):
386 return self.get_profile_bool_value( 'snapshots.path.auto', True, profile_id )
387@@ -494,6 +633,12 @@
388 def set_automatic_backup_weekday( self, value, profile_id = None ):
389 self.set_profile_int_value( 'snapshots.automatic_backup_weekday', value, profile_id )
390
391+ def get_custom_backup_time( self, profile_id = None ):
392+ return self.get_profile_str_value( 'snapshots.custom_backup_time', '8,12,18,23', profile_id )
393+
394+ def set_custom_backup_time( self, value, profile_id = None ):
395+ self.set_profile_str_value( 'snapshots.custom_backup_time', value, profile_id )
396+
397 #def get_per_directory_schedule( self, profile_id = None ):
398 # return self.get_profile_bool_value( 'snapshots.expert.per_directory_schedule', False, profile_id )
399
400@@ -816,6 +961,8 @@
401 cron_line = 'echo "{msg}\n*/10 * * * * {cmd}"'
402 if self.HOUR == backup_mode:
403 cron_line = 'echo "{msg}\n0 * * * * {cmd}"'
404+ if self.CUSTOM_HOUR == backup_mode:
405+ cron_line = 'echo "{msg}\n0 ' + self.get_custom_backup_time( profile_id ) + ' * * * {cmd}"'
406 elif self.DAY == backup_mode:
407 cron_line = "echo \"{msg}\n%s %s * * * {cmd}\"" % (minute, hour)
408 elif self.WEEK == backup_mode:
409
410=== modified file 'common/configfile.py'
411--- common/configfile.py 2010-03-05 20:23:32 +0000
412+++ common/configfile.py 2012-10-23 19:40:24 +0000
413@@ -347,4 +347,3 @@
414
415 def set_profile_bool_value( self, key, value, profile_id = None ):
416 self.set_bool_value( self._get_profile_key_( key, profile_id ), value )
417-
418
419=== modified file 'common/debian_specific/control'
420--- common/debian_specific/control 2012-03-06 21:23:31 +0000
421+++ common/debian_specific/control 2012-10-23 19:40:24 +0000
422@@ -6,7 +6,7 @@
423 Maintainer: BIT Team <bit-team@lists.launchpad.net>
424 Homepage: http://backintime.le-web.org
425 Architecture: all
426-Depends: python, rsync, cron
427+Depends: python, rsync, cron, openssh-client, sshfs
428 Recommends: powermgmt-base
429 Conflicts: backintime
430 Replaces: backintime
431
432=== added file 'common/dummytools.py'
433--- common/dummytools.py 1970-01-01 00:00:00 +0000
434+++ common/dummytools.py 2012-10-23 19:40:24 +0000
435@@ -0,0 +1,105 @@
436+# Copyright (c) 2012 Germar Reitze
437+#
438+# This program is free software; you can redistribute it and/or modify
439+# it under the terms of the GNU General Public License as published by
440+# the Free Software Foundation; either version 2 of the License, or
441+# (at your option) any later version.
442+#
443+# This program is distributed in the hope that it will be useful,
444+# but WITHOUT ANY WARRANTY; without even the implied warranty of
445+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
446+# GNU General Public License for more details.
447+#
448+# You should have received a copy of the GNU General Public License along
449+# with this program; if not, write to the Free Software Foundation, Inc.,
450+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
451+
452+import gettext
453+
454+import config
455+import mount
456+
457+_=gettext.gettext
458+
459+class Dummy(mount.MountControl):
460+ """
461+ This is a template for mounting services. For simple mount services
462+ all you need to do is:
463+ - add your settings in gnome|kde/settingsdialog.py (search for the dummy examples)
464+ - add settings in gnome/settingsdialog.glade (copy GtkFrame 'mode_dummy')
465+ - add settings in common/config.py (search for the dummy examples)
466+ - modify a copy of this file
467+
468+ Please use self.mountpoint as your local mountpoint.
469+ This class inherit from mount.MountControl. All methodes from MountControl can
470+ be used exactly like they were in this class.
471+ Methodes from MountControl also can be overriden in here if you need
472+ something different."""
473+ def __init__(self, cfg = None, profile_id = None, hash_id = None, tmp_mount = False, **kwargs):
474+ self.config = cfg
475+ if self.config is None:
476+ self.config = config.Config()
477+
478+ self.profile_id = profile_id
479+ if not self.profile_id:
480+ self.profile_id = self.config.get_current_profile()
481+
482+ self.tmp_mount = tmp_mount
483+ self.hash_id = hash_id
484+
485+ #init MountControl
486+ mount.MountControl.__init__(self)
487+
488+ self.all_kwargs = {}
489+
490+ #First we need to map the settings.
491+ #If <arg> is in kwargs (e.g. if this class is called with dummytools.Dummy(<arg> = <value>)
492+ #this will map self.<arg> to kwargs[<arg>]; else self.<arg> = <default> from config
493+ #e.g. self.setattr_kwargs(<arg>, <default>, **kwargs)
494+ self.setattr_kwargs('mode', self.config.get_snapshots_mode(self.profile_id), **kwargs)
495+ self.setattr_kwargs('hash_collision', self.config.get_hash_collision(), **kwargs)
496+ #start editing from here---------------------------------------------------------
497+ self.setattr_kwargs('user', self.config.get_dummy_user(self.profile_id), **kwargs)
498+ self.setattr_kwargs('host', self.config.get_dummy_host(self.profile_id), **kwargs)
499+ self.setattr_kwargs('port', self.config.get_dummy_port(self.profile_id), **kwargs)
500+
501+ self.set_default_args()
502+
503+ #if self.mountpoint is not the remote snapshot path you can specify
504+ #a subfolder of self.mountpoint for the symlink
505+ self.symlink_subfolder = None
506+
507+ self.log_command = '%s: %s@%s' % (self.mode, self.user, self.host)
508+
509+ def _mount(self):
510+ """mount the service"""
511+ #implement your mountprocess here
512+ pass
513+
514+ def _umount(self):
515+ """umount the service"""
516+ #implement your unmountprocess here
517+ pass
518+
519+ def pre_mount_check(self, first_run = False):
520+ """check what ever conditions must be given for the mount to be done successful
521+ raise MountException( _('Error discription') ) if service can not mount
522+ return True if everything is okay
523+ all pre|post_[u]mount_check can also be used to prepare things or clean up"""
524+ return True
525+
526+ def post_mount_check(self):
527+ """check if mount was successful
528+ raise MountException( _('Error discription') ) if not"""
529+ return True
530+
531+ def pre_umount_check(self):
532+ """check if service is safe to umount
533+ raise MountException( _('Error discription') ) if not"""
534+ return True
535+
536+ def post_umount_check(self):
537+ """check if umount successful
538+ raise MountException( _('Error discription') ) if not"""
539+ return True
540+
541\ No newline at end of file
542
543=== modified file 'common/man/C/backintime.1.gz'
544Binary 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
545=== added file 'common/mount.py'
546--- common/mount.py 1970-01-01 00:00:00 +0000
547+++ common/mount.py 2012-10-23 19:40:24 +0000
548@@ -0,0 +1,421 @@
549+# Copyright (c) 2012 Germar Reitze
550+#
551+# This program is free software; you can redistribute it and/or modify
552+# it under the terms of the GNU General Public License as published by
553+# the Free Software Foundation; either version 2 of the License, or
554+# (at your option) any later version.
555+#
556+# This program is distributed in the hope that it will be useful,
557+# but WITHOUT ANY WARRANTY; without even the implied warranty of
558+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
559+# GNU General Public License for more details.
560+#
561+# You should have received a copy of the GNU General Public License along
562+# with this program; if not, write to the Free Software Foundation, Inc.,
563+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
564+
565+import os
566+import subprocess
567+import socket
568+import json
569+import gettext
570+from zlib import crc32
571+from time import sleep
572+
573+import config
574+import logger
575+
576+_=gettext.gettext
577+
578+class MountException(Exception):
579+ pass
580+
581+class HashCollision(Exception):
582+ pass
583+
584+class Mount(object):
585+ def __init__(self, cfg = None, profile_id = None, tmp_mount = False):
586+ self.config = cfg
587+ if self.config is None:
588+ self.config = config.Config()
589+
590+ self.profile_id = profile_id
591+ if self.profile_id is None:
592+ self.profile_id = self.config.get_current_profile()
593+
594+ self.tmp_mount = tmp_mount
595+
596+ def mount(self, mode = None, check = True, **kwargs):
597+ if mode is None:
598+ mode = self.config.get_snapshots_mode(self.profile_id)
599+
600+ if self.config.SNAPSHOT_MODES[mode][0] is None:
601+ #mode doesn't need to mount
602+ return 'local'
603+ else:
604+ while True:
605+ try:
606+ mounttools = self.config.SNAPSHOT_MODES[mode][0]
607+ tools = mounttools(cfg = self.config, profile_id = self.profile_id, tmp_mount = self.tmp_mount, mode = mode, **kwargs)
608+ return tools.mount(check = check)
609+ except HashCollision as ex:
610+ logger.warning(str(ex))
611+ del tools
612+ check = False
613+ continue
614+ break
615+
616+ def umount(self, hash_id = None):
617+ if hash_id is None:
618+ hash_id = self.config.current_hash_id
619+ if hash_id == 'local':
620+ #mode doesn't need to umount
621+ return
622+ else:
623+ umount_info = os.path.join(self.config.MOUNT_ROOT, self.config.get_user(), 'mnt', hash_id, 'umount')
624+ with open(umount_info, 'r') as f:
625+ data_string = f.read()
626+ f.close()
627+ kwargs = json.loads(data_string)
628+ mode = kwargs.pop('mode')
629+ mounttools = self.config.SNAPSHOT_MODES[mode][0]
630+ tools = mounttools(cfg = self.config, profile_id = self.profile_id, tmp_mount = self.tmp_mount, mode = mode, hash_id = hash_id, **kwargs)
631+ tools.umount()
632+
633+ def pre_mount_check(self, mode = None, first_run = False, **kwargs):
634+ """called by SettingsDialog.save_profile() to check
635+ if settings are correct before saving"""
636+ if mode is None:
637+ mode = self.config.get_snapshots_mode(self.profile_id)
638+
639+ if self.config.SNAPSHOT_MODES[mode][0] is None:
640+ #mode doesn't need to mount
641+ return True
642+ else:
643+ mounttools = self.config.SNAPSHOT_MODES[mode][0]
644+ tools = mounttools(cfg = self.config, profile_id = self.profile_id, tmp_mount = self.tmp_mount, mode = mode, **kwargs)
645+ return tools.pre_mount_check(first_run)
646+
647+ def remount(self, new_profile_id, mode = None, hash_id = None, **kwargs):
648+ """mode <= new profile
649+ kwargs <= new profile
650+ hash_id <= old profile
651+ self.profile_id <= old profile"""
652+ if mode is None:
653+ mode = self.config.get_snapshots_mode(new_profile_id)
654+ if hash_id is None:
655+ hash_id = self.config.current_hash_id
656+
657+ if self.config.SNAPSHOT_MODES[mode][0] is None:
658+ #new profile don't need to mount.
659+ self.umount(hash_id = hash_id)
660+ return 'local'
661+
662+ if hash_id == 'local':
663+ #old profile don't need to umount.
664+ self.profile_id = new_profile_id
665+ return self.mount(mode = mode, **kwargs)
666+
667+ mounttools = self.config.SNAPSHOT_MODES[mode][0]
668+ tools = mounttools(cfg = self.config, profile_id = new_profile_id, tmp_mount = self.tmp_mount, mode = mode, **kwargs)
669+ if tools.compare_remount(hash_id):
670+ #profiles uses the same settings. just swap the symlinks
671+ tools.remove_symlink(profile_id = self.profile_id)
672+ tools.set_symlink(profile_id = new_profile_id, hash_id = hash_id)
673+ return hash_id
674+ else:
675+ #profiles are different. we need to umount and mount again
676+ self.umount(hash_id = hash_id)
677+ self.profile_id = new_profile_id
678+ return self.mount(mode = mode, **kwargs)
679+
680+class MountControl(object):
681+ def __init__(self):
682+ self.local_host = self.config.get_host()
683+ self.local_user = self.config.get_user()
684+ self.pid = self.config.get_pid()
685+
686+ def set_default_args(self):
687+ #self.destination should contain all arguments that are nessesary for mount.
688+ args = self.all_kwargs.keys()
689+ self.destination = '%s:' % self.all_kwargs['mode']
690+ args.remove('mode')
691+ args.sort()
692+ for arg in args:
693+ self.destination += ' %s' % self.all_kwargs[arg]
694+
695+ #unique id for every different mount settings. Similar settings even in
696+ #different profiles will generate the same hash_id and so share the same
697+ #mountpoint
698+ if self.hash_id is None:
699+ self.hash_id = self.hash(self.destination)
700+
701+ self.mount_root = self.config.MOUNT_ROOT
702+ self.mount_user_path = os.path.join(self.mount_root, self.local_user)
703+ self.snapshots_path = self.config.get_snapshots_path(profile_id = self.profile_id, mode = self.mode, tmp_mount = self.tmp_mount)
704+
705+ self.hash_id_path = self.get_hash_id_path()
706+ self.mountpoint = self.get_mountpoint()
707+ self.lock_path = self.get_lock_path()
708+ self.umount_info = self.get_umount_info()
709+
710+ def mount(self, check = True):
711+ self.create_mountstructure()
712+ self.mountprocess_lock_acquire()
713+ try:
714+ if self.is_mounted():
715+ if not self.compare_umount_info():
716+ #We probably have a hash collision
717+ self.config.increment_hash_collision()
718+ raise HashCollision( _('Hash collision occurred in hash_id %s. Incrementing global value hash_collision and try again.') % self.hash_id)
719+ logger.info('Mountpoint %s is already mounted' % self.mountpoint)
720+ else:
721+ if check:
722+ self.pre_mount_check()
723+ self._mount()
724+ self.post_mount_check()
725+ logger.info('mount %s on %s' % (self.log_command, self.mountpoint))
726+ self.write_umount_info()
727+ except Exception:
728+ raise
729+ else:
730+ self.set_mount_lock()
731+ self.set_symlink()
732+ finally:
733+ self.mountprocess_lock_release()
734+ return self.hash_id
735+
736+ def umount(self):
737+ self.mountprocess_lock_acquire()
738+ try:
739+ if not os.path.isdir(self.hash_id_path):
740+ logger.info('Mountpoint %s does not exist.' % self.hash_id_path)
741+ else:
742+ if not self.is_mounted():
743+ logger.info('Mountpoint %s is not mounted' % self.hash_id_path)
744+ else:
745+ if self.check_mount_lock():
746+ logger.info('Mountpoint %s still in use. Keep mounted' % self.mountpoint)
747+ else:
748+ self.pre_umount_check()
749+ self._umount()
750+ self.post_umount_check()
751+ if len(os.listdir(self.mountpoint)) > 0:
752+ logger.warning('Mountpoint %s not empty after unmount' % self.mountpoint)
753+ else:
754+ logger.info('unmount %s from %s' % (self.log_command, self.mountpoint))
755+ except Exception:
756+ raise
757+ else:
758+ self.del_mount_lock()
759+ self.remove_symlink()
760+ finally:
761+ self.mountprocess_lock_release()
762+
763+ def is_mounted(self):
764+ """return True if path is is already mounted"""
765+ try:
766+ subprocess.check_call(['mountpoint', self.mountpoint], stdout=open(os.devnull, 'w'))
767+ except subprocess.CalledProcessError:
768+ if len(os.listdir(self.mountpoint)) > 0:
769+ raise MountException( _('mountpoint %s not empty.') % self.mountpoint)
770+ return False
771+ else:
772+ return True
773+
774+ def create_mountstructure(self):
775+ """ folder structure in /tmp/backintime/<user>/:
776+ mnt/ <= used for mount points
777+ <pid>.lock <= mountprocess lock that will prevent different
778+ processes modifying mountpoints at one time
779+ <hash_id>/ <= will be shared by all profiles with the same mount settings
780+ mountpoint/ <= real mountpoint
781+ umount <= json file with all nessesary args for unmount
782+ locks/ <= for each process you have a <pid>.lock file
783+ <profile id>_<pid>/ <= sym-link to the right path. return by config.get_snapshots_path
784+ (can be ../mnt/<hash_id>/mount_point for ssh or
785+ ../mnt/<hash_id>/<HOST>/<SHARE> for fusesmb ...)
786+ tmp_<profile id>_<pid>/ <= sym-link for testing mountpoints in settingsdialog
787+ """
788+ self.mkdir(self.mount_root, 0777, force_chmod = True)
789+ self.mkdir(self.mount_user_path, 0700)
790+ self.mkdir(os.path.join(self.mount_user_path, 'mnt'), 0700)
791+ self.mkdir(self.hash_id_path, 0700)
792+ self.mkdir(self.mountpoint, 0700)
793+ self.mkdir(self.lock_path, 0700)
794+
795+ def mkdir(self, path, mode = 0777, force_chmod = False):
796+ if not os.path.isdir(path):
797+ os.mkdir(path, mode)
798+ if force_chmod:
799+ #hack: debian and ubuntu won't set go+w on mkdir in tmp
800+ os.chmod(path, mode)
801+
802+ def mountprocess_lock_acquire(self, timeout = 60):
803+ """block while an other process is mounting or unmounting"""
804+ lock_path = os.path.join(self.mount_user_path, 'mnt')
805+ lock_suffix = '.lock'
806+ lock = self.pid + lock_suffix
807+ count = 0
808+ while self.check_locks(lock_path, lock_suffix):
809+ count += 1
810+ if count == timeout:
811+ raise MountException( _('Mountprocess lock timeout') )
812+ sleep(1)
813+
814+ with open(os.path.join(lock_path, lock), 'w') as f:
815+ f.write(self.pid)
816+ f.close()
817+
818+ def mountprocess_lock_release(self):
819+ lock_path = os.path.join(self.mount_user_path, 'mnt')
820+ lock_suffix = '.lock'
821+ lock = os.path.join(lock_path, self.pid + lock_suffix)
822+ if os.path.exists(lock):
823+ os.remove(lock)
824+
825+ def set_mount_lock(self):
826+ """lock mount for this process"""
827+ if self.tmp_mount:
828+ lock_suffix = '.tmp.lock'
829+ else:
830+ lock_suffix = '.lock'
831+ lock = self.pid + lock_suffix
832+ with open(os.path.join(self.lock_path, lock), 'w') as f:
833+ f.write(self.pid)
834+ f.close()
835+
836+ def check_mount_lock(self):
837+ """return True if mount is locked by other processes"""
838+ lock_suffix = '.lock'
839+ return self.check_locks(self.lock_path, lock_suffix)
840+
841+ def del_mount_lock(self):
842+ """remove mount lock for this process"""
843+ if self.tmp_mount:
844+ lock_suffix = '.tmp.lock'
845+ else:
846+ lock_suffix = '.lock'
847+ lock = os.path.join(self.lock_path, self.pid + lock_suffix)
848+ if os.path.exists(lock):
849+ os.remove(lock)
850+
851+ def check_process_alive(self, pid):
852+ """check if process is still alive"""
853+ if os.path.exists(os.path.join('/proc', pid)):
854+ return True
855+ return False
856+
857+ def check_locks(self, path, lock_suffix):
858+ """return True if there are active locks"""
859+ for file in os.listdir(path):
860+ if not file[-len(lock_suffix):] == lock_suffix:
861+ continue
862+ is_tmp = False
863+ if os.path.basename(file)[-len(lock_suffix)-len('.tmp'):-len(lock_suffix)] == '.tmp':
864+ is_tmp = True
865+ if is_tmp:
866+ lock_pid = os.path.basename(file)[:-len('.tmp')-len(lock_suffix)]
867+ else:
868+ lock_pid = os.path.basename(file)[:-len(lock_suffix)]
869+ if lock_pid == self.pid:
870+ if is_tmp == self.tmp_mount:
871+ continue
872+ if self.check_process_alive(lock_pid):
873+ return True
874+ else:
875+ #clean up
876+ os.remove(os.path.join(path, file))
877+ for symlink in os.listdir(self.mount_user_path):
878+ if symlink.endswith('_%s' % lock_pid):
879+ os.remove(os.path.join(self.mount_user_path, symlink))
880+ return False
881+
882+ def setattr_kwargs(self, arg, default, **kwargs):
883+ """if kwargs[arg] exist set self.<arg> to kwargs[arg]
884+ else set self.<arg> to default which should be the value from config"""
885+ if arg in kwargs:
886+ value = kwargs[arg]
887+ else:
888+ value = default
889+ setattr(self, arg, value)
890+ #make dictionary with all used args for umount
891+ self.all_kwargs[arg] = value
892+
893+ def write_umount_info(self):
894+ """dump dictionary self.all_kwargs to umount_info file"""
895+ data_string = json.dumps(self.all_kwargs)
896+ with open(self.umount_info, 'w') as f:
897+ f.write(data_string)
898+ f.close
899+
900+ def read_umount_info(self, umount_info = None):
901+ """load dictionary kwargs from umount_info file"""
902+ if umount_info is None:
903+ umount_info = self.umount_info
904+ with open(umount_info, 'r') as f:
905+ data_string = f.read()
906+ f.close()
907+ return json.loads(data_string)
908+
909+ def compare_umount_info(self, umount_info = None):
910+ """just in case of hash collisions in <hash_id> we compare self.all_kwargs
911+ with the old saved in umount_info file.
912+ return True if both are identical"""
913+ #run self.all_kwargs through json first
914+ current_kwargs = json.loads(json.dumps(self.all_kwargs))
915+ saved_kwargs = self.read_umount_info(umount_info)
916+ if not len(current_kwargs) == len(saved_kwargs):
917+ return False
918+ for arg in current_kwargs.keys():
919+ if not arg in saved_kwargs.keys():
920+ return False
921+ if not current_kwargs[arg] == saved_kwargs[arg]:
922+ return False
923+ return True
924+
925+ def compare_remount(self, old_hash_id):
926+ """return True if profiles are identiacal and we don't need to remount"""
927+ if old_hash_id == self.hash_id:
928+ return self.compare_umount_info(self.get_umount_info(old_hash_id))
929+ return False
930+
931+ def set_symlink(self, profile_id = None, hash_id = None, tmp_mount = None):
932+ if profile_id is None:
933+ profile_id = self.profile_id
934+ if hash_id is None:
935+ hash_id = self.hash_id
936+ if tmp_mount is None:
937+ tmp_mount = self.tmp_mount
938+ dst = self.config.get_snapshots_path(profile_id = profile_id, mode = self.mode, tmp_mount = tmp_mount)
939+ mountpoint = self.get_mountpoint(hash_id)
940+ if self.symlink_subfolder is None:
941+ src = mountpoint
942+ else:
943+ src = os.path.join(mountpoint, self.symlink_subfolder)
944+ os.symlink(src, dst)
945+
946+ def remove_symlink(self, profile_id = None, tmp_mount = None):
947+ if profile_id is None:
948+ profile_id = self.profile_id
949+ if tmp_mount is None:
950+ tmp_mount = self.tmp_mount
951+ os.remove(self.config.get_snapshots_path(profile_id = profile_id, mode = self.mode, tmp_mount = tmp_mount))
952+
953+ def hash(self, str):
954+ """return a hex crc32 hash of str"""
955+ return('%X' % (crc32(str) & 0xFFFFFFFF))
956+
957+ def get_hash_id_path(self, hash_id = None):
958+ if hash_id is None:
959+ hash_id = self.hash_id
960+ return os.path.join(self.mount_user_path, 'mnt', self.hash_id)
961+
962+ def get_mountpoint(self, hash_id = None):
963+ return os.path.join(self.get_hash_id_path(hash_id), 'mountpoint')
964+
965+ def get_lock_path(self, hash_id = None):
966+ return os.path.join(self.get_hash_id_path(hash_id), 'locks')
967+
968+ def get_umount_info(self, hash_id = None):
969+ return os.path.join(self.get_hash_id_path(hash_id), 'umount')
970
971=== modified file 'common/snapshots.py'
972--- common/snapshots.py 2012-03-05 10:06:01 +0000
973+++ common/snapshots.py 2012-10-23 19:40:24 +0000
974@@ -27,6 +27,7 @@
975 import pwd
976 import grp
977 import socket
978+import subprocess
979
980 import config
981 import configfile
982@@ -112,6 +113,11 @@
983 #print path
984 return path
985
986+ def get_snapshot_path_ssh( self, date ):
987+ profile_id = self.config.get_current_profile()
988+ path = os.path.join( self.config.get_snapshots_full_path_ssh( profile_id ), self.get_snapshot_id( date ) )
989+ return path
990+
991 def get_snapshot_info_path( self, date ):
992 return os.path.join( self.get_snapshot_path( date ), 'info' )
993
994@@ -132,6 +138,14 @@
995 def get_snapshot_path_to( self, snapshot_id, toPath = '/' ):
996 return os.path.join( self._get_snapshot_data_path( snapshot_id ), toPath[ 1 : ] )
997
998+ def _get_snapshot_data_path_ssh( self, snapshot_id ):
999+ if len( snapshot_id ) <= 1:
1000+ return '/';
1001+ return os.path.join( self.get_snapshot_path_ssh( snapshot_id ), 'backup' )
1002+
1003+ def get_snapshot_path_to_ssh( self, snapshot_id, toPath = '/' ):
1004+ return os.path.join( self._get_snapshot_data_path_ssh( snapshot_id ), toPath[ 1 : ] )
1005+
1006 def can_open_path( self, snapshot_id, full_path ):
1007 #full_path = self.get_snapshot_path_to( snapshot_id, path )
1008 if not os.path.exists( full_path ):
1009@@ -452,6 +466,18 @@
1010 def restore( self, snapshot_id, path, callback = None, restore_to = '' ):
1011 if restore_to.endswith('/'):
1012 restore_to = restore_to[ : -1 ]
1013+
1014+ #ssh
1015+ ssh = False
1016+ if self.config.get_snapshots_mode() == 'ssh':
1017+ ssh = True
1018+ (ssh_host, ssh_port, ssh_user, ssh_path) = self.config.get_ssh_host_port_user_path()
1019+ ssh_cipher = self.config.get_ssh_cipher()
1020+ if ssh_cipher == 'default':
1021+ ssh_cipher_suffix = ''
1022+ else:
1023+ ssh_cipher_suffix = '-c %s' % ssh_cipher
1024+ rsync_ssh_suffix = '--rsh="ssh -p %s %s" "%s@%s:' % ( str(ssh_port), ssh_cipher_suffix, ssh_user, ssh_host )
1025
1026 logger.info( "Restore: %s to: %s" % (path, restore_to) )
1027
1028@@ -466,6 +492,8 @@
1029 cmd = cmd + "--backup --suffix=%s " % backup_suffix
1030 #cmd = cmd + '--chmod=+w '
1031 src_base = self.get_snapshot_path_to( snapshot_id )
1032+ if ssh:
1033+ src_base = self.get_snapshot_path_to_ssh( snapshot_id )
1034 src_path = path
1035 src_delta = 0
1036 if len(restore_to) > 0:
1037@@ -475,17 +503,24 @@
1038 items = os.path.split(src_path)
1039 aux = items[0]
1040 if aux.startswith('/'):
1041- aux = aux[1:]
1042- src_base = os.path.join(src_base, aux) + '/'
1043+ aux = aux[1:]
1044+ if len(aux) > 0: #bugfix: restore system root ended in <src_base>//.<src_path>
1045+ src_base = os.path.join(src_base, aux) + '/'
1046 src_path = '/' + items[1]
1047- src_delta = len(items[0])
1048+ if items[0] == '/':
1049+ src_delta = 0
1050+ else:
1051+ src_delta = len(items[0])
1052
1053 #print "src_base: %s" % src_base
1054 #print "src_path: %s" % src_path
1055 #print "src_delta: %s" % src_delta
1056 #print "snapshot_id: %s" % snapshot_id
1057
1058- cmd = cmd + "\"%s.%s\" %s" % ( src_base, src_path, restore_to + '/' )
1059+ if ssh:
1060+ cmd = cmd + rsync_ssh_suffix + "%s.%s\" %s" % ( src_base, src_path, restore_to + '/' )
1061+ else:
1062+ cmd = cmd + "\"%s.%s\" %s" % ( src_base, src_path, restore_to + '/' )
1063 self.restore_callback( callback, True, cmd )
1064 self._execute( cmd, callback )
1065
1066@@ -600,13 +635,33 @@
1067 def remove_snapshot( self, snapshot_id ):
1068 if len( snapshot_id ) <= 1:
1069 return
1070-
1071- path = self.get_snapshot_path( snapshot_id )
1072- #cmd = "chmod -R u+rwx \"%s\"" % path
1073- cmd = "find \"%s\" -type d -exec chmod u+wx {} \\;" % path #Debian patch
1074- self._execute( cmd )
1075- cmd = "rm -rfv \"%s\"" % path
1076- self._execute( cmd )
1077+ #ssh
1078+ profile_id = self.config.get_current_profile()
1079+ ssh = False
1080+ if self.config.get_snapshots_mode() == 'ssh':
1081+ ssh = True
1082+ (ssh_host, ssh_port, ssh_user, ssh_path) = self.config.get_ssh_host_port_user_path(profile_id)
1083+ ssh_cipher = self.config.get_ssh_cipher(profile_id)
1084+ if ssh_cipher == 'default':
1085+ ssh_cipher_suffix = ''
1086+ else:
1087+ ssh_cipher_suffix = '-c %s' % ssh_cipher
1088+ cmd_ssh = 'ssh -p %s %s %s@%s ' % ( ssh_port, ssh_cipher_suffix, ssh_user, ssh_host )
1089+
1090+ if not ssh:
1091+ path = self.get_snapshot_path( snapshot_id )
1092+ #cmd = "chmod -R u+rwx \"%s\"" % path
1093+ cmd = "find \"%s\" -type d -exec chmod u+wx {} \\;" % path #Debian patch
1094+ self._execute( cmd )
1095+ cmd = "rm -rf \"%s\"" % path
1096+ self._execute( cmd )
1097+ else:
1098+ path = self.get_snapshot_path_ssh( snapshot_id )
1099+ #cmd = "chmod -R u+rwx \"%s\"" % path
1100+ cmd = cmd_ssh + '\'find \"%s\" -type d -exec chmod u+wx \"{}\" \\;\'' % path #Debian patch
1101+ self._execute( cmd )
1102+ cmd = cmd_ssh + "rm -rf \"%s\"" % path
1103+ self._execute( cmd )
1104
1105 def copy_snapshot( self, snapshot_id, new_folder ):
1106 '''Copies a known snapshot to a new location'''
1107@@ -1016,6 +1071,22 @@
1108 new_snapshot_id = 'new_snapshot'
1109 new_snapshot_path = self.get_snapshot_path( new_snapshot_id )
1110
1111+ #ssh
1112+ profile_id = self.config.get_current_profile()
1113+ ssh = False
1114+ if self.config.get_snapshots_mode() == 'ssh':
1115+ ssh = True
1116+ (ssh_host, ssh_port, ssh_user, ssh_path) = self.config.get_ssh_host_port_user_path(profile_id)
1117+ ssh_cipher = self.config.get_ssh_cipher(profile_id)
1118+ if ssh_cipher == 'default':
1119+ ssh_cipher_suffix = ''
1120+ else:
1121+ ssh_cipher_suffix = '-c %s' % ssh_cipher
1122+ rsync_ssh_suffix = '--rsh="ssh -p %s %s" "%s@%s:' % ( str(ssh_port), ssh_cipher_suffix, ssh_user, ssh_host )
1123+ cmd_ssh = 'ssh -p %s %s %s@%s ' % ( str(ssh_port), ssh_cipher_suffix, ssh_user, ssh_host )
1124+ new_snapshot_path_ssh = self.get_snapshot_path_ssh( new_snapshot_id )
1125+ new_snapshot_path_to_ssh = self.get_snapshot_path_to_ssh( new_snapshot_id )
1126+
1127 if os.path.exists( new_snapshot_path ):
1128 #self._execute( "find \"%s\" -type d -exec chmod +w {} \;" % new_snapshot_path )
1129 #self._execute( "chmod -R u+rwx \"%s\"" % new_snapshot_path )
1130@@ -1101,7 +1172,11 @@
1131 logger.info( "Compare with old snapshot: %s" % prev_snapshot_id )
1132
1133 prev_snapshot_folder = self.get_snapshot_path_to( prev_snapshot_id )
1134+ prev_snapshot_folder_ssh = self.get_snapshot_path_to_ssh( prev_snapshot_id )
1135 cmd = rsync_prefix + ' -i --dry-run --out-format="BACKINTIME: %i %n%L"' + rsync_suffix + '"' + prev_snapshot_folder + '"'
1136+ if ssh:
1137+ cmd = rsync_prefix + ' -i --dry-run --out-format="BACKINTIME: %i %n%L"' + rsync_suffix
1138+ cmd += rsync_ssh_suffix + prev_snapshot_folder_ssh + '"'
1139 params = [ prev_snapshot_folder, False ]
1140 #try_cmd = self._execute_output( cmd, self._exec_rsync_compare_callback, prev_snapshot_name )
1141 self.append_to_take_snapshot_log( '[I] ' + cmd, 3 )
1142@@ -1135,21 +1210,33 @@
1143 #if force or len( ignore_folders ) == 0:
1144
1145 prev_snapshot_path = self.get_snapshot_path_to( prev_snapshot_id )
1146+ prev_snapshot_path_ssh = self.get_snapshot_path_to_ssh( prev_snapshot_id )
1147
1148 #make source snapshot folders rw to allow cp -al
1149- self._execute( "find \"%s\" -type d -exec chmod u+wx {} \\;" % prev_snapshot_path ) #Debian patch
1150+ if not ssh:
1151+ self._execute( "find \"%s\" -type d -exec chmod u+wx {} \\;" % prev_snapshot_path ) #Debian patch
1152+ else:
1153+ self._execute( cmd_ssh + '\'find \"%s\" -type d -exec chmod u+wx \"{}\" \\;\'' % prev_snapshot_path_ssh ) #Debian patch
1154
1155 #clone snapshot
1156 cmd = "cp -aRl \"%s\"* \"%s\"" % ( prev_snapshot_path, new_snapshot_path_to )
1157+ if ssh:
1158+ cmd = cmd_ssh + "cp -aRl \"%s\"* \"%s\"" % ( prev_snapshot_path_ssh, new_snapshot_path_to_ssh )
1159 self.append_to_take_snapshot_log( '[I] ' + cmd, 3 )
1160 cmd_ret_val = self._execute( cmd )
1161 self.append_to_take_snapshot_log( "[I] returns: %s" % cmd_ret_val, 3 )
1162
1163 #make source snapshot folders read-only
1164- self._execute( "find \"%s\" -type d -exec chmod a-w {} \\;" % prev_snapshot_path ) #Debian patch
1165+ if not ssh:
1166+ self._execute( "find \"%s\" -type d -exec chmod a-w {} \\;" % prev_snapshot_path ) #Debian patch
1167+ else:
1168+ self._execute( cmd_ssh + '\'find \"%s\" -type d -exec chmod a-w \"{}\" \\;\'' % prev_snapshot_path_ssh ) #Debian patch
1169
1170 #make snapshot items rw to allow xopy xattr
1171- self._execute( "chmod -R a+w \"%s\"" % new_snapshot_path )
1172+ if not ssh:
1173+ self._execute( "chmod -R a+w \"%s\"" % new_snapshot_path )
1174+ else:
1175+ self._execute( cmd_ssh + "chmod -R a+w \"%s\"" % new_snapshot_path_ssh )
1176
1177 #else:
1178 # for folder in include_folders:
1179@@ -1165,6 +1252,8 @@
1180 #sync changed folders
1181 logger.info( "Call rsync to take the snapshot" )
1182 cmd = rsync_prefix + ' -v ' + rsync_suffix + '"' + new_snapshot_path_to + '"'
1183+ if ssh:
1184+ cmd = rsync_prefix + ' -v ' + rsync_suffix + rsync_ssh_suffix + new_snapshot_path_to_ssh + '"'
1185 self.set_take_snapshot_message( 0, _('Take snapshot') )
1186
1187 params = [False]
1188@@ -1174,12 +1263,19 @@
1189 has_errors = False
1190 if params[0]:
1191 if not self.config.continue_on_errors():
1192- self._execute( "find \"%s\" -type d -exec chmod u+wx {} \\;" % new_snapshot_path ) #Debian patch
1193- self._execute( "rm -rf \"%s\"" % new_snapshot_path )
1194+ if not ssh:
1195+ self._execute( "find \"%s\" -type d -exec chmod u+wx {} \\;" % new_snapshot_path ) #Debian patch
1196+ self._execute( "rm -rf \"%s\"" % new_snapshot_path )
1197+ else:
1198+ self._execute( cmd_ssh + '\'find \"%s\" -type d -exec chmod u+wx \"{}\" \\;\'' % new_snapshot_path_ssh ) #Debian patch
1199+ self._execute( cmd_ssh + "rm -rf \"%s\"" % new_snapshot_path_ssh )
1200
1201 #fix previous snapshot: make read-only again
1202 if len( prev_snapshot_id ) > 0:
1203- self._execute( "chmod -R a-w \"%s\"" % self.get_snapshot_path_to( prev_snapshot_id ) )
1204+ if not ssh:
1205+ self._execute( "chmod -R a-w \"%s\"" % self.get_snapshot_path_to( prev_snapshot_id ) )
1206+ else:
1207+ self._execute( cmd_ssh + "chmod -R a-w \"%s\"" % self.get_snapshot_path_to_ssh( prev_snapshot_id ) )
1208
1209 return [ False, True ]
1210
1211@@ -1199,12 +1295,34 @@
1212 path_to_explore = self.get_snapshot_path_to( new_snapshot_id ).rstrip( '/' )
1213 fileinfo_dict = {}
1214
1215- for path, dirs, files in os.walk( path_to_explore ):
1216- dirs.extend( files )
1217- for item in dirs:
1218- item_path = os.path.join( path, item )[ len( path_to_explore ) : ]
1219- fileinfo_dict[item_path] = 1
1220- self._save_path_info( fileinfo, item_path )
1221+ permission_done = False
1222+ if ssh:
1223+ path_to_explore_ssh = self.get_snapshot_path_to_ssh( new_snapshot_id ).rstrip( '/' )
1224+ cmd = ['ssh', '-p', str(ssh_port)]
1225+ if not ssh_cipher == 'default':
1226+ cmd.extend(['-c', ssh_cipher])
1227+ cmd.extend(['%s@%s' % (ssh_user, ssh_host)])
1228+ cmd.extend(['find', path_to_explore_ssh, '-name', '\*', '-print'])
1229+
1230+ find = subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
1231+ output, err = find.communicate()
1232+ if len(err) > 0:
1233+ logger.warning('Save permission over ssh failed. Retry normal methode')
1234+ else:
1235+ for line in output.split('\n'):
1236+ if not len(line) == 0:
1237+ item_path = line[ len( path_to_explore_ssh ) : ]
1238+ fileinfo_dict[item_path] = 1
1239+ self._save_path_info( fileinfo, item_path )
1240+ permission_done = True
1241+
1242+ if not permission_done:
1243+ for path, dirs, files in os.walk( path_to_explore ):
1244+ dirs.extend( files )
1245+ for item in dirs:
1246+ item_path = os.path.join( path, item )[ len( path_to_explore ) : ]
1247+ fileinfo_dict[item_path] = 1
1248+ self._save_path_info( fileinfo, item_path )
1249
1250 # We now copy on forehand, so copying afterwards is not necessary anymore
1251 ##copy ignored folders
1252@@ -1267,7 +1385,11 @@
1253
1254 #rename snapshot
1255 snapshot_path = self.get_snapshot_path( snapshot_id )
1256- os.system( "mv \"%s\" \"%s\"" % ( new_snapshot_path, snapshot_path ) )
1257+ snapshot_path_ssh = self.get_snapshot_path_ssh( snapshot_id )
1258+ if not ssh:
1259+ os.system( "mv \"%s\" \"%s\"" % ( new_snapshot_path, snapshot_path ) )
1260+ else:
1261+ os.system( cmd_ssh + "mv \"%s\" \"%s\"" % ( new_snapshot_path_ssh, snapshot_path_ssh ) )
1262 if not os.path.exists( snapshot_path ):
1263 logger.error( "Can't rename %s to %s" % ( new_snapshot_path, snapshot_path ) )
1264 self.set_take_snapshot_message( 1, _('Can\'t rename %s to %s') % ( new_snapshot_path, snapshot_path ) )
1265@@ -1275,11 +1397,17 @@
1266 return [ False, True ]
1267
1268 #make new snapshot read-only
1269- self._execute( "chmod -R a-w \"%s\"" % snapshot_path )
1270+ if not ssh:
1271+ self._execute( "chmod -R a-w \"%s\"" % snapshot_path )
1272+ else:
1273+ self._execute( cmd_ssh + "chmod -R a-w \"%s\"" % snapshot_path_ssh )
1274
1275 #fix previous snapshot: make read-only again
1276 if len( prev_snapshot_id ) > 0:
1277- self._execute( "chmod -R a-w \"%s\"" % self.get_snapshot_path_to( prev_snapshot_id ) )
1278+ if not ssh:
1279+ self._execute( "chmod -R a-w \"%s\"" % self.get_snapshot_path_to( prev_snapshot_id ) )
1280+ else:
1281+ self._execute( cmd_ssh + "chmod -R a-w \"%s\"" % self.get_snapshot_path_to_ssh( prev_snapshot_id ) )
1282
1283 return [ True, has_errors ]
1284
1285
1286=== added file 'common/sshtools.py'
1287--- common/sshtools.py 1970-01-01 00:00:00 +0000
1288+++ common/sshtools.py 2012-10-23 19:40:24 +0000
1289@@ -0,0 +1,309 @@
1290+# Copyright (c) 2012 Germar Reitze
1291+#
1292+# This program is free software; you can redistribute it and/or modify
1293+# it under the terms of the GNU General Public License as published by
1294+# the Free Software Foundation; either version 2 of the License, or
1295+# (at your option) any later version.
1296+#
1297+# This program is distributed in the hope that it will be useful,
1298+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1299+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1300+# GNU General Public License for more details.
1301+#
1302+# You should have received a copy of the GNU General Public License along
1303+# with this program; if not, write to the Free Software Foundation, Inc.,
1304+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1305+
1306+import os
1307+import grp
1308+import subprocess
1309+import gettext
1310+import string
1311+import random
1312+import tempfile
1313+from time import sleep
1314+
1315+import config
1316+import mount
1317+import logger
1318+import tools
1319+
1320+_=gettext.gettext
1321+
1322+class SSH(mount.MountControl):
1323+ """
1324+ Mount remote path with sshfs. The real take_snapshot process will use
1325+ rsync over ssh. Other commands run remote over ssh.
1326+ """
1327+ def __init__(self, cfg = None, profile_id = None, hash_id = None, tmp_mount = False, **kwargs):
1328+ self.config = cfg
1329+ if self.config is None:
1330+ self.config = config.Config()
1331+
1332+ self.profile_id = profile_id
1333+ if not self.profile_id:
1334+ self.profile_id = self.config.get_current_profile()
1335+
1336+ self.tmp_mount = tmp_mount
1337+ self.hash_id = hash_id
1338+
1339+ #init MountControl
1340+ mount.MountControl.__init__(self)
1341+
1342+ self.all_kwargs = {}
1343+
1344+ #First we need to map the settings.
1345+ self.setattr_kwargs('mode', self.config.get_snapshots_mode(self.profile_id), **kwargs)
1346+ self.setattr_kwargs('hash_collision', self.config.get_hash_collision(), **kwargs)
1347+ #start editing from here---------------------------------------------------------
1348+ self.setattr_kwargs('user', self.config.get_ssh_user(self.profile_id), **kwargs)
1349+ self.setattr_kwargs('host', self.config.get_ssh_host(self.profile_id), **kwargs)
1350+ self.setattr_kwargs('port', self.config.get_ssh_port(self.profile_id), **kwargs)
1351+ self.setattr_kwargs('path', self.config.get_snapshots_path_ssh(self.profile_id), **kwargs)
1352+ self.setattr_kwargs('cipher', self.config.get_ssh_cipher(self.profile_id), **kwargs)
1353+
1354+ self.set_default_args()
1355+
1356+ self.symlink_subfolder = None
1357+ self.user_host_path = '%s@%s:%s' % (self.user, self.host, self.path)
1358+ self.log_command = '%s: %s' % (self.mode, self.user_host_path)
1359+
1360+ def _mount(self):
1361+ """mount the service"""
1362+ sshfs = ['sshfs', '-p', str(self.port)]
1363+ if not self.cipher == 'default':
1364+ sshfs.extend(['-o', 'Ciphers=%s' % self.cipher])
1365+ sshfs.extend([self.user_host_path, self.mountpoint])
1366+ #bugfix: sshfs doesn't mount if locale in LC_ALL is not available on remote host
1367+ #LANG or other envirnoment variable are no problem.
1368+ env = os.environ.copy()
1369+ if 'LC_ALL' in env.keys():
1370+ env['LC_ALL'] = 'C'
1371+ try:
1372+ subprocess.check_call(sshfs, env = env)
1373+ except subprocess.CalledProcessError as ex:
1374+ raise mount.MountException( _('Can\'t mount %s') % ' '.join(sshfs))
1375+
1376+ def _umount(self):
1377+ """umount the service"""
1378+ try:
1379+ subprocess.check_call(['fusermount', '-u', self.mountpoint])
1380+ except subprocess.CalledProcessError as ex:
1381+ raise mount.MountException( _('Can\'t unmount sshfs %s') % self.mountpoint)
1382+
1383+ def pre_mount_check(self, first_run = False):
1384+ """check what ever conditions must be given for the mount to be done successful
1385+ raise MountException( _('Error discription') ) if service can not mount
1386+ return True if everything is okay
1387+ all pre|post_[u]mount_check can also be used to prepare things or clean up"""
1388+ self.check_ping_host()
1389+ if first_run:
1390+ self.check_fuse()
1391+ self.check_known_hosts()
1392+ self.check_login()
1393+ if first_run:
1394+ self.check_cipher()
1395+ self.check_remote_folder()
1396+ if first_run:
1397+ self.check_remote_commands()
1398+ return True
1399+
1400+ def post_mount_check(self):
1401+ """check if mount was successful
1402+ raise MountException( _('Error discription') ) if not"""
1403+ return True
1404+
1405+ def pre_umount_check(self):
1406+ """check if service is safe to umount
1407+ raise MountException( _('Error discription') ) if not"""
1408+ return True
1409+
1410+ def post_umount_check(self):
1411+ """check if umount successful
1412+ raise MountException( _('Error discription') ) if not"""
1413+ return True
1414+
1415+ def check_fuse(self):
1416+ """check if sshfs is installed and user is part of group fuse"""
1417+ if not self.pathexists('sshfs'):
1418+ raise mount.MountException( _('sshfs not found. Please install e.g. \'apt-get install sshfs\'') )
1419+ user = self.config.get_user()
1420+ fuse_grp_members = grp.getgrnam('fuse')[3]
1421+ if not user in fuse_grp_members:
1422+ 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))
1423+
1424+ def pathexists(self, filename):
1425+ """Checks if 'filename' is present in the system PATH.
1426+ In other words, it checks if os.execvp(filename, ...) will work.
1427+ shameless stolen from GnuPGInterface;)"""
1428+ pathenv = os.getenv("PATH")
1429+ path = pathenv.split(":")
1430+ for directory in path:
1431+ fullpath = os.path.join(directory, filename)
1432+ if (os.path.exists(fullpath)):
1433+ return True
1434+ return False
1435+
1436+ def check_login(self):
1437+ """check passwordless authentication to host"""
1438+ ssh = ['ssh', '-o', 'PreferredAuthentications=publickey']
1439+ ssh.extend(['-p', str(self.port), self.user + '@' + self.host])
1440+ ssh.extend(['echo', '"Hello"'])
1441+ try:
1442+ subprocess.check_call(ssh, stdout=open(os.devnull, 'w'))
1443+ except subprocess.CalledProcessError:
1444+ raise mount.MountException( _('Password-less authentication for %s@%s failed. Look at \'man backintime\' for further instructions.') % (self.user, self.host))
1445+
1446+ def check_cipher(self):
1447+ """check if both host and localhost support cipher"""
1448+ if not self.cipher == 'default':
1449+ ssh = ['ssh']
1450+ ssh.extend(['-o', 'Ciphers=%s' % self.cipher])
1451+ ssh.extend(['-p', str(self.port), self.user + '@' + self.host, 'echo', '"Hello"'])
1452+ err = subprocess.Popen(ssh, stdout=open(os.devnull, 'w'), stderr=subprocess.PIPE).communicate()[1]
1453+ if err:
1454+ raise mount.MountException( _('Cipher %s failed for %s:\n%s') % (self.cipher, self.host, err))
1455+
1456+ def benchmark_cipher(self, size = '40'):
1457+ import tempfile
1458+ temp = tempfile.mkstemp()[1]
1459+ print('create random data file')
1460+ subprocess.call(['dd', 'if=/dev/urandom', 'of=%s' % temp, 'bs=1M', 'count=%s' % size])
1461+ keys = self.config.SSH_CIPHERS.keys()
1462+ keys.sort()
1463+ for cipher in keys:
1464+ if cipher == 'default':
1465+ continue
1466+ print('%s:' % cipher)
1467+ for i in range(2):
1468+ subprocess.call(['scp', '-p', str(self.port), '-c', cipher, temp, self.user_host_path])
1469+ subprocess.call(['ssh', '%s@%s' % (self.user, self.host), 'rm', os.path.join(self.path, os.path.basename(temp))])
1470+ os.remove(temp)
1471+
1472+ def check_known_hosts(self):
1473+ """check ssh_known_hosts"""
1474+ 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)
1475+ if output.find('Host %s found' % self.host) < 0:
1476+ raise mount.MountException( _('%s not found in ssh_known_hosts.') % self.host)
1477+
1478+ def check_remote_folder(self):
1479+ """check if remote folder exists and is write- and executable.
1480+ Create folder if it doesn't exist."""
1481+ cmd = 'd=0;'
1482+ cmd += '[[ -e %s ]] || d=1;' % self.path #path doesn't exist. set d=1 to indicate
1483+ cmd += '[[ $d -eq 1 ]] && mkdir %s; err=$?;' % self.path #create path, get errorcode from mkdir
1484+ cmd += '[[ $d -eq 1 ]] && exit $err;' #return errorcode from mkdir
1485+ cmd += '[[ -d %s ]] || exit 11;' % self.path #path is no directory
1486+ cmd += '[[ -w %s ]] || exit 12;' % self.path #path is not writeable
1487+ cmd += '[[ -x %s ]] || exit 13;' % self.path #path is not executable
1488+ cmd += 'exit 20' #everything is fine
1489+ try:
1490+ subprocess.check_call(['ssh', '-p', str(self.port), self.user + '@' + self.host, cmd], stdout=open(os.devnull, 'w'))
1491+ except subprocess.CalledProcessError as ex:
1492+ if ex.returncode == 20:
1493+ #clean exit
1494+ pass
1495+ elif ex.returncode == 11:
1496+ raise mount.MountException( _('Remote path exists but is not a directory:\n %s') % self.path)
1497+ elif ex.returncode == 12:
1498+ raise mount.MountException( _('Remote path is not writeable:\n %s') % self.path)
1499+ elif ex.returncode == 13:
1500+ raise mount.MountException( _('Remote path is not executable:\n %s') % self.path)
1501+ else:
1502+ raise mount.MountException( _('Couldn\'t create remote path:\n %s') % self.path)
1503+ else:
1504+ #returncode is 0
1505+ logger.info('Create remote folder %s' % self.path)
1506+
1507+ def check_ping_host(self):
1508+ try:
1509+ subprocess.check_call(['ping', '-q', '-c3', '-l3', self.host], stdout = open(os.devnull, 'w') )
1510+ except subprocess.CalledProcessError:
1511+ raise mount.MountException( _('Ping %s failed. Host is down or wrong address.') % self.host)
1512+
1513+ def check_remote_commands(self):
1514+ """try all relevant commands for take_snapshot on remote host.
1515+ specialy embedded Linux devices using 'BusyBox' sometimes doesn't
1516+ support everything that is need to run backintime.
1517+ also check for hardlink-support on remote host.
1518+ """
1519+ #check rsync
1520+ tmp_file = tempfile.mkstemp()[1]
1521+ rsync = tools.get_rsync_prefix( self.config ) + ' --dry-run --chmod=Du+wx %s ' % tmp_file
1522+
1523+ if self.cipher == 'default':
1524+ ssh_cipher_suffix = ''
1525+ else:
1526+ ssh_cipher_suffix = '-c %s' % self.cipher
1527+ rsync += '--rsh="ssh -p %s %s" ' % ( str(self.port), ssh_cipher_suffix)
1528+ rsync += '"%s@%s:%s"' % (self.user, self.host, self.path)
1529+
1530+ #use os.system for compatiblity with snapshots.py
1531+ err = os.system(rsync)
1532+ if err:
1533+ os.remove(tmp_file)
1534+ raise mount.MountException( _('Remote host %s doesn\'t support \'%s\':\n%s\nLook at \'man backintime\' for further instructions') % (self.host, rsync, err))
1535+ os.remove(tmp_file)
1536+
1537+ #check cp chmod find and rm
1538+ remote_tmp_dir = os.path.join(self.path, 'tmp_%s' % self.random_id())
1539+ cmd = 'tmp=%s ; ' % remote_tmp_dir
1540+ #first define a function to clean up and exit
1541+ cmd += 'cleanup(){ '
1542+ cmd += '[[ -e $tmp/a ]] && rm $tmp/a >/dev/null 2>&1; '
1543+ cmd += '[[ -e $tmp/b ]] && rm $tmp/b >/dev/null 2>&1; '
1544+ cmd += '[[ -e $tmp ]] && rmdir $tmp >/dev/null 2>&1; '
1545+ cmd += 'exit $1; }; '
1546+ #create tmp_RANDOM dir and file a
1547+ cmd += '[[ -e $tmp ]] || mkdir $tmp; touch $tmp/a; '
1548+ #try to create hardlink b from a
1549+ cmd += 'echo \"cp -aRl SOURCE DEST\"; cp -aRl $tmp/a $tmp/b >/dev/null; err_cp=$?; '
1550+ cmd += '[[ $err_cp -ne 0 ]] && cleanup $err_cp; '
1551+ #list inodes of a and b
1552+ cmd += 'ls -i $tmp/a; ls -i $tmp/b; '
1553+ #try to chmod
1554+ cmd += 'echo \"chmod u+rw FILE\"; chmod u+rw $tmp/a >/dev/null; err_chmod=$?; '
1555+ cmd += '[[ $err_chmod -ne 0 ]] && cleanup $err_chmod; '
1556+ #try to find and chmod
1557+ cmd += 'echo \"find PATH -type f -exec chmod u-wx \"{}\" \\;\"; '
1558+ cmd += 'find $tmp -type f -exec chmod u-wx \"{}\" \\; >/dev/null; err_find=$?; '
1559+ cmd += '[[ $err_find -ne 0 ]] && cleanup $err_find; '
1560+ #try to rm -rf
1561+ cmd += 'echo \"rm -rf PATH\"; rm -rf $tmp >/dev/null; err_rm=$?; '
1562+ cmd += '[[ $err_rm -ne 0 ]] && cleanup $err_rm; '
1563+ #if we end up here, everything should be fine
1564+ cmd += 'echo \"done\"'
1565+ output, err = subprocess.Popen(['ssh', '-p', str(self.port), self.user + '@' + self.host, cmd],
1566+ stdout=subprocess.PIPE,
1567+ stderr=subprocess.PIPE).communicate()
1568+
1569+## print('ERROR: %s' % err)
1570+## print('OUTPUT: %s' % output)
1571+ output_split = output.split('\n')
1572+ while True:
1573+ if len(output_split) > 0 and len(output_split[-1]) == 0:
1574+ output_split = output_split[:-1]
1575+ else:
1576+ break
1577+ if err or not output_split[-1].startswith('done'):
1578+ for command in ('cp', 'chmod', 'find', 'rm'):
1579+ if output_split[-1].startswith(command):
1580+ 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))
1581+ raise mount.MountException( _('Check commands on host %s returned unknown error:\n%s\nLook at \'man backintime\' for further instructions') % (self.host, err))
1582+
1583+ i = 1
1584+ inode1 = 'ABC'
1585+ inode2 = 'DEF'
1586+ for line in output_split:
1587+ if line.startswith('cp'):
1588+ try:
1589+ inode1 = output_split[i].split(' ')[0]
1590+ inode2 = output_split[i+1].split(' ')[0]
1591+ except IndexError:
1592+ pass
1593+ if not inode1 == inode2:
1594+ raise mount.MountException( _('Remote host %s doesn\'t support hardlinks') % self.host)
1595+ i += 1
1596+
1597+ def random_id(self, size=6, chars=string.ascii_uppercase + string.digits):
1598+ return ''.join(random.choice(chars) for x in range(size))
1599
1600=== modified file 'common/tools.py'
1601--- common/tools.py 2012-02-20 13:12:41 +0000
1602+++ common/tools.py 2012-10-23 19:40:24 +0000
1603@@ -399,7 +399,27 @@
1604 obj = os.stat(path)
1605 unique_key = (obj.st_size, int(obj.st_mtime))
1606 return unique_key
1607-
1608+
1609+def check_cron_pattern(str):
1610+ '''check if str look like '0,10,13,15,17,20,23' or '*/6' '''
1611+ if str.find(' ') >= 0:
1612+ return False
1613+ try:
1614+ if str.startswith('*/'):
1615+ if int(str[2:]) <= 24:
1616+ return True
1617+ else:
1618+ return False
1619+ list = str.split(',')
1620+ for s in list:
1621+ if int(s) <= 24:
1622+ continue
1623+ else:
1624+ return False
1625+ return True
1626+ except ValueError:
1627+ return False
1628+
1629 #
1630 #
1631 class UniquenessSet:
1632
1633=== modified file 'gnome/app.py'
1634--- gnome/app.py 2012-02-22 09:55:08 +0000
1635+++ gnome/app.py 2012-10-23 19:40:24 +0000
1636@@ -44,6 +44,7 @@
1637 import snapshots
1638 import guiapplicationinstance
1639 import tools
1640+import mount
1641
1642 import settingsdialog
1643 import logviewdialog
1644@@ -309,20 +310,31 @@
1645
1646 if not self.config.is_configured():
1647 return
1648-
1649- if self.snapshots.has_old_snapshots():
1650- settingsdialog.SettingsDialog( self.config, self.snapshots, self ).update_snapshots_location()
1651-
1652- profile_id = self.config.get_current_profile()
1653- if not self.config.can_backup( profile_id ):
1654- 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') )
1655+
1656+ if self.snapshots.has_old_snapshots():
1657+ settingsdialog.SettingsDialog( self.config, self.snapshots, self ).update_snapshots_location()
1658+
1659+ profile_id = self.config.get_current_profile()
1660+
1661+ #mount
1662+ try:
1663+ mnt = mount.Mount(cfg = self.config, profile_id = profile_id)
1664+ hash_id = mnt.mount()
1665+ except mount.MountException as ex:
1666+ messagebox.show_error( self.window, self.config, str(ex) )
1667+ sys.exit(1)
1668+ else:
1669+ self.config.set_current_hash_id(hash_id)
1670+
1671+ if not self.config.can_backup( profile_id ):
1672+ 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') )
1673
1674 self.update_profiles()
1675 self.update_backup_info()
1676 gobject.timeout_add( 1000, self.update_backup_info )
1677
1678 def on_combo_profiles_changed( self, *params ):
1679- if self.disable_combo_changed:
1680+ if self.disable_combo_changed:
1681 return
1682
1683 iter = self.combo_profiles.get_active_iter()
1684@@ -335,6 +347,7 @@
1685 if not first_update_all and profile_id == self.config.get_current_profile():
1686 return
1687
1688+ self.remount(profile_id, self.config.get_current_profile())
1689 self.config.set_current_profile( profile_id )
1690 self.first_update_all = False
1691 self.update_all( first_update_all )
1692@@ -352,7 +365,7 @@
1693 iter = self.store_profiles.append( [ self.config.get_profile_name( profile_id ), profile_id ] )
1694 if profile_id == self.config.get_current_profile():
1695 select_iter = iter
1696-
1697+
1698 self.disable_combo_changed = False
1699
1700 if not select_iter is None:
1701@@ -467,6 +480,15 @@
1702 self.fill_places()
1703 self.fill_time_line( False )
1704 self.update_folder_view( 1, selected_file, show_snapshots )
1705+
1706+ def remount( self, new_profile_id, old_profile_id):
1707+ try:
1708+ mnt = mount.Mount(cfg = self.config, profile_id = old_profile_id)
1709+ hash_id = mnt.remount(new_profile_id)
1710+ except mount.MountException as ex:
1711+ messagebox.show_error( self.window, self.config, str(ex) )
1712+ else:
1713+ self.config.set_current_hash_id(hash_id)
1714
1715 def places_pix_renderer_function( self, column, renderer, model, iter, user_data ):
1716 if len( model.get_value( iter, 1 ) ) == 0:
1717@@ -731,6 +753,13 @@
1718 self.config.set_int_value( 'gnome.main_window.hpaned2', main_window_hpaned2 )
1719 self.config.set_str_value( 'gnome.last_path', self.folder_path )
1720 self.config.set_bool_value( 'gnome.show_hidden_files', self.show_hidden_files )
1721+
1722+ #mount
1723+ try:
1724+ mnt = mount.Mount(cfg = self.config)
1725+ mnt.umount(self.config.current_hash_id)
1726+ except mount.MountException as ex:
1727+ messagebox.show_error( self.window, self.config, str(ex) )
1728
1729 self.config.save()
1730 self.window.destroy()
1731@@ -966,10 +995,22 @@
1732 def on_btn_settings_clicked( self, button ):
1733 snapshots_full_path = self.config.get_snapshots_full_path()
1734 include_folders = self.config.get_include()
1735+ hash_id = self.config.current_hash_id
1736
1737 settingsdialog.SettingsDialog( self.config, self.snapshots, self ).run()
1738+
1739+ #mount
1740+ try:
1741+ mnt = mount.Mount(cfg = self.config)
1742+ new_hash_id = mnt.remount(self.config.get_current_profile())
1743+ except mount.MountException as ex:
1744+ messagebox.show_error( self.window, self.config, str(ex) )
1745+ else:
1746+ self.config.set_current_hash_id(new_hash_id)
1747
1748- if snapshots_full_path != self.config.get_snapshots_full_path() or include_folders != self.config.get_include():
1749+ if snapshots_full_path != self.config.get_snapshots_full_path() \
1750+ or include_folders != self.config.get_include() \
1751+ or hash_id != self.config.current_hash_id:
1752 self.update_all( False )
1753 self.update_profiles()
1754
1755
1756=== modified file 'gnome/man/C/backintime-gnome.1.gz'
1757Binary 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
1758=== modified file 'gnome/settingsdialog.glade'
1759--- gnome/settingsdialog.glade 2012-02-20 11:30:09 +0000
1760+++ gnome/settingsdialog.glade 2012-10-23 19:40:24 +0000
1761@@ -177,239 +177,607 @@
1762 <property name="border_width">5</property>
1763 <property name="spacing">5</property>
1764 <child>
1765- <object class="GtkFrame" id="frame1">
1766+ <object class="GtkFrame" id="select_modes">
1767 <property name="visible">True</property>
1768 <property name="can_focus">False</property>
1769 <property name="label_xalign">0</property>
1770 <property name="shadow_type">none</property>
1771 <child>
1772+ <object class="GtkHBox" id="hbox_select_modes">
1773+ <property name="visible">True</property>
1774+ <property name="can_focus">False</property>
1775+ <child>
1776+ <object class="GtkLabel" id="lbl_select_modes">
1777+ <property name="visible">True</property>
1778+ <property name="can_focus">False</property>
1779+ <property name="xalign">1</property>
1780+ <property name="label" translatable="yes">Mode:</property>
1781+ </object>
1782+ <packing>
1783+ <property name="expand">False</property>
1784+ <property name="fill">True</property>
1785+ <property name="position">0</property>
1786+ </packing>
1787+ </child>
1788+ <child>
1789+ <object class="GtkComboBox" id="combo_modes">
1790+ <property name="visible">True</property>
1791+ <property name="can_focus">False</property>
1792+ <signal name="changed" handler="on_combo_modes_changed" swapped="no"/>
1793+ </object>
1794+ <packing>
1795+ <property name="expand">True</property>
1796+ <property name="fill">True</property>
1797+ <property name="position">1</property>
1798+ </packing>
1799+ </child>
1800+ </object>
1801+ </child>
1802+ </object>
1803+ <packing>
1804+ <property name="expand">False</property>
1805+ <property name="fill">True</property>
1806+ <property name="position">0</property>
1807+ </packing>
1808+ </child>
1809+ <child>
1810+ <object class="GtkFrame" id="mode_local">
1811+ <property name="can_focus">False</property>
1812+ <property name="label_xalign">0</property>
1813+ <property name="shadow_type">none</property>
1814+ <child>
1815 <object class="GtkAlignment" id="alignment1">
1816 <property name="visible">True</property>
1817 <property name="can_focus">False</property>
1818 <property name="left_padding">12</property>
1819 <child>
1820- <object class="GtkVBox" id="vbox1">
1821- <property name="visible">True</property>
1822- <property name="can_focus">False</property>
1823- <property name="spacing">2</property>
1824- <child>
1825- <object class="GtkHBox" id="hbox4">
1826- <property name="visible">True</property>
1827- <property name="can_focus">False</property>
1828- <child>
1829- <object class="GtkEntry" id="edit_where">
1830- <property name="visible">True</property>
1831- <property name="can_focus">True</property>
1832- <property name="editable">False</property>
1833- <property name="invisible_char">•</property>
1834- <property name="primary_icon_activatable">False</property>
1835- <property name="secondary_icon_activatable">False</property>
1836- <property name="primary_icon_sensitive">True</property>
1837- <property name="secondary_icon_sensitive">True</property>
1838- </object>
1839- <packing>
1840- <property name="expand">True</property>
1841- <property name="fill">True</property>
1842- <property name="position">0</property>
1843- </packing>
1844- </child>
1845- <child>
1846- <object class="GtkButton" id="btn_where">
1847- <property name="visible">True</property>
1848- <property name="can_focus">True</property>
1849- <property name="receives_default">True</property>
1850+ <object class="GtkHBox" id="hbox4">
1851+ <property name="visible">True</property>
1852+ <property name="can_focus">False</property>
1853+ <child>
1854+ <object class="GtkEntry" id="edit_where">
1855+ <property name="visible">True</property>
1856+ <property name="can_focus">True</property>
1857+ <property name="editable">False</property>
1858+ <property name="invisible_char">•</property>
1859+ <property name="primary_icon_activatable">False</property>
1860+ <property name="secondary_icon_activatable">False</property>
1861+ <property name="primary_icon_sensitive">True</property>
1862+ <property name="secondary_icon_sensitive">True</property>
1863+ </object>
1864+ <packing>
1865+ <property name="expand">True</property>
1866+ <property name="fill">True</property>
1867+ <property name="position">0</property>
1868+ </packing>
1869+ </child>
1870+ <child>
1871+ <object class="GtkButton" id="btn_where">
1872+ <property name="visible">True</property>
1873+ <property name="can_focus">True</property>
1874+ <property name="receives_default">True</property>
1875+ <property name="use_action_appearance">False</property>
1876+ <signal name="clicked" handler="on_btn_where_clicked" swapped="no"/>
1877+ <child>
1878+ <object class="GtkImage" id="image1">
1879+ <property name="visible">True</property>
1880+ <property name="can_focus">False</property>
1881+ <property name="stock">gtk-directory</property>
1882+ </object>
1883+ </child>
1884+ </object>
1885+ <packing>
1886+ <property name="expand">False</property>
1887+ <property name="fill">True</property>
1888+ <property name="position">1</property>
1889+ </packing>
1890+ </child>
1891+ </object>
1892+ </child>
1893+ </object>
1894+ </child>
1895+ <child type="label">
1896+ <object class="GtkLabel" id="lbl_where">
1897+ <property name="visible">True</property>
1898+ <property name="can_focus">False</property>
1899+ <property name="label" translatable="yes">&lt;b&gt;Where to save snapshots&lt;/b&gt;</property>
1900+ <property name="use_markup">True</property>
1901+ </object>
1902+ </child>
1903+ </object>
1904+ <packing>
1905+ <property name="expand">False</property>
1906+ <property name="fill">True</property>
1907+ <property name="position">10</property>
1908+ </packing>
1909+ </child>
1910+ <child>
1911+ <object class="GtkFrame" id="mode_ssh">
1912+ <property name="can_focus">False</property>
1913+ <property name="label_xalign">0</property>
1914+ <property name="shadow_type">none</property>
1915+ <child>
1916+ <object class="GtkAlignment" id="alignment_ssh">
1917+ <property name="visible">True</property>
1918+ <property name="can_focus">False</property>
1919+ <property name="left_padding">12</property>
1920+ <child>
1921+ <object class="GtkVBox" id="vbox_ssh">
1922+ <property name="visible">True</property>
1923+ <property name="can_focus">False</property>
1924+ <property name="spacing">2</property>
1925+ <child>
1926+ <object class="GtkHBox" id="hbox_ssh1">
1927+ <property name="visible">True</property>
1928+ <property name="can_focus">False</property>
1929+ <property name="spacing">2</property>
1930+ <child>
1931+ <object class="GtkLabel" id="lbl_ssh_host">
1932+ <property name="visible">True</property>
1933+ <property name="can_focus">False</property>
1934+ <property name="xalign">1</property>
1935+ <property name="label" translatable="yes">Host:</property>
1936+ <property name="width_chars">8</property>
1937+ </object>
1938+ <packing>
1939+ <property name="expand">False</property>
1940+ <property name="fill">True</property>
1941+ <property name="position">0</property>
1942+ </packing>
1943+ </child>
1944+ <child>
1945+ <object class="GtkEntry" id="txt_ssh_host">
1946+ <property name="visible">True</property>
1947+ <property name="can_focus">True</property>
1948+ <property name="invisible_char">•</property>
1949+ <property name="primary_icon_activatable">False</property>
1950+ <property name="secondary_icon_activatable">False</property>
1951+ <property name="primary_icon_sensitive">True</property>
1952+ <property name="secondary_icon_sensitive">True</property>
1953+ </object>
1954+ <packing>
1955+ <property name="expand">True</property>
1956+ <property name="fill">True</property>
1957+ <property name="position">1</property>
1958+ </packing>
1959+ </child>
1960+ <child>
1961+ <object class="GtkLabel" id="lbl_ssh_port">
1962+ <property name="visible">True</property>
1963+ <property name="can_focus">False</property>
1964+ <property name="xalign">1</property>
1965+ <property name="label" translatable="yes">Port:</property>
1966+ <property name="width_chars">8</property>
1967+ </object>
1968+ <packing>
1969+ <property name="expand">False</property>
1970+ <property name="fill">True</property>
1971+ <property name="position">2</property>
1972+ </packing>
1973+ </child>
1974+ <child>
1975+ <object class="GtkEntry" id="txt_ssh_port">
1976+ <property name="visible">True</property>
1977+ <property name="can_focus">True</property>
1978+ <property name="invisible_char">•</property>
1979+ <property name="primary_icon_activatable">False</property>
1980+ <property name="secondary_icon_activatable">False</property>
1981+ <property name="primary_icon_sensitive">True</property>
1982+ <property name="secondary_icon_sensitive">True</property>
1983+ </object>
1984+ <packing>
1985+ <property name="expand">True</property>
1986+ <property name="fill">True</property>
1987+ <property name="position">3</property>
1988+ </packing>
1989+ </child>
1990+ <child>
1991+ <object class="GtkLabel" id="lbl_ssh_user">
1992+ <property name="visible">True</property>
1993+ <property name="can_focus">False</property>
1994+ <property name="xalign">1</property>
1995+ <property name="label" translatable="yes">User:</property>
1996+ <property name="width_chars">8</property>
1997+ </object>
1998+ <packing>
1999+ <property name="expand">False</property>
2000+ <property name="fill">True</property>
2001+ <property name="position">4</property>
2002+ </packing>
2003+ </child>
2004+ <child>
2005+ <object class="GtkEntry" id="txt_ssh_user">
2006+ <property name="visible">True</property>
2007+ <property name="can_focus">True</property>
2008+ <property name="invisible_char">•</property>
2009+ <property name="primary_icon_activatable">False</property>
2010+ <property name="secondary_icon_activatable">False</property>
2011+ <property name="primary_icon_sensitive">True</property>
2012+ <property name="secondary_icon_sensitive">True</property>
2013+ </object>
2014+ <packing>
2015+ <property name="expand">True</property>
2016+ <property name="fill">True</property>
2017+ <property name="position">5</property>
2018+ </packing>
2019+ </child>
2020+ </object>
2021+ <packing>
2022+ <property name="expand">True</property>
2023+ <property name="fill">True</property>
2024+ <property name="position">0</property>
2025+ </packing>
2026+ </child>
2027+ <child>
2028+ <object class="GtkHBox" id="hbox_ssh2">
2029+ <property name="visible">True</property>
2030+ <property name="can_focus">False</property>
2031+ <property name="spacing">2</property>
2032+ <child>
2033+ <object class="GtkLabel" id="lbl_ssh_path">
2034+ <property name="visible">True</property>
2035+ <property name="can_focus">False</property>
2036+ <property name="xalign">1</property>
2037+ <property name="label" translatable="yes">Path:</property>
2038+ <property name="width_chars">8</property>
2039+ </object>
2040+ <packing>
2041+ <property name="expand">False</property>
2042+ <property name="fill">True</property>
2043+ <property name="position">0</property>
2044+ </packing>
2045+ </child>
2046+ <child>
2047+ <object class="GtkEntry" id="txt_ssh_path">
2048+ <property name="visible">True</property>
2049+ <property name="can_focus">True</property>
2050+ <property name="invisible_char">•</property>
2051+ <property name="primary_icon_activatable">False</property>
2052+ <property name="secondary_icon_activatable">False</property>
2053+ <property name="primary_icon_sensitive">True</property>
2054+ <property name="secondary_icon_sensitive">True</property>
2055+ </object>
2056+ <packing>
2057+ <property name="expand">True</property>
2058+ <property name="fill">True</property>
2059+ <property name="position">1</property>
2060+ </packing>
2061+ </child>
2062+ <child>
2063+ <object class="GtkLabel" id="lbl_ssh_cipher">
2064+ <property name="visible">True</property>
2065+ <property name="can_focus">False</property>
2066+ <property name="xalign">1</property>
2067+ <property name="label" translatable="yes">Cipher:</property>
2068+ <property name="width_chars">8</property>
2069+ </object>
2070+ <packing>
2071+ <property name="expand">False</property>
2072+ <property name="fill">True</property>
2073+ <property name="position">2</property>
2074+ </packing>
2075+ </child>
2076+ <child>
2077+ <object class="GtkComboBox" id="combo_ssh_cipher">
2078+ <property name="visible">True</property>
2079+ <property name="can_focus">False</property>
2080+ </object>
2081+ <packing>
2082+ <property name="expand">True</property>
2083+ <property name="fill">True</property>
2084+ <property name="position">3</property>
2085+ </packing>
2086+ </child>
2087+ </object>
2088+ <packing>
2089+ <property name="expand">True</property>
2090+ <property name="fill">True</property>
2091+ <property name="position">1</property>
2092+ </packing>
2093+ </child>
2094+ </object>
2095+ </child>
2096+ </object>
2097+ </child>
2098+ <child type="label">
2099+ <object class="GtkLabel" id="lbl_ssh">
2100+ <property name="visible">True</property>
2101+ <property name="can_focus">False</property>
2102+ <property name="label" translatable="yes">&lt;b&gt;SSH Settings&lt;/b&gt;</property>
2103+ <property name="use_markup">True</property>
2104+ </object>
2105+ </child>
2106+ </object>
2107+ <packing>
2108+ <property name="expand">False</property>
2109+ <property name="fill">True</property>
2110+ <property name="position">20</property>
2111+ </packing>
2112+ </child>
2113+ <child>
2114+ <object class="GtkFrame" id="mode_dummy">
2115+ <property name="can_focus">False</property>
2116+ <property name="label_xalign">0</property>
2117+ <property name="shadow_type">none</property>
2118+ <child>
2119+ <object class="GtkAlignment" id="alignment_dummy">
2120+ <property name="visible">True</property>
2121+ <property name="can_focus">False</property>
2122+ <property name="left_padding">12</property>
2123+ <child>
2124+ <object class="GtkHBox" id="hbox_dummy">
2125+ <property name="visible">True</property>
2126+ <property name="can_focus">False</property>
2127+ <property name="spacing">2</property>
2128+ <child>
2129+ <object class="GtkLabel" id="lbl_dummy_host">
2130+ <property name="visible">True</property>
2131+ <property name="can_focus">False</property>
2132+ <property name="xalign">1</property>
2133+ <property name="label" translatable="yes">Host:</property>
2134+ <property name="width_chars">8</property>
2135+ </object>
2136+ <packing>
2137+ <property name="expand">False</property>
2138+ <property name="fill">True</property>
2139+ <property name="position">0</property>
2140+ </packing>
2141+ </child>
2142+ <child>
2143+ <object class="GtkEntry" id="txt_dummy_host">
2144+ <property name="visible">True</property>
2145+ <property name="can_focus">True</property>
2146+ <property name="invisible_char">•</property>
2147+ <property name="primary_icon_activatable">False</property>
2148+ <property name="secondary_icon_activatable">False</property>
2149+ <property name="primary_icon_sensitive">True</property>
2150+ <property name="secondary_icon_sensitive">True</property>
2151+ </object>
2152+ <packing>
2153+ <property name="expand">True</property>
2154+ <property name="fill">True</property>
2155+ <property name="position">1</property>
2156+ </packing>
2157+ </child>
2158+ <child>
2159+ <object class="GtkLabel" id="lbl_dummy_port">
2160+ <property name="visible">True</property>
2161+ <property name="can_focus">False</property>
2162+ <property name="xalign">1</property>
2163+ <property name="label" translatable="yes">Port:</property>
2164+ <property name="width_chars">8</property>
2165+ </object>
2166+ <packing>
2167+ <property name="expand">False</property>
2168+ <property name="fill">True</property>
2169+ <property name="position">2</property>
2170+ </packing>
2171+ </child>
2172+ <child>
2173+ <object class="GtkEntry" id="txt_dummy_port">
2174+ <property name="visible">True</property>
2175+ <property name="can_focus">True</property>
2176+ <property name="invisible_char">•</property>
2177+ <property name="primary_icon_activatable">False</property>
2178+ <property name="secondary_icon_activatable">False</property>
2179+ <property name="primary_icon_sensitive">True</property>
2180+ <property name="secondary_icon_sensitive">True</property>
2181+ </object>
2182+ <packing>
2183+ <property name="expand">True</property>
2184+ <property name="fill">True</property>
2185+ <property name="position">3</property>
2186+ </packing>
2187+ </child>
2188+ <child>
2189+ <object class="GtkLabel" id="lbl_dummy_user">
2190+ <property name="visible">True</property>
2191+ <property name="can_focus">False</property>
2192+ <property name="xalign">1</property>
2193+ <property name="label" translatable="yes">User:</property>
2194+ <property name="width_chars">8</property>
2195+ </object>
2196+ <packing>
2197+ <property name="expand">False</property>
2198+ <property name="fill">True</property>
2199+ <property name="position">4</property>
2200+ </packing>
2201+ </child>
2202+ <child>
2203+ <object class="GtkEntry" id="txt_dummy_user">
2204+ <property name="visible">True</property>
2205+ <property name="can_focus">True</property>
2206+ <property name="invisible_char">•</property>
2207+ <property name="primary_icon_activatable">False</property>
2208+ <property name="secondary_icon_activatable">False</property>
2209+ <property name="primary_icon_sensitive">True</property>
2210+ <property name="secondary_icon_sensitive">True</property>
2211+ </object>
2212+ <packing>
2213+ <property name="expand">True</property>
2214+ <property name="fill">True</property>
2215+ <property name="position">5</property>
2216+ </packing>
2217+ </child>
2218+ </object>
2219+ </child>
2220+ </object>
2221+ </child>
2222+ <child type="label">
2223+ <object class="GtkLabel" id="lbl_dummy">
2224+ <property name="visible">True</property>
2225+ <property name="can_focus">False</property>
2226+ <property name="label" translatable="yes">&lt;b&gt;Dummy Settings&lt;/b&gt;</property>
2227+ <property name="use_markup">True</property>
2228+ </object>
2229+ </child>
2230+ </object>
2231+ <packing>
2232+ <property name="expand">False</property>
2233+ <property name="fill">True</property>
2234+ <property name="position">800</property>
2235+ </packing>
2236+ </child>
2237+ <child>
2238+ <object class="GtkFrame" id="advanced">
2239+ <property name="visible">True</property>
2240+ <property name="can_focus">False</property>
2241+ <property name="label_xalign">0</property>
2242+ <property name="shadow_type">none</property>
2243+ <child>
2244+ <object class="GtkExpander" id="expander1">
2245+ <property name="visible">True</property>
2246+ <property name="can_focus">True</property>
2247+ <child>
2248+ <object class="GtkAlignment" id="alignment3">
2249+ <property name="visible">True</property>
2250+ <property name="can_focus">False</property>
2251+ <property name="left_padding">12</property>
2252+ <child>
2253+ <object class="GtkVBox" id="vbox3">
2254+ <property name="visible">True</property>
2255+ <property name="can_focus">False</property>
2256+ <property name="spacing">2</property>
2257+ <child>
2258+ <object class="GtkCheckButton" id="cb_auto_host_user_profile">
2259+ <property name="label" translatable="yes">Auto Host / User / Profile Id</property>
2260+ <property name="visible">True</property>
2261+ <property name="can_focus">True</property>
2262+ <property name="receives_default">False</property>
2263 <property name="use_action_appearance">False</property>
2264- <signal name="clicked" handler="on_btn_where_clicked" swapped="no"/>
2265- <child>
2266- <object class="GtkImage" id="image1">
2267- <property name="visible">True</property>
2268- <property name="can_focus">False</property>
2269- <property name="stock">gtk-directory</property>
2270- </object>
2271- </child>
2272- </object>
2273- <packing>
2274- <property name="expand">False</property>
2275+ <property name="draw_indicator">True</property>
2276+ <signal name="toggled" handler="on_cb_auto_host_user_profile_toggled" swapped="no"/>
2277+ </object>
2278+ <packing>
2279+ <property name="expand">True</property>
2280+ <property name="fill">True</property>
2281+ <property name="position">0</property>
2282+ </packing>
2283+ </child>
2284+ <child>
2285+ <object class="GtkHBox" id="hbox8">
2286+ <property name="visible">True</property>
2287+ <property name="can_focus">False</property>
2288+ <property name="spacing">2</property>
2289+ <child>
2290+ <object class="GtkLabel" id="lbl_host">
2291+ <property name="visible">True</property>
2292+ <property name="can_focus">False</property>
2293+ <property name="xalign">1</property>
2294+ <property name="label" translatable="yes">Host:</property>
2295+ <property name="width_chars">8</property>
2296+ </object>
2297+ <packing>
2298+ <property name="expand">False</property>
2299+ <property name="fill">True</property>
2300+ <property name="position">0</property>
2301+ </packing>
2302+ </child>
2303+ <child>
2304+ <object class="GtkEntry" id="txt_host">
2305+ <property name="visible">True</property>
2306+ <property name="can_focus">True</property>
2307+ <property name="invisible_char">•</property>
2308+ <property name="primary_icon_activatable">False</property>
2309+ <property name="secondary_icon_activatable">False</property>
2310+ <property name="primary_icon_sensitive">True</property>
2311+ <property name="secondary_icon_sensitive">True</property>
2312+ </object>
2313+ <packing>
2314+ <property name="expand">True</property>
2315+ <property name="fill">True</property>
2316+ <property name="position">1</property>
2317+ </packing>
2318+ </child>
2319+ <child>
2320+ <object class="GtkLabel" id="lbl_user">
2321+ <property name="visible">True</property>
2322+ <property name="can_focus">False</property>
2323+ <property name="xalign">1</property>
2324+ <property name="label" translatable="yes">User:</property>
2325+ <property name="width_chars">8</property>
2326+ </object>
2327+ <packing>
2328+ <property name="expand">False</property>
2329+ <property name="fill">True</property>
2330+ <property name="position">2</property>
2331+ </packing>
2332+ </child>
2333+ <child>
2334+ <object class="GtkEntry" id="txt_user">
2335+ <property name="visible">True</property>
2336+ <property name="can_focus">True</property>
2337+ <property name="invisible_char">•</property>
2338+ <property name="primary_icon_activatable">False</property>
2339+ <property name="secondary_icon_activatable">False</property>
2340+ <property name="primary_icon_sensitive">True</property>
2341+ <property name="secondary_icon_sensitive">True</property>
2342+ </object>
2343+ <packing>
2344+ <property name="expand">True</property>
2345+ <property name="fill">True</property>
2346+ <property name="position">3</property>
2347+ </packing>
2348+ </child>
2349+ <child>
2350+ <object class="GtkLabel" id="lbl_profile">
2351+ <property name="visible">True</property>
2352+ <property name="can_focus">False</property>
2353+ <property name="xalign">1</property>
2354+ <property name="label" translatable="yes">Profile Id:</property>
2355+ <property name="width_chars">8</property>
2356+ </object>
2357+ <packing>
2358+ <property name="expand">False</property>
2359+ <property name="fill">True</property>
2360+ <property name="position">4</property>
2361+ </packing>
2362+ </child>
2363+ <child>
2364+ <object class="GtkEntry" id="txt_profile">
2365+ <property name="visible">True</property>
2366+ <property name="can_focus">True</property>
2367+ <property name="invisible_char">•</property>
2368+ <property name="primary_icon_activatable">False</property>
2369+ <property name="secondary_icon_activatable">False</property>
2370+ <property name="primary_icon_sensitive">True</property>
2371+ <property name="secondary_icon_sensitive">True</property>
2372+ </object>
2373+ <packing>
2374+ <property name="expand">True</property>
2375+ <property name="fill">True</property>
2376+ <property name="position">5</property>
2377+ </packing>
2378+ </child>
2379+ </object>
2380+ <packing>
2381+ <property name="expand">True</property>
2382 <property name="fill">True</property>
2383 <property name="position">1</property>
2384 </packing>
2385 </child>
2386 </object>
2387- <packing>
2388- <property name="expand">True</property>
2389- <property name="fill">True</property>
2390- <property name="position">0</property>
2391- </packing>
2392- </child>
2393- <child>
2394- <object class="GtkExpander" id="expander1">
2395- <property name="visible">True</property>
2396- <property name="can_focus">True</property>
2397- <child>
2398- <object class="GtkAlignment" id="alignment3">
2399- <property name="visible">True</property>
2400- <property name="can_focus">False</property>
2401- <property name="left_padding">12</property>
2402- <child>
2403- <object class="GtkVBox" id="vbox3">
2404- <property name="visible">True</property>
2405- <property name="can_focus">False</property>
2406- <property name="spacing">2</property>
2407- <child>
2408- <object class="GtkCheckButton" id="cb_auto_host_user_profile">
2409- <property name="label" translatable="yes">Auto Host / User / Profile Id</property>
2410- <property name="visible">True</property>
2411- <property name="can_focus">True</property>
2412- <property name="receives_default">False</property>
2413- <property name="use_action_appearance">False</property>
2414- <property name="draw_indicator">True</property>
2415- <signal name="toggled" handler="on_cb_auto_host_user_profile_toggled" swapped="no"/>
2416- </object>
2417- <packing>
2418- <property name="expand">True</property>
2419- <property name="fill">True</property>
2420- <property name="position">0</property>
2421- </packing>
2422- </child>
2423- <child>
2424- <object class="GtkHBox" id="hbox8">
2425- <property name="visible">True</property>
2426- <property name="can_focus">False</property>
2427- <property name="spacing">2</property>
2428- <child>
2429- <object class="GtkLabel" id="lbl_host">
2430- <property name="visible">True</property>
2431- <property name="can_focus">False</property>
2432- <property name="xalign">1</property>
2433- <property name="yalign">0.5899999737739563</property>
2434- <property name="label" translatable="yes">Host:</property>
2435- </object>
2436- <packing>
2437- <property name="expand">True</property>
2438- <property name="fill">True</property>
2439- <property name="position">0</property>
2440- </packing>
2441- </child>
2442- <child>
2443- <object class="GtkEntry" id="txt_host">
2444- <property name="visible">True</property>
2445- <property name="can_focus">True</property>
2446- <property name="invisible_char">•</property>
2447- <property name="primary_icon_activatable">False</property>
2448- <property name="secondary_icon_activatable">False</property>
2449- <property name="primary_icon_sensitive">True</property>
2450- <property name="secondary_icon_sensitive">True</property>
2451- </object>
2452- <packing>
2453- <property name="expand">True</property>
2454- <property name="fill">True</property>
2455- <property name="position">1</property>
2456- </packing>
2457- </child>
2458- <child>
2459- <object class="GtkLabel" id="lbl_user">
2460- <property name="visible">True</property>
2461- <property name="can_focus">False</property>
2462- <property name="xalign">1</property>
2463- <property name="label" translatable="yes">User:</property>
2464- </object>
2465- <packing>
2466- <property name="expand">True</property>
2467- <property name="fill">True</property>
2468- <property name="position">2</property>
2469- </packing>
2470- </child>
2471- <child>
2472- <object class="GtkEntry" id="txt_user">
2473- <property name="visible">True</property>
2474- <property name="can_focus">True</property>
2475- <property name="invisible_char">•</property>
2476- <property name="primary_icon_activatable">False</property>
2477- <property name="secondary_icon_activatable">False</property>
2478- <property name="primary_icon_sensitive">True</property>
2479- <property name="secondary_icon_sensitive">True</property>
2480- </object>
2481- <packing>
2482- <property name="expand">True</property>
2483- <property name="fill">True</property>
2484- <property name="position">3</property>
2485- </packing>
2486- </child>
2487- <child>
2488- <object class="GtkLabel" id="lbl_profile">
2489- <property name="visible">True</property>
2490- <property name="can_focus">False</property>
2491- <property name="xalign">1</property>
2492- <property name="label" translatable="yes">Profile Id:</property>
2493- </object>
2494- <packing>
2495- <property name="expand">True</property>
2496- <property name="fill">True</property>
2497- <property name="position">4</property>
2498- </packing>
2499- </child>
2500- <child>
2501- <object class="GtkEntry" id="txt_profile">
2502- <property name="visible">True</property>
2503- <property name="can_focus">True</property>
2504- <property name="invisible_char">•</property>
2505- <property name="primary_icon_activatable">False</property>
2506- <property name="secondary_icon_activatable">False</property>
2507- <property name="primary_icon_sensitive">True</property>
2508- <property name="secondary_icon_sensitive">True</property>
2509- </object>
2510- <packing>
2511- <property name="expand">True</property>
2512- <property name="fill">True</property>
2513- <property name="position">5</property>
2514- </packing>
2515- </child>
2516- </object>
2517- <packing>
2518- <property name="expand">True</property>
2519- <property name="fill">True</property>
2520- <property name="position">1</property>
2521- </packing>
2522- </child>
2523- </object>
2524- </child>
2525- </object>
2526- </child>
2527- <child type="label">
2528- <object class="GtkLabel" id="label7">
2529- <property name="visible">True</property>
2530- <property name="can_focus">False</property>
2531- <property name="label" translatable="yes">Advanced</property>
2532- </object>
2533- </child>
2534- </object>
2535- <packing>
2536- <property name="expand">True</property>
2537- <property name="fill">True</property>
2538- <property name="position">1</property>
2539- </packing>
2540- </child>
2541- </object>
2542- </child>
2543- </object>
2544- </child>
2545- <child type="label">
2546- <object class="GtkLabel" id="label4">
2547- <property name="visible">True</property>
2548- <property name="can_focus">False</property>
2549- <property name="label" translatable="yes">&lt;b&gt;Where to save snapshots&lt;/b&gt;</property>
2550- <property name="use_markup">True</property>
2551+ </child>
2552+ </object>
2553+ </child>
2554+ <child type="label">
2555+ <object class="GtkLabel" id="label7">
2556+ <property name="visible">True</property>
2557+ <property name="can_focus">False</property>
2558+ <property name="label" translatable="yes">Advanced</property>
2559+ </object>
2560+ </child>
2561 </object>
2562 </child>
2563 </object>
2564 <packing>
2565 <property name="expand">False</property>
2566 <property name="fill">True</property>
2567- <property name="position">0</property>
2568+ <property name="position">900</property>
2569 </packing>
2570 </child>
2571 <child>
2572- <object class="GtkFrame" id="frame2">
2573+ <object class="GtkFrame" id="shedule">
2574 <property name="visible">True</property>
2575 <property name="can_focus">False</property>
2576 <property name="label_xalign">0</property>
2577@@ -423,7 +791,7 @@
2578 <object class="GtkTable" id="grid1">
2579 <property name="visible">True</property>
2580 <property name="can_focus">False</property>
2581- <property name="n_rows">4</property>
2582+ <property name="n_rows">5</property>
2583 <property name="n_columns">2</property>
2584 <property name="column_spacing">5</property>
2585 <property name="row_spacing">4</property>
2586@@ -438,6 +806,19 @@
2587 </packing>
2588 </child>
2589 <child>
2590+ <object class="GtkLabel" id="lbl_backup_time_custom">
2591+ <property name="visible">True</property>
2592+ <property name="can_focus">False</property>
2593+ <property name="xalign">1</property>
2594+ <property name="ypad">6</property>
2595+ <property name="label" translatable="yes">Hours:</property>
2596+ </object>
2597+ <packing>
2598+ <property name="top_attach">4</property>
2599+ <property name="x_options">GTK_FILL</property>
2600+ </packing>
2601+ </child>
2602+ <child>
2603 <object class="GtkLabel" id="lbl_backup_time">
2604 <property name="visible">True</property>
2605 <property name="can_focus">False</property>
2606@@ -477,6 +858,22 @@
2607 </packing>
2608 </child>
2609 <child>
2610+ <object class="GtkEntry" id="txt_backup_time_custom">
2611+ <property name="visible">True</property>
2612+ <property name="can_focus">True</property>
2613+ <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>
2614+ <property name="invisible_char">•</property>
2615+ <property name="primary_icon_activatable">False</property>
2616+ <property name="secondary_icon_activatable">False</property>
2617+ <property name="primary_icon_sensitive">True</property>
2618+ <property name="secondary_icon_sensitive">True</property>
2619+ </object>
2620+ <packing>
2621+ <property name="left_attach">1</property>
2622+ <property name="top_attach">4</property>
2623+ </packing>
2624+ </child>
2625+ <child>
2626 <object class="GtkComboBox" id="cb_backup_time">
2627 <property name="visible">True</property>
2628 <property name="can_focus">False</property>
2629@@ -522,7 +919,7 @@
2630 <packing>
2631 <property name="expand">False</property>
2632 <property name="fill">True</property>
2633- <property name="position">1</property>
2634+ <property name="position">950</property>
2635 </packing>
2636 </child>
2637 <child>
2638@@ -549,7 +946,7 @@
2639 <packing>
2640 <property name="expand">True</property>
2641 <property name="fill">True</property>
2642- <property name="position">2</property>
2643+ <property name="position">1000</property>
2644 </packing>
2645 </child>
2646 </object>
2647
2648=== modified file 'gnome/settingsdialog.py'
2649--- gnome/settingsdialog.py 2012-02-20 11:18:31 +0000
2650+++ gnome/settingsdialog.py 2012-10-23 19:40:24 +0000
2651@@ -30,6 +30,7 @@
2652 import config
2653 import messagebox
2654 import tools
2655+import mount
2656
2657
2658 _=gettext.gettext
2659@@ -74,7 +75,8 @@
2660 'on_combo_profiles_changed': self.on_combo_profiles_changed,
2661 'on_btn_where_clicked': self.on_btn_where_clicked,
2662 'on_cb_backup_mode_changed': self.on_cb_backup_mode_changed,
2663- 'on_cb_auto_host_user_profile_toggled': self.update_host_user_profile
2664+ 'on_cb_auto_host_user_profile_toggled': self.update_host_user_profile,
2665+ 'on_combo_modes_changed': self.on_combo_modes_changed
2666 }
2667
2668 builder.connect_signals(signals)
2669@@ -98,6 +100,26 @@
2670
2671 self.disable_combo_changed = False
2672
2673+ #snapshots mode (local, ssh, ...)
2674+ self.store_modes = gtk.ListStore(str, str)
2675+ keys = self.config.SNAPSHOT_MODES.keys()
2676+ keys.sort()
2677+ for key in keys:
2678+ self.store_modes.append([self.config.SNAPSHOT_MODES[key][1], key])
2679+
2680+ self.combo_modes = get( 'combo_modes' )
2681+ self.combo_modes.set_model( self.store_modes )
2682+
2683+ self.combo_modes.clear()
2684+ text_renderer = gtk.CellRendererText()
2685+ self.combo_modes.pack_start( text_renderer, True )
2686+ self.combo_modes.add_attribute( text_renderer, 'text', 0 )
2687+
2688+ self.mode = None
2689+ self.mode_local = get('mode_local')
2690+ self.mode_ssh = get('mode_ssh')
2691+## self.mode_dummy = get('mode_dummy')
2692+
2693 #set current folder
2694 #self.fcb_where = get( 'fcb_where' )
2695 #self.fcb_where.set_show_hidden( self.parent.show_hidden_files )
2696@@ -110,6 +132,31 @@
2697 self.txt_user = get('txt_user')
2698 self.lbl_profile = get('lbl_profile')
2699 self.txt_profile = get('txt_profile')
2700+
2701+ #ssh
2702+ self.txt_ssh_host = get('txt_ssh_host')
2703+ self.txt_ssh_port = get('txt_ssh_port')
2704+ self.txt_ssh_user = get('txt_ssh_user')
2705+ self.txt_ssh_path = get('txt_ssh_path')
2706+
2707+ self.store_ssh_cipher = gtk.ListStore(str, str)
2708+ keys = self.config.SSH_CIPHERS.keys()
2709+ keys.sort()
2710+ for key in keys:
2711+ self.store_ssh_cipher.append([self.config.SSH_CIPHERS[key], key])
2712+
2713+ self.combo_ssh_cipher = get( 'combo_ssh_cipher' )
2714+ self.combo_ssh_cipher.set_model( self.store_ssh_cipher )
2715+
2716+ self.combo_ssh_cipher.clear()
2717+ text_renderer = gtk.CellRendererText()
2718+ self.combo_ssh_cipher.pack_start( text_renderer, True )
2719+ self.combo_ssh_cipher.add_attribute( text_renderer, 'text', 0 )
2720+
2721+## #dummy
2722+## self.txt_dummy_host = get('txt_dummy_host')
2723+## self.txt_dummy_port = get('txt_dummy_port')
2724+## self.txt_dummy_user = get('txt_dummy_user')
2725
2726 #automatic backup mode store
2727 self.store_backup_mode = gtk.ListStore( str, int )
2728@@ -136,6 +183,10 @@
2729 for t in xrange( 1, 8 ):
2730 self.store_backup_weekday.append( [ datetime.date(2011, 11, 6 + t).strftime("%A"), t ] )
2731
2732+ #custom backup time
2733+ self.txt_backup_time_custom = get('txt_backup_time_custom')
2734+ self.lbl_backup_time_custom = get('lbl_backup_time_custom')
2735+
2736 #per directory schedule
2737 #self.cb_per_directory_schedule = get( 'cb_per_directory_schedule' )
2738 #self.lbl_schedule = get( 'lbl_schedule' )
2739@@ -184,7 +235,17 @@
2740 self.store_exclude = gtk.ListStore( str, str )
2741 self.list_exclude.set_model( self.store_exclude )
2742
2743- get( 'lbl_highly_recommended_excluded' ).set_text( ', '.join(self.config.DEFAULT_EXCLUDE) )
2744+ exclude = ''
2745+ i = 1
2746+ prev_lines = 0
2747+ for ex in self.config.DEFAULT_EXCLUDE:
2748+ exclude += ex
2749+ if i < len(self.config.DEFAULT_EXCLUDE):
2750+ exclude += ', '
2751+ if len(exclude)-prev_lines > 80:
2752+ exclude += '\n'
2753+ prev_lines += len(exclude)
2754+ get( 'lbl_highly_recommended_excluded' ).set_text( exclude )
2755
2756 #setup automatic backup mode
2757 self.cb_backup_mode = get( 'cb_backup_mode' )
2758@@ -228,6 +289,8 @@
2759
2760 self.lbl_backup_weekday = get( 'lbl_backup_weekday' )
2761
2762+ self.hbox_backup_time = get( 'hbox_backup_time' )
2763+
2764 #setup remove old backups older than
2765 self.edit_remove_old_backup_value = get( 'edit_remove_old_backup_value' )
2766 self.cb_remove_old_backup_unit = get( 'cb_remove_old_backup_unit' )
2767@@ -376,6 +439,15 @@
2768 self.lbl_backup_day.hide()
2769 self.cb_backup_day.hide()
2770
2771+ if backup_mode == self.config.CUSTOM_HOUR:
2772+ self.lbl_backup_time_custom.show()
2773+ self.txt_backup_time_custom.show()
2774+ self.txt_backup_time_custom.set_sensitive( True )
2775+ self.txt_backup_time_custom.set_text( self.config.get_custom_backup_time( self.profile_id ) )
2776+ else:
2777+ self.lbl_backup_time_custom.hide()
2778+ self.txt_backup_time_custom.hide()
2779+
2780 def update_host_user_profile( self, *params ):
2781 value = not self.cb_auto_host_user_profile.get_active()
2782 self.lbl_host.set_sensitive( value )
2783@@ -384,6 +456,20 @@
2784 self.txt_user.set_sensitive( value )
2785 self.lbl_profile.set_sensitive( value )
2786 self.txt_profile.set_sensitive( value )
2787+
2788+ def on_combo_modes_changed(self, *params):
2789+ iter = self.combo_modes.get_active_iter()
2790+ if iter is None:
2791+ return
2792+
2793+ active_mode = self.store_modes.get_value( iter, 1 )
2794+ if active_mode != self.mode:
2795+ for mode in self.config.SNAPSHOT_MODES.keys():
2796+ if active_mode == mode:
2797+ getattr(self, 'mode_%s' % mode).show()
2798+ else:
2799+ getattr(self, 'mode_%s' % mode).hide()
2800+ self.mode = active_mode
2801
2802 def on_combo_profiles_changed( self, *params ):
2803 if self.disable_combo_changed:
2804@@ -425,10 +511,22 @@
2805 else:
2806 self.btn_edit_profile.set_sensitive( True )
2807 self.btn_remove_profile.set_sensitive( True )
2808+
2809+ #set mode
2810+ i = 0
2811+ iter = self.store_modes.get_iter_first()
2812+ default_mode = self.config.get_snapshots_mode(self.profile_id)
2813+ while not iter is None:
2814+ if self.store_modes.get_value( iter, 1 ) == default_mode:
2815+ self.combo_modes.set_active( i )
2816+ break
2817+ iter = self.store_modes.iter_next( iter )
2818+ i = i + 1
2819+ self.on_combo_modes_changed()
2820
2821 #set current folder
2822 #self.fcb_where.set_filename( self.config.get_snapshots_path() )
2823- self.edit_where.set_text( self.config.get_snapshots_path( self.profile_id ) )
2824+ self.edit_where.set_text( self.config.get_snapshots_path( self.profile_id, mode = 'local' ) )
2825 self.cb_auto_host_user_profile.set_active( self.config.get_auto_host_user_profile( self.profile_id ) )
2826 host, user, profile = self.config.get_host_user_profile( self.profile_id )
2827 self.txt_host.set_text( host )
2828@@ -436,6 +534,27 @@
2829 self.txt_profile.set_text( profile )
2830 self.update_host_user_profile()
2831
2832+ #ssh
2833+ self.txt_ssh_host.set_text( self.config.get_ssh_host( self.profile_id ) )
2834+ self.txt_ssh_port.set_text( str(self.config.get_ssh_port( self.profile_id )) )
2835+ self.txt_ssh_user.set_text( self.config.get_ssh_user( self.profile_id ) )
2836+ self.txt_ssh_path.set_text( self.config.get_snapshots_path_ssh( self.profile_id ) )
2837+ #set chipher
2838+ i = 0
2839+ iter = self.store_ssh_cipher.get_iter_first()
2840+ default_mode = self.config.get_ssh_cipher(self.profile_id)
2841+ while not iter is None:
2842+ if self.store_ssh_cipher.get_value( iter, 1 ) == default_mode:
2843+ self.combo_ssh_cipher.set_active( i )
2844+ break
2845+ iter = self.store_ssh_cipher.iter_next( iter )
2846+ i = i + 1
2847+
2848+## #dummy
2849+## self.txt_dummy_host.set_text( self.config.get_dummy_host( self.profile_id ) )
2850+## self.txt_dummy_port.set_text( str(self.config.get_dummy_port( self.profile_id )) )
2851+## self.txt_dummy_user.set_text( self.config.get_dummy_user( self.profile_id ) )
2852+
2853 #per directory schedule
2854 #self.cb_per_directory_schedule.set_active( self.config.get_per_directory_schedule() )
2855
2856@@ -504,6 +623,9 @@
2857
2858 self.on_cb_backup_mode_changed()
2859
2860+ #setup custom backup time
2861+ self.txt_backup_time_custom.set_text( self.config.get_custom_backup_time( self.profile_id ) )
2862+
2863 #setup remove old backups older than
2864 enabled, value, unit = self.config.get_remove_old_snapshots( self.profile_id )
2865
2866@@ -589,7 +711,12 @@
2867 def save_profile( self ):
2868 #profile_id = self.config.get_current_profile()
2869 #snapshots path
2870- snapshots_path = self.edit_where.get_text()
2871+ iter = self.combo_modes.get_active_iter()
2872+ mode = self.store_modes.get_value( iter, 1 )
2873+ if self.config.SNAPSHOT_MODES[mode][0] is None:
2874+ snapshots_path = self.edit_where.get_text()
2875+ else:
2876+ snapshots_path = self.config.get_snapshots_path(self.profile_id, mode = mode, tmp_mount = True)
2877
2878 #hack
2879 if snapshots_path.startswith( '//' ):
2880@@ -611,6 +738,49 @@
2881 while not iter is None:
2882 exclude_list.append( self.store_exclude.get_value( iter, 0 ) )
2883 iter = self.store_exclude.iter_next( iter )
2884+
2885+ if self.store_backup_mode.get_value( self.cb_backup_mode.get_active_iter(), 1 ) == self.config.CUSTOM_HOUR:
2886+ if not tools.check_cron_pattern(self.txt_backup_time_custom.get_text()):
2887+ 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') )
2888+ return False
2889+
2890+ mount_kwargs = {}
2891+
2892+ #ssh settings
2893+ ssh_host = self.txt_ssh_host.get_text()
2894+ ssh_port = self.txt_ssh_port.get_text()
2895+ ssh_user = self.txt_ssh_user.get_text()
2896+ ssh_path = self.txt_ssh_path.get_text()
2897+ iter = self.combo_ssh_cipher.get_active_iter()
2898+ ssh_cipher = self.store_ssh_cipher.get_value( iter, 1 )
2899+ if mode == 'ssh':
2900+ mount_kwargs = { 'host': ssh_host, 'port': int(ssh_port), 'user': ssh_user, 'path': ssh_path, 'cipher': ssh_cipher }
2901+
2902+## #dummy settings
2903+## dummy_host = self.txt_dummy_host.get_text()
2904+## dummy_port = self.txt_dummy_port.get_text()
2905+## dummy_user = self.txt_dummy_user.get_text()
2906+## if mode == 'dummy':
2907+## #values must have exactly the same Type (str, int or bool)
2908+## #as they are set in config or you will run into false-positive
2909+## #HashCollision warnings
2910+## mount_kwargs = { 'host': dummy_host, 'port': int(dummy_port), 'user': dummy_user }
2911+
2912+ if not self.config.SNAPSHOT_MODES[mode][0] is None:
2913+ #pre_mount_check
2914+ mnt = mount.Mount(cfg = self.config, profile_id = self.profile_id, tmp_mount = True)
2915+ try:
2916+ mnt.pre_mount_check(mode = mode, first_run = True, **mount_kwargs)
2917+ except mount.MountException as ex:
2918+ self.error_handler(str(ex))
2919+ return False
2920+
2921+ #okay, lets try to mount
2922+ try:
2923+ hash_id = mnt.mount(mode = mode, check = False, **mount_kwargs)
2924+ except mount.MountException as ex:
2925+ self.error_handler(str(ex))
2926+ return False
2927
2928 #check if back folder changed
2929 #if len( self.config.get_snapshots_path() ) > 0 and self.config.get_snapshots_path() != snapshots_path:
2930@@ -620,7 +790,21 @@
2931 #ok let's save to config
2932 self.config.set_auto_host_user_profile( self.cb_auto_host_user_profile.get_active(), self.profile_id )
2933 self.config.set_host_user_profile( self.txt_host.get_text(), self.txt_user.get_text(), self.txt_profile.get_text(), self.profile_id )
2934- self.config.set_snapshots_path( snapshots_path, self.profile_id )
2935+ self.config.set_snapshots_path( snapshots_path, self.profile_id , mode)
2936+
2937+ self.config.set_snapshots_mode(mode, self.profile_id)
2938+
2939+ #save ssh
2940+ self.config.set_ssh_host(ssh_host, self.profile_id)
2941+ self.config.set_ssh_port(ssh_port, self.profile_id)
2942+ self.config.set_ssh_user(ssh_user, self.profile_id)
2943+ self.config.set_snapshots_path_ssh(ssh_path, self.profile_id)
2944+ self.config.set_ssh_cipher(ssh_cipher, self.profile_id)
2945+
2946+## #save dummy
2947+## self.config.set_dummy_host(dummy_host, self.profile_id)
2948+## self.config.set_dummy_port(dummy_port, self.profile_id)
2949+## self.config.set_dummy_user(dummy_user, self.profile_id)
2950
2951 #if not msg is None:
2952 # messagebox.show_error( self.dialog, self.config, msg )
2953@@ -634,6 +818,7 @@
2954 self.config.set_automatic_backup_time( self.store_backup_time.get_value( self.cb_backup_time.get_active_iter(), 1 ), self.profile_id )
2955 self.config.set_automatic_backup_day( self.store_backup_day.get_value( self.cb_backup_day.get_active_iter(), 1 ), self.profile_id )
2956 self.config.set_automatic_backup_weekday( self.store_backup_weekday.get_value( self.cb_backup_weekday.get_active_iter(), 1 ), self.profile_id )
2957+ self.config.set_custom_backup_time( self.txt_backup_time_custom.get_text(), self.profile_id )
2958
2959 #auto-remove snapshots
2960 self.config.set_remove_old_snapshots(
2961@@ -674,6 +859,15 @@
2962 self.config.set_preserve_xattr( self.cb_preserve_xattr.get_active(), self.profile_id )
2963 self.config.set_copy_unsafe_links( self.cb_copy_unsafe_links.get_active(), self.profile_id )
2964 self.config.set_copy_links( self.cb_copy_links.get_active(), self.profile_id )
2965+
2966+ #umount
2967+ if not self.config.SNAPSHOT_MODES[mode][0] is None:
2968+ try:
2969+ mnt.umount(hash_id = hash_id)
2970+ except mount.MountException as ex:
2971+ self.error_handler(str(ex))
2972+ return False
2973+ return True
2974
2975 def update_remove_old_backups( self, button ):
2976 enabled = self.cb_remove_old_backup.get_active()
2977@@ -716,7 +910,7 @@
2978
2979 self.config.clear_handlers()
2980 self.dialog.destroy()
2981-
2982+
2983 def update_snapshots_location( self ):
2984 '''Update snapshot location dialog'''
2985 self.config.set_question_handler( self.question_handler )
2986@@ -852,7 +1046,8 @@
2987 self.dialog.destroy()
2988
2989 def validate( self ):
2990- self.save_profile()
2991+ if not self.save_profile():
2992+ return False
2993
2994 if not self.config.check_config():
2995 return False
2996
2997=== modified file 'kde4/app.py'
2998--- kde4/app.py 2012-03-05 16:04:13 +0000
2999+++ kde4/app.py 2012-10-23 19:40:24 +0000
3000@@ -37,6 +37,7 @@
3001 import logger
3002 import snapshots
3003 import guiapplicationinstance
3004+import mount
3005
3006 from PyQt4.QtGui import *
3007 from PyQt4.QtCore import *
3008@@ -318,6 +319,17 @@
3009 settingsdialog.SettingsDialog( self ).update_snapshots_location()
3010
3011 profile_id = cfg.get_current_profile()
3012+
3013+ #mount
3014+ try:
3015+ mnt = mount.Mount(cfg = self.config, profile_id = profile_id)
3016+ hash_id = mnt.mount()
3017+ except mount.MountException as ex:
3018+ KMessageBox.error( self, QString.fromUtf8( str(ex) ) )
3019+ sys.exit(1)
3020+ else:
3021+ self.config.set_current_hash_id(hash_id)
3022+
3023 if not cfg.can_backup( profile_id ):
3024 KMessageBox.error( self, QString.fromUtf8( _('Can\'t find snapshots folder.\nIf it is on a removable drive please plug it and then press OK') ) )
3025
3026@@ -378,6 +390,13 @@
3027 self.config.set_int_value( 'kde4.main_window.files_view.sort.column', self.list_files_view.header().sortIndicatorSection() )
3028 self.config.set_bool_value( 'kde4.main_window.files_view.sort.ascending', self.list_files_view.header().sortIndicatorOrder() == Qt.AscendingOrder )
3029
3030+ #umount
3031+ try:
3032+ mnt = mount.Mount(cfg = self.config)
3033+ mnt.umount(self.config.current_hash_id)
3034+ except mount.MountException as ex:
3035+ KMessageBox.error( self, QString.fromUtf8( str(ex) ) )
3036+
3037 self.config.save()
3038
3039 event.accept()
3040@@ -419,8 +438,18 @@
3041 return
3042
3043 if profile_id != self.config.get_current_profile():
3044+ self.remount(profile_id, self.config.get_current_profile())
3045 self.config.set_current_profile( profile_id )
3046 self.update_profile()
3047+
3048+ def remount( self, new_profile_id, old_profile_id):
3049+ try:
3050+ mnt = mount.Mount(cfg = self.config, profile_id = old_profile_id)
3051+ hash_id = mnt.remount(new_profile_id)
3052+ except mount.MountException as ex:
3053+ KMessageBox.error( self, QString.fromUtf8( str(ex) ) )
3054+ else:
3055+ self.config.set_current_hash_id(hash_id)
3056
3057 def get_default_startup_folder_and_file( self ):
3058 last_path = self.config.get_str_value( 'gnome.last_path', '' )
3059@@ -779,6 +808,8 @@
3060
3061 def on_btn_settings_clicked( self ):
3062 if QDialog.Accepted == settingsdialog.SettingsDialog( self ).exec_():
3063+ profile_id = self.config.get_current_profile()
3064+ self.remount(profile_id, profile_id)
3065 self.update_profiles()
3066
3067 def on_btn_about_clicked( self ):
3068
3069=== modified file 'kde4/man/C/backintime-kde4.1.gz'
3070Binary 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
3071=== modified file 'kde4/settingsdialog.py'
3072--- kde4/settingsdialog.py 2012-02-20 11:30:09 +0000
3073+++ kde4/settingsdialog.py 2012-10-23 19:40:24 +0000
3074@@ -32,6 +32,7 @@
3075 import config
3076 import tools
3077 import kde4tools
3078+import mount
3079
3080
3081 _=gettext.gettext
3082@@ -102,9 +103,25 @@
3083 tab_widget = QWidget( self )
3084 self.tabs_widget.addTab( tab_widget, QString.fromUtf8( _( 'General' ) ) )
3085 layout = QVBoxLayout( tab_widget )
3086-
3087+
3088+ #select mode
3089+ self.mode = None
3090+ vlayout = QVBoxLayout()
3091+ layout.addLayout( vlayout )
3092+
3093+ self.lbl_modes = QLabel( QString.fromUtf8( _( 'Mode:' ) ), self )
3094+ vlayout.addWidget( self.lbl_modes )
3095+
3096+ self.combo_modes = KComboBox( self )
3097+ vlayout.addWidget( self.combo_modes )
3098+ store_modes = {}
3099+ for key in self.config.SNAPSHOT_MODES.keys():
3100+ store_modes[key] = self.config.SNAPSHOT_MODES[key][1]
3101+ self.fill_combo( self.combo_modes, store_modes )
3102+
3103 #Where to save snapshots
3104 group_box = QGroupBox( self )
3105+ self.mode_local = group_box
3106 group_box.setTitle( QString.fromUtf8( _( 'Where to save snapshots' ) ) )
3107 layout.addWidget( group_box )
3108
3109@@ -120,7 +137,75 @@
3110 self.btn_snapshots_path = KPushButton( KIcon( 'folder' ), '', self )
3111 hlayout.addWidget( self.btn_snapshots_path )
3112 QObject.connect( self.btn_snapshots_path, SIGNAL('clicked()'), self.on_btn_snapshots_path_clicked )
3113-
3114+
3115+ #SSH
3116+ group_box = QGroupBox( self )
3117+ self.mode_ssh = group_box
3118+ group_box.setTitle( QString.fromUtf8( _( 'SSH Settings' ) ) )
3119+ layout.addWidget( group_box )
3120+
3121+ vlayout = QVBoxLayout( group_box )
3122+
3123+ hlayout1 = QHBoxLayout()
3124+ vlayout.addLayout( hlayout1 )
3125+ hlayout2 = QHBoxLayout()
3126+ vlayout.addLayout( hlayout2 )
3127+
3128+ self.lbl_ssh_host = QLabel( QString.fromUtf8( _( 'Host:' ) ), self )
3129+ hlayout1.addWidget( self.lbl_ssh_host )
3130+ self.txt_ssh_host = KLineEdit( self )
3131+ hlayout1.addWidget( self.txt_ssh_host )
3132+
3133+ self.lbl_ssh_port = QLabel( QString.fromUtf8( _( 'Port:' ) ), self )
3134+ hlayout1.addWidget( self.lbl_ssh_port )
3135+ self.txt_ssh_port = KLineEdit( self )
3136+ hlayout1.addWidget( self.txt_ssh_port )
3137+
3138+ self.lbl_ssh_user = QLabel( QString.fromUtf8( _( 'User:' ) ), self )
3139+ hlayout1.addWidget( self.lbl_ssh_user )
3140+ self.txt_ssh_user = KLineEdit( self )
3141+ hlayout1.addWidget( self.txt_ssh_user )
3142+
3143+ self.lbl_ssh_path = QLabel( QString.fromUtf8( _( 'Path:' ) ), self )
3144+ hlayout2.addWidget( self.lbl_ssh_path )
3145+ self.txt_ssh_path = KLineEdit( self )
3146+ hlayout2.addWidget( self.txt_ssh_path )
3147+
3148+ self.lbl_ssh_cipher = QLabel( QString.fromUtf8( _( 'Cipher:' ) ), self )
3149+ hlayout2.addWidget( self.lbl_ssh_cipher )
3150+ self.combo_ssh_cipher = KComboBox( self )
3151+ hlayout2.addWidget( self.combo_ssh_cipher )
3152+ self.fill_combo( self.combo_ssh_cipher, self.config.SSH_CIPHERS )
3153+
3154+## #Dummy
3155+## group_box = QGroupBox( self )
3156+## self.mode_dummy = group_box
3157+## group_box.setTitle( QString.fromUtf8( _( 'Dummy Settings' ) ) )
3158+## layout.addWidget( group_box )
3159+##
3160+## vlayout = QVBoxLayout( group_box )
3161+##
3162+## hlayout = QHBoxLayout()
3163+## vlayout.addLayout( hlayout )
3164+##
3165+## self.lbl_dummy_host = QLabel( QString.fromUtf8( _( 'Host:' ) ), self )
3166+## hlayout.addWidget( self.lbl_dummy_host )
3167+## self.txt_dummy_host = KLineEdit( self )
3168+## hlayout.addWidget( self.txt_dummy_host )
3169+##
3170+## self.lbl_dummy_port = QLabel( QString.fromUtf8( _( 'Port:' ) ), self )
3171+## hlayout.addWidget( self.lbl_dummy_port )
3172+## self.txt_dummy_port = KLineEdit( self )
3173+## hlayout.addWidget( self.txt_dummy_port )
3174+##
3175+## self.lbl_dummy_user = QLabel( QString.fromUtf8( _( 'User:' ) ), self )
3176+## hlayout.addWidget( self.lbl_dummy_user )
3177+## self.txt_dummy_user = KLineEdit( self )
3178+## hlayout.addWidget( self.txt_dummy_user )
3179+
3180+ QObject.connect( self.combo_modes, SIGNAL('currentIndexChanged(int)'), self.on_combo_modes_changed )
3181+ self.on_combo_modes_changed()
3182+
3183 #host, user, profile id
3184 hlayout = QHBoxLayout()
3185 layout.addLayout( hlayout )
3186@@ -200,6 +285,14 @@
3187 for t in xrange( 0, 2300, 100 ):
3188 self.combo_automatic_snapshots_time.addItem( QIcon(), QString.fromUtf8( datetime.time( t/100, t%100 ).strftime("%H:%M") ), QVariant( t ) )
3189
3190+ self.lbl_automatic_snapshots_time_custom = QLabel( QString.fromUtf8( _( 'Hours:' ) ), self )
3191+ self.lbl_automatic_snapshots_time_custom.setContentsMargins( 5, 0, 0, 0 )
3192+ self.lbl_automatic_snapshots_time_custom.setAlignment( Qt.AlignRight | Qt.AlignVCenter )
3193+ glayout.addWidget( self.lbl_automatic_snapshots_time_custom, 4, 0 )
3194+
3195+ self.txt_automatic_snapshots_time_custom = KLineEdit( self )
3196+ glayout.addWidget( self.txt_automatic_snapshots_time_custom, 4, 1 )
3197+
3198 QObject.connect( self.combo_automatic_snapshots, SIGNAL('currentIndexChanged(int)'), self.current_automatic_snapshot_changed )
3199
3200 #
3201@@ -470,6 +563,13 @@
3202 self.update_profiles()
3203
3204 def update_automatic_snapshot_time( self, backup_mode ):
3205+ if backup_mode == self.config.CUSTOM_HOUR:
3206+ self.lbl_automatic_snapshots_time_custom.show()
3207+ self.txt_automatic_snapshots_time_custom.show()
3208+ else:
3209+ self.lbl_automatic_snapshots_time_custom.hide()
3210+ self.txt_automatic_snapshots_time_custom.hide()
3211+
3212 if backup_mode == self.config.WEEK:
3213 self.lbl_automatic_snapshots_weekday.show()
3214 self.combo_automatic_snapshots_weekday.show()
3215@@ -542,7 +642,23 @@
3216 self.btn_remove_profile.setEnabled( True )
3217
3218 #TAB: General
3219- self.edit_snapshots_path.setText( QString.fromUtf8( self.config.get_snapshots_path() ) )
3220+ #mode
3221+ self.set_combo_value( self.combo_modes, self.config.get_snapshots_mode(), type = 'str' )
3222+
3223+ #local
3224+ self.edit_snapshots_path.setText( QString.fromUtf8( self.config.get_snapshots_path( mode = 'local') ) )
3225+
3226+ #ssh
3227+ self.txt_ssh_host.setText( QString.fromUtf8( self.config.get_ssh_host() ) )
3228+ self.txt_ssh_port.setText( QString.fromUtf8( str(self.config.get_ssh_port()) ) )
3229+ self.txt_ssh_user.setText( QString.fromUtf8( self.config.get_ssh_user() ) )
3230+ self.txt_ssh_path.setText( QString.fromUtf8( self.config.get_snapshots_path_ssh() ) )
3231+ self.set_combo_value( self.combo_ssh_cipher, self.config.get_ssh_cipher(), type = 'str' )
3232+
3233+## #dummy
3234+## self.txt_dummy_host.setText( QString.fromUtf8( self.config.get_dummy_host() ) )
3235+## self.txt_dummy_port.setText( QString.fromUtf8( self.config.get_dummy_port() ) )
3236+## self.txt_dummy_user.setText( QString.fromUtf8( self.config.get_dummy_user() ) )
3237
3238 self.cb_auto_host_user_profile.setChecked( self.config.get_auto_host_user_profile() )
3239 host, user, profile = self.config.get_host_user_profile()
3240@@ -555,6 +671,7 @@
3241 self.set_combo_value( self.combo_automatic_snapshots_time, self.config.get_automatic_backup_time() )
3242 self.set_combo_value( self.combo_automatic_snapshots_day, self.config.get_automatic_backup_day() )
3243 self.set_combo_value( self.combo_automatic_snapshots_weekday, self.config.get_automatic_backup_weekday() )
3244+ self.txt_automatic_snapshots_time_custom.setText( self.config.get_custom_backup_time() )
3245 self.update_automatic_snapshot_time( self.config.get_automatic_backup_mode() )
3246
3247 #TAB: Include
3248@@ -619,13 +736,76 @@
3249 self.update_min_free_space()
3250
3251 def save_profile( self ):
3252+ if self.combo_automatic_snapshots.itemData( self.combo_automatic_snapshots.currentIndex() ).toInt()[0] == self.config.CUSTOM_HOUR:
3253+ if not tools.check_cron_pattern(str( self.txt_automatic_snapshots_time_custom.text().toUtf8() ) ):
3254+ 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') )
3255+ return False
3256+
3257+ #mode
3258+ mode = str( self.combo_modes.itemData( self.combo_modes.currentIndex() ).toString().toUtf8() )
3259+ self.config.set_snapshots_mode( mode )
3260+ mount_kwargs = {}
3261+
3262+ #ssh
3263+ ssh_host = str( self.txt_ssh_host.text().toUtf8() )
3264+ ssh_port = str( self.txt_ssh_port.text().toUtf8() )
3265+ ssh_user = str( self.txt_ssh_user.text().toUtf8() )
3266+ ssh_path = str( self.txt_ssh_path.text().toUtf8() )
3267+ ssh_cipher = str( self.combo_ssh_cipher.itemData( self.combo_ssh_cipher.currentIndex() ).toString().toUtf8() )
3268+ if mode == 'ssh':
3269+ mount_kwargs = { 'host': ssh_host, 'port': int(ssh_port), 'user': ssh_user, 'path': ssh_path, 'cipher': ssh_cipher }
3270+
3271+## #dummy
3272+## dummy_host = str( self.txt_dummy_host.text().toUtf8() )
3273+## dummy_port = str( self.txt_dummy_port.text().toUtf8() )
3274+## dummy_user = str( self.txt_dummy_user.text().toUtf8() )
3275+## if mode == 'dummy':
3276+## #values must have exactly the same Type (str, int or bool)
3277+## #as they are set in config or you will run into false-positive
3278+## #HashCollision warnings
3279+## mount_kwargs = { 'host': dummy_host, 'port': int(dummy_port), 'user': dummy_user }
3280+
3281+ if not self.config.SNAPSHOT_MODES[mode][0] is None:
3282+ #pre_mount_check
3283+ mnt = mount.Mount(cfg = self.config, tmp_mount = True)
3284+ try:
3285+ mnt.pre_mount_check(mode = mode, first_run = True, **mount_kwargs)
3286+ except mount.MountException as ex:
3287+ self.error_handler(str(ex))
3288+ return False
3289+
3290+ #okay, lets try to mount
3291+ try:
3292+ hash_id = mnt.mount(mode = mode, check = False, **mount_kwargs)
3293+ except mount.MountException as ex:
3294+ self.error_handler(str(ex))
3295+ return False
3296+
3297 #snapshots path
3298 self.config.set_auto_host_user_profile( self.cb_auto_host_user_profile.isChecked() )
3299 self.config.set_host_user_profile(
3300 str( self.txt_host.text().toUtf8() ),
3301 str( self.txt_user.text().toUtf8() ),
3302 str( self.txt_profile.text().toUtf8() ) )
3303- self.config.set_snapshots_path( str( self.edit_snapshots_path.text().toUtf8() ) )
3304+
3305+ if self.config.SNAPSHOT_MODES[mode][0] is None:
3306+ snapshots_path = str( self.edit_snapshots_path.text().toUtf8() )
3307+ else:
3308+ snapshots_path = self.config.get_snapshots_path(mode = mode, tmp_mount = True)
3309+
3310+ self.config.set_snapshots_path( snapshots_path, mode = mode )
3311+
3312+ #save ssh
3313+ self.config.set_ssh_host(ssh_host)
3314+ self.config.set_ssh_port(ssh_port)
3315+ self.config.set_ssh_user(ssh_user)
3316+ self.config.set_snapshots_path_ssh(ssh_path)
3317+ self.config.set_ssh_cipher(ssh_cipher)
3318+
3319+## #save dummy
3320+## self.config.set_dummy_host(dummy_host)
3321+## self.config.set_dummy_port(dummy_port)
3322+## self.config.set_dummy_user(dummy_user)
3323
3324 #include list
3325 include_list = []
3326@@ -648,6 +828,7 @@
3327 self.config.set_automatic_backup_time( self.combo_automatic_snapshots_time.itemData( self.combo_automatic_snapshots_time.currentIndex() ).toInt()[0] )
3328 self.config.set_automatic_backup_weekday( self.combo_automatic_snapshots_weekday.itemData( self.combo_automatic_snapshots_weekday.currentIndex() ).toInt()[0] )
3329 self.config.set_automatic_backup_day( self.combo_automatic_snapshots_day.itemData( self.combo_automatic_snapshots_day.currentIndex() ).toInt()[0] )
3330+ self.config.set_custom_backup_time( str( self.txt_automatic_snapshots_time_custom.text().toUtf8() ) )
3331
3332 #auto-remove
3333 self.config.set_remove_old_snapshots(
3334@@ -684,6 +865,15 @@
3335 self.config.set_preserve_xattr( self.cb_preserve_xattr.isChecked() )
3336 self.config.set_copy_unsafe_links( self.cb_copy_unsafe_links.isChecked() )
3337 self.config.set_copy_links( self.cb_copy_links.isChecked() )
3338+
3339+ #umount
3340+ if not self.config.SNAPSHOT_MODES[mode][0] is None:
3341+ try:
3342+ mnt.umount(hash_id = hash_id)
3343+ except mount.MountException as ex:
3344+ self.error_handler(str(ex))
3345+ return False
3346+ return True
3347
3348 def error_handler( self, message ):
3349 KMessageBox.error( self, QString.fromUtf8( message ) )
3350@@ -777,14 +967,18 @@
3351 for key in keys:
3352 combo.addItem( QIcon(), QString.fromUtf8( dict[ key ] ), QVariant( key ) )
3353
3354- def set_combo_value( self, combo, value ):
3355+ def set_combo_value( self, combo, value, type = 'int' ):
3356 for i in xrange( combo.count() ):
3357- if value == combo.itemData( i ).toInt()[0]:
3358+ if type == 'int' and value == combo.itemData( i ).toInt()[0]:
3359+ combo.setCurrentIndex( i )
3360+ break
3361+ if type == 'str' and value == combo.itemData( i ).toString():
3362 combo.setCurrentIndex( i )
3363 break
3364
3365 def validate( self ):
3366- self.save_profile()
3367+ if not self.save_profile():
3368+ return False
3369
3370 if not self.config.check_config():
3371 return False
3372@@ -886,7 +1080,21 @@
3373 if not self.question_handler( _('Are you sure you want to change snapshots folder ?') ):
3374 return
3375 self.edit_snapshots_path.setText( QString.fromUtf8( self.config.prepare_path( path ) ) )
3376-
3377+
3378+ def on_combo_modes_changed(self, *params):
3379+ if len(params) == 0:
3380+ index = self.combo_modes.currentIndex()
3381+ else:
3382+ index = params[0]
3383+ active_mode = str( self.combo_modes.itemData( index ).toString().toUtf8() )
3384+ if active_mode != self.mode:
3385+ for mode in self.config.SNAPSHOT_MODES.keys():
3386+ if active_mode == mode:
3387+ getattr(self, 'mode_%s' % mode).show()
3388+ else:
3389+ getattr(self, 'mode_%s' % mode).hide()
3390+ self.mode = active_mode
3391+
3392 def accept( self ):
3393 if self.validate():
3394 KDialog.accept( self )

Subscribers

People subscribed via source and target branches

to all changes: