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