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