From: Eric Bollengier Date: Sat, 3 Mar 2007 11:05:58 +0000 (+0000) Subject: Merge trunk into branch-2.0 gui/brestore X-Git-Tag: Release-2.0.3~11 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=69678cc6faa432dba92ab091d8d9514132a9c57e;p=bacula%2Fbacula Merge trunk into branch-2.0 gui/brestore git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/branches/Branch-2.0@4304 91ce42f0-d328-0410-95d8-f526ca767f89 --- diff --git a/gui/brestore/README b/gui/brestore/README index 49ff752159..94a3314c32 100644 --- a/gui/brestore/README +++ b/gui/brestore/README @@ -17,7 +17,7 @@ It has the following features : * Simple restoration by the generation of a BSR file - * Works indifferently with postgresql and mysql + * Works with either PostgreSQL or MySQL We have a working (if a bit unpolished) version ready, and think it is time for us to publish it and have user and Kern feedback. @@ -31,90 +31,11 @@ What's missing : * Plenty of features. As we don't have lots of imagination. Don't hesitate to suggest us your ideas ... - * We want use bacula to generate bsr files, at this time, you can't restore + * We want use Bacula to generate bsr files, at this time, you can't restore files across different Storage (Full on tape, Incr on Disk for example) For the moment, we want to concentrate on having a restoration interface. We don't care about seeing the restoration progress, or anything else. -These tools are available with Bweb. +brestore is also available within Bweb. -################ INSTALL ####################################### - -# you can put glade file on /usr/share/brestore or /usr/local/share/brestore -mkdir -p /usr/share/brestore -install -m 644 -o root -g root brestore.glade /usr/share/brestore -install -m 755 -o root -g root brestore.pl /usr/bin - -# if you doesn't use bweb and bconsole.pl -# you must get Bconsole.pm from bweb modules (bweb/lib/Bconsole.pm) - -install -m 644 -o root -g root Bconsole.pm /usr/share/perl5 - -# copy bsr file directly to your director, use must use scp (or nfs stuff) -# if you doesn't want to use this, you can dump the bsr file with "Gen BSR" button. -# so you must have an ssh-key exchange. (you can also hack brestore.pl to use -# something like '$err = `xterm -e scp $src $1 2>&1`' - -your_login@yourws:~$ ssh-copy-id -i .ssh/your_pub_key login@bacula-dir - -# you have to configure brestore preferences. - -################ WINDOWS INSTALL ############################### - -Brestore works on win32 with ActivePerl. To get all libraries, you -can install GCSTAR. You have also to copy libglade-2.0.dll - -################ INSTALL MODULES ############################### - -Perl Modules -The Debian Way - -apt-get install libgtk2-gladexml-perl -apt-get install libdbd-mysql-perl -apt-get install libdbd-pg-perl -apt-get install libexpect-perl -apt-get install libwww-perl - -The Red Hat way -Please contribute, neither of us have a Red Hat to test on :) -Other distros/Unices -Please contribute ... -The generic Way, if those modules aren't packaged by your distro - -perl -MCPAN -e shell -install Gtk2 -install Expect -install Gtk2::GladeXML -install LWP - -install DBD::mysql -or -install DBD::Pg - -################ BCONSOLE WITHOUT CONIO ######################## - -If you doesn't use Bweb, you must use bconsole without conio support. For that, use -bconsole -n option. - -WARNING : You must be able to execute bconsole and able to read bconsole.conf ! - -################ CONFIGURATION ################################# - -You can make configuration from brestore himself (if brestore can't find -his glade file). - -Example : - -$parameters = { - 'mozilla' => 'mozilla', - 'bconsole' => '/usr/local/bin/bconsole.noconio -c /etc/bacula/bconsole.conf', - 'bweb' => 'http://localhost/cgi-bin/bweb/bweb.pl', - 'connection_string' => 'DBI:mysql:database=bacula;host=127.0.0.1;port=3306', - 'username' => 'bacula', - 'password' => 'xxxx', - 'bsr_dest' => 'file://var/tmp', - 'debug' => 0, - 'use_ok_bkp_only' => 1, - 'default_restore_job' => 'restore' - }; diff --git a/gui/brestore/ReleaseNotes b/gui/brestore/ReleaseNotes index e5210f6f75..bbdbc240bc 100644 --- a/gui/brestore/ReleaseNotes +++ b/gui/brestore/ReleaseNotes @@ -1,6 +1,17 @@ - Release Notes for brestore 1.39.28 + Release Notes for brestore 2.0.2 -Version 1.40: +Version 2.0.2-2: + - fix cache create bug + +Version 2.0.2-1: + - Fix bug with accent, use filenameid/pathid instead of filename + +Version 2.0.1-1: + - Do nothing if destination string is empty + - Fix warning message when using with bweb http connector + - Add CVS version number to UA brestore + +Version 2.0.0: - Add debian package - Add BwebConsole package to keep bconsole installation and scp usage on admin workstation. It permit to use brestore on diff --git a/gui/brestore/brestore-fr.glade b/gui/brestore/brestore-fr.glade index e1088f7a3f..5d605e21f3 100644 --- a/gui/brestore/brestore-fr.glade +++ b/gui/brestore/brestore-fr.glade @@ -745,7 +745,7 @@ This year True - Can be very long... + Peut prendre beaucoup de temps... True True GTK_RELIEF_NORMAL @@ -1133,8 +1133,7 @@ This year True - <small>Bacula Copyright ©2000-2006 Kern Sibbald </small> -<small>brestore Copyright ©2006 Marc Cousin and Eric Bollengier</small> + <small>Bacula Copyright ©2000-2006FSF Europe </small> False True @@ -1875,8 +1874,7 @@ Ou, si le director est sur une autre machine : <b> scp://bacula@hostdir:/path/to/bsr </b> Ou si le director fonctionne avec le connecteur http bweb -<b> file://var/tmp</b> -Et vous devez positionner bconsole sur +vous devez laisser ce champs vide, et vous devez positionner bconsole sur <b> http://user:pass@director/cgi-bin/bweb/bconsole.pl</b> False True diff --git a/gui/brestore/brestore.glade b/gui/brestore/brestore.glade index b5256ebc52..b32a8e693e 100644 --- a/gui/brestore/brestore.glade +++ b/gui/brestore/brestore.glade @@ -1135,8 +1135,7 @@ This year True - <small>Bacula Copyright ©2000-2006 Kern Sibbald </small> -<small>brestore Copyright ©2006 Marc Cousin and Eric Bollengier</small> + <small>Bacula Copyright ©2000-2006FSF Europe</small> False True @@ -1872,15 +1871,14 @@ Example : DBI:Pg:database=bacula;host=127.0.0.1;port=5432 To launch a restore job, bacula need a BSR file. This file must be <b>local</b> to director, so -if director is local, put something like : +if the director is local, put something like : <b> file://path/to/bacula/work/dir</b> or if director is on other host <b> scp://bacula@hostdir:/path/to/bsr </b> -or if director works with the bweb http connector -<b> file://var/tmp</b> -and set bconsole to -<b> http://user:pass@director/cgi-bin/bweb/brestore.pl</b> +or if the director works with the bweb http connector +keep it empty and set bconsole to +<b> http://user:pass@director/cgi-bin/bweb/bconsole.pl</b> False True GTK_JUSTIFY_LEFT diff --git a/gui/brestore/brestore.pl b/gui/brestore/brestore.pl index d2710bb2cf..aa946332b8 100755 --- a/gui/brestore/brestore.pl +++ b/gui/brestore/brestore.pl @@ -1,5 +1,6 @@ #!/usr/bin/perl -w -use strict ; + +use strict; # path to your brestore.glade my $glade_file = 'brestore.glade' ; @@ -69,230 +70,451 @@ use Gtk2::GladeXML; use Gtk2::SimpleList; # easy wrapper for list views use Gtk2::Gdk::Keysyms; # keyboard code constants use Data::Dumper qw/Dumper/; -use DBI; my $debug=0; # can be on brestore.conf +our ($VERSION) = ('$Revision$' =~ /(\d+\.\d+)/); -################################################################ +package Pref; +use DBI; +sub new +{ + my ($class, $config_file) = @_; + + my $self = bless { + config_file => $config_file, + password => '', # db passwd + username => '', # db username + connection_string => '',# db connection string + bconsole => 'bconsole', # path and arg to bconsole + bsr_dest => '', # destination url for bsr files + debug => 0, # debug level 0|1 + use_ok_bkp_only => 1, # dont use bad backup + bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url + see_all_versions => 0, # display all file versions in FileInfo + mozilla => 'mozilla', # mozilla bin + default_restore_job => 'restore', # regular expression to select default + # restore job -package DlgFileVersion; + # keywords that are used to fill DlgPref + chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ], + entry_keyword => [ qw/username password bweb mozilla + connection_string default_restore_job + bconsole bsr_dest glade_file/], + }; -sub on_versions_close_clicked -{ - my ($self, $widget)=@_; - $self->{version}->destroy(); -} + $self->read_config(); -sub on_selection_button_press_event -{ - print STDERR "on_selection_button_press_event()\n"; + return $self; } -sub fileview_data_get +sub read_config { - my ($self, $widget, $context, $data, $info, $time,$string) = @_; + my ($self) = @_; - DlgResto::drag_set_info($widget, - $self->{cwd}, - $data); + # We read the parameters. They come from the configuration files + my $cfgfile ; my $tmpbuffer; + if (open FICCFG, $self->{config_file}) + { + while(read FICCFG,$tmpbuffer,4096) + { + $cfgfile .= $tmpbuffer; + } + close FICCFG; + my $refparams; + no strict; # I have no idea of the contents of the file + eval '$refparams' . " = $cfgfile"; + use strict; + + for my $p (keys %{$refparams}) { + $self->{$p} = $refparams->{$p}; + } + + } else { + # TODO : Force dumb default values and display a message + } } -sub new +sub write_config { - my ($class, $dbh, $client, $path, $file) = @_; - my $self = bless { - cwd => $path, - version => undef, # main window - }; + my ($self) = @_; + + $self->{error} = ''; + my %parameters; - # we load version widget of $glade_file - my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version"); + for my $k (@{ $self->{entry_keyword} }) { + $parameters{$k} = $self->{$k}; + } - # Connect signals magically - $glade_box->signal_autoconnect_from_package($self); + for my $k (@{ $self->{chk_keyword} }) { + $parameters{$k} = $self->{$k}; + } - $glade_box->get_widget("version_label") - ->set_markup("File revisions : $client:$path/$file"); + if (open FICCFG,">$self->{config_file}") + { + print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]); + close FICCFG; + } + else + { + $self->{error} = "Can't write configuration $!"; + } + return $self->{error}; +} - my $widget = $glade_box->get_widget('version_fileview'); - my $fileview = Gtk2::SimpleList->new_from_treeview( - $widget, - 'h_name' => 'hidden', - 'h_jobid' => 'hidden', - 'h_type' => 'hidden', +sub connect_db +{ + my $self = shift ; - 'InChanger' => 'pixbuf', - 'Volume' => 'text', - 'JobId' => 'text', - 'Size' => 'text', - 'Date' => 'text', - 'MD5' => 'text', - ); - DlgResto::init_drag_drop($fileview); + if ($self->{dbh}) { + $self->{dbh}->disconnect() ; + } - my @v = DlgResto::get_all_file_versions($dbh, - "$path/", - $file, - $client, - 1); - for my $ver (@v) { - my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname) - = @{$ver}; - my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon; + delete $self->{dbh}; + delete $self->{error}; - DlgResto::listview_push($fileview, - $file, $jobid, 'file', - $icon, $volname, $jobid,DlgResto::human($size), - scalar(localtime($mtime)), $md5); + if (not $self->{connection_string}) + { + # The parameters have not been set. Maybe the conf + # file is empty for now + $self->{error} = "No configuration found for database connection. " . + "Please set this up."; + return 0; } - - $self->{version} = $glade_box->get_widget('dlg_version'); - $self->{version}->show(); - return $self; + if (not eval { + $self->{dbh} = DBI->connect($self->{connection_string}, + $self->{username}, + $self->{password}) + }) + { + $self->{error} = "Can't open bacula database. " . + "Database connect string '" . + $self->{connection_string} ."' $!"; + return 0; + } + $self->{dbh}->{RowCacheSize}=100; + return 1; } -sub on_forward_keypress -{ - return 0; +sub go_bweb +{ + my ($self, $url, $msg) = @_; + + unless ($self->{mozilla} and $self->{bweb}) { + new DlgWarn("You must install Bweb and set your mozilla bin to $msg"); + return -1; + } + + if ($^O eq 'MSWin32') { + system("start /B $self->{mozilla} \"$self->{bweb}$url\""); + + } elsif (!fork()) { + system("$self->{mozilla} -remote 'Ping()'"); + my $cmd = "$self->{mozilla} '$self->{bweb}$url'"; + if ($? == 0) { + $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ; + } + exec($cmd); + exit 1; + } + return ($? == 0); } 1; + ################################################################ -package DlgWarn; + +package Bbase; sub new { - my ($package, $text) = @_; + my ($class, %arg) = @_; - my $self = bless {}; + my $self = bless { + conf => $arg{conf}, + }, $class; - my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn"); + return $self; +} - # Connect signals magically - $glade->signal_autoconnect_from_package($self); - $glade->get_widget('label_warn')->set_text($text); +use Data::Dumper; - print STDERR "$text\n"; +sub debug +{ + my ($self, $what, %arg) = @_; + + if ($self->{conf}->{debug} and defined $what) { + my $level=0; + if ($arg{up}) { + $level++; + } + my $line = (caller($level))[2]; + my $func = (caller($level+1))[3] || 'main'; + print "$func:$line\t"; + if (ref $what) { + print Data::Dumper::Dumper($what); + } elsif ($arg{md5}) { + print "MD5=", md5_base64($what), " str=", $what,"\n"; + } else { + print $what, "\n"; + } + } +} - $self->{window} = $glade->get_widget('dlg_warn'); - $self->{window}->show_all(); - return $self; +sub dbh_strcat +{ + my ($self, @what) = @_; + if ($self->{conf}->{connection_string} =~ /dbi:pg/i) { + return join(' || ', @what); + } else { + return 'CONCAT(' . join(',', @what) . ')' ; + } } -sub on_close_clicked +sub dbh_prepare +{ + my ($self, $query) = @_; + $self->debug($query, up => 1); + return $self->{conf}->{dbh}->prepare($query); +} + +sub dbh_do +{ + my ($self, $query) = @_; + $self->debug($query, up => 1); + return $self->{conf}->{dbh}->do($query); +} + +sub dbh_selectall_arrayref +{ + my ($self, $query) = @_; + $self->debug($query, up => 1); + return $self->{conf}->{dbh}->selectall_arrayref($query); +} + +sub dbh_selectrow_arrayref +{ + my ($self, $query) = @_; + $self->debug($query, up => 1); + return $self->{conf}->{dbh}->selectrow_arrayref($query); +} + +sub dbh { my ($self) = @_; - $self->{window}->destroy(); + return $self->{conf}->{dbh}; } + 1; ################################################################ -package BwebConsole; -use LWP::UserAgent; -use HTTP::Request::Common; +# Manage preference +package DlgPref; +# my $pref = new Pref(config_file => 'brestore.conf'); +# my $dlg = new DlgPref($pref); +# my $dlg_resto = new DlgResto($pref); +# $dlg->display($dlg_resto); sub new { - my ($class, %arg) = @_; + my ($class, $pref) = @_; my $self = bless { - pref => $arg{pref}, # Pref object - timeout => $arg{timeout} || 20, - debug => $arg{debug} || 0, - 'list_job' => '', - 'list_client' => '', - 'list_fileset' => '', - 'list_storage' => '', - 'run' => '', - }; + pref => $pref, # Pref ref + dlgresto => undef, # DlgResto ref + }; return $self; } -sub prepare +sub display { - my ($self, @what) = @_; - my $ua = LWP::UserAgent->new(); - $ua->agent("Brestore "); - my $req = POST($self->{pref}->{bconsole}, - Content_Type => 'form-data', - Content => [ map { (action => $_) } @what ]); - #$req->authorization_basic('eric', 'test'); + my ($self, $dlgresto) = @_ ; - my $res = $ua->request($req); + unless ($self->{glade}) { + $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ; + $self->{glade}->signal_autoconnect_from_package($self); + } - if ($res->is_success) { - foreach my $l (split(/\n/, $res->content)) { - my ($k, $c) = split(/=/,$l,2); - $self->{$k} = $c; - } - } else { - $self->{error} = "Can't connect to bweb : " . $res->status_line; - new DlgWarn($self->{error}); + $self->{dlgresto} = $dlgresto; + + my $g = $self->{glade}; + my $p = $self->{pref}; + + for my $k (@{ $p->{entry_keyword} }) { + $g->get_widget("entry_$k")->set_text($p->{$k}) ; + } + + for my $k (@{ $p->{chk_keyword} }) { + $g->get_widget("chkbp_$k")->set_active($p->{$k}) ; } + + $g->get_widget("dlg_pref")->show_all() ; } -sub run +sub on_applybutton_clicked { - my ($self, %arg) = @_; - - my $ua = LWP::UserAgent->new(); - $ua->agent("Brestore "); - my $req = POST($self->{pref}->{bconsole}, - Content_Type => 'form-data', - Content => [ job => $arg{job}, - client => $arg{client}, - storage => $arg{storage} || '', - fileset => $arg{fileset} || '', - where => $arg{where}, - replace => $arg{replace}, - priority=> $arg{prio} || '', - action => 'run', - timeout => 10, - bootstrap => [$arg{bootstrap}], - ]); - #$req->authorization_basic('eric', 'test'); + my ($self) = @_; + my $glade = $self->{glade}; + my $pref = $self->{pref}; - my $res = $ua->request($req); + for my $k (@{ $pref->{entry_keyword} }) { + my $w = $glade->get_widget("entry_$k") ; + $pref->{$k} = $w->get_text(); + } - if ($res->is_success) { - foreach my $l (split(/\n/, $res->content)) { - my ($k, $c) = split(/=/,$l,2); - $self->{$k} = $c; - } - } + for my $k (@{ $pref->{chk_keyword} }) { + my $w = $glade->get_widget("chkbp_$k") ; + $pref->{$k} = $w->get_active(); + } - if ($self->{run}) { - unlink($arg{bootstrap}); - new DlgWarn("Can't connect to bweb : " . $res->status_line); - } + if (!$pref->write_config() && $pref->connect_db()) { + $self->{dlgresto}->set_status('Preferences updated'); + $self->{dlgresto}->init_server_backup_combobox(); + $self->{dlgresto}->set_status($pref->{error}); - return $self->{run}; + } else { + $self->{dlgresto}->set_status($pref->{error}); + } } -sub list_job +# Handle prefs ok click (apply/dismiss dialog) +sub on_okbutton_clicked { my ($self) = @_; - return sort split(/;/, $self->{'list_job'}); -} + $self->on_applybutton_clicked(); -sub list_fileset + unless ($self->{pref}->{error}) { + $self->on_cancelbutton_clicked(); + } +} +sub on_dialog_delete_event { my ($self) = @_; - return sort split(/;/, $self->{'list_fileset'}); + $self->on_cancelbutton_clicked(); + 1; } -sub list_storage +sub on_cancelbutton_clicked { my ($self) = @_; - return sort split(/;/, $self->{'list_storage'}); + $self->{glade}->get_widget('dlg_pref')->hide(); + delete $self->{dlgresto}; } -sub list_client +1; + +################################################################ + +package DlgFileVersion; + +sub on_versions_close_clicked { - my ($self) = @_; - return sort split(/;/, $self->{'list_client'}); + my ($self, $widget)=@_; + $self->{version}->destroy(); +} + +sub on_selection_button_press_event +{ + print STDERR "on_selection_button_press_event()\n"; +} + +sub fileview_data_get +{ + my ($self, $widget, $context, $data, $info, $time,$string) = @_; + + DlgResto::drag_set_info($widget, + $self->{cwd}, + $data); +} + +sub new +{ + my ($class, $bvfs, $client, $path, $file, $cwd, $fn) = @_; + my $self = bless { + cwd => $cwd, + version => undef, # main window + }; + + # we load version widget of $glade_file + my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_version"); + + # Connect signals magically + $glade_box->signal_autoconnect_from_package($self); + + $glade_box->get_widget("version_label") + ->set_markup("File revisions : $client:$cwd$fn"); + + my $widget = $glade_box->get_widget('version_fileview'); + my $fileview = Gtk2::SimpleList->new_from_treeview( + $widget, + 'h_pathid' => 'hidden', + 'h_filenameid' => 'hidden', + 'h_name' => 'hidden', + 'h_jobid' => 'hidden', + 'h_type' => 'hidden', + + 'InChanger' => 'pixbuf', + 'Volume' => 'text', + 'JobId' => 'text', + 'Size' => 'text', + 'Date' => 'text', + 'MD5' => 'text', + ); + DlgResto::init_drag_drop($fileview); + + my @v = $bvfs->get_all_file_versions($path, + $file, + $client, + 1); + for my $ver (@v) { + my (undef,$pid,$fid,$jobid,$fileindex,$mtime,$size, + $inchanger,$md5,$volname) = @{$ver}; + my $icon = ($inchanger)?$DlgResto::yesicon:$DlgResto::noicon; + + DlgResto::listview_push($fileview,$pid,$fid, + $fn, $jobid, 'file', + $icon, $volname, $jobid,DlgResto::human($size), + scalar(localtime($mtime)), $md5); + } + + $self->{version} = $glade_box->get_widget('dlg_version'); + $self->{version}->show(); + + return $self; +} + +sub on_forward_keypress +{ + return 0; +} + +1; +################################################################ +package DlgWarn; + +sub new +{ + my ($package, $text) = @_; + + my $self = bless {}; + + my $glade = Gtk2::GladeXML->new($glade_file, "dlg_warn"); + + # Connect signals magically + $glade->signal_autoconnect_from_package($self); + $glade->get_widget('label_warn')->set_text($text); + + print STDERR "$text\n"; + + $self->{window} = $glade->get_widget('dlg_warn'); + $self->{window}->show_all(); + return $self; } +sub on_close_clicked +{ + my ($self) = @_; + $self->{window}->destroy(); +} 1; ################################################################ @@ -422,6 +644,8 @@ sub show_job my $ret = $self->{pref}->go_bweb("?action=dsp_cur_job;jobid=$jobid;client=$client", "view job status"); + $self->on_cancel_resto_clicked(); + if ($ret == -1) { my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'info', 'close', "Your job have been submited to bacula. @@ -429,8 +653,6 @@ To follow it, you must use bconsole (or install/configure bweb)"); $widget->run; $widget->destroy(); } - - $self->on_cancel_resto_clicked(); } sub on_cancel_resto_clicked @@ -539,6 +761,10 @@ sub copy_bsr print "$src => $dst\n" if ($debug); + if (!$dst) { + return $src; + } + my $ret=0 ; my $err ; my $dstfile; @@ -595,287 +821,36 @@ sub on_about_okbutton_clicked 1; ################################################################ -# preference reader -package Pref; - -sub new -{ - my ($class, $config_file) = @_; - - my $self = bless { - config_file => $config_file, - password => '', # db passwd - username => '', # db username - connection_string => '',# db connection string - bconsole => 'bconsole', # path and arg to bconsole - bsr_dest => '', # destination url for bsr files - debug => 0, # debug level 0|1 - use_ok_bkp_only => 1, # dont use bad backup - bweb => 'http://localhost/cgi-bin/bweb/bweb.pl', # bweb url - glade_file => $glade_file, - see_all_versions => 0, # display all file versions in FileInfo - mozilla => 'mozilla', # mozilla bin - default_restore_job => 'restore', # regular expression to select default - # restore job - # keywords that are used to fill DlgPref - chk_keyword => [ qw/use_ok_bkp_only debug see_all_versions/ ], - entry_keyword => [ qw/username password bweb mozilla - connection_string default_restore_job - bconsole bsr_dest glade_file/], - }; +package DlgResto; +use base qw/Bbase/; - $self->read_config(); +our $diricon; +our $fileicon; +our $yesicon; +our $noicon; - return $self; +# Kept as is from the perl-gtk example. Draws the pretty icons +sub render_icons +{ + my $self = shift; + unless ($diricon) { + my $size = 'button'; + $diricon = $self->{mainwin}->render_icon('gtk-open', $size); + $fileicon = $self->{mainwin}->render_icon('gtk-new', $size); + $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size); + $noicon = $self->{mainwin}->render_icon('gtk-no', $size); + } } - -sub read_config +# init combo (and create ListStore object) +sub init_combo { - my ($self) = @_; - - # We read the parameters. They come from the configuration files - my $cfgfile ; my $tmpbuffer; - if (open FICCFG, $self->{config_file}) - { - while(read FICCFG,$tmpbuffer,4096) - { - $cfgfile .= $tmpbuffer; - } - close FICCFG; - my $refparams; - no strict; # I have no idea of the contents of the file - eval '$refparams' . " = $cfgfile"; - use strict; - - for my $p (keys %{$refparams}) { - $self->{$p} = $refparams->{$p}; - } - - if (defined $self->{debug}) { - $debug = $self->{debug} ; - } - } else { - # TODO : Force dumb default values and display a message - } -} - -sub write_config -{ - my ($self) = @_; - - my %parameters; - - for my $k (@{ $self->{entry_keyword} }) { - $parameters{$k} = $self->{$k}; - } - - for my $k (@{ $self->{chk_keyword} }) { - $parameters{$k} = $self->{$k}; - } - - if (open FICCFG,">$self->{config_file}") - { - print FICCFG Data::Dumper->Dump([\%parameters], [qw($parameters)]); - close FICCFG; - } - else - { - # TODO : Display a message - } -} - -sub connect_db -{ - my $self = shift ; - - if ($self->{dbh}) { - $self->{dbh}->disconnect() ; - } - - delete $self->{dbh}; - delete $self->{error}; - - if (not $self->{connection_string}) - { - # The parameters have not been set. Maybe the conf - # file is empty for now - $self->{error} = "No configuration found for database connection. " . - "Please set this up."; - return 0; - } - - if (not eval { - $self->{dbh} = DBI->connect($self->{connection_string}, - $self->{username}, - $self->{password}) - }) - { - $self->{error} = "Can't open bacula database. " . - "Database connect string '" . - $self->{connection_string} ."' $!"; - return 0; - } - $self->{dbh}->{RowCacheSize}=100; - return 1; -} - -sub go_bweb -{ - my ($self, $url, $msg) = @_; - - unless ($self->{mozilla} and $self->{bweb}) { - new DlgWarn("You must install Bweb and set your mozilla bin to $msg"); - return -1; - } - - if ($^O eq 'MSWin32') { - system("start /B $self->{mozilla} \"$self->{bweb}$url\""); - - } elsif (!fork()) { - system("$self->{mozilla} -remote 'Ping()'"); - my $cmd = "$self->{mozilla} '$self->{bweb}$url'"; - if ($? == 0) { - $cmd = "$self->{mozilla} -remote 'OpenURL($self->{bweb}$url,new-tab)'" ; - } - exec($cmd); - exit 1; - } - return ($? == 0); -} - -1; - -################################################################ -# Manage preference -package DlgPref; - -# my $pref = new Pref(config_file => 'brestore.conf'); -# my $dlg = new DlgPref($pref); -# my $dlg_resto = new DlgResto($pref); -# $dlg->display($dlg_resto); -sub new -{ - my ($class, $pref) = @_; - - my $self = bless { - pref => $pref, # Pref ref - dlgresto => undef, # DlgResto ref - }; - - return $self; -} - -sub display -{ - my ($self, $dlgresto) = @_ ; - - unless ($self->{glade}) { - $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_pref") ; - $self->{glade}->signal_autoconnect_from_package($self); - } - - $self->{dlgresto} = $dlgresto; - - my $g = $self->{glade}; - my $p = $self->{pref}; - - for my $k (@{ $p->{entry_keyword} }) { - $g->get_widget("entry_$k")->set_text($p->{$k}) ; - } - - for my $k (@{ $p->{chk_keyword} }) { - $g->get_widget("chkbp_$k")->set_active($p->{$k}) ; - } - - $g->get_widget("dlg_pref")->show_all() ; -} - -sub on_applybutton_clicked -{ - my ($self) = @_; - my $glade = $self->{glade}; - my $pref = $self->{pref}; - - for my $k (@{ $pref->{entry_keyword} }) { - my $w = $glade->get_widget("entry_$k") ; - $pref->{$k} = $w->get_text(); - } - - for my $k (@{ $pref->{chk_keyword} }) { - my $w = $glade->get_widget("chkbp_$k") ; - $pref->{$k} = $w->get_active(); - } - - $pref->write_config(); - if ($pref->connect_db()) { - $self->{dlgresto}->set_dbh($pref->{dbh}); - $self->{dlgresto}->set_status('Preferences updated'); - $self->{dlgresto}->init_server_backup_combobox(); - $self->{dlgresto}->create_brestore_tables(); - $self->{dlgresto}->set_status($pref->{error}); - } else { - $self->{dlgresto}->set_status($pref->{error}); - } -} - -# Handle prefs ok click (apply/dismiss dialog) -sub on_okbutton_clicked -{ - my ($self) = @_; - $self->on_applybutton_clicked(); - - unless ($self->{pref}->{error}) { - $self->on_cancelbutton_clicked(); - } -} -sub on_dialog_delete_event -{ - my ($self) = @_; - $self->on_cancelbutton_clicked(); - 1; -} - -sub on_cancelbutton_clicked -{ - my ($self) = @_; - $self->{glade}->get_widget('dlg_pref')->hide(); - delete $self->{dlgresto}; -} -1; - -################################################################ -# Main Interface - -package DlgResto; - -our $diricon; -our $fileicon; -our $yesicon; -our $noicon; - -# Kept as is from the perl-gtk example. Draws the pretty icons -sub render_icons -{ - my $self = shift; - unless ($diricon) { - my $size = 'button'; - $diricon = $self->{mainwin}->render_icon('gtk-open', $size); - $fileicon = $self->{mainwin}->render_icon('gtk-new', $size); - $yesicon = $self->{mainwin}->render_icon('gtk-yes', $size); - $noicon = $self->{mainwin}->render_icon('gtk-no', $size); - } -} - -# init combo (and create ListStore object) -sub init_combo -{ - my ($widget, @type) = @_ ; - my %type_info = ('text' => 'Glib::String', - 'markup' => 'Glib::String', - ) ; - - my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type ); + my ($widget, @type) = @_ ; + my %type_info = ('text' => 'Glib::String', + 'markup' => 'Glib::String', + ) ; + + my $lst = new Gtk2::ListStore ( map { $type_info{$_} } @type ); $widget->set_model($lst); my $i=0; @@ -920,23 +895,49 @@ sub human return sprintf($format, $val, $unit[$i]); } -sub set_dbh +sub get_wanted_job_status { - my ($self, $dbh) = @_; - $self->{dbh} = $dbh; + my ($ok_only) = @_; + + if ($ok_only) { + return "'T'"; + } else { + return "'T', 'A', 'E'"; + } } -sub init_drag_drop +# This sub gives a full list of the EndTimes for a ClientId +# ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'], +# ['Date', 'FileSet', 'Type', 'Status', 'JobId']..) +sub get_all_endtimes_for_job { - my ($fileview) = shift; - my $fileview_target_entry = {target => 'STRING', - flags => ['GTK_TARGET_SAME_APP'], - info => 40 }; - - $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'], - ['copy'],$fileview_target_entry); - $fileview->get_selection->set_mode('multiple'); - + my ($self, $client, $ok_only)=@_; + my $status = get_wanted_job_status($ok_only); + my $query = " + SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId + FROM Job,Client,FileSet + WHERE Job.ClientId=Client.ClientId + AND Client.Name = '$client' + AND Job.Type = 'B' + AND JobStatus IN ($status) + AND Job.FileSetId = FileSet.FileSetId + ORDER BY EndTime desc"; + my $result = $self->dbh_selectall_arrayref($query); + + return @$result; +} + +sub init_drag_drop +{ + my ($fileview) = shift; + my $fileview_target_entry = {target => 'STRING', + flags => ['GTK_TARGET_SAME_APP'], + info => 40 }; + + $fileview->enable_model_drag_source(['button1_mask', 'button3_mask'], + ['copy'],$fileview_target_entry); + $fileview->get_selection->set_mode('multiple'); + # set some useful SimpleList properties $fileview->set_headers_clickable(0); foreach ($fileview->get_columns()) @@ -946,52 +947,11 @@ sub init_drag_drop } } -sub debug -{ - my ($self, $what) = @_; - - if ($debug) { - if (ref $what) { - print Data::Dumper::Dumper($what); - } elsif (defined $what) { - print "$what\n"; - } - } -} - -sub dbh_prepare -{ - my ($self, $query) = @_; - $self->debug($query); - return $self->{dbh}->prepare($query); -} - -sub dbh_do -{ - my ($self, $query) = @_; - $self->debug($query); - return $self->{dbh}->do($query); -} - -sub dbh_selectall_arrayref -{ - my ($self, $query) = @_; - $self->debug($query); - return $self->{dbh}->selectall_arrayref($query); -} - -sub dbh_selectrow_arrayref -{ - my ($self, $query) = @_; - $self->debug($query); - return $self->{dbh}->selectrow_arrayref($query); -} - sub new { my ($class, $pref) = @_; my $self = bless { - pref => $pref, + conf => $pref, dirtree => undef, CurrentJobIds => [], location => undef, # location entry widget @@ -1009,9 +969,12 @@ sub new restore_backup_combobox => undef, # date combobox widget list_client => undef, # Gtk2::ListStore list_backup => undef, # Gtk2::ListStore - cache_ppathid => {}, # + cache_ppathid => {}, # + bvfs => undef, }; + $self->{bvfs} = new Bvfs(conf => $pref); + # load menu (to use handler with self reference) my $glade = Gtk2::GladeXML->new($glade_file, "filelist_file_menu"); $glade->signal_autoconnect_from_package($self); @@ -1046,6 +1009,8 @@ sub new my $widget = $glade->get_widget('fileview'); my $fileview = $self->{fileview} = Gtk2::SimpleList->new_from_treeview( $widget, + 'h_pathid' => 'hidden', + 'h_filenameid' => 'hidden', 'h_name' => 'hidden', 'h_jobid' => 'hidden', 'h_type' => 'hidden', @@ -1055,25 +1020,27 @@ sub new 'Size' => 'text', 'Date' => 'text'); init_drag_drop($fileview); - $fileview->set_search_column(4); # search on File Name + $fileview->set_search_column(6); # search on File Name # Connect glade-restore_list to Gtk2::SimpleList $widget = $glade->get_widget('restorelist'); my $restore_list = $self->{restore_list} = Gtk2::SimpleList->new_from_treeview( $widget, + 'h_pathid' => 'hidden', #0 + 'h_filenameid' => 'hidden', 'h_name' => 'hidden', 'h_jobid' => 'hidden', 'h_type' => 'hidden', - 'h_curjobid' => 'hidden', + 'h_curjobid' => 'hidden', #5 '' => 'pixbuf', 'File Name' => 'text', 'JobId' => 'text', 'FileIndex' => 'text', - 'Nb Files' => 'text', #8 - 'Size' => 'text', #9 - 'size_b' => 'hidden', #10 + 'Nb Files' => 'text', #10 + 'Size' => 'text', #11 + 'size_b' => 'hidden', #12 ); my @restore_list_target_table = ({'target' => 'STRING', @@ -1086,6 +1053,8 @@ sub new $widget = $glade->get_widget('infoview'); my $infoview = $self->{fileinfo} = Gtk2::SimpleList->new_from_treeview( $widget, + 'h_pathid' => 'hidden', + 'h_filenameid' => 'hidden', 'h_name' => 'hidden', 'h_jobid' => 'hidden', 'h_type' => 'hidden', @@ -1102,9 +1071,8 @@ sub new $pref->connect_db() || $self->{dlg_pref}->display($self); if ($pref->{dbh}) { - $self->{dbh} = $pref->{dbh}; $self->init_server_backup_combobox(); - $self->create_brestore_tables(); + $self->{bvfs}->create_brestore_tables(); } $self->set_status($pref->{error}); @@ -1144,40 +1112,6 @@ sub get_all_clients return map { $_->[0] } @$result; } -sub get_wanted_job_status -{ - my ($ok_only) = @_; - - if ($ok_only) { - return "'T'"; - } else { - return "'T', 'A', 'E'"; - } -} - -# This sub gives a full list of the EndTimes for a ClientId -# ( [ 'Date', 'FileSet', 'Type', 'Status', 'JobId'], -# ['Date', 'FileSet', 'Type', 'Status', 'JobId']..) -sub get_all_endtimes_for_job -{ - my ($dbh, $client, $ok_only)=@_; - my $status = get_wanted_job_status($ok_only); - my $query = " - SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId - FROM Job,Client,FileSet - WHERE Job.ClientId=Client.ClientId - AND Client.Name = '$client' - AND Job.Type = 'B' - AND JobStatus IN ($status) - AND Job.FileSetId = FileSet.FileSetId - ORDER BY EndTime desc"; - print STDERR $query,"\n" if $debug; - my $result = $dbh->selectall_arrayref($query); - - return @$result; -} - - # init infoview widget sub clear_infoview { @@ -1209,22 +1143,26 @@ sub on_estimate_clicked my $title = "Computing size...\n"; my $txt=""; + + # ($pid,$fid,$name,$jobid,'file',$curjobids, + # undef, undef, undef, $dirfileindex); foreach my $entry (@{$self->{restore_list}->{data}}) { - unless ($entry->[9]) { - my ($size, $nb) = $self->estimate_restore_size($entry); - $entry->[10] = $size; - $entry->[9] = human($size); - $entry->[8] = $nb; + unless ($entry->[11]) { + my ($size, $nb) = $self->{bvfs}->estimate_restore_size($entry,\&refresh_screen); + $entry->[12] = $size; + $entry->[11] = human($size); + $entry->[10] = $nb; } - my $name = unpack('u', $entry->[0]); + my $name = unpack('u', $entry->[2]); - $txt .= "\n$name : " . $entry->[8] . " file(s)/" . $entry->[9] ; + $txt .= "\n$name : ". $entry->[10] ." file(s)/". $entry->[11] ; + $self->debug($title . $txt); $widget->set_markup($title . $txt); - $size_total+=$entry->[10]; - $nb_total+=$entry->[8]; + $size_total+=$entry->[12]; + $nb_total+=$entry->[10]; refresh_screen(); } @@ -1235,6 +1173,8 @@ sub on_estimate_clicked return 0; } + + sub on_gen_bsr_clicked { my ($self) = @_; @@ -1283,6 +1223,7 @@ sub on_gen_bsr_clicked } } + use File::Temp qw/tempfile/; sub on_go_button_clicked @@ -1305,13 +1246,14 @@ sub on_go_button_clicked my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g); my $vol = [ keys %a ] ; # need only one occurrence of each volume - new DlgLaunch(pref => $self->{pref}, + new DlgLaunch(pref => $self->{conf}, volumes => $vol, bsr_file => $filename, ); } + our $client_list_empty = 'Clients list'; our %type_markup = ('F' => '$label F', 'D' => '$label D', @@ -1327,7 +1269,6 @@ sub on_list_client_changed { my ($self, $widget) = @_; return 0 unless defined $self->{fileview}; - my $dbh = $self->{dbh}; $self->{list_backup}->clear(); @@ -1335,9 +1276,22 @@ sub on_list_client_changed return 0 ; } - my @endtimes=get_all_endtimes_for_job($dbh, - $self->current_client, - $self->{pref}->{use_ok_bkp_only}); + $self->{CurrentJobIds} = [ + set_job_ids_for_date($self->dbh(), + $self->current_client, + $self->current_date, + $self->{pref}->{use_ok_bkp_only}) + ]; + + my $fs = $self->{bvfs}; + $fs->set_curjobids(@{$self->{CurrentJobIds}}); + $fs->ch_dir($fs->get_root()); + # refresh_fileview will be done by list_backup_changed + + + my @endtimes=$self->get_all_endtimes_for_job($self->current_client, + $self->{pref}->{use_ok_bkp_only}); + foreach my $endtime (@endtimes) { my $i = $self->{list_backup}->append(); @@ -1352,21 +1306,10 @@ sub on_list_client_changed ); } $self->{restore_backup_combobox}->set_active(0); - - $self->{CurrentJobIds} = [ - set_job_ids_for_date($dbh, - $self->current_client, - $self->current_date, - $self->{pref}->{use_ok_bkp_only}) - ]; - - $self->update_brestore_table(@{$self->{CurrentJobIds}}); - - $self->ch_dir(''); - $self->refresh_fileview(); 0; } + sub fill_server_list { my ($dbh, $combo, $list) = @_; @@ -1389,7 +1332,7 @@ sub fill_server_list sub init_server_backup_combobox { my $self = shift ; - fill_server_list($self->{dbh}, + fill_server_list($self->{conf}->{dbh}, $self->{client_combobox}, $self->{list_client}) ; } @@ -1403,7 +1346,7 @@ sub refresh_fileview my ($self) = @_; my $fileview = $self->{fileview}; my $client_combobox = $self->{client_combobox}; - my $cwd = $self->{cwd}; + my $bvfs = $self->{bvfs}; @{$fileview->{data}} = (); @@ -1416,29 +1359,30 @@ sub refresh_fileview return; } - my @list_dirs = $self->list_dirs($cwd,$client_name); - # [ [listfiles.id, listfiles.Name, File.LStat, File.JobId]..] - my $files = $self->list_files($cwd); - print "CWD : $cwd\n" if ($debug); + # [ [dirid, dir_basename, File.LStat, jobid]..] + my $list_dirs = $bvfs->ls_dirs(); + # [ [filenameid, listfiles.id, listfiles.Name, File.LStat, File.JobId]..] + my $files = $bvfs->ls_files(); my $file_count = 0 ; my $total_bytes = 0; # Add directories to view - foreach my $dir_entry (@list_dirs) { - #my $time = localtime($self->dir_attrib("$cwd/$dir",'st_mtime')); - my $time = localtime(lstat_attrib($dir_entry->[1],'st_mtime')); - my $dir = $dir_entry->[0]; + foreach my $dir_entry (@$list_dirs) { + my $time = localtime(Bvfs::lstat_attrib($dir_entry->[2],'st_mtime')); $total_bytes += 4096; $file_count++; listview_push($fileview, - $dir, - $self->dir_attrib("$cwd/$dir",'jobid'), + $dir_entry->[0], + 0, + $dir_entry->[1], + # TODO: voir ce que l'on met la + $dir_entry->[3], 'dir', $diricon, - $dir, + $dir_entry->[1], "4 Kb", $time); } @@ -1446,24 +1390,26 @@ sub refresh_fileview # Add files to view foreach my $file (@$files) { - my $size = file_attrib($file,'st_size'); - my $time = localtime(file_attrib($file,'st_mtime')); + my $size = Bvfs::file_attrib($file,'st_size'); + my $time = localtime(Bvfs::file_attrib($file,'st_mtime')); $total_bytes += $size; $file_count++; - # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId] - + # $file = [filenameid,listfiles.id,listfiles.Name,File.LStat,File.JobId] listview_push($fileview, - $file->[1], - $file->[3], + $bvfs->{cwd}, + $file->[0], + $file->[2], + $file->[4], 'file', $fileicon, - $file->[1], + $file->[2], human($size), $time); } $self->set_status("$file_count files/" . human($total_bytes)); - + $self->{cwd} = $self->{bvfs}->pwd(); + $self->{location}->set_text($self->{cwd}); # set a decent default selection (makes keyboard nav easy) $fileview->select(0); } @@ -1487,18 +1433,13 @@ sub drag_set_info # doc ligne 93 # Ok, we have a corner case : # path can be empty - my $file; - if ($path eq '') - { - $file = pack("u", $file_info[0]); - } - else - { - $file = pack("u", $path . '/' . $file_info[0]); - } - push @ret, join(" ; ", $file, - $file_info[1], # $jobid - $file_info[2], # $type + my $file = pack("u", $path . $file_info[2]); + + push @ret, join(" ; ", $file, + $file_info[0], # $pathid + $file_info[1], # $filenameid + $file_info[3], # $jobid + $file_info[4], # $type ); } @@ -1507,6 +1448,8 @@ sub drag_set_info $data->set_text($data_get,-1); } + + sub fileview_data_get { my ($self, $widget, $context, $data, $info, $time,$string) = @_; @@ -1523,24 +1466,26 @@ sub restore_list_data_received { my ($self, $widget, $context, $x, $y, $data, $info, $time) = @_; my @ret; - + $self->debug("start\n"); if ($info eq 40 || $info eq 0) # patch for display!=:0 { foreach my $elt (split(/ :: /, $data->data())) { - - my ($file, $jobid, $type) = + my ($file, $pathid, $filenameid, $jobid, $type) = split(/ ; /, $elt); $file = unpack("u", $file); - $self->add_selected_file_to_list($file, $jobid, $type); + $self->add_selected_file_to_list($pathid,$filenameid, + $file, $jobid, $type); } } + $self->debug("end\n"); } sub on_back_button_clicked { my $self = shift; - $self->up_dir(); + $self->{bvfs}->up_dir(); + $self->refresh_fileview(); } sub on_location_go_button_clicked { @@ -1561,30 +1506,20 @@ sub on_bweb_activate $self->{pref}->go_bweb('', "go on bweb"); } -# Change to parent directory -sub up_dir -{ - my $self = shift ; - if ($self->{cwd} eq '/') - { - $self->ch_dir(''); - } - my @dirs = split(/\//, $self->{cwd}); - pop @dirs; - $self->ch_dir(join('/', @dirs)); -} - # Change the current working directory # * Updates fileview, location, and selection # sub ch_dir { - my $self = shift; - $self->{cwd} = shift; - - $self->refresh_fileview(); - $self->{location}->set_text($self->{cwd}); - + my ($self,$l) = @_; + + my $p = $self->{bvfs}->get_pathid($l); + if ($p) { + $self->{bvfs}->ch_dir($p); + $self->refresh_fileview(); + } else { + $self->set_status("Can't find $l"); + } 1; } @@ -1632,8 +1567,8 @@ sub listview_get_first my ($list) = shift; my @selected = $list->get_selected_indices(); if (@selected > 0) { - my ($name, @other) = @{$list->{data}->[$selected[0]]}; - return (unpack('u', $name), @other); + my ($pid,$fid,$name, @other) = @{$list->{data}->[$selected[0]]}; + return ($pid,$fid,unpack('u', $name), @other); } else { return undef; } @@ -1646,20 +1581,19 @@ sub listview_get_all my @selected = $list->get_selected_indices(); my @ret; for my $i (@selected) { - my ($name, @other) = @{$list->{data}->[$i]}; - push @ret, [unpack('u', $name), @other]; + my ($pid,$fid,$name, @other) = @{$list->{data}->[$i]}; + push @ret, [$pid,$fid,unpack('u', $name), @other]; } return @ret; } - sub listview_push { - my ($list, $name, @other) = @_; - push @{$list->{data}}, [pack('u', $name), @other]; + my ($list, $pid, $fid, $name, @other) = @_; + push @{$list->{data}}, [$pid,$fid,pack('u', $name), @other]; } -#---------------------------------------------------------------------- +#----------------------------------------------------------------- # Handle keypress in file-view # * Translates backspace into a 'cd ..' command # * All other key presses left for GTK @@ -1672,7 +1606,7 @@ sub on_fileview_key_release_event return 0; } if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) { - $self->up_dir(); + $self->on_back_button_clicked(); return 1; # eat keypress } @@ -1684,7 +1618,7 @@ sub on_forward_keypress return 0; } -#---------------------------------------------------------------------- +#------------------------------------------------------------------- # Handle double-click (or enter) on file-view # * Translates into a 'cd ' command # @@ -1692,25 +1626,14 @@ sub on_fileview_row_activated { my ($self, $widget) = @_; - my ($name, undef, $type, undef) = listview_get_first($widget); + my ($pid,$fid,$name, undef, $type, undef) = listview_get_first($widget); if ($type eq 'dir') { - if ($self->{cwd} eq '') - { - $self->ch_dir($name); - } - elsif ($self->{cwd} eq '/') - { - $self->ch_dir('/' . $name); - } - else - { - $self->ch_dir($self->{cwd} . '/' . $name); - } - + $self->{bvfs}->ch_dir($pid); + $self->refresh_fileview(); } else { - $self->fill_infoview($self->{cwd}, $name); + $self->fill_infoview($pid,$fid,$name); } return 1; # consume event @@ -1718,22 +1641,21 @@ sub on_fileview_row_activated sub fill_infoview { - my ($self, $path, $file) = @_; + my ($self, $path, $file, $fn) = @_; $self->clear_infoview(); - my @v = get_all_file_versions($self->{dbh}, - "$path/", - $file, - $self->current_client, - $self->{pref}->{see_all_versions}); + my @v = $self->{bvfs}->get_all_file_versions($path, + $file, + $self->current_client, + $self->{pref}->{see_all_versions}); for my $ver (@v) { - my (undef,$fn,$jobid,$fileindex,$mtime,$size,$inchanger,$md5,$volname) - = @{$ver}; + my (undef,$pid,$fid,$jobid,$fileindex,$mtime, + $size,$inchanger,$md5,$volname) = @{$ver}; my $icon = ($inchanger)?$yesicon:$noicon; $mtime = localtime($mtime) ; - listview_push($self->{fileinfo}, - $file, $jobid, 'file', + listview_push($self->{fileinfo},$pid,$fid, + $fn, $jobid, 'file', $icon, $volname, $jobid, human($size), $mtime, $md5); } } @@ -1756,13 +1678,12 @@ sub on_list_backups_changed return 0 unless defined $self->{fileview}; $self->{CurrentJobIds} = [ - set_job_ids_for_date($self->{dbh}, + set_job_ids_for_date($self->dbh(), $self->current_client, $self->current_date, $self->{pref}->{use_ok_bkp_only}) ]; - $self->update_brestore_table(@{$self->{CurrentJobIds}}); - + $self->{bvfs}->set_curjobids(@{$self->{CurrentJobIds}}); $self->refresh_fileview(); 0; } @@ -1805,11 +1726,11 @@ sub on_see_all_version my @lst = listview_get_all($self->{fileview}); for my $i (@lst) { - my ($name, undef) = @{$i}; + my ($pid,$fid,$name, undef) = @{$i}; - new DlgFileVersion($self->{dbh}, + new DlgFileVersion($self->{bvfs}, $self->current_client, - $self->{cwd}, $name); + $pid,$fid,$self->{cwd},$name); } } @@ -1822,7 +1743,7 @@ sub on_right_click_filelist my $type = ''; if (@sel == 1) { - $type = $sel[0]->[2]; # $type + $type = $sel[0]->[4]; # $type } my $w; @@ -1851,18 +1772,17 @@ sub context_add_to_filelist foreach my $i (@sel) { - my ($file, $jobid, $type, undef) = @{$i}; + my ($pid, $fid, $file, $jobid, $type, undef) = @{$i}; $file = $self->{cwd} . '/' . $file; - $self->add_selected_file_to_list($file, $jobid, $type); + $self->add_selected_file_to_list($pid, $fid, $file, $jobid, $type); } } # Adds a file to the filelist sub add_selected_file_to_list { - my ($self, $name, $jobid, $type)=@_; + my ($self, $pid, $fid, $name, $jobid, $type)=@_; - my $dbh = $self->{dbh}; my $restore_list = $self->{restore_list}; my $curjobids=join(',', @{$self->{CurrentJobIds}}); @@ -1876,555 +1796,94 @@ sub add_selected_file_to_list { $name .= '/'; # For bacula } - my $dirfileindex = get_fileindex_from_dir_jobid($dbh,$name,$jobid); - listview_push($restore_list, + my $dirfileindex = $self->{bvfs}->get_fileindex_from_dir_jobid($pid,$jobid); + listview_push($restore_list,$pid,0, $name, $jobid, 'dir', $curjobids, - $diricon, $name,$curjobids,$dirfileindex); - } - elsif ($type eq 'file') - { - my $fileindex = get_fileindex_from_file_jobid($dbh,$name,$jobid); - - listview_push($restore_list, - $name, $jobid, 'file', $curjobids, - $fileicon, $name, $jobid, $fileindex ); - } -} - -# TODO : we want be able to restore files from a bad ended backup -# we have JobStatus IN ('T', 'A', 'E') and we must - -# Data acces subs from here. Interaction with SGBD and caching - -# This sub retrieves the list of jobs corresponding to the jobs selected in the -# GUI and stores them in @CurrentJobIds -sub set_job_ids_for_date -{ - my ($dbh, $client, $date, $only_ok)=@_; - - if (!$client or !$date) { - return (); - } - - my $status = get_wanted_job_status($only_ok); - - # The algorithm : for a client, we get all the backups for each - # fileset, in reverse order Then, for each fileset, we store the 'good' - # incrementals and differentials until we have found a full so it goes - # like this : store all incrementals until we have found a differential - # or a full, then find the full # - - my $query = "SELECT JobId, FileSet, Level, JobStatus - FROM Job, Client, FileSet - WHERE Job.ClientId = Client.ClientId - AND FileSet.FileSetId = Job.FileSetId - AND EndTime <= '$date' - AND Client.Name = '$client' - AND Type IN ('B') - AND JobStatus IN ($status) - ORDER BY FileSet, JobTDate DESC"; - - print STDERR $query,"\n" if $debug; - my @CurrentJobIds; - my $result = $dbh->selectall_arrayref($query); - my %progress; - foreach my $refrow (@$result) - { - my $jobid = $refrow->[0]; - my $fileset = $refrow->[1]; - my $level = $refrow->[2]; - - defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown - - next if $progress{$fileset} eq 'F'; # It's over for this fileset... - - if ($level eq 'I') - { - next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I'); - push @CurrentJobIds,($jobid); - } - elsif ($level eq 'D') - { - next if $progress{$fileset} eq 'D'; # We allready have a differential - push @CurrentJobIds,($jobid); - } - elsif ($level eq 'F') - { - push @CurrentJobIds,($jobid); - } - - my $status = $refrow->[3] ; - if ($status eq 'T') { # good end of job - $progress{$fileset} = $level; - } - } - print Data::Dumper::Dumper(\@CurrentJobIds) if $debug; - - return @CurrentJobIds; -} - -# Lists all directories contained inside a directory. -# Uses the current dir, the client name, and CurrentJobIds for visibility. -# Returns an array of dirs -sub list_dirs -{ - my ($self,$dir,$client)=@_; - - print "list_dirs(<$dir>, <$client>)\n" if $debug; - - if ($dir ne '' and substr $dir,-1 ne '/') - { - $dir .= '/'; # In the db, there is a / at the end of the dirs ... - } - - my $dbh = $self->{dbh}; - my $query = "SELECT PathId FROM Path WHERE Path = ? - UNION SELECT PathId FROM brestore_missing_path WHERE PATH = ?"; - my $sth = $dbh->prepare($query); - $sth->execute($dir,$dir); - my $result = $sth->fetchrow_arrayref(); - $sth->finish(); - my $pathid = $result->[0]; - my @jobids = @{$self->{CurrentJobIds}}; - my $jobclause = join (',', @jobids); - # Let's retrieve the list of the visible dirs in this dir ... - # First, I need the empty filenameid to locate efficiently the dirs in the file table - $query = "SELECT FilenameId FROM Filename WHERE Name = ''"; - $sth = $dbh->prepare($query); - $sth->execute(); - $result = $sth->fetchrow_arrayref(); - $sth->finish(); - my $dir_filenameid = $result->[0]; - - # Then we get all the dir entries from File ... - # It's ugly because there are records in brestore_missing_path ... - $query = " -SELECT Path, JobId, Lstat FROM( - ( - SELECT Path.Path, lower(Path.Path), - listfile.JobId, listfile.Lstat - FROM ( - SELECT DISTINCT brestore_pathhierarchy.PathId - FROM brestore_pathhierarchy - JOIN Path - ON (brestore_pathhierarchy.PathId = Path.PathId) - JOIN brestore_pathvisibility - ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId) - WHERE brestore_pathhierarchy.PPathId = $pathid - AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath - JOIN Path ON (listpath.PathId = Path.PathId) - LEFT JOIN ( - SELECT File.PathId, File.JobId, File.Lstat FROM File - WHERE File.FilenameId = $dir_filenameid - AND File.JobId IN ($jobclause)) AS listfile - ON (listpath.PathId = listfile.PathId) - UNION - SELECT brestore_missing_path.Path, lower(brestore_missing_path.Path), - listfile.JobId, listfile.Lstat - FROM ( - SELECT DISTINCT brestore_pathhierarchy.PathId - FROM brestore_pathhierarchy - JOIN brestore_missing_path - ON (brestore_pathhierarchy.PathId = brestore_missing_path.PathId) - JOIN brestore_pathvisibility - ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId) - WHERE brestore_pathhierarchy.PPathId = $pathid - AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath - JOIN brestore_missing_path ON (listpath.PathId = brestore_missing_path.PathId) - LEFT JOIN ( - SELECT File.PathId, File.JobId, File.Lstat FROM File - WHERE File.FilenameId = $dir_filenameid - AND File.JobId IN ($jobclause)) AS listfile - ON (listpath.PathId = listfile.PathId)) -ORDER BY 2,3 DESC ) As a"; - print STDERR "$query\n" if $debug; - $sth=$dbh->prepare($query); - $sth->execute(); - $result = $sth->fetchall_arrayref(); - my @return_list; - my $prev_dir=''; - foreach my $refrow (@{$result}) - { - my $dir = $refrow->[0]; - my $jobid = $refrow->[1]; - my $lstat = $refrow->[2]; - next if ($dir eq $prev_dir); - # We have to clean up this dirname ... we only want it's 'basename' - my $return_value; - if ($dir ne '/') - { - my @temp = split ('/',$dir); - $return_value = pop @temp; - } - else - { - $return_value = '/'; - } - my @return_array = ($return_value,$lstat); - push @return_list,(\@return_array); - $prev_dir = $dir; - } - return @return_list; -} - - -# List all files in a directory. dir as parameter, CurrentJobIds for visibility -# Returns an array of dirs -sub list_files -{ - my ($self, $dir)=@_; - my $dbh = $self->{dbh}; - - my $empty = []; - - print "list_files($dir)\n" if $debug; - - if ($dir ne '' and substr $dir,-1 ne '/') - { - $dir .= '/'; # In the db, there is a / at the end of the dirs ... - } - - my $query = "SELECT Path.PathId - FROM Path - WHERE Path.Path = '$dir' - UNION - SELECT brestore_missing_path.PathId - FROM brestore_missing_path - WHERE brestore_missing_path.Path = '$dir'"; - print $query,"\n" if $debug; - my @list_pathid=(); - my $result = $dbh->selectall_arrayref($query); - foreach my $refrow (@$result) - { - push @list_pathid,($refrow->[0]); - } - - if (@list_pathid == 0) - { - print "No pathid found for $dir\n" if $debug; - return $empty; - } - - my $inlistpath = join (',', @list_pathid); - my $inclause = join (',', @{$self->{CurrentJobIds}}); - if ($inclause eq '') - { - return $empty; - } - - $query = -"SELECT listfiles.id, listfiles.Name, File.LStat, File.JobId - FROM - (SELECT Filename.Name, max(File.FileId) as id - FROM File, Filename - WHERE File.FilenameId = Filename.FilenameId - AND Filename.Name != '' - AND File.PathId IN ($inlistpath) - AND File.JobId IN ($inclause) - GROUP BY Filename.Name - ORDER BY Filename.Name) AS listfiles, -File -WHERE File.FileId = listfiles.id"; - - print STDERR $query,"\n" if $debug; - $result = $dbh->selectall_arrayref($query); - - return $result; -} - -sub refresh_screen -{ - Gtk2->main_iteration while (Gtk2->events_pending); -} - -sub create_brestore_tables -{ - my ($self) = @_; - - my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1"; - - unless ($self->dbh_do($verif)) { - new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them."); - - $self->{error} = "Creating internal brestore tables"; - my $req = " - CREATE TABLE brestore_knownjobid - ( - JobId int4 NOT NULL, - CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId) - )"; - $self->dbh_do($req); - } - - $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1"; - unless ($self->dbh_do($verif)) { - my $req = " - CREATE TABLE brestore_pathhierarchy - ( - PathId int4 NOT NULL, - PPathId int4 NOT NULL, - CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId) - )"; - $self->dbh_do($req); - - - $req = "CREATE INDEX brestore_pathhierarchy_ppathid - ON brestore_pathhierarchy (PPathId)"; - $self->dbh_do($req); - } - - $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1"; - unless ($self->dbh_do($verif)) { - my $req = " - CREATE TABLE brestore_pathvisibility - ( - PathId int4 NOT NULL, - JobId int4 NOT NULL, - Size int8 DEFAULT 0, - Files int4 DEFAULT 0, - CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId) - )"; - $self->dbh_do($req); - - $req = "CREATE INDEX brestore_pathvisibility_jobid - ON brestore_pathvisibility (JobId)"; - $self->dbh_do($req); - } - - $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1"; - unless ($self->dbh_do($verif)) { - my $req = " - CREATE TABLE brestore_missing_path - ( - PathId int4 NOT NULL, - Path text NOT NULL, - CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId) - )"; - $self->dbh_do($req); - - $req = "CREATE INDEX brestore_missing_path_path - ON brestore_missing_path (Path)"; - $self->dbh_do($req); - } -} - -# Recursive function to calculate the visibility of each directory in the cache -# tree Working with references to save time and memory -# For each directory, we want to propagate it's visible jobids onto it's -# parents directory. -# A tree is visible if -# - it's been in a backup pointed by the CurrentJobIds -# - one of it's subdirs is in a backup pointed by the CurrentJobIds -# In the second case, the directory is visible but has no metadata. -# We symbolize this with lstat = 1 for this jobid in the cache. - -# Input : reference directory -# Output : visibility of this dir. Has to know visibility of all subdirs -# to know it's visibility, hence the recursing. -sub list_visible -{ - my ($refdir)=@_; - - my %visibility; - # Get the subdirs array references list - my @list_ref_subdirs; - while( my (undef,$ref_subdir) = each (%{$refdir->[0]})) - { - push @list_ref_subdirs,($ref_subdir); - } - - # Now lets recurse over these subdirs and retrieve the reference of a hash - # containing the jobs where they are visible - foreach my $ref_subdir (@list_ref_subdirs) - { - my $ref_list_jobs = list_visible($ref_subdir); - foreach my $jobid (keys %$ref_list_jobs) - { - $visibility{$jobid}=1; - } - } - - # Ok. Now, we've got the list of those jobs. We are going to update our - # hash (element 1 of the dir array) containing our jobs Do NOT overwrite - # the lstat for the known jobids. Put 1 in the new elements... But first, - # let's store the current jobids - my @known_jobids; - foreach my $jobid (keys %{$refdir->[1]}) - { - push @known_jobids,($jobid); - } - - # Add the new jobs - foreach my $jobid (keys %visibility) - { - next if ($refdir->[1]->{$jobid}); - $refdir->[1]->{$jobid} = 1; + $diricon, $name,$curjobids,$dirfileindex); } - # Add the known_jobids to %visibility - foreach my $jobid (@known_jobids) + elsif ($type eq 'file') { - $visibility{$jobid}=1; + my $fileindex = $self->{bvfs}->get_fileindex_from_file_jobid($pid,$fid,$jobid); + + listview_push($restore_list,$pid,$fid, + $name, $jobid, 'file', $curjobids, + $fileicon, $name, $jobid, $fileindex ); } - return \%visibility; } -# Returns the list of media required for a list of jobids. -# Input : dbh, jobid1, jobid2... -# Output : reference to array of (joibd, inchanger) -sub get_required_media_from_jobid -{ - my ($dbh, @jobids)=@_; - my $inclause = join(',',@jobids); - my $query = " -SELECT DISTINCT JobMedia.MediaId, Media.InChanger -FROM JobMedia, Media -WHERE JobMedia.MediaId=Media.MediaId -AND JobId In ($inclause) -ORDER BY MediaId"; - my $result = $dbh->selectall_arrayref($query); - return $result; -} +# TODO : we want be able to restore files from a bad ended backup +# we have JobStatus IN ('T', 'A', 'E') and we must -# Returns the fileindex from dirname and jobid. -# Input : dbh, dirname, jobid -# Output : fileindex -sub get_fileindex_from_dir_jobid -{ - my ($dbh, $dirname, $jobid)=@_; - my $query; - $query = "SELECT File.FileIndex - FROM File, Filename, Path - WHERE File.FilenameId = Filename.FilenameId - AND File.PathId = Path.PathId - AND Filename.Name = '' - AND Path.Path = '$dirname' - AND File.JobId = '$jobid' - "; - - print STDERR $query,"\n" if $debug; - my $result = $dbh->selectall_arrayref($query); - return $result->[0]->[0]; -} +# Data acces subs from here. Interaction with SGBD and caching -# Returns the fileindex from filename and jobid. -# Input : dbh, filename, jobid -# Output : fileindex -sub get_fileindex_from_file_jobid +# This sub retrieves the list of jobs corresponding to the jobs selected in the +# GUI and stores them in @CurrentJobIds +sub set_job_ids_for_date { - my ($dbh, $filename, $jobid)=@_; - - my @dirs = split(/\//, $filename); - $filename=pop(@dirs); - my $dirname = join('/', @dirs) . '/'; - - - my $query; - $query = -"SELECT File.FileIndex - FROM File, Filename, Path - WHERE File.FilenameId = Filename.FilenameId - AND File.PathId = Path.PathId - AND Filename.Name = '$filename' - AND Path.Path = '$dirname' - AND File.JobId = '$jobid'"; - - print STDERR $query,"\n" if $debug; - my $result = $dbh->selectall_arrayref($query); - return $result->[0]->[0]; -} - + my ($dbh, $client, $date, $only_ok)=@_; -# Returns list of versions of a file that could be restored -# returns an array of -# ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname) -# It's the same as entries of restore_list (hidden) + mtime and size and inchanger -# and volname and md5 -# and of course, there will be only one jobid in the array of jobids... -sub get_all_file_versions -{ - my ($dbh,$path,$file,$client,$see_all)=@_; - - defined $see_all or $see_all=0; + if (!$client or !$date) { + return (); + } - my @versions; - my $query; - $query = -"SELECT File.JobId, File.FileIndex, File.Lstat, - File.Md5, Media.VolumeName, Media.InChanger - FROM File, Filename, Path, Job, Client, JobMedia, Media - WHERE File.FilenameId = Filename.FilenameId - AND File.PathId=Path.PathId - AND File.JobId = Job.JobId - AND Job.ClientId = Client.ClientId - AND Job.JobId = JobMedia.JobId - AND File.FileIndex >= JobMedia.FirstIndex - AND File.FileIndex <= JobMedia.LastIndex - AND JobMedia.MediaId = Media.MediaId - AND Path.Path = '$path' - AND Filename.Name = '$file' - AND Client.Name = '$client'"; + my $status = get_wanted_job_status($only_ok); - print STDERR $query if $debug; + # The algorithm : for a client, we get all the backups for each + # fileset, in reverse order Then, for each fileset, we store the 'good' + # incrementals and differentials until we have found a full so it goes + # like this : store all incrementals until we have found a differential + # or a full, then find the full # + + my $query = "SELECT JobId, FileSet, Level, JobStatus + FROM Job, Client, FileSet + WHERE Job.ClientId = Client.ClientId + AND FileSet.FileSetId = Job.FileSetId + AND EndTime <= '$date' + AND Client.Name = '$client' + AND Type IN ('B') + AND JobStatus IN ($status) + ORDER BY FileSet, JobTDate DESC"; + my @CurrentJobIds; my $result = $dbh->selectall_arrayref($query); - + my %progress; foreach my $refrow (@$result) { - my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow; - my @attribs = parse_lstat($lstat); - my $mtime = array_attrib('st_mtime',\@attribs); - my $size = array_attrib('st_size',\@attribs); + my $jobid = $refrow->[0]; + my $fileset = $refrow->[1]; + my $level = $refrow->[2]; - my @list = ('FILE:', $path.$file, $jobid, $fileindex, $mtime, $size, - $inchanger, $md5, $volname); - push @versions, (\@list); - } - - # We have the list of all versions of this file. - # We'll sort it by mtime desc, size, md5, inchanger desc - # the rest of the algorithm will be simpler - # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname) - @versions = sort { $b->[4] <=> $a->[4] - || $a->[5] <=> $b->[5] - || $a->[7] cmp $a->[7] - || $b->[6] <=> $a->[6]} @versions; - - my @good_versions; - my %allready_seen_by_mtime; - my %allready_seen_by_md5; - # Now we should create a new array with only the interesting records - foreach my $ref (@versions) - { - if ($ref->[7]) + defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown + + next if $progress{$fileset} eq 'F'; # It's over for this fileset... + + if ($level eq 'I') { - # The file has a md5. We compare his md5 to other known md5... - # We take size into account. It may happen that 2 files - # have the same md5sum and are different. size is a supplementary - # criterion - - # If we allready have a (better) version - next if ( (not $see_all) - and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}); + next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I'); + push @CurrentJobIds,($jobid); + } + elsif ($level eq 'D') + { + next if $progress{$fileset} eq 'D'; # We allready have a differential + push @CurrentJobIds,($jobid); + } + elsif ($level eq 'F') + { + push @CurrentJobIds,($jobid); + } - # we never met this one before... - $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1; + my $status = $refrow->[3] ; + if ($status eq 'T') { # good end of job + $progress{$fileset} = $level; } - # Even if it has a md5, we should also work with mtimes - # We allready have a (better) version - next if ( (not $see_all) - and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]}); - $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1; - - # We reached there. The file hasn't been seen. - push @good_versions,($ref); } - - # To be nice with the user, we re-sort good_versions by - # inchanger desc, mtime desc - @good_versions = sort { $b->[4] <=> $a->[4] - || $b->[2] <=> $a->[2]} @good_versions; - - return @good_versions; + + return @CurrentJobIds; +} + +sub refresh_screen +{ + Gtk2->main_iteration while (Gtk2->events_pending); } # TODO : bsr must use only good backup or not (see use_ok_bkp_only) @@ -2472,7 +1931,8 @@ WHERE Job.JobId = JobMedia.JobId # reminder : restore_list looks like this : - # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex); + # ($pid,$fid,$name,$jobid,'file',$curjobids, + # undef, undef, undef, $dirfileindex); # Here, we retrieve every file/dir that could be in the restore # We do as simple as possible for the SQL engine (no crazy joins, @@ -2481,17 +1941,22 @@ WHERE Job.JobId = JobMedia.JobId my @select_queries; foreach my $entry (@{$self->{restore_list}->{data}}) { - if ($entry->[2] eq 'dir') + if ($entry->[4] eq 'dir') { - my $dir = unpack('u', $entry->[0]); - my $inclause = $entry->[3]; #curjobids - + my $dirid = $entry->[0]; + my $inclause = $entry->[5]; #curjobids + my $query = "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId FROM File, Path, Filename WHERE Path.PathId = File.PathId AND File.FilenameId = Filename.FilenameId - AND Path.Path LIKE '$dir%' + AND Path.Path LIKE + (SELECT ". $self->dbh_strcat('Path',"'\%'") ." FROM Path + WHERE PathId IN ($dirid) + UNION + SELECT " . $self->dbh_strcat('Path',"'\%'") ." FROM brestore_missing_path WHERE PathId IN ($dirid) + ) AND File.JobId IN ($inclause) )"; push @select_queries,($query); } @@ -2499,29 +1964,27 @@ WHERE Job.JobId = JobMedia.JobId { # It's a file. Great, we allready have most # of what is needed. Simple and efficient query - my $file = unpack('u', $entry->[0]); - my @file = split '/',$file; - $file = pop @file; - my $dir = join('/',@file); + my $dir = $entry->[0]; + my $file = $entry->[1]; - my $jobid = $entry->[1]; - my $fileindex = $entry->[7]; - my $inclause = $entry->[3]; # curjobids + my $jobid = $entry->[3]; + my $fileindex = $entry->[9]; + my $inclause = $entry->[5]; # curjobids my $query = "(SELECT Path.Path, Filename.Name, File.FileIndex, File.JobId - FROM File, Path, Filename - WHERE Path.PathId = File.PathId + FROM File,Path,Filename + WHERE File.PathId = $dir + AND File.PathId = Path.PathId + AND File.FilenameId = $file AND File.FilenameId = Filename.FilenameId - AND Path.Path = '$dir/' - AND Filename.Name = '$file' - AND File.JobId = $jobid)"; + AND File.JobId = $jobid + ) +"; push @select_queries,($query); } } $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n"; - print STDERR $query,"\n" if $debug; - #Now we run the query and parse the result... # there may be a lot of records, so we better be efficient # We use the bind column method, working with references... @@ -2766,55 +2229,322 @@ WHERE Job.JobId = JobMedia.JobId $prev_mediatype=$mediatype; $prev_volblocks=$volblocks; - } + } + + # Ok, we're out of the loop. Alas, there's still the last record ... + if ($first_of_current_range != $prev_fileindex) + { + # we are in a range + push @fileindex_ranges,("$first_of_current_range-$prev_fileindex"); + + } + else + { + # We are out of a range, + # but there is only one element in the range + push @fileindex_ranges,("$first_of_current_range"); + + } + $bsr.=print_bsr_section(\@fileindex_ranges, + $prev_volsessionid, + $prev_volsessiontime, + $prev_volumename, + $prev_volfile, + $prev_mediatype, + $prev_volblocks, + $count); + + return $bsr; +} + +sub print_bsr_section +{ + my ($ref_fileindex_ranges,$volsessionid, + $volsessiontime,$volumename,$volfile, + $mediatype,$volblocks,$count)=@_; + + my $bsr=''; + $bsr .= "Volume=\"$volumename\"\n"; + $bsr .= "MediaType=\"$mediatype\"\n"; + $bsr .= "VolSessionId=$volsessionid\n"; + $bsr .= "VolSessionTime=$volsessiontime\n"; + $bsr .= "VolFile=$volfile\n"; + $bsr .= "VolBlock=$volblocks\n"; + + foreach my $range (@{$ref_fileindex_ranges}) + { + $bsr .= "FileIndex=$range\n"; + } + + $bsr .= "Count=$count\n"; + return $bsr; +} + +1; + +################################################################ + +package Bvfs; +use base qw/Bbase/; + +sub get_pathid +{ + my ($self, $dir) = @_; + my $query = + "SELECT PathId FROM Path WHERE Path = ? + UNION + SELECT PathId FROM brestore_missing_path WHERE Path = ?"; + my $sth = $self->dbh_prepare($query); + $sth->execute($dir,$dir); + my $result = $sth->fetchall_arrayref(); + $sth->finish(); + + return join(',', map { $_->[0] } @$result); +} + + +sub update_cache +{ + my ($self) = @_; + + my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId"; + my $jobs = $self->dbh_selectall_arrayref($query); + + $self->update_brestore_table(map { $_->[0] } @$jobs); +} + +sub get_root +{ + my ($self, $dir) = @_; + return $self->get_pathid(''); +} + +sub ch_dir +{ + my ($self, $pathid) = @_; + $self->{cwd} = $pathid; +} + +sub up_dir +{ + my ($self) = @_ ; + my $query = + "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId IN ($self->{cwd}) "; + + my $all = $self->dbh_selectall_arrayref($query); + return unless ($all); # already at root + + my $dir = join(',', map { $_->[0] } @$all); + if ($dir) { + $self->{cwd} = $dir; + } +} + +sub pwd +{ + my ($self) = @_; + return $self->get_path($self->{cwd}); +} - # Ok, we're out of the loop. Alas, there's still the last record ... - if ($first_of_current_range != $prev_fileindex) - { - # we are in a range - push @fileindex_ranges,("$first_of_current_range-$prev_fileindex"); - - } - else - { - # We are out of a range, - # but there is only one element in the range - push @fileindex_ranges,("$first_of_current_range"); - - } - $bsr.=print_bsr_section(\@fileindex_ranges, - $prev_volsessionid, - $prev_volsessiontime, - $prev_volumename, - $prev_volfile, - $prev_mediatype, - $prev_volblocks, - $count); +sub get_path +{ + my ($self, $pathid) = @_; + $self->debug("Call with pathid = $pathid"); + my $query = + "SELECT Path FROM Path WHERE PathId IN (?) + UNION + SELECT Path FROM brestore_missing_path WHERE PathId IN (?)"; + my $sth = $self->dbh_prepare($query); + $sth->execute($pathid,$pathid); + my $result = $sth->fetchrow_arrayref(); + $sth->finish(); + return $result->[0]; +} + +sub set_curjobids +{ + my ($self, @jobids) = @_; + $self->{curjobids} = join(',', @jobids); + $self->update_brestore_table(@jobids); +} + +sub ls_files +{ + my ($self) = @_; + + return undef unless ($self->{curjobids}); + + my $inclause = $self->{curjobids}; + my $inlistpath = $self->{cwd}; + + my $query = +"SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId + FROM + (SELECT Filename.Name, max(File.FileId) as id + FROM File, Filename + WHERE File.FilenameId = Filename.FilenameId + AND Filename.Name != '' + AND File.PathId IN ($inlistpath) + AND File.JobId IN ($inclause) + GROUP BY Filename.Name + ORDER BY Filename.Name) AS listfiles, +File +WHERE File.FileId = listfiles.id"; - return $bsr; + $self->debug($query); + my $result = $self->dbh_selectall_arrayref($query); + $self->debug($result); + + return $result; } -sub print_bsr_section +# return ($dirid,$dir_basename,$lstat,$jobid) +sub ls_dirs { - my ($ref_fileindex_ranges,$volsessionid, - $volsessiontime,$volumename,$volfile, - $mediatype,$volblocks,$count)=@_; - - my $bsr=''; - $bsr .= "Volume=\"$volumename\"\n"; - $bsr .= "MediaType=\"$mediatype\"\n"; - $bsr .= "VolSessionId=$volsessionid\n"; - $bsr .= "VolSessionTime=$volsessiontime\n"; - $bsr .= "VolFile=$volfile\n"; - $bsr .= "VolBlock=$volblocks\n"; - - foreach my $range (@{$ref_fileindex_ranges}) + my ($self) = @_; + + return undef unless ($self->{curjobids}); + + my $pathid = $self->{cwd}; + my $jobclause = $self->{curjobids}; + + # Let's retrieve the list of the visible dirs in this dir ... + # First, I need the empty filenameid to locate efficiently the dirs in the file table + my $query = "SELECT FilenameId FROM Filename WHERE Name = ''"; + my $sth = $self->dbh_prepare($query); + $sth->execute(); + my $result = $sth->fetchrow_arrayref(); + $sth->finish(); + my $dir_filenameid = $result->[0]; + + # Then we get all the dir entries from File ... + # It's ugly because there are records in brestore_missing_path ... + $query = " +SELECT PathId, Path, JobId, Lstat FROM( + ( + SELECT Path.PathId, Path.Path, lower(Path.Path), + listfile.JobId, listfile.Lstat + FROM ( + SELECT DISTINCT brestore_pathhierarchy.PathId + FROM brestore_pathhierarchy + JOIN Path + ON (brestore_pathhierarchy.PathId = Path.PathId) + JOIN brestore_pathvisibility + ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId) + WHERE brestore_pathhierarchy.PPathId = $pathid + AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath + JOIN Path ON (listpath.PathId = Path.PathId) + LEFT JOIN ( + SELECT File.PathId, File.JobId, File.Lstat FROM File + WHERE File.FilenameId = $dir_filenameid + AND File.JobId IN ($jobclause)) AS listfile + ON (listpath.PathId = listfile.PathId) + UNION + SELECT brestore_missing_path.PathId, brestore_missing_path.Path, + lower(brestore_missing_path.Path), listfile.JobId, listfile.Lstat + FROM ( + SELECT DISTINCT brestore_pathhierarchy.PathId + FROM brestore_pathhierarchy + JOIN brestore_missing_path + ON (brestore_pathhierarchy.PathId = brestore_missing_path.PathId) + JOIN brestore_pathvisibility + ON (brestore_pathhierarchy.PathId = brestore_pathvisibility.PathId) + WHERE brestore_pathhierarchy.PPathId = $pathid + AND brestore_pathvisibility.jobid IN ($jobclause)) AS listpath + JOIN brestore_missing_path ON (listpath.PathId = brestore_missing_path.PathId) + LEFT JOIN ( + SELECT File.PathId, File.JobId, File.Lstat FROM File + WHERE File.FilenameId = $dir_filenameid + AND File.JobId IN ($jobclause)) AS listfile + ON (listpath.PathId = listfile.PathId)) +ORDER BY 2,3 DESC ) As a"; + $self->debug($query); + $sth=$self->dbh_prepare($query); + $sth->execute(); + $result = $sth->fetchall_arrayref(); + my @return_list; + my $prev_dir=''; + foreach my $refrow (@{$result}) { - $bsr .= "FileIndex=$range\n"; + my $dirid = $refrow->[0]; + my $dir = $refrow->[1]; + my $lstat = $refrow->[3]; + my $jobid = $refrow->[2] || 0; + next if ($dirid eq $prev_dir); + # We have to clean up this dirname ... we only want it's 'basename' + my $return_value; + if ($dir ne '/') + { + my @temp = split ('/',$dir); + $return_value = pop @temp; + } + else + { + $return_value = '/'; + } + my @return_array = ($dirid,$return_value,$lstat,$jobid); + push @return_list,(\@return_array); + $prev_dir = $dirid; } + $self->debug(\@return_list); + return \@return_list; +} + +# Returns the list of media required for a list of jobids. +# Input : self, jobid1, jobid2... +# Output : reference to array of (joibd, inchanger) +sub get_required_media_from_jobid +{ + my ($self, @jobids)=@_; + my $inclause = join(',',@jobids); + my $query = " +SELECT DISTINCT JobMedia.MediaId, Media.InChanger +FROM JobMedia, Media +WHERE JobMedia.MediaId=Media.MediaId +AND JobId In ($inclause) +ORDER BY MediaId"; + my $result = $self->dbh_selectall_arrayref($query); + return $result; +} + +# Returns the fileindex from dirname and jobid. +# Input : self, dirid, jobid +# Output : fileindex +sub get_fileindex_from_dir_jobid +{ + my ($self, $dirid, $jobid)=@_; + my $query; + $query = "SELECT File.FileIndex + FROM File, Filename + WHERE File.FilenameId = Filename.FilenameId + AND File.PathId = $dirid + AND Filename.Name = '' + AND File.JobId = '$jobid' + "; + + $self->debug($query); + my $result = $self->dbh_selectall_arrayref($query); + return $result->[0]->[0]; +} + +# Returns the fileindex from filename and jobid. +# Input : self, dirid, filenameid, jobid +# Output : fileindex +sub get_fileindex_from_file_jobid +{ + my ($self, $dirid, $filenameid, $jobid)=@_; - $bsr .= "Count=$count\n"; - return $bsr; + my $query; + $query = +"SELECT File.FileIndex + FROM File + WHERE File.PathId = $dirid + AND File.FilenameId = $filenameid + AND File.JobId = $jobid"; + + $self->debug($query); + my $result = $self->dbh_selectall_arrayref($query); + return $result->[0]->[0]; } # This function estimates the size to be restored for an entry of the restore @@ -2824,20 +2554,25 @@ sub print_bsr_section sub estimate_restore_size { # reminder : restore_list looks like this : - # ($name,$jobid,'file',$curjobids, undef, undef, undef, $dirfileindex); - my $self=shift; - my ($entry)=@_; + # ($pid,$fid,$name,$jobid,'file',$curjobids, + # undef, undef, undef, $dirfileindex); + my ($self, $entry, $refresh) = @_; my $query; - if ($entry->[2] eq 'dir') + if ($entry->[4] eq 'dir') { - my $dir = unpack('u', $entry->[0]); - my $inclause = $entry->[3]; #curjobids + my $dir = $entry->[0]; + + my $inclause = $entry->[5]; #curjobids $query = "SELECT Path.Path, File.FilenameId, File.LStat FROM File, Path, Job WHERE Path.PathId = File.PathId AND File.JobId = Job.JobId - AND Path.Path LIKE '$dir%' + AND Path.Path LIKE + (SELECT Path || '%' FROM Path WHERE PathId IN ($dir) + UNION + SELECT Path || '%' FROM brestore_missing_path WHERE PathId IN ($dir) + ) AND File.JobId IN ($inclause) ORDER BY Path.Path, File.FilenameId, Job.StartTime DESC"; } @@ -2845,25 +2580,21 @@ sub estimate_restore_size { # It's a file. Great, we allready have most # of what is needed. Simple and efficient query - my $file = unpack('u', $entry->[0]); - my @file = split '/',$file; - $file = pop @file; - my $dir = join('/',@file); + my $dir = $entry->[0]; + my $fileid = $entry->[1]; - my $jobid = $entry->[1]; - my $fileindex = $entry->[7]; - my $inclause = $entry->[3]; # curjobids + my $jobid = $entry->[3]; + my $fileindex = $entry->[9]; + my $inclause = $entry->[5]; # curjobids $query = "SELECT Path.Path, File.FilenameId, File.Lstat - FROM File, Path, Filename + FROM File, Path WHERE Path.PathId = File.PathId - AND Path.Path = '$dir/' - AND Filename.Name = '$file' - AND File.JobId = $jobid - AND Filename.FilenameId = File.FilenameId"; + AND Path.PathId = $dir + AND File.FilenameId = $fileid + AND File.JobId = $jobid"; } - print STDERR $query,"\n" if $debug; my ($path,$nameid,$lstat); my $sth = $self->dbh_prepare($query); $sth->execute; @@ -2873,7 +2604,7 @@ sub estimate_restore_size my $total_size=0; my $total_files=0; - refresh_screen(); + &$refresh(); my $rcount=0; # We fetch all rows @@ -2882,27 +2613,121 @@ sub estimate_restore_size # Only the latest version of a file next if ($nameid eq $old_nameid and $path eq $old_path); - if ($rcount > 15000) { - refresh_screen(); - $rcount=0; - } else { - $rcount++; + if ($rcount > 15000) { + &$refresh(); + $rcount=0; + } else { + $rcount++; + } + + # We get the size of this file + my $size=lstat_attrib($lstat,'st_size'); + $total_size += $size; + $total_files++; + $old_path=$path; + $old_nameid=$nameid; + } + + return ($total_size,$total_files); +} + +# Returns list of versions of a file that could be restored +# returns an array of +# ('FILE:',jobid,fileindex,mtime,size,inchanger,md5,volname) +# there will be only one jobid in the array of jobids... +sub get_all_file_versions +{ + my ($self,$pathid,$fileid,$client,$see_all)=@_; + + defined $see_all or $see_all=0; + + my @versions; + my $query; + $query = +"SELECT File.JobId, File.FileIndex, File.Lstat, + File.Md5, Media.VolumeName, Media.InChanger + FROM File, Job, Client, JobMedia, Media + WHERE File.FilenameId = $fileid + AND File.PathId=$pathid + AND File.JobId = Job.JobId + AND Job.ClientId = Client.ClientId + AND Job.JobId = JobMedia.JobId + AND File.FileIndex >= JobMedia.FirstIndex + AND File.FileIndex <= JobMedia.LastIndex + AND JobMedia.MediaId = Media.MediaId + AND Client.Name = '$client'"; + + $self->debug($query); + + my $result = $self->dbh_selectall_arrayref($query); + + foreach my $refrow (@$result) + { + my ($jobid, $fileindex, $lstat, $md5, $volname, $inchanger) = @$refrow; + my @attribs = parse_lstat($lstat); + my $mtime = array_attrib('st_mtime',\@attribs); + my $size = array_attrib('st_size',\@attribs); + + my @list = ('FILE:',$pathid,$fileid,$jobid, + $fileindex, $mtime, $size, $inchanger, + $md5, $volname); + push @versions, (\@list); + } + + # We have the list of all versions of this file. + # We'll sort it by mtime desc, size, md5, inchanger desc + # the rest of the algorithm will be simpler + # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname) + @versions = sort { $b->[5] <=> $a->[5] + || $a->[6] <=> $b->[6] + || $a->[8] cmp $a->[8] + || $b->[7] <=> $a->[7]} @versions; + + + my @good_versions; + my %allready_seen_by_mtime; + my %allready_seen_by_md5; + # Now we should create a new array with only the interesting records + foreach my $ref (@versions) + { + if ($ref->[8]) + { + # The file has a md5. We compare his md5 to other known md5... + # We take size into account. It may happen that 2 files + # have the same md5sum and are different. size is a supplementary + # criterion + + # If we allready have a (better) version + next if ( (not $see_all) + and $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}); + + # we never met this one before... + $allready_seen_by_md5{$ref->[8] .'-'. $ref->[6]}=1; } - - # We get the size of this file - my $size=lstat_attrib($lstat,'st_size'); - $total_size += $size; - $total_files++; - $old_path=$path; - $old_nameid=$nameid; + # Even if it has a md5, we should also work with mtimes + # We allready have a (better) version + next if ( (not $see_all) + and $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6]}); + $allready_seen_by_mtime{$ref->[5] .'-'. $ref->[6] . '-' . $ref->[8]}=1; + + # We reached there. The file hasn't been seen. + push @good_versions,($ref); } - return ($total_size,$total_files); + + # To be nice with the user, we re-sort good_versions by + # inchanger desc, mtime desc + @good_versions = sort { $b->[5] <=> $a->[5] + || $b->[3] <=> $a->[3]} @good_versions; + + return @good_versions; } + sub update_brestore_table { my ($self, @jobs) = @_; - my $dbh = $self->{dbh}; + + $self->debug(\@jobs); foreach my $job (sort {$a <=> $b} @jobs) { @@ -2957,31 +2782,30 @@ sub update_brestore_table WHERE JobId=$job) AS b ON (a.PathId = b.PathId) WHERE b.PathId IS NULL)"; - print STDERR $query,"\n" if ($debug); + my $rows_affected; - while (($rows_affected = $dbh->do($query)) and ($rows_affected !~ /^0/)) + while (($rows_affected = $self->dbh_do($query)) and ($rows_affected !~ /^0/)) { print STDERR "Recursively adding $rows_affected records from $job\n"; } # Job's done $query = "INSERT INTO brestore_knownjobid (JobId) VALUES ($job)"; - $dbh->do($query); + $self->dbh_do($query); } } sub cleanup_brestore_table { my ($self) = @_; - my $dbh = $self->{dbh}; my $query = "SELECT JobId from brestore_knownjobid"; - my @jobs = @{$dbh->selectall_arrayref($query)}; + my @jobs = @{$self->dbh_selectall_arrayref($query)}; foreach my $jobentry (@jobs) { my $job = $jobentry->[0]; $query = "SELECT FileId from File WHERE JobId = $job LIMIT 1"; - my $result = $dbh->selectall_arrayref($query); + my $result = $self->dbh_selectall_arrayref($query); if (scalar(@{$result})) { # There are still files for this jobid @@ -2989,13 +2813,34 @@ sub cleanup_brestore_table } else { $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job"; - $dbh->do($query); + $self->dbh_do($query); $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job"; - $dbh->do($query); + $self->dbh_do($query); } } } +sub parent_dir +{ + my ($path) = @_; + # Root Unix case : + if ($path eq '/') + { + return ''; + } + # Root Windows case : + if ($path =~ /^[a-z]+:\/$/i) + { + return ''; + } + # Split + my @tmp = split('/',$path); + # We remove the last ... + pop @tmp; + my $tmp = join ('/',@tmp) . '/'; + return $tmp; +} + sub build_path_hierarchy { my ($self, $path,$pathid)=@_; @@ -3004,11 +2849,10 @@ sub build_path_hierarchy # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy while ($path ne '') { - #print STDERR "$path\n" if $debug; if (! $self->{cache_ppathid}->{$pathid}) { my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?"; - my $sth2 = $self->{dbh}->prepare_cached($query); + my $sth2 = $self->{conf}->{dbh}->prepare_cached($query); $sth2->execute($pathid); # Do we have a result ? if (my $refrow = $sth2->fetchrow_arrayref) @@ -3028,7 +2872,7 @@ sub build_path_hierarchy $self->{cache_ppathid}->{$pathid}= $ppathid; $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)"; - $sth2 = $self->{dbh}->prepare_cached($query); + $sth2 = $self->{conf}->{dbh}->prepare_cached($query); $sth2->execute($pathid,$ppathid); $sth2->finish(); $path = $ppath; @@ -3044,6 +2888,7 @@ sub build_path_hierarchy return 1; } + sub return_pathid_from_path { my ($self, $path) = @_; @@ -3051,7 +2896,7 @@ sub return_pathid_from_path UNION SELECT PathId FROM brestore_missing_path WHERE Path = ?"; #print STDERR $query,"\n" if $debug; - my $sth = $self->{dbh}->prepare_cached($query); + my $sth = $self->{conf}->{dbh}->prepare_cached($query); $sth->execute($path,$path); my $result =$sth->fetchrow_arrayref(); $sth->finish(); @@ -3065,24 +2910,24 @@ sub return_pathid_from_path # the pathid, because of mysql $query = "INSERT INTO Path (Path) VALUES (?)"; #print STDERR $query,"\n" if $debug; - $sth = $self->{dbh}->prepare_cached($query); + $sth = $self->{conf}->{dbh}->prepare_cached($query); $sth->execute($path); $sth->finish(); $query = " INSERT INTO brestore_missing_path (PathId,Path) SELECT PathId,Path FROM Path WHERE Path = ?"; #print STDERR $query,"\n" if $debug; - $sth = $self->{dbh}->prepare_cached($query); + $sth = $self->{conf}->{dbh}->prepare_cached($query); $sth->execute($path); $sth->finish(); $query = " DELETE FROM Path WHERE Path = ?"; #print STDERR $query,"\n" if $debug; - $sth = $self->{dbh}->prepare_cached($query); + $sth = $self->{conf}->{dbh}->prepare_cached($query); $sth->execute($path); $sth->finish(); $query = "SELECT PathId FROM brestore_missing_path WHERE Path = ?"; #print STDERR $query,"\n" if $debug; - $sth = $self->{dbh}->prepare_cached($query); + $sth = $self->{conf}->{dbh}->prepare_cached($query); $sth->execute($path); $result = $sth->fetchrow_arrayref(); $sth->finish(); @@ -3090,25 +2935,76 @@ sub return_pathid_from_path } } -sub parent_dir + +sub create_brestore_tables { - my ($path) = @_; - # Root Unix case : - if ($path eq '/') - { - return ''; + my ($self) = @_; + + my $verif = "SELECT 1 FROM brestore_knownjobid LIMIT 1"; + + unless ($self->dbh_do($verif)) { + new DlgWarn("brestore can't find brestore_xxx tables on your database. I will create them."); + + $self->{error} = "Creating internal brestore tables"; + my $req = " + CREATE TABLE brestore_knownjobid + ( + JobId int4 NOT NULL, + CONSTRAINT brestore_knownjobid_pkey PRIMARY KEY (JobId) + )"; + $self->dbh_do($req); } - # Root Windows case : - if ($path =~ /^[a-z]+:\/$/i) - { - return ''; + + $verif = "SELECT 1 FROM brestore_pathhierarchy LIMIT 1"; + unless ($self->dbh_do($verif)) { + my $req = " + CREATE TABLE brestore_pathhierarchy + ( + PathId int4 NOT NULL, + PPathId int4 NOT NULL, + CONSTRAINT brestore_pathhierarchy_pkey PRIMARY KEY (PathId) + )"; + $self->dbh_do($req); + + + $req = "CREATE INDEX brestore_pathhierarchy_ppathid + ON brestore_pathhierarchy (PPathId)"; + $self->dbh_do($req); + } + + $verif = "SELECT 1 FROM brestore_pathvisibility LIMIT 1"; + unless ($self->dbh_do($verif)) { + my $req = " + CREATE TABLE brestore_pathvisibility + ( + PathId int4 NOT NULL, + JobId int4 NOT NULL, + Size int8 DEFAULT 0, + Files int4 DEFAULT 0, + CONSTRAINT brestore_pathvisibility_pkey PRIMARY KEY (JobId, PathId) + )"; + $self->dbh_do($req); + + $req = "CREATE INDEX brestore_pathvisibility_jobid + ON brestore_pathvisibility (JobId)"; + $self->dbh_do($req); + } + + $verif = "SELECT 1 FROM brestore_missing_path LIMIT 1"; + unless ($self->dbh_do($verif)) { + my $req = " + CREATE TABLE brestore_missing_path + ( + PathId int4 NOT NULL, + Path text NOT NULL, + CONSTRAINT brestore_missing_path_pkey PRIMARY KEY (PathId) + )"; + $self->dbh_do($req); + + $req = "CREATE INDEX brestore_missing_path_path + ON brestore_missing_path (Path)"; + $self->dbh_do($req); } - # Split - my @tmp = split('/',$path); - # We remove the last ... - pop @tmp; - my $tmp = join ('/',@tmp) . '/'; - return $tmp; } # Get metadata @@ -3126,69 +3022,28 @@ sub parent_dir } sub file_attrib - { # $file = [listfiles.id, listfiles.Name, File.LStat, File.JobId] + { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId] my ($file, $attrib)=@_; if (defined $attrib_name_id{$attrib}) { - my @d = split(' ', $file->[2]) ; # TODO : cache this + my @d = split(' ', $file->[3]) ; # TODO : cache this return from_base64($d[$attrib_name_id{$attrib}]); } elsif ($attrib eq 'jobid') { - return $file->[3]; + return $file->[4]; } elsif ($attrib eq 'name') { - return $file->[1]; + return $file->[2]; } else { die "Attribute not known : $attrib.\n"; } } - - # Return the jobid or attribute asked for a dir - sub dir_attrib - { - my ($self,$dir,$attrib)=@_; - - my @dir = split('/',$dir,-1); - my $refdir=$self->{dirtree}->{$self->current_client}; - - if (not defined $attrib_name_id{$attrib} and $attrib ne 'jobid') - { - die "Attribute not known : $attrib.\n"; - } - # Find the leaf - foreach my $subdir (@dir) - { - $refdir = $refdir->[0]->{$subdir}; - } - - # $refdir is now the reference to the dir's array - # Is the a jobid in @CurrentJobIds where the lstat is - # defined (we'll search in reverse order) - foreach my $jobid (reverse(sort {$a <=> $b } @{$self->{CurrentJobIds}})) - { - if (defined $refdir->[1]->{$jobid} and $refdir->[1]->{$jobid} ne '1') - { - if ($attrib eq 'jobid') - { - return $jobid; - } - else - { - my @attribs = parse_lstat($refdir->[1]->{$jobid}); - return $attribs[$attrib_name_id{$attrib}+1]; - } - } - } - - return 0; # We cannot get a good attribute. - # This directory is here for the sake of visibility - } sub lstat_attrib { @@ -3263,32 +3118,114 @@ sub parent_dir } } - 1; ################################################################ - -package Batch; -use base qw/DlgResto/; +package BwebConsole; +use LWP::UserAgent; +use HTTP::Request::Common; sub new { - my ($class, $conf) = @_; - my $self = bless {info => $conf}, $class; + my ($class, %arg) = @_; - $self->{dbh} = $conf->{dbh}; + my $self = bless { + pref => $arg{pref}, # Pref object + timeout => $arg{timeout} || 20, + debug => $arg{debug} || 0, + 'list_job' => '', + 'list_client' => '', + 'list_fileset' => '', + 'list_storage' => '', + 'run' => '', + }; return $self; } -sub update_cache +sub prepare +{ + my ($self, @what) = @_; + my $ua = LWP::UserAgent->new(); + $ua->agent("Brestore/$VERSION"); + my $req = POST($self->{pref}->{bconsole}, + Content_Type => 'form-data', + Content => [ map { (action => $_) } @what ]); + #$req->authorization_basic('eric', 'test'); + + my $res = $ua->request($req); + + if ($res->is_success) { + foreach my $l (split(/\n/, $res->content)) { + my ($k, $c) = split(/=/,$l,2); + $self->{$k} = $c; + } + } else { + $self->{error} = "Can't connect to bweb : " . $res->status_line; + new DlgWarn($self->{error}); + } +} + +sub run +{ + my ($self, %arg) = @_; + + my $ua = LWP::UserAgent->new(); + $ua->agent("Brestore/$VERSION"); + my $req = POST($self->{pref}->{bconsole}, + Content_Type => 'form-data', + Content => [ job => $arg{job}, + client => $arg{client}, + storage => $arg{storage} || '', + fileset => $arg{fileset} || '', + where => $arg{where}, + replace => $arg{replace}, + priority=> $arg{prio} || '', + action => 'run', + timeout => 10, + bootstrap => [$arg{bootstrap}], + ]); + #$req->authorization_basic('eric', 'test'); + + my $res = $ua->request($req); + + if ($res->is_success) { + foreach my $l (split(/\n/, $res->content)) { + my ($k, $c) = split(/=/,$l,2); + $self->{$k} = $c; + } + } + + if (!$self->{run}) { + new DlgWarn("Can't connect to bweb : " . $res->status_line); + } + + unlink($arg{bootstrap}); + + return $self->{run}; +} + +sub list_job { my ($self) = @_; + return sort split(/;/, $self->{'list_job'}); +} - my $query = "SELECT JobId from Job WHERE JobId NOT IN (SELECT JobId FROM brestore_knownjobid) order by JobId"; - my $jobs = $self->dbh_selectall_arrayref($query); +sub list_fileset +{ + my ($self) = @_; + return sort split(/;/, $self->{'list_fileset'}); +} - $self->update_brestore_table(map { $_->[0] } @$jobs); +sub list_storage +{ + my ($self) = @_; + return sort split(/;/, $self->{'list_storage'}); +} +sub list_client +{ + my ($self) = @_; + return sort split(/;/, $self->{'list_client'}); } 1; @@ -3318,10 +3255,9 @@ if (! -f $file_conf) { } if ($batch_mod) { - my $b = new Batch($p); + my $vfs = new Bvfs(conf => $p); if ($p->connect_db()) { - $b->set_dbh($p->{dbh}); - $b->update_cache(); + $vfs->update_cache(); } exit (0); } @@ -3363,27 +3299,22 @@ Gtk2->main; # Start Gtk2 main loop exit 0; - __END__ +package main; -TODO : - - -# Code pour trier les colonnes - my $mod = $fileview->get_model(); - $mod->set_default_sort_func(sub { - my ($model, $item1, $item2) = @_; - my $a = $model->get($item1, 1); # récupération de la valeur de la 2ème - my $b = $model->get($item2, 1); # colonne (indice 1) - return $a cmp $b; - } - ); - - $fileview->set_headers_clickable(1); - my $col = $fileview->get_column(1); # la colonne NOM, colonne numéro 2 - $col->signal_connect('clicked', sub { - my ($colonne, $model) = @_; - $model->set_sort_column_id (1, 'ascending'); - }, - $mod - ); +my $p = new Pref("$ENV{HOME}/.brestore.conf"); +$p->{debug} = 1; +$p->connect_db() || print $p->{error}; + +my $bvfs = new Bvfs(conf => $p); + +$bvfs->debug($bvfs->get_root()); +$bvfs->ch_dir($bvfs->get_root()); +$bvfs->up_dir(); +$bvfs->set_curjobids(268,178,282,281,279); +$bvfs->ls_files(); +my $dirs = $bvfs->ls_dirs(); +$bvfs->ch_dir(123496); +$dirs = $bvfs->ls_dirs(); +$bvfs->ls_files(); +map { $bvfs->debug($_) } $bvfs->get_all_file_versions($bvfs->{cwd},312433, "exw3srv3", 1);