Merge lp:~ryan-c-ahearn/backintime/command_line_interface into lp:backintime/1.0

Proposed by Ryan Ahearn
Status: Needs review
Proposed branch: lp:~ryan-c-ahearn/backintime/command_line_interface
Merge into: lp:backintime/1.0
Diff against target: 1954 lines (+1735/-5)
30 files modified
.bzrignore (+2/-0)
AUTHORS (+1/-0)
README (+12/-0)
cli/Makefile.template (+25/-0)
cli/backintime-config (+27/-0)
cli/backintime-restore (+27/-0)
cli/bitconfig.py (+142/-0)
cli/bitrestore.py (+131/-0)
cli/configure (+12/-0)
cli/copywidget.py (+67/-0)
cli/debian_specific/control (+10/-0)
cli/debian_specific/control.source (+14/-0)
cli/debian_specific/postrm (+3/-0)
cli/debian_specific/rules (+50/-0)
cli/excludewidget.py (+118/-0)
cli/expertwidget.py (+47/-0)
cli/generalwidget.py (+93/-0)
cli/includewidget.py (+118/-0)
cli/optionswidget.py (+45/-0)
cli/profilewidget.py (+162/-0)
cli/removesnapshot.py (+51/-0)
cli/removewidget.py (+114/-0)
cli/restorewidget.py (+107/-0)
cli/snapshotname.py (+52/-0)
cli/snapshotswidget.py (+158/-0)
cli/viewerwidget.py (+132/-0)
common/backintime.py (+1/-1)
common/logger.py (+0/-3)
makedeb.sh (+1/-1)
updateversion.sh (+13/-0)
To merge this branch: bzr merge lp:~ryan-c-ahearn/backintime/command_line_interface
Reviewer Review Type Date Requested Status
Back In Time Team Pending
Review via email: mp+20422@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Ryan Ahearn (ryan-c-ahearn) wrote :

Here's my first cut of a command line interface for back in time configuration and restore.

It's not the prettiest interface right now, but it seems functionally solid. All of the displayed strings have the gettext call around them, but I don't speak any other languages so I have left the language files alone.

I tried to leave common alone as much as possible, but I had to suppress a couple of print calls to clean up the display when a user kicked off taking a snapshot from inside of backintime-restore.

Let me know what you think.

691. By Ryan Ahearn

Fixed bug in setting the hour to run backintime for daily/weekly/monthly backups

Unmerged revisions

691. By Ryan Ahearn

Fixed bug in setting the hour to run backintime for daily/weekly/monthly backups

690. By Ryan Ahearn

Added gettext calls to all strings that will be displayed to the user

689. By Ryan Ahearn

Added cli entries to the updateversion script

688. By Ryan Ahearn

Added myself to the AUTHORS file

687. By Ryan Ahearn

Added sections on cli to the README

686. By Ryan Ahearn

Added simple packaging files for backintime-cli

685. By Ryan Ahearn

Uncommented the man page install lines from Makefile.template to get them installed

684. By Ryan Ahearn

Added backintime-config and backintime-restore man pages

683. By Ryan Ahearn

Can now set the backup time for daily, weekly, and monthly backups

682. By Ryan Ahearn

Added ability to name and delete snapshots from restore window

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file '.bzrignore'
2--- .bzrignore 1970-01-01 00:00:00 +0000
3+++ .bzrignore 2010-03-02 06:06:17 +0000
4@@ -0,0 +1,2 @@
5+Makefile
6+common/po/*.mo
7
8=== modified file 'AUTHORS'
9--- AUTHORS 2009-11-06 13:00:42 +0000
10+++ AUTHORS 2010-03-02 06:06:17 +0000
11@@ -1,3 +1,4 @@
12 Oprea Dan (<dan@le-web.org>)
13 Bart de Koning (<bratdaking@gmail.com>)
14 Richard Bailey (<rmjb@mail.com>)
15+Ryan Ahearn (<ryan.c.ahearn@gmail.com>)
16
17=== modified file 'README'
18--- README 2009-11-02 13:52:00 +0000
19+++ README 2010-03-02 06:06:17 +0000
20@@ -39,6 +39,12 @@
21 sudo dpkg -i backintime-common-<version>.deb
22 sudo dpkg -i backintime-kde4-<version>.deb
23
24+ Command Line Interface: (python-urwid >= 0.9.9)
25+
26+ ./makedeb.sh
27+ sudo dpkg -i backintime-common-<version>.deb
28+ sudo dpkg -i backintime-cli-<version>.deb
29+
30 NOTE:
31 Ubuntu 8.04: to install KDE4 4.1 check this link:
32 http://news.softpedia.com/news/How-To-Install-KDE-4-1-On-Ubuntu-8-04-91034.shtml
33@@ -50,6 +56,12 @@
34 ./configure
35 make
36 sudo make install
37+
38+ Command Line Interface (dependencies: python-urwid (> 0.9.9)):
39+ cd cli
40+ ./configure
41+ make
42+ sudo make install
43
44 GNOME (dependencies: python-glade2, python-gnome2, meld):
45 cd gnome
46
47=== added directory 'cli'
48=== added file 'cli/Makefile.template'
49--- cli/Makefile.template 1970-01-01 00:00:00 +0000
50+++ cli/Makefile.template 2010-03-02 06:06:17 +0000
51@@ -0,0 +1,25 @@
52+PREFIX=/usr
53+DEST=$(DESTDIR)$(PREFIX)
54+
55+all:
56+
57+clean:
58+
59+install:
60+ #install python
61+ install -d $(DEST)/share/backintime/cli
62+ install --mode=644 *.py $(DEST)/share/backintime/cli
63+
64+ #install copyright file
65+ install -d $(DEST)/share/doc/backintime-cli
66+ install --mode=644 ../common/debian_specific/copyright $(DEST)/share/doc/backintime-cli
67+
68+ #install man file(s)
69+ install -d $(DEST)/share/man/man1
70+ install --mode=644 man/C/*.gz $(DEST)/share/man/man1
71+
72+ #install application
73+ install -d $(DEST)/bin
74+ install backintime-config $(DEST)/bin
75+ install backintime-restore $(DEST)/bin
76+
77
78=== added file 'cli/backintime-config'
79--- cli/backintime-config 1970-01-01 00:00:00 +0000
80+++ cli/backintime-config 2010-03-02 06:06:17 +0000
81@@ -0,0 +1,27 @@
82+#!/bin/sh
83+
84+# Back In Time
85+# Copyright (C) 2010 Ryan Ahearn
86+#
87+# This program is free software; you can redistribute it and/or modify
88+# it under the terms of the GNU General Public License as published by
89+# the Free Software Foundation; either version 2 of the License, or
90+# (at your option) any later version.
91+#
92+# This program is distributed in the hope that it will be useful,
93+# but WITHOUT ANY WARRANTY; without even the implied warranty of
94+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
95+# GNU General Public License for more details.
96+#
97+# You should have received a copy of the GNU General Public License along
98+# with this program; if not, write to the Free Software Foundation, Inc.,
99+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
100+
101+if [ -f bitconfig.py ]; then
102+ APP_PATH="."
103+else
104+ APP_PATH="/usr/share/backintime/cli"
105+fi
106+
107+python ${APP_PATH}/bitconfig.py "$@"
108+
109
110=== added file 'cli/backintime-restore'
111--- cli/backintime-restore 1970-01-01 00:00:00 +0000
112+++ cli/backintime-restore 2010-03-02 06:06:17 +0000
113@@ -0,0 +1,27 @@
114+#!/bin/sh
115+
116+# Back In Time
117+# Copyright (C) 2010 Ryan Ahearn
118+#
119+# This program is free software; you can redistribute it and/or modify
120+# it under the terms of the GNU General Public License as published by
121+# the Free Software Foundation; either version 2 of the License, or
122+# (at your option) any later version.
123+#
124+# This program is distributed in the hope that it will be useful,
125+# but WITHOUT ANY WARRANTY; without even the implied warranty of
126+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
127+# GNU General Public License for more details.
128+#
129+# You should have received a copy of the GNU General Public License along
130+# with this program; if not, write to the Free Software Foundation, Inc.,
131+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
132+
133+if [ -f bitrestore.py ]; then
134+ APP_PATH="."
135+else
136+ APP_PATH="/usr/share/backintime/cli"
137+fi
138+
139+python ${APP_PATH}/bitrestore.py "$@"
140+
141
142=== added file 'cli/bitconfig.py'
143--- cli/bitconfig.py 1970-01-01 00:00:00 +0000
144+++ cli/bitconfig.py 2010-03-02 06:06:17 +0000
145@@ -0,0 +1,142 @@
146+# Back In Time
147+# Copyright (C) 2010 Ryan Ahearn
148+#
149+# This program is free software; you can redistribute it and/or modify
150+# it under the terms of the GNU General Public License as published by
151+# the Free Software Foundation; either version 2 of the License, or
152+# (at your option) any later version.
153+#
154+# This program is distributed in the hope that it will be useful,
155+# but WITHOUT ANY WARRANTY; without even the implied warranty of
156+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
157+# GNU General Public License for more details.
158+#
159+# You should have received a copy of the GNU General Public License along
160+# with this program; if not, write to the Free Software Foundation, Inc.,
161+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
162+
163+
164+import os.path
165+import sys
166+import urwid
167+import gettext
168+
169+sys.path = [os.path.join( os.path.dirname( os.path.abspath( os.path.dirname( __file__ ) ) ), 'common' )] + sys.path
170+
171+import backintime
172+import config
173+import logger
174+import guiapplicationinstance
175+import profilewidget
176+import generalwidget
177+import includewidget
178+import excludewidget
179+import removewidget
180+import optionswidget
181+import expertwidget
182+
183+_=gettext.gettext
184+
185+class MainWindow(object):
186+
187+ def __init__( self, config ):
188+ self._config = config
189+
190+ self._header = _('Back in Time Configuration') + '. ' + _('F6 exits')
191+ setting_sections = [ "Profiles", "General", "Include", "Exclude", "Auto-remove", "Options", "Expert Options" ]
192+ self._listbox_content = [
193+ urwid.Divider(), # position 0
194+ urwid.Text( _("Profile") + ": " + config.get_profile_name() ), # position 1
195+ urwid.Divider(), # position 2
196+ urwid.GridFlow( [ urwid.AttrWrap( urwid.Button( _(txt), self.sections_button_press, txt ), 'button', 'button_focus' ) for txt in setting_sections ],
197+ 18, 2, 1, 'center' ), # position 3
198+ urwid.Divider(), # position 4
199+ profilewidget.ProfileWidget( self, config ), # position 5
200+ ]
201+
202+ def sections_button_press( self, button, user_data ):
203+ if user_data == 'Profiles':
204+ widget = profilewidget.ProfileWidget( self, self._config )
205+ elif user_data == 'General':
206+ widget = generalwidget.GeneralWidget( self, self._config )
207+ elif user_data == 'Include':
208+ widget = includewidget.IncludeWidget( self, self._config )
209+ elif user_data == 'Exclude':
210+ widget = excludewidget.ExcludeWidget( self, self._config )
211+ elif user_data == 'Auto-remove':
212+ widget = removewidget.RemoveWidget( self._config )
213+ elif user_data == 'Options':
214+ widget = optionswidget.OptionsWidget( self._config )
215+ elif user_data == 'Expert Options':
216+ widget = expertwidget.ExpertOptionsWidget( self._config )
217+ self.update_content( widget, 5 )
218+
219+ def show( self ):
220+ header = urwid.AttrWrap( urwid.Text( self._header, align='center' ), 'header' )
221+ self._content = urwid.SimpleListWalker( self._listbox_content )
222+ self._listbox = urwid.ListBox( self._content )
223+ frame = urwid.Frame( urwid.AttrWrap( self._listbox, 'body' ), header=header )
224+
225+ palette = [
226+ ( 'body', 'black', 'light gray', 'standout' ),
227+ ( 'error', 'dark red', 'light gray', 'bold' ),
228+ ( 'header', 'white', 'dark red', 'bold' ),
229+ ( 'button', 'black', 'dark cyan' ),
230+ ( 'button_focus', 'white', 'dark blue', 'bold' ),
231+ ( 'edit_box', 'light gray', 'dark blue' ),
232+ ( 'edit_focus', 'white', 'dark blue', 'bold' )
233+ ]
234+
235+ urwid.MainLoop( frame, palette, unhandled_input=self.unhandled_input ).run()
236+
237+ def update_content( self, widget, position ):
238+ self._content[position] = widget
239+
240+ def update_profile_name( self ):
241+ self.update_content( urwid.Text( _('Profile') + ': ' + self._config.get_profile_name() ), 1 )
242+
243+ def unhandled_input( self, key ):
244+ if key == 'f6':
245+ self.on_close()
246+
247+ def on_close( self ):
248+ self.update_content( ConfirmSave( self._config ), 5 )
249+
250+class ConfirmSave(urwid.WidgetWrap):
251+
252+ def __init__( self, config ):
253+ self._config = config
254+
255+ pile_list = [
256+ urwid.Padding( urwid.Text( _('Save configuration?') ), 'center' ),
257+ urwid.Divider(),
258+ urwid.GridFlow( [
259+ urwid.AttrWrap( urwid.Button( _('Yes'), self.save ), 'button', 'button_focus' ),
260+ urwid.AttrWrap( urwid.Button( _('No'), self.close ), 'button', 'button_focus' )
261+ ], 10, 2, 1, 'center' )
262+ ]
263+
264+ display_widget = urwid.Pile( pile_list )
265+ urwid.WidgetWrap.__init__( self, display_widget )
266+
267+ def save( self, button ):
268+ self._config.save()
269+ self._config.setup_cron()
270+ self.close()
271+
272+ def close( self, button=None ):
273+ raise urwid.ExitMainLoop()
274+
275+if __name__ == '__main__':
276+ cfg = backintime.start_app( 'backintime-config' )
277+ raise_cmd = ''
278+ if len( sys.argv ) > 1:
279+ raise_cmd = '\n'.join( sys.argv[ 1 : ] )
280+ app_instance = guiapplicationinstance.GUIApplicationInstance( cfg.get_app_instance_file(), raise_cmd )
281+
282+ logger.openlog()
283+ main_window = MainWindow( cfg )
284+ main_window.show()
285+ logger.closelog()
286+
287+ app_instance.exit_application()
288
289=== added file 'cli/bitrestore.py'
290--- cli/bitrestore.py 1970-01-01 00:00:00 +0000
291+++ cli/bitrestore.py 2010-03-02 06:06:17 +0000
292@@ -0,0 +1,131 @@
293+# Back In Time
294+# Copyright (C) 2010 Ryan Ahearn
295+#
296+# This program is free software; you can redistribute it and/or modify
297+# it under the terms of the GNU General Public License as published by
298+# the Free Software Foundation; either version 2 of the License, or
299+# (at your option) any later version.
300+#
301+# This program is distributed in the hope that it will be useful,
302+# but WITHOUT ANY WARRANTY; without even the implied warranty of
303+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304+# GNU General Public License for more details.
305+#
306+# You should have received a copy of the GNU General Public License along
307+# with this program; if not, write to the Free Software Foundation, Inc.,
308+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309+
310+
311+import os.path
312+import sys
313+import urwid
314+import gettext
315+
316+sys.path = [os.path.join( os.path.dirname( os.path.abspath( os.path.dirname( __file__ ) ) ), 'common' )] + sys.path
317+
318+import backintime
319+import config
320+import logger
321+import guiapplicationinstance
322+import profilewidget
323+import restorewidget
324+import removesnapshot
325+import snapshotname
326+
327+_=gettext.gettext
328+
329+class MainWindow(object):
330+
331+ def __init__( self, config ):
332+ self._config = config
333+ self.selected_snapshot = ''
334+ self.selected_snapshot_name = ''
335+ self.current_directory = config.get_str_value( 'cli.last_path', '/' )
336+ self.show_hidden = False
337+
338+ self._header = _('Back in Time Restore Utility') + '. ' + _('F6 exits')
339+ self._profile_button = urwid.Button( config.get_profile_name(), self.change_profile )
340+ buttons = [ 'Take Snapshot', 'Update Snapshots', 'Snapshot Name', 'Remove Snapshot' ]
341+ self._listbox_content = [
342+ urwid.Divider(), # position 0
343+ urwid.AttrWrap( self._profile_button, 'button', 'button_focus' ), # position 1
344+ urwid.Divider(), # position 2
345+ urwid.GridFlow( [ urwid.AttrWrap( urwid.Button( _(txt), self.button_press, txt ), 'button', 'button_focus' ) for txt in buttons ],
346+ 20, 2, 1, 'center' ), # position 3
347+ urwid.Divider(), # position 4
348+ restorewidget.RestoreWidget( self, config ), # position 5
349+ ]
350+
351+ def change_profile( self, button ):
352+ self.update_content( profilewidget.ProfileWidget( self, self._config, False ), 5 )
353+
354+ def button_press( self, button, user_data ):
355+ if user_data == 'Take Snapshot':
356+ backintime.take_snapshot_now_async( self._config )
357+ elif user_data == 'Update Snapshots':
358+ self.update_content( restorewidget.RestoreWidget( self, self._config ), 5 )
359+ elif user_data == 'Snapshot Name' and self.selected_snapshot != '':
360+ self.update_content( snapshotname.SnapshotName( self, self._config ), 5 )
361+ elif user_data == 'Remove Snapshot' and self.selected_snapshot != '':
362+ self.update_content( removesnapshot.RemoveSnapshot( self, self._config ), 5 )
363+
364+ def show( self ):
365+ header = urwid.AttrWrap( urwid.Text( self._header, align='center' ), 'header' )
366+ self._content = urwid.SimpleListWalker( self._listbox_content )
367+ self._listbox = urwid.ListBox( self._content )
368+ frame = urwid.Frame( urwid.AttrWrap( self._listbox, 'body' ), header=header )
369+
370+ palette = [
371+ ( 'body', 'black', 'light gray', 'standout' ),
372+ ( 'error', 'dark red', 'light gray', 'bold' ),
373+ ( 'header', 'white', 'dark red', 'bold' ),
374+ ( 'button', 'black', 'dark cyan' ),
375+ ( 'button_focus', 'white', 'dark blue', 'bold' ),
376+ ( 'edit_box', 'light gray', 'dark blue' ),
377+ ( 'edit_focus', 'white', 'dark blue', 'bold' )
378+ ]
379+
380+ self.main_loop = urwid.MainLoop( frame, palette, unhandled_input=self.unhandled_input )
381+ self.main_loop.run()
382+
383+ def update_content( self, widget, position ):
384+ self._content[position] = widget
385+
386+ def update_profile_name( self ):
387+ self._profile_button.set_label( self._config.get_profile_name() )
388+ self.selected_snapshot = ''
389+ self.update_content( restorewidget.RestoreWidget( self, self._config ), 5 )
390+
391+ def update_current_directory( self, new_directory ):
392+ self.current_directory = new_directory
393+ self._config.set_str_value( 'cli.last_path', new_directory )
394+ self.update_content( restorewidget.RestoreWidget( self, self._config ), 5 )
395+
396+ def is_selected_snapshot( self, snapshot ):
397+ if self.selected_snapshot == snapshot:
398+ return True
399+ else:
400+ return False
401+
402+ def unhandled_input( self, key ):
403+ if key == 'f6':
404+ self.on_close()
405+
406+ def on_close( self ):
407+ self._config.save()
408+ raise urwid.ExitMainLoop()
409+
410+
411+if __name__ == '__main__':
412+ cfg = backintime.start_app( 'backintime-restore' )
413+ raise_cmd = ''
414+ if len( sys.argv ) > 1:
415+ raise_cmd = '\n'.join( sys.argv[ 1 : ] )
416+ app_instance = guiapplicationinstance.GUIApplicationInstance( cfg.get_app_instance_file(), raise_cmd )
417+
418+ logger.openlog()
419+ main_window = MainWindow( cfg )
420+ main_window.show()
421+ logger.closelog()
422+
423+ app_instance.exit_application()
424
425=== added file 'cli/configure'
426--- cli/configure 1970-01-01 00:00:00 +0000
427+++ cli/configure 2010-03-02 06:06:17 +0000
428@@ -0,0 +1,12 @@
429+#!/bin/bash
430+
431+if [ -e Makefile ]; then
432+ rm Makefile;
433+fi
434+
435+cp Makefile.template Makefile
436+
437+echo "All OK. Now run:"
438+echo " make"
439+echo " sudo make install"
440+
441
442=== added file 'cli/copywidget.py'
443--- cli/copywidget.py 1970-01-01 00:00:00 +0000
444+++ cli/copywidget.py 2010-03-02 06:06:17 +0000
445@@ -0,0 +1,67 @@
446+# Back In Time
447+# Copyright (C) 2010 Ryan Ahearn
448+#
449+# This program is free software; you can redistribute it and/or modify
450+# it under the terms of the GNU General Public License as published by
451+# the Free Software Foundation; either version 2 of the License, or
452+# (at your option) any later version.
453+#
454+# This program is distributed in the hope that it will be useful,
455+# but WITHOUT ANY WARRANTY; without even the implied warranty of
456+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
457+# GNU General Public License for more details.
458+#
459+# You should have received a copy of the GNU General Public License along
460+# with this program; if not, write to the Free Software Foundation, Inc.,
461+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
462+
463+
464+import os.path
465+import shutil
466+import urwid
467+import gettext
468+
469+import logger
470+import restorewidget
471+
472+_=gettext.gettext
473+
474+class CopyWidget(urwid.WidgetWrap):
475+
476+ def __init__( self, main_win, config, to_copy ):
477+ self._main_win = main_win
478+ self._config = config
479+ self._to_copy = to_copy
480+
481+ self._edit_box = urwid.Edit(( 'body', _('Enter destination') + ': ' ))
482+ pile_list = [
483+ urwid.AttrWrap( self._edit_box, 'edit_box', 'edit_focus' ),
484+ urwid.Divider(),
485+ urwid.GridFlow( [
486+ urwid.AttrWrap( urwid.Button( _('OK'), self.ok_pressed ), 'button', 'button_focus' ),
487+ urwid.AttrWrap( urwid.Button( _('Cancel'), self.go_home ), 'button', 'button_focus' )
488+ ], 10, 2, 1, 'right' )
489+ ]
490+
491+ display_widget = urwid.Pile( pile_list )
492+ urwid.WidgetWrap.__init__( self, display_widget )
493+
494+ def ok_pressed( self, button ):
495+ destination = self._edit_box.get_edit_text().strip()
496+ logger.info( 'copying %s to %s' % ( self._to_copy, destination ) )
497+ if os.path.isdir( self._to_copy ):
498+ self._copy_directory( destination )
499+ else:
500+ self._copy_file( destination )
501+ self.go_home()
502+
503+ def _copy_directory( self, destination ):
504+ while os.path.exists( destination ):
505+ destination = os.path.join( destination, os.path.basename( self._to_copy ) )
506+ shutil.copytree( self._to_copy, destination, True )
507+
508+ def _copy_file( self, destination ):
509+ shutil.copy2( self._to_copy, destination )
510+
511+ def go_home( self, button=None ):
512+ self._main_win.update_content( restorewidget.RestoreWidget( self._main_win, self._config ), 5 )
513
514=== added directory 'cli/debian_specific'
515=== added file 'cli/debian_specific/control'
516--- cli/debian_specific/control 1970-01-01 00:00:00 +0000
517+++ cli/debian_specific/control 2010-03-02 06:06:17 +0000
518@@ -0,0 +1,10 @@
519+Package: backintime-cli
520+Version: 0.9.99.18
521+Priority: optional
522+Section: utils
523+Maintainer: BIT Team <bit-team@lists.launchpad.net>
524+Architecture: all
525+Homepage: http://backintime.le-web.org
526+Depends: python-urwid (>> 0.9.9), backintime-common (= 0.9.99.18)
527+Description: Simple backup system for command line use.
528+ This is a command line interface frontend for backintime-common.
529
530=== added file 'cli/debian_specific/control.source'
531--- cli/debian_specific/control.source 1970-01-01 00:00:00 +0000
532+++ cli/debian_specific/control.source 2010-03-02 06:06:17 +0000
533@@ -0,0 +1,14 @@
534+Source: backintime-cli
535+Maintainer: BIT Team <bit-team@lists.launchpad.net>
536+Section: utils
537+Priority: optional
538+Standards-Version: 3.7.3
539+Homepage: http://backintime.le-web.org
540+
541+Package: backintime-cli
542+Architecture: all
543+Section: utils
544+Priority: optional
545+Depends: python-urwid (>> 0.9.9), backintime-common (= 0.9.99.18)
546+Description: Simple backup system for command line use.
547+ This is a command line interface frontend for backintime-common.
548
549=== added file 'cli/debian_specific/postrm'
550--- cli/debian_specific/postrm 1970-01-01 00:00:00 +0000
551+++ cli/debian_specific/postrm 2010-03-02 06:06:17 +0000
552@@ -0,0 +1,3 @@
553+#!/bin/sh
554+rm -rf /usr/share/backintime/cli/*.pyc
555+exit 0
556
557=== added file 'cli/debian_specific/rules'
558--- cli/debian_specific/rules 1970-01-01 00:00:00 +0000
559+++ cli/debian_specific/rules 2010-03-02 06:06:17 +0000
560@@ -0,0 +1,50 @@
561+#!/usr/bin/make -f
562+
563+package = backintime-cli
564+
565+clean:
566+ dh_testdir
567+ dh_clean
568+ rm -f build
569+
570+install: build
571+ #install python
572+ #dh_installdirs /usr/share/backintime/gnome
573+ dh_install *.py /usr/share/backintime/cli
574+
575+ #install copyright file
576+ #dh_installdirs /usr/share/doc/backintime-gnome
577+ dh_install debian/copyright /usr/share/doc/backintime-cli
578+
579+ #install man file(s)
580+ #dh_installdirs /usr/share/man/man1
581+ dh_install man/C/*.gz /usr/share/man/man1
582+
583+ #install application
584+ #dh_installdirs /usr/bin
585+ dh_install backintime-config /usr/bin
586+ dh_install backintime-restore /usr/bin
587+
588+build:
589+ touch build
590+
591+binary-indep: install
592+ dh_testdir -i
593+ dh_testroot -i
594+ #dh_installdocs -i NEWS
595+ #dh_installchangelogs -i ChangeLog
596+ #dh_strip -i
597+ #dh_compress -i
598+ dh_fixperms -i
599+ dh_installdeb -i
600+ #dh_shlibdeps -i
601+ dh_gencontrol -i
602+ dh_md5sums -i
603+ dh_builddeb -i
604+
605+binary-arch: install
606+
607+binary: binary-indep
608+
609+.PHONY: binary binary-arch binary-indep clean
610+
611
612=== added file 'cli/excludewidget.py'
613--- cli/excludewidget.py 1970-01-01 00:00:00 +0000
614+++ cli/excludewidget.py 2010-03-02 06:06:17 +0000
615@@ -0,0 +1,118 @@
616+# Back In Time
617+# Copyright (C) 2010 Ryan Ahearn
618+#
619+# This program is free software; you can redistribute it and/or modify
620+# it under the terms of the GNU General Public License as published by
621+# the Free Software Foundation; either version 2 of the License, or
622+# (at your option) any later version.
623+#
624+# This program is distributed in the hope that it will be useful,
625+# but WITHOUT ANY WARRANTY; without even the implied warranty of
626+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
627+# GNU General Public License for more details.
628+#
629+# You should have received a copy of the GNU General Public License along
630+# with this program; if not, write to the Free Software Foundation, Inc.,
631+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
632+
633+import urwid
634+import gettext
635+
636+import logger
637+
638+_=gettext.gettext
639+
640+class ExcludeWidget(urwid.WidgetWrap):
641+
642+ def __init__( self, main_win, config ):
643+ self._main_win = main_win
644+ self._config = config
645+ self._to_delete = None
646+
647+ all_patterns = config.get_exclude_patterns()
648+ if len( all_patterns ) == 0:
649+ chooser = urwid.Divider()
650+ else:
651+ excluded_patterns = []
652+ for pattern in all_patterns:
653+ urwid.RadioButton( excluded_patterns, pattern, False, self.pattern_selected )
654+ chooser = urwid.Pile( excluded_patterns )
655+
656+ pile_list = [
657+ urwid.Text( _('Patterns, files or folders') ),
658+ urwid.Divider(),
659+ chooser,
660+ urwid.Divider(),
661+ urwid.GridFlow([
662+ urwid.AttrWrap( urwid.Button( _('Add'), self.add_pattern ), 'button', 'button_focus' ),
663+ urwid.AttrWrap( urwid.Button( _('Delete'), self.delete_pattern ), 'button', 'button_focus' ),
664+ ], 10, 2, 1, 'center' )
665+ ]
666+
667+ display_widget = urwid.Pile( pile_list )
668+ urwid.WidgetWrap.__init__( self, display_widget )
669+
670+ def pattern_selected( self, button, new_state ):
671+ if new_state:
672+ self._to_delete = button.get_label()
673+
674+ def add_pattern( self, button ):
675+ self._main_win.update_content( AddExcludeWidget( self._main_win, self._config ), 5 )
676+
677+ def delete_pattern( self, button ):
678+ if self._to_delete is not None:
679+ self._main_win.update_content( DeleteExcludeWidget( self._main_win, self._config, self._to_delete ), 5 )
680+
681+ def go_to_exclude_patterns( self, button=None ):
682+ self._main_win.update_content( ExcludeWidget( self._main_win, self._config ), 5 )
683+
684+
685+class AddExcludeWidget(ExcludeWidget):
686+
687+ def __init__( self, main_win, config ):
688+ self._main_win = main_win
689+ self._config = config
690+
691+ self._edit_box = urwid.Edit(( 'body', _('Enter pattern, file, or folder to exclude') + ': ' ))
692+ pile_list = [
693+ urwid.AttrWrap( self._edit_box, 'edit_box', 'edit_focus' ),
694+ urwid.Divider(),
695+ urwid.GridFlow( [
696+ urwid.AttrWrap( urwid.Button( _('OK'), self.ok_pressed ), 'button', 'button_focus' ),
697+ urwid.AttrWrap( urwid.Button( _('Cancel'), self.go_to_exclude_patterns ), 'button', 'button_focus' )
698+ ], 10, 2, 1, 'right' )
699+ ]
700+
701+ display_widget = urwid.Pile( pile_list )
702+ urwid.WidgetWrap.__init__( self, display_widget )
703+
704+ def ok_pressed( self, button ):
705+ all_patterns = self._config.get_exclude_patterns()
706+ all_patterns.append( self._edit_box.get_edit_text().strip() )
707+ self._config.set_exclude_patterns( all_patterns )
708+ self.go_to_exclude_patterns()
709+
710+class DeleteExcludeWidget(ExcludeWidget):
711+
712+ def __init__( self, main_win, config, to_delete ):
713+ self._main_win = main_win
714+ self._config = config
715+ self._to_delete = to_delete
716+
717+ pile_list = [
718+ urwid.Text( _('Are you sure you want to remove') + ' "' + to_delete + '" ' + _('from the excluded patterns?') ),
719+ urwid.Divider(),
720+ urwid.GridFlow( [
721+ urwid.AttrWrap( urwid.Button( _('Yes'), self.yes_pressed ), 'button', 'button_focus' ),
722+ urwid.AttrWrap( urwid.Button( _('No'), self.go_to_exclude_patterns ), 'button', 'button_focus' )
723+ ], 10, 2, 1, 'right' )
724+ ]
725+
726+ display_widget = urwid.Pile( pile_list )
727+ urwid.WidgetWrap.__init__( self, display_widget )
728+
729+ def yes_pressed( self, button ):
730+ all_patterns = self._config.get_exclude_patterns()
731+ all_patterns.remove( self._to_delete )
732+ self._config.set_exclude_patterns( all_patterns )
733+ self.go_to_exclude_patterns()
734
735=== added file 'cli/expertwidget.py'
736--- cli/expertwidget.py 1970-01-01 00:00:00 +0000
737+++ cli/expertwidget.py 2010-03-02 06:06:17 +0000
738@@ -0,0 +1,47 @@
739+# Back In Time
740+# Copyright (C) 2010 Ryan Ahearn
741+#
742+# This program is free software; you can redistribute it and/or modify
743+# it under the terms of the GNU General Public License as published by
744+# the Free Software Foundation; either version 2 of the License, or
745+# (at your option) any later version.
746+#
747+# This program is distributed in the hope that it will be useful,
748+# but WITHOUT ANY WARRANTY; without even the implied warranty of
749+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
750+# GNU General Public License for more details.
751+#
752+# You should have received a copy of the GNU General Public License along
753+# with this program; if not, write to the Free Software Foundation, Inc.,
754+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
755+
756+import urwid
757+import gettext
758+
759+import logger
760+
761+_=gettext.gettext
762+
763+class ExpertOptionsWidget(urwid.WidgetWrap):
764+
765+ def __init__( self, config ):
766+ self._config = config
767+
768+ pile_list = [
769+ urwid.Padding( urwid.Text(( 'error', _('Change these options only if you really know what you are doing!') )), 'center' ),
770+ urwid.Divider(),
771+ urwid.CheckBox( _("Run 'nice' as cron job (default: enabled)"), config.is_run_nice_from_cron_enabled(), on_state_change=self.flip_nice_cron ),
772+ urwid.CheckBox( _("Run 'ionice' as cron job (default: enabled)"), config.is_run_ionice_from_cron_enabled(), on_state_change=self.flip_ionice_cron ),
773+ urwid.CheckBox( _("Run 'ionice' when taking a manual snapshot (default: disabled)"), config.is_run_ionice_from_user_enabled(), on_state_change=self.flip_ionice_user )
774+ ]
775+ display_widget = urwid.Pile( pile_list )
776+ urwid.WidgetWrap.__init__( self, display_widget )
777+
778+ def flip_nice_cron( self, check_box, new_state ):
779+ self._config.set_run_nice_from_cron_enabled( new_state )
780+
781+ def flip_ionice_cron( self, check_box, new_state ):
782+ self._config.set_run_ionice_from_cron_enabled( new_state )
783+
784+ def flip_ionice_user( self, check_box, new_state ):
785+ self._config.set_run_ionice_from_user_enabled( new_state )
786
787=== added file 'cli/generalwidget.py'
788--- cli/generalwidget.py 1970-01-01 00:00:00 +0000
789+++ cli/generalwidget.py 2010-03-02 06:06:17 +0000
790@@ -0,0 +1,93 @@
791+# Back In Time
792+# Copyright (C) 2010 Ryan Ahearn
793+#
794+# This program is free software; you can redistribute it and/or modify
795+# it under the terms of the GNU General Public License as published by
796+# the Free Software Foundation; either version 2 of the License, or
797+# (at your option) any later version.
798+#
799+# This program is distributed in the hope that it will be useful,
800+# but WITHOUT ANY WARRANTY; without even the implied warranty of
801+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
802+# GNU General Public License for more details.
803+#
804+# You should have received a copy of the GNU General Public License along
805+# with this program; if not, write to the Free Software Foundation, Inc.,
806+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
807+
808+import urwid
809+import gettext
810+
811+import logger
812+
813+_=gettext.gettext
814+
815+class GeneralWidget(urwid.WidgetWrap):
816+
817+ def __init__( self, main_win, config ):
818+ self._config = config
819+ self._main_win = main_win
820+
821+ backup_mode = config.get_automatic_backup_mode()
822+ backup_modes = []
823+ for key, value in sorted( config.AUTOMATIC_BACKUP_MODES.items() ):
824+ if backup_mode == key:
825+ urwid.RadioButton( backup_modes, value, True, self.schedule_changed, key )
826+ else:
827+ urwid.RadioButton( backup_modes, value, False, self.schedule_changed, key )
828+ schedule_chooser = urwid.Pile( backup_modes )
829+
830+ location = urwid.Edit(( 'body', _('Where to save snapshots') + ': ' ), config.get_snapshots_path() )
831+ urwid.connect_signal( location, 'change', self.location_changed )
832+ self._error = urwid.Text( '' )
833+ pile_list = [
834+ urwid.AttrWrap( location, 'edit_box', 'edit_focus' ),
835+ urwid.AttrWrap( self._error, 'error' ),
836+ urwid.Divider()
837+ ]
838+
839+ if backup_mode > config.HOUR:
840+ # display chooser to set hour of the day to run the cron job
841+ set_hour = config.get_automatic_backup_time() / 100
842+ hour_choices = []
843+ for i in range( 24 ):
844+ if set_hour == i:
845+ urwid.RadioButton( hour_choices, ( "%2d:00" % i ), True, self.time_changed, i )
846+ else:
847+ urwid.RadioButton( hour_choices, ( "%2d:00" % i ), False, self.time_changed, i )
848+ hour_chooser = urwid.Pile( hour_choices )
849+ pile_list.append(
850+ urwid.Columns( [
851+ urwid.Pile( [
852+ urwid.Text( _('Schedule') ),
853+ schedule_chooser
854+ ] ),
855+ urwid.Pile( [
856+ urwid.Text( _('Hour') ),
857+ hour_chooser
858+ ] )
859+ ], 1 )
860+ )
861+ else:
862+ pile_list.extend( [
863+ urwid.Text( _('Schedule') ),
864+ schedule_chooser
865+ ] )
866+
867+ display_widget = urwid.Pile( pile_list )
868+ urwid.WidgetWrap.__init__( self, display_widget )
869+
870+ def schedule_changed( self, check_box, new_state, user_data ):
871+ if new_state:
872+ self._config.set_automatic_backup_mode( user_data )
873+ self._main_win.update_content( GeneralWidget( self._main_win, self._config ), 5 )
874+
875+ def time_changed( self, check_box, new_state, user_data ):
876+ if new_state:
877+ self._config.set_automatic_backup_time( user_data * 100 )
878+
879+ def location_changed( self, edit, new_edit_text ):
880+ if not self._config.set_snapshots_path( new_edit_text.strip() ):
881+ self._error.set_text( _('Could not use this path, are you sure it exists and you have write access?') )
882+ else:
883+ self._error.set_text( '' )
884
885=== added file 'cli/includewidget.py'
886--- cli/includewidget.py 1970-01-01 00:00:00 +0000
887+++ cli/includewidget.py 2010-03-02 06:06:17 +0000
888@@ -0,0 +1,118 @@
889+# Back In Time
890+# Copyright (C) 2010 Ryan Ahearn
891+#
892+# This program is free software; you can redistribute it and/or modify
893+# it under the terms of the GNU General Public License as published by
894+# the Free Software Foundation; either version 2 of the License, or
895+# (at your option) any later version.
896+#
897+# This program is distributed in the hope that it will be useful,
898+# but WITHOUT ANY WARRANTY; without even the implied warranty of
899+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
900+# GNU General Public License for more details.
901+#
902+# You should have received a copy of the GNU General Public License along
903+# with this program; if not, write to the Free Software Foundation, Inc.,
904+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
905+
906+import urwid
907+import gettext
908+
909+import logger
910+
911+_=gettext.gettext
912+
913+class IncludeWidget(urwid.WidgetWrap):
914+
915+ def __init__( self, main_win, config ):
916+ self._main_win = main_win
917+ self._config = config
918+ self._to_delete = None
919+
920+ all_folders = config.get_include_folders()
921+ if len( all_folders ) == 0:
922+ chooser = urwid.Divider()
923+ else:
924+ included_folders = []
925+ for folder in all_folders:
926+ urwid.RadioButton( included_folders, folder, False, self.folder_selected )
927+ chooser = urwid.Pile( included_folders )
928+
929+ pile_list = [
930+ urwid.Text( _('Include Folders') ),
931+ urwid.Divider(),
932+ chooser,
933+ urwid.Divider(),
934+ urwid.GridFlow( [
935+ urwid.AttrWrap( urwid.Button( _('Add'), self.add_folder ), 'button', 'button_focus' ),
936+ urwid.AttrWrap( urwid.Button( _('Delete'), self.delete_folder ), 'button', 'button_focus' ),
937+ ], 10, 2, 1, 'center' )
938+ ]
939+
940+ display_widget = urwid.Pile( pile_list )
941+ urwid.WidgetWrap.__init__( self, display_widget )
942+
943+ def folder_selected( self, button, new_state ):
944+ if new_state:
945+ self._to_delete = button.get_label()
946+
947+ def add_folder( self, button ):
948+ self._main_win.update_content( AddIncludeWidget( self._main_win, self._config ), 5 )
949+
950+ def delete_folder( self, button ):
951+ if self._to_delete is not None:
952+ self._main_win.update_content( DeleteIncludeWidget( self._main_win, self._config, self._to_delete ), 5 )
953+
954+ def go_to_include_folders( self, button=None ):
955+ self._main_win.update_content( IncludeWidget( self._main_win, self._config ), 5 )
956+
957+
958+class AddIncludeWidget(IncludeWidget):
959+
960+ def __init__( self, main_win, config ):
961+ self._main_win = main_win
962+ self._config = config
963+
964+ self._edit_box = urwid.Edit(( 'body', _('Enter path for folder to backup') + ': ' ))
965+ pile_list = [
966+ urwid.AttrWrap( self._edit_box, 'edit_box', 'edit_focus' ),
967+ urwid.Divider(),
968+ urwid.GridFlow( [
969+ urwid.AttrWrap( urwid.Button( _('OK'), self.ok_pressed ), 'button', 'button_focus' ),
970+ urwid.AttrWrap( urwid.Button( _('Cancel'), self.go_to_include_folders ), 'button', 'button_focus' )
971+ ], 10, 2, 1, 'right' )
972+ ]
973+
974+ display_widget = urwid.Pile( pile_list )
975+ urwid.WidgetWrap.__init__( self, display_widget )
976+
977+ def ok_pressed( self, button ):
978+ all_folders = self._config.get_include_folders()
979+ all_folders.append( self._edit_box.get_edit_text().strip() )
980+ self._config.set_include_folders( all_folders )
981+ self.go_to_include_folders()
982+
983+class DeleteIncludeWidget(IncludeWidget):
984+
985+ def __init__( self, main_win, config, to_delete ):
986+ self._main_win = main_win
987+ self._config = config
988+ self._to_delete = to_delete
989+
990+ pile_list = [
991+ urwid.Text( _('Are you sure you want to remove') + ' "' + to_delete + '" ' + _('from the backed up folders?') ),
992+ urwid.Divider(),
993+ urwid.GridFlow( [
994+ urwid.AttrWrap( urwid.Button( _('Yes'), self.yes_pressed ), 'button', 'button_focus' ),
995+ urwid.AttrWrap( urwid.Button( _('No'), self.go_to_include_folders ), 'button', 'button_focus' )
996+ ], 10, 2, 1, 'right' )
997+ ]
998+
999+ display_widget = urwid.Pile( pile_list )
1000+ urwid.WidgetWrap.__init__( self, display_widget )
1001+
1002+ def yes_pressed( self, button ):
1003+ all_folders = self._config.get_include_folders()
1004+ all_folders.remove( self._to_delete )
1005+ self._config.set_include_folders( all_folders )
1006+ self.go_to_include_folders()
1007
1008=== added directory 'cli/man'
1009=== added directory 'cli/man/C'
1010=== added file 'cli/man/C/backintime-config.1.gz'
1011Binary files cli/man/C/backintime-config.1.gz 1970-01-01 00:00:00 +0000 and cli/man/C/backintime-config.1.gz 2010-03-02 06:06:18 +0000 differ
1012=== added file 'cli/man/C/backintime-restore.1.gz'
1013Binary files cli/man/C/backintime-restore.1.gz 1970-01-01 00:00:00 +0000 and cli/man/C/backintime-restore.1.gz 2010-03-02 06:06:18 +0000 differ
1014=== added file 'cli/optionswidget.py'
1015--- cli/optionswidget.py 1970-01-01 00:00:00 +0000
1016+++ cli/optionswidget.py 2010-03-02 06:06:17 +0000
1017@@ -0,0 +1,45 @@
1018+# Back In Time
1019+# Copyright (C) 2010 Ryan Ahearn
1020+#
1021+# This program is free software; you can redistribute it and/or modify
1022+# it under the terms of the GNU General Public License as published by
1023+# the Free Software Foundation; either version 2 of the License, or
1024+# (at your option) any later version.
1025+#
1026+# This program is distributed in the hope that it will be useful,
1027+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1028+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1029+# GNU General Public License for more details.
1030+#
1031+# You should have received a copy of the GNU General Public License along
1032+# with this program; if not, write to the Free Software Foundation, Inc.,
1033+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1034+
1035+import urwid
1036+import gettext
1037+
1038+import logger
1039+
1040+_=gettext.gettext
1041+
1042+class OptionsWidget(urwid.WidgetWrap):
1043+
1044+ def __init__( self, config ):
1045+ self._config = config
1046+
1047+ pile_list = [
1048+ urwid.CheckBox( _('Enable notifications'), config.is_notify_enabled(), on_state_change=self.flip_notify ),
1049+ urwid.CheckBox( _('Disable snapshots when on battery'), config.is_no_on_battery_enabled(), on_state_change=self.flip_battery ),
1050+ urwid.CheckBox( _('Backup files on restore'), config.is_backup_on_restore_enabled(), on_state_change=self.flip_backup_on_restore )
1051+ ]
1052+ display_widget = urwid.Pile( pile_list )
1053+ urwid.WidgetWrap.__init__( self, display_widget )
1054+
1055+ def flip_notify( self, check_box, new_state ):
1056+ self._config.set_notify_enabled( new_state )
1057+
1058+ def flip_battery( self, check_box, new_state ):
1059+ self._config.set_no_on_battery_enabled( new_state )
1060+
1061+ def flip_backup_on_restore( self, check_box, new_state ):
1062+ self._config.set_backup_on_restore( new_state )
1063
1064=== added file 'cli/profilewidget.py'
1065--- cli/profilewidget.py 1970-01-01 00:00:00 +0000
1066+++ cli/profilewidget.py 2010-03-02 06:06:17 +0000
1067@@ -0,0 +1,162 @@
1068+# Back In Time
1069+# Copyright (C) 2010 Ryan Ahearn
1070+#
1071+# This program is free software; you can redistribute it and/or modify
1072+# it under the terms of the GNU General Public License as published by
1073+# the Free Software Foundation; either version 2 of the License, or
1074+# (at your option) any later version.
1075+#
1076+# This program is distributed in the hope that it will be useful,
1077+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1078+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1079+# GNU General Public License for more details.
1080+#
1081+# You should have received a copy of the GNU General Public License along
1082+# with this program; if not, write to the Free Software Foundation, Inc.,
1083+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1084+
1085+import urwid
1086+import gettext
1087+
1088+import logger
1089+
1090+_=gettext.gettext
1091+
1092+class ProfileWidget(urwid.WidgetWrap):
1093+
1094+ def __init__( self, main_win, config, display_buttons=True ):
1095+ self._main_win = main_win
1096+ self._config = config
1097+
1098+ current_profile = config.get_current_profile()
1099+ profile_ids = config.get_profiles()
1100+ profiles = []
1101+ for profile_id in profile_ids:
1102+ profile_name = config.get_profile_name( profile_id )
1103+ if profile_id == current_profile:
1104+ urwid.RadioButton( profiles, profile_name, True, self.profile_changed, profile_id )
1105+ else:
1106+ urwid.RadioButton( profiles, profile_name, False, self.profile_changed, profile_id )
1107+ chooser = urwid.Pile( profiles )
1108+
1109+ pile_list = [
1110+ urwid.Text( _('Choose a profile') ),
1111+ urwid.Divider(),
1112+ chooser,
1113+ urwid.Divider()
1114+ ]
1115+
1116+ if display_buttons:
1117+ buttons = [
1118+ urwid.Button( _('Edit'), self.edit_profile ),
1119+ urwid.Button( _('New'), self.add_profile )
1120+ ]
1121+ if len( profile_ids ) > 1:
1122+ buttons.append( urwid.Button( _('Delete'), self.delete_profile ) )
1123+ pile_list.append( urwid.GridFlow( [ urwid.AttrWrap( button, 'button', 'button_focus' ) for button in buttons ], 10, 2, 1, 'center' ) )
1124+
1125+ display_widget = urwid.Pile( pile_list )
1126+ urwid.WidgetWrap.__init__( self, display_widget )
1127+
1128+ def profile_changed( self, button, new_state, user_data ):
1129+ if new_state:
1130+ self._config.set_current_profile( user_data )
1131+ self._main_win.update_profile_name()
1132+
1133+ def add_profile( self, button ):
1134+ self._main_win.update_content( AddProfileWidget( self._main_win, self._config ), 5 )
1135+
1136+ def edit_profile( self, button ):
1137+ self._main_win.update_content( EditProfileWidget( self._main_win, self._config ), 5 )
1138+
1139+ def delete_profile( self, button ):
1140+ self._main_win.update_content( DeleteProfileWidget( self._main_win, self._config ), 5 )
1141+
1142+ def go_to_profile_list( self, button=None ):
1143+ self._main_win.update_profile_name()
1144+ self._main_win.update_content( ProfileWidget( self._main_win, self._config ), 5 )
1145+
1146+
1147+class EditProfileWidget(ProfileWidget):
1148+
1149+ def __init__( self, main_win, config ):
1150+ self._main_win = main_win
1151+ self._config = config
1152+
1153+ self._edit_box = urwid.Edit(( 'body', _('Enter new name for profile') + ' ' + config.get_profile_name() + ': ' ))
1154+ self._error = urwid.Text( '' )
1155+ pile_list = [
1156+ urwid.AttrWrap( self._edit_box, 'edit_box', 'edit_focus' ),
1157+ urwid.AttrWrap( self._error, 'error' ),
1158+ urwid.Divider(),
1159+ urwid.GridFlow( [
1160+ urwid.AttrWrap( urwid.Button( _('OK'), self.ok_pressed ), 'button', 'button_focus' ),
1161+ urwid.AttrWrap( urwid.Button( _('Cancel'), self.go_to_profile_list ), 'button', 'button_focus' )
1162+ ], 10, 2, 1, 'right' )
1163+ ]
1164+
1165+ display_widget = urwid.Pile( pile_list )
1166+ urwid.WidgetWrap.__init__( self, display_widget )
1167+
1168+ def ok_pressed( self, button ):
1169+ name = self._edit_box.get_edit_text().strip()
1170+ if name == '':
1171+ self._error.set_text( _('Profile must be given a name') )
1172+ elif self._config.set_profile_name( name ):
1173+ self.go_to_profile_list()
1174+ else:
1175+ self._error.set_text( _('Profile name already exists, choose another') )
1176+
1177+
1178+class AddProfileWidget(ProfileWidget):
1179+
1180+ def __init__( self, main_win, config ):
1181+ self._main_win = main_win
1182+ self._config = config
1183+
1184+ self._edit_box = urwid.Edit(( 'body', _('Enter name for new profile') + ': ' ))
1185+ self._error = urwid.Text( '' )
1186+ pile_list = [
1187+ urwid.AttrWrap( self._edit_box, 'edit_box', 'edit_focus' ),
1188+ urwid.AttrWrap( self._error, 'error' ),
1189+ urwid.Divider(),
1190+ urwid.GridFlow([
1191+ urwid.AttrWrap( urwid.Button( _('OK'), self.ok_pressed ), 'button', 'button_focus' ),
1192+ urwid.AttrWrap( urwid.Button( _('Cancel'), self.go_to_profile_list ), 'button', 'button_focus' )
1193+ ], 10, 2, 1, 'right' )
1194+ ]
1195+
1196+ display_widget = urwid.Pile( pile_list )
1197+ urwid.WidgetWrap.__init__( self, display_widget )
1198+
1199+ def ok_pressed( self, button ):
1200+ name = self._edit_box.get_edit_text().strip()
1201+ if name == '':
1202+ self._error.set_text( _('Profile must be given a name') )
1203+ elif self._config.add_profile( name ):
1204+ self.go_to_profile_list()
1205+ else:
1206+ self._error.set_text( _('Profile name already exists, choose another') )
1207+
1208+
1209+class DeleteProfileWidget(ProfileWidget):
1210+
1211+ def __init__( self, main_win, config ):
1212+ self._main_win = main_win
1213+ self._config = config
1214+
1215+ pile_list = [
1216+ urwid.Text( _('Are You Sure you want to delete profile') + ' "' + config.get_profile_name() + '"' ),
1217+ urwid.Divider(),
1218+ urwid.GridFlow([
1219+ urwid.AttrWrap( urwid.Button( _('Yes'), self.yes_pressed ), 'button', 'button_focus' ),
1220+ urwid.AttrWrap( urwid.Button( _('No'), self.go_to_profile_list ), 'button', 'button_focus' )
1221+ ], 10, 2, 1, 'right' )
1222+ ]
1223+
1224+ display_widget = urwid.Pile( pile_list )
1225+ urwid.WidgetWrap.__init__( self, display_widget )
1226+
1227+ def yes_pressed( self, button ):
1228+ self._config.remove_profile()
1229+ self.go_to_profile_list()
1230
1231=== added file 'cli/removesnapshot.py'
1232--- cli/removesnapshot.py 1970-01-01 00:00:00 +0000
1233+++ cli/removesnapshot.py 2010-03-02 06:06:17 +0000
1234@@ -0,0 +1,51 @@
1235+# Back In Time
1236+# Copyright (C) 2010 Ryan Ahearn
1237+#
1238+# This program is free software; you can redistribute it and/or modify
1239+# it under the terms of the GNU General Public License as published by
1240+# the Free Software Foundation; either version 2 of the License, or
1241+# (at your option) any later version.
1242+#
1243+# This program is distributed in the hope that it will be useful,
1244+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1245+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1246+# GNU General Public License for more details.
1247+#
1248+# You should have received a copy of the GNU General Public License along
1249+# with this program; if not, write to the Free Software Foundation, Inc.,
1250+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1251+
1252+import urwid
1253+import gettext
1254+
1255+import logger
1256+import restorewidget
1257+import snapshots
1258+
1259+_=gettext.gettext
1260+
1261+class RemoveSnapshot(urwid.WidgetWrap):
1262+
1263+ def __init__( self, main_win, config ):
1264+ self._main_win = main_win
1265+ self._config = config
1266+ self._snapshots = snapshots.Snapshots( config )
1267+
1268+ pile_list = [
1269+ urwid.Text( _('Are you sure you want to remove snapshot') + ' "' + main_win.selected_snapshot_name + '"' ),
1270+ urwid.Divider(),
1271+ urwid.GridFlow( [
1272+ urwid.AttrWrap( urwid.Button( _('Yes'), self.yes_pressed ), 'button', 'button_focus' ),
1273+ urwid.AttrWrap( urwid.Button( _('No'), self.go_home ), 'button', 'button_focus' )
1274+ ], 10, 2, 1, 'right' )
1275+ ]
1276+
1277+ display_widget = urwid.Pile( pile_list )
1278+ urwid.WidgetWrap.__init__( self, display_widget )
1279+
1280+ def yes_pressed( self, button ):
1281+ self._snapshots.remove_snapshot( self._main_win.selected_snapshot )
1282+ self.go_home()
1283+
1284+ def go_home( self, button=None ):
1285+ self._main_win.update_content( restorewidget.RestoreWidget( self._main_win, self._config ), 5 )
1286
1287=== added file 'cli/removewidget.py'
1288--- cli/removewidget.py 1970-01-01 00:00:00 +0000
1289+++ cli/removewidget.py 2010-03-02 06:06:17 +0000
1290@@ -0,0 +1,114 @@
1291+# Back In Time
1292+# Copyright (C) 2010 Ryan Ahearn
1293+#
1294+# This program is free software; you can redistribute it and/or modify
1295+# it under the terms of the GNU General Public License as published by
1296+# the Free Software Foundation; either version 2 of the License, or
1297+# (at your option) any later version.
1298+#
1299+# This program is distributed in the hope that it will be useful,
1300+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1301+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1302+# GNU General Public License for more details.
1303+#
1304+# You should have received a copy of the GNU General Public License along
1305+# with this program; if not, write to the Free Software Foundation, Inc.,
1306+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1307+
1308+import urwid
1309+import gettext
1310+
1311+import logger
1312+
1313+_=gettext.gettext
1314+
1315+class RemoveWidget(urwid.WidgetWrap):
1316+
1317+ def __init__( self, config ):
1318+ self._config = config
1319+
1320+ old_enabled, self._remove_old_value, self._remove_old_unit = config.get_remove_old_snapshots()
1321+ self._remove_old_enabled = urwid.CheckBox( _('Older than:'), old_enabled, on_state_change=self.flip_old_enabled )
1322+ remove_old_value_box = urwid.IntEdit( '', self._remove_old_value )
1323+ urwid.connect_signal( remove_old_value_box, 'change', self.remove_old_value_changed )
1324+ remove_units = []
1325+ for key, value in sorted( config.REMOVE_OLD_BACKUP_UNITS.items() ):
1326+ if self._remove_old_unit == key:
1327+ urwid.RadioButton( remove_units, value, True, self.remove_old_unit_changed, key )
1328+ else:
1329+ urwid.RadioButton( remove_units, value, False, self.remove_old_unit_changed, key )
1330+ remove_unit_chooser = urwid.Pile( remove_units )
1331+ age_columns = urwid.Columns( [
1332+ self._remove_old_enabled,
1333+ urwid.AttrWrap( remove_old_value_box, 'edit_box', 'edit_focus' ),
1334+ remove_unit_chooser
1335+ ], 1 )
1336+
1337+ space_enabled, self._free_space_value, self._free_space_unit = config.get_min_free_space()
1338+ self._free_space_enabled = urwid.CheckBox( _('If free space is less than:'), space_enabled, on_state_change=self.flip_free_space_enabled )
1339+ free_space_value_box = urwid.IntEdit( '', self._free_space_value )
1340+ urwid.connect_signal( free_space_value_box, 'change', self.free_space_value_changed )
1341+ free_space_units = []
1342+ for key, value in sorted( config.MIN_FREE_SPACE_UNITS.items() ):
1343+ if self._free_space_unit == key:
1344+ urwid.RadioButton( free_space_units, value, True, self.free_space_unit_changed, key )
1345+ else:
1346+ urwid.RadioButton( free_space_units, value, False, self.free_space_unit_changed, key )
1347+ space_unit_chooser = urwid.Pile( free_space_units )
1348+ space_columns = urwid.Columns( [
1349+ self._free_space_enabled,
1350+ urwid.AttrWrap( free_space_value_box, 'edit_box', 'edit_focus' ),
1351+ space_unit_chooser
1352+ ], 1 )
1353+
1354+ pile_list = [
1355+ age_columns,
1356+ urwid.Divider(),
1357+ space_columns,
1358+ urwid.Divider(),
1359+ urwid.CheckBox( _('Smart remove'), config.get_smart_remove(), on_state_change=self.flip_smart_remove ),
1360+ urwid.Padding( urwid.Text( _('- keep all snapshots from today and yesterday') ), left=4 ),
1361+ urwid.Padding( urwid.Text( _('- keep one snapshot for the last week and one for two weeks ago') ), left=4 ),
1362+ urwid.Padding( urwid.Text( _('- keep one snapshot per month for all previous months of this year and all months of the last year') ), left=4 ),
1363+ urwid.Padding( urwid.Text( _('- keep one snapshot per year for all other years') ), left=4 ),
1364+ urwid.Divider(),
1365+ urwid.CheckBox( _("Don't remove named snapshots"), config.get_dont_remove_named_snapshots(), on_state_change=self.flip_remove_named )
1366+ ]
1367+ display_widget = urwid.Pile( pile_list )
1368+ urwid.WidgetWrap.__init__( self, display_widget )
1369+
1370+ def flip_remove_named( self, check_box, new_state ):
1371+ self._config.set_dont_remove_named_snapshots( new_state )
1372+
1373+ def flip_smart_remove( self, check_box, new_state ):
1374+ self._config.set_smart_remove( new_state )
1375+
1376+ def flip_free_space_enabled( self, check_box, new_state ):
1377+ self._config.set_min_free_space( new_state, self._free_space_value, self._free_space_unit )
1378+
1379+ def free_space_value_changed( self, edit, new_edit_text ):
1380+ if new_edit_text == '':
1381+ self._free_space_value = 0
1382+ else:
1383+ self._free_space_value = new_edit_text.strip()
1384+ self._config.set_min_free_space( self._free_space_enabled.get_state(), self._free_space_value, self._free_space_unit )
1385+
1386+ def free_space_unit_changed( self, check_box, new_state, user_data ):
1387+ if new_state:
1388+ self._free_space_unit = user_data
1389+ self._config.set_min_free_space( self._free_space_enabled.get_state(), self._free_space_value, self._free_space_unit )
1390+
1391+ def flip_old_enabled( self, check_box, new_state ):
1392+ self._config.set_remove_old_snapshots( new_state, self._remove_old_value, self._remove_old_unit )
1393+
1394+ def remove_old_value_changed( self, edit, new_edit_text ):
1395+ if new_edit_text == '':
1396+ self._remove_old_value = 0
1397+ else:
1398+ self._remove_old_value = new_edit_text.strip()
1399+ self._config.set_remove_old_snapshots( self._remove_old_enabled.get_state(), self._remove_old_value, self._remove_old_unit )
1400+
1401+ def remove_old_unit_changed( self, check_box, new_state, user_data ):
1402+ if new_state:
1403+ self._remove_old_unit = user_data
1404+ self._config.set_remove_old_snapshots( self._remove_old_enabled.get_state(), self._remove_old_value, self._remove_old_unit )
1405
1406=== added file 'cli/restorewidget.py'
1407--- cli/restorewidget.py 1970-01-01 00:00:00 +0000
1408+++ cli/restorewidget.py 2010-03-02 06:06:17 +0000
1409@@ -0,0 +1,107 @@
1410+# Back In Time
1411+# Copyright (C) 2010 Ryan Ahearn
1412+#
1413+# This program is free software; you can redistribute it and/or modify
1414+# it under the terms of the GNU General Public License as published by
1415+# the Free Software Foundation; either version 2 of the License, or
1416+# (at your option) any later version.
1417+#
1418+# This program is distributed in the hope that it will be useful,
1419+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1420+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1421+# GNU General Public License for more details.
1422+#
1423+# You should have received a copy of the GNU General Public License along
1424+# with this program; if not, write to the Free Software Foundation, Inc.,
1425+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1426+
1427+
1428+import urwid
1429+import datetime
1430+import gettext
1431+
1432+import logger
1433+import snapshots
1434+import viewerwidget
1435+
1436+_=gettext.gettext
1437+
1438+class RestoreWidget(urwid.WidgetWrap):
1439+
1440+ def __init__( self, main_win, config ):
1441+ self._main_win = main_win
1442+ self._config = config
1443+
1444+ self._snapshots = snapshots.Snapshots( config )
1445+ snapshots_pile = [
1446+ urwid.Text( _('Snapshots') ),
1447+ urwid.Divider(),
1448+ ]
1449+ snapshots_pile.extend( self.build_snapshots_pile() )
1450+
1451+ display_widget = urwid.Columns( [
1452+ urwid.Pile( snapshots_pile ),
1453+ ( 'weight', 3, viewerwidget.ViewerWidget( main_win, config, self._snapshots ) )
1454+ ], 1 )
1455+ urwid.WidgetWrap.__init__( self, display_widget )
1456+
1457+ def build_snapshots_pile( self ):
1458+ snapshots_list = self._snapshots.get_snapshots_list()
1459+ groups = []
1460+ now = datetime.date.today()
1461+
1462+ #today
1463+ date = now
1464+ groups.append( ( _('Today'), self._snapshots.get_snapshot_id( date ), [] ) )
1465+ #yesterday
1466+ date = now - datetime.timedelta( days = 1 )
1467+ groups.append( ( _('Yesterday'), self._snapshots.get_snapshot_id( date ), [] ) )
1468+ #this week
1469+ date = now - datetime.timedelta( days = now.weekday() )
1470+ groups.append( ( _('This week'), self._snapshots.get_snapshot_id( date ), [] ) )
1471+ #last week
1472+ date = now - datetime.timedelta( days = now.weekday() + 7 )
1473+ groups.append( ( _('Last week'), self._snapshots.get_snapshot_id( date ), [] ) )
1474+ #any other groups
1475+ for snapshot_id in snapshots_list:
1476+ found = False
1477+ for group in groups:
1478+ if snapshot_id >= group[1]:
1479+ group[2].append( snapshot_id )
1480+ found = True
1481+ break
1482+
1483+ if not found:
1484+ year = int( snapshot_id[0:4] )
1485+ month = int( snapshot_id[4:6] )
1486+ date = datetime.date( year, month, 1 )
1487+
1488+ group_name = ''
1489+ if year == now.year:
1490+ group_name = date.strftime( '%B' ).capitalize()
1491+ else:
1492+ group_name = date.strftime( '%B, %Y' ).capitalize()
1493+
1494+ groups.append( ( group_name, self._snapshots.get_snapshot_id( date ), [ snapshot_id ] ) )
1495+
1496+ snapshots_buttons = []
1497+ pile_list = [
1498+ urwid.RadioButton( snapshots_buttons, _('Now'), self._main_win.is_selected_snapshot( '' ), self.change_snapshot, '' )
1499+ ]
1500+ for group in groups:
1501+ if len( group[2] ) > 0:
1502+ pile_list.append( urwid.Text( group[0] ) )
1503+ for snapshot_id in group[2]:
1504+ display_name = self._snapshots.get_snapshot_display_id( snapshot_id )
1505+ user_name = self._snapshots.get_snapshot_name( snapshot_id )
1506+ if len( user_name ) > 0:
1507+ display_name += ' - ' + user_name
1508+ pile_list.append( urwid.RadioButton( snapshots_buttons, display_name, self._main_win.is_selected_snapshot( snapshot_id ), self.change_snapshot, snapshot_id ) )
1509+
1510+ return pile_list
1511+
1512+ def change_snapshot( self, check_box, new_state, user_data ):
1513+ if new_state:
1514+ self._main_win.selected_snapshot = user_data
1515+ self._main_win.selected_snapshot_name = check_box.get_label()
1516+ self._main_win.update_content( RestoreWidget( self._main_win, self._config ), 5 )
1517
1518=== added file 'cli/snapshotname.py'
1519--- cli/snapshotname.py 1970-01-01 00:00:00 +0000
1520+++ cli/snapshotname.py 2010-03-02 06:06:17 +0000
1521@@ -0,0 +1,52 @@
1522+# Back In Time
1523+# Copyright (C) 2010 Ryan Ahearn
1524+#
1525+# This program is free software; you can redistribute it and/or modify
1526+# it under the terms of the GNU General Public License as published by
1527+# the Free Software Foundation; either version 2 of the License, or
1528+# (at your option) any later version.
1529+#
1530+# This program is distributed in the hope that it will be useful,
1531+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1532+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1533+# GNU General Public License for more details.
1534+#
1535+# You should have received a copy of the GNU General Public License along
1536+# with this program; if not, write to the Free Software Foundation, Inc.,
1537+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1538+
1539+import urwid
1540+import gettext
1541+
1542+import logger
1543+import restorewidget
1544+import snapshots
1545+
1546+_=gettext.gettext
1547+
1548+class SnapshotName(urwid.WidgetWrap):
1549+
1550+ def __init__( self, main_win, config ):
1551+ self._main_win = main_win
1552+ self._config = config
1553+ self._snapshots = snapshots.Snapshots( config )
1554+
1555+ self._edit_box = urwid.Edit(( 'body', _('Snapshot Name') + ': ' ), self._snapshots.get_snapshot_name( main_win.selected_snapshot ) )
1556+ pile_list = [
1557+ urwid.AttrWrap( self._edit_box, 'edit_box', 'edit_focus' ),
1558+ urwid.Divider(),
1559+ urwid.GridFlow( [
1560+ urwid.AttrWrap( urwid.Button( _('OK'), self.ok_pressed ), 'button', 'button_focus' ),
1561+ urwid.AttrWrap( urwid.Button( _('Cancel'), self.cancel_pressed ), 'button', 'button_focus' )
1562+ ], 10, 2, 1, 'right' )
1563+ ]
1564+
1565+ display_widget = urwid.Pile( pile_list )
1566+ urwid.WidgetWrap.__init__( self, display_widget )
1567+
1568+ def ok_pressed( self, button ):
1569+ self._snapshots.set_snapshot_name( self._main_win.selected_snapshot, self._edit_box.get_edit_text().strip() )
1570+ self._main_win.update_content( restorewidget.RestoreWidget( self._main_win, self._config ), 5 )
1571+
1572+ def cancel_pressed( self, button ):
1573+ self._main_win.update_content( restorewidget.RestoreWidget( self._main_win, self._config ), 5 )
1574
1575=== added file 'cli/snapshotswidget.py'
1576--- cli/snapshotswidget.py 1970-01-01 00:00:00 +0000
1577+++ cli/snapshotswidget.py 2010-03-02 06:06:17 +0000
1578@@ -0,0 +1,158 @@
1579+# Back In Time
1580+# Copyright (C) 2010 Ryan Ahearn
1581+#
1582+# This program is free software; you can redistribute it and/or modify
1583+# it under the terms of the GNU General Public License as published by
1584+# the Free Software Foundation; either version 2 of the License, or
1585+# (at your option) any later version.
1586+#
1587+# This program is distributed in the hope that it will be useful,
1588+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1589+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1590+# GNU General Public License for more details.
1591+#
1592+# You should have received a copy of the GNU General Public License along
1593+# with this program; if not, write to the Free Software Foundation, Inc.,
1594+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1595+
1596+
1597+import os.path
1598+import subprocess
1599+import tempfile
1600+import urwid
1601+import gettext
1602+
1603+import logger
1604+import restorewidget
1605+
1606+_=gettext.gettext
1607+
1608+class SnapshotsWidget(urwid.WidgetWrap):
1609+
1610+ def __init__( self, main_win, config, snapshots, path ):
1611+ self._main_win = main_win
1612+ self._config = config
1613+ self._snapshots = snapshots
1614+ self._path = path
1615+ self._selected_left = ''
1616+ self._selected_right = None
1617+
1618+ left_side, right_side = self._build_snapshot_lists()
1619+ pile_list = [
1620+ urwid.Text( _('Diff versions of') + ': %s' % path ),
1621+ urwid.Divider(),
1622+ urwid.Columns( [
1623+ urwid.Pile( left_side ),
1624+ urwid.Pile( right_side )
1625+ ], 1 ),
1626+ urwid.Divider(),
1627+ urwid.GridFlow( [
1628+ urwid.AttrWrap( urwid.Button( _('Jump to'), self.jump ), 'button', 'button_focus' ),
1629+ urwid.AttrWrap( urwid.Button( _('Diff'), self.diff ), 'button', 'button_focus' ),
1630+ urwid.AttrWrap( urwid.Button( _('Cancel'), self.go_back ), 'button', 'button_focus' )
1631+ ], 11, 2, 1, 'right' )
1632+ ]
1633+
1634+ display_widget = urwid.Pile( pile_list )
1635+ urwid.WidgetWrap.__init__( self, display_widget )
1636+
1637+ def _build_snapshot_lists( self ):
1638+ snapshots_list = self._snapshots.get_snapshots_list()
1639+ left_list = [
1640+ urwid.Text( _('Snapshots') + ':' ),
1641+ urwid.Divider()
1642+ ]
1643+ left_list_buttons = []
1644+ right_list = [
1645+ urwid.Text( _('Diff with') + ':' ),
1646+ urwid.Divider()
1647+ ]
1648+ right_list_buttons = []
1649+
1650+ path = self._snapshots.get_snapshot_path_to( self._main_win.selected_snapshot, self._path )
1651+ isdir = os.path.isdir( path )
1652+
1653+ # add now
1654+ path = self._path
1655+ if os.path.lexists( path ):
1656+ if os.path.isdir( path ) == isdir:
1657+ left_list.append( urwid.RadioButton( left_list_buttons, _('Now'), True, self.left_changed, '' ) )
1658+ right_list.append( urwid.RadioButton( right_list_buttons, _('Now'), False, self.right_changed, '' ) )
1659+
1660+ # add snapshots
1661+ for snapshot in snapshots_list:
1662+ path = self._snapshots.get_snapshot_path_to( snapshot, self._path )
1663+ if os.path.lexists( path ):
1664+ if os.path.isdir( path ) == isdir:
1665+ display_name = self._snapshots.get_snapshot_display_id( snapshot )
1666+ user_name = self._snapshots.get_snapshot_name( snapshot )
1667+ if len( user_name ) > 0:
1668+ display_name += ' - ' + user_name
1669+ if self._main_win.is_selected_snapshot( snapshot ):
1670+ selected = True
1671+ self._selected_right = snapshot
1672+ else:
1673+ selected = False
1674+ left_list.append( urwid.RadioButton( left_list_buttons, display_name, False, self.left_changed, snapshot ) )
1675+ right_list.append( urwid.RadioButton( right_list_buttons, display_name, selected, self.right_changed, snapshot ) )
1676+
1677+ return left_list, right_list
1678+
1679+ def left_changed( self, radio_button, new_state, user_data ):
1680+ if new_state:
1681+ self._selected_left = user_data
1682+
1683+ def right_changed( self, radio_button, new_state, user_data ):
1684+ if new_state:
1685+ self._selected_right = user_data
1686+
1687+ def jump( self, button ):
1688+ self._main_win.selected_snapshot = self._selected_left
1689+ self._main_win.update_content( restorewidget.RestoreWidget( self._main_win, self._config ), 5 )
1690+
1691+ def diff( self, button ):
1692+ if self._selected_right is not None:
1693+ self._main_win.update_content( DiffWidget( self._main_win, self._config, self._snapshots, self._path, self._selected_left, self._selected_right ), 5 )
1694+
1695+ def go_back( self, button ):
1696+ self._main_win.update_content( restorewidget.RestoreWidget( self._main_win, self._config ), 5 )
1697+
1698+class DiffWidget(SnapshotsWidget):
1699+ def __init__( self, main_win, config, snapshots, path, selected_left, selected_right ):
1700+ self._main_win = main_win
1701+ self._config = config
1702+
1703+ path_left = snapshots.get_snapshot_path_to( selected_left, path )
1704+ path_right = snapshots.get_snapshot_path_to( selected_right, path )
1705+
1706+ diff_cmd = 'diff "%s" "%s"' % ( path_left, path_right )
1707+ logger.info( 'diff cmd = "%s"' % diff_cmd )
1708+
1709+ wc_cmd = '%s | wc -l' % diff_cmd
1710+ callback = subprocess.Popen( wc_cmd, shell=True, stdout=subprocess.PIPE )
1711+ output = callback.communicate()
1712+ if main_win.main_loop.screen_size:
1713+ rows = main_win.main_loop.screen_size[1]
1714+ else:
1715+ rows = main_win.main_loop.screen.get_cols_rows()[1]
1716+
1717+ if int( output[0] ) > rows:
1718+ temp_file, temp_file_name = tempfile.mkstemp( '_bitdiff', os.path.basename( path ), text=True )
1719+ output_text = _('This diff was too long for the window, it has been written to') + ': %s' % temp_file_name
1720+ callback = subprocess.Popen( diff_cmd, shell=True, stdout=temp_file )
1721+ callback.communicate()
1722+ else:
1723+ callback = subprocess.Popen( diff_cmd, shell=True, stdout=subprocess.PIPE )
1724+ output = callback.communicate()
1725+ output_text = output[0]
1726+
1727+ pile_list = [
1728+ urwid.Text( diff_cmd ),
1729+ urwid.Divider(),
1730+ urwid.Text( output_text ),
1731+ urwid.Divider(),
1732+ urwid.AttrWrap( urwid.Button( _('OK'), self.go_back ), 'button', 'button_focus' )
1733+ ]
1734+
1735+ display_widget = urwid.Pile( pile_list )
1736+ urwid.WidgetWrap.__init__( self, display_widget )
1737
1738=== added file 'cli/viewerwidget.py'
1739--- cli/viewerwidget.py 1970-01-01 00:00:00 +0000
1740+++ cli/viewerwidget.py 2010-03-02 06:06:17 +0000
1741@@ -0,0 +1,132 @@
1742+# Back In Time
1743+# Copyright (C) 2010 Ryan Ahearn
1744+#
1745+# This program is free software; you can redistribute it and/or modify
1746+# it under the terms of the GNU General Public License as published by
1747+# the Free Software Foundation; either version 2 of the License, or
1748+# (at your option) any later version.
1749+#
1750+# This program is distributed in the hope that it will be useful,
1751+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1752+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1753+# GNU General Public License for more details.
1754+#
1755+# You should have received a copy of the GNU General Public License along
1756+# with this program; if not, write to the Free Software Foundation, Inc.,
1757+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1758+
1759+
1760+import os
1761+import os.path
1762+import urwid
1763+import gettext
1764+
1765+import logger
1766+import restorewidget
1767+import copywidget
1768+import snapshotswidget
1769+
1770+_=gettext.gettext
1771+
1772+class ViewerWidget(urwid.WidgetWrap):
1773+
1774+ def __init__( self, main_win, config, snapshots ):
1775+ self._main_win = main_win
1776+ self._config = config
1777+ self._snapshots = snapshots
1778+ self._selected = ''
1779+
1780+ if main_win.show_hidden:
1781+ hidden_label = _('Hide hidden files')
1782+ else:
1783+ hidden_label = _('Show hidden files')
1784+ self.build_fileview()
1785+ pile_list = [
1786+ urwid.Columns( [
1787+ ( 'fixed', 6, urwid.AttrWrap( urwid.Button( _('Up'), self.up_directory ), 'button', 'button_focus' ) ),
1788+ urwid.Text( main_win.current_directory, wrap='clip' )
1789+ ], 1),
1790+ urwid.Divider(),
1791+ urwid.Columns( [
1792+ ( 'fixed', 21, urwid.AttrWrap( urwid.Button( hidden_label, self.flip_hidden ), 'button', 'button_focus' ) ),
1793+ ( 'fixed', 11, urwid.AttrWrap( urwid.Button( _('Restore'), self.restore ), 'button', 'button_focus' ) ),
1794+ ( 'fixed', 8, urwid.AttrWrap( urwid.Button( _('Copy'), self.copy ), 'button', 'button_focus' ) ),
1795+ ( 'fixed', 13,urwid.AttrWrap( urwid.Button( _('Snapshots'), self.snapshots ), 'button', 'button_focus' ) )
1796+ ], 1 ),
1797+ urwid.Divider(),
1798+ urwid.Columns( [ self.build_shortcuts(),
1799+ ( 'weight', 3, urwid.Pile( self._file_list ) ) ], 1 )
1800+ ]
1801+ display_widget = urwid.Pile( pile_list )
1802+ urwid.WidgetWrap.__init__( self, display_widget )
1803+
1804+ def build_fileview( self ):
1805+ self._file_list = [
1806+ urwid.Text( _('Files and Folders') ),
1807+ urwid.Divider()
1808+ ]
1809+
1810+ sid = self._main_win.selected_snapshot
1811+
1812+ full_path = self._snapshots.get_snapshot_path_to( sid, self._main_win.current_directory )
1813+
1814+ if not self._snapshots.can_open_path( sid, full_path ):
1815+ self._file_list.append( urwid.Text( _('This folder does not exist') ) )
1816+ self._file_list.append( urwid.Text( _('in current snapshot !' ) ) )
1817+ return
1818+
1819+ directory = os.listdir( full_path )
1820+ decorated = [ ( not os.path.isdir( os.path.join( full_path, file_name ) ), file_name ) for file_name in directory ]
1821+ decorated.sort()
1822+ restore_radios = []
1823+ for is_file, file_name in decorated:
1824+ if self._main_win.show_hidden or not file_name.startswith( '.' ):
1825+ if is_file:
1826+ self._file_list.append( urwid.RadioButton( restore_radios, file_name, False, self.select_for_restore, file_name ) )
1827+ else:
1828+ self._file_list.append( urwid.Columns( [
1829+ ( 'fixed', 4, urwid.RadioButton( restore_radios, '', False, self.select_for_restore, file_name ) ),
1830+ urwid.AttrWrap( urwid.Button( file_name, self.go_to_directory ), 'button', 'button_focus' )
1831+ ], 0 ) )
1832+
1833+
1834+ def select_for_restore( self, radio_button, new_state, user_data ):
1835+ if new_state:
1836+ self._selected = os.path.join( self._main_win.current_directory, user_data )
1837+
1838+ def go_to_directory( self, button ):
1839+ new_directory = os.path.join( self._main_win.current_directory, button.get_label() )
1840+ self._main_win.update_current_directory( new_directory )
1841+
1842+ def build_shortcuts( self ):
1843+ pile_list = [ urwid.Text( _('Shortcuts') ) ]
1844+
1845+ included_folders = self._config.get_include_folders()
1846+ for label in included_folders:
1847+ pile_list.append( urwid.AttrWrap( urwid.Button( label, self.go_to_shortcut ), 'button', 'button_focus' ) )
1848+
1849+ return urwid.Pile( pile_list )
1850+
1851+ def go_to_shortcut( self, button ):
1852+ self._main_win.update_current_directory( button.get_label() )
1853+
1854+ def up_directory( self, button ):
1855+ self._main_win.update_current_directory( os.path.dirname( self._main_win.current_directory ) )
1856+
1857+ def flip_hidden( self, button ):
1858+ self._main_win.show_hidden = not self._main_win.show_hidden
1859+ self._main_win.update_content( restorewidget.RestoreWidget( self._main_win, self._config ), 5 )
1860+
1861+ def restore( self, button ):
1862+ if self._main_win.selected_snapshot != '' and self._selected != '':
1863+ self._snapshots.restore( self._main_win.selected_snapshot, self._selected )
1864+
1865+ def copy( self, button ):
1866+ if self._main_win.selected_snapshot != '' and self._selected != '':
1867+ source_path = self._snapshots.get_snapshot_path_to( self._main_win.selected_snapshot, self._selected )
1868+ self._main_win.update_content( copywidget.CopyWidget( self._main_win, self._config, source_path ), 5 )
1869+
1870+ def snapshots( self, button ):
1871+ if self._selected != '':
1872+ self._main_win.update_content( snapshotswidget.SnapshotsWidget( self._main_win, self._config, self._snapshots, self._selected ), 5 )
1873+
1874
1875=== modified file 'common/backintime.py'
1876--- common/backintime.py 2009-12-17 10:18:53 +0000
1877+++ common/backintime.py 2010-03-02 06:06:18 +0000
1878@@ -83,7 +83,6 @@
1879
1880 def start_app( app_name = 'backintime', extra_args = [] ):
1881 cfg = config.Config()
1882- print_version( cfg, app_name )
1883
1884 skip = False
1885 index = 0
1886@@ -111,6 +110,7 @@
1887 sys.exit(0)
1888
1889 if arg == '--version' or arg == '-v':
1890+ print_version( cfg, app_name )
1891 sys.exit(0)
1892
1893 if arg == '--license':
1894
1895=== modified file 'common/logger.py'
1896--- common/logger.py 2009-11-02 13:52:00 +0000
1897+++ common/logger.py 2010-03-02 06:06:18 +0000
1898@@ -27,14 +27,11 @@
1899 syslog.closelog()
1900
1901 def error( msg ):
1902- print 'ERROR: ' + msg
1903 syslog.syslog( syslog.LOG_ERR, 'ERROR: ' + msg )
1904
1905 def warning( msg ):
1906- print 'WARNING: ' + msg
1907 syslog.syslog( syslog.LOG_WARNING, 'WARNING: ' + msg )
1908
1909 def info( msg ):
1910- print 'INFO: ' + msg
1911 syslog.syslog( syslog.LOG_INFO, 'INFO: ' + msg )
1912
1913
1914=== modified file 'makedeb.sh'
1915--- makedeb.sh 2009-05-12 10:13:25 +0000
1916+++ makedeb.sh 2010-03-02 06:06:17 +0000
1917@@ -1,6 +1,6 @@
1918 #!/bin/bash
1919
1920-for i in common gnome kde4; do
1921+for i in common gnome kde4 cli; do
1922 PKGNAME=`cat $i/debian_specific/control | grep "Package:" | cut -d" " -f2`
1923 PKGVER=`cat $i/debian_specific/control | grep "Version:" | cut -d" " -f2`
1924 PKGARCH=`cat $i/debian_specific/control | grep "Architecture:" | cut -d" " -f2`
1925
1926=== modified file 'updateversion.sh'
1927--- updateversion.sh 2009-04-27 13:43:00 +0000
1928+++ updateversion.sh 2010-03-02 06:06:17 +0000
1929@@ -12,6 +12,9 @@
1930 echo "Update 'kde4/debian_specific/control'"
1931 sed -i -e "s/^Version: .*$/Version: $VERSION/" -e "s/backintime-common (= [^)]*)/backintime-common (= $VERSION)/" kde4/debian_specific/control
1932
1933+echo "Update 'cli/debian_specific/control'"
1934+sed -i -e "s/^Version: .*$/Version: $VERSION/" -e "s/backintime-common (= [^)]*)/backintime-common (= $VERSION)/" cli/debian_specific/control
1935+
1936 echo "Update 'common/config.py'"
1937 sed -i -e "s/^\tVERSION = '.*'$/\tVERSION = '$VERSION'/" common/config.py
1938
1939@@ -33,6 +36,16 @@
1940 sed -i -e "s/\.TH\(.*\)\"version\([^\"]*\)\"\(.*\)$/.TH\1\"version $VERSION\"\3/" $FILE
1941 gzip --best $FILE
1942
1943+echo "Update CLI man page"
1944+FILE=cli/man/C/backintime-config.1
1945+gzip -d $FILE.gz
1946+sed -i -e "s/\.TH\(.*\)\"version\([^\"]*\)\"\(.*\)$/.TH\1\"version $VERSION\"\3/" $FILE
1947+gzip --best $FILE
1948+FILE=cli/man/C/backintime-restore.1
1949+gzip -d $FILE.gz
1950+sed -i -e "s/\.TH\(.*\)\"version\([^\"]*\)\"\(.*\)$/.TH\1\"version $VERSION\"\3/" $FILE
1951+gzip --best $FILE
1952+
1953 echo "Update help .omf file"
1954 sed -i -e "s/^\([ \]*\)<version\([^0-9]*\)\([^\"]*\)\(.*\)$/\1<version\2$VERSION\4/" gnome/docbook/C/backintime-C.omf
1955

Subscribers

People subscribed via source and target branches