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