Merge lp:~bratdaking/backintime/snapshot-location into lp:backintime/1.0
- snapshot-location
- Merge into release-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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Back In Time Team | Pending | ||
Review via email: mp+14644@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Bart de Koning (bratdaking) wrote : | # |
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 ) |
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