#!/usr/bin/perl -w # Author: Matthieu Moy # Version: 0.1 # The latest version should allways be availlable from # http://www-verimag.imag.fr/~moy/ibcd # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This file is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with GNU Emacs; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. my $Gtk2_available; BEGIN { eval "use Gtk2 '-init';"; $Gtk2_available = $@ ? 0 : 1; } use strict; use warnings; use Cwd; use File::Find; use File::Compare; use File::Path; use POSIX 'strftime'; use Getopt::Long; # default values my $false = 0; my $true = 1; my %Options; GetOptions(\%Options, "previous=s", "source=s", "current=s", "device=s", "verbose", "gui", "help"); my $source=$Options{"source"}; my $previous=$Options{"previous"}; my $verbose=$Options{"verbose"}; my $current=$Options{"current"}; my $device=$Options{"device"}; my $gui=!$Options{"gui"}; if ($Options{"help"}) { print "ibcd : Incremental backup on CD. Usage: ibcd --nogui --source s --previous p [ --current c ] [ --device x,y ] [ --help ] Options description: --nogui disable Gtk2 interface. Necessary to use the other options. --source s The directory you want to backup --previous p A previous backup of the directory --current c Name of the directory in which you want to create the new backup (directory from the root of the CD) --device x,y Name of the burning device --help Print this message. " ; exit 1; } if (! $device or $device eq "") {$device = "0,0";} if ($Gtk2_available && $gui) { my $window; my $choice_menu; my %choice_key; my $menu_item; my $vbox; my $button; my $source_txt; my $current_txt; my $previous_txt; my $device_menu; # create a new window $window = new Gtk2::Window( 'toplevel' ); $window->set_title( "Backup on CD" ); $window->signal_connect( 'delete_event', sub { exit( 0 ); } ); $device_menu = new Gtk2::Menu(); open(CDRSCAN, "cdrecord -scanbus |"); my $i = 0; while() { s/\n// ; if (! / *([0-9],[0-9]),[0-9] */) { next; } # Create a new menu-item with a name... $menu_item = new Gtk2::MenuItem( $_ ); $menu_item->set_data("user_data", ++$i); $choice_key{$i}=$1; $device_menu->append( $menu_item ); $menu_item->show(); } # This is the root menu, and will be the label displayed on the menu bar. # There won't be a signal handler attached, as it only pops up the rest # of the menu when pressed. $choice_menu = new Gtk2::OptionMenu(); $choice_menu->show(); # Now we specify that we want our newly created "menu" to be the menu # for the "root menu" $choice_menu->set_menu( $device_menu ); my $fullh = new Gtk2::VBox(); $fullh->show(); $window->add($fullh); my $cdr_chose = new Gtk2::HBox(); $cdr_chose->show(); $fullh->add($cdr_chose); # my $frame = new Gtk::Frame( "CD Recorder" ); # $frame->add( $choice_menu ); # $window->pack_start( $frame, $false, $false, 0 ); my $label = new Gtk2::Label("CD recorder"); $label->show(); $cdr_chose->add($label); $cdr_chose->add($choice_menu); my $but; my $sourcesel = new Gtk2::FileSelection("Directory to backup"); $sourcesel->ok_button->signal_connect( "clicked", sub { $source_txt->set_text($sourcesel->get_filename()); $sourcesel->hide() ;} ); $sourcesel->cancel_button->signal_connect( "clicked", sub { $sourcesel->hide() ; } ); my $sourcev = new Gtk2::HBox(); $label = new Gtk2::Label("Directory to backup: "); $label->show(); $sourcev->add($label); $sourcev->show(); $source_txt = new Gtk2::Entry(); $source_txt->set_size_request(350, -1); $source_txt->show(); $sourcev->add($source_txt); $but = new Gtk2::Button("Browse"); $but->signal_connect( "clicked", sub { $sourcesel->show() ; } ); $but->show(); $sourcev->add($but); $fullh->add($sourcev); my $previousv = new Gtk2::HBox(); $label = new Gtk2::Label("Location of the previous backup: "); $label->show(); $previousv->add($label); $previousv->show(); $previous_txt = new Gtk2::Entry(); $previous_txt->set_size_request(350, -1); $previous_txt->show(); $previousv->add($previous_txt); my $previoussel = new Gtk2::FileSelection("Directory to backup"); $previoussel->ok_button->signal_connect( "clicked", sub { $previous_txt->set_text($previoussel->get_filename()); $previoussel->hide() ;} ); $previoussel->cancel_button->signal_connect( "clicked", sub { $previoussel->hide() ; } ); $but = new Gtk2::Button("Browse"); $but->signal_connect( "clicked", sub { $previoussel->show() ; } ); $but->show(); $previousv->add($but); $fullh->add($previousv); my $newbackup = new Gtk2::HBox(); $newbackup->show(); $label = new Gtk2::Label("Name of the backup"); $label->show(); $newbackup->add($label); $current_txt = new Gtk2::Entry(); $current_txt->set_text(strftime("%d_%B_%Y", localtime)); $current_txt->show(); $newbackup->add($current_txt); $fullh->add($newbackup); my $buts = new Gtk2::HBox(); $buts->show(); $but = new_from_stock Gtk2::Button("gtk-quit"); $but->show(); $but->signal_connect( "clicked", sub { exit( 0 ); } ); $buts->add($but); $but = new_from_stock Gtk2::Button("gtk-help"); $but->signal_connect( "clicked", \&help_method ); $but->show(); $buts->add($but); $but = new_from_stock Gtk2::Button("gtk-ok"); $but->signal_connect( "clicked", \&go_method ); $but->show(); $buts->add($but); $fullh->add($buts); $window->show(); Gtk2->main; sub go_method { $source = $source_txt->get_text(); $current = $current_txt->get_text(); $previous = $previous_txt->get_text(); $device = $choice_key{$device_menu->get_active()->get_data("user_data")}; print "device ====== $device.. $device_menu->get_active()->get_name()\n"; Gtk2->main_quit(); } sub help_method { my $helpwin = new Gtk2::Window("toplevel"); my $vbox = new Gtk2::VBox(); $vbox->show(); my $helptext = new Gtk2::TextBuffer(); $helptext->set_text("IBCD: Incremental Backup on CD Select the writing device you want to use, then the directory you want to backup. Then, select a previous backup you have made of this directory. Typically, this backup is on the same CD. Give a name for the backup you want to create now and click OK. The program will now create a CD image containing all the files that have been modified since the last backup, and symbolic links to the unmodified files. The image is then written on the CD."); my $helpbuffer = new_with_buffer Gtk2::TextView($helptext); $helpbuffer->show(); $vbox->add($helpbuffer); my $ok = new_from_stock Gtk2::Button("gtk-close"); $ok->show(); $ok->signal_connect("clicked", sub { $helpwin->destroy(); } ); $vbox->add($ok); $helpwin->add($vbox); $helpwin->show(); } } $source =~ s/\/$// ; $previous =~ s/\/$// ; $previous =~ m/(.*)\/([^\/]*)$/ ; my $previousbase = $1 ; my $previousname = $2 ; if ($current eq "") { $current = strftime("${previousname}_%d_%B_%Y", localtime); } if ($verbose) { print "previous = ", $previous, "\n"; print "source = ", $source, "\n"; print "current = ", $current, "\n"; print "device = ", $device, "\n"; } my $tmpdir="/tmp/incremental.$$" ; mkdir $tmpdir; my $links="$tmpdir/links" ; mkdir $links ; my $list="$tmpdir/file-list" ; open(LIST, ">$list"); my $listline; my $img="$tmpdir/image.iso" ; treat_source(); sub treat_source { chdir $source ; find (\&treat_file, $source); } sub treat_file { my $location = cwd; my $file = $File::Find::name; my $oldfile = $file; return unless -f $file; # don't do anything for directory. my $basename = $file; # remove the base directory. $basename =~ s/^$source\///; # previous copy of the file my $previousfile = "$previous/$basename" ; my $fileoncd = "$current/$basename"; $fileoncd =~ s?=?\\=?g ; $fileoncd =~ s?\\?\\\\?g ; $fileoncd =~ s?/$??g ; if ( -f $previousfile && compare($previousfile, $file) == 0) { my $dirname = $file; $dirname =~ s?/[^/]*$?? ; $file =~ s?^.*/?? ; my $dir; my $dots = ".." ; chdir $links ; my $base = $basename ; # if $base contains a / if ($base =~ /\//) { $base =~ s?/[^/]*$?? ; foreach $dir (split ("/", $base)) { mkdir $dir; chdir $dir; $dots="../$dots" ; } } symlink("$dots/$previousname/$basename", $file); $listline = "$fileoncd=$links/$basename" ; } else { $listline = "$fileoncd=$oldfile" ; } if ($verbose) { print $listline, "\n"; } print LIST $listline, "\n"; chdir $location ; } my $msinfo=`cdrecord -msinfo dev=$device` ; $msinfo =~ s/\n// ; if (! $msinfo =~ /[0-9]+,[0-9]+/) { print "Can not obtain multisession info.\n command output is $msinfo" ; exit 1; } my $mkisocmd = "mkisofs -gui -graft-points -A IBMSC -R -path-list $list -C $msinfo -M $device -D -o $img" ; print $mkisocmd, "\n" ; system $mkisocmd ; if ( -f $img ) { printf "Image $img created\n" ; } else { printf "Image $img NOT created\n" ; exit(2); } system "cdrecord -v -multi dev=$device $img"; print "bye.\n";