From: Eric Bollengier Date: Wed, 7 Feb 2007 17:16:10 +0000 (+0000) Subject: ebl release file on sf X-Git-Tag: Release-2.0.2~1 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=19b1e00b12ecfda40b99db75af9602f4410b76bb;p=bacula%2Fbacula ebl release file on sf git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/tags/Release-2.0.2@4143 91ce42f0-d328-0410-95d8-f526ca767f89 --- diff --git a/gui/brestore/COPYING b/gui/brestore/COPYING new file mode 100644 index 0000000000..960fe7469f --- /dev/null +++ b/gui/brestore/COPYING @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/gui/brestore/INSTALL b/gui/brestore/INSTALL new file mode 100644 index 0000000000..00eb94dece --- /dev/null +++ b/gui/brestore/INSTALL @@ -0,0 +1,100 @@ +################ INSTALL NOTES FOR BRESTORE #################### + +# you can install the user interfact 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 don't use bweb and bconsole.pl +# you must get Bconsole.pm from bweb modules (bweb/lib/Bconsole.pm) +# and install it. If bweb is installed, Bconsole.pm is already +# installed. + +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 +perl, perl-DBI, perl-DBD-mysql, perl-DBD-Pg, perl-Expect, perl-libwww-perl +perl-Gtk2-GladeXML + +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 don'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 modify the configuration from brestore itself (if brestore can't find +his glade file). + +Example : + +$parameters = { + 'mozilla' => 'mozilla', + 'bconsole' => '/usr/local/bin/bconsole -n -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' + }; + +or if you are using bweb http connector + +$parameters = { + 'glade_file' => '/home/eric/brestore/brestore-fr.glade', + 'mozilla' => 'firefox', + 'bconsole' => 'http://bacula-dir/cgi-bin/bweb/bconsole.pl', + 'bweb' => 'http://bacula-dir/cgi-bin/bweb/bweb.pl', + 'username' => 'bacula', + 'connection_string' => 'DBI:Pg:database=bacula;host=95.1.8.205', + 'password' => 'xxxx', + 'bsr_dest' => '', + 'see_all_versions' => '', + 'debug' => 1, + 'use_ok_bkp_only' => '', + 'default_restore_job' => 'Restore' + }; + diff --git a/gui/brestore/README b/gui/brestore/README new file mode 100644 index 0000000000..94a3314c32 --- /dev/null +++ b/gui/brestore/README @@ -0,0 +1,41 @@ +################################################################ +# brestore -- a bacula perl interface # +################################################################ + +As we felt a need for a restoration GUI for bacula, we developed a simple +Perl/GTK GUI. +It has the following features : + + * Direct SQL access to the database for good performance + + * Fast Time Navigation (switch almost instantaneously between the different + versions of a directory, by changing the date from a list) + + * Possibility to choose a selected file, then browse all its available + versions, and see directly if these versions are online in a library or + not + + * Simple restoration by the generation of a BSR file + + * 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. + +What's missing : + + * We want to add some other features such as : the user has selected a FD + and a date, we want to display the list of media requested and their + availability. + + * 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 + 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. + +brestore is also available within Bweb. + diff --git a/gui/brestore/ReleaseNotes b/gui/brestore/ReleaseNotes new file mode 100644 index 0000000000..55a8a95448 --- /dev/null +++ b/gui/brestore/ReleaseNotes @@ -0,0 +1,26 @@ + Release Notes for brestore 2.0.2 + +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 + win32 system. + - Add brestore.pl -b option to batch cache creation + - Add brestore tables to bacula's database for + speedup charging + - BugFix : perl << operator works only with 32bytes value + => using *64 instead + +Version 1.39: + - Add Gen BSR function + - Add new option "see all version" + - Add Estimate function + diff --git a/gui/brestore/brestore-fr.glade b/gui/brestore/brestore-fr.glade new file mode 100644 index 0000000000..7ed8a7f527 --- /dev/null +++ b/gui/brestore/brestore-fr.glade @@ -0,0 +1,3297 @@ + + + + + + + True + brestore + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 1024 + 768 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + False + + + + + + + True + False + 0 + + + + + True + GTK_PACK_DIRECTION_LTR + GTK_PACK_DIRECTION_LTR + + + + True + _Fichier + True + + + + + + + True + + + + + + True + gtk-quit + True + + + + + + + + + + + True + _Options + True + + + + + + + True + _Préférences + True + + + + + + + + + + + + True + _Aide + True + + + + + + + True + _A propos + True + + + + + + + + + + 0 + False + False + + + + + + True + GTK_ORIENTATION_HORIZONTAL + GTK_TOOLBAR_ICONS + True + True + + + + True + gtk-go-up + True + True + False + + + + False + True + + + + + + True + True + True + True + + + False + False + + + + + + True + gtk-preferences + True + True + False + + + + False + True + + + + + + True + Aller sur Bweb + gtk-jump-to + True + True + False + + + + False + True + + + + + + + + + + True + True + True + False + + + + + + + False + False + + + + + 0 + False + False + + + + + + True + GTK_ORIENTATION_HORIZONTAL + GTK_TOOLBAR_BOTH + True + True + + + + True + True + True + False + + + False + False + + + + + + True + True + True + False + + + + True + True + True + + + + + + False + False + + + + + + 5 + True + True + True + False + + + + True + + + + + False + False + + + + + + True + True + True + False + + + + Last 20 jobs +This week +This month +This year + False + True + + + + + + False + False + + + + + + True + True + True + False + + + + True + True + True + + + + + + False + False + + + + + + 5 + True + True + True + False + + + + True + + + + + False + False + + + + + + True + True + True + False + + + + True + True + True + GTK_RELIEF_NORMAL + True + + + + + True + 0.5 + 0.5 + 0 + 0 + 0 + 0 + 0 + 0 + + + + True + False + 2 + + + + True + gtk-jump-to + 4 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + Aller à + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + + + + + + False + False + + + + + + True + False + True + True + + + False + False + + + + + + True + True + True + False + + + + True + GTK_BUTTONBOX_DEFAULT_STYLE + 0 + + + + + False + False + + + + + + True + True + True + False + + + + 250 + True + True + True + True + 0 + + True + * + False + + + + + + False + False + + + + + 0 + False + False + + + + + + True + True + 430 + + + + True + True + 600 + + + + 3 + True + 0 + 0.5 + GTK_SHADOW_ETCHED_IN + + + + 3 + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + 3 + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_ETCHED_IN + GTK_CORNER_TOP_LEFT + + + + + True + True + True + True + True + False + True + False + False + False + + + + + + + + + + + + + + + True + <b>Liste des fichiers</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + True + False + + + + + + 3 + True + 0 + 0.5 + GTK_SHADOW_ETCHED_IN + + + + 3 + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + 3 + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_ETCHED_IN + GTK_CORNER_TOP_LEFT + + + + + True + True + True + True + True + True + True + False + False + False + + + + + + + + + + + True + <b>Version des fichiers</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + True + True + + + + + True + False + + + + + + 3 + True + False + 0 + + + + True + False + 0 + + + + True + True + <big><b>Liste des fichiers à restaurer</b></big> + False + True + GTK_JUSTIFY_CENTER + False + True + 0.5 + 0.5 + 0 + 7 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + GTK_BUTTONBOX_END + 0 + + + + True + True + True + gtk-clear + True + GTK_RELIEF_NORMAL + True + + + + + + + True + Can be very long... + True + True + GTK_RELIEF_NORMAL + True + + + + + True + 0.5 + 0.5 + 0 + 0 + 0 + 0 + 0 + 0 + + + + True + False + 2 + + + + True + gtk-zoom-in + 4 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + Estimer + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + + + + + + + True + True + True + GTK_RELIEF_NORMAL + True + + + + + True + 0.5 + 0.5 + 0 + 0 + 0 + 0 + 0 + 0 + + + + True + False + 2 + + + + True + gtk-revert-to-saved + 4 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + Restaurer + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + + + + + + + True + Générer un fichier bootstrap + True + True + GTK_RELIEF_NORMAL + True + + + + + True + 0.5 + 0.5 + 0 + 0 + 0 + 0 + 0 + 0 + + + + True + False + 2 + + + + True + gtk-justify-left + 4 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + Gen BSR + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + + + + + + 0 + True + True + GTK_PACK_END + + + + + 0 + False + True + + + + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + + True + True + True + False + False + False + False + False + False + + + + + + + 0 + True + True + + + + + + 1 + True + False + + + 0 + False + False + + + + + True + True + + + + + 0 + True + True + + + + + + + + About + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_MOUSE + False + False + True + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + True + + + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + -5 + + + + + + 0 + False + True + GTK_PACK_END + + + + + + 4 + True + False + 0 + + + + True + True + <big><b>brestore a Perl/GTK interface for bacula</b></big> + False + True + GTK_JUSTIFY_CENTER + False + True + 0.5 + 0.5 + 0 + 7 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + <small>Bacula Copyright ©2000-2006FSF Europe </small> + + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 4 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + 0 + True + True + + + + + + + + Preferences + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_MOUSE + False + True + True + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + True + + + + + + True + False + 4 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + -6 + + + + + + + True + True + True + gtk-apply + True + GTK_RELIEF_NORMAL + True + -10 + + + + + + + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + -5 + + + + + + 0 + False + True + GTK_PACK_END + + + + + + True + True + True + True + GTK_POS_TOP + False + False + + + + True + False + 0 + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + False + 0 + + + + True + True + Exemple : DBI:mysql:database=bacula;host=192.168.0.2;port=3306 +Exemple : DBI:Pg:database=bacula;host=127.0.0.1;port=5432 + + False + False + GTK_JUSTIFY_LEFT + False + True + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + 5 + True + 3 + 2 + False + 4 + 0 + + + + True + Chaîne DBD : + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 8 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 0 + 1 + fill + + + + + + + True + Username : + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 4 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 1 + 2 + fill + + + + + + + True + Password : + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 4 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 2 + 3 + fill + + + + + + + True + True + True + True + True + 0 + + True + * + False + + + 1 + 2 + 0 + 1 + + + + + + + True + True + True + False + 40 + + True + * + False + 40 + + + 1 + 2 + 2 + 3 + + + + + + + True + True + True + True + 40 + + True + * + False + 40 + + + 1 + 2 + 1 + 2 + + + + + + 0 + False + True + + + + + + + + + + True + <b>SQL connexion</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + False + True + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + False + 0 + + + + True + JobStatus is set to 'T' + True + Utiliser seulement les backup OK + True + GTK_RELIEF_NORMAL + True + False + False + True + + + 0 + False + False + + + + + + True + True + Voir toutes les versions des fichiers + True + GTK_RELIEF_NORMAL + True + False + False + True + + + 0 + False + False + + + + + + + + + + True + <b>Sélections</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + False + True + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + True + Afficher les messages de debug + True + GTK_RELIEF_NORMAL + True + False + False + True + + + + + + + + True + <b>Debug</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + False + True + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + 1 + 1 + False + 0 + 0 + + + + 6 + True + False + 0 + + + + True + /path/to/brestore.glade + True + True + True + 0 + + True + ● + False + + + 0 + False + False + + + + + 0 + 1 + 0 + 1 + + + + + + + + + + True + <b>Fichier Glade</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + False + True + + + + + False + True + + + + + + True + Options Générales + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + + + + True + False + 0 + + + + True + Pour lancer une restauration, brestore a besoin de lancer la bconsole. + False + False + GTK_JUSTIFY_LEFT + True + False + 0.5 + 0.5 + 0 + 6 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + 4 + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 5 + + + + 3 + True + False + 3 + + + + True + Le fichier BSR doit être copié sur le director, donc + +Si le director est local, utilisez quelque chose comme : +<b> file://path/to/bacula/work/dir</b> + +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 +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 + GTK_JUSTIFY_LEFT + False + False + 0.129999995232 + 0.5 + 0 + 3 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + BSR destination string + True + True + True + 0 + file://tmp + True + * + False + + + 0 + False + False + + + + + + + + + + True + <b>BSR destination string</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + False + False + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + 8 + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 5 + + + + True + False + 0 + + + + True + /path/to/bconsole -n -c /path/to/bconsole.conf +or +http://user:pass@director/cgi-bin/bweb/bconsole.pl + True + True + True + 0 + /usr/local/bacula/sbin/bconsole -n -c /usr/local/bacula/etc/bconsole.conf + True + * + False + + + 0 + False + False + + + + + + + + + + True + <b>commande bconsole</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + 8 + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 5 + + + + True + True + True + True + 0 + RestoreFiles + True + ● + False + + + + + + + + True + <b>Job de restauration par défaut</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + 2 + 2 + False + 3 + 3 + + + + True + Url Bweb : + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 0 + 1 + fill + + + + + + + True + Mozilla : + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 1 + 2 + fill + + + + + + + True + http://bacula-dir/cgi-bin/bweb/bweb.pl (ou laisser vide) + True + True + True + 0 + + True + ● + False + + + 1 + 2 + 0 + 1 + + + + + + + True + /path/to/mozilla + True + True + True + 0 + + True + ● + False + + + 1 + 2 + 1 + 2 + + + + + + + + + + + True + <b>Suivre une restauration</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + False + True + + + + + + True + Connexion Bacula + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + + + 0 + True + True + + + + + + + + Versions + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 650 + 270 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + False + + + + True + False + 0 + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + False + 3 + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_ETCHED_IN + GTK_CORNER_TOP_LEFT + + + + + True + True + True + True + True + False + True + False + False + False + + + + + + 0 + True + True + + + + + + + + + + True + <b>Version des fichiers</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + + True + + + 0 + False + True + + + + + + 3 + True + GTK_BUTTONBOX_END + 3 + + + + True + True + True + gtk-close + True + GTK_RELIEF_NORMAL + True + + + + + + 0 + False + True + + + + + + + + + + + True + Ajouter à la liste de restauration + True + + + + + + + True + Voir toutes les versions + True + + + + + + + + + + True + Add to restore list + True + + + + + + + True + Options de lancement de la restauration + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + False + False + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + True + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + -6 + + + + + + + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + -5 + + + + + + 0 + False + True + GTK_PACK_END + + + + + + True + False + 0 + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + False + 0 + + + + True + Ce job va utiliser les médias suivant : + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 10 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + 3 + True + True + GTK_POLICY_NEVER + GTK_POLICY_NEVER + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + False + False + False + False + + + + + 0 + True + True + + + + + + + + + + True + <b>Informations</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + 3 + True + 0.5 + 0.5 + 1 + 1 + 5 + 0 + 20 + 30 + + + + True + False + 10 + + + + True + True + True + + + 0 + False + True + + + + + + True + Ou restaurer + True + True + True + 0 + /tmp/bacula-restore + True + * + False + + + 0 + True + True + + + + + + + + + + True + <b>Destination</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + False + True + + + + + + 3 + True + True + False + 0 + + + + True + 0.5 + 0.5 + 1 + 1 + 3 + 3 + 12 + 5 + + + + True + 4 + 2 + False + 5 + 15 + + + + True + True + Ecrasement + True + GTK_RELIEF_NORMAL + True + True + False + True + + + 0 + 1 + 0 + 1 + fill + + + + + + + True + Storage + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 1 + 2 + 1 + 2 + fill + + + + + + + True + FileSet + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 1 + 2 + 2 + 3 + fill + + + + + + + True + Job + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 1 + 2 + 3 + 4 + fill + + + + + + + True + False + True + + + 0 + 1 + 1 + 2 + fill + + + + + + True + True + True + + + 0 + 1 + 2 + 3 + fill + + + + + + True + True + True + + + 0 + 1 + 3 + 4 + fill + + + + + + True + False + 3 + + + + True + True + 1 + 0 + True + GTK_UPDATE_ALWAYS + False + False + 10 1 100 1 10 10 + + + 0 + True + True + + + + + + True + Priorité + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + 1 + 2 + 0 + 1 + fill + fill + + + + + + + + + + True + <b>Options</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + 0 + True + True + + + + + + + + Informations + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + True + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + True + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + -5 + + + + + + 0 + False + True + GTK_PACK_END + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + 3 + True + True + GTK_POLICY_NEVER + GTK_POLICY_NEVER + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_NONE + True + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + + + + + + True + <b>Copiez ceci dans votre bconsole</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + + + + Warning + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + True + 200 + True + True + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + True + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-close + True + GTK_RELIEF_NORMAL + True + -7 + + + + + + 0 + False + True + GTK_PACK_END + + + + + + True + False + 0 + + + + True + gtk-dialog-warning + 6 + 0.5 + 0.5 + 5 + 0 + + + 0 + False + False + + + + + + True + True + + False + True + GTK_JUSTIFY_LEFT + False + True + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + True + True + + + + + 0 + True + True + + + + + + + diff --git a/gui/brestore/brestore.console_apps b/gui/brestore/brestore.console_apps new file mode 100644 index 0000000000..2630d85661 --- /dev/null +++ b/gui/brestore/brestore.console_apps @@ -0,0 +1,3 @@ +USER=root +PROGRAM=/usr/bin/brestore.pl +SESSION=true diff --git a/gui/brestore/brestore.desktop b/gui/brestore/brestore.desktop new file mode 100644 index 0000000000..f5ff3fee38 --- /dev/null +++ b/gui/brestore/brestore.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Version=1.39 +Encoding=UTF-8 +Type=Application +Name=Brestore +GenericName=Backup application +Comment=Bacula restoration gui +Exec=brestore.pl +Icon=brestore.png +Categories=Archiving;Utility diff --git a/gui/brestore/brestore.desktop.consolehelper b/gui/brestore/brestore.desktop.consolehelper new file mode 100644 index 0000000000..70c78aacad --- /dev/null +++ b/gui/brestore/brestore.desktop.consolehelper @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=Brestore +Comment=Bacula Restoration GUI +Icon=/usr/share/pixmaps/brestore.png +Exec=/usr/bin/brestore +Terminal=false +Type=Application +Encoding=UTF-8 +X-Desktop-File-Install-Version=0.3 +Categories=System;Application;Utility; +StartupNotify=true diff --git a/gui/brestore/brestore.desktop.xsu b/gui/brestore/brestore.desktop.xsu new file mode 100644 index 0000000000..37bc2a2831 --- /dev/null +++ b/gui/brestore/brestore.desktop.xsu @@ -0,0 +1,11 @@ +[Desktop Entry] +Name=Brestore +Comment=Bacula Restoration GUI +Icon=/usr/share/pixmaps/brestore.png +Exec=gnomesu -t "Query" -c "/usr/bin/brestore.pl" -d -e -m "In order to run the bacula restore gui as root, ^additional information is required." +Terminal=false +Type=Application +Encoding=UTF-8 +X-Desktop-File-Install-Version=0.3 +Categories=System;Application;Utility; +StartupNotify=true diff --git a/gui/brestore/brestore.glade b/gui/brestore/brestore.glade new file mode 100644 index 0000000000..b32a8e693e --- /dev/null +++ b/gui/brestore/brestore.glade @@ -0,0 +1,3301 @@ + + + + + + + True + brestore + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 1024 + 768 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + False + + + + + + + True + False + 0 + + + + + True + GTK_PACK_DIRECTION_LTR + GTK_PACK_DIRECTION_LTR + + + + True + _File + True + + + + + + + True + + + + + + True + gtk-quit + True + + + + + + + + + + + True + _Tools + True + + + + + + + True + _Preferences + True + + + + + + + + + + + + True + _Help + True + + + + + + + True + _About + True + + + + + + + + + + 0 + False + False + + + + + + True + GTK_ORIENTATION_HORIZONTAL + GTK_TOOLBAR_ICONS + True + True + + + + True + Parent directory + gtk-go-up + True + True + False + + + + False + True + + + + + + True + True + True + True + + + False + False + + + + + + True + Preferences + gtk-preferences + True + True + False + + + + False + True + + + + + + True + Go to Bweb + gtk-jump-to + True + True + False + + + + False + True + + + + + + + + + + True + True + True + False + + + + + + + False + False + + + + + 0 + False + False + + + + + + True + GTK_ORIENTATION_HORIZONTAL + GTK_TOOLBAR_BOTH + True + True + + + + True + True + True + False + + + False + False + + + + + + True + True + True + False + + + + True + True + True + + + + + + False + False + + + + + + 5 + True + True + True + False + + + + True + + + + + False + False + + + + + + True + True + True + False + + + + Last 20 jobs +This week +This month +This year + False + True + + + + + + False + False + + + + + + True + True + True + False + + + + True + True + True + + + + + + False + False + + + + + + 5 + True + True + True + False + + + + True + + + + + False + False + + + + + + True + True + True + False + + + + True + True + True + GTK_RELIEF_NORMAL + True + + + + + True + 0.5 + 0.5 + 0 + 0 + 0 + 0 + 0 + 0 + + + + True + False + 2 + + + + True + gtk-jump-to + 4 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + Location + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + + + + + + False + False + + + + + + True + False + True + True + + + False + False + + + + + + True + True + True + False + + + + True + GTK_BUTTONBOX_DEFAULT_STYLE + 0 + + + + + False + False + + + + + + True + True + True + False + + + + 230 + True + True + True + True + 0 + + True + * + False + + + + + + False + False + + + + + 0 + False + False + + + + + + True + True + 430 + + + + True + True + 600 + + + + 3 + True + 0 + 0.5 + GTK_SHADOW_ETCHED_IN + + + + 3 + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + 3 + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_ETCHED_IN + GTK_CORNER_TOP_LEFT + + + + + True + True + True + True + True + False + True + False + False + False + + + + + + + + + + + + + + + True + <b>File list</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + True + False + + + + + + 3 + True + 0 + 0.5 + GTK_SHADOW_ETCHED_IN + + + + 3 + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + 3 + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_ETCHED_IN + GTK_CORNER_TOP_LEFT + + + + + True + True + True + True + True + True + True + False + False + False + + + + + + + + + + + True + <b>File revisions</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + True + True + + + + + True + False + + + + + + 3 + True + False + 0 + + + + True + False + 0 + + + + True + True + <big><b>Restore items list</b></big> + False + True + GTK_JUSTIFY_CENTER + False + True + 0.5 + 0.5 + 0 + 7 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + GTK_BUTTONBOX_END + 0 + + + + True + True + True + gtk-clear + True + GTK_RELIEF_NORMAL + True + + + + + + + True + Can be very long... + True + True + GTK_RELIEF_NORMAL + True + + + + + True + 0.5 + 0.5 + 0 + 0 + 0 + 0 + 0 + 0 + + + + True + False + 2 + + + + True + gtk-zoom-in + 4 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + Estimate + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + + + + + + + True + True + True + GTK_RELIEF_NORMAL + True + + + + + True + 0.5 + 0.5 + 0 + 0 + 0 + 0 + 0 + 0 + + + + True + False + 2 + + + + True + gtk-revert-to-saved + 4 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + Restore + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + + + + + + + True + Generate a bootstrap file + True + True + GTK_RELIEF_NORMAL + True + + + + + True + 0.5 + 0.5 + 0 + 0 + 0 + 0 + 0 + 0 + + + + True + False + 2 + + + + True + gtk-justify-left + 4 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + Gen BSR + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + + + + + + 0 + True + True + GTK_PACK_END + + + + + 0 + False + True + + + + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + + True + True + True + False + False + False + False + False + False + + + + + + + 0 + True + True + + + + + + 1 + True + False + + + 0 + False + False + + + + + True + True + + + + + 0 + True + True + + + + + + + + About + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_MOUSE + False + False + True + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + True + + + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + -5 + + + + + + 0 + False + True + GTK_PACK_END + + + + + + 4 + True + False + 0 + + + + True + True + <big><b>brestore a Perl/GTK interface for bacula</b></big> + False + True + GTK_JUSTIFY_CENTER + False + True + 0.5 + 0.5 + 0 + 7 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + <small>Bacula Copyright ©2000-2006FSF Europe</small> + + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 4 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + 0 + True + True + + + + + + + + Preferences + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_MOUSE + False + True + True + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + True + + + + + + True + False + 4 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + -6 + + + + + + + True + True + True + gtk-apply + True + GTK_RELIEF_NORMAL + True + -10 + + + + + + + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + -5 + + + + + + 0 + False + True + GTK_PACK_END + + + + + + True + True + True + True + GTK_POS_TOP + False + False + + + + True + False + 0 + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + False + 0 + + + + True + True + Example : DBI:mysql:database=bacula;host=192.168.0.2;port=3306 +Example : DBI:Pg:database=bacula;host=127.0.0.1;port=5432 + + False + False + GTK_JUSTIFY_LEFT + False + True + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + 5 + True + 3 + 2 + False + 4 + 0 + + + + True + DBD Chain : + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 8 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 0 + 1 + fill + + + + + + + True + Username : + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 4 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 1 + 2 + fill + + + + + + + True + Password : + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 4 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 2 + 3 + fill + + + + + + + True + True + True + True + True + 0 + + True + * + False + + + 1 + 2 + 0 + 1 + + + + + + + True + True + True + False + 40 + + True + * + False + 40 + + + 1 + 2 + 2 + 3 + + + + + + + True + True + True + True + 40 + + True + * + False + 40 + + + 1 + 2 + 1 + 2 + + + + + + 0 + False + True + + + + + + + + + + True + <b>SQL connection</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + False + True + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + False + 0 + + + + True + JobStatus is set to 'T' + True + Use only good ended backup + True + GTK_RELIEF_NORMAL + True + False + False + True + + + 0 + False + False + + + + + + True + True + See all file versions + True + GTK_RELIEF_NORMAL + True + False + False + True + + + 0 + False + False + + + + + + + + + + True + <b>Selections</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + False + True + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + True + Display debug message + True + GTK_RELIEF_NORMAL + True + False + False + True + + + + + + + + True + <b>Debug</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + False + True + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + 1 + 1 + False + 0 + 0 + + + + 6 + True + False + 0 + + + + True + /path/to/brestore.glade + True + True + True + 0 + + True + ● + False + + + 0 + False + False + + + + + 0 + 1 + 0 + 1 + + + + + + + + + + True + <b>Glade file</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + False + True + + + + + False + True + + + + + + True + General options + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + + + + True + False + 0 + + + + True + To launch restore, brestore have to speak with bacula. + False + False + GTK_JUSTIFY_LEFT + True + False + 0.5 + 0.5 + 0 + 6 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + 4 + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 5 + + + + 3 + True + False + 3 + + + + True + True + To launch a restore job, bacula need a BSR file. +This file must be <b>local</b> to director, so + +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 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 + False + True + 0.129999995232 + 0.5 + 0 + 3 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + BSR destination string (dont use file://tmp) + True + True + True + 0 + file://var/tmp + True + * + False + + + 0 + False + False + + + + + + + + + + True + <b>BSR destination string</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + False + False + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + 8 + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 5 + + + + True + False + 0 + + + + True + /path/to/bconsole -n -c /path/to/bconsole.conf +or +http://user:pass@director/cgi-bin/bweb/bconsole.pl + True + True + True + 0 + /usr/local/bacula/sbin/bconsole -n -c /usr/local/bacula/etc/bconsole.conf + True + * + False + + + 0 + False + False + + + + + + + + + + True + <b>bconsole command</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + 8 + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 5 + + + + True + Your default restore job name (see bacula-dir.conf) + True + True + True + 0 + RestoreFiles + True + ● + False + + + + + + + + True + <b>Default restore job</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + 2 + 2 + False + 3 + 3 + + + + True + Bweb url : + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 0 + 1 + fill + + + + + + + True + Mozilla path : + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 1 + 2 + fill + + + + + + + True + http://bacula-dir/cgi-bin/bweb/bweb.pl (or keep empty) + True + True + True + 0 + + True + ● + False + + + 1 + 2 + 0 + 1 + + + + + + + True + /path/to/mozilla + True + True + True + 0 + + True + ● + False + + + 1 + 2 + 1 + 2 + + + + + + + + + + + True + <b>Follow restoration</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + False + True + + + + + + True + Bacula connection + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + + + 0 + True + True + + + + + + + + Versions + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 650 + 270 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + False + + + + True + False + 0 + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + False + 3 + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_ETCHED_IN + GTK_CORNER_TOP_LEFT + + + + + True + True + True + True + True + False + True + False + False + False + + + + + + 0 + True + True + + + + + + + + + + True + <b>File revisions</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + + True + + + 0 + False + True + + + + + + 3 + True + GTK_BUTTONBOX_END + 3 + + + + True + True + True + gtk-close + True + GTK_RELIEF_NORMAL + True + + + + + + 0 + False + True + + + + + + + + + + + True + Add to the restore list + True + + + + + + + True + See all versions + True + + + + + + + + + + True + Add to restore list + True + + + + + + + True + Ready to submit to bacula ? + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + False + False + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + True + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + -6 + + + + + + + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + -5 + + + + + + 0 + False + True + GTK_PACK_END + + + + + + True + False + 0 + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + True + False + 0 + + + + True + This job will use following volume(s) : + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 10 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + 3 + True + True + GTK_POLICY_NEVER + GTK_POLICY_NEVER + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + False + False + False + False + + + + + 0 + True + True + + + + + + + + + + True + <b>Informations</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + 3 + True + 0.5 + 0.5 + 1 + 1 + 5 + 0 + 20 + 30 + + + + True + False + 10 + + + + True + True + True + + + 0 + False + True + + + + + + True + Where to restore + True + True + True + 0 + /tmp/bacula-restore + True + * + False + + + 0 + True + True + + + + + + + + + + True + <b>Destination</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + False + True + + + + + + 3 + True + True + False + 0 + + + + True + 0.5 + 0.5 + 1 + 1 + 3 + 3 + 12 + 5 + + + + True + 4 + 2 + False + 5 + 15 + + + + True + True + Replace + True + GTK_RELIEF_NORMAL + True + True + False + True + + + 0 + 1 + 0 + 1 + fill + + + + + + + True + Storage + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 1 + 2 + 1 + 2 + fill + + + + + + + True + FileSet + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 1 + 2 + 2 + 3 + fill + + + + + + + True + Job + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 1 + 2 + 3 + 4 + fill + + + + + + + True + False + True + + + 0 + 1 + 1 + 2 + fill + + + + + + True + True + True + + + 0 + 1 + 2 + 3 + fill + + + + + + True + True + True + + + 0 + 1 + 3 + 4 + fill + + + + + + True + False + 3 + + + + True + True + 1 + 0 + True + GTK_UPDATE_ALWAYS + False + False + 10 1 100 1 10 10 + + + 0 + True + True + + + + + + True + Priority + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + 1 + 2 + 0 + 1 + fill + fill + + + + + + + + + + True + <b>More Options</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + 0 + True + True + + + + + + + + Informations + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + True + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + True + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + -5 + + + + + + 0 + False + True + GTK_PACK_END + + + + + + True + 0 + 0.5 + GTK_SHADOW_NONE + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 12 + 0 + + + + 3 + True + True + GTK_POLICY_NEVER + GTK_POLICY_NEVER + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_NONE + True + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + + + + + + True + <b>Past this in your bconsole</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + label_item + + + + + 0 + True + True + + + + + + + + Warning + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + True + 200 + True + True + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + True + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + gtk-close + True + GTK_RELIEF_NORMAL + True + -7 + + + + + + 0 + False + True + GTK_PACK_END + + + + + + True + False + 0 + + + + True + gtk-dialog-warning + 6 + 0.5 + 0.5 + 5 + 0 + + + 0 + False + False + + + + + + True + True + + False + True + GTK_JUSTIFY_LEFT + False + True + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + True + True + + + + + 0 + True + True + + + + + + + diff --git a/gui/brestore/brestore.pamd b/gui/brestore/brestore.pamd new file mode 100644 index 0000000000..15cb90fa87 --- /dev/null +++ b/gui/brestore/brestore.pamd @@ -0,0 +1,7 @@ +#%PAM-1.0 +auth sufficient pam_rootok.so +auth sufficient pam_timestamp.so +auth required pam_stack.so service=system-auth +session optional pam_xauth.so +session optional pam_timestamp.so +account required pam_permit.so diff --git a/gui/brestore/brestore.pl b/gui/brestore/brestore.pl new file mode 100755 index 0000000000..b88177a9c2 --- /dev/null +++ b/gui/brestore/brestore.pl @@ -0,0 +1,3318 @@ +#!/usr/bin/perl -w + +use strict; + +# path to your brestore.glade +my $glade_file = 'brestore.glade' ; + +=head1 NAME + + brestore.pl - A Perl/Gtk console for Bacula + +=head1 VERSION + + $Id$ + +=head1 INSTALL + + Setup ~/.brestore.conf to find your brestore.glade + + On debian like system, you need : + - libgtk2-gladexml-perl + - libdbd-mysql-perl or libdbd-pg-perl + - libexpect-perl + + You have to add brestore_xxx tables to your catalog. + + To speed up database query you have to create theses indexes + - CREATE INDEX file_pathid on File(PathId); + - ... + + To follow restore job, you must have a running Bweb installation. + +=head1 COPYRIGHT + + Bacula® - The Network Backup Solution + + Copyright (C) 2000-2006 Free Software Foundation Europe e.V. + + Brestore authors are Marc Cousin and Eric Bollengier. + The main author of Bacula is Kern Sibbald, with contributions from + many others, a complete list can be found in the file AUTHORS. + + This program is Free Software; you can redistribute it and/or + modify it under the terms of version two of the GNU General Public + License as published by the Free Software Foundation plus additions + that are listed in the file LICENSE. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + + Bacula® is a registered trademark of John Walker. + The licensor of Bacula is the Free Software Foundation Europe + (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich, + Switzerland, email:ftf@fsfeurope.org. + + Base 64 functions from Karl Hakimian + Integrally copied from recover.pl from bacula source distribution. + +=cut + +use Gtk2; # auto-initialize Gtk2 +use Gtk2::GladeXML; +use Gtk2::SimpleList; # easy wrapper for list views +use Gtk2::Gdk::Keysyms; # keyboard code constants +use Data::Dumper qw/Dumper/; +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 + + # 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/], + }; + + $self->read_config(); + + return $self; +} + +sub read_config +{ + 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}; + } + + } else { + # TODO : Force dumb default values and display a message + } +} + +sub write_config +{ + my ($self) = @_; + + $self->{error} = ''; + 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 + { + $self->{error} = "Can't write configuration $!"; + } + return $self->{error}; +} + +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; + +################################################################ + +package Bbase; + +sub new +{ + my ($class, %arg) = @_; + + my $self = bless { + conf => $arg{conf}, + }, $class; + + return $self; +} + +use Data::Dumper; + +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"; + } + } +} + +sub dbh_strcat +{ + my ($self, @what) = @_; + if ($self->{conf}->{connection_string} =~ /dbi:pg/i) { + return join(' || ', @what); + } else { + return 'CONCAT(' . join(',', @what) . ')' ; + } +} + +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) = @_; + return $self->{conf}->{dbh}; +} + +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(); + } + + 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}); + + } 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; + +################################################################ + +package DlgFileVersion; + +sub on_versions_close_clicked +{ + 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; + +################################################################ + +package DlgLaunch; + +#use Bconsole; + +# %arg = (bsr_file => '/path/to/bsr', # on director +# volumes => [ '00001', '00004'] +# pref => ref Pref +# ); + +sub get_bconsole +{ + my ($pref) = (@_); + + if ($pref->{bconsole} =~ /^http/) { + return new BwebConsole(pref => $pref); + } else { + if (eval { require Bconsole; }) { + return new Bconsole(pref => $pref); + } else { + new DlgWarn("Can't use bconsole, verify your setup"); + return undef; + } + } +} + +sub new +{ + my ($class, %arg) = @_; + + my $self = bless { + bsr_file => $arg{bsr_file}, # /path/to/bsr on director + pref => $arg{pref}, # Pref ref + glade => undef, # GladeXML ref + bconsole => undef, # Bconsole ref + }; + + my $console = $self->{bconsole} = get_bconsole($arg{pref}); + unless ($console) { + return undef; + } + + # we load launch widget of $glade_file + my $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, + "dlg_launch"); + + # Connect signals magically + $glade->signal_autoconnect_from_package($self); + + my $widget = $glade->get_widget('volumeview'); + my $volview = Gtk2::SimpleList->new_from_treeview( + $widget, + 'InChanger' => 'pixbuf', + 'Volume' => 'text', + ); + + my $infos = get_volume_inchanger($arg{pref}->{dbh}, $arg{volumes}) ; + + # we replace 0 and 1 by $noicon and $yesicon + for my $i (@{$infos}) { + if ($i->[0] == 0) { + $i->[0] = $DlgResto::noicon; + } else { + $i->[0] = $DlgResto::yesicon; + } + } + + # fill volume view + push @{ $volview->{data} }, @{$infos} ; + + $console->prepare(qw/list_client list_job list_fileset list_storage/); + + # fill client combobox (with director defined clients + my @clients = $console->list_client() ; # get from bconsole + if ($console->{error}) { + new DlgWarn("Can't use bconsole:\n$arg{pref}->{bconsole}: $console->{error}") ; + } + my $w = $self->{combo_client} = $glade->get_widget('combo_launch_client') ; + $self->{list_client} = DlgResto::init_combo($w, 'text'); + DlgResto::fill_combo($self->{list_client}, + $DlgResto::client_list_empty, + @clients); + $w->set_active(0); + + # fill fileset combobox + my @fileset = $console->list_fileset() ; + $w = $self->{combo_fileset} = $glade->get_widget('combo_launch_fileset') ; + $self->{list_fileset} = DlgResto::init_combo($w, 'text'); + DlgResto::fill_combo($self->{list_fileset}, '', @fileset); + + # fill job combobox + my @job = $console->list_job() ; + $w = $self->{combo_job} = $glade->get_widget('combo_launch_job') ; + $self->{list_job} = DlgResto::init_combo($w, 'text'); + DlgResto::fill_combo($self->{list_job}, '', @job); + + # find default_restore_job in jobs list + my $default_restore_job = $arg{pref}->{default_restore_job} ; + my $index=0; + my $i=1; # 0 is '' + for my $j (@job) { + if ($j =~ /$default_restore_job/io) { + $index=$i; + last; + } + $i++; + } + $w->set_active($index); + + # fill storage combobox + my @storage = $console->list_storage() ; + $w = $self->{combo_storage} = $glade->get_widget('combo_launch_storage') ; + $self->{list_storage} = DlgResto::init_combo($w, 'text'); + DlgResto::fill_combo($self->{list_storage}, '', @storage); + + $glade->get_widget('dlg_launch')->show_all(); + + return $self; +} + +sub show_job +{ + my ($self, $client, $jobid) = @_; + + 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. +To follow it, you must use bconsole (or install/configure bweb)"); + $widget->run; + $widget->destroy(); + } +} + +sub on_cancel_resto_clicked +{ + my ($self) = @_ ; + $self->{glade}->get_widget('dlg_launch')->destroy(); +} + +sub on_submit_resto_clicked +{ + my ($self) = @_ ; + my $glade = $self->{glade}; + + my $r = $self->copy_bsr($self->{bsr_file}, $self->{pref}->{bsr_dest}) ; + + unless ($r) { + new DlgWarn("Can't copy bsr file to director ($self->{error})"); + return; + } + + my $fileset = $glade->get_widget('combo_launch_fileset') + ->get_active_text(); + + my $storage = $glade->get_widget('combo_launch_storage') + ->get_active_text(); + + my $where = $glade->get_widget('entry_launch_where')->get_text(); + + my $job = $glade->get_widget('combo_launch_job') + ->get_active_text(); + + if (! $job) { + new DlgWarn("Can't use this job"); + return; + } + + my $client = $glade->get_widget('combo_launch_client') + ->get_active_text(); + + if (! $client or $client eq $DlgResto::client_list_empty) { + new DlgWarn("Can't use this client ($client)"); + return; + } + + my $prio = $glade->get_widget('spin_launch_priority')->get_value(); + + my $replace = $glade->get_widget('chkbp_launch_replace')->get_active(); + $replace=($replace)?'always':'never'; + + my $jobid = $self->{bconsole}->run(job => $job, + client => $client, + storage => $storage, + fileset => $fileset, + where => $where, + replace => $replace, + priority=> $prio, + bootstrap => $r); + + $self->show_job($client, $jobid); +} + +sub on_combo_storage_button_press_event +{ + my ($self) = @_; + print "on_combo_storage_button_press_event()\n"; +} + +sub on_combo_fileset_button_press_event +{ + my ($self) = @_; + print "on_combo_fileset_button_press_event()\n"; + +} + +sub on_combo_job_button_press_event +{ + my ($self) = @_; + print "on_combo_job_button_press_event()\n"; +} + +sub get_volume_inchanger +{ + my ($dbh, $vols) = @_; + + my $lst = join(',', map { $dbh->quote($_) } @{ $vols } ) ; + + my $rq = "SELECT InChanger, VolumeName + FROM Media + WHERE VolumeName IN ($lst) + "; + + my $res = $dbh->selectall_arrayref($rq); + return $res; # [ [ 1, VolName].. ] +} + + +use File::Copy qw/copy/; +use File::Basename qw/basename/; + +# We must kown the path+filename destination +# $self->{error} contains error message +# it return 0/1 if fail/success +sub copy_bsr +{ + my ($self, $src, $dst) = @_ ; + print "$src => $dst\n" + if ($debug); + + if (!$dst) { + return $src; + } + + my $ret=0 ; + my $err ; + my $dstfile; + + if ($dst =~ m!file:/(/.+)!) { + $ret = copy($src, $1); + $err = $!; + $dstfile = "$1/" . basename($src) ; + + } elsif ($dst =~ m!scp://([^:]+:(.+))!) { + $err = `scp $src $1 2>&1` ; + $ret = ($? == 0) ; + $dstfile = "$2/" . basename($src) ; + + } else { + $ret = 0; + $err = "$dst not implemented yet"; + File::Copy::copy($src, \*STDOUT); + } + + $self->{error} = $err; + + if ($ret == 0) { + $self->{error} = $err; + return ''; + + } else { + return $dstfile; + } +} +1; + +################################################################ + +package DlgAbout; + +my $about_widget; + +sub display +{ + unless ($about_widget) { + my $glade_box = Gtk2::GladeXML->new($glade_file, "dlg_about") ; + $about_widget = $glade_box->get_widget("dlg_about") ; + $glade_box->signal_autoconnect_from_package('DlgAbout'); + } + $about_widget->show() ; +} + +sub on_about_okbutton_clicked +{ + $about_widget->hide() ; +} + +1; + +################################################################ + +package DlgResto; +use base qw/Bbase/; + +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 ); + + $widget->set_model($lst); + my $i=0; + for my $t (@type) { + my $cell; + if ($t eq 'text' or $t eq 'markup') { + $cell = new Gtk2::CellRendererText(); + } + $widget->pack_start($cell, 1); + $widget->add_attribute($cell, $t, $i++); + } + return $lst; +} + +# fill simple combo (one element per row) +sub fill_combo +{ + my ($list, @what) = @_; + + $list->clear(); + + foreach my $w (@what) + { + chomp($w); + my $i = $list->append(); + $list->set($i, 0, $w); + } +} + +# display Mb/Gb/Kb +sub human +{ + my @unit = qw(b Kb Mb Gb Tb); + my $val = shift; + my $i=0; + my $format = '%i %s'; + while ($val / 1024 > 1) { + $i++; + $val /= 1024; + } + $format = ($i>0)?'%0.1f %s':'%i %s'; + return sprintf($format, $val, $unit[$i]); +} + +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 ($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()) + { + $_->set_resizable(1); + $_->set_sizing('grow-only'); + } +} + +sub new +{ + my ($class, $pref) = @_; + my $self = bless { + conf => $pref, + dirtree => undef, + CurrentJobIds => [], + location => undef, # location entry widget + mainwin => undef, # mainwin widget + filelist_file_menu => undef, # file menu widget + filelist_dir_menu => undef, # dir menu widget + glade => undef, # glade object + status => undef, # status bar widget + dlg_pref => undef, # DlgPref object + fileattrib => {}, # cache file + fileview => undef, # fileview widget SimpleList + fileinfo => undef, # fileinfo widget SimpleList + cwd => '/', + client_combobox => undef, # client_combobox widget + restore_backup_combobox => undef, # date combobox widget + list_client => undef, # Gtk2::ListStore + list_backup => undef, # Gtk2::ListStore + 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); + $self->{filelist_file_menu} = $glade->get_widget("filelist_file_menu"); + + $glade = Gtk2::GladeXML->new($glade_file, "filelist_dir_menu"); + $glade->signal_autoconnect_from_package($self); + $self->{filelist_dir_menu} = $glade->get_widget("filelist_dir_menu"); + + $glade = $self->{glade} = Gtk2::GladeXML->new($glade_file, "dlg_resto"); + $glade->signal_autoconnect_from_package($self); + + $self->{status} = $glade->get_widget('statusbar'); + $self->{mainwin} = $glade->get_widget('dlg_resto'); + $self->{location} = $glade->get_widget('entry_location'); + $self->render_icons(); + + $self->{dlg_pref} = new DlgPref($pref); + + my $c = $self->{client_combobox} = $glade->get_widget('combo_client'); + $self->{list_client} = init_combo($c, 'text'); + + $c = $self->{restore_backup_combobox} = $glade->get_widget('combo_list_backups'); + $self->{list_backup} = init_combo($c, 'text', 'markup'); + + # Connect glade-fileview to Gtk2::SimpleList + # and set up drag n drop between $fileview and $restore_list + + # WARNING : we have big dirty thinks with gtk/perl and utf8/iso strings + # we use an hidden field uuencoded to bypass theses bugs (h_name) + + 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', + + '' => 'pixbuf', + 'File Name' => 'text', + 'Size' => 'text', + 'Date' => 'text'); + init_drag_drop($fileview); + $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', #5 + + '' => 'pixbuf', + 'File Name' => 'text', + 'JobId' => 'text', + 'FileIndex' => 'text', + + 'Nb Files' => 'text', #10 + 'Size' => 'text', #11 + 'size_b' => 'hidden', #12 + ); + + my @restore_list_target_table = ({'target' => 'STRING', + 'flags' => [], + 'info' => 40 }); + + $restore_list->enable_model_drag_dest(['copy'],@restore_list_target_table); + $restore_list->get_selection->set_mode('multiple'); + + $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', + + 'InChanger' => 'pixbuf', + 'Volume' => 'text', + 'JobId' => 'text', + 'Size' => 'text', + 'Date' => 'text', + 'MD5' => 'text'); + + init_drag_drop($infoview); + + $pref->connect_db() || $self->{dlg_pref}->display($self); + + if ($pref->{dbh}) { + $self->init_server_backup_combobox(); + $self->{bvfs}->create_brestore_tables(); + } + + $self->set_status($pref->{error}); +} + +# set status bar informations +sub set_status +{ + my ($self, $string) = @_; + return unless ($string); + + my $context = $self->{status}->get_context_id('Main'); + $self->{status}->push($context, $string); +} + +sub on_time_select_changed +{ + my ($self) = @_; +} + +sub get_active_time +{ + my ($self) = @_; + my $c = $self->{glade}->get_widget('combo_time'); + return $c->get_active_text; +} + +# This sub returns all clients declared in DB +sub get_all_clients +{ + my $dbh = shift; + my $query = "SELECT Name FROM Client ORDER BY Name"; + print STDERR $query,"\n" if $debug; + + my $result = $dbh->selectall_arrayref($query); + + return map { $_->[0] } @$result; +} + +# init infoview widget +sub clear_infoview +{ + my $self = shift; + @{$self->{fileinfo}->{data}} = (); +} + +# init restore_list +sub on_clear_clicked +{ + my $self = shift; + @{$self->{restore_list}->{data}} = (); +} + +sub on_estimate_clicked +{ + my ($self) = @_; + + my $size_total=0; + my $nb_total=0; + + # TODO : If we get here, things could get lenghty ... draw a popup window . + my $widget = Gtk2::MessageDialog->new($self->{mainwin}, + 'destroy-with-parent', + 'info', 'close', + 'Computing size...'); + $widget->show; + refresh_screen(); + + 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->[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->[2]); + + $txt .= "\n$name : ". $entry->[10] ." file(s)/". $entry->[11] ; + $self->debug($title . $txt); + $widget->set_markup($title . $txt); + + $size_total+=$entry->[12]; + $nb_total+=$entry->[10]; + refresh_screen(); + } + + $txt .= "\n\nTotal : $nb_total file(s)/" . human($size_total); + $widget->set_markup("Size estimation :\n" . $txt); + $widget->signal_connect ("response", sub { my $w=shift; $w->destroy();}); + + return 0; +} + + + +sub on_gen_bsr_clicked +{ + my ($self) = @_; + + my @options = ("Choose a bsr file", $self->{mainwin}, 'save', + 'gtk-save','ok', 'gtk-cancel', 'cancel'); + + + my $w = new Gtk2::FileChooserDialog ( @options ); + my $ok = 0; + my $save; + while (!$ok) { + my $a = $w->run(); + if ($a eq 'cancel') { + $ok = 1; + } + + if ($a eq 'ok') { + my $f = $w->get_filename(); + if (-f $f) { + my $dlg = Gtk2::MessageDialog->new($self->{mainwin}, + 'destroy-with-parent', + 'warning', 'ok-cancel', 'This file already exists, do you want to overwrite it ?'); + if ($dlg->run() eq 'ok') { + $save = $f; + } + $dlg->destroy(); + } else { + $save = $f; + } + $ok = 1; + } + } + + $w->destroy(); + + if ($save) { + if (open(FP, ">$save")) { + my $bsr = $self->create_filelist(); + print FP $bsr; + close(FP); + $self->set_status("Dumping BSR to $save ok"); + } else { + $self->set_status("Can't dump BSR to $save: $!"); + } + } +} + + +use File::Temp qw/tempfile/; + +sub on_go_button_clicked +{ + my $self = shift; + unless (scalar(@{$self->{restore_list}->{data}})) { + new DlgWarn("No file to restore"); + return 0; + } + my $bsr = $self->create_filelist(); + my ($fh, $filename) = tempfile(); + $fh->print($bsr); + close($fh); + chmod(0644, $filename); + + print "Dumping BSR info to $filename\n" + if ($debug); + + # we get Volume list + my %a = map { $_ => 1 } ($bsr =~ /Volume="(.+)"/g); + my $vol = [ keys %a ] ; # need only one occurrence of each volume + + 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', + 'I' => '$label I', + 'B' => '$label B', + + 'A' => '$label', + 'T' => '$label', + 'E' => '$label', + ); + +sub on_list_client_changed +{ + my ($self, $widget) = @_; + return 0 unless defined $self->{fileview}; + + $self->{list_backup}->clear(); + + if ($self->current_client eq $client_list_empty) { + return 0 ; + } + + $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->update_brestore_table(@{$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(); + + my $label = $endtime->[1] . " (" . $endtime->[4] . ")"; + eval "\$label = \"$type_markup{$endtime->[2]}\""; # job type + eval "\$label = \"$type_markup{$endtime->[3]}\""; # job status + + $self->{list_backup}->set($i, + 0, $endtime->[0], + 1, $label, + ); + } + $self->{restore_backup_combobox}->set_active(0); + 0; +} + + +sub fill_server_list +{ + my ($dbh, $combo, $list) = @_; + + my @clients=get_all_clients($dbh); + + $list->clear(); + + my $i = $list->append(); + $list->set($i, 0, $client_list_empty); + + foreach my $client (@clients) + { + $i = $list->append(); + $list->set($i, 0, $client); + } + $combo->set_active(0); +} + +sub init_server_backup_combobox +{ + my $self = shift ; + fill_server_list($self->{conf}->{dbh}, + $self->{client_combobox}, + $self->{list_client}) ; +} + +#---------------------------------------------------------------------- +#Refreshes the file-view Redraws everything. The dir data is cached, the file +#data isn't. There is additionnal complexity for dirs (visibility problems), +#so the @CurrentJobIds is not sufficient. +sub refresh_fileview +{ + my ($self) = @_; + my $fileview = $self->{fileview}; + my $client_combobox = $self->{client_combobox}; + my $bvfs = $self->{bvfs}; + + @{$fileview->{data}} = (); + + $self->clear_infoview(); + + my $client_name = $self->current_client; + + if (!$client_name or ($client_name eq $client_list_empty)) { + $self->set_status("Client list empty"); + return; + } + + # [ [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(Bvfs::lstat_attrib($dir_entry->[2],'st_mtime')); + $total_bytes += 4096; + $file_count++; + + listview_push($fileview, + $dir_entry->[0], + 0, + $dir_entry->[1], + # TODO: voir ce que l'on met la + $dir_entry->[3], + 'dir', + + $diricon, + $dir_entry->[1], + "4 Kb", + $time); + } + + # Add files to view + foreach my $file (@$files) + { + my $size = Bvfs::file_attrib($file,'st_size'); + my $time = localtime(Bvfs::file_attrib($file,'st_mtime')); + $total_bytes += $size; + $file_count++; + # $file = [filenameid,listfiles.id,listfiles.Name,File.LStat,File.JobId] + listview_push($fileview, + $bvfs->{cwd}, + $file->[0], + $file->[2], + $file->[4], + 'file', + + $fileicon, + $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); +} + + +sub on_about_activate +{ + DlgAbout::display(); +} + +sub drag_set_info +{ + my ($tree, $path, $data) = @_; + + my @items = listview_get_all($tree) ; + my @ret; + foreach my $i (@items) + { + my @file_info = @{$i}; + + # doc ligne 93 + # Ok, we have a corner case : + # path can be empty + 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 + ); + } + + my $data_get = join(" :: ", @ret); + + $data->set_text($data_get,-1); +} + + + +sub fileview_data_get +{ + my ($self, $widget, $context, $data, $info, $time,$string) = @_; + drag_set_info($widget, $self->{cwd}, $data); +} + +sub fileinfo_data_get +{ + my ($self, $widget, $context, $data, $info, $time,$string) = @_; + drag_set_info($widget, $self->{cwd}, $data); +} + +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, $pathid, $filenameid, $jobid, $type) = + split(/ ; /, $elt); + $file = unpack("u", $file); + + $self->add_selected_file_to_list($pathid,$filenameid, + $file, $jobid, $type); + } + } + $self->debug("end\n"); +} + +sub on_back_button_clicked { + my $self = shift; + $self->{bvfs}->up_dir(); + $self->refresh_fileview(); +} +sub on_location_go_button_clicked +{ + my $self = shift; + $self->ch_dir($self->{location}->get_text()); +} +sub on_quit_activate {Gtk2->main_quit;} +sub on_preferences_activate +{ + my $self = shift; + $self->{dlg_pref}->display($self) ; +} +sub on_main_delete_event {Gtk2->main_quit;} +sub on_bweb_activate +{ + my $self = shift; + $self->set_status("Open bweb on your browser"); + $self->{pref}->go_bweb('', "go on bweb"); +} + +# Change the current working directory +# * Updates fileview, location, and selection +# +sub ch_dir +{ + 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; +} + +# Handle dialog 'close' (window-decoration induced close) +# * Just hide the dialog, and tell Gtk not to do anything else +# +sub on_delete_event +{ + my ($self, $w) = @_; + $w->hide; + Gtk2::main_quit(); + 1; # consume this event! +} + +# Handle key presses in location text edit control +# * Translate a Return/Enter key into a 'Go' command +# * All other key presses left for GTK +# +sub on_location_entry_key_release_event +{ + my $self = shift; + my $widget = shift; + my $event = shift; + + my $keypress = $event->keyval; + if ($keypress == $Gtk2::Gdk::Keysyms{KP_Enter} || + $keypress == $Gtk2::Gdk::Keysyms{Return}) + { + $self->ch_dir($widget->get_text()); + + return 1; # consume keypress + } + + return 0; # let gtk have the keypress +} + +sub on_fileview_key_press_event +{ + my ($self, $widget, $event) = @_; + return 0; +} + +sub listview_get_first +{ + my ($list) = shift; + my @selected = $list->get_selected_indices(); + if (@selected > 0) { + my ($pid,$fid,$name, @other) = @{$list->{data}->[$selected[0]]}; + return ($pid,$fid,unpack('u', $name), @other); + } else { + return undef; + } +} + +sub listview_get_all +{ + my ($list) = shift; + + my @selected = $list->get_selected_indices(); + my @ret; + for my $i (@selected) { + my ($pid,$fid,$name, @other) = @{$list->{data}->[$i]}; + push @ret, [$pid,$fid,unpack('u', $name), @other]; + } + return @ret; +} + +sub listview_push +{ + 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 +# +sub on_fileview_key_release_event +{ + my ($self, $widget, $event) = @_; + if (not $event->keyval) + { + return 0; + } + if ($event->keyval == $Gtk2::Gdk::Keysyms{BackSpace}) { + $self->on_back_button_clicked(); + return 1; # eat keypress + } + + return 0; # let gtk have keypress +} + +sub on_forward_keypress +{ + return 0; +} + +#------------------------------------------------------------------- +# Handle double-click (or enter) on file-view +# * Translates into a 'cd ' command +# +sub on_fileview_row_activated +{ + my ($self, $widget) = @_; + + my ($pid,$fid,$name, undef, $type, undef) = listview_get_first($widget); + + if ($type eq 'dir') + { + $self->{bvfs}->ch_dir($pid); + $self->refresh_fileview(); + } else { + $self->fill_infoview($pid,$fid,$name); + } + + return 1; # consume event +} + +sub fill_infoview +{ + my ($self, $path, $file, $fn) = @_; + $self->clear_infoview(); + my @v = $self->{bvfs}->get_all_file_versions($path, + $file, + $self->current_client, + $self->{pref}->{see_all_versions}); + for my $ver (@v) { + my (undef,$pid,$fid,$jobid,$fileindex,$mtime, + $size,$inchanger,$md5,$volname) = @{$ver}; + my $icon = ($inchanger)?$yesicon:$noicon; + + $mtime = localtime($mtime) ; + + listview_push($self->{fileinfo},$pid,$fid, + $fn, $jobid, 'file', + $icon, $volname, $jobid, human($size), $mtime, $md5); + } +} + +sub current_date +{ + my $self = shift ; + return $self->{restore_backup_combobox}->get_active_text; +} + +sub current_client +{ + my $self = shift ; + return $self->{client_combobox}->get_active_text; +} + +sub on_list_backups_changed +{ + my ($self, $widget) = @_; + return 0 unless defined $self->{fileview}; + + $self->{CurrentJobIds} = [ + set_job_ids_for_date($self->dbh(), + $self->current_client, + $self->current_date, + $self->{pref}->{use_ok_bkp_only}) + ]; + $self->{bvfs}->set_curjobids(@{$self->{CurrentJobIds}}); + $self->refresh_fileview(); + 0; +} + +sub on_restore_list_keypress +{ + my ($self, $widget, $event) = @_; + if ($event->keyval == $Gtk2::Gdk::Keysyms{Delete}) + { + my @sel = $widget->get_selected_indices; + foreach my $elt (reverse(sort {$a <=> $b} @sel)) + { + splice @{$self->{restore_list}->{data}},$elt,1; + } + } +} + +sub on_fileview_button_press_event +{ + my ($self,$widget,$event) = @_; + if ($event->button == 3) + { + $self->on_right_click_filelist($widget,$event); + return 1; + } + + if ($event->button == 2) + { + $self->on_see_all_version(); + return 1; + } + + return 0; +} + +sub on_see_all_version +{ + my ($self) = @_; + + my @lst = listview_get_all($self->{fileview}); + + for my $i (@lst) { + my ($pid,$fid,$name, undef) = @{$i}; + + new DlgFileVersion($self->{bvfs}, + $self->current_client, + $pid,$fid,$self->{cwd},$name); + } +} + +sub on_right_click_filelist +{ + my ($self,$widget,$event) = @_; + # I need to know what's selected + my @sel = listview_get_all($self->{fileview}); + + my $type = ''; + + if (@sel == 1) { + $type = $sel[0]->[4]; # $type + } + + my $w; + + if (@sel >=2 or $type eq 'dir') + { + # We have selected more than one or it is a directories + $w = $self->{filelist_dir_menu}; + } + else + { + $w = $self->{filelist_file_menu}; + } + $w->popup(undef, + undef, + undef, + undef, + $event->button, $event->time); +} + +sub context_add_to_filelist +{ + my ($self) = @_; + + my @sel = listview_get_all($self->{fileview}); + + foreach my $i (@sel) + { + my ($pid, $fid, $file, $jobid, $type, undef) = @{$i}; + $file = $self->{cwd} . '/' . $file; + $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, $pid, $fid, $name, $jobid, $type)=@_; + + my $restore_list = $self->{restore_list}; + + my $curjobids=join(',', @{$self->{CurrentJobIds}}); + + if ($type eq 'dir') + { + # dirty hack + $name =~ s!^//+!/!; + + if ($name and substr $name,-1 ne '/') + { + $name .= '/'; # For bacula + } + 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 = $self->{bvfs}->get_fileindex_from_file_jobid($pid,$fid,$jobid); + + listview_push($restore_list,$pid,$fid, + $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"; + + 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; + } + } + + 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) +# This sub creates a BSR from the information in the restore_list +# Returns the BSR as a string +sub create_filelist +{ + my $self = shift; + my %mediainfos; + # This query gets all jobid/jobmedia/media combination. + my $query = " +SELECT Job.JobId, Job.VolsessionId, Job.VolsessionTime, JobMedia.StartFile, + JobMedia.EndFile, JobMedia.FirstIndex, JobMedia.LastIndex, + JobMedia.StartBlock, JobMedia.EndBlock, JobMedia.VolIndex, + Media.Volumename, Media.MediaType +FROM Job, JobMedia, Media +WHERE Job.JobId = JobMedia.JobId + AND JobMedia.MediaId = Media.MediaId + ORDER BY JobMedia.FirstIndex, JobMedia.LastIndex"; + + + my $result = $self->dbh_selectall_arrayref($query); + + # We will store everything hashed by jobid. + + foreach my $refrow (@$result) + { + my ($jobid, $volsessionid, $volsessiontime, $startfile, $endfile, + $firstindex, $lastindex, $startblock, $endblock, + $volindex, $volumename, $mediatype) = @{$refrow}; + + # We just have to deal with the case where starfile != endfile + # In this case, we concatenate both, for the bsr + if ($startfile != $endfile) { + $startfile = $startfile . '-' . $endfile; + } + + my @tmparray = + ($jobid, $volsessionid, $volsessiontime, $startfile, + $firstindex, $lastindex, $startblock .'-'. $endblock, + $volindex, $volumename, $mediatype); + + push @{$mediainfos{$refrow->[0]}},(\@tmparray); + } + + + # reminder : restore_list looks like this : + # ($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, + # no pseudo join (>= FirstIndex ...), etc ... + # We do a SQL union of all the files/dirs specified in the restore_list + my @select_queries; + foreach my $entry (@{$self->{restore_list}->{data}}) + { + if ($entry->[4] eq 'dir') + { + 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 + (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); + } + else + { + # It's a file. Great, we allready have most + # of what is needed. Simple and efficient query + my $dir = $entry->[0]; + my $file = $entry->[1]; + + 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 File.PathId = $dir + AND File.PathId = Path.PathId + AND File.FilenameId = $file + AND File.FilenameId = Filename.FilenameId + AND File.JobId = $jobid + ) +"; + push @select_queries,($query); + } + } + $query = join("\nUNION ALL\n",@select_queries) . "\nORDER BY FileIndex\n"; + + #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... + + my $sth = $self->dbh_prepare($query); + $sth->execute; + + my ($path,$name,$fileindex,$jobid); + $sth->bind_columns(\$path,\$name,\$fileindex,\$jobid); + + # The temp place we're going to save all file + # list to before the real list + my @temp_list; + + RECORD_LOOP: + while ($sth->fetchrow_arrayref()) + { + # This may look dumb, but we're going to do a join by ourselves, + # to save memory and avoid sending a complex query to mysql + my $complete_path = $path . $name; + my $is_dir = 0; + + if ( $name eq '') + { + $is_dir = 1; + } + + # Remove trailing slash (normalize file and dir name) + $complete_path =~ s/\/$//; + + # Let's find the ref(s) for the %mediainfo element(s) + # containing the data for this file + # There can be several matches. It is the pseudo join. + my $med_idx=0; + my $max_elt=@{$mediainfos{$jobid}}-1; + MEDIA_LOOP: + while($med_idx <= $max_elt) + { + my $ref = $mediainfos{$jobid}->[$med_idx]; + # First, can we get rid of the first elements of the + # array ? (if they don't contain valuable records + # anymore + if ($fileindex > $ref->[5]) + { + # It seems we don't need anymore + # this entry in %mediainfo (the input data + # is sorted...) + # We get rid of it. + shift @{$mediainfos{$jobid}}; + $max_elt--; + next MEDIA_LOOP; + } + # We will do work on this elt. We can ++ + # $med_idx for next loop + $med_idx++; + + # %mediainfo row looks like : + # (jobid,VolsessionId,VolsessionTime,File,FirstIndex, + # LastIndex,StartBlock-EndBlock,VolIndex,Volumename, + # MediaType) + + # We are in range. We store and continue looping + # in the medias + if ($fileindex >= $ref->[4]) + { + my @data = ($complete_path,$is_dir, + $fileindex,$ref); + push @temp_list,(\@data); + next MEDIA_LOOP; + } + + # We are not in range. No point in continuing looping + # We go to next record. + next RECORD_LOOP; + } + } + # Now we have the array. + # We're going to sort it, by + # path, volsessiontime DESC (get the most recent file...) + # The array rows look like this : + # complete_path,is_dir,fileindex, + # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex, + # LastIndex,StartBlock-EndBlock,VolIndex,Volumename,MediaType) + @temp_list = sort {$a->[0] cmp $b->[0] + || $b->[3]->[2] <=> $a->[3]->[2] + } @temp_list; + + my @restore_list; + my $prev_complete_path='////'; # Sure not to match + my $prev_is_file=1; + my $prev_jobid; + + while (my $refrow = shift @temp_list) + { + # For the sake of readability, we load $refrow + # contents in real scalars + my ($complete_path, $is_dir, $fileindex, $refother)=@{$refrow}; + my $jobid= $refother->[0]; # We don't need the rest... + + # We skip this entry. + # We allready have a newer one and this + # isn't a continuation of the same file + next if ($complete_path eq $prev_complete_path + and $jobid != $prev_jobid); + + + if ($prev_is_file + and $complete_path =~ m|^\Q$prev_complete_path\E/|) + { + # We would be recursing inside a file. + # Just what we don't want (dir replaced by file + # between two backups + next; + } + elsif ($is_dir) + { + # It is a directory + push @restore_list,($refrow); + + $prev_complete_path = $complete_path; + $prev_jobid = $jobid; + $prev_is_file = 0; + } + else + { + # It is a file + push @restore_list,($refrow); + + $prev_complete_path = $complete_path; + $prev_jobid = $jobid; + $prev_is_file = 1; + } + } + # We get rid of @temp_list... save memory + @temp_list=(); + + # Ok everything is in the list. Let's sort it again in another way. + # This time it will be in the bsr file order + + # we sort the results by + # volsessiontime, volsessionid, volindex, fileindex + # to get all files in right order... + # Reminder : The array rows look like this : + # complete_path,is_dir,fileindex, + # ref->(jobid,VolsessionId,VolsessionTime,File,FirstIndex,LastIndex, + # StartBlock-EndBlock,VolIndex,Volumename,MediaType) + + @restore_list= sort { $a->[3]->[2] <=> $b->[3]->[2] + || $a->[3]->[1] <=> $b->[3]->[1] + || $a->[3]->[7] <=> $b->[3]->[7] + || $a->[2] <=> $b->[2] } + @restore_list; + + # Now that everything is ready, we create the bsr + my $prev_fileindex=-1; + my $prev_volsessionid=-1; + my $prev_volsessiontime=-1; + my $prev_volumename=-1; + my $prev_volfile=-1; + my $prev_mediatype; + my $prev_volblocks; + my $count=0; + my $first_of_current_range=0; + my @fileindex_ranges; + my $bsr=''; + + foreach my $refrow (@restore_list) + { + my (undef,undef,$fileindex,$refother)=@{$refrow}; + my (undef,$volsessionid,$volsessiontime,$volfile,undef,undef, + $volblocks,undef,$volumename,$mediatype)=@{$refother}; + + # We can specifiy the number of files in each section of the + # bsr to speedup restore (bacula can then jump over the + # end of tape files. + $count++; + + + if ($prev_volumename eq '-1') + { + # We only have to start the new range... + $first_of_current_range=$fileindex; + } + elsif ($prev_volsessionid != $volsessionid + or $prev_volsessiontime != $volsessiontime + or $prev_volumename ne $volumename + or $prev_volfile ne $volfile) + { + # We have to create a new section in the bsr... + # We print the previous one ... + # (before that, save the current range ...) + 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-1); + $count=1; + # Reset for next loop + @fileindex_ranges=(); + $first_of_current_range=$fileindex; + } + elsif ($fileindex-1 != $prev_fileindex) + { + # End of a range of fileindexes + 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"); + } + $first_of_current_range=$fileindex; + } + $prev_fileindex=$fileindex; + $prev_volsessionid = $volsessionid; + $prev_volsessiontime = $volsessiontime; + $prev_volumename = $volumename; + $prev_volfile=$volfile; + $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}); +} + +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); +} + +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"; + + $self->debug($query); + my $result = $self->dbh_selectall_arrayref($query); + $self->debug($result); + + return $result; +} + +# return ($dirid,$dir_basename,$lstat,$jobid) +sub ls_dirs +{ + 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}) + { + 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)=@_; + + 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 +# list +# In : self,reference to the entry +# Out : size in bytes, number of files +sub estimate_restore_size +{ + # reminder : restore_list looks like this : + # ($pid,$fid,$name,$jobid,'file',$curjobids, + # undef, undef, undef, $dirfileindex); + my ($self, $entry, $refresh) = @_; + my $query; + if ($entry->[4] eq 'dir') + { + 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 + (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"; + } + else + { + # It's a file. Great, we allready have most + # of what is needed. Simple and efficient query + my $dir = $entry->[0]; + my $fileid = $entry->[1]; + + my $jobid = $entry->[3]; + my $fileindex = $entry->[9]; + my $inclause = $entry->[5]; # curjobids + $query = +"SELECT Path.Path, File.FilenameId, File.Lstat + FROM File, Path + WHERE Path.PathId = File.PathId + AND Path.PathId = $dir + AND File.FilenameId = $fileid + AND File.JobId = $jobid"; + } + + my ($path,$nameid,$lstat); + my $sth = $self->dbh_prepare($query); + $sth->execute; + $sth->bind_columns(\$path,\$nameid,\$lstat); + my $old_path=''; + my $old_nameid=''; + my $total_size=0; + my $total_files=0; + + &$refresh(); + + my $rcount=0; + # We fetch all rows + while ($sth->fetchrow_arrayref()) + { + # Only the latest version of a file + next if ($nameid eq $old_nameid and $path eq $old_path); + + 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; + } + # 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); + } + + # 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) = @_; + + foreach my $job (sort {$a <=> $b} @jobs) + { + my $query = "SELECT 1 FROM brestore_knownjobid WHERE JobId = $job"; + my $retour = $self->dbh_selectrow_arrayref($query); + next if ($retour and ($retour->[0] == 1)); # We have allready done this one ... + + print STDERR "Inserting path records for JobId $job\n"; + $query = "INSERT INTO brestore_pathvisibility (PathId, JobId) + (SELECT DISTINCT PathId, JobId FROM File WHERE JobId = $job)"; + + $self->dbh_do($query); + + # Now we have to do the directory recursion stuff to determine missing visibility + # We try to avoid recursion, to be as fast as possible + # We also only work on not allready hierarchised directories... + + print STDERR "Creating missing recursion paths for $job\n"; + + $query = "SELECT brestore_pathvisibility.PathId, Path FROM brestore_pathvisibility + JOIN Path ON( brestore_pathvisibility.PathId = Path.PathId) + LEFT JOIN brestore_pathhierarchy ON (brestore_pathvisibility.PathId = brestore_pathhierarchy.PathId) + WHERE brestore_pathvisibility.JobId = $job + AND brestore_pathhierarchy.PathId IS NULL + ORDER BY Path"; + + my $sth = $self->dbh_prepare($query); + $sth->execute(); + my $pathid; my $path; + $sth->bind_columns(\$pathid,\$path); + + while ($sth->fetch) + { + $self->build_path_hierarchy($path,$pathid); + } + $sth->finish(); + + # Great. We have calculated all dependancies. We can use them to add the missing pathids ... + # This query gives all parent pathids for a given jobid that aren't stored. + # It has to be called until no record is updated ... + $query = " + INSERT INTO brestore_pathvisibility (PathId, JobId) ( + SELECT a.PathId,$job + FROM + (SELECT DISTINCT h.PPathId AS PathId + FROM brestore_pathhierarchy AS h + JOIN brestore_pathvisibility AS p ON (h.PathId=p.PathId) + WHERE p.JobId=$job) AS a + LEFT JOIN + (SELECT PathId + FROM brestore_pathvisibility + WHERE JobId=$job) AS b + ON (a.PathId = b.PathId) + WHERE b.PathId IS NULL)"; + + my $rows_affected; + 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)"; + $self->dbh_do($query); + } +} + +sub cleanup_brestore_table +{ + my ($self) = @_; + + my $query = "SELECT JobId from brestore_knownjobid"; + 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 = $self->dbh_selectall_arrayref($query); + if (scalar(@{$result})) + { + # There are still files for this jobid + print STDERR "$job still exists. Not cleaning...\n"; + + } else { + $query = "DELETE FROM brestore_pathvisibility WHERE JobId = $job"; + $self->dbh_do($query); + $query = "DELETE FROM brestore_knownjobid WHERE JobId = $job"; + $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)=@_; + # Does the ppathid exist for this ? we use a memory cache... + # In order to avoid the full loop, we consider that if a dir is allready in the + # brestore_pathhierarchy table, then there is no need to calculate all the hierarchy + while ($path ne '') + { + if (! $self->{cache_ppathid}->{$pathid}) + { + my $query = "SELECT PPathId FROM brestore_pathhierarchy WHERE PathId = ?"; + my $sth2 = $self->{conf}->{dbh}->prepare_cached($query); + $sth2->execute($pathid); + # Do we have a result ? + if (my $refrow = $sth2->fetchrow_arrayref) + { + $self->{cache_ppathid}->{$pathid}=$refrow->[0]; + $sth2->finish(); + # This dir was in the db ... + # It means we can leave, the tree has allready been built for + # this dir + return 1; + } else { + $sth2->finish(); + # We have to create the record ... + # What's the current p_path ? + my $ppath = parent_dir($path); + my $ppathid = $self->return_pathid_from_path($ppath); + $self->{cache_ppathid}->{$pathid}= $ppathid; + + $query = "INSERT INTO brestore_pathhierarchy (pathid, ppathid) VALUES (?,?)"; + $sth2 = $self->{conf}->{dbh}->prepare_cached($query); + $sth2->execute($pathid,$ppathid); + $sth2->finish(); + $path = $ppath; + $pathid = $ppathid; + } + } else { + # It's allready in the cache. + # We can leave, no time to waste here, all the parent dirs have allready + # been done + return 1; + } + } + return 1; +} + + +sub return_pathid_from_path +{ + my ($self, $path) = @_; + my $query = "SELECT PathId FROM Path WHERE Path = ? + UNION + SELECT PathId FROM brestore_missing_path WHERE Path = ?"; + #print STDERR $query,"\n" if $debug; + my $sth = $self->{conf}->{dbh}->prepare_cached($query); + $sth->execute($path,$path); + my $result =$sth->fetchrow_arrayref(); + $sth->finish(); + if (defined $result) + { + return $result->[0]; + + } else { + # A bit dirty : we insert into path AND missing_path, to be sure + # we aren't deleted by a purge. We still need to insert into path to get + # the pathid, because of mysql + $query = "INSERT INTO Path (Path) VALUES (?)"; + #print STDERR $query,"\n" if $debug; + $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->{conf}->{dbh}->prepare_cached($query); + $sth->execute($path); + $sth->finish(); + $query = " DELETE FROM Path WHERE Path = ?"; + #print STDERR $query,"\n" if $debug; + $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->{conf}->{dbh}->prepare_cached($query); + $sth->execute($path); + $result = $sth->fetchrow_arrayref(); + $sth->finish(); + return $result->[0]; + } +} + + +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); + } +} + +# Get metadata +{ + my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2, + 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5, + 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8, + 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11, + 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14, + 'data_stream' => 15);; + sub array_attrib + { + my ($attrib,$ref_attrib)=@_; + return $ref_attrib->[$attrib_name_id{$attrib}]; + } + + sub file_attrib + { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId] + + my ($file, $attrib)=@_; + + if (defined $attrib_name_id{$attrib}) { + + my @d = split(' ', $file->[3]) ; # TODO : cache this + + return from_base64($d[$attrib_name_id{$attrib}]); + + } elsif ($attrib eq 'jobid') { + + return $file->[4]; + + } elsif ($attrib eq 'name') { + + return $file->[2]; + + } else { + die "Attribute not known : $attrib.\n"; + } + } + + sub lstat_attrib + { + my ($lstat,$attrib)=@_; + if ($lstat and defined $attrib_name_id{$attrib}) + { + my @d = split(' ', $lstat) ; # TODO : cache this + return from_base64($d[$attrib_name_id{$attrib}]); + } + return 0; + } +} + +{ + # Base 64 functions, directly from recover.pl. + # Thanks to + # Karl Hakimian + # This section is also under GPL v2 or later. + my @base64_digits; + my @base64_map; + my $is_init=0; + sub init_base64 + { + @base64_digits = ( + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + ); + @base64_map = (0) x 128; + + for (my $i=0; $i<64; $i++) { + $base64_map[ord($base64_digits[$i])] = $i; + } + $is_init = 1; + } + + sub from_base64 { + if(not $is_init) + { + init_base64(); + } + my $where = shift; + my $val = 0; + my $i = 0; + my $neg = 0; + + if (substr($where, 0, 1) eq '-') { + $neg = 1; + $where = substr($where, 1); + } + + while ($where ne '') { + $val *= 64; + my $d = substr($where, 0, 1); + $val += $base64_map[ord(substr($where, 0, 1))]; + $where = substr($where, 1); + } + + return $val; + } + + sub parse_lstat { + my ($lstat)=@_; + my @attribs = split(' ',$lstat); + foreach my $element (@attribs) + { + $element = from_base64($element); + } + return @attribs; + } +} + +1; + +################################################################ +package BwebConsole; +use LWP::UserAgent; +use HTTP::Request::Common; + +sub new +{ + my ($class, %arg) = @_; + + 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 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'}); +} + +sub list_fileset +{ + my ($self) = @_; + return sort split(/;/, $self->{'list_fileset'}); +} + +sub list_storage +{ + my ($self) = @_; + return sort split(/;/, $self->{'list_storage'}); +} +sub list_client +{ + my ($self) = @_; + return sort split(/;/, $self->{'list_client'}); +} + +1; + +package main; + +use Getopt::Long ; + +sub HELP_MESSAGE +{ + print STDERR "Usage: $0 [--conf=brestore.conf] [--batch] [--debug]\n"; + exit 1; +} + +my $file_conf = "$ENV{HOME}/.brestore.conf" ; +my $batch_mod; + +GetOptions("conf=s" => \$file_conf, + "batch" => \$batch_mod, + "debug" => \$debug, + "help" => \&HELP_MESSAGE) ; + +my $p = new Pref($file_conf); + +if (! -f $file_conf) { + $p->write_config(); +} + +if ($batch_mod) { + my $vfs = new Bvfs(conf => $p); + if ($p->connect_db()) { + $vfs->update_cache(); + } + exit (0); +} + +$glade_file = $p->{glade_file}; + +foreach my $path ('','.','/usr/share/brestore','/usr/local/share/brestore') { + if (-f "$path/$glade_file") { + $glade_file = "$path/$glade_file" ; + last; + } +} + +# gtk have lots of warning on stderr +if ($^O eq 'MSWin32') +{ + close(STDERR); + open(STDERR, ">stderr.log"); +} + +Gtk2->init(); + +if ( -f $glade_file) { + my $w = new DlgResto($p); + +} else { + my $widget = Gtk2::MessageDialog->new(undef, 'modal', 'error', 'close', +"Can't find your brestore.glade (glade_file => '$glade_file') +Please, edit your $file_conf to setup it." ); + + $widget->signal_connect('destroy', sub { Gtk2->main_quit() ; }); + $widget->run; + exit 1; +} + +Gtk2->main; # Start Gtk2 main loop + +# that's it! + +exit 0; + +__END__ +package main; + +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); diff --git a/gui/brestore/brestore.png b/gui/brestore/brestore.png new file mode 100644 index 0000000000..ccb7d46ff6 Binary files /dev/null and b/gui/brestore/brestore.png differ