minor tweak and new CSS theme
[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 sub getUnits() {
19         my @ret;
20
21         my $dbh = DBI->connect($dsn, $db_user, "", { RaiseError => 1, AutoCommit => 1 } );
22         my $sth = $dbh->prepare(qq{ SELECT id, share FROM shares ORDER BY share} );
23         $sth->execute();
24         push @ret, { 'id' => '', 'share' => '-'};       # dummy any
25
26         while ( my $row = $sth->fetchrow_hashref() ) {
27                 push @ret, $row;
28         }
29         $dbh->disconnect();
30         return @ret;
31 }
32
33 sub epoch_to_iso {
34         my $t = shift || return;
35         my $iso = BackupPC::Lib::timeStamp($t);
36         $iso =~ s/\s/ /g;
37         return $iso;
38 }
39
40 sub getWhere($) {
41         my ($param)    = @_;
42         my @conditions;
43
44         sub mk_epoch_date($$) {
45                 my ($name,$suffix) = @_;
46
47                 my $yyyy = $param->{ $name . '_year_' . $suffix} || return;
48                 my $mm .= $param->{ $name . '_month_' . $suffix} ||
49                         ( $suffix eq 'from' ? 1 : 12);
50                 my $dd .= $param->{ $name . '_day_' . $suffix} ||
51                         ( $suffix eq 'from' ? 1 : 31);
52                 my $dt = new DateTime(
53                         year => $yyyy,
54                         month => $mm,
55                         day => $dd
56                 );
57                 return $dt->epoch || 'NULL';
58         }
59
60         my $backup_from = mk_epoch_date('search_backup', 'from');
61         push @conditions, qq{ backups.date >= $backup_from } if ($backup_from);
62         my $backup_to = mk_epoch_date('search_backup', 'to');
63         push @conditions, qq{ backups.date <= $backup_to } if ($backup_to);
64
65         my $files_from = mk_epoch_date('search', 'from');
66         push @conditions, qq{ files.date >= $files_from } if ($files_from);
67         my $files_to = mk_epoch_date('search', 'to');
68         push @conditions, qq{ files.date <= $files_to } if ($files_to);
69
70         print STDERR "backup: $backup_from - $backup_to files: $files_from - $files_to cond:" . join(" | ",@conditions);
71     
72         push( @conditions, ' files.shareid = ' . $param->{'search_share'} ) if ($param->{'search_share'});
73
74         push (@conditions, " upper(files.path) LIKE upper('%".$param->{'search_filename'}."%')") if ($param->{'search_filename'});
75
76         return (
77                 join(" and ", @conditions),
78                 $files_from, $files_to,
79                 $backup_from, $backup_to
80         );
81 }
82
83
84 sub getFiles($$) {
85         my ($where, $offset) = @_;
86
87         my $dbh = DBI->connect($dsn, $db_user, "", { RaiseError => 1, AutoCommit => 1 } );
88
89         my $sql_cols = qq{
90                 files.id                        AS fid,
91                 hosts.name                      AS hname,
92                 shares.name                     AS sname,
93                 shares.share                    AS sharename,
94                 files.backupNum                 AS backupNum,
95                 files.name                      AS filename,
96                 files.path                      AS filepath,
97                 files.date                      AS date,
98                 files.type                      AS filetype,
99                 files.size                      AS size,
100                 -- dvds.name                    AS dvd
101                 null                            AS dvd
102         };
103
104         my $sql_from = qq{
105                 FROM files 
106                         INNER JOIN shares       ON files.shareID=shares.ID
107                         INNER JOIN hosts        ON hosts.ID = shares.hostID
108                         INNER JOIN backups      ON backups.num = files.backupNum and backups.hostID = hosts.ID AND backups.shareID = shares.ID
109         };
110
111         my $sql_dvd_from = qq{
112                         -- LEFT  JOIN dvds              ON dvds.ID = files.dvdid
113         };
114
115         my $sql_where;
116         $sql_where = " WHERE ". $where if ($where);
117
118         my $sql_order = qq{
119                 ORDER BY files.date
120                 LIMIT $on_page
121                 OFFSET ?
122         };
123
124         my $sql_count = qq{ select count(files.id) $sql_from $sql_where };
125         my $sql_results = qq{ select $sql_cols $sql_from $sql_dvd_from $sql_where $sql_order };
126
127         $offset ||= 0;
128         $offset = ($offset * $on_page);
129
130         my $sth = $dbh->prepare($sql_count);
131         $sth->execute();
132         my ($results) = $sth->fetchrow_array();
133
134         $sth = $dbh->prepare($sql_results);
135         $sth->execute( $offset );
136
137         if ($sth->rows != $results) {
138                 my $bug = "$0 BUG: [[ $sql_count ]] = $results while [[ $sql_results ]] = " . $sth->rows;
139                 $bug =~ s/\s+/ /gs;
140                 print STDERR "$bug\n";
141         }
142
143         my @ret;
144       
145         while (my $row = $sth->fetchrow_hashref()) {
146                 push(@ret, { 
147                         'hname'         => $row->{'hname'}, 
148                         'sname'         => $row->{'sname'},
149                         'sharename'     => $row->{'sharename'},
150                         'backupno'      => $row->{'backupnum'},
151                         'fname'         => $row->{'filename'},
152                         'fpath'         => $row->{'filepath'},
153                         'networkpath'   => $row->{'networkpath'},
154                         'date'          => $row->{'date'},
155                         'type'          => $row->{'filetype'},
156                         'size'          => $row->{'size'},
157                         'id'            => $row->{'fid'},
158                         'dvd'           => $row->{'dvd'}
159                 });
160         }
161      
162         $sth->finish();
163         $dbh->disconnect();
164         return ($results, \@ret);
165 }
166
167 sub getBackupsNotBurned() {
168
169         my $dbh = DBI->connect($dsn, $db_user, "", { RaiseError => 1, AutoCommit => 1 } );
170         my $sql = q{ 
171         SELECT
172                 backups.hostID          AS hostid,
173                 min(hosts.name)         AS host,
174                 backups.num             AS backupno,
175                 min(backups.type)       AS type,
176                 min(backups.date)       AS date,
177                 min(backups.size)       AS size
178         FROM files 
179                 INNER JOIN shares       ON files.shareID=shares.ID
180                 INNER JOIN hosts        ON hosts.ID = shares.hostID
181                 INNER JOIN backups      ON backups.num = files.backupNum and backups.hostID = hosts.ID AND backups.shareID = shares.ID
182         WHERE 
183                 files.dvdid     IS NULL
184         GROUP BY 
185                 backups.hostID, backups.num
186         ORDER BY min(backups.date)
187         };
188         my $sth = $dbh->prepare( $sql );
189         my @ret;
190         $sth->execute();
191
192         while ( my $row = $sth->fetchrow_hashref() ) {
193                 $row->{'age'} = sprintf("%0.1f", ( (time() - $row->{'date'}) / 86400 ) );
194                 $row->{'size'} = sprintf("%0.2f", $row->{'size'} / 1024 / 1024);
195                 push @ret, $row;
196         }
197       
198         return @ret;      
199 }
200
201 sub displayBackupsGrid()
202   {
203       my $retHTML = "";
204       my $addForm = 1;
205       
206       if ($addForm) {
207
208             $retHTML .= <<EOF3;
209 <script language="javascript" type="text/javascript">
210 <!--
211
212     function checkAll(location)
213     {
214       for (var i=0;i<document.forma.elements.length;i++)
215       {
216         var e = document.forma.elements[i];
217         if ((e.checked || !e.checked) && e.name != \'all\') {
218             if (eval("document.forma."+location+".checked")) {
219                 e.checked = true;
220             } else {
221                 e.checked = false;
222             }
223         }
224       }
225     }
226 //-->
227 </script>      
228 EOF3
229               $retHTML .= q{<form name="forma" method="GET" action="}."$MyURL"."?action=burn\"";
230               $retHTML.= q{<input type="hidden" value="burn" name="action">};
231               $retHTML .= q{<input type="hidden" value="results" name="search_results">};
232         }
233         $retHTML .= qq{
234                 <table style="fview" border="1" cellspacing="0" cellpadding="3">
235                 <tr class="tableheader">
236         };
237
238         if ($addForm) {
239             $retHTML .= "<td class=\"tableheader\"><input type=\"checkbox\" name=\"allFiles\" onClick=\"checkAll('allFiles');\"></td>";
240         }
241         $retHTML .=  qq{
242                 <td align="center">Host</td>
243                 <td align="center">Backup no</td>
244                 <td align="center">Type</td>
245                 <td align="center">date</td>
246                 <td align="center">age/days</td>
247                 <td align="center">size/MB</td>
248                 </tr>
249         };
250
251         my @backups = getBackupsNotBurned();
252         my $backup;
253
254         if ($addForm) {
255                 $retHTML .= qq{
256                         <tr><td colspan=7 style="tableheader">
257                         <input type="submit" value="Burn selected backups on medium" name="submitBurner">
258                         </td></tr>
259                 };
260         }
261
262         foreach $backup(@backups) {
263
264                 my $ftype = "";
265             
266                 $retHTML .= "<tr>";
267                 if ($addForm) {
268                         $retHTML .= '<td class="fview"><input type="checkbox" name="fcb' .
269                                 $backup->{'hostid'}.'_'.$backup->{'backupno'} . 
270                                 '" value="' . $backup->{'hostid'}.'_'.$backup->{'backupno'} .
271                                 '"></td>';
272                 }           
273             
274                 $retHTML .= '<td class="fviewborder">' . $backup->{'host'} . '</td>' .
275                         '<td class="fviewborder">' . $backup->{'backupno'} . '</td>' .
276                         '<td class="fviewborder">' . $backup->{'type'} . '</td>' .
277                         '<td class="fviewborder">' . epoch_to_iso( $backup->{'date'} ) . '</td>' .
278                         '<td class="fviewborder">' . $backup->{'age'} . '</td>' .
279                         '<td class="fviewborder">' . $backup->{'size'} . '</td>' .
280                         '</tr>';
281         }
282
283         $retHTML .= "</table>";
284
285         if ($addForm) {
286                 $retHTML .= "</form>";
287         }
288       
289         return $retHTML;
290 }      
291
292 sub displayGrid($$$$) {
293         my ($where, $addForm, $offset, $hilite) = @_;
294         my $retHTML = "";
295  
296         my $start_t = time();
297
298         my ($results, $files) = getFiles($where, $offset);
299
300         my $dur_t = time() - $start_t;
301         my $dur = sprintf("%0.4fs", $dur_t);
302
303         my ($from, $to) = (($offset * $on_page) + 1, ($offset * $on_page) + $on_page);
304
305         if ($results <= 0) {
306                 $retHTML .= qq{
307                         <p style="color: red;">No results found...</p>
308                 };
309                 return $retHTML;
310         } else {
311                 # DEBUG
312                 #use Data::Dumper;
313                 #$retHTML .= '<pre>' . Dumper($files) . '</pre>';
314         }
315
316
317         if ($addForm) {
318                 $retHTML .= qq{<form name="forma" method="GET" action="$MyURL">};
319                 $retHTML.= qq{<input type="hidden" value="search" name="action">};
320                 $retHTML .= qq{<input type="hidden" value="results" name="search_results">};
321         }
322
323
324         $retHTML .= qq{
325         <div>
326         Found <b>$results files</b> showing <b>$from - $to</b> (took $dur)
327         </div>
328         <table style="fview" width="100%" border="0" cellpadding="2" cellspacing="0">
329                 <tr class="fviewheader"> 
330                 <td align="center">Share</td>
331                 <td align="center">Type and Name</td>
332                 <td align="center">#</td>
333                 <td align="center">Size</td>
334                 <td align="center">Date</td>
335                 <td align="center">Media</td>
336                 </tr>
337         };
338
339         my $file;
340
341         sub hilite_html($$) {
342                 my ($html, $search) = @_;
343                 $html =~ s#($search)#<b>$1</b>#gis;
344                 return $html;
345         }
346
347         sub restore_link($$$$$$) {
348                 my $type = shift;
349                 my $action = 'RestoreFile';
350                 $action = 'browse' if (lc($type) eq 'dir');
351                 return sprintf(qq{<a href="?action=%s&host=%s&num=%d&share=%s&dir=%s">%s</a>}, $action, @_);
352         }
353
354         foreach $file (@{ $files }) {
355                 my $typeStr  = BackupPC::Attrib::fileType2Text(undef, $file->{'type'});
356                 $retHTML .= qq{<tr class="fviewborder">};
357
358                 $retHTML .=
359                         qq{<td class="fviewborder" align="right">} . $file->{'sharename'} . qq{</td>} .
360                         qq{<td class="fviewborder"><img src="$Conf{CgiImageDirURL}/icon-$typeStr.gif" alt="$typeStr" align="middle">&nbsp;} . hilite_html( $file->{'fpath'}, $hilite ) . qq{</td>} .
361                         qq{<td class="fviewborder" align="center">} . restore_link( $typeStr, ${EscURI( $file->{'hname'} )}, $file->{'backupno'}, ${EscURI( $file->{'sname'})}, ${EscURI( $file->{'fpath'} )}, $file->{'backupno'} ) . qq{</td>} .
362                         qq{<td class="fviewborder" align="right">} . $file->{'size'} . qq{</td>} .
363                         qq{<td class="fviewborder">} . epoch_to_iso( $file->{'date'} ) . qq{</td>} .
364                         qq{<td class="fviewborder">} . $file->{'dvd'} . qq{</td>};
365
366                 $retHTML .= "</tr>";
367         }
368         $retHTML .= "</table>";
369
370         # all variables which has to be transfered
371         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/) {
372                 $retHTML .= qq{<INPUT TYPE="hidden" NAME="$n" VALUE="$In{$n}">\n};
373         }
374
375         my $del = '';
376         my $max_page = int( $results / $on_page );
377         my $page = 0;
378
379         my $link_fmt = '<a href = "#" onclick="document.forma.offset.value=%d;document.forma.submit();">%s</a>';
380
381         $retHTML .= '<div style="text-align: center;">';
382
383         if ($offset > 0) {
384                 $retHTML .= sprintf($link_fmt, $offset - 1, '&lt;&lt;') . ' ';
385         }
386
387         while ($page <= $max_page) {
388                 if ($page == $offset) {
389                         $retHTML .= $del . '<b>' . ($page + 1) . '</b>';
390                 } else {
391                         $retHTML .= $del . sprintf($link_fmt, $page, $page + 1);
392                 }
393
394                 if ($page < $offset - $pager_pages && $page != 0) {
395                         $retHTML .= " ... ";
396                         $page = $offset - $pager_pages;
397                         $del = '';
398                 } elsif ($page > $offset + $pager_pages && $page != $max_page) {
399                         $retHTML .= " ... ";
400                         $page = $max_page;
401                         $del = '';
402                 } else {
403                         $del = ' | ';
404                         $page++;
405                 }
406         }
407
408         if ($offset < $max_page) {
409                 $retHTML .= ' ' . sprintf($link_fmt, $offset + 1, '&gt;&gt;');
410         }
411
412         $retHTML .= "</div>";
413
414         $retHTML .= "</form>" if ($addForm);
415
416         return $retHTML;
417 }
418
419 1;