de06984019623efccead0025407d22c63855fe79
[BackupPC.git] / lib / BackupPC / SearchLib.pm
1 #!/usr/bin/perl
2 package BackupPC::SearchLib;
3
4 use strict;
5 use BackupPC::CGI::Lib qw(:all);
6 use BackupPC::Attrib qw(:all);
7 use DBI;
8 use DateTime;
9 use vars qw(%In $MyURL);
10 use Time::HiRes qw/time/;
11
12 my $on_page = 100;
13 my $pager_pages = 10;
14
15 my $dsn = $Conf{SearchDSN};
16 my $db_user = $Conf{SearchUser} || '';
17
18 my $index_path = $Conf{HyperEstraierIndex};
19 if ($index_path) {
20         $index_path = $TopDir . '/' . $index_path;
21         $index_path =~ s#//#/#g;
22 }
23
24 my $dbh;
25
26 sub get_dbh {
27         $dbh ||= DBI->connect($dsn, $db_user, "", { RaiseError => 1, AutoCommit => 1 } );
28         return $dbh;
29 }
30
31 sub getUnits() {
32         my @ret;
33
34         my $dbh = get_dbh();
35         my $sth = $dbh->prepare(qq{
36                 SELECT
37                         shares.id       as id,
38                         hosts.name || ':' || shares.name as share
39                 FROM shares
40                 JOIN hosts on hostid = hosts.id
41                 ORDER BY share
42         } );
43         $sth->execute();
44         push @ret, { 'id' => '', 'share' => '-'};       # dummy any
45
46         while ( my $row = $sth->fetchrow_hashref() ) {
47                 push @ret, $row;
48         }
49         return @ret;
50 }
51
52 sub epoch_to_iso {
53         my $t = shift || return;
54         my $iso = BackupPC::Lib::timeStamp(undef, $t);
55         $iso =~ s/\s/ /g;
56         return $iso;
57 }
58
59 sub dates_from_form($) {
60         my $param = shift || return;
61
62         sub mk_epoch_date($$) {
63                 my ($name,$suffix) = @_;
64
65                 my $yyyy = $param->{ $name . '_year_' . $suffix} || return undef;
66                 my $mm .= $param->{ $name . '_month_' . $suffix} ||
67                         ( $suffix eq 'from' ? 1 : 12);
68                 my $dd .= $param->{ $name . '_day_' . $suffix} ||
69                         ( $suffix eq 'from' ? 1 : 31);
70
71                 $yyyy =~ s/\D//g;
72                 $mm =~ s/\D//g;
73                 $dd =~ s/\D//g;
74
75                 my $dt = new DateTime(
76                         year => $yyyy,
77                         month => $mm,
78                         day => $dd
79                 );
80                 print STDERR "mk_epoch_date($name,$suffix) [$yyyy-$mm-$dd] = " . $dt->ymd . " " . $dt->hms . "\n";
81                 return $dt->epoch || 'NULL';
82         }
83
84         my @ret = (
85                 mk_epoch_date('search_backup', 'from'),
86                 mk_epoch_date('search_backup', 'to'),
87                 mk_epoch_date('search', 'from'),
88                 mk_epoch_date('search', 'to'),
89         );
90
91         return @ret;
92
93 }
94
95
96 sub getWhere($) {
97         my $param = shift || return;
98
99         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
100
101         my @conditions;
102         push @conditions, qq{ backups.date >= $backup_from } if ($backup_from);
103         push @conditions, qq{ backups.date <= $backup_to } if ($backup_to);
104         push @conditions, qq{ files.date >= $files_from } if ($files_from);
105         push @conditions, qq{ files.date <= $files_to } if ($files_to);
106
107         print STDERR "backup: $backup_from - $backup_to files: $files_from - $files_to cond:" . join(" | ",@conditions);
108
109         push( @conditions, ' files.shareid = ' . $param->{'search_share'} ) if ($param->{'search_share'});
110         push (@conditions, " upper(files.path) LIKE upper('%".$param->{'search_filename'}."%')") if ($param->{'search_filename'});
111
112         return join(" and ", @conditions);
113 }
114
115
116 sub getFiles($) {
117         my ($param) = @_;
118
119         my $offset = $param->{'offset'} || 0;
120         $offset *= $on_page;
121
122         my $dbh = get_dbh();
123
124         my $sql_cols = qq{
125                 files.id                        AS fid,
126                 hosts.name                      AS hname,
127                 shares.name                     AS sname,
128                 files.backupnum                 AS backupnum,
129                 files.path                      AS filepath,
130                 files.date                      AS date,
131                 files.type                      AS type,
132                 files.size                      AS size
133         };
134
135         my $sql_from = qq{
136                 FROM files 
137                         INNER JOIN shares       ON files.shareID=shares.ID
138                         INNER JOIN hosts        ON hosts.ID = shares.hostID
139                         INNER JOIN backups      ON backups.num = files.backupnum and backups.hostID = hosts.ID AND backups.shareID = files.shareID
140         };
141
142         my $sql_where;
143         my $where = getWhere($param);
144         $sql_where = " WHERE ". $where if ($where);
145
146         my $sql_order = qq{
147                 ORDER BY files.date
148                 LIMIT $on_page
149                 OFFSET ?
150         };
151
152         my $sql_count = qq{ select count(files.id) $sql_from $sql_where };
153         my $sql_results = qq{ select $sql_cols $sql_from $sql_where $sql_order };
154
155         my $sth = $dbh->prepare($sql_count);
156         $sth->execute();
157         my ($results) = $sth->fetchrow_array();
158
159         $sth = $dbh->prepare($sql_results);
160         $sth->execute( $offset );
161
162         if ($sth->rows != $results) {
163                 my $bug = "$0 BUG: [[ $sql_count ]] = $results while [[ $sql_results ]] = " . $sth->rows;
164                 $bug =~ s/\s+/ /gs;
165                 print STDERR "$bug\n";
166         }
167
168         my @ret;
169       
170         while (my $row = $sth->fetchrow_hashref()) {
171                 push @ret, $row;
172         }
173      
174         $sth->finish();
175         return ($results, \@ret);
176 }
177
178 sub getHyperEstraier_url($) {
179         my ($use_hest) = @_;
180
181         return unless $use_hest;
182
183         use HyperEstraier;
184         my ($index_path, $index_node_url);
185
186         if ($use_hest =~ m#^http://#) {
187                 $index_node_url = $use_hest;
188         } else {
189                 $index_path = $TopDir . '/' . $index_path;
190                 $index_path =~ s#//#/#g;
191         }
192         return ($index_path, $index_node_url);
193 }
194
195 sub getFilesHyperEstraier($) {
196         my ($param) = @_;
197
198         my $offset = $param->{'offset'} || 0;
199         $offset *= $on_page;
200
201         die "no index_path?" unless ($index_path);
202
203         use HyperEstraier;
204
205         my ($index_path, $index_node_url) = getHyperEstraier_url($index_path);
206
207         # open the database
208         my $db;
209         if ($index_path) {
210                 $db = HyperEstraier::Database->new();
211                 $db->open($index_path, $HyperEstraier::ESTDBREADER);
212         } elsif ($index_node_url) {
213                 $db ||= HyperEstraier::Node->new($index_node_url);
214                 $db->set_auth('admin', 'admin');
215         } else {
216                 die "BUG: unimplemented";
217         }
218
219         # create a search condition object
220         my $cond = HyperEstraier::Condition->new();
221
222         my $q = $param->{'search_filename'};
223         my $shareid = $param->{'search_share'};
224
225         if (length($q) > 0) {
226                 # exact match
227                 $cond->add_attr("filepath ISTRINC $q");
228
229                 $q =~ s/(.)/$1 /g;
230                 # set the search phrase to the search condition object
231                 $cond->set_phrase($q);
232         }
233
234         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
235
236         $cond->add_attr("backup_date NUMGE $backup_from") if ($backup_from);
237         $cond->add_attr("backup_date NUMLE $backup_to") if ($backup_to);
238
239         $cond->add_attr("date NUMGE $files_from") if ($files_from);
240         $cond->add_attr("date NUMLE $files_to") if ($files_to);
241
242         $cond->add_attr("shareid NUMEQ $shareid") if ($shareid);
243
244 #       $cond->set_max( $offset + $on_page );
245         $cond->set_options( $HyperEstraier::Condition::SURE );
246         $cond->set_order( 'date NUMA' );
247
248         # get the result of search
249         my @res;
250         my ($result, $hits);
251
252         if ($index_path) {
253                 $result = $db->search($cond, 0);
254                 $hits = $result->size;
255         } elsif ($index_node_url) {
256                 $result = $db->search($cond, 0);
257                 $hits = $result->doc_num;
258         } else {
259                 die "BUG: unimplemented";
260         }
261
262         # for each document in result
263         for my $i ($offset .. ($offset + $on_page - 1)) {
264                 last if ($i >= $hits);
265
266                 my $doc;
267                 if ($index_path) {
268                         my $id = $result->get($i);
269                         $doc = $db->get_doc($id, 0);
270                 } elsif ($index_node_url) {
271                         $doc = $result->get_doc($i);
272                 } else {
273                         die "BUG: unimplemented";
274                 }
275
276                 my $row;
277                 foreach my $c (qw/fid hname sname backupnum fiilename filepath date type size/) {
278                         $row->{$c} = $doc->attr($c);
279                 }
280                 push @res, $row;
281         }
282
283         return ($hits, \@res);
284 }
285
286 sub getGzipName($$$)
287 {
288         my ($host, $share, $backupnum) = @_;
289         my $ret = $Conf{GzipSchema};
290         
291         $share =~ s/\//_/g;
292         $ret =~ s/\\h/$host/ge;
293         $ret =~ s/\\s/$share/ge;
294         $ret =~ s/\\n/$backupnum/ge;
295         
296         return $ret;
297         
298 }
299
300 sub getBackupsNotBurned() {
301
302         my $dbh = get_dbh();
303         my $sql = q{ 
304         SELECT
305                 backups.hostID          AS hostid,
306                 min(hosts.name)         AS host,
307                 min(shares.name)        AS share,
308                 backups.num             AS backupnum,
309                 min(backups.type)       AS type,
310                 min(backups.date)       AS date,
311                 min(backups.size)       AS size
312         FROM files 
313                 INNER JOIN shares       ON files.shareID=shares.ID
314                 INNER JOIN hosts        ON hosts.ID = shares.hostID
315                 INNER JOIN backups      ON backups.num = files.backupnum and backups.hostID = hosts.ID AND backups.shareID = shares.ID
316         GROUP BY 
317                 backups.hostID, backups.num, backups.shareid
318         ORDER BY min(backups.date)
319         };
320         my $sth = $dbh->prepare( $sql );
321         my @ret;
322         $sth->execute();
323
324         while ( my $row = $sth->fetchrow_hashref() ) {
325                 $row->{'age'} = sprintf("%0.1f", ( (time() - $row->{'date'}) / 86400 ) );
326                 $row->{'size'} = sprintf("%0.2f", $row->{'size'} / 1024 / 1024);
327                 my (undef,undef,undef,undef,undef,undef,undef,$fs_size,undef,undef,undef,undef,undef) = 
328                         stat( $Conf{InstallDir}.'/'.$Conf{GzipTempDir}.'/'.
329                                 getGzipName($row->{'host'}, $row->{share}, $row->{'backupnum'}));
330                 $row->{'fs_size'} = $fs_size;
331                 push @ret, $row;
332         }
333       
334         return @ret;      
335 }
336
337 sub displayBackupsGrid()
338   {
339       my $retHTML = "";
340       
341         $retHTML .= <<EOF3;
342 <script language="javascript" type="text/javascript">
343 <!--
344
345     function checkAll(location)
346     {
347       for (var i=0;i<document.forma.elements.length;i++)
348       {
349         var e = document.forma.elements[i];
350         if ((e.checked || !e.checked) && e.name != \'all\') {
351             if (eval("document.forma."+location+".checked")) {
352                 e.checked = true;
353             } else {
354                 e.checked = false;
355             }
356         }
357       }
358     }
359 //-->
360 </script>      
361 EOF3
362         $retHTML .= q{
363                 <form name="forma" method="GET" action="$MyURL?action=burn">
364                 <input type="hidden" value="burn" name="action">
365                 <input type="hidden" value="results" name="search_results">
366                 <table style="fview" border="0" cellspacing="0" cellpadding="2">
367                 <tr class="tableheader">
368                 <td class="tableheader">
369                         <input type="checkbox" name="allFiles" onClick="checkAll('allFiles');">
370                 </td>
371                 <td align="center">Share</td>
372                 <td align="center">Backup no</td>
373                 <td align="center">Type</td>
374                 <td align="center">date</td>
375                 <td align="center">age/days</td>
376                 <td align="center">size/MB</td>
377                 <td align="center">gzip size</td>
378                 </tr>
379
380                 <tr><td colspan=7 style="tableheader">
381                 <input type="submit" value="Burn selected backups on medium" name="submitBurner">
382                 </td></tr>
383         };
384
385         my @color = (' bgcolor="#e0e0e0"', '');
386
387         my $i = 0;
388         my $host = '';
389
390         foreach my $backup ( getBackupsNotBurned() ) {
391
392                 if ($host ne $backup->{'host'}) {
393                         $i++;
394                         $host = $backup->{'host'};
395                 }
396                 my $ftype = "";
397             
398                 $retHTML .= "<tr" . $color[$i %2 ] . ">";
399                 $retHTML .= '<td class="fview"><input type="checkbox" name="fcb' .
400                                 $backup->{'hostid'}.'_'.$backup->{'backupnum'} . 
401                                 '" value="' . $backup->{'hostid'}.'_'.$backup->{'backupnum'} .
402                                 '"></td>';
403             
404                 $retHTML .=
405                         '<td align="right">' . $backup->{'host'} . ':' . $backup->{'share'} . '</td>' .
406                         '<td align="center">' . $backup->{'backupnum'} . '</td>' .
407                         '<td align="center">' . $backup->{'type'} . '</td>' .
408                         '<td align="center">' . epoch_to_iso( $backup->{'date'} ) . '</td>' .
409                         '<td align="center">' . $backup->{'age'} . '</td>' .
410                         '<td align="right">' . $backup->{'size'} . '</td>' .
411                         '<td align="right">' . $backup->{'fs_size'} .'</td>' .
412                         "</tr>\n";
413
414
415         }
416
417         $retHTML .= "</table>";
418         $retHTML .= "</form>";
419       
420         return $retHTML;
421 }      
422
423 sub displayGrid($) {
424         my ($param) = @_;
425
426         my $offset = $param->{'offset'};
427         my $hilite = $param->{'search_filename'};
428
429         my $retHTML = "";
430  
431         my $start_t = time();
432
433         my ($results, $files);
434         if ($param->{'use_hest'} && length($hilite) > 0) {
435                 ($results, $files) = getFilesHyperEstraier($param);
436         } else {
437                 ($results, $files) = getFiles($param);
438         }
439
440         my $dur_t = time() - $start_t;
441         my $dur = sprintf("%0.4fs", $dur_t);
442
443         my ($from, $to) = (($offset * $on_page) + 1, ($offset * $on_page) + $on_page);
444
445         if ($results <= 0) {
446                 $retHTML .= qq{
447                         <p style="color: red;">No results found...</p>
448                 };
449                 return $retHTML;
450         } else {
451                 # DEBUG
452                 #use Data::Dumper;
453                 #$retHTML .= '<pre>' . Dumper($files) . '</pre>';
454         }
455
456
457         $retHTML .= qq{
458         <div>
459         Found <b>$results files</b> showing <b>$from - $to</b> (took $dur)
460         </div>
461         <table style="fview" width="100%" border="0" cellpadding="2" cellspacing="0">
462                 <tr class="fviewheader"> 
463                 <td></td>
464                 <td align="center">Share</td>
465                 <td align="center">Type and Name</td>
466                 <td align="center">#</td>
467                 <td align="center">Size</td>
468                 <td align="center">Date</td>
469                 <td align="center">Media</td>
470                 </tr>
471         };
472
473         my $file;
474
475         sub hilite_html($$) {
476                 my ($html, $search) = @_;
477                 $html =~ s#($search)#<b>$1</b>#gis;
478                 return $html;
479         }
480
481         sub restore_link($$$$$$) {
482                 my $type = shift;
483                 my $action = 'RestoreFile';
484                 $action = 'browse' if (lc($type) eq 'dir');
485                 return sprintf(qq{<a href="?action=%s&host=%s&num=%d&share=%s&dir=%s">%s</a>}, $action, @_);
486         }
487
488         my $i = $offset * $on_page;
489
490         foreach $file (@{ $files }) {
491                 $i++;
492
493                 my $typeStr  = BackupPC::Attrib::fileType2Text(undef, $file->{'type'});
494                 $retHTML .= qq{<tr class="fviewborder">};
495
496                 $retHTML .= qq{<td class="fviewborder">$i</td>};
497
498                 $retHTML .=
499                         qq{<td class="fviewborder" align="right">} . $file->{'hname'} . ':' . $file->{'sname'} . qq{</td>} .
500                         qq{<td class="fviewborder"><img src="$Conf{CgiImageDirURL}/icon-$typeStr.gif" alt="$typeStr" align="middle">&nbsp;} . hilite_html( $file->{'filepath'}, $hilite ) . qq{</td>} .
501                         qq{<td class="fviewborder" align="center">} . restore_link( $typeStr, ${EscURI( $file->{'hname'} )}, $file->{'backupnum'}, ${EscURI( $file->{'sname'})}, ${EscURI( $file->{'filepath'} )}, $file->{'backupnum'} ) . qq{</td>} .
502                         qq{<td class="fviewborder" align="right">} . $file->{'size'} . qq{</td>} .
503                         qq{<td class="fviewborder">} . epoch_to_iso( $file->{'date'} ) . qq{</td>} .
504                         qq{<td class="fviewborder">} . '?' . qq{</td>};
505
506                 $retHTML .= "</tr>";
507         }
508         $retHTML .= "</table>";
509
510         # all variables which has to be transfered
511         foreach my $n (qw/search_day_from search_month_from search_year_from search_day_to search_month_to search_year_to search_backup_day_from search_backup_month_from search_backup_year_from search_backup_day_to search_backup_month_to search_backup_year_to search_filename offset/) {
512                 $retHTML .= qq{<INPUT TYPE="hidden" NAME="$n" VALUE="$In{$n}">\n};
513         }
514
515         my $del = '';
516         my $max_page = int( $results / $on_page );
517         my $page = 0;
518
519         sub page_link($$$) {
520                 my ($param,$page,$display) = @_;
521
522                 $param->{'offset'} = $page;
523
524                 my $html = '<a href = "' . $MyURL;
525                 my $del = '?';
526                 foreach my $k (keys %{ $param }) {
527                         if ($param->{$k}) {
528                                 $html .= $del . $k . '=' . ${EscURI( $param->{$k} )};
529                                 $del = '&';
530                         }
531                 }
532                 $html .= '">' . $display . '</a>';
533         }
534
535         $retHTML .= '<div style="text-align: center;">';
536
537         if ($offset > 0) {
538                 $retHTML .= page_link($param, $offset - 1, '&lt;&lt;') . ' ';
539         }
540
541         while ($page <= $max_page) {
542                 if ($page == $offset) {
543                         $retHTML .= $del . '<b>' . ($page + 1) . '</b>';
544                 } else {
545                         $retHTML .= $del . page_link($param, $page, $page + 1);
546                 }
547
548                 if ($page < $offset - $pager_pages && $page != 0) {
549                         $retHTML .= " ... ";
550                         $page = $offset - $pager_pages;
551                         $del = '';
552                 } elsif ($page > $offset + $pager_pages && $page != $max_page) {
553                         $retHTML .= " ... ";
554                         $page = $max_page;
555                         $del = '';
556                 } else {
557                         $del = ' | ';
558                         $page++;
559                 }
560         }
561
562         if ($offset < $max_page) {
563                 $retHTML .= ' ' . page_link($param, $offset + 1, '&gt;&gt;');
564         }
565
566         $retHTML .= "</div>";
567
568         return $retHTML;
569 }
570
571 1;