r8601@llin: dpavlin | 2005-10-15 21:00:32 +0200
[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_index_path = $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
124 sub getFiles($) {
125         my ($param) = @_;
126
127         my $offset = $param->{'offset'} || 0;
128         $offset *= $on_page;
129
130         my $dbh = get_dbh();
131
132         my $sql_cols = qq{
133                 files.id                        AS fid,
134                 hosts.name                      AS hname,
135                 shares.name                     AS sname,
136                 files.backupnum                 AS backupnum,
137                 files.path                      AS filepath,
138                 files.date                      AS date,
139                 files.type                      AS type,
140                 files.size                      AS size
141         };
142
143         my $sql_from = qq{
144                 FROM files 
145                         INNER JOIN shares       ON files.shareID=shares.ID
146                         INNER JOIN hosts        ON hosts.ID = shares.hostID
147                         INNER JOIN backups      ON backups.num = files.backupnum and backups.hostID = hosts.ID AND backups.shareID = files.shareID
148         };
149
150         my $sql_where;
151         my $where = getWhere($param);
152         $sql_where = " WHERE ". $where if ($where);
153
154         my $sql_order = qq{
155                 ORDER BY files.date
156                 LIMIT $on_page
157                 OFFSET ?
158         };
159
160         my $sql_count = qq{ select count(files.id) $sql_from $sql_where };
161         my $sql_results = qq{ select $sql_cols $sql_from $sql_where $sql_order };
162
163         my $sth = $dbh->prepare($sql_count);
164         $sth->execute();
165         my ($results) = $sth->fetchrow_array();
166
167         $sth = $dbh->prepare($sql_results);
168         $sth->execute( $offset );
169
170         if ($sth->rows != $results) {
171                 my $bug = "$0 BUG: [[ $sql_count ]] = $results while [[ $sql_results ]] = " . $sth->rows;
172                 $bug =~ s/\s+/ /gs;
173                 print STDERR "$bug\n";
174         }
175
176         my @ret;
177       
178         while (my $row = $sth->fetchrow_hashref()) {
179                 push @ret, $row;
180         }
181      
182         $sth->finish();
183         return ($results, \@ret);
184 }
185
186 sub getHyperEstraier_url($) {
187         my ($use_hest) = @_;
188
189         return unless $use_hest;
190
191         use HyperEstraier;
192         my ($index_path, $index_node_url);
193
194         if ($use_hest =~ m#^http://#) {
195                 $index_node_url = $use_hest;
196         } else {
197                 $index_path = $TopDir . '/' . $index_path;
198                 $index_path =~ s#//#/#g;
199         }
200         return ($index_path, $index_node_url);
201 }
202
203 sub getFilesHyperEstraier($) {
204         my ($param) = @_;
205
206         my $offset = $param->{'offset'} || 0;
207         $offset *= $on_page;
208
209         die "no index_path?" unless ($hest_index_path);
210
211         use HyperEstraier;
212
213         my ($index_path, $index_node_url) = getHyperEstraier_url($hest_index_path);
214
215         # open the database
216         my $db;
217         if ($index_path) {
218                 $db = HyperEstraier::Database->new();
219                 $db->open($index_path, $HyperEstraier::ESTDBREADER);
220         } elsif ($index_node_url) {
221                 $db ||= HyperEstraier::Node->new($index_node_url);
222                 $db->set_auth('admin', 'admin');
223         } else {
224                 die "BUG: unimplemented";
225         }
226
227         # create a search condition object
228         my $cond = HyperEstraier::Condition->new();
229
230         my $q = $param->{'search_filename'};
231         my $shareid = $param->{'search_share'};
232
233         if (length($q) > 0) {
234                 # exact match
235                 $cond->add_attr("filepath ISTRINC $q");
236
237                 $q =~ s/(.)/$1 /g;
238                 # set the search phrase to the search condition object
239                 $cond->set_phrase($q);
240         }
241
242         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
243
244         $cond->add_attr("backup_date NUMGE $backup_from") if ($backup_from);
245         $cond->add_attr("backup_date NUMLE $backup_to") if ($backup_to);
246
247         $cond->add_attr("date NUMGE $files_from") if ($files_from);
248         $cond->add_attr("date NUMLE $files_to") if ($files_to);
249
250         $cond->add_attr("shareid NUMEQ $shareid") if ($shareid);
251
252 #       $cond->set_max( $offset + $on_page );
253         $cond->set_options( $HyperEstraier::Condition::SURE );
254         $cond->set_order( 'date NUMA' );
255
256         # get the result of search
257         my @res;
258         my ($result, $hits);
259
260         if ($index_path) {
261                 $result = $db->search($cond, 0);
262                 $hits = $result->size;
263         } elsif ($index_node_url) {
264                 $result = $db->search($cond, 0);
265                 $hits = $result->doc_num;
266         } else {
267                 die "BUG: unimplemented";
268         }
269
270         # for each document in result
271         for my $i ($offset .. ($offset + $on_page - 1)) {
272                 last if ($i >= $hits);
273
274                 my $doc;
275                 if ($index_path) {
276                         my $id = $result->get($i);
277                         $doc = $db->get_doc($id, 0);
278                 } elsif ($index_node_url) {
279                         $doc = $result->get_doc($i);
280                 } else {
281                         die "BUG: unimplemented";
282                 }
283
284                 my $row;
285                 foreach my $c (qw/fid hname sname backupnum fiilename filepath date type size/) {
286                         $row->{$c} = $doc->attr($c);
287                 }
288                 push @res, $row;
289         }
290
291         return ($hits, \@res);
292 }
293
294 sub getGzipName($$$)
295 {
296         my ($host, $share, $backupnum) = @_;
297         my $ret = $Conf{GzipSchema};
298         
299         $share =~ s/\//_/g;
300         $ret =~ s/\\h/$host/ge;
301         $ret =~ s/\\s/$share/ge;
302         $ret =~ s/\\n/$backupnum/ge;
303
304         $ret =~ s/__+/_/g;
305
306         return $ret;
307         
308 }
309
310 sub get_tgz_size_by_name($) {
311         my $name = shift;
312
313         my $tgz = $Conf{InstallDir}.'/'.$Conf{GzipTempDir}.'/'.$name;
314
315         my $size = -1;
316
317         if (-f $tgz) {
318                 $size = (stat($tgz))[7];
319         } elsif (-d $tgz) {
320                 opendir(my $dir, $tgz) || die "can't opendir $tgz: $!";
321                 my @parts = grep { !/^\./ && -f "$tgz/$_" } readdir($dir);
322                 $size = 0;
323                 foreach my $part (@parts) {
324                         $size += (stat("$tgz/$part"))[7] || die "can't stat $tgz/$part: $!";
325                 }
326                 closedir $dir;
327         }
328
329         return $size;
330 }
331
332 sub getGzipSize($$)
333 {
334         my ($hostID, $backupNum) = @_;
335         my $sql;
336         my $dbh = get_dbh();
337         
338         $sql = q{ 
339                                 SELECT hosts.name  as host,
340                                            shares.name as share,
341                                            backups.num as backupnum
342                                 FROM hosts, backups, shares
343                                 WHERE shares.id=backups.shareid AND
344                                           hosts.id =backups.hostid AND
345                                           hosts.id=? AND
346                                           backups.num=?
347                         };
348         my $sth = $dbh->prepare($sql);
349         $sth->execute($hostID, $backupNum);
350
351         my $row = $sth->fetchrow_hashref();
352
353         return get_tgz_size_by_name(
354                 getGzipName($row->{'host'}, $row->{share}, $row->{'backupnum'})
355         );
356 }
357
358 sub getBackupsNotBurned() {
359
360         my $dbh = get_dbh();
361
362         my $sql = q{
363                 SELECT 
364                         backups.hostID AS hostID,
365                         hosts.name AS host,
366                         shares.name AS share,
367                         backups.num AS backupnum,
368                         backups.type AS type,
369                         backups.date AS date,
370                         backups.size AS size,
371                         backups.id AS id,
372                         backups.inc_size AS inc_size,
373                         backups.parts AS parts
374                 FROM backups 
375                 INNER JOIN shares       ON backups.shareID=shares.ID
376                 INNER JOIN hosts        ON backups.hostID = hosts.ID
377                 LEFT OUTER JOIN archive_backup ON archive_backup.backup_id = backups.id 
378                 WHERE backups.inc_size > 0 AND backups.inc_deleted is false AND archive_backup.backup_id IS NULL
379                 GROUP BY
380                         backups.hostID,
381                         hosts.name,
382                         shares.name,
383                         backups.num,
384                         backups.shareid,
385                         backups.id,
386                         backups.type,
387                         backups.date,
388                         backups.size,
389                         backups.inc_size,
390                         backups.parts
391                 ORDER BY backups.date
392         };
393         my $sth = $dbh->prepare( $sql );
394         my @ret;
395         $sth->execute();
396
397         while ( my $row = $sth->fetchrow_hashref() ) {
398                 $row->{'age'} = sprintf("%0.1f", ( (time() - $row->{'date'}) / 86400 ) );
399                 $row->{'size'} = sprintf("%0.2f", $row->{'size'} / 1024 / 1024);
400
401                 # do some cluster calculation (approximate) and convert to kB
402                 $row->{'inc_size'} = int(($row->{'inc_size'} + 1023 ) / ( 2 * 1024 ) * 2);
403                 push @ret, $row;
404         }
405       
406         return @ret;      
407 }
408
409 sub displayBackupsGrid() {
410
411         my $retHTML .= q{
412                 <form id="forma" method="POST" action="}.$MyURL.q{?action=burn">
413         };
414
415         $retHTML .= <<'EOF3';
416 <style type="text/css">
417 <!--
418 DIV#fixedBox {
419         position: absolute;
420         top: 50em;
421         left: -24%;
422         padding: 0.5em;
423         width: 20%;
424         background-color: #E0F0E0;
425         border: 1px solid #00C000;
426 }
427
428 DIV#fixedBox, DIV#fixedBox INPUT, DIV#fixedBox TEXTAREA {
429         font-size: 10pt;
430 }
431
432 FORM>DIV#fixedBox {
433         position: fixed !important;
434         left: 0.5em !important;
435         top: auto !important;
436         bottom: 1em !important;
437         width: 15% !important;
438 }
439
440 DIV#fixedBox INPUT[type=text], DIV#fixedBox TEXTAREA {
441         border: 1px solid #00C000;
442 }
443
444 DIV#fixedBox #note {
445         display: block;
446         width: 100%;
447 }
448
449 DIV#fixedBox #submitBurner {
450         display: block;
451         width: 100%;
452         margin-top: 0.5em;
453         cursor: pointer;
454 }
455
456 * HTML {
457         overflow-y: hidden;
458 }
459
460 * HTML BODY {
461         overflow-y: auto;
462         height: 100%;
463         font-size: 100%;
464 }
465
466 * HTML DIV#fixedBox {
467         position: absolute;
468 }
469
470 #mContainer, #gradient, #mask, #progressIndicator {
471         display: block;
472         width: 100%;
473         font-size: 10pt;
474         font-weight: bold;
475         text-align: center;
476         vertical-align: middle;
477         padding: 1px;
478 }
479
480 #gradient, #mask, #progressIndicator {
481         left: 0;
482         border-width: 1px;
483         border-style: solid;
484         border-color: #000000;
485         color: #404040;
486         margin: 0.4em;
487         position: absolute;
488         margin-left: -1px;
489         margin-top: -1px;
490         margin-bottom: -1px;
491         overflow: hidden;
492 }
493
494 #mContainer {
495         display: block;
496         position: relative;
497         padding: 0px;
498         margin-top: 0.4em;
499         margin-bottom: 0.5em;
500 }
501
502 #gradient {
503         z-index: 1;
504         background-color: #FFFF00;
505 }
506
507 #mask {
508         z-index: 2;
509         background-color: #FFFFFF;
510 }
511
512 #progressIndicator {
513         z-index: 3;
514         background-color: transparent;
515 }
516
517 #parts {
518         padding: 0.4em;
519         display: none;
520         width: 100%;
521         font-size: 80%;
522         color: #ff0000;
523         text-align: center;
524 }
525 -->
526 </style>
527 <script type="text/javascript">
528 <!--
529
530 var debug_div = null;
531 EOF3
532
533         # take maximum archive size from configuration
534         $retHTML .= 'var media_size = '. $Conf{MaxArchiveSize} .';';
535
536         $retHTML .= <<'EOF3';
537
538 function debug(msg) {
539         return; // Disable debugging
540
541         if (! debug_div) debug_div = document.getElementById('debug');
542
543         // this will create debug div if it doesn't exist.
544         if (! debug_div) {
545                 debug_div = document.createElement('div');
546                 if (document.body) document.body.appendChild(debug_div);
547                 else debug_div = null;
548         }
549         if (debug_div) {
550                 debug_div.appendChild(document.createTextNode(msg));
551                 debug_div.appendChild(document.createElement("br"));
552         }
553 }
554
555
556 var element_id_cache = Array();
557
558 function element_id(name,element) {
559         if (! element_id_cache[name]) {
560                 element_id_cache[name] = self.document.getElementById(name);
561         }
562         return element_id_cache[name];
563 }
564
565 function checkAll(location) {
566         var f = element_id('forma') || null;
567         if (!f) return false;
568
569         var len = f.elements.length;
570         var check_all = element_id('allFiles');
571         var suma = check_all.checked ? (parseInt(f.elements['totalsize'].value) || 0) : 0;
572
573         for (var i = 0; i < len; i++) {
574                 var e = f.elements[i];
575                 if (e.name != 'all' && e.name.substr(0, 3) == 'fcb') {
576                         if (check_all.checked) {
577                                 if (e.checked) continue;
578                                 var el = element_id("fss" + e.name.substr(3));
579                                 var size = parseInt(el.value) || 0;
580                                 debug('suma: '+suma+' size: '+size);
581                                 if ((suma + size) < media_size) {
582                                         suma += size;
583                                         e.checked = true;
584                                 } else {
585                                         break;
586                                 }
587                         } else {
588                                 e.checked = false;
589                         }
590                 }
591         }
592         update_sum(suma);
593 }
594
595 function update_sum(suma, suma_disp) {
596         if (! suma_disp) suma_disp = suma;
597         element_id('forma').elements['totalsize'].value = suma_disp;
598         pbar_set(suma, media_size);
599         debug('total size: ' + suma);
600 }
601
602 function sumiraj(e) {
603         var suma = parseInt(element_id('forma').elements['totalsize'].value) || 0;
604         var len = element_id('forma').elements.length;
605         if (e) {
606                 var size = parseInt( element_id("fss" + e.name.substr(3)).value);
607                 if (e.checked) {
608                         suma += size;
609                 } else {
610                         suma -= size;
611                 }
612
613                 var parts = parseInt( element_id("prt" + e.name.substr(3)).value);
614                 if (suma > media_size && suma == size && parts > 1) {
615                         element_id("parts").innerHTML = "This will take "+parts+" mediums!";
616                         element_id("parts").style.display = 'block';
617                         update_sum(media_size, suma);
618                         suma = media_size;
619                         return suma;
620                 } else {
621                         element_id("parts").style.display = 'none';
622                 }
623
624                 if (suma < 0) suma = 0;
625         } else {
626                 suma = 0;
627                 for (var i = 0; i < len; i++) {
628                         var e = element_id('forma').elements[i];
629                         if (e.name != 'all' && e.checked && e.name.substr(0,3) == 'fcb') {
630                                 var el = element_id("fss" + e.name.substr(3));
631                                 if (el && el.value) suma += parseInt(el.value) || 0;
632                         }
633                 }
634         }
635         update_sum(suma);
636         return suma;
637 }
638
639 /* progress bar */
640
641 var _pbar_width = null;
642 var _pbar_warn = 10;    // change color in last 10%
643
644 function pbar_reset() {
645         element_id("mask").style.left = "0px";
646         _pbar_width = element_id("mContainer").offsetWidth - 2;
647         element_id("mask").style.width = _pbar_width + "px";
648         element_id("mask").style.display = "block";
649         element_id("progressIndicator").style.zIndex  = 10;
650         element_id("progressIndicator").innerHTML = "0";
651 }
652
653 function dec2hex(d) {
654         var hch = '0123456789ABCDEF';
655         var a = d % 16;
656         var q = (d - a) / 16;
657         return hch.charAt(q) + hch.charAt(a);
658 }
659
660 function pbar_set(amount, max) {
661         debug('pbar_set('+amount+', '+max+')');
662
663         if (_pbar_width == null) {
664                 var _mc = element_id("mContainer");
665                 if (_pbar_width == null) _pbar_width = parseInt(_mc.offsetWidth ? (_mc.offsetWidth - 2) : 0) || null;
666                 if (_pbar_width == null) _pbar_width = parseInt(_mc.clientWidth ? (_mc.clientWidth + 2) : 0) || null;
667                 if (_pbar_width == null) _pbar_width = 0;
668         }
669
670         var pcnt = Math.floor(amount * 100 / max);
671         var p90 = 100 - _pbar_warn;
672         var pcol = pcnt - p90;
673         if (Math.round(pcnt) <= 100) {
674                 if (pcol < 0) pcol = 0;
675                 var e = element_id("submitBurner");
676                 debug('enable_button');
677                 e.disabled = false;
678                 var a = e.getAttributeNode('disabled') || null;
679                 if (a) e.removeAttributeNode(a);
680         } else {
681                 debug('disable button');
682                 pcol = _pbar_warn;
683                 var e = element_id("submitBurner");
684                 if (!e.disabled) e.disabled = true;
685         }
686         var col_g = Math.floor((_pbar_warn - pcol) * 255 / _pbar_warn);
687         var col = '#FF' + dec2hex(col_g) + '00';
688
689         //debug('pcol: '+pcol+' g:'+col_g+' _pbar_warn:'+ _pbar_warn + ' color: '+col);
690         element_id("gradient").style.backgroundColor = col;
691
692         element_id("progressIndicator").innerHTML = pcnt + '%';
693         //element_id("progressIndicator").innerHTML = amount;
694
695         element_id("mask").style.clip = 'rect(' + Array(
696                 '0px',
697                 element_id("mask").offsetWidth + 'px',
698                 element_id("mask").offsetHeight + 'px',
699                 Math.round(_pbar_width * amount / max) + 'px'
700         ).join(' ') + ')';
701 }
702
703 if (!self.body) self.body = new Object();
704 self.onload = self.document.onload = self.body.onload = function() {
705         //pbar_reset();
706         sumiraj();
707 };
708
709 // -->
710 </script>
711 <div id="fixedBox">
712
713 Size: <input type="text" name="totalsize" size="7" readonly="readonly" style="text-align:right;" value="0" /> kB
714
715 <div id="mContainer">
716         <div id="gradient">&nbsp;</div>
717         <div id="mask">&nbsp;</div>
718         <div id="progressIndicator">0%</div>
719 </div>
720 <br/>
721
722 <div id="parts">&nbsp;</div>
723
724 Note:
725 <textarea name="note" cols="10" rows="5" id="note"></textarea>
726
727 <input type="submit" id="submitBurner" value="Burn selected" name="submitBurner" />
728
729 </div>
730 <!--
731 <div id="debug" style="float: right; width: 10em; border: 1px #ff0000 solid; background-color: #ffe0e0; -moz-opacity: 0.7;">
732 no debug output yet
733 </div>
734 -->
735 EOF3
736         $retHTML .= q{
737                         <input type="hidden" value="burn" name="action">
738                         <input type="hidden" value="results" name="search_results">
739                         <table style="fview" border="0" cellspacing="0" cellpadding="2">
740                         <tr class="tableheader">
741                         <td class="tableheader">
742                                 <input type="checkbox" name="allFiles" id="allFiles" onClick="checkAll('allFiles');">
743                         </td>
744                         <td align="center">Share</td>
745                         <td align="center">Backup no</td>
746                         <td align="center">Type</td>
747                         <td align="center">date</td>
748                         <td align="center">age/days</td>
749                         <td align="center">size/MB</td>
750                         <td align="center">gzip size/kB</td>
751                         </tr>
752
753         };
754
755         my @color = (' bgcolor="#e0e0e0"', '');
756
757         my $i = 0;
758         my $host = '';
759
760         foreach my $backup ( getBackupsNotBurned() ) {
761
762                 if ($host ne $backup->{'host'}) {
763                         $i++;
764                         $host = $backup->{'host'};
765                 }
766                 my $ftype = "";
767
768                 my $checkbox_key = $backup->{'hostid'}. '_' .$backup->{'backupnum'} . '_' . $backup->{'id'};
769
770                 $retHTML .=
771                         '<tr' . $color[$i %2 ] . '>
772                         <td class="fview">';
773
774                 if (($backup->{'inc_size'} || 0) > 0) {
775                         $retHTML .= '
776                         <input type="checkbox" name="fcb' . $checkbox_key . '" value="' . $checkbox_key . '" onClick="sumiraj(this);">';
777                 }
778
779                 $retHTML .=
780                         '</td>' .
781                         '<td align="right">' . $backup->{'host'} . ':' . $backup->{'share'} . '</td>' .
782                         '<td align="center">' . $backup->{'backupnum'} . '</td>' .
783                         '<td align="center">' . $backup->{'type'} . '</td>' .
784                         '<td align="center">' . epoch_to_iso( $backup->{'date'} ) . '</td>' .
785                         '<td align="center">' . $backup->{'age'} . '</td>' .
786                         '<td align="right">' . $backup->{'size'} . '</td>' .
787                         '<td align="right">' . $backup->{'inc_size'} .
788                         '<input type="hidden" id="fss'.$checkbox_key .'" value="'. $backup->{'inc_size'} .'"></td>' .
789                         '<input type="hidden" id="prt'.$checkbox_key .'" value="'. $backup->{'parts'} .'"></td>' .
790
791                         "</tr>\n";
792         }
793
794         $retHTML .= "</table>";
795         $retHTML .= "</form>";
796       
797         return $retHTML;
798 }      
799
800 sub displayGrid($) {
801         my ($param) = @_;
802
803         my $offset = $param->{'offset'};
804         my $hilite = $param->{'search_filename'};
805
806         my $retHTML = "";
807  
808         my $start_t = time();
809
810         my ($results, $files);
811         if ($param->{'use_hest'} && length($hilite) > 0) {
812                 ($results, $files) = getFilesHyperEstraier($param);
813         } else {
814                 ($results, $files) = getFiles($param);
815         }
816
817         my $dur_t = time() - $start_t;
818         my $dur = sprintf("%0.4fs", $dur_t);
819
820         my ($from, $to) = (($offset * $on_page) + 1, ($offset * $on_page) + $on_page);
821
822         if ($results <= 0) {
823                 $retHTML .= qq{
824                         <p style="color: red;">No results found...</p>
825                 };
826                 return $retHTML;
827         } else {
828                 # DEBUG
829                 #use Data::Dumper;
830                 #$retHTML .= '<pre>' . Dumper($files) . '</pre>';
831         }
832
833
834         $retHTML .= qq{
835         <div>
836         Found <b>$results files</b> showing <b>$from - $to</b> (took $dur)
837         </div>
838         <table style="fview" width="100%" border="0" cellpadding="2" cellspacing="0">
839                 <tr class="fviewheader"> 
840                 <td></td>
841                 <td align="center">Share</td>
842                 <td align="center">Type and Name</td>
843                 <td align="center">#</td>
844                 <td align="center">Size</td>
845                 <td align="center">Date</td>
846                 <td align="center">Media</td>
847                 </tr>
848         };
849
850         my $file;
851
852         sub hilite_html($$) {
853                 my ($html, $search) = @_;
854                 $html =~ s#($search)#<b>$1</b>#gis;
855                 return $html;
856         }
857
858         sub restore_link($$$$$$) {
859                 my $type = shift;
860                 my $action = 'RestoreFile';
861                 $action = 'browse' if (lc($type) eq 'dir');
862                 return sprintf(qq{<a href="?action=%s&host=%s&num=%d&share=%s&dir=%s">%s</a>}, $action, @_);
863         }
864
865         my $sth_archived;
866         my %archived_cache;
867
868         sub check_archived($$$) {
869                 my ($host, $share, $num) = @_;
870
871                 if (my $html = $archived_cache{"$host $share $num"}) {
872                         return $html;
873                 }
874
875                 $sth_archived ||= $dbh->prepare(qq{
876                         select
877                                 dvd_nr, note,
878                                 count(archive_burned.copy) as copies
879                         from archive
880                         inner join archive_burned on archive_burned.archive_id = archive.id
881                         inner join archive_backup on archive.id = archive_backup.archive_id
882                         inner join backups on backups.id = archive_backup.backup_id
883                         inner join hosts on hosts.id = backups.hostid
884                         inner join shares on shares.id = backups.shareid
885                         where hosts.name = ? and shares.name = ? and backups.num = ?
886                         group by dvd_nr, note
887                 });
888
889                 my @mediums;
890
891                 $sth_archived->execute($host, $share, $num);
892                 while (my $row = $sth_archived->fetchrow_hashref()) {
893                         push @mediums, '<abbr title="' .
894                                 $row->{'note'} .
895                                 ' [' . $row->{'copies'} . ']' .
896                                 '">' .$row->{'dvd_nr'} .
897                                 '</abbr>';
898                 }
899
900                 my $html = join(", ",@mediums);
901                 $archived_cache{"$host $share $num"} = $html;
902                 return $html;
903         }
904
905         my $i = $offset * $on_page;
906
907         foreach $file (@{ $files }) {
908                 $i++;
909
910                 my $typeStr  = BackupPC::Attrib::fileType2Text(undef, $file->{'type'});
911                 $retHTML .= qq{<tr class="fviewborder">};
912
913                 $retHTML .= qq{<td class="fviewborder">$i</td>};
914
915                 $retHTML .=
916                         qq{<td class="fviewborder" align="right">} . $file->{'hname'} . ':' . $file->{'sname'} . qq{</td>} .
917                         qq{<td class="fviewborder"><img src="$Conf{CgiImageDirURL}/icon-$typeStr.gif" alt="$typeStr" align="middle">&nbsp;} . hilite_html( $file->{'filepath'}, $hilite ) . qq{</td>} .
918                         qq{<td class="fviewborder" align="center">} . restore_link( $typeStr, ${EscURI( $file->{'hname'} )}, $file->{'backupnum'}, ${EscURI( $file->{'sname'})}, ${EscURI( $file->{'filepath'} )}, $file->{'backupnum'} ) . qq{</td>} .
919                         qq{<td class="fviewborder" align="right">} . $file->{'size'} . qq{</td>} .
920                         qq{<td class="fviewborder">} . epoch_to_iso( $file->{'date'} ) . qq{</td>} .
921                         qq{<td class="fviewborder">} . check_archived( $file->{'hname'}, $file->{'sname'}, $file->{'backupnum'} ) . qq{</td>};
922
923                 $retHTML .= "</tr>";
924         }
925         $retHTML .= "</table>";
926
927         # all variables which has to be transfered
928         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/) {
929                 $retHTML .= qq{<INPUT TYPE="hidden" NAME="$n" VALUE="$In{$n}">\n};
930         }
931
932         my $del = '';
933         my $max_page = int( $results / $on_page );
934         my $page = 0;
935
936         sub page_link($$$) {
937                 my ($param,$page,$display) = @_;
938
939                 $param->{'offset'} = $page;
940
941                 my $html = '<a href = "' . $MyURL;
942                 my $del = '?';
943                 foreach my $k (keys %{ $param }) {
944                         if ($param->{$k}) {
945                                 $html .= $del . $k . '=' . ${EscURI( $param->{$k} )};
946                                 $del = '&';
947                         }
948                 }
949                 $html .= '">' . $display . '</a>';
950         }
951
952         $retHTML .= '<div style="text-align: center;">';
953
954         if ($offset > 0) {
955                 $retHTML .= page_link($param, $offset - 1, '&lt;&lt;') . ' ';
956         }
957
958         while ($page <= $max_page) {
959                 if ($page == $offset) {
960                         $retHTML .= $del . '<b>' . ($page + 1) . '</b>';
961                 } else {
962                         $retHTML .= $del . page_link($param, $page, $page + 1);
963                 }
964
965                 if ($page < $offset - $pager_pages && $page != 0) {
966                         $retHTML .= " ... ";
967                         $page = $offset - $pager_pages;
968                         $del = '';
969                 } elsif ($page > $offset + $pager_pages && $page != $max_page) {
970                         $retHTML .= " ... ";
971                         $page = $max_page;
972                         $del = '';
973                 } else {
974                         $del = ' | ';
975                         $page++;
976                 }
977         }
978
979         if ($offset < $max_page) {
980                 $retHTML .= ' ' . page_link($param, $offset + 1, '&gt;&gt;');
981         }
982
983         $retHTML .= "</div>";
984
985         return $retHTML;
986 }
987
988 1;