fix restore url escaping
[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         $t += 60 * 60 * +2;     # FIXME add TZ
36         my $dt = DateTime->from_epoch( epoch => $t ) || return;
37         print STDERR "BUG: $t != " . $dt->epoch . "\n" unless ($t == $dt->epoch);
38         return $dt->ymd . ' ' . $dt->hms;
39 }
40
41 sub getWhere($) {
42         my ($param)    = @_;
43         my @conditions;
44
45         sub mk_epoch_date($$) {
46                 my ($name,$suffix) = @_;
47
48                 my $yyyy = $param->{ $name . '_year_' . $suffix} || return;
49                 my $mm .= $param->{ $name . '_month_' . $suffix} ||
50                         ( $suffix eq 'from' ? 1 : 12);
51                 my $dd .= $param->{ $name . '_day_' . $suffix} ||
52                         ( $suffix eq 'from' ? 1 : 31);
53                 my $dt = new DateTime(
54                         year => $yyyy,
55                         month => $mm,
56                         day => $dd
57                 );
58                 return $dt->epoch || 'NULL';
59         }
60
61         my $backup_from = mk_epoch_date('search_backup', 'from');
62         push @conditions, qq{ backups.date >= $backup_from } if ($backup_from);
63         my $backup_to = mk_epoch_date('search_backup', 'to');
64         push @conditions, qq{ backups.date <= $backup_to } if ($backup_to);
65
66         my $files_from = mk_epoch_date('search', 'from');
67         push @conditions, qq{ files.date >= $files_from } if ($files_from);
68         my $files_to = mk_epoch_date('search', 'to');
69         push @conditions, qq{ files.date <= $files_to } if ($files_to);
70
71         print STDERR "backup: $backup_from - $backup_to files: $files_from - $files_to cond:" . join(" | ",@conditions);
72     
73         push( @conditions, ' files.shareid = ' . $param->{'search_share'} ) if ($param->{'search_share'});
74
75         push (@conditions, " upper(files.path) LIKE upper('%".$param->{'search_filename'}."%')") if ($param->{'search_filename'});
76
77         return (
78                 join(" and ", @conditions),
79                 $files_from, $files_to,
80                 $backup_from, $backup_to
81         );
82 }
83
84
85 sub getFiles($$) {
86         my ($where, $offset) = @_;
87
88         my $dbh = DBI->connect($dsn, $db_user, "", { RaiseError => 1, AutoCommit => 1 } );
89
90         my $sql_cols = qq{
91                 files.id                        AS fid,
92                 hosts.name                      AS hname,
93                 shares.name                     AS sname,
94                 shares.share                    AS sharename,
95                 files.backupNum                 AS backupNum,
96                 files.name                      AS filename,
97                 files.path                      AS filepath,
98                 files.date                      AS date,
99                 files.type                      AS filetype,
100                 files.size                      AS size,
101                 dvds.name                       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 backups
179                 INNER JOIN hosts        ON hosts.ID = backups.hostID
180         WHERE 
181                 files.dvdid     IS NULL
182         GROUP BY 
183                 backups.hostID, backups.num
184         ORDER BY min(backups.date)
185         };
186         my $sth = $dbh->prepare( $sql );
187         my @ret;
188         $sth->execute();
189
190         while ( my $row = $sth->fetchrow_hashref() ) {
191                 $row->{'age'} = sprintf("%0.1f", ( (time() - $row->{'date'}) / 86400 ) );
192                 $row->{'size'} = sprintf("%0.2f", $row->{'size'} / 1024 / 1024);
193                 push @ret, $row;
194         }
195       
196         return @ret;      
197 }
198
199 sub displayBackupsGrid()
200   {
201       my $retHTML = "";
202       my $addForm = 1;
203       
204       if ($addForm) {
205
206             $retHTML .= <<EOF3;
207 <script language="javascript" type="text/javascript">
208 <!--
209
210     function checkAll(location)
211     {
212       for (var i=0;i<document.forma.elements.length;i++)
213       {
214         var e = document.forma.elements[i];
215         if ((e.checked || !e.checked) && e.name != \'all\') {
216             if (eval("document.forma."+location+".checked")) {
217                 e.checked = true;
218             } else {
219                 e.checked = false;
220             }
221         }
222       }
223     }
224 //-->
225 </script>      
226 EOF3
227               $retHTML .= q{<form name="forma" method="GET" action="}."$MyURL"."?action=burn\"";
228               $retHTML.= q{<input type="hidden" value="burn" name="action">};
229               $retHTML .= q{<input type="hidden" value="results" name="search_results">};
230         }
231         $retHTML .= qq{<table style="fview"><tr>};
232
233         if ($addForm) {
234             $retHTML .= "<td class=\"tableheader\"><input type=\"checkbox\" name=\"allFiles\" onClick=\"checkAll('allFiles');\"></td>";
235         }
236         $retHTML .=  qq{
237                 <td class="tableheader">Host</td>
238                 <td class="tableheader">Backup no</td>
239                 <td class="tableheader">Type</td>
240                 <td class="tableheader">date</td>
241                 <td class="tableheader">age/days</td>
242                 <td class="tableheader">size/MB</td>
243                 </tr>
244         };
245
246         my @backups = getBackupsNotBurned();
247         my $backup;
248
249         if ($addForm) {
250                 $retHTML .= qq{
251                         <tr><td colspan=7 style="tableheader">
252                         <input type="submit" value="Burn selected backups on medium" name="submitBurner">
253                         </td></tr>
254                 };
255         }
256
257         foreach $backup(@backups) {
258
259                 my $ftype = "";
260             
261                 $retHTML .= "<tr>";
262                 if ($addForm) {
263                         $retHTML .= '<td class="fview"><input type="checkbox" name="fcb' .
264                                 $backup->{'hostid'}.'_'.$backup->{'backupno'} . 
265                                 '" value="' . $backup->{'hostid'}.'_'.$backup->{'backupno'} .
266                                 '"></td>';
267                 }           
268             
269                 $retHTML .= '<td class="fviewborder">' . $backup->{'host'} . '</td>' .
270                         '<td class="fviewborder">' . $backup->{'backupno'} . '</td>' .
271                         '<td class="fviewborder">' . $backup->{'type'} . '</td>' .
272                         '<td class="fviewborder">' . epoch_to_iso( $backup->{'date'} ) . '</td>' .
273                         '<td class="fviewborder">' . $backup->{'age'} . '</td>' .
274                         '<td class="fviewborder">' . $backup->{'size'} . '</td>' .
275                         '</tr>';
276         }
277
278         $retHTML .= "</table>";
279
280         if ($addForm) {
281                 $retHTML .= "</form>";
282         }
283       
284         return $retHTML;
285 }      
286
287 sub displayGrid($$$$) {
288         my ($where, $addForm, $offset, $hilite) = @_;
289         my $retHTML = "";
290  
291         my $start_t = time();
292
293         my ($results, $files) = getFiles($where, $offset);
294
295         my $dur_t = time() - $start_t;
296         my $dur = sprintf("%0.4fs", $dur_t);
297
298         my ($from, $to) = (($offset * $on_page) + 1, ($offset * $on_page) + $on_page);
299
300         if ($results <= 0) {
301                 $retHTML .= qq{
302                         <p style="color: red;">No results found...</p>
303                 };
304                 return $retHTML;
305         } else {
306                 # DEBUG
307                 #use Data::Dumper;
308                 #$retHTML .= '<pre>' . Dumper($files) . '</pre>';
309         }
310
311
312         if ($addForm) {
313                 $retHTML .= qq{<form name="forma" method="GET" action="$MyURL">};
314                 $retHTML.= qq{<input type="hidden" value="search" name="action">};
315                 $retHTML .= qq{<input type="hidden" value="results" name="search_results">};
316         }
317
318
319         $retHTML .= qq{
320         <br/>Found <b>$results files</b> showing <b>$from - $to</b> (took $dur)
321         <table style="fview" width="100%">
322                 <tr> 
323                 <td class="tableheader">Share</td>
324                 <td class="tableheader">Name</td>
325                 <td class="tableheader">Type</td>
326                 <td class="tableheader">#</td>
327                 <td class="tableheader">Size</td>
328                 <td class="tableheader">Date</td>
329                 <td class="tableheader">Media</td>
330                 </tr>
331         };
332
333         my $file;
334
335         sub hilite_html($$) {
336                 my ($html, $search) = @_;
337                 $html =~ s#($search)#<b>$1</b>#gis;
338                 return $html;
339         }
340
341         sub restore_link($$$$$$) {
342                 my $type = shift;
343                 my $action = 'RestoreFile';
344                 $action = 'browse' if (lc($type) eq 'dir');
345                 return sprintf(qq{<a href="?action=%s&host=%s&num=%d&share=%s&dir=%s">%s</a>}, $action, @_);
346         }
347
348         foreach $file (@{ $files }) {
349                 my $typeStr  = BackupPC::Attrib::fileType2Text(undef, $file->{'type'});
350                 $retHTML .= "<tr>";
351
352                 foreach my $v ((
353                         $file->{'sharename'},
354                         qq{<img src="$Conf{CgiImageDirURL}/icon-$typeStr.gif" align="center">&nbsp;} . hilite_html( $file->{'fpath'}, $hilite ),
355                         $typeStr,
356                         restore_link( $typeStr, ${EscURI( $file->{'hname'} )}, $file->{'backupno'}, ${EscURI( $file->{'sname'})}, ${EscURI( $file->{'fpath'} )}, $file->{'backupno'} ),
357                         $file->{'size'},
358                         epoch_to_iso( $file->{'date'} ),
359                         $file->{'dvd'}
360                 )) {
361                         $retHTML .= qq{<td class="fviewborder">$v</td>};
362                 }
363
364                 $retHTML .= "</tr>";
365         }
366         $retHTML .= "</table>";
367
368         # all variables which has to be transfered
369         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/) {
370                 $retHTML .= qq{<INPUT TYPE="hidden" NAME="$n" VALUE="$In{$n}">\n};
371         }
372
373         my $del = '';
374         my $max_page = int( $results / $on_page );
375         my $page = 0;
376
377         my $link_fmt = '<a href = "#" onclick="document.forma.offset.value=%d;document.forma.submit();">%s</a>';
378
379         $retHTML .= '<div style="text-align: center;">';
380
381         if ($offset > 0) {
382                 $retHTML .= sprintf($link_fmt, $offset - 1, '&lt;&lt;') . ' ';
383         }
384
385         while ($page <= $max_page) {
386                 if ($page == $offset) {
387                         $retHTML .= $del . '<b>' . ($page + 1) . '</b>';
388                 } else {
389                         $retHTML .= $del . sprintf($link_fmt, $page, $page + 1);
390                 }
391
392                 if ($page < $offset - $pager_pages && $page != 0) {
393                         $retHTML .= " ... ";
394                         $page = $offset - $pager_pages;
395                         $del = '';
396                 } elsif ($page > $offset + $pager_pages && $page != $max_page) {
397                         $retHTML .= " ... ";
398                         $page = $max_page;
399                         $del = '';
400                 } else {
401                         $del = ' | ';
402                         $page++;
403                 }
404         }
405
406         if ($offset < $max_page) {
407                 $retHTML .= ' ' . sprintf($link_fmt, $offset + 1, '&gt;&gt;');
408         }
409
410         $retHTML .= "</div>";
411
412         $retHTML .= "</form>" if ($addForm);
413
414         return $retHTML;
415 }
416
417 1;