Merge lp:~bratdaking/backintime/snapshot-location into lp:backintime/1.0

Proposed by Bart de Koning
Status: Merged
Merged at revision: not available
Proposed branch: lp:~bratdaking/backintime/snapshot-location
Merge into: lp:backintime/1.0
Diff against target: 978 lines (+546/-62)
9 files modified
common/backintime.py (+3/-1)
common/config.py (+191/-13)
common/snapshots.py (+155/-39)
common/tools.py (+146/-0)
gnome/app.py (+13/-3)
gnome/settingsdialog.glade (+15/-1)
gnome/settingsdialog.py (+9/-2)
kde4/app.py (+8/-3)
kde4/settingsdialog.py (+6/-0)
To merge this branch: bzr merge lp:~bratdaking/backintime/snapshot-location
Reviewer Review Type Date Requested Status
Back In Time Team Pending
Review via email: mp+14644@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Bart de Koning (bratdaking) wrote :

Hey, its ready for merging! The GNOME and the KDE versions are tested and working as supposed and stable. So let me know what you think about it!
In short:

1) New Snapshot folder: backintime/machine/user/profile_id
2) Update process: blocks the creation of new snapshots until GUI is started (logs warnings)
   When GUI is launched asks you whether you like to move your old snaphots
   If you do not ask again if you are sure, if not BiT cannot take snapshots, if you do BiT can take snapshots (on new location), but snapshots are still on their old spot
   If you do want to move, your snapshots are moved (when more than 1 profile it asks you if you like it to have them all in the same folder), and BiT will take new snapshots again.
3) Snapshots can be left on the old spot, to be able to distinguish between snapshots snapshots_ids end with an unique random based tag (basically it is now able to show and recover from snapshots from other locations than the snapshot folder)
4) To test whether a snapshot path is a real snapshot it tests whether the folder backup exists within a snapshot (instead of only a name with 15 or 19 characters)

Cheers,
Bart

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'common/backintime.py'
2--- common/backintime.py 2009-11-02 13:52:00 +0000
3+++ common/backintime.py 2009-11-09 11:15:22 +0000
4@@ -25,6 +25,7 @@
5 import config
6 import logger
7 import snapshots
8+import tools
9
10 _=gettext.gettext
11
12@@ -41,6 +42,7 @@
13
14
15 def print_version( cfg, app_name ):
16+ print ''
17 print 'Back In Time'
18 print 'Version: ' + cfg.VERSION
19 print ''
20@@ -83,7 +85,7 @@
21
22 skip = False
23 index = 0
24-
25+
26 for arg in sys.argv[ 1 : ]:
27 index = index + 1
28
29
30=== modified file 'common/config.py'
31--- common/config.py 2009-11-02 13:52:00 +0000
32+++ common/config.py 2009-11-09 11:15:23 +0000
33@@ -20,10 +20,12 @@
34 import os
35 import datetime
36 import gettext
37+import socket
38+import random
39
40 import configfile
41 import tools
42-
43+import logger
44
45 _=gettext.gettext
46
47@@ -35,7 +37,7 @@
48 APP_NAME = 'Back In Time'
49 VERSION = '0.9.99beta1'
50 COPYRIGHT = 'Copyright (c) 2008-2009 Oprea Dan, Bart de Koning, Richard Bailey'
51- CONFIG_VERSION = 3
52+ CONFIG_VERSION = 4
53
54 NONE = 0
55 _5_MIN = 2
56@@ -135,6 +137,25 @@
57 self.remap_key( 'snapshots.min_free_space.unit', 'profile1.snapshots.min_free_space.unit' )
58 self.remap_key( 'snapshots.dont_remove_named_snapshots', 'profile1.snapshots.dont_remove_named_snapshots' )
59 self.set_int_value( 'config.version', 3 )
60+
61+ if self.get_int_value( 'config.version', 1 ) < 4:
62+ # version 4 uses as path backintime/machine/user/profile_id
63+ # but must be able to read old paths
64+ profiles = self.get_profiles()
65+ self.set_bool_value( 'update.other_folders', True )
66+ logger.info( "Update to config version 4: other snapshot locations" )
67+
68+ for profile_id in profiles:
69+ old_folder = self.get_snapshots_path( profile_id )
70+ other_folder = os.path.join( old_folder, 'backintime' )
71+ other_folder_key = 'profile' + str( profile_id ) + '.snapshots.other_folders'
72+ self.set_str_value( other_folder_key, other_folder )
73+ #self.set_snapshots_path( old_folder, profile_id )
74+ tag = str( random.randint(100, 999) )
75+ logger.info( "Random tag for profile %s: %s" %( profile_id, tag ) )
76+ self.set_profile_str_value( 'snapshots.tag', tag, profile_id )
77+
78+ self.set_int_value( 'config.version', 4 )
79
80 def save( self ):
81 configfile.ConfigFile.save( self, self._LOCAL_CONFIG_PATH )
82@@ -153,11 +174,12 @@
83 self.notify_error( _('Profile: "%s"') % profile_name + '\n' + _('Snapshots folder is not valid !') )
84 return False
85
86- for other_profile in checked_profiles:
87- if snapshots_path == self.get_snapshots_path( other_profile[0] ):
88- self.notify_error( _('Profiles "%s" and "%s" have the same snapshots path !') % ( profile_name, other_profile[1] ) )
89- return False
90-
91+ ## Should not check for similar snapshot paths any longer!
92+ #for other_profile in checked_profiles:
93+ # if snapshots_path == self.get_snapshots_path( other_profile[0] ):
94+ # self.notify_error( _('Profiles "%s" and "%s" have the same snapshots path !') % ( profile_name, other_profile[1] ) )
95+ # return False
96+ #
97 #if not os.path.isdir( snapshots_path ):
98 # return ( 0, _('Snapshots folder is not valid !') )
99
100@@ -191,20 +213,35 @@
101 return self.get_profile_str_value( 'snapshots.path', '', profile_id )
102
103 def get_snapshots_full_path( self, profile_id = None ):
104- return os.path.join( self.get_snapshots_path( profile_id ), 'backintime' )
105+ '''Returns the full path for the snapshots: .../backintime/machine/user/profile_id/'''
106+ if self.get_int_value( 'config.version', 1 ) < 4:
107+ return os.path.join( self.get_snapshots_path( profile_id ), 'backintime' )
108+ else:
109+ machine = socket.gethostname()
110+ user = os.environ['LOGNAME']
111+ return os.path.join( self.get_snapshots_path( profile_id ), 'backintime', machine, user, profile_id )
112
113 def set_snapshots_path( self, value, profile_id = None ):
114 """Sets the snapshot path to value, initializes, and checks it"""
115 if len( value ) <= 0:
116 return False
117
118+ if profile_id == None:
119+ print "BUG: calling set_snapshots_path without profile_id!"
120+ tjoep
121+ return False
122+
123 if not os.path.isdir( value ):
124 self.notify_error( _( '%s is not a folder !' ) )
125 return False
126
127 #Initialize the snapshots folder
128- full_path = os.path.join( value, 'backintime' )
129+ print "Check snapshot folder: %s" % value
130+ machine = socket.gethostname()
131+ user = os.environ['LOGNAME']
132+ full_path = os.path.join( value, 'backintime', machine, user, profile_id )
133 if not os.path.isdir( full_path ):
134+ print "Create folder: %s" % full_path
135 tools.make_dirs( full_path )
136 if not os.path.isdir( full_path ):
137 self.notify_error( _( 'Can\'t write to: %s\nAre you sure you have write access ?' % value ) )
138@@ -221,6 +258,24 @@
139 self.set_profile_str_value( 'snapshots.path', value, profile_id )
140 return True
141
142+ def get_other_folders_paths( self, profile_id = None ):
143+ '''Returns the other snapshots folders paths as a list'''
144+ value = self.get_profile_str_value( 'snapshots.other_folders', '', profile_id )
145+ if len( value ) <= 0:
146+ return []
147+
148+ paths = []
149+
150+ for item in value.split(':'):
151+ fields = item.split( '|' )
152+
153+ path = os.path.expanduser( item )
154+ path = os.path.abspath( path )
155+
156+ paths.append( ( path ) )
157+
158+ return paths
159+
160 def get_include_folders( self, profile_id = None ):
161 value = self.get_profile_str_value( 'snapshots.include_folders', '', profile_id )
162 if len( value ) <= 0:
163@@ -252,9 +307,9 @@
164 value = value + item[0] + '|' + str( item[1] )
165
166 self.set_profile_str_value( 'snapshots.include_folders', value, profile_id )
167-
168- # Sets the default exclude patterns: caches, thumbnails, trashbins, and backups
169+
170 def get_exclude_patterns( self, profile_id = None ):
171+ '''Gets the exclude patterns (default: caches, thumbnails, trashbins, and backups)'''
172 value = self.get_profile_str_value( 'snapshots.exclude_patterns', '.cache*:[Cc]ache*:.thumbnails*:[Tt]rash*:*.backup*:*~', profile_id )
173 if len( value ) <= 0:
174 return []
175@@ -263,6 +318,9 @@
176 def set_exclude_patterns( self, list, profile_id = None ):
177 self.set_profile_str_value( 'snapshots.exclude_patterns', ':'.join( list ), profile_id )
178
179+ def get_tag( self, profile_id = None ):
180+ return self.get_profile_str_value( 'snapshots.tag', str(random.randint(100, 999)), profile_id )
181+
182 def get_automatic_backup_mode( self, profile_id = None ):
183 return self.get_profile_int_value( 'snapshots.automatic_backup_mode', self.NONE, profile_id )
184
185@@ -440,6 +498,7 @@
186 return path
187
188 def is_configured( self, profile_id = None ):
189+ '''Checks if the program is configured'''
190 if len( self.get_snapshots_path( profile_id ) ) == 0:
191 return False
192
193@@ -447,12 +506,126 @@
194 return False
195
196 return True
197-
198+
199+ def update_snapshot_location( self ):
200+ '''Updates to location: backintime/machine/user/profile_id'''
201+ if self.get_update_other_folders() == True:
202+ logger.info( 'Snapshot location update flag detected' )
203+ logger.warning( 'Snapshot location needs update' )
204+ profiles = self.get_profiles()
205+
206+ answer_change = self.question_handler( _('BackinTime changed its backup format.\n\nYour old snapshots can be moved according to this new format. OK?') )
207+ #print answer_change
208+ if answer_change == True:
209+ logger.info( 'Update snapshot locations' )
210+ #print len( profiles )
211+
212+ if len( profiles ) == 1:
213+ logger.info( 'Only 1 profile found' )
214+ answer_same = True
215+ elif len( profiles ) > 1:
216+ answer_same = self.question_handler( _('%s profiles found. \n\nThe new backup format supports storage of different users and profiles on the same location. Do you want the same location for both profiles? \n\n(The program will still be able to discriminate between them)') % len( profiles ) )
217+ else:
218+ logger.warning( 'No profiles are found!' )
219+ self.notify_error( _( 'No profiles are found. Will have to update to profiles first, please restart BackinTime' ) )
220+ logger.info( 'Config version is %s' % str( self.get_int_value( 'config.version', 1 ) ) )
221+
222+ if self.get_int_value( 'config.version', 1 ) > 1:
223+ self.set_int_value( 'config.version', 2 )
224+ logger.info( 'Config version set to 2' )
225+ return False
226+
227+ # Moving old snapshots per profile_id
228+ #print answer_same
229+ profile_id = profiles[0]
230+ #print profile_id
231+ #old_folder = self.get_snapshots_path( profile_id )
232+ #print old_folder
233+ main_folder = self.get_snapshots_path( profile_id )
234+ old_snapshots_paths=[]
235+ counter = 0
236+ success = []
237+
238+ for profile_id in profiles:
239+ #print counter
240+ old_snapshots_paths.append( self.get_snapshots_path( profile_id ) )
241+ #print old_snapshots_paths
242+ old_folder = os.path.join( self.get_snapshots_path( profile_id ), 'backintime' )
243+ #print old_folder
244+ if profile_id != "1" and answer_same == True:
245+ #print 'profile_id != 1, answer = True'
246+ self.set_snapshots_path( main_folder, profile_id )
247+ logger.info( 'Folder of profile %s is set to %s' %( profile_id, main_folder ) )
248+ else:
249+ self.set_snapshots_path( self.get_snapshots_path( profile_id ), profile_id )
250+ logger.info( 'Folder of profile %s is set to %s' %( profile_id, main_folder ) )
251+ new_folder = self.get_snapshots_full_path( profile_id )
252+ #print new_folder
253+ #snapshots_to_move = tools.get_snapshots_list_in_folder( old_folder )
254+ #print snapshots_to_move
255+
256+ output = tools.move_snapshots_folder( old_folder, new_folder )
257+
258+ snapshots_left = tools.get_snapshots_list_in_folder( old_folder )
259+ if output == True:
260+ success.append( True )
261+ if len( snapshots_left ) == 0:
262+ logger.info( 'Update was successful. Snapshots of profile %s are moved to their new location' % profile_id )
263+ else:
264+ logger.warning( 'Not all snapshots are removed from the original folder!' )
265+ logger.info( 'The following snapshots are still present: %s' % snapshots_left )
266+ logger.info( 'You could move them manually or leave them where they are now' )
267+ else:
268+ logger.warning( '%s: are not moved to their new location!' %snapshots_left )
269+
270+ answer_unsuccessful = self.question_handler( _('%s\nof profile %s are not moved to their new location\nDo you want to proceed?\n(BackinTime will be able to continue taking snapshots, however the remaining snapshots will not be considered for automatic removal)\n\nIf not BackinTime will restore former settings for this profile, however cannot continue taking snapshots' %( snapshots_left, profile_id ) ) )
271+ if answer_unsuccessful == True:
272+ success.append( True )
273+ else:
274+ success.append( False )
275+ # restore
276+ logger.info( 'Restore former settings' )
277+ self.set_snapshots_path( old_snapshots_paths[counter], profile_id )
278+ #print self.get_snapshots_path( profile_id )
279+ self.error_handler( _('Former settings of profile %s are restored.\nBackinTime cannot continue taking new snapshots.\n\nYou can manually move the snapshots, \nif you are done restart BackinTime to proceed' %profile_id ) )
280+
281+ counter = counter + 1
282+
283+ #print success
284+ overall_success = True
285+ for item in success:
286+ if item == False:
287+ overall_success = False
288+ if overall_success == True:
289+ self.set_update_other_folders( False )
290+ #print self.get_update_other_folders()
291+ logger.info( 'BackinTime will be able to make new snapshots again!' )
292+ self.error_handler( _('Update was successful!\n\nBackinTime will continue taking snapshots again as scheduled' ) )
293+ elif answer_change == False:
294+ logger.info( 'Move refused by user' )
295+ logger.warning( 'Old snapshots are not taken into account by smart-remove' )
296+ answer_continue = self.question_handler( _('Are you sure you do not want to move your old snapshots?\n\n\nIf you do, you will not see these questions again next time, BackinTime will continue making snapshots again, but smart-remove cannot take your old snapshots into account any longer!\n\nIf you do not, you will be asked again next time you start BackinTime.') )
297+ if answer_continue == True:
298+ self.set_update_other_folders( False )
299+ for profile_id in profiles:
300+ old_folder = self.get_snapshots_path( profile_id )
301+ self.set_snapshots_path( old_folder, profile_id )
302+ logger.info( 'Folder of profile %s is set to %s' %( profile_id, self.get_snapshots_path( profile_id ) ) )
303+
304+ logger.info( 'BackinTime will be able to make new snapshots again!' )
305+ self.error_handler( _('BackinTime will continue taking snapshots again as scheduled' ) )
306+ else:
307+ self.error_handler( _( 'BackinTime still cannot continue taking new snapshots.\nRestart BackinTime to see the questions again' ) )
308+ else:
309+ return False
310+
311 def can_backup( self, profile_id = None ):
312+ '''Checks if snapshots_path exists'''
313 if not self.is_configured( profile_id ):
314 return False
315
316 if not os.path.isdir( self.get_snapshots_full_path( profile_id ) ):
317+ print "%s does not exist" % self.get_snapshots_full_path( profile_id )
318 return False
319
320 return True
321@@ -534,7 +707,12 @@
322 os.system( "( crontab -l; %s ) | crontab -" % cron_line )
323
324 return True
325-
326+
327+ def get_update_other_folders( self ):
328+ return self.get_bool_value( 'update.other_folders', True )
329+
330+ def set_update_other_folders( self, value ):
331+ self.set_bool_value( 'update.other_folders', value )
332
333 if __name__ == "__main__":
334 config = Config()
335
336=== modified file 'common/snapshots.py'
337--- common/snapshots.py 2009-11-02 13:52:00 +0000
338+++ common/snapshots.py 2009-11-09 11:15:23 +0000
339@@ -40,6 +40,8 @@
340
341
342 class Snapshots:
343+ SNAPSHOT_VERSION = 3
344+
345 def __init__( self, cfg = None ):
346 self.config = cfg
347 if self.config is None:
348@@ -48,19 +50,64 @@
349 self.plugin_manager = pluginmanager.PluginManager()
350
351 def get_snapshot_id( self, date ):
352- if type( date ) is datetime.datetime:
353- return date.strftime( '%Y%m%d-%H%M%S' )
354-
355- if type( date ) is datetime.date:
356- return date.strftime( '%Y%m%d-000000' )
357-
358- if type( date ) is str:
359- return date
360+ profile_id = self.config.get_current_profile()
361+ tag = self.config.get_tag( profile_id )
362+
363+ if type( date ) is datetime.datetime:
364+ snapshot_id = date.strftime( '%Y%m%d-%H%M%S' ) + '-' + tag
365+ return snapshot_id
366+
367+ if type( date ) is datetime.date:
368+ snapshot_id = date.strftime( '%Y%m%d-000000' ) + '-' + tag
369+ return snapshot_id
370+
371+ if type( date ) is str:
372+ snapshot_id = date
373+ return snapshot_id
374+
375+ return ""
376+
377+ def get_snapshot_old_id( self, date ):
378+ profile_id = self.config.get_current_profile()
379+
380+ if type( date ) is datetime.datetime:
381+ snapshot_id = date.strftime( '%Y%m%d-%H%M%S' )
382+ return snapshot_id
383+
384+ if type( date ) is datetime.date:
385+ snapshot_id = date.strftime( '%Y%m%d-000000' )
386+ return snapshot_id
387+
388+ if type( date ) is str:
389+ snapshot_id = date
390+ return snapshot_id
391
392 return ""
393
394 def get_snapshot_path( self, date ):
395- return os.path.join( self.config.get_snapshots_full_path(), self.get_snapshot_id( date ) )
396+ profile_id = self.config.get_current_profile()
397+ path = os.path.join( self.config.get_snapshots_full_path( profile_id ), self.get_snapshot_id( date ) )
398+ if os.path.exists( path ):
399+ #print path
400+ return path
401+ other_folders = self.config.get_other_folders_paths()
402+ for folder in other_folders:
403+ path_other = os.path.join( folder, self.get_snapshot_id( date ) )
404+ if os.path.exists( path_other ):
405+ #print path_other
406+ return path_other
407+ old_path = os.path.join( self.config.get_snapshots_full_path( profile_id ), self.get_snapshot_old_id( date ) )
408+ if os.path.exists( path ):
409+ #print path
410+ return path
411+ other_folders = self.config.get_other_folders_paths()
412+ for folder in other_folders:
413+ path_other = os.path.join( folder, self.get_snapshot_old_id( date ) )
414+ if os.path.exists( path_other ):
415+ #print path_other
416+ return path_other
417+ #print path
418+ return path
419
420 def get_snapshot_info_path( self, date ):
421 return os.path.join( self.get_snapshot_path( date ), 'info' )
422@@ -258,11 +305,12 @@
423 backup_suffix = '.backup.' + datetime.date.today().strftime( '%Y%m%d' )
424 #cmd = "rsync -avR --copy-unsafe-links --whole-file --backup --suffix=%s --chmod=+w %s/.%s %s" % ( backup_suffix, self.get_snapshot_path_to( snapshot_id ), path, '/' )
425 cmd = "rsync -avRAXE --whole-file --backup --suffix=%s " % backup_suffix
426- cmd = cmd + '--chmod=+w '
427+ #cmd = cmd + '--chmod=+w '
428 cmd = cmd + "\"%s.%s\" %s" % ( self.get_snapshot_path_to( snapshot_id ), path, '/' )
429 self._execute( cmd )
430
431 #restore permissions
432+ logger.info( "Restore permissions" )
433 file_info_dict = self.load_fileinfo_dict( snapshot_id, info_file.get_int_value( 'snapshot_version' ) )
434 if len( file_info_dict ) > 0:
435 #explore items
436@@ -290,23 +338,67 @@
437 for item_path in all_dirs:
438 self._restore_path_info( item_path, file_info_dict )
439
440+
441 def get_snapshots_list( self, sort_reverse = True ):
442- biglist = []
443- snapshots_path = self.config.get_snapshots_full_path()
444-
445- try:
446- biglist = os.listdir( snapshots_path )
447- except:
448- pass
449-
450- list = []
451-
452- for item in biglist:
453- if len( item ) != 15:
454- continue
455- if os.path.isdir( os.path.join( snapshots_path, item ) ):
456- list.append( item )
457-
458+ '''Returns a list with the snapshot_ids of all snapshots in the snapshots folder'''
459+ biglist = []
460+ profile_id = self.config.get_current_profile()
461+ snapshots_path = self.config.get_snapshots_full_path( profile_id )
462+
463+ try:
464+ biglist = os.listdir( snapshots_path )
465+ except:
466+ pass
467+
468+ list = []
469+
470+ for item in biglist:
471+ if len( item ) != 15 and len( item ) != 19:
472+ continue
473+ if os.path.isdir( os.path.join( snapshots_path, item, 'backup' ) ):
474+ list.append( item )
475+
476+ list.sort( reverse = sort_reverse )
477+ return list
478+
479+ def get_snapshots_and_other_list( self, sort_reverse = True ):
480+ '''Returns a list with the snapshot_ids, and paths, of all snapshots in the snapshots_folder and the other_folders'''
481+
482+ biglist = []
483+ profile_id = self.config.get_current_profile()
484+ snapshots_path = self.config.get_snapshots_full_path( profile_id )
485+ snapshots_other_paths = self.config.get_other_folders_paths()
486+
487+ try:
488+ biglist = os.listdir( snapshots_path )
489+ except:
490+ pass
491+
492+ list = []
493+
494+ for item in biglist:
495+ if len( item ) != 15 and len( item ) != 19:
496+ continue
497+ if os.path.isdir( os.path.join( snapshots_path, item, 'backup' ) ):
498+ #a = ( item, snapshots_path )
499+ list.append( item )
500+
501+
502+ if len( snapshots_other_paths ) > 0:
503+ for folder in snapshots_other_paths:
504+ folderlist = []
505+ try:
506+ folderlist = os.listdir( folder )
507+ except:
508+ pass
509+
510+ for member in folderlist:
511+ if len( member ) != 15 and len( member ) != 19:
512+ continue
513+ if os.path.isdir( os.path.join( folder, member, 'backup' ) ):
514+ #a = ( member, folder )
515+ list.append( member )
516+
517 list.sort( reverse = sort_reverse )
518 return list
519
520@@ -321,6 +413,14 @@
521 cmd = "rm -rfv \"%s\"" % path
522 self._execute( cmd )
523
524+ def copy_snapshot( self, snapshot_id, new_folder ):
525+ '''Copies a known snapshot to a new location'''
526+ current.path = self.get_snapshot_path( snapshot_id )
527+ #need to implement hardlinking to existing folder -> cp newest snapshot folder, rsync -aEAXHv --delete to this folder
528+ cmd = "cp -al \"%s\"* \"%s\"" % ( current_path, new_folder )
529+ logger.info( '%s is copied to folder %s' %( snapshot_id, new_folder ) )
530+ self._execute( cmd )
531+
532 def _get_last_snapshot_info( self ):
533 lines = ''
534 dict = {}
535@@ -376,6 +476,9 @@
536 elif self.config.is_no_on_battery_enabled() and tools.on_battery():
537 logger.info( 'Deferring backup while on battery' )
538 logger.warning( 'Backup not performed' )
539+ elif self.config.get_update_other_folders() == True:
540+ logger.info( 'The application needs to change the backup format. Start the GUI to proceed. (As long as you do not you will not be able to make new snapshots!)' )
541+ logger.warning( 'Backup not performed' )
542 else:
543 instance = applicationinstance.ApplicationInstance( self.config.get_take_snapshot_instance_file(), False )
544 if not instance.check():
545@@ -396,15 +499,17 @@
546 now = now.replace( second = 0 )
547
548 include_folders, ignore_folders, dict = self._get_backup_folders( now, force )
549-
550+
551 if len( include_folders ) <= 0:
552 logger.info( 'Nothing to do' )
553 else:
554 self.plugin_manager.on_process_begins() #take snapshot process begin
555-
556+ logger.info( "on process begins" )
557 self.set_take_snapshot_message( 0, '...' )
558-
559- if not self.config.can_backup():
560+ profile_id = self.config.get_current_profile()
561+ logger.info( "Profile_id: %s" % profile_id )
562+
563+ if not self.config.can_backup( profile_id ):
564 if self.plugin_manager.has_gui_plugins() and self.config.is_notify_enabled():
565 for counter in xrange( 30, 0, -1 ):
566 self.set_take_snapshot_message( 1,
567@@ -415,13 +520,13 @@
568 if self.config.can_backup():
569 break
570
571- if not self.config.can_backup():
572+ if not self.config.can_backup( profile_id ):
573 logger.warning( 'Can\'t find snapshots folder !' )
574 self.plugin_manager.on_error( 3 ) #Can't find snapshots directory (is it on a removable drive ?)
575 else:
576 snapshot_id = self.get_snapshot_id( now )
577 snapshot_path = self.get_snapshot_path( snapshot_id )
578-
579+
580 if os.path.exists( snapshot_path ):
581 logger.warning( "Snapshot path \"%s\" already exists" % snapshot_path )
582 self.plugin_manager.on_error( 4, snapshot_id ) #This snapshots already exists
583@@ -580,7 +685,7 @@
584
585 new_snapshot_id = 'new_snapshot'
586 new_snapshot_path = self.get_snapshot_path( new_snapshot_id )
587-
588+
589 if os.path.exists( new_snapshot_path ):
590 #self._execute( "find \"%s\" -type d -exec chmod +w {} \;" % new_snapshot_path )
591 #self._execute( "chmod -R a+rwx \"%s\"" % new_snapshot_path )
592@@ -594,7 +699,7 @@
593 return False
594
595 new_snapshot_path_to = self.get_snapshot_path_to( new_snapshot_id )
596-
597+
598 #create exclude patterns string
599 items = []
600 for exclude in self.config.get_exclude_patterns():
601@@ -629,9 +734,17 @@
602 self._set_last_snapshot_info( dict )
603
604 #check previous backup
605+ #snapshots = self.get_snapshots_and_other_list() -> should only contain the personal snapshots
606 snapshots = self.get_snapshots_list()
607 prev_snapshot_id = ''
608-
609+
610+ if len( snapshots ) == 0:
611+ snapshots = self.get_snapshots_and_other_list()
612+ # When there is no snapshots it takes the last snapshot from the other folders
613+ # It should delete the excluded folders then
614+ rsync_prefix = rsync_prefix + '--delete-excluded '
615+
616+
617 if len( snapshots ) > 0:
618 prev_snapshot_id = snapshots[0]
619 prev_snapshot_name = self.get_snapshot_display_id( prev_snapshot_id )
620@@ -731,17 +844,19 @@
621
622 fileinfo.close()
623
624- #create info file
625+ #create info file
626 logger.info( "Create info file" )
627 machine = socket.gethostname()
628 user = os.environ['LOGNAME']
629 profile_id = self.config.get_current_profile()
630+ tag = self.config.get_tag( profile_id )
631 info_file = configfile.ConfigFile()
632- info_file.set_int_value( 'snapshot_version', 2 )
633- info_file.set_str_value( 'snapshot_date', snapshot_id )
634+ info_file.set_int_value( 'snapshot_version', self.SNAPSHOT_VERSION )
635+ info_file.set_str_value( 'snapshot_date', snapshot_id[0:15] )
636 info_file.set_str_value( 'snapshot_machine', machine )
637 info_file.set_str_value( 'snapshot_user', user )
638 info_file.set_int_value( 'snapshot_profile_id', profile_id )
639+ info_file.set_int_value( 'snapshot_tag', tag )
640 info_file.save( self.get_snapshot_info_path( new_snapshot_id ) )
641 info_file = None
642
643@@ -787,6 +902,7 @@
644
645 def smart_remove( self, now_full = None ):
646 snapshots = self.get_snapshots_list()
647+ logger.info( "[smart remove] considered: %s" % snapshots )
648 if len( snapshots ) <= 1:
649 logger.info( "[smart remove] There is only one snapshots, so keep it" )
650 return
651@@ -849,7 +965,7 @@
652 snapshots = self.get_snapshots_list( False )
653
654 old_backup_id = self.get_snapshot_id( self.config.get_remove_old_snapshots_date() )
655- logger.info( "Remove backups older than: %s" % old_backup_id )
656+ logger.info( "Remove backups older than: %s" % old_backup_id[0:15] )
657
658 while True:
659 if len( snapshots ) <= 1:
660
661=== modified file 'common/tools.py'
662--- common/tools.py 2009-11-02 13:52:00 +0000
663+++ common/tools.py 2009-11-09 11:15:23 +0000
664@@ -141,3 +141,149 @@
665 else:
666 return False
667
668+def get_snapshots_list_in_folder( folder, sort_reverse = True ):
669+ biglist = []
670+ #print folder
671+
672+ try:
673+ biglist = os.listdir( folder )
674+ #print biglist
675+ except:
676+ pass
677+
678+ list = []
679+
680+ for item in biglist:
681+ #print item + ' ' + str(len( item ))
682+ if len( item ) != 15 and len( item ) != 19:
683+ continue
684+ if os.path.isdir( os.path.join( folder, item, 'backup' ) ):
685+ #print item
686+ list.append( item )
687+
688+ list.sort( reverse = sort_reverse )
689+ return list
690+
691+def get_nonsnapshots_list_in_folder( folder, sort_reverse = True ):
692+ biglist = []
693+ #print folder
694+
695+ try:
696+ biglist = os.listdir( folder )
697+ #print biglist
698+ except:
699+ pass
700+
701+ list = []
702+
703+ for item in biglist:
704+ #print item + ' ' + str(len( item ))
705+ if len( item ) != 15 and len( item ) != 19:
706+ list.append( item )
707+ else:
708+ if os.path.isdir( os.path.join( folder, item, 'backup' ) ):
709+ #print item
710+ continue
711+ else:
712+ list.append( item )
713+
714+ list.sort( reverse = sort_reverse )
715+ return list
716+
717+def move_snapshots_folder( old_folder, new_folder ):
718+ '''Moves all the snapshots from one folder to another'''
719+ print "\nMove snapshots from %s to %s" %( old_folder, new_folder )
720+
721+ # Fetch a list with snapshots for verification
722+ snapshots_to_move = get_snapshots_list_in_folder( old_folder )
723+ snapshots_already_there = []
724+ if os.path.exists( new_folder ) == True:
725+ snapshots_already_there = get_snapshots_list_in_folder( new_folder )
726+ else:
727+ tools.make_dirs( new_folder )
728+ print "To move: %s" % snapshots_to_move
729+ print "Already there: %s" % snapshots_already_there
730+ snapshots_expected = snapshots_to_move + snapshots_already_there
731+ print "Snapshots expected: %s" % snapshots_expected
732+
733+ # Check if both folders are within the same os
734+ device_old = os.stat( old_folder ).st_dev
735+ device_new = os.stat( new_folder ).st_dev
736+ if device_old == device_new:
737+ # Use move
738+ for snapshot in snapshots_to_move:
739+ cmd = "mv -f \"%s/%s\" \"%s\"" %( old_folder, snapshot, new_folder )
740+ _execute( cmd )
741+ else:
742+ # Use rsync
743+ # Prepare hardlinks
744+ if len( snapshots_already_there ) > 0:
745+ first_snapshot_path = os.path.join( new_folder, snapshots_to_move[ len( snapshots_to_move ) - 1 ] )
746+ snapshot_to_hardlink_path = os.path.join( new_folder, snapshots_already_there[0] )
747+ cmd = "cp -al \"%s\" \"%s\"" % ( snapshot_to_hardlink_path, first_snapshot_path )
748+ _execute( cmd )
749+
750+ # Prepare excludes
751+ nonsnapshots = get_nonsnapshots_list_in_folder( old_folder )
752+ print "Nonsnapshots: %s" % nonsnapshots
753+ items = []
754+ for nonsnapshot in nonsnapshots:
755+ for item in items:
756+ if nonsnapshot == item:
757+ break
758+ items.append( "--exclude=\"%s\"" % nonsnapshot )
759+ rsync_exclude = ' '.join( items )
760+ #print rsync_exclude
761+
762+ # Move move move
763+ cmd = "rsync -aEAXHv --delete " + old_folder + " " + new_folder + " " + rsync_exclude
764+ _execute( cmd )
765+
766+ # Remove old ones
767+ snapshots_not_moved = []
768+ for snapshot in snapshots_to_move:
769+ if os.path.exists( os.path.join( new_folder, snapshot, "backup" ) ):
770+ if os.path.exists( os.path.join( old_folder, snapshot) ):
771+ print "Remove: %s" %snapshot
772+ path_to_remove = os.path.join( old_folder, snapshot )
773+ cmd = "find \"%s\" -type d -exec chmod u+wx {} \\;" % path_to_remove #Debian patch
774+ _execute( cmd )
775+ cmd = "rm -rfv \"%s\"" % path_to_remove
776+ _execute( cmd )
777+ else:
778+ print "%s was already removed" %snapshot
779+ else:
780+ snapshots_not_moved.append( snapshot )
781+
782+ # Check snapshot list
783+ if len( snapshots_not_moved ) == 0:
784+ print "Succes!\n"
785+ return True
786+ else:
787+ print "Error! Not moved: %s\n" %snapshots_not_moved
788+ return False
789+
790+def _execute( cmd, callback = None, user_data = None ):
791+ ret_val = 0
792+
793+ if callback is None:
794+ ret_val = os.system( cmd )
795+ else:
796+ pipe = os.popen( cmd, 'r' )
797+
798+ while True:
799+ line = pipe.readline()
800+ if len( line ) == 0:
801+ break
802+ callback( line.strip(), user_data )
803+
804+ ret_val = pipe.close()
805+ if ret_val is None:
806+ ret_val = 0
807+
808+ if ret_val != 0:
809+ print "Command \"%s\" returns %s" % ( cmd, ret_val )
810+ else:
811+ print "Command \"%s\" returns %s" % ( cmd, ret_val )
812+
813+ return ret_val
814
815=== modified file 'gnome/app.py'
816--- gnome/app.py 2009-11-02 13:52:00 +0000
817+++ gnome/app.py 2009-11-09 11:15:23 +0000
818@@ -301,8 +301,12 @@
819
820 if not self.config.is_configured():
821 return
822-
823- if not self.config.can_backup():
824+
825+ if self.config.get_update_other_folders() == True:
826+ settingsdialog.SettingsDialog( self.config, self ).update_snapshot_location()
827+
828+ profile_id = self.config.get_current_profile()
829+ if not self.config.can_backup( profile_id ):
830 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') )
831
832 self.update_profiles()
833@@ -617,7 +621,12 @@
834 self.store_time_line.clear()
835 self.store_time_line.append( [ gnometools.get_snapshot_display_markup( self.snapshots, '/' ), '/' ] )
836
837- self.snapshots_list = self.snapshots.get_snapshots_list()
838+ self.snapshots_list = self.snapshots.get_snapshots_and_other_list()
839+
840+ #print self.snapshots_list
841+ #self.snapshots_list = []
842+ #for item in self.snapshots_list_complete:
843+ # self.snapshots_list.append( item[0] )
844
845 groups = []
846 now = datetime.date.today()
847@@ -1126,6 +1135,7 @@
848
849 logger.openlog()
850 main_window = MainWindow( cfg, app_instance )
851+
852 if cfg.is_configured():
853 gtk.main()
854 logger.closelog()
855
856=== modified file 'gnome/settingsdialog.glade'
857--- gnome/settingsdialog.glade 2009-10-07 10:01:30 +0000
858+++ gnome/settingsdialog.glade 2009-11-09 11:15:23 +0000
859@@ -421,7 +421,7 @@
860 <object class="GtkTable" id="table2">
861 <property name="visible">True</property>
862 <property name="border_width">5</property>
863- <property name="n_rows">5</property>
864+ <property name="n_rows">6</property>
865 <property name="n_columns">3</property>
866 <property name="column_spacing">5</property>
867 <property name="row_spacing">5</property>
868@@ -550,6 +550,20 @@
869 <property name="x_padding">20</property>
870 </packing>
871 </child>
872+ <child>
873+ <object class="GtkLabel" id="label5">
874+ <property name="visible">True</property>
875+ <property name="label" translatable="yes">Auto-remove only removes snapshots from the selected snapshots folder</property>
876+ <attributes>
877+ <attribute name="style" value="italic"/>
878+ </attributes>
879+ </object>
880+ <packing>
881+ <property name="right_attach">3</property>
882+ <property name="top_attach">5</property>
883+ <property name="bottom_attach">6</property>
884+ </packing>
885+ </child>
886 </object>
887 <packing>
888 <property name="position">3</property>
889
890=== modified file 'gnome/settingsdialog.py'
891--- gnome/settingsdialog.py 2009-11-02 13:52:00 +0000
892+++ gnome/settingsdialog.py 2009-11-09 11:15:23 +0000
893@@ -379,6 +379,7 @@
894 self.cb_no_on_battery.set_active( self.config.is_no_on_battery_enabled() )
895
896 def save_profile( self ):
897+ profile_id = self.config.get_current_profile()
898 #snapshots path
899 snapshots_path = self.edit_where.get_text()
900
901@@ -406,7 +407,7 @@
902 # return False
903
904 #ok let's save to config
905- self.config.set_snapshots_path( snapshots_path )
906+ self.config.set_snapshots_path( snapshots_path, profile_id )
907 #if not msg is None:
908 # messagebox.show_error( self.dialog, self.config, msg )
909 # return False
910@@ -482,7 +483,13 @@
911 self.config.clear_handlers()
912
913 self.dialog.destroy()
914-
915+
916+ def update_snapshot_location( self ):
917+ '''Update snapshot location dialog'''
918+ self.config.set_question_handler( self.question_handler )
919+ self.config.set_error_handler( self.error_handler )
920+ self.config.update_snapshot_location()
921+
922 def on_add_profile(self, button, data=None):
923
924 name = messagebox.text_input_dialog( self.dialog, self.config, _('New profile'), None )
925
926=== modified file 'kde4/app.py'
927--- kde4/app.py 2009-11-02 13:52:00 +0000
928+++ kde4/app.py 2009-11-09 11:15:23 +0000
929@@ -297,8 +297,13 @@
930
931 if not cfg.is_configured():
932 return
933+
934+ if self.config.get_update_other_folders() == True:
935+ settingsdialog.SettingsDialog( self ).update_snapshot_location()
936
937- if not cfg.can_backup():
938+
939+ profile_id = cfg.get_current_profile()
940+ if not cfg.can_backup( profile_id ):
941 KMessageBox.error( self, QString.fromUtf8( _('Can\'t find snapshots folder.\nIf it is on a removable drive please plug it and then press OK') ) )
942
943 QObject.connect( self.list_files_view_model.dirLister(), SIGNAL('completed()'), self.on_dir_lister_completed )
944@@ -497,7 +502,7 @@
945 elif not self.btn_take_snapshot.isEnabled():
946 self.btn_take_snapshot.setEnabled( True )
947
948- snapshots_list = self.snapshots.get_snapshots_list()
949+ snapshots_list = self.snapshots.get_snapshots_and_other_list()
950
951 if snapshots_list != self.snapshots_list:
952 self.snapshots_list = snapshots_list
953@@ -617,7 +622,7 @@
954 self.add_time_line( QString.fromUtf8( self.snapshots.get_snapshot_display_name( '/' ) ), '/' )
955
956 if get_snapshots_list:
957- self.snapshots_list = self.snapshots.get_snapshots_list()
958+ self.snapshots_list = self.snapshots.get_snapshots_and_other_list()
959
960 groups = []
961 now = datetime.date.today()
962
963=== modified file 'kde4/settingsdialog.py'
964--- kde4/settingsdialog.py 2009-11-02 13:52:00 +0000
965+++ kde4/settingsdialog.py 2009-11-09 11:15:23 +0000
966@@ -457,6 +457,12 @@
967
968 return ret_val
969
970+ def update_snapshot_location( self ):
971+ '''Update snapshot location dialog'''
972+ self.config.set_question_handler( self.question_handler )
973+ self.config.set_error_handler( self.error_handler )
974+ self.config.update_snapshot_location()
975+
976 def update_include_columns( self ):
977 if self.cb_per_diretory_schedule.isChecked():
978 self.list_include.showColumn( 1 )

Subscribers

People subscribed via source and target branches