r10317@llin: dpavlin | 2006-03-07 17:24:19 +0100
[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                 }
318         } else {
319                 die "BUG: unimplemented";
320         }
321
322         # for each document in result
323         for my $i ($offset .. ($offset + $on_page - 1)) {
324                 last if ($i >= $result->doc_num);
325
326                 my $doc;
327                 if ($hest_node_url) {
328                         $doc = $result->get_doc($i);
329                 } else {
330                         die "BUG: unimplemented";
331                 }
332
333                 my $row;
334                 foreach my $c (qw/fid hname sname backupnum filepath date type size/) {
335                         $row->{$c} = $doc->attr($c);
336                 }
337                 push @res, $row;
338         }
339
340         return ($hits, \@res);
341 }
342
343 sub getGzipName($$$)
344 {
345         my ($host, $share, $backupnum) = @_;
346         my $ret = $Conf{GzipSchema};
347         
348         $share =~ s/\//_/g;
349         $ret =~ s/\\h/$host/ge;
350         $ret =~ s/\\s/$share/ge;
351         $ret =~ s/\\n/$backupnum/ge;
352
353         $ret =~ s/__+/_/g;
354
355         return $ret;
356         
357 }
358
359 sub get_tgz_size_by_name($) {
360         my $name = shift;
361
362         my $tgz = $Conf{InstallDir}.'/'.$Conf{GzipTempDir}.'/'.$name;
363
364         my $size = -1;
365
366         if (-f "${tgz}.tar.gz") {
367                 $size = (stat("${tgz}.tar.gz"))[7];
368         } elsif (-d $tgz) {
369                 opendir(my $dir, $tgz) || die "can't opendir $tgz: $!";
370                 my @parts = grep { !/^\./ && !/md5/ && -f "$tgz/$_" } readdir($dir);
371                 $size = 0;
372                 foreach my $part (@parts) {
373                         $size += (stat("$tgz/$part"))[7] || die "can't stat $tgz/$part: $!";
374                 }
375                 closedir $dir;
376         } else {
377                 return -1;
378         }
379
380         return $size;
381 }
382
383 sub getGzipSize($$)
384 {
385         my ($hostID, $backupNum) = @_;
386         my $sql;
387         my $dbh = get_dbh();
388         
389         $sql = q{ 
390                                 SELECT hosts.name  as host,
391                                            shares.name as share,
392                                            backups.num as backupnum
393                                 FROM hosts, backups, shares
394                                 WHERE shares.id=backups.shareid AND
395                                           hosts.id =backups.hostid AND
396                                           hosts.id=? AND
397                                           backups.num=?
398                         };
399         my $sth = $dbh->prepare($sql);
400         $sth->execute($hostID, $backupNum);
401
402         my $row = $sth->fetchrow_hashref();
403
404         return get_tgz_size_by_name(
405                 getGzipName($row->{'host'}, $row->{share}, $row->{'backupnum'})
406         );
407 }
408
409 sub getVolumes($) {
410         my $id = shift;
411
412         my $max_archive_size = $Conf{MaxArchiveSize} || die "no MaxArchiveSize";
413
414         my $sth = $dbh->prepare(qq{
415                 select
416                         size
417                 from backup_parts
418                 where backup_id = ?
419                 order by part_nr asc
420         });
421
422         $sth->execute($id);
423
424         my $cumulative_size = 0;
425         my $volumes = 1;
426
427         while(my ($size) = $sth->fetchrow_array) {
428                 if ($cumulative_size + $size > $max_archive_size) {
429                         $volumes++;
430                         $cumulative_size = $size;
431                 } else {
432                         $cumulative_size += $size;
433                 }
434         }
435
436         return ($volumes,$cumulative_size);
437 }
438
439 sub getBackupsNotBurned($) {
440
441         my $param = shift;
442         my $dbh = get_dbh();
443
444         my $order = getSort('burn', 'sql', $param->{'sort'});
445
446 print STDERR "## sort=". ($param->{'sort'} || 'no sort param') . " burn sql order: $order\n";
447
448         my $sql = qq{
449                 SELECT 
450                         backups.hostID AS hostID,
451                         hosts.name AS host,
452                         shares.name AS share,
453                         backups.num AS backupnum,
454                         backups.type AS type,
455                         backups.date AS date,
456                         date_part('epoch',now()) - backups.date as age,
457                         backups.size AS size,
458                         backups.id AS id,
459                         backups.inc_size AS inc_size,
460                         backups.parts AS parts
461                 FROM backups 
462                 INNER JOIN shares       ON backups.shareID=shares.ID
463                 INNER JOIN hosts        ON backups.hostID = hosts.ID
464                 LEFT OUTER JOIN archive_backup ON archive_backup.backup_id = backups.id 
465                 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
466                 GROUP BY
467                         backups.hostID,
468                         hosts.name,
469                         shares.name,
470                         backups.num,
471                         backups.shareid,
472                         backups.id,
473                         backups.type,
474                         backups.date,
475                         backups.size,
476                         backups.inc_size,
477                         backups.parts
478                 ORDER BY $order
479         };
480         my $sth = $dbh->prepare( $sql );
481         my @ret;
482         $sth->execute();
483
484         while ( my $row = $sth->fetchrow_hashref() ) {
485                 $row->{'age'} = sprintf("%0.1f", ( $row->{'age'} / 86400 ) );
486                 #$row->{'age'} = sprintf("%0.1f", ( (time() - $row->{'date'}) / 86400 ) );
487
488                 my $max_archive_size = $Conf{MaxArchiveSize} || die "no MaxArchiveSize";
489                 if ($row->{size} > $max_archive_size) {
490                         ($row->{volumes}, $row->{inc_size_calc}) = getVolumes($row->{id});
491                 }
492
493                 $row->{size} = sprintf("%0.2f", $row->{size} / 1024 / 1024);
494
495                 # do some cluster calculation (approximate)
496                 $row->{inc_size} = int(( ($row->{inc_size} + 1023 ) / 2 )  * 2);
497                 $row->{inc_size_calc} ||= $row->{inc_size};
498                 push @ret, $row;
499         }
500       
501         return @ret;
502 }
503
504 sub displayBackupsGrid($) {
505
506         my $param = shift;
507
508         my $max_archive_size = $Conf{MaxArchiveSize} || die "no MaxArchiveSize";
509         my $max_archive_file_size = $Conf{MaxArchiveFileSize}  || die "no MaxFileInSize";
510
511         my $retHTML .= q{
512                 <form id="forma" method="POST" action="}.$MyURL.q{?action=burn">
513         };
514
515         $retHTML .= <<'EOF3';
516 <style type="text/css">
517 <!--
518 DIV#fixedBox {
519         position: absolute;
520         top: 50em;
521         left: -24%;
522         padding: 0.5em;
523         width: 20%;
524         background-color: #E0F0E0;
525         border: 1px solid #00C000;
526 }
527
528 DIV#fixedBox, DIV#fixedBox INPUT, DIV#fixedBox TEXTAREA {
529         font-size: 10pt;
530 }
531
532 FORM>DIV#fixedBox {
533         position: fixed !important;
534         left: 0.5em !important;
535         top: auto !important;
536         bottom: 1em !important;
537         width: 15% !important;
538 }
539
540 DIV#fixedBox INPUT[type=text], DIV#fixedBox TEXTAREA {
541         border: 1px solid #00C000;
542 }
543
544 DIV#fixedBox #note {
545         display: block;
546         width: 100%;
547 }
548
549 DIV#fixedBox #submitBurner {
550         display: block;
551         width: 100%;
552         margin-top: 0.5em;
553         cursor: pointer;
554 }
555
556 * HTML {
557         overflow-y: hidden;
558 }
559
560 * HTML BODY {
561         overflow-y: auto;
562         height: 100%;
563         font-size: 100%;
564 }
565
566 * HTML DIV#fixedBox {
567         position: absolute;
568 }
569
570 #mContainer, #gradient, #mask, #progressIndicator {
571         display: block;
572         width: 100%;
573         font-size: 10pt;
574         font-weight: bold;
575         text-align: center;
576         vertical-align: middle;
577         padding: 1px;
578 }
579
580 #gradient, #mask, #progressIndicator {
581         left: 0;
582         border-width: 1px;
583         border-style: solid;
584         border-color: #000000;
585         color: #404040;
586         margin: 0.4em;
587         position: absolute;
588         margin-left: -1px;
589         margin-top: -1px;
590         margin-bottom: -1px;
591         overflow: hidden;
592 }
593
594 #mContainer {
595         display: block;
596         position: relative;
597         padding: 0px;
598         margin-top: 0.4em;
599         margin-bottom: 0.5em;
600 }
601
602 #gradient {
603         z-index: 1;
604         background-color: #FFFF00;
605 }
606
607 #mask {
608         z-index: 2;
609         background-color: #FFFFFF;
610 }
611
612 #progressIndicator {
613         z-index: 3;
614         background-color: transparent;
615 }
616
617 #volumes {
618         padding: 0.4em;
619         display: none;
620         width: 100%;
621         font-size: 80%;
622         color: #ff0000;
623         text-align: center;
624 }
625 -->
626 </style>
627 <script type="text/javascript">
628 <!--
629
630 var debug_div = null;
631 EOF3
632
633         # take maximum archive size from configuration
634         $retHTML .= qq{
635 var media_size = $max_archive_size ;
636 var max_file_size = $max_archive_file_size;
637
638 };
639
640         $retHTML .= <<'EOF3';
641
642 function debug(msg) {
643         return; // Disable debugging
644
645         if (! debug_div) debug_div = document.getElementById('debug');
646
647         // this will create debug div if it doesn't exist.
648         if (! debug_div) {
649                 debug_div = document.createElement('div');
650                 if (document.body) document.body.appendChild(debug_div);
651                 else debug_div = null;
652         }
653         if (debug_div) {
654                 debug_div.appendChild(document.createTextNode(msg));
655                 debug_div.appendChild(document.createElement("br"));
656         }
657 }
658
659
660 var element_id_cache = Array();
661
662 function element_id(name,element) {
663         if (! element_id_cache[name]) {
664                 element_id_cache[name] = self.document.getElementById(name);
665         }
666         return element_id_cache[name];
667 }
668
669 function checkAll(location) {
670         var f = element_id('forma') || null;
671         if (!f) return false;
672
673         var len = f.elements.length;
674         var check_all = element_id('allFiles');
675         var suma = check_all.checked ? (parseInt(f.elements['totalsize'].value) || 0) : 0;
676
677         for (var i = 0; i < len; i++) {
678                 var e = f.elements[i];
679                 if (e.name != 'all' && e.name.substr(0, 3) == 'fcb') {
680                         if (check_all.checked) {
681                                 if (e.checked) continue;
682                                 var el = element_id("fss" + e.name.substr(3));
683                                 var size = parseInt(el.value) || 0;
684                                 debug('suma: '+suma+' size: '+size);
685                                 if ((suma + size) < media_size) {
686                                         suma += size;
687                                         e.checked = true;
688                                 } else {
689                                         break;
690                                 }
691                         } else {
692                                 e.checked = false;
693                         }
694                 }
695         }
696         update_sum(suma);
697 }
698
699 function update_sum(suma, suma_disp) {
700         if (! suma_disp) suma_disp = suma;
701         suma_disp = Math.floor(suma_disp / 1024);
702         element_id('forma').elements['totalsize_kb'].value = suma_disp;
703         element_id('forma').elements['totalsize'].value = suma;
704         pbar_set(suma, media_size);
705         debug('total size: ' + suma);
706 }
707
708 function update_size(name, checked, suma) {
709         var size = parseInt( element_id("fss" + name).value);
710
711         if (checked) {
712                 suma += size;
713         } else {
714                 suma -= size;
715         }
716
717         var volumes = parseInt( element_id("prt" + name).value);
718         debug('update_size('+name+','+checked+') suma: '+suma+' volumes: '+volumes);
719         if (volumes > 1) {
720                 if (checked) {
721                         element_id("volumes").innerHTML = "This will take "+volumes+" mediums!";
722                         element_id("volumes").style.display = 'block';
723                         suma = size;
724                         update_sum(suma);
725                 } else {
726                         suma -= size;
727                         element_id("volumes").style.display = 'none';
728                 }
729         }
730
731         return suma;
732 }
733
734 function sumiraj(e) {
735         var suma = parseInt(element_id('forma').elements['totalsize'].value) || 0;
736         var len = element_id('forma').elements.length;
737         if (e) {
738                 suma = update_size(e.name.substr(3), e.checked, suma);
739                 if (suma < 0) suma = 0;
740         } else {
741                 suma = 0;
742                 for (var i = 0; i < len; i++) {
743                         var fel = element_id('forma').elements[i];
744                         if (fel.name != 'all' && fel.checked && fel.name.substr(0,3) == 'fcb') {
745                                 suma = update_size(fel.name.substr(3), fel.checked, suma);
746                         }
747                 }
748         }
749         update_sum(suma);
750         return suma;
751 }
752
753 /* progress bar */
754
755 var _pbar_width = null;
756 var _pbar_warn = 10;    // change color in last 10%
757
758 function pbar_reset() {
759         element_id("mask").style.left = "0px";
760         _pbar_width = element_id("mContainer").offsetWidth - 2;
761         element_id("mask").style.width = _pbar_width + "px";
762         element_id("mask").style.display = "block";
763         element_id("progressIndicator").style.zIndex  = 10;
764         element_id("progressIndicator").innerHTML = "0";
765 }
766
767 function dec2hex(d) {
768         var hch = '0123456789ABCDEF';
769         var a = d % 16;
770         var q = (d - a) / 16;
771         return hch.charAt(q) + hch.charAt(a);
772 }
773
774 function pbar_set(amount, max) {
775         debug('pbar_set('+amount+', '+max+')');
776
777         if (_pbar_width == null) {
778                 var _mc = element_id("mContainer");
779                 if (_pbar_width == null) _pbar_width = parseInt(_mc.offsetWidth ? (_mc.offsetWidth - 2) : 0) || null;
780                 if (_pbar_width == null) _pbar_width = parseInt(_mc.clientWidth ? (_mc.clientWidth + 2) : 0) || null;
781                 if (_pbar_width == null) _pbar_width = 0;
782         }
783
784         var pcnt = Math.floor(amount * 100 / max);
785         var p90 = 100 - _pbar_warn;
786         var pcol = pcnt - p90;
787         if (Math.round(pcnt) <= 100) {
788                 if (pcol < 0) pcol = 0;
789                 var e = element_id("submitBurner");
790                 debug('enable_button');
791                 e.disabled = false;
792                 var a = e.getAttributeNode('disabled') || null;
793                 if (a) e.removeAttributeNode(a);
794         } else {
795                 debug('disable button');
796                 pcol = _pbar_warn;
797                 var e = element_id("submitBurner");
798                 if (!e.disabled) e.disabled = true;
799         }
800         var col_g = Math.floor((_pbar_warn - pcol) * 255 / _pbar_warn);
801         var col = '#FF' + dec2hex(col_g) + '00';
802
803         //debug('pcol: '+pcol+' g:'+col_g+' _pbar_warn:'+ _pbar_warn + ' color: '+col);
804         element_id("gradient").style.backgroundColor = col;
805
806         element_id("progressIndicator").innerHTML = pcnt + '%';
807         //element_id("progressIndicator").innerHTML = amount;
808
809         element_id("mask").style.clip = 'rect(' + Array(
810                 '0px',
811                 element_id("mask").offsetWidth + 'px',
812                 element_id("mask").offsetHeight + 'px',
813                 Math.round(_pbar_width * amount / max) + 'px'
814         ).join(' ') + ')';
815 }
816
817 if (!self.body) self.body = new Object();
818 self.onload = self.document.onload = self.body.onload = function() {
819         //pbar_reset();
820         sumiraj();
821 };
822
823 // -->
824 </script>
825 <div id="fixedBox">
826
827 <input type="hidden" name="totalsize"/>
828 Size: <input type="text" name="totalsize_kb" size="7" readonly="readonly" style="text-align:right;" value="0" /> kB
829
830 <div id="mContainer">
831         <div id="gradient">&nbsp;</div>
832         <div id="mask">&nbsp;</div>
833         <div id="progressIndicator">0%</div>
834 </div>
835 <br/>
836
837 <div id="volumes">&nbsp;</div>
838
839 Note:
840 <textarea name="note" cols="10" rows="5" id="note"></textarea>
841
842 <input type="submit" id="submitBurner" value="Burn selected" name="submitBurner" />
843
844 </div>
845 <!--
846 <div id="debug" style="float: right; width: 10em; border: 1px #ff0000 solid; background-color: #ffe0e0; -moz-opacity: 0.7;">
847 no debug output yet
848 </div>
849 -->
850 EOF3
851         $retHTML .= q{
852                         <input type="hidden" value="burn" name="action">
853                         <input type="hidden" value="results" name="search_results">
854                         <table style="fview" border="0" cellspacing="0" cellpadding="2">
855                         <tr class="tableheader">
856                         <td class="tableheader">
857                                 <input type="checkbox" name="allFiles" id="allFiles" onClick="checkAll('allFiles');">
858                         </td>
859         } .
860                 sort_header($param, 'Share', 'share', 'center') .
861                 sort_header($param, '#', 'num', 'center') .
862         qq{
863                         <td align="center">Type</td>
864         } .
865                 sort_header($param, 'Date', 'date', 'center') .
866                 sort_header($param, 'Age/days', 'age', 'center') .
867                 sort_header($param, 'Size/Mb', 'size', 'center') .
868                 sort_header($param, 'gzip size/Kb', 'incsize', 'center') .
869         qq{
870                         <td align="center">medias</td></tr>
871         };
872
873         my @color = (' bgcolor="#e0e0e0"', '');
874
875         my $i = 0;
876         my $host = '';
877
878         foreach my $backup ( getBackupsNotBurned($param) ) {
879
880                 if ($host ne $backup->{'host'}) {
881                         $i++;
882                         $host = $backup->{'host'};
883                 }
884                 my $ftype = "";
885
886                 my $checkbox_key = $backup->{'hostid'}. '_' .$backup->{'backupnum'} . '_' . $backup->{'id'};
887
888                 $retHTML .=
889                         '<tr' . $color[$i %2 ] . '>
890                         <td class="fview">';
891
892                 if (($backup->{'inc_size'} || 0) > 0) {
893                         $retHTML .= '
894                         <input type="checkbox" name="fcb' . $checkbox_key . '" value="' . $checkbox_key . '" onClick="sumiraj(this);">';
895                 }
896
897                 my $img_url = $Conf{CgiImageDirURL};
898
899                 $retHTML .=
900                         '</td>' .
901                         '<td align="right">' . $backup->{'host'} . ':' . $backup->{'share'} . '</td>' .
902                         '<td align="center">' . $backup->{'backupnum'} . '</td>' .
903                         '<td align="center">' . $backup->{'type'} . '</td>' .
904                         '<td align="center">' . epoch_to_iso( $backup->{'date'} ) . '</td>' .
905                         '<td align="center">' . $backup->{'age'} . '</td>' .
906                         '<td align="right">' . $backup->{'size'} . '</td>' .
907                         '<td align="right">' . sprintf("%0.1f", $backup->{'inc_size'} / 1024 ) .
908                         '<input type="hidden" id="fss'.$checkbox_key .'" value="'. $backup->{'inc_size_calc'} .'"></td>' .
909                         '<input type="hidden" id="prt'.$checkbox_key .'" value="'. $backup->{'volumes'} .'"></td>' .
910                         '<td align="left">' . ( qq{<img src="$img_url/icon-cd.gif" alt="media">} x $backup->{volumes} ) . '</td>' .
911
912                         "</tr>\n";
913         }
914
915         $retHTML .= "</table>";
916         $retHTML .= "</form>";
917       
918         return $retHTML;
919 }      
920
921 sub displayGrid($) {
922         my ($param) = @_;
923
924         my $offset = $param->{'offset'};
925         my $hilite = $param->{'search_filename'};
926
927         my $retHTML = "";
928  
929         my $start_t = time();
930
931         my ($results, $files);
932         if ($param->{'use_hest'} && length($hilite) > 0) {
933                 ($results, $files) = getFilesHyperEstraier($param);
934         } else {
935                 ($results, $files) = getFiles($param);
936         }
937
938         my $dur_t = time() - $start_t;
939         my $dur = sprintf("%0.4fs", $dur_t);
940
941         my ($from, $to) = (($offset * $on_page) + 1, ($offset * $on_page) + $on_page);
942
943         if ($results <= 0) {
944                 $retHTML .= qq{
945                         <p style="color: red;">No results found...</p>
946                 };
947                 return $retHTML;
948         } else {
949                 # DEBUG
950                 #use Data::Dumper;
951                 #$retHTML .= '<pre>' . Dumper($files) . '</pre>';
952         }
953
954
955         $retHTML .= qq{
956         <div>
957         Found <b>$results files</b> showing <b>$from - $to</b> (took $dur)
958         </div>
959         <table style="fview" width="100%" border="0" cellpadding="2" cellspacing="0">
960                 <tr class="fviewheader"> 
961                 <td></td>
962         };
963
964         sub sort_header($$$$) {
965                 my ($param, $display, $name, $align) = @_;
966
967                 my ($sort_what, $sort_direction) = split(/_/,$param->{'sort'},2);
968
969                 my $old_sort = $param->{'sort'};
970
971                 my $html = qq{<td align="$align"};
972                 my $arrow = '';
973
974                 if (lc($sort_what) eq lc($name)) {
975                         my $direction = lc($sort_direction);
976
977                         # swap direction or fallback to default
978                         $direction =~ tr/ad/da/;
979                         $direction = 'a' unless ($direction =~ /[ad]/);
980
981                         $param->{'sort'} = $name . '_' . $direction;
982                         $html .= ' style="border: 1px solid #808080;"';
983                 
984                         # add unicode arrow for direction
985                         $arrow .= '&nbsp;';
986                         $arrow .= $direction eq 'a'  ?  '&#9650;'
987                                 : $direction eq 'd'  ?  '&#9660;'
988                                 :                       ''
989                                 ;
990
991                 } else {
992                         $param->{'sort'} = $name . '_a';
993                 }
994
995                 $html .= '><a href="' . page_uri($param) . '">' . $display . '</a>' . $arrow . '</td>';
996                 $param->{'sort'} = $old_sort;
997
998                 return $html;
999         }
1000
1001         $retHTML .=
1002                 sort_header($param, 'Share', 'share', 'center') .
1003                 sort_header($param, 'Type and Name', 'path', 'center') .
1004                 sort_header($param, '#', 'num', 'center') .
1005                 sort_header($param, 'Size', 'size', 'center') .
1006                 sort_header($param, 'Date', 'date', 'center');
1007
1008         $retHTML .= qq{
1009                 <td align="center">Media</td>
1010                 </tr>
1011         };
1012
1013         my $file;
1014
1015         sub hilite_html($$) {
1016                 my ($html, $search) = @_;
1017                 $html =~ s#($search)#<b>$1</b>#gis;
1018                 return $html;
1019         }
1020
1021         sub restore_link($$$$$$) {
1022                 my $type = shift;
1023                 my $action = 'RestoreFile';
1024                 $action = 'browse' if (lc($type) eq 'dir');
1025                 return sprintf(qq{<a href="?action=%s&host=%s&num=%d&share=%s&dir=%s">%s</a>}, $action, @_);
1026         }
1027
1028         my $sth_archived;
1029         my %archived_cache;
1030
1031         sub check_archived($$$) {
1032                 my ($host, $share, $num) = @_;
1033
1034                 if (my $html = $archived_cache{"$host $share $num"}) {
1035                         return $html;
1036                 }
1037
1038                 $sth_archived ||= $dbh->prepare(qq{
1039                         select
1040                                 dvd_nr, note,
1041                                 count(archive_burned.copy) as copies
1042                         from archive
1043                         inner join archive_burned on archive_burned.archive_id = archive.id
1044                         inner join archive_backup on archive.id = archive_backup.archive_id
1045                         inner join backups on backups.id = archive_backup.backup_id
1046                         inner join hosts on hosts.id = backups.hostid
1047                         inner join shares on shares.id = backups.shareid
1048                         where hosts.name = ? and shares.name = ? and backups.num = ?
1049                         group by dvd_nr, note
1050                 });
1051
1052                 my @mediums;
1053
1054                 $sth_archived->execute($host, $share, $num);
1055                 while (my $row = $sth_archived->fetchrow_hashref()) {
1056                         push @mediums, '<abbr title="' .
1057                                 $row->{'note'} .
1058                                 ' [' . $row->{'copies'} . ']' .
1059                                 '">' .$row->{'dvd_nr'} .
1060                                 '</abbr>';
1061                 }
1062
1063                 my $html = join(", ",@mediums);
1064                 $archived_cache{"$host $share $num"} = $html;
1065                 return $html;
1066         }
1067
1068         my $i = $offset * $on_page;
1069
1070         foreach $file (@{ $files }) {
1071                 $i++;
1072
1073                 my $typeStr  = BackupPC::Attrib::fileType2Text(undef, $file->{'type'});
1074                 $retHTML .= qq{<tr class="fviewborder">};
1075
1076                 $retHTML .= qq{<td class="fviewborder">$i</td>};
1077
1078                 $retHTML .=
1079                         qq{<td class="fviewborder" align="right">} . $file->{'hname'} . ':' . $file->{'sname'} . qq{</td>} .
1080                         qq{<td class="fviewborder"><img src="$Conf{CgiImageDirURL}/icon-$typeStr.gif" alt="$typeStr" align="middle">&nbsp;} . hilite_html( $file->{'filepath'}, $hilite ) . qq{</td>} .
1081                         qq{<td class="fviewborder" align="center">} . restore_link( $typeStr, ${EscURI( $file->{'hname'} )}, $file->{'backupnum'}, ${EscURI( $file->{'sname'})}, ${EscURI( $file->{'filepath'} )}, $file->{'backupnum'} ) . qq{</td>} .
1082                         qq{<td class="fviewborder" align="right">} . $file->{'size'} . qq{</td>} .
1083                         qq{<td class="fviewborder">} . epoch_to_iso( $file->{'date'} ) . qq{</td>} .
1084                         qq{<td class="fviewborder">} . check_archived( $file->{'hname'}, $file->{'sname'}, $file->{'backupnum'} ) . qq{</td>};
1085
1086                 $retHTML .= "</tr>";
1087         }
1088         $retHTML .= "</table>";
1089
1090         # all variables which has to be transfered
1091         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/) {
1092                 $retHTML .= qq{<INPUT TYPE="hidden" NAME="$n" VALUE="$In{$n}">\n};
1093         }
1094
1095         my $del = '';
1096         my $max_page = int( $results / $on_page );
1097         my $page = 0;
1098
1099         sub page_uri($) {
1100                 my $param = shift || die "no param?";
1101
1102                 my $uri = $MyURL;
1103                 my $del = '?';
1104                 foreach my $k (keys %{ $param }) {
1105                         if ($param->{$k}) {
1106                                 $uri .= $del . $k . '=' . ${EscURI( $param->{$k} )};
1107                                 $del = '&';
1108                         }
1109                 }
1110                 return $uri;
1111         }
1112
1113         sub page_link($$$) {
1114                 my ($param,$page,$display) = @_;
1115
1116                 $param->{'offset'} = $page if (defined($page));
1117
1118                 my $html = '<a href = "' . page_uri($param) . '">' . $display . '</a>';
1119         }
1120
1121         $retHTML .= '<div style="text-align: center;">';
1122
1123         if ($offset > 0) {
1124                 $retHTML .= page_link($param, $offset - 1, '&lt;&lt;') . ' ';
1125         }
1126
1127         while ($page <= $max_page) {
1128                 if ($page == $offset) {
1129                         $retHTML .= $del . '<b>' . ($page + 1) . '</b>';
1130                 } else {
1131                         $retHTML .= $del . page_link($param, $page, $page + 1);
1132                 }
1133
1134                 if ($page < $offset - $pager_pages && $page != 0) {
1135                         $retHTML .= " ... ";
1136                         $page = $offset - $pager_pages;
1137                         $del = '';
1138                 } elsif ($page > $offset + $pager_pages && $page != $max_page) {
1139                         $retHTML .= " ... ";
1140                         $page = $max_page;
1141                         $del = '';
1142                 } else {
1143                         $del = ' | ';
1144                         $page++;
1145                 }
1146         }
1147
1148         if ($offset < $max_page) {
1149                 $retHTML .= ' ' . page_link($param, $offset + 1, '&gt;&gt;');
1150         }
1151
1152         $retHTML .= "</div>";
1153
1154         return $retHTML;
1155 }
1156
1157 1;