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