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

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

Description of the change

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

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

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

725. By Adam Reeve

Merge from trunk

726. By Adam Reeve

Merge from trunk

Unmerged revisions

726. By Adam Reeve

Merge from trunk

725. By Adam Reeve

Merge from trunk

724. By Adam Reeve

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

723. By Adam Reeve

Removed all my vim modelines

722. By Adam Reeve

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

721. By Adam Reeve

Work on KDE interface for network access

720. By Adam Reeve

Fixed crash when config file doesn't exist

719. By Adam Reeve

Work on samba access, GTK interface should be finished now

718. By Adam Reeve

Merged changes in trunk

717. By Adam Reeve

Work on gnome interface for network shares

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'common/config.py'
2--- common/config.py 2010-04-01 17:57:51 +0000
3+++ common/config.py 2010-04-16 06:46:25 +0000
4@@ -22,10 +22,12 @@
5 import gettext
6 import socket
7 import random
8+import re
9
10 import configfile
11 import tools
12 import logger
13+import network
14
15 _=gettext.gettext
16
17@@ -175,10 +177,41 @@
18 self.remove_profile_key( 'snapshots.include_folders', profile_id )
19 self.remove_profile_key( 'snapshots.exclude_patterns', profile_id )
20
21- self.set_int_value( 'config.version', self.CONFIG_VERSION )
22+ # make sure config version is set
23+ self.set_int_value( 'config.version', self.CONFIG_VERSION )
24+
25+ # mount required network shares
26+ self.mount_shares()
27+
28+ def mount_shares( self, profile=None ):
29+ self.network_mounts = {}
30+ if profile is None:
31+ profiles = self.get_profiles()
32+ else:
33+ profiles = [profile]
34+ for profile_id in profiles:
35+ if (self.get_snapshot_access_mode(profile_id) in network.NETWORK_ACCESS_MODES):
36+ # mount network share now if the snapshots are stored on a network share
37+ path = self.get_profile_str_value('snapshots.network_path','',profile_id)
38+ protocol = self.get_snapshot_access_mode(profile_id)
39+ username = self.get_profile_str_value('snapshots.username','',profile_id)
40+ password = network.password_decode(self.get_profile_str_value('snapshots.password','',profile_id))
41+ self.network_mounts[profile_id] = network.NetworkMount(path,protocol,username,password)
42+
43+ def unmount_shares( self ):
44+ for mount in self.network_mounts.values():
45+ mount.unmount()
46+ self.network_mounts.clear()
47+
48+ def update_shares( self ):
49+ self.unmount_shares()
50+ self.mount_shares()
51
52 def save( self ):
53 configfile.ConfigFile.save( self, self._LOCAL_CONFIG_PATH )
54+ # make sure no other users can read the config file
55+ # as it may contain encoded passwords for network shares
56+ os.chmod(self._LOCAL_CONFIG_PATH,0600)
57
58 def check_config( self ):
59 profiles = self.get_profiles()
60@@ -252,8 +285,27 @@
61 return user
62
63 def get_snapshots_path( self, profile_id = None ):
64+ if (self.get_snapshot_access_mode(profile_id) in network.NETWORK_ACCESS_MODES):
65+ if profile_id is None:
66+ profile_id = self.get_current_profile()
67+ if profile_id not in self.network_mounts.keys():
68+ self.mount_shares(profile_id)
69+ return self.network_mounts[profile_id].path()
70+ else:
71+ return self.get_snapshots_local_path( profile_id )
72+
73+ def get_snapshots_local_path( self, profile_id = None ):
74 return self.get_profile_str_value( 'snapshots.path', '', profile_id )
75
76+ def get_snapshots_username( self, profile_id = None ):
77+ return self.get_profile_str_value( 'snapshots.username', '', profile_id )
78+
79+ def get_snapshots_password( self, profile_id = None ):
80+ return network.password_decode(self.get_profile_str_value( 'snapshots.password', '', profile_id ))
81+
82+ def get_snapshots_network_path( self, profile_id = None ):
83+ return self.get_profile_str_value( 'snapshots.network_path', '', profile_id )
84+
85 def get_snapshots_full_path( self, profile_id = None, version = None ):
86 '''Returns the full path for the snapshots: .../backintime/machine/user/profile_id/'''
87 if version is None:
88@@ -304,6 +356,54 @@
89
90 os.rmdir( check_path )
91 self.set_profile_str_value( 'snapshots.path', value, profile_id )
92+ self.set_profile_int_value( 'snapshots.access', network.LOCAL, profile_id )
93+ return True
94+
95+ def set_snapshots_network_details( self, location, protocol, username, password, profile_id = None ):
96+ """Sets network access details and checks that they work"""
97+ # remove anything like smb: at the start
98+ match = re.compile(r"^([a-zA-Z]+:)?(//)?(.+)$").match(location)
99+ if not match: # only occurs if location is empty
100+ self.notify_error( _( 'Invalid network location, %s' ) % location )
101+ return False
102+ location = "//"+match.group(3)
103+
104+ if profile_id == None:
105+ profile_id = self.get_current_profile()
106+
107+ test_mount = network.NetworkMount(location, protocol, username, password)
108+ if not test_mount.mounted():
109+ test_mount.unmount() #removes directory
110+ self.notify_error( _( 'Can\'t mount network share: %s\nAre you sure you sure your settings are correct?' % location ) )
111+ return False
112+
113+ #Initialize the snapshots folder
114+ print "Check snapshot mount: %s" % location
115+ machine = socket.gethostname()
116+ user = self.get_user()
117+ full_path = os.path.join( test_mount.path(), 'backintime', machine, user, profile_id )
118+ if not os.path.isdir( full_path ):
119+ print "Create folder: %s" % full_path
120+ tools.make_dirs( full_path )
121+ if not os.path.isdir( full_path ):
122+ self.notify_error( _( 'Can\'t write to: %s\nAre you sure you have write access ?' % location ) )
123+ return False
124+
125+ #Test write access for the folder
126+ check_path = os.path.join( full_path, 'check' )
127+ tools.make_dirs( check_path )
128+ if not os.path.isdir( check_path ):
129+ self.notify_error( _( 'Can\'t write to: %s\nAre you sure you have write access ?' % full_path.replace(test_mount.path(),location) ) )
130+ return False
131+ os.rmdir( check_path )
132+
133+ test_mount.unmount()
134+
135+ encoded_pw = network.password_encode(password)
136+ self.set_profile_int_value( 'snapshots.access', protocol, profile_id )
137+ self.set_profile_str_value( 'snapshots.network_path', location, profile_id )
138+ self.set_profile_str_value( 'snapshots.username', username, profile_id )
139+ self.set_profile_str_value( 'snapshots.password', encoded_pw, profile_id )
140 return True
141
142 def get_other_folders_paths( self, profile_id = None ):
143@@ -418,6 +518,12 @@
144 def get_tag( self, profile_id = None ):
145 return self.get_profile_str_value( 'snapshots.tag', str(random.randint(100, 999)), profile_id )
146
147+ def get_snapshot_access_mode( self, profile_id = None ):
148+ return self.get_profile_int_value( 'snapshots.access', network.LOCAL, profile_id )
149+
150+ def set_snapshot_access_mode( self, value, profile_id = None ):
151+ self.set_profile_int_value( 'snapshots.access', value, profile_id )
152+
153 def get_automatic_backup_mode( self, profile_id = None ):
154 return self.get_profile_int_value( 'snapshots.automatic_backup_mode', self.NONE, profile_id )
155
156
157=== modified file 'common/debian_specific/control'
158--- common/debian_specific/control 2010-04-01 17:57:51 +0000
159+++ common/debian_specific/control 2010-04-16 06:46:25 +0000
160@@ -7,7 +7,7 @@
161 Homepage: http://backintime.le-web.org
162 Architecture: all
163 Depends: python, rsync, cron
164-Recommends: powermgmt-base
165+Recommends: powermgmt-base, smbfs
166 Conflicts: backintime
167 Replaces: backintime
168 Description: Simple backup system (common)
169
170=== added file 'common/network.py'
171--- common/network.py 1970-01-01 00:00:00 +0000
172+++ common/network.py 2010-04-16 06:46:25 +0000
173@@ -0,0 +1,105 @@
174+# Back In Time
175+# Copyright (C) 2008-2009 Oprea Dan, Bart de Koning, Richard Bailey
176+#
177+# This program is free software; you can redistribute it and/or modify
178+# it under the terms of the GNU General Public License as published by
179+# the Free Software Foundation; either version 2 of the License, or
180+# (at your option) any later version.
181+#
182+# This program is distributed in the hope that it will be useful,
183+# but WITHOUT ANY WARRANTY; without even the implied warranty of
184+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
185+# GNU General Public License for more details.
186+#
187+# You should have received a copy of the GNU General Public License along
188+# with this program; if not, write to the Free Software Foundation, Inc.,
189+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
190+
191+import os.path
192+import os
193+import tempfile
194+import base64
195+import gettext
196+
197+import tools
198+
199+_=gettext.gettext
200+
201+LOCAL = 0
202+SAMBA = 1
203+
204+NETWORK_ACCESS_MODES = [SAMBA]
205+
206+SNAPSHOT_ACCESS = {
207+ LOCAL : _('Local folder'),
208+ SAMBA : _('Windows network share (Samba)')
209+ }
210+
211+def password_encode(password):
212+ """encode a password using base64 but remove extra
213+ equals signs from the end so they can be stored
214+ in the config file"""
215+ # this is not secure at all but means that the password
216+ # isn't human readable in the config file. A more secure
217+ # alternative would be to use the python keyring module
218+ # to store the password using the KDE or Gnome keyring manager
219+ # but this isn't available in most distros yet
220+ return base64.urlsafe_b64encode(password).strip("=")
221+
222+def password_decode(password):
223+ """return the base64 decoded password and pad with equals
224+ signs first so that the string is a multiple of 4 long"""
225+ return base64.urlsafe_b64decode(password+'='*(4-len(password)%4))
226+
227+class NetworkMount:
228+ def __init__( self, location, protocol, username="", password="" ):
229+ self.location = location
230+ self.username = username
231+ self.password = password
232+ supported_protocols = [SAMBA]
233+ if protocol in supported_protocols:
234+ self.protocol = protocol
235+ else:
236+ raise RuntimeError, "Unsupported network protocol, "+protocol
237+ self.mount()
238+
239+ def __del__( self ):
240+ self.unmount()
241+
242+ def mount( self ):
243+ self.mount_dir = tempfile.mkdtemp(prefix="backintime_mount_")
244+ # need to use full paths to mount and unmount commands so that they work in cron
245+ if (self.protocol == SAMBA):
246+ cmd = "/sbin/mount.cifs "+self.location+" "+self.mount_dir
247+ if (self.username != "" and self.password != ""):
248+ cmd += " -o username="+self.username+",password="+self.password
249+ ret = _execute(cmd)
250+ if (ret != 0):
251+ print "Error mounting network share "+self.location
252+
253+ def unmount( self ):
254+ """Unmount a network share and delete the mount directory.
255+ You can't rely on python to delete the NetworkMount object so
256+ this should be called manually when finished with a network share"""
257+ if self.mounted():
258+ if (self.protocol == SAMBA):
259+ cmd = "/sbin/umount.cifs "+self.mount_dir
260+ _execute(cmd)
261+ if os.path.isdir(self.mount_dir):
262+ os.rmdir(self.mount_dir)
263+
264+ def path( self ):
265+ return self.mount_dir
266+
267+ def mounted( self ):
268+ if not os.path.isdir(self.mount_dir):
269+ return False
270+ ret = _execute("mountpoint "+self.mount_dir)
271+ if (ret == 0):
272+ return True
273+ else:
274+ return False
275+
276+def _execute( cmd ):
277+ ret_val = os.system( cmd )
278+ return ret_val
279
280=== modified file 'common/snapshots.py'
281--- common/snapshots.py 2010-03-29 14:27:12 +0000
282+++ common/snapshots.py 2010-04-16 06:46:25 +0000
283@@ -34,6 +34,7 @@
284 import applicationinstance
285 import tools
286 import pluginmanager
287+import network
288
289
290 _=gettext.gettext
291@@ -514,26 +515,45 @@
292 #print answer_same
293 profile_id = profiles[0]
294 #print profile_id
295- #old_folder = self.get_snapshots_path( profile_id )
296- #print old_folder
297+ main_snapshot_access = self.config.get_snapshot_access_mode(profile_id)
298+ if main_snapshot_access in network.NETWORK_ACCESS_MODES:
299+ main_network_path=self.config.get_snapshots_network_path(profile_id)
300+ main_username=self.config.get_snapshots_username(profile_id)
301+ main_password=self.config.get_snapshots_password(profile_id)
302+
303 main_folder = self.config.get_snapshots_path( profile_id )
304- old_snapshots_paths=[]
305+ old_snapshots_paths = []
306+ old_snapshot_access = []
307+ old_network_path = {}
308+ old_username = {}
309+ old_password = {}
310 counter = 0
311 success = []
312
313 for profile_id in profiles:
314 #print counter
315+ old_snapshot_access.append(self.config.get_snapshot_access_mode(profile_id))
316+ if old_snapshot_access[counter] in network.NETWORK_ACCESS_MODES:
317+ old_network_path[counter] = self.config.get_snapshots_network_path(profile_id)
318+ old_username[counter] = self.config.get_snapshots_username(profile_id)
319+ old_password[counter] = self.config.get_snapshots_password(profile_id)
320 old_snapshots_paths.append( self.config.get_snapshots_path( profile_id ) )
321 #print old_snapshots_paths
322 old_folder = os.path.join( self.config.get_snapshots_path( profile_id ), 'backintime' )
323 #print old_folder
324 if profile_id != "1" and answer_same == True:
325 #print 'profile_id != 1, answer = True'
326- self.config.set_snapshots_path( main_folder, profile_id )
327+ if main_snapshot_access in network.NETWORK_ACCESS_MODES:
328+ self.config.set_snapshots_network_details( main_network_path, main_snapshot_access, main_username, main_password, profile_id )
329+ else:
330+ self.config.set_snapshots_path( main_folder, profile_id )
331 logger.info( 'Folder of profile %s is set to %s' %( profile_id, main_folder ) )
332 else:
333- self.config.set_snapshots_path( self.config.get_snapshots_path( profile_id ), profile_id )
334- logger.info( 'Folder of profile %s is set to %s' %( profile_id, main_folder ) )
335+ if old_snapshot_access[counter] in network.NETWORK_ACCESS_MODES:
336+ self.config.set_snapshots_network_details( old_network_path[counter], old_snapshot_access[counter], old_username[counter], old_password[counter], profile_id )
337+ else:
338+ self.config.set_snapshots_path( self.config.get_snapshots_path( profile_id ), profile_id )
339+ logger.info( 'Folder of profile %s is set to %s' %( profile_id, old_folder ) )
340 new_folder = self.config.get_snapshots_full_path( profile_id )
341 #print new_folder
342 #snapshots_to_move = tools.get_snapshots_list_in_folder( old_folder )
343@@ -560,7 +580,10 @@
344 success.append( False )
345 # restore
346 logger.info( 'Restore former settings' )
347- self.config.set_snapshots_path( old_snapshots_paths[counter], profile_id )
348+ if old_snapshot_access[counter] in network.NETWORK_ACCESS_MODES:
349+ self.config.set_snapshots_network_details( old_network_path[counter], old_snapshot_access[counter], old_username[counter], old_password[counter], profile_id )
350+ else:
351+ self.config.set_snapshots_path( old_snapshots_paths[counter], profile_id )
352 #print self.get_snapshots_path( profile_id )
353 self.config.error_handler( _('Former settings of profile %s are restored.\nBackinTime cannot continue taking new snapshots.\n\nYou can manually move the snapshots, \nif you are done restart BackinTime to proceed' %profile_id ) )
354
355@@ -584,9 +607,16 @@
356 if answer_continue == True:
357 #self.set_update_other_folders( False )
358 for profile_id in profiles:
359- old_folder = self.config.get_snapshots_path( profile_id )
360- self.config.set_snapshots_path( old_folder, profile_id )
361- logger.info( 'Folder of profile %s is set to %s' %( profile_id, self.get_snapshots_path( profile_id ) ) )
362+ old_snapshot_access = self.config.get_snapshot_access_mode(profile_id)
363+ if old_snapshot_access in network.NETWORK_ACCESS_MODES:
364+ old_network_path = self.config.get_snapshots_network_path(profile_id)
365+ old_username = self.config.get_snapshots_username(profile_id)
366+ old_password = self.config.get_snapshots_password(profile_id)
367+ self.config.set_snapshots_network_details( old_network_path, old_snapshot_access, old_username, old_password, profile_id )
368+ else:
369+ old_folder = self.config.get_snapshots_path( profile_id )
370+ self.config.set_snapshots_path( old_folder, profile_id )
371+ logger.info( 'Folder of profile %s is set to %s' %( profile_id, self.get_snapshot_path( profile_id ) ) )
372
373 logger.info( 'BackinTime will be able to make new snapshots again!' )
374 self.config.error_handler( _('BackinTime will continue taking snapshots again as scheduled' ) )
375@@ -1276,4 +1306,5 @@
376 config = config.Config()
377 snapshots = Snapshots( config )
378 snapshots.take_snapshot()
379+ config.unmount_shares()
380
381
382=== modified file 'common/tools.py'
383--- common/tools.py 2010-02-14 13:47:14 +0000
384+++ common/tools.py 2010-04-16 06:46:25 +0000
385@@ -21,12 +21,10 @@
386 import sys
387 import subprocess
388
389-
390 ON_AC = 0
391 ON_BATTERY = 1
392 POWER_ERROR = 255
393
394-
395 def get_backintime_path( path ):
396 return os.path.join( os.path.dirname( os.path.abspath( os.path.dirname( __file__ ) ) ), path )
397
398@@ -268,7 +266,7 @@
399 return False
400
401
402-def _execute( cmd, callback = None, user_data = None ):
403+def _execute( cmd, callback = None, user_data = None):
404 ret_val = 0
405
406 if callback is None:
407
408=== modified file 'gnome/app.py'
409--- gnome/app.py 2010-03-23 09:34:13 +0000
410+++ gnome/app.py 2010-04-16 06:46:25 +0000
411@@ -452,6 +452,7 @@
412 #fill lists
413 selected_file = None
414 show_snapshots = False
415+ self.config.update_shares()
416 if init:
417 self.folder_path, selected_file, show_snapshots = self.get_startup_folder_and_file()
418 self.snapshot_id = '/'
419@@ -925,12 +926,17 @@
420 self.on_help()
421
422 def on_btn_settings_clicked( self, button ):
423- snapshots_path = self.config.get_snapshots_path()
424+ snapshots_local_path = self.config.get_snapshots_local_path()
425+ snapshots_network_path = self.config.get_snapshots_network_path()
426+ snapshots_access = self.config.get_snapshot_access_mode()
427 include_folders = self.config.get_include()
428
429 settingsdialog.SettingsDialog( self.config, self.snapshots, self ).run()
430
431- if snapshots_path != self.config.get_snapshots_path() or include_folders != self.config.get_include():
432+ if (snapshots_local_path != self.config.get_snapshots_local_path() or
433+ snapshots_network_path != self.config.get_snapshots_network_path() or
434+ snapshots_access != self.config.get_snapshot_access_mode() or
435+ include_folders != self.config.get_include()):
436 self.update_all( False )
437 self.update_profiles()
438
439@@ -1173,5 +1179,7 @@
440 gtk.main()
441 logger.closelog()
442
443+ cfg.unmount_shares()
444+
445 app_instance.exit_application()
446
447
448=== modified file 'gnome/settingsdialog.glade'
449--- gnome/settingsdialog.glade 2010-03-05 20:41:59 +0000
450+++ gnome/settingsdialog.glade 2010-04-16 06:46:25 +0000
451@@ -123,42 +123,167 @@
452 <property name="label_xalign">0</property>
453 <property name="shadow_type">none</property>
454 <child>
455- <object class="GtkAlignment" id="alignment1">
456+ <object class="GtkVBox" id="vbox1">
457 <property name="visible">True</property>
458- <property name="left_padding">12</property>
459- <child>
460- <object class="GtkHBox" id="hbox4">
461- <property name="visible">True</property>
462- <child>
463- <object class="GtkEntry" id="edit_where">
464- <property name="visible">True</property>
465- <property name="can_focus">True</property>
466- <property name="editable">False</property>
467- <property name="invisible_char">&#x25CF;</property>
468- </object>
469- <packing>
470- <property name="position">0</property>
471- </packing>
472- </child>
473- <child>
474- <object class="GtkButton" id="btn_where">
475- <property name="visible">True</property>
476- <property name="can_focus">True</property>
477- <property name="receives_default">True</property>
478- <signal name="clicked" handler="on_btn_where_clicked"/>
479- <child>
480- <object class="GtkImage" id="image1">
481- <property name="visible">True</property>
482- <property name="stock">gtk-directory</property>
483- </object>
484- </child>
485- </object>
486- <packing>
487- <property name="expand">False</property>
488- <property name="position">1</property>
489- </packing>
490- </child>
491- </object>
492+ <property name="orientation">vertical</property>
493+ <property name="spacing">3</property>
494+ <child>
495+ <object class="GtkAlignment" id="alignment3">
496+ <property name="visible">True</property>
497+ <property name="left_padding">12</property>
498+ <child>
499+ <object class="GtkHBox" id="hbox8">
500+ <property name="visible">True</property>
501+ <property name="homogeneous">True</property>
502+ <child>
503+ <object class="GtkComboBox" id="cb_snapshot_access">
504+ <property name="visible">True</property>
505+ <signal name="changed" handler="on_cb_snapshot_access_changed"/>
506+ </object>
507+ <packing>
508+ <property name="position">0</property>
509+ </packing>
510+ </child>
511+ </object>
512+ </child>
513+ </object>
514+ <packing>
515+ <property name="position">0</property>
516+ </packing>
517+ </child>
518+ <child>
519+ <object class="GtkAlignment" id="alignment_where">
520+ <property name="visible">True</property>
521+ <property name="left_padding">12</property>
522+ <child>
523+ <object class="GtkHBox" id="hbox4">
524+ <property name="visible">True</property>
525+ <child>
526+ <object class="GtkEntry" id="edit_where">
527+ <property name="visible">True</property>
528+ <property name="can_focus">True</property>
529+ <property name="editable">False</property>
530+ <property name="invisible_char">&#x2022;</property>
531+ </object>
532+ <packing>
533+ <property name="position">0</property>
534+ </packing>
535+ </child>
536+ <child>
537+ <object class="GtkButton" id="btn_where">
538+ <property name="visible">True</property>
539+ <property name="can_focus">True</property>
540+ <property name="receives_default">True</property>
541+ <signal name="clicked" handler="on_btn_where_clicked"/>
542+ <child>
543+ <object class="GtkImage" id="image1">
544+ <property name="visible">True</property>
545+ <property name="stock">gtk-directory</property>
546+ </object>
547+ </child>
548+ </object>
549+ <packing>
550+ <property name="expand">False</property>
551+ <property name="position">1</property>
552+ </packing>
553+ </child>
554+ </object>
555+ </child>
556+ </object>
557+ <packing>
558+ <property name="position">1</property>
559+ </packing>
560+ </child>
561+ <child>
562+ <object class="GtkAlignment" id="alignment_network">
563+ <property name="left_padding">12</property>
564+ <child>
565+ <object class="GtkTable" id="table1">
566+ <property name="visible">True</property>
567+ <property name="n_rows">3</property>
568+ <property name="n_columns">2</property>
569+ <property name="column_spacing">25</property>
570+ <child>
571+ <object class="GtkEntry" id="edit_where_network">
572+ <property name="visible">True</property>
573+ <property name="can_focus">True</property>
574+ <property name="invisible_char">&#x2022;</property>
575+ <property name="caps_lock_warning">False</property>
576+ </object>
577+ <packing>
578+ <property name="left_attach">1</property>
579+ <property name="right_attach">2</property>
580+ </packing>
581+ </child>
582+ <child>
583+ <object class="GtkLabel" id="label7">
584+ <property name="visible">True</property>
585+ <property name="xalign">0</property>
586+ <property name="label" translatable="yes">Network location</property>
587+ </object>
588+ <packing>
589+ <property name="x_options">GTK_FILL</property>
590+ </packing>
591+ </child>
592+ <child>
593+ <object class="GtkLabel" id="label8">
594+ <property name="visible">True</property>
595+ <property name="xalign">0</property>
596+ <property name="label" translatable="yes">Username</property>
597+ </object>
598+ <packing>
599+ <property name="top_attach">1</property>
600+ <property name="bottom_attach">2</property>
601+ <property name="x_options">GTK_FILL</property>
602+ </packing>
603+ </child>
604+ <child>
605+ <object class="GtkEntry" id="edit_username">
606+ <property name="visible">True</property>
607+ <property name="can_focus">True</property>
608+ <property name="invisible_char">&#x2022;</property>
609+ <property name="caps_lock_warning">False</property>
610+ </object>
611+ <packing>
612+ <property name="left_attach">1</property>
613+ <property name="right_attach">2</property>
614+ <property name="top_attach">1</property>
615+ <property name="bottom_attach">2</property>
616+ </packing>
617+ </child>
618+ <child>
619+ <object class="GtkLabel" id="label9">
620+ <property name="visible">True</property>
621+ <property name="xalign">0</property>
622+ <property name="label" translatable="yes">Password</property>
623+ </object>
624+ <packing>
625+ <property name="top_attach">2</property>
626+ <property name="bottom_attach">3</property>
627+ <property name="x_options">GTK_FILL</property>
628+ <property name="y_options">GTK_FILL</property>
629+ </packing>
630+ </child>
631+ <child>
632+ <object class="GtkEntry" id="edit_password">
633+ <property name="visible">True</property>
634+ <property name="can_focus">True</property>
635+ <property name="visibility">False</property>
636+ <property name="invisible_char">&#x2022;</property>
637+ </object>
638+ <packing>
639+ <property name="left_attach">1</property>
640+ <property name="right_attach">2</property>
641+ <property name="top_attach">2</property>
642+ <property name="bottom_attach">3</property>
643+ </packing>
644+ </child>
645+ </object>
646+ </child>
647+ </object>
648+ <packing>
649+ <property name="position">2</property>
650+ </packing>
651 </child>
652 </object>
653 </child>
654
655=== modified file 'gnome/settingsdialog.py'
656--- gnome/settingsdialog.py 2010-03-05 20:41:59 +0000
657+++ gnome/settingsdialog.py 2010-04-16 06:46:25 +0000
658@@ -30,6 +30,7 @@
659 import config
660 import messagebox
661 import tools
662+import network
663
664
665 _=gettext.gettext
666@@ -74,6 +75,7 @@
667 'on_combo_profiles_changed': self.on_combo_profiles_changed,
668 'on_btn_where_clicked': self.on_btn_where_clicked,
669 'on_cb_backup_mode_changed': self.on_cb_backup_mode_changed,
670+ 'on_cb_snapshot_access_changed': self.on_cb_snapshot_access_changed,
671 }
672
673 builder.connect_signals(signals)
674@@ -102,6 +104,19 @@
675 #self.fcb_where = get( 'fcb_where' )
676 #self.fcb_where.set_show_hidden( self.parent.show_hidden_files )
677 self.edit_where = get( 'edit_where' )
678+
679+ #snapshot location access
680+ self.store_snapshot_access_mode = gtk.ListStore( str, int )
681+ map = network.SNAPSHOT_ACCESS
682+ self.rev_snapshot_access_modes = {}
683+ keys = map.keys()
684+ keys.sort()
685+ for key in keys:
686+ self.rev_snapshot_access_modes[ map[key] ] = key
687+ self.store_snapshot_access_mode.append( [ map[key], key ] )
688+ self.edit_where_network = get( 'edit_where_network' )
689+ self.edit_username = get( 'edit_username' )
690+ self.edit_password = get( 'edit_password' )
691
692 #automatic backup mode store
693 self.store_backup_mode = gtk.ListStore( str, int )
694@@ -165,6 +180,18 @@
695
696 self.store_exclude = gtk.ListStore( str, str )
697 self.list_exclude.set_model( self.store_exclude )
698+
699+ #setup snapshot access mode
700+ self.cb_snapshot_access = get( 'cb_snapshot_access' )
701+ self.cb_snapshot_access.set_model( self.store_snapshot_access_mode)
702+
703+ self.cb_snapshot_access.clear()
704+ renderer = gtk.CellRendererText()
705+ self.cb_snapshot_access.pack_start( renderer, True )
706+ self.cb_snapshot_access.add_attribute( renderer, 'text', 0 )
707+
708+ self.alignment_where = get( 'alignment_where' )
709+ self.alignment_network = get( 'alignment_network' )
710
711 #setup automatic backup mode
712 self.cb_backup_mode = get( 'cb_backup_mode' )
713@@ -306,6 +333,17 @@
714 self.profile_id = profile_id
715
716 self.update_profile()
717+
718+ def on_cb_snapshot_access_changed( self, *params ):
719+ iter = self.cb_snapshot_access.get_active_iter()
720+ access = self.store_snapshot_access_mode.get_value( iter, 1 )
721+
722+ if access in network.NETWORK_ACCESS_MODES:
723+ self.alignment_where.hide()
724+ self.alignment_network.show()
725+ else:
726+ self.alignment_where.show()
727+ self.alignment_network.hide()
728
729 def update_profiles( self ):
730 self.disable_combo_changed = True
731@@ -335,7 +373,12 @@
732
733 #set current folder
734 #self.fcb_where.set_filename( self.config.get_snapshots_path() )
735- self.edit_where.set_text( self.config.get_snapshots_path( self.profile_id ) )
736+ self.edit_where.set_text( self.config.get_snapshots_local_path( self.profile_id ) )
737+
738+ #setup network paths
739+ self.edit_where_network.set_text( self.config.get_snapshots_network_path( self.profile_id ) )
740+ self.edit_username.set_text( self.config.get_snapshots_username( self.profile_id ) )
741+ self.edit_password.set_text( self.config.get_snapshots_password( self.profile_id ) )
742
743 #per directory schedule
744 #self.cb_per_directory_schedule.set_active( self.config.get_per_directory_schedule() )
745@@ -358,6 +401,17 @@
746 if len( exclude_patterns ) > 0:
747 for exclude_pattern in exclude_patterns:
748 self.store_exclude.append( [exclude_pattern, gtk.STOCK_DELETE] )
749+
750+ #setup snapshot access mode
751+ i = 0
752+ iter = self.store_snapshot_access_mode.get_iter_first()
753+ default_mode = self.config.get_snapshot_access_mode( self.profile_id )
754+ while not iter is None:
755+ if self.store_snapshot_access_mode.get_value( iter, 1 ) == default_mode:
756+ self.cb_snapshot_access.set_active( i )
757+ break
758+ iter = self.store_snapshot_access_mode.iter_next( iter )
759+ i = i + 1
760
761 #setup automatic backup mode
762 i = 0
763@@ -443,13 +497,26 @@
764
765 def save_profile( self ):
766 #profile_id = self.config.get_current_profile()
767- #snapshots path
768- snapshots_path = self.edit_where.get_text()
769-
770- #hack
771- if snapshots_path.startswith( '//' ):
772- snapshots_path = snapshots_path[ 1 : ]
773-
774+
775+ iter = self.cb_snapshot_access.get_active_iter()
776+ access = self.store_snapshot_access_mode.get_value( iter, 1 )
777+ if access in network.NETWORK_ACCESS_MODES:
778+ network_path = self.edit_where_network.get_text()
779+ username = self.edit_username.get_text()
780+ password = self.edit_password.get_text()
781+ if not self.config.set_snapshots_network_details(network_path,access,username,password):
782+ return False
783+ self.config.update_shares()
784+ else:
785+ #snapshots path
786+ snapshots_path = self.edit_where.get_text()
787+ #hack
788+ if snapshots_path.startswith( '//' ):
789+ snapshots_path = snapshots_path[ 1 : ]
790+ #ok let's save to config
791+ if not self.config.set_snapshots_path( snapshots_path, self.profile_id ):
792+ return False
793+
794 #include list
795 include_list = []
796 iter = self.store_include.get_iter_first()
797@@ -471,12 +538,6 @@
798 #if len( self.config.get_snapshots_path() ) > 0 and self.config.get_snapshots_path() != snapshots_path:
799 # if gtk.RESPONSE_YES != messagebox.show_question( self.dialog, self.config, _('Are you sure you want to change snapshots folder ?') ):
800 # return False
801-
802- #ok let's save to config
803- self.config.set_snapshots_path( snapshots_path, self.profile_id )
804- #if not msg is None:
805- # messagebox.show_error( self.dialog, self.config, msg )
806- # return False
807
808 self.config.set_include( include_list, self.profile_id )
809 self.config.set_exclude( exclude_list, self.profile_id )
810@@ -509,6 +570,8 @@
811 self.config.set_run_ionice_from_cron_enabled( self.cb_run_ionice_from_cron.get_active(), self.profile_id )
812 self.config.set_run_ionice_from_user_enabled( self.cb_run_ionice_from_user.get_active(), self.profile_id )
813 self.config.set_no_on_battery_enabled( self.cb_no_on_battery.get_active(), self.profile_id )
814+
815+ return True
816
817 def update_remove_old_backups( self, button ):
818 enabled = self.cb_remove_old_backup.get_active()
819@@ -687,7 +750,8 @@
820 self.dialog.destroy()
821
822 def validate( self ):
823- self.save_profile()
824+ if not self.save_profile():
825+ return False
826
827 if not self.config.check_config():
828 return False
829
830=== modified file 'kde4/app.py'
831--- kde4/app.py 2010-04-01 17:57:51 +0000
832+++ kde4/app.py 2010-04-16 06:46:25 +0000
833@@ -389,6 +389,8 @@
834
835 self.disable_profile_changed = False
836
837+ self.config.update_shares()
838+
839 def update_profile( self ):
840 self.update_time_line()
841 self.update_places()
842@@ -1098,5 +1100,7 @@
843
844 logger.closelog()
845
846+ cfg.unmount_shares()
847+
848 app_instance.exit_application()
849
850
851=== modified file 'kde4/settingsdialog.py'
852--- kde4/settingsdialog.py 2010-03-05 20:41:59 +0000
853+++ kde4/settingsdialog.py 2010-04-16 06:46:25 +0000
854@@ -32,6 +32,7 @@
855 import config
856 import tools
857 import kde4tools
858+import network
859
860
861 _=gettext.gettext
862@@ -108,8 +109,15 @@
863 group_box.setTitle( QString.fromUtf8( _( 'Where to save snapshots' ) ) )
864 layout.addWidget( group_box )
865
866- hlayout = QHBoxLayout( group_box )
867-
868+ vlayout = QVBoxLayout( group_box )
869+
870+ self.combo_snapshot_access = KComboBox( self )
871+ vlayout.addWidget(self.combo_snapshot_access)
872+ self.fill_combo( self.combo_snapshot_access, network.SNAPSHOT_ACCESS )
873+ QObject.connect( self.combo_snapshot_access, SIGNAL('currentIndexChanged(int)'), self.current_snapshot_access_changed )
874+
875+ # - In a local folder
876+ hlayout = QHBoxLayout()
877 self.edit_snapshots_path = KLineEdit( self )
878 self.edit_snapshots_path.setReadOnly( True )
879 hlayout.addWidget( self.edit_snapshots_path )
880@@ -118,6 +126,38 @@
881 hlayout.addWidget( self.btn_snapshots_path )
882 QObject.connect( self.btn_snapshots_path, SIGNAL('clicked()'), self.on_btn_snapshots_path_clicked )
883
884+ self.local_path_widgets = [self.edit_snapshots_path, self.btn_snapshots_path]
885+
886+ vlayout.addLayout(hlayout)
887+
888+ # - Over the network
889+ glayout = QGridLayout()
890+ self.edit_snapshots_network_path = KLineEdit( self )
891+ self.label_snapshots_network_path = QLabel(QString.fromUtf8( _( 'Network location')), self )
892+ self.label_snapshots_network_path.setBuddy(self.edit_snapshots_network_path)
893+ glayout.addWidget(self.label_snapshots_network_path,0,0)
894+ glayout.addWidget(self.edit_snapshots_network_path,0,1)
895+
896+ self.edit_snapshots_username = KLineEdit( self )
897+ self.label_snapshots_username = QLabel(QString.fromUtf8( _( 'Username')), self )
898+ self.label_snapshots_username.setBuddy(self.edit_snapshots_username)
899+ glayout.addWidget(self.label_snapshots_username,1,0)
900+ glayout.addWidget(self.edit_snapshots_username,1,1)
901+
902+ self.edit_snapshots_password = KLineEdit( self )
903+ self.edit_snapshots_password.setEchoMode(KLineEdit.EchoMode(KLineEdit.Password))
904+ self.label_snapshots_password = QLabel(QString.fromUtf8( _( 'Password')), self )
905+ self.label_snapshots_password.setBuddy(self.edit_snapshots_password)
906+ glayout.addWidget(self.label_snapshots_password,2,0)
907+ glayout.addWidget(self.edit_snapshots_password,2,1)
908+
909+ self.network_widgets = [self.edit_snapshots_network_path, self.label_snapshots_network_path,
910+ self.edit_snapshots_username, self.label_snapshots_username,
911+ self.edit_snapshots_password, self.label_snapshots_password]
912+
913+ glayout.setHorizontalSpacing(35)
914+ vlayout.addLayout(glayout)
915+
916 #Schedule
917 group_box = QGroupBox( self )
918 self.global_schedule_group_box = group_box
919@@ -130,7 +170,7 @@
920 hlayout.addWidget( self.combo_automatic_snapshots, 2 )
921 self.fill_combo( self.combo_automatic_snapshots, self.config.AUTOMATIC_BACKUP_MODES )
922
923- hlayout_time = QHBoxLayout( group_box )
924+ hlayout_time = QHBoxLayout()
925 hlayout.addLayout( hlayout_time )
926
927 self.lbl_automatic_snapshots_time = QLabel( QString.fromUtf8( _( 'Hour:' ) ), self )
928@@ -356,6 +396,17 @@
929 def current_automatic_snapshot_changed( self, index ):
930 backup_mode = self.combo_automatic_snapshots.itemData( index ).toInt()[0]
931 self.update_automatic_snapshot_time( backup_mode )
932+
933+ def update_snapshot_access_mode( self, access_mode ):
934+ network_access = (access_mode in network.NETWORK_ACCESS_MODES)
935+ for widget in self.local_path_widgets:
936+ widget.setVisible(not network_access)
937+ for widget in self.network_widgets:
938+ widget.setVisible(network_access)
939+
940+ def current_snapshot_access_changed( self, index ):
941+ access = self.combo_snapshot_access.itemData( index ).toInt()[0]
942+ self.update_snapshot_access_mode( access )
943
944 def current_profile_changed( self, index ):
945 if self.disable_profile_changed:
946@@ -395,7 +446,15 @@
947 self.btn_remove_profile.setEnabled( True )
948
949 #TAB: General
950- self.edit_snapshots_path.setText( QString.fromUtf8( self.config.get_snapshots_path() ) )
951+ self.set_combo_value( self.combo_snapshot_access, self.config.get_snapshot_access_mode() )
952+ self.update_snapshot_access_mode( self.config.get_snapshot_access_mode() )
953+
954+ self.edit_snapshots_path.setText( QString.fromUtf8( self.config.get_snapshots_local_path() ) )
955+
956+ self.edit_snapshots_network_path.setText( QString.fromUtf8( self.config.get_snapshots_network_path() ))
957+ self.edit_snapshots_username.setText( QString.fromUtf8( self.config.get_snapshots_username() ))
958+ self.edit_snapshots_password.setText( QString.fromUtf8( self.config.get_snapshots_password() ))
959+
960 self.set_combo_value( self.combo_automatic_snapshots, self.config.get_automatic_backup_mode() )
961 self.set_combo_value( self.combo_automatic_snapshots_time, self.config.get_automatic_backup_time() )
962 self.update_automatic_snapshot_time( self.config.get_automatic_backup_mode() )
963@@ -450,7 +509,17 @@
964
965 def save_profile( self ):
966 #snapshots path
967- self.config.set_snapshots_path( str( self.edit_snapshots_path.text().toUtf8() ) )
968+ access = self.combo_snapshot_access.itemData( self.combo_snapshot_access.currentIndex() ).toInt()[0]
969+ if access in network.NETWORK_ACCESS_MODES:
970+ network_path = str( self.edit_snapshots_network_path.text().toUtf8() )
971+ username = str( self.edit_snapshots_username.text().toUtf8() )
972+ password = str( self.edit_snapshots_password.text().toUtf8() )
973+ if not self.config.set_snapshots_network_details(network_path,access,username,password):
974+ return False
975+ self.config.update_shares()
976+ else:
977+ if not self.config.set_snapshots_path( str( self.edit_snapshots_path.text().toUtf8() ) ):
978+ return False
979
980 #include list
981 include_list = []
982@@ -495,6 +564,8 @@
983 self.config.set_run_ionice_from_user_enabled( self.cb_run_ionice_from_user.isChecked() )
984 self.config.set_no_on_battery_enabled( self.cb_no_on_battery.isChecked() )
985
986+ return True
987+
988 def error_handler( self, message ):
989 KMessageBox.error( self, QString.fromUtf8( message ) )
990
991@@ -594,7 +665,8 @@
992 break
993
994 def validate( self ):
995- self.save_profile()
996+ if not self.save_profile():
997+ return False
998
999 if not self.config.check_config():
1000 return False

Subscribers

People subscribed via source and target branches