show scheduled and burned columns
[BackupPC.git] / lib / BackupPC / Search.pm
1 #!/usr/bin/perl
2 package BackupPC::Search;
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 use XML::Writer;
12 use IO::File;
13 use Data::Dump qw(dump);
14
15 require Exporter;
16 our @ISA=qw(Exporter);
17 our @EXPORT=qw(unit);
18
19 my $on_page = 100;
20 my $pager_pages = 10;
21
22 my $dbh;
23
24 my $bpc = BackupPC::Lib->new || die;
25 $bpc->ConfigRead('_search_archive');
26 my %Conf = $bpc->Conf();
27
28 sub search_module {
29
30         my $search_module = $Conf{SearchModule} || die "search is disabled";
31         eval "use $search_module";
32         if ( $@ ) {
33                 warn "ERROR: $search_module: $!";
34         } else {
35                 warn "# using $search_module for full-text search";
36         }
37
38         return $search_module->new( %Conf );
39 }
40
41 my $dbh;
42
43 sub get_dbh {
44         $dbh ||= DBI->connect($Conf{SearchDSN}, $Conf{SearchUser}, "", { RaiseError => 1, AutoCommit => 1 } );
45         return $dbh;
46 }
47
48 sub getUnits() {
49         my @ret;
50
51         my $dbh = get_dbh();
52         my $sth = $dbh->prepare(qq{
53                 SELECT
54                         shares.id       as id,
55                         hosts.name || ':' || shares.name as share
56                 FROM shares
57                 JOIN hosts on hostid = hosts.id
58                 ORDER BY share
59         } );
60         $sth->execute();
61         push @ret, { 'id' => '', 'share' => '-'};       # dummy any
62
63         while ( my $row = $sth->fetchrow_hashref() ) {
64                 push @ret, $row;
65         }
66         return @ret;
67 }
68
69 sub epoch_to_iso {
70         my $t = shift || return;
71         my $iso = BackupPC::Lib::timeStamp(undef, $t);
72         $iso =~ s/\s/ /g;
73         return $iso;
74 }
75
76 sub dates_from_form($) {
77         my $param = shift || return;
78
79         sub mk_epoch_date($$) {
80                 my ($name,$suffix) = @_;
81
82                 my $yyyy = $param->{ $name . '_year_' . $suffix} || return undef;
83                 my $mm .= $param->{ $name . '_month_' . $suffix} ||
84                         ( $suffix eq 'from' ? 1 : 12);
85                 my $dd .= $param->{ $name . '_day_' . $suffix} ||
86                         ( $suffix eq 'from' ? 1 : 31);
87
88                 $yyyy =~ s/\D//g;
89                 $mm =~ s/\D//g;
90                 $dd =~ s/\D//g;
91
92                 my $h = my $m = my $s = 0;
93                 if ($suffix eq 'to') {
94                         $h = 23;
95                         $m = 59;
96                         $s = 59;
97                 }
98
99                 my $dt = new DateTime(
100                         year => $yyyy,
101                         month => $mm,
102                         day => $dd,
103                         hour => $h,
104                         minute => $m,
105                         second => $s,
106                 );
107                 print STDERR "mk_epoch_date($name,$suffix) [$yyyy-$mm-$dd] = " . $dt->ymd . " " . $dt->hms . "\n";
108                 return $dt->epoch || 'NULL';
109         }
110
111         my @ret = (
112                 mk_epoch_date('search_backup', 'from'),
113                 mk_epoch_date('search_backup', 'to'),
114                 mk_epoch_date('search', 'from'),
115                 mk_epoch_date('search', 'to'),
116         );
117
118         return @ret;
119
120 }
121
122
123 sub getWhere($) {
124         my $param = shift || return;
125
126         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
127
128         my @conditions;
129         push @conditions, qq{ backups.date >= $backup_from } if ($backup_from);
130         push @conditions, qq{ backups.date <= $backup_to } if ($backup_to);
131         push @conditions, qq{ files.date >= $files_from } if ($files_from);
132         push @conditions, qq{ files.date <= $files_to } if ($files_to);
133
134         print STDERR "backup: $backup_from - $backup_to files: $files_from - $files_to cond:" . join(" and ",@conditions);
135
136         push( @conditions, ' files.shareid = ' . $param->{'search_share'} ) if ($param->{'search_share'});
137         push (@conditions, " upper(files.path) LIKE upper('%".$param->{'search_filename'}."%')") if ($param->{'search_filename'});
138
139         if ( $param->{burned} ) {
140                 my $is_what = 'is null';
141                 $is_what = '= 1' if ($param->{burned} eq 'burned');
142                 push @conditions, "archive_burned.part $is_what";
143                 push @conditions, "archive_burned.copy $is_what";
144         }
145
146         return join(" and ", @conditions);
147 }
148
149 my $sort_def = {
150         search => {
151                 default => 'date_a',
152                 sql => {
153                         sname_d => 'shares.name DESC',
154                         sname_a => 'shares.name ASC',
155                         filepath_d => 'files.path DESC',
156                         filepath_a => 'files.path ASC',
157                         backupnum_d => 'files.backupnum DESC',
158                         backupnum_a => 'files.backupnum ASC',
159                         size_d => 'files.size DESC',
160                         size_a => 'files.size ASC',
161                         date_d => 'files.date DESC',
162                         date_a => 'files.date ASC',
163                 },
164         }, burn => {
165                 default => 'date_a',
166                 sql => {
167                         sname_d => 'host DESC, share DESC',
168                         sname_a => 'host ASC, share ASC',
169                         num_d => 'backupnum DESC',
170                         num_a => 'backupnum ASC',
171                         date_d => 'date DESC',
172                         date_a => 'date ASC',
173                         age_d => 'age DESC',
174                         age_a => 'age ASC',
175                         size_d => 'size DESC',
176                         size_a => 'size ASC',
177                         incsize_d => 'inc_size DESC',
178                         incsize_a => 'inc_size ASC',
179                 }
180         }
181 };
182
183 sub getSort($$$) {
184         my ($part,$type, $sort_order) = @_;
185
186         die "unknown part: $part" unless ($sort_def->{$part});
187         die "unknown type: $type" unless ($sort_def->{$part}->{$type});
188
189         $sort_order ||= $sort_def->{$part}->{'default'};
190
191         if (my $ret = $sort_def->{$part}->{$type}->{$sort_order}) {
192                 return $ret;
193         } else {
194                 # fallback to default sort order
195                 return $sort_def->{$part}->{$type}->{ $sort_def->{$part}->{'default'} };
196         }
197 }
198
199 sub getFiles($) {
200         my ($param) = @_;
201
202         my $offset = $param->{'offset'} || 0;
203         $offset *= $on_page;
204
205         my $dbh = get_dbh();
206
207         my $sql_cols = qq{
208                 files.id                        AS fid,
209                 hosts.name                      AS hname,
210                 shares.name                     AS sname,
211                 files.backupnum                 AS backupnum,
212                 files.path                      AS filepath,
213                 files.date                      AS date,
214                 files.type                      AS type,
215                 files.size                      AS size
216         };
217
218         my $sql_from = qq{
219                 FROM files 
220                         INNER JOIN shares       ON files.shareID=shares.ID
221                         INNER JOIN hosts        ON hosts.ID = shares.hostID
222                         INNER JOIN backups      ON backups.num = files.backupnum and backups.hostID = hosts.ID AND backups.shareID = files.shareID
223         };
224
225         my $sql_where;
226         my $where = getWhere($param);
227         $sql_where = " WHERE ". $where if ($where);
228
229         # do we have to add tables for burned media?
230         if ( $param->{burned} ) {
231                 $sql_from .= qq{
232                         LEFT OUTER JOIN archive_backup on archive_backup.backup_id = backups.id
233                         LEFT OUTER JOIN archive_burned on archive_burned.archive_id = archive_backup.archive_id
234                 };
235         }
236
237         my $order = getSort('search', 'sql', $param->{'sort'});
238
239         my $sql_order = qq{
240                 ORDER BY $order
241                 LIMIT $on_page
242                 OFFSET ?
243         };
244
245         my $sql_count = qq{ select count(files.id) $sql_from $sql_where };
246         my $sql_results = qq{ select $sql_cols $sql_from $sql_where $sql_order };
247
248         my $sth = $dbh->prepare($sql_count);
249         $sth->execute();
250         my ($results) = $sth->fetchrow_array();
251
252         $sth = $dbh->prepare($sql_results);
253         $sth->execute( $offset );
254
255         if ($sth->rows != $results) {
256                 my $bug = "$0 BUG: [[ $sql_count ]] = $results while [[ $sql_results ]] = " . $sth->rows;
257                 $bug =~ s/\s+/ /gs;
258                 print STDERR "$bug\n";
259         }
260
261         my @ret;
262       
263         while (my $row = $sth->fetchrow_hashref()) {
264                 push @ret, $row;
265         }
266      
267         $sth->finish();
268         return ($results, \@ret);
269 }
270
271 sub getFilesHyperEstraier($) {
272         my ($param) = @_;
273
274         my $offset = $param->{'offset'} || 0;
275         $offset *= $on_page;
276
277         my $q = $param->{'search_filename'};
278         my $shareid = $param->{'search_share'};
279         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
280
281         return search_module->search(
282                 $offset, $on_page, $param->{sort},
283                 $q, $shareid, $backup_from, $backup_to, $files_from, $files_to
284         );
285                 
286 }
287
288 sub getGzipName($$$)
289 {
290         my ($host, $share, $backupnum) = @_;
291         my $ret = $Conf{GzipSchema};
292         
293         $share =~ s/\//_/g;
294         $ret =~ s/\\h/$host/ge;
295         $ret =~ s/\\s/$share/ge;
296         $ret =~ s/\\n/$backupnum/ge;
297
298         $ret =~ s/__+/_/g;
299
300         return $ret;
301         
302 }
303
304 sub get_tgz_size_by_name($) {
305         my $name = shift;
306
307         my $tgz = $Conf{GzipTempDir}.'/'.$name;
308         my $size = -1;
309
310         $|=1;
311         if (-f "${tgz}.tar.gz") {
312                 $size = (stat("${tgz}.tar.gz"))[7];
313         } elsif (-d $tgz) {
314                 opendir(my $dir, $tgz) || die "can't opendir $tgz: $!";
315                 my @parts = grep { !/^\./ && !/md5/ && -f "$tgz/$_" } readdir($dir);
316                 $size = 0;
317                 foreach my $part (@parts) {
318                         my $currSize =  (stat("$tgz/$part"))[7]; 
319                         $size += (stat("$tgz/$part"))[7] || die "can't stat $tgz/$part: $!";
320                 }
321
322                 closedir $dir;
323         } else {
324                 return -1;
325         }
326
327         return $size;
328 }
329
330 sub getGzipSizeFromBackupID($) {
331         my ($backupID) = @_;
332         my $dbh = get_dbh();
333         my $sql = q{
334                                 SELECT hosts.name  as host,
335                                            shares.name as share,
336                                            backups.num as backupnum
337                                 FROM hosts, backups, shares
338                                 WHERE shares.id=backups.shareid AND
339                                           hosts.id =backups.hostid AND
340                                           backups.id = ?
341         };
342         my $sth = $dbh->prepare($sql);
343         $sth->execute($backupID);
344         my $row = $sth->fetchrow_hashref();
345
346         return get_tgz_size_by_name(
347                 getGzipName($row->{'host'}, $row->{share}, $row->{backupnum})
348         );
349 }
350
351 sub getGzipSize($$)
352 {
353         my ($hostID, $backupNum) = @_;
354         my $sql;
355         my $dbh = get_dbh();
356         
357         $sql = q{ 
358                                 SELECT hosts.name  as host,
359                                            shares.name as share,
360                                            backups.num as backupnum
361                                 FROM hosts, backups, shares
362                                 WHERE shares.id=backups.shareid AND
363                                           hosts.id =backups.hostid AND
364                                           hosts.id=? AND
365                                           backups.num=?
366                         };
367         my $sth = $dbh->prepare($sql);
368         $sth->execute($hostID, $backupNum);
369
370         my $row = $sth->fetchrow_hashref();
371
372         return get_tgz_size_by_name(
373                 getGzipName($row->{'host'}, $row->{share}, $row->{'backupnum'})
374         );
375 }
376
377
378 sub getBackupsNotBurned($) {
379
380         my $param = shift;
381         my $dbh = get_dbh();
382
383         my $order = getSort('burn', 'sql', $param->{'sort'});
384
385 print STDERR "## sort=". ($param->{'sort'} || 'no sort param') . " burn sql order: $order\n";
386
387         my $sql = qq{
388                 SELECT
389                         p.id,
390                         p.filename,
391                         b.date,
392                         date_part('epoch',now()) - b.date as age,
393                         p.size,
394                         count(ap.*) as scheduled,
395                         count(ab.*) as burned
396                 FROM backup_parts p
397                 JOIN backups b          ON b.id = p.backup_id
398                 LEFT OUTER JOIN archive_parts  ap ON ap.backup_part_id = p.id
399                 LEFT OUTER JOIN archive_burned ab ON ab.archive_id = ap.archive_id
400                 GROUP BY p.id,filename,b.date,age,p.size,p.part_nr
401                 ORDER BY b.date,p.part_nr
402         };
403         my $sth = $dbh->prepare( $sql );
404         my @ret;
405         $sth->execute();
406
407         while ( my $row = $sth->fetchrow_hashref() ) {
408                 $row->{'age'} = sprintf("%0.1f", ( $row->{'age'} / 86400 ) );
409                 #$row->{'age'} = sprintf("%0.1f", ( (time() - $row->{'date'}) / 86400 ) );
410                 push @ret, $row;
411         }
412       
413         return @ret;
414 }
415
416 sub displayBackupsGrid($) {
417
418         my $param = shift;
419
420         my $max_archive_size = $Conf{ArchiveMediaSize} || die "no ArchiveMediaSize";
421         my $max_archive_file_size = $Conf{ArchiveChunkSize}  || die "no MaxFileInSize";
422
423         my $retHTML .= q{
424                 <form id="forma" method="POST" action="}.$MyURL.q{?action=burn">
425         };
426
427         $retHTML .= <<'EOF3';
428 <style type="text/css">
429 <!--
430 DIV#fixedBox {
431         position: absolute;
432         top: 50em;
433         left: -24%;
434         padding: 0.5em;
435         width: 20%;
436         background-color: #E0F0E0;
437         border: 1px solid #00C000;
438 }
439
440 DIV#fixedBox, DIV#fixedBox INPUT, DIV#fixedBox TEXTAREA {
441         font-size: 10pt;
442 }
443
444 FORM>DIV#fixedBox {
445         position: fixed !important;
446         left: 0.5em !important;
447         top: auto !important;
448         bottom: 1em !important;
449         width: 15% !important;
450 }
451
452 DIV#fixedBox INPUT[type=text], DIV#fixedBox TEXTAREA {
453         border: 1px solid #00C000;
454 }
455
456 DIV#fixedBox #note {
457         display: block;
458         width: 100%;
459 }
460
461 DIV#fixedBox #submitBurner {
462         display: block;
463         width: 100%;
464         margin-top: 0.5em;
465         cursor: pointer;
466 }
467
468 * HTML {
469         overflow-y: hidden;
470 }
471
472 * HTML BODY {
473         overflow-y: auto;
474         height: 100%;
475         font-size: 100%;
476 }
477
478 * HTML DIV#fixedBox {
479         position: absolute;
480 }
481
482 #mContainer, #gradient, #mask, #progressIndicator {
483         display: block;
484         width: 100%;
485         font-size: 10pt;
486         font-weight: bold;
487         text-align: center;
488         vertical-align: middle;
489         padding: 1px;
490 }
491
492 #gradient, #mask, #progressIndicator {
493         left: 0;
494         border-width: 1px;
495         border-style: solid;
496         border-color: #000000;
497         color: #404040;
498         margin: 0.4em;
499         position: absolute;
500         margin-left: -1px;
501         margin-top: -1px;
502         margin-bottom: -1px;
503         overflow: hidden;
504 }
505
506 #mContainer {
507         display: block;
508         position: relative;
509         padding: 0px;
510         margin-top: 0.4em;
511         margin-bottom: 0.5em;
512 }
513
514 #gradient {
515         z-index: 1;
516         background-color: #FFFF00;
517 }
518
519 #mask {
520         z-index: 2;
521         background-color: #FFFFFF;
522 }
523
524 #progressIndicator {
525         z-index: 3;
526         background-color: transparent;
527 }
528
529 #volumes {
530         padding: 0.4em;
531         display: none;
532         width: 100%;
533         font-size: 80%;
534         color: #ff0000;
535         text-align: center;
536 }
537 -->
538 </style>
539 <script type="text/javascript">
540 <!--
541
542 var debug_div;
543 EOF3
544
545         # take maximum archive size from configuration
546         $retHTML .= qq{
547 var media_size = $max_archive_size ;
548 var max_file_size = $max_archive_file_size;
549
550 };
551
552         $retHTML .= <<'EOF3';
553
554 function debug(msg) {
555         return; // Disable debugging
556
557         if (! debug_div) debug_div = document.getElementById('debug');
558
559         // this will create debug div if it doesn't exist.
560         if (! debug_div) {
561                 debug_div = document.createElement('div');
562                 if (document.body) document.body.appendChild(debug_div);
563                 else debug_div = null;
564         }
565         if (debug_div) {
566                 debug_div.appendChild(document.createTextNode(msg));
567                 debug_div.appendChild(document.createElement("br"));
568         }
569 }
570
571
572 var element_id_cache = Array();
573
574 function element_id(name,element) {
575         if (! element_id_cache[name]) {
576                 element_id_cache[name] = self.document.getElementById(name);
577         }
578         return element_id_cache[name];
579 }
580
581 function checkAll(location) {
582         var f = element_id('forma') || null;
583         if (!f) return false;
584
585         var len = f.elements.length;
586         var check_all = element_id('allFiles');
587         var suma = check_all.checked ? (parseInt(f.elements['totalsize'].value) || 0) : 0;
588
589         for (var i = 0; i < len; i++) {
590                 var e = f.elements[i];
591                 if (e.name != 'all' && e.name.substr(0, 3) == 'fcb') {
592                         if (check_all.checked) {
593                                 if (e.checked) continue;
594                                 var el = element_id("fss" + e.name.substr(3));
595                                 var size = parseInt(el.value) || 0;
596                                 debug('suma: '+suma+' size: '+size);
597                                 if ((suma + size) < media_size) {
598                                         suma += size;
599                                         e.checked = true;
600                                 } else {
601                                         break;
602                                 }
603                         } else {
604                                 e.checked = false;
605                         }
606                 }
607         }
608         update_sum(suma);
609 }
610
611 function update_sum(suma, suma_disp) {
612         if (! suma_disp) suma_disp = suma;
613         suma_disp = Math.floor(suma_disp / 1024);
614         element_id('forma').elements['totalsize_kb'].value = suma_disp;
615         element_id('forma').elements['totalsize'].value = suma;
616         pbar_set(suma, media_size);
617         debug('total size: ' + suma);
618 }
619
620 function update_size(name, checked, suma) {
621         var size = parseInt( element_id("fss" + name).value);
622
623         if (checked) {
624                 suma += size;
625         } else {
626                 suma -= size;
627         }
628
629         debug('update_size('+name+','+checked+') suma: '+suma);
630 /* FIXME
631         if (volumes > 1) {
632                 if (checked) {
633                         element_id("volumes").innerHTML = "This will take "+volumes+" mediums!";
634                         element_id("volumes").style.display = 'block';
635                         suma = size;
636                         update_sum(suma);
637                 } else {
638                         suma -= size;
639                         element_id("volumes").style.display = 'none';
640                 }
641         }
642 */
643         return suma;
644 }
645
646 function sumiraj(e) {
647         var suma = parseInt(element_id('forma').elements['totalsize'].value) || 0;
648         var len = element_id('forma').elements.length;
649         if (e) {
650                 suma = update_size(e.name.substr(3), e.checked, suma);
651                 if (suma < 0) suma = 0;
652         } else {
653                 suma = 0;
654                 for (var i = 0; i < len; i++) {
655                         var fel = element_id('forma').elements[i];
656                         if (fel.name != 'all' && fel.checked && fel.name.substr(0,3) == 'fcb') {
657                                 suma = update_size(fel.name.substr(3), fel.checked, suma);
658                         } 
659                 }
660         }
661         update_sum(suma);
662         return suma;
663 }
664
665 /* progress bar */
666
667 var _pbar_width = null;
668 var _pbar_warn = 10;    // change color in last 10%
669
670 function pbar_reset() {
671         element_id("mask").style.left = "0px";
672         _pbar_width = element_id("mContainer").offsetWidth - 2;
673         element_id("mask").style.width = _pbar_width + "px";
674         element_id("mask").style.display = "block";
675         element_id("progressIndicator").style.zIndex  = 10;
676         element_id("progressIndicator").innerHTML = "0";
677 }
678
679 function dec2hex(d) {
680         var hch = '0123456789ABCDEF';
681         var a = d % 16;
682         var q = (d - a) / 16;
683         return hch.charAt(q) + hch.charAt(a);
684 }
685
686 function pbar_set(amount, max) {
687         debug('pbar_set('+amount+', '+max+')');
688
689         if (_pbar_width == null) {
690                 var _mc = element_id("mContainer");
691                 if (_pbar_width == null) _pbar_width = parseInt(_mc.offsetWidth ? (_mc.offsetWidth - 2) : 0) || null;
692                 if (_pbar_width == null) _pbar_width = parseInt(_mc.clientWidth ? (_mc.clientWidth + 2) : 0) || null;
693                 if (_pbar_width == null) _pbar_width = 0;
694         }
695
696         var pcnt = Math.floor(amount * 100 / max);
697         var p90 = 100 - _pbar_warn;
698         var pcol = pcnt - p90;
699         if (Math.round(pcnt) <= 100) {
700                 if (pcol < 0) pcol = 0;
701                 var e = element_id("submitBurner");
702                 debug('enable_button');
703                 e.disabled = false;
704                 var a = e.getAttributeNode('disabled') || null;
705                 if (a) e.removeAttributeNode(a);
706         } else {
707                 debug('disable button');
708                 pcol = _pbar_warn;
709                 var e = element_id("submitBurner");
710                 if (!e.disabled) e.disabled = true;
711         }
712         var col_g = Math.floor((_pbar_warn - pcol) * 255 / _pbar_warn);
713         var col = '#FF' + dec2hex(col_g) + '00';
714
715         //debug('pcol: '+pcol+' g:'+col_g+' _pbar_warn:'+ _pbar_warn + ' color: '+col);
716         element_id("gradient").style.backgroundColor = col;
717
718         element_id("progressIndicator").innerHTML = pcnt + '%';
719         //element_id("progressIndicator").innerHTML = amount;
720
721         element_id("mask").style.clip = 'rect(' + Array(
722                 '0px',
723                 element_id("mask").offsetWidth + 'px',
724                 element_id("mask").offsetHeight + 'px',
725                 Math.round(_pbar_width * amount / max) + 'px'
726         ).join(' ') + ')';
727 }
728
729 if (!self.body) self.body = new Object();
730 self.onload = self.document.onload = self.body.onload = function() {
731         //pbar_reset();
732         sumiraj();
733 };
734
735 // -->
736 </script>
737 <div id="fixedBox">
738
739 <input type="hidden" name="totalsize"/>
740 Size: <input type="text" name="totalsize_kb" size="7" readonly="readonly" style="text-align:right;" value="0" /> kB
741
742 <div id="mContainer">
743         <div id="gradient">&nbsp;</div>
744         <div id="mask">&nbsp;</div>
745         <div id="progressIndicator">0%</div>
746 </div>
747 <br/>
748
749 <div id="volumes">&nbsp;</div>
750
751 Note:
752 <textarea name="note" cols="10" rows="5" id="note"></textarea>
753
754 <input type="submit" id="submitBurner" value="Burn selected" name="submitBurner" />
755
756 </div>
757 <!--
758 <div id="debug" style="float: right; width: 10em; border: 1px #ff0000 solid; background-color: #ffe0e0; -moz-opacity: 0.7;">
759 no debug output yet
760 </div>
761 -->
762 EOF3
763         $retHTML .= q{
764                         <input type="hidden" value="burn" name="action">
765                         <input type="hidden" value="results" name="search_results">
766                         <table style="fview" border="0" cellspacing="0" cellpadding="2">
767                         <tr class="tableheader">
768                         <td class="tableheader">
769                                 <input type="checkbox" name="allFiles" id="allFiles" onClick="checkAll('allFiles');">
770                         </td>
771         } .
772                 sort_header($param, 'Filename', 'filename', 'left') .
773                 sort_header($param, 'Date', 'date', 'center') .
774                 sort_header($param, 'Age/days', 'age', 'center') .
775                 sort_header($param, 'Size', 'size', 'center') .
776         qq{
777                         <td align="center" title="scheduled">sc</td>
778                         <td align="center" title="burned">bu</td>
779                 </tr>
780         };
781
782         my @color = (' bgcolor="#e0e0e0"', '');
783
784         my $i = 1;
785 #       my $img_url = $Conf{CgiImageDirURL};
786
787         foreach my $backup ( getBackupsNotBurned($param) ) {
788
789                 $retHTML .= join(''
790                         ,'<tr',$color[$i++%2],'>'
791                         ,'<td class="fview">'
792                                 ,'<input type="checkbox" name="fcb',$backup->{id},'" value="',$backup->{id},'" onClick="sumiraj(this);">'
793                                 ,'<input type="hidden" id="fss',$backup->{id},'" value="',$backup->{size},'">'
794                         ,'</td>'
795                         ,'<td align="left">', $backup->{'filename'}, '</td>'
796                         ,'<td align="center">', epoch_to_iso( $backup->{'date'} ), '</td>'
797                         ,'<td align="center">', $backup->{'age'}, '</td>'
798                         ,'<td align="right">', unit($backup->{'size'}), '</td>'
799                         ,'<td align="center">', $backup->{scheduled}, '</td>'
800                         ,'<td align="center">', $backup->{burned}, '</td>'
801                         ,"</tr>\n"
802                 );
803         }
804
805         $retHTML .= "</table>";
806         $retHTML .= "</form>";
807       
808         return $retHTML;
809 }      
810
811 sub displayGrid($) {
812         my ($param) = @_;
813
814         my $offset = $param->{'offset'};
815         my $hilite = $param->{'search_filename'};
816
817         my $retHTML = "";
818  
819         my $start_t = time();
820
821         my ($results, $files);
822         if ($param->{'use_hest'} && length($hilite) > 0) {
823                 ($results, $files) = getFilesHyperEstraier($param);
824         } else {
825                 ($results, $files) = getFiles($param);
826         }
827
828         my $dur_t = time() - $start_t;
829         my $dur = sprintf("%0.4fs", $dur_t);
830
831         my ($from, $to) = (($offset * $on_page) + 1, ($offset * $on_page) + $on_page);
832
833         if ($results <= 0) {
834                 $retHTML .= qq{
835                         <p style="color: red;">No results found...</p>
836                 };
837                 return $retHTML;
838         } else {
839                 # DEBUG
840                 #use Data::Dumper;
841                 #$retHTML .= '<pre>' . Dumper($files) . '</pre>';
842         }
843
844
845         $retHTML .= qq{
846         <div>
847         Found <b>$results files</b> showing <b>$from - $to</b> (took $dur)
848         </div>
849         <table style="fview" width="100%" border="0" cellpadding="2" cellspacing="0">
850                 <tr class="fviewheader"> 
851                 <td></td>
852         };
853
854         sub sort_header($$$$) {
855                 my ($param, $display, $name, $align) = @_;
856
857                 my ($sort_what, $sort_direction) = split(/_/,$param->{'sort'},2);
858
859                 my $old_sort = $param->{'sort'};
860
861                 my $html = qq{<td align="$align"};
862                 my $arrow = '';
863
864                 if (lc($sort_what) eq lc($name)) {
865                         my $direction = lc($sort_direction);
866
867                         # swap direction or fallback to default
868                         $direction =~ tr/ad/da/;
869                         $direction = 'a' unless ($direction =~ /[ad]/);
870
871                         $param->{'sort'} = $name . '_' . $direction;
872                         $html .= ' style="border: 1px solid #808080;"';
873                 
874                         # add unicode arrow for direction
875                         $arrow .= '&nbsp;';
876                         $arrow .= $direction eq 'a'  ?  '&#9650;'
877                                 : $direction eq 'd'  ?  '&#9660;'
878                                 :                       ''
879                                 ;
880
881                 } else {
882                         $param->{'sort'} = $name . '_a';
883                 }
884
885                 $html .= '><a href="' . page_uri($param) . '">' . $display . '</a>' . $arrow . '</td>';
886                 $param->{'sort'} = $old_sort;
887
888                 return $html;
889         }
890
891         $retHTML .=
892                 sort_header($param, 'Share', 'sname', 'center') .
893                 sort_header($param, 'Type and Name', 'filepath', 'center') .
894                 sort_header($param, '#', 'backupnum', 'center') .
895                 sort_header($param, 'Size', 'size', 'center') .
896                 sort_header($param, 'Date', 'date', 'center');
897
898         $retHTML .= qq{
899                 <td align="center">Media</td>
900                 </tr>
901         };
902
903         my $file;
904
905         sub hilite_html($$) {
906                 my ($html, $search) = @_;
907                 $html =~ s#($search)#<b>$1</b>#gis;
908                 return $html;
909         }
910
911         sub restore_link($$$$$$) {
912                 my $type = shift;
913                 my $action = 'RestoreFile';
914                 $action = 'browse' if (lc($type) eq 'dir');
915                 return sprintf(qq{<a href="?action=%s&host=%s&num=%d&share=%s&dir=%s">%s</a>}, $action, @_);
916         }
917
918         my $sth_archived;
919         my %archived_cache;
920
921         sub check_archived($$$) {
922                 my ($host, $share, $num) = @_;
923
924                 if (my $html = $archived_cache{"$host $share $num"}) {
925                         return $html;
926                 }
927
928                 $sth_archived ||= $dbh->prepare(qq{
929                         select
930                                 dvd_nr, note,
931                                 count(archive_burned.copy) as copies
932                         from archive
933                         inner join archive_burned on archive_burned.archive_id = archive.id
934                         inner join archive_backup on archive.id = archive_backup.archive_id
935                         inner join backups on backups.id = archive_backup.backup_id
936                         inner join hosts on hosts.id = backups.hostid
937                         inner join shares on shares.id = backups.shareid
938                         where hosts.name = ? and shares.name = ? and backups.num = ?
939                         group by dvd_nr, note
940                 });
941
942                 my @mediums;
943
944                 $sth_archived->execute($host, $share, $num);
945                 while (my $row = $sth_archived->fetchrow_hashref()) {
946                         push @mediums, '<abbr title="' .
947                                 $row->{'note'} .
948                                 ' [' . $row->{'copies'} . ']' .
949                                 '">' .$row->{'dvd_nr'} .
950                                 '</abbr>';
951                 }
952
953                 my $html = join(", ",@mediums);
954                 $archived_cache{"$host $share $num"} = $html;
955                 return $html;
956         }
957
958         my $i = $offset * $on_page;
959
960         foreach $file (@{ $files }) {
961                 $i++;
962
963                 my $typeStr  = BackupPC::Attrib::fileType2Text(undef, $file->{'type'});
964                 $retHTML .= qq{<tr class="fviewborder">};
965
966                 $retHTML .= qq{<td class="fviewborder">$i</td>};
967
968                 $retHTML .=
969                         qq{<td class="fviewborder" align="right">} . $file->{'hname'} . ':' . $file->{'sname'} . qq{</td>} .
970                         qq{<td class="fviewborder"><img src="$Conf{CgiImageDirURL}/icon-$typeStr.png" alt="$typeStr" align="middle">&nbsp;} . hilite_html( $file->{'filepath'}, $hilite ) . qq{</td>} .
971                         qq{<td class="fviewborder" align="center">} . restore_link( $typeStr, ${EscURI( $file->{'hname'} )}, $file->{'backupnum'}, ${EscURI( $file->{'sname'})}, ${EscURI( $file->{'filepath'} )}, $file->{'backupnum'} ) . qq{</td>} .
972                         qq{<td class="fviewborder" align="right">} . $file->{'size'} . qq{</td>} .
973                         qq{<td class="fviewborder">} . epoch_to_iso( $file->{'date'} ) . qq{</td>} .
974                         qq{<td class="fviewborder">} . check_archived( $file->{'hname'}, $file->{'sname'}, $file->{'backupnum'} ) . qq{</td>};
975
976                 $retHTML .= "</tr>";
977         }
978         $retHTML .= "</table>";
979
980         # all variables which has to be transfered
981         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/) {
982                 $retHTML .= qq{<INPUT TYPE="hidden" NAME="$n" VALUE="$In{$n}">\n};
983         }
984
985         my $del = '';
986         my $max_page = int( $results / $on_page );
987         my $page = 0;
988
989         sub page_uri($) {
990                 my $param = shift || die "no param?";
991
992                 my $uri = $MyURL;
993                 my $del = '?';
994                 foreach my $k (keys %{ $param }) {
995                         if ($param->{$k}) {
996                                 $uri .= $del . $k . '=' . ${EscURI( $param->{$k} )};
997                                 $del = '&';
998                         }
999                 }
1000                 return $uri;
1001         }
1002
1003         sub page_link($$$) {
1004                 my ($param,$page,$display) = @_;
1005
1006                 $param->{'offset'} = $page if (defined($page));
1007
1008                 my $html = '<a href = "' . page_uri($param) . '">' . $display . '</a>';
1009         }
1010
1011         $retHTML .= '<div style="text-align: center;">';
1012
1013         if ($offset > 0) {
1014                 $retHTML .= page_link($param, $offset - 1, '&lt;&lt;') . ' ';
1015         }
1016
1017         while ($page <= $max_page) {
1018                 if ($page == $offset) {
1019                         $retHTML .= $del . '<b>' . ($page + 1) . '</b>';
1020                 } else {
1021                         $retHTML .= $del . page_link($param, $page, $page + 1);
1022                 }
1023
1024                 if ($page < $offset - $pager_pages && $page != 0) {
1025                         $retHTML .= " ... ";
1026                         $page = $offset - $pager_pages;
1027                         $del = '';
1028                 } elsif ($page > $offset + $pager_pages && $page != $max_page) {
1029                         $retHTML .= " ... ";
1030                         $page = $max_page;
1031                         $del = '';
1032                 } else {
1033                         $del = ' | ';
1034                         $page++;
1035                 }
1036         }
1037
1038         if ($offset < $max_page) {
1039                 $retHTML .= ' ' . page_link($param, $offset + 1, '&gt;&gt;');
1040         }
1041
1042         $retHTML .= "</div>";
1043
1044         return $retHTML;
1045 }
1046
1047 my @units = qw/b k M G/;
1048 sub unit {
1049         my $v = shift;
1050
1051         my $o = 0;
1052
1053         while ( ( $v / 10000 ) >= 1 ) {
1054                 $o++;
1055                 $v /= 1024;
1056         }
1057
1058         if ( $v >= 1 ) {
1059                 return sprintf("%d%s", $v, $units[$o]);
1060         } elsif ( $v == 0 ) {
1061                 return 0;
1062         } else {
1063                 return sprintf("%.1f%s", $v, $units[$o]);
1064         }
1065 }
1066
1067 1;