27842a00d1d77f908745ca6d991e53568ebae22c
[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
12 my $on_page = 100;
13 my $pager_pages = 10;
14
15 my $dsn = $Conf{SearchDSN};
16 my $db_user = $Conf{SearchUser} || '';
17
18 my $hest_index_path = $Conf{HyperEstraierIndex};
19
20 my $dbh;
21
22 sub get_dbh {
23         $dbh ||= DBI->connect($dsn, $db_user, "", { RaiseError => 1, AutoCommit => 1 } );
24         return $dbh;
25 }
26
27 sub getUnits() {
28         my @ret;
29
30         my $dbh = get_dbh();
31         my $sth = $dbh->prepare(qq{
32                 SELECT
33                         shares.id       as id,
34                         hosts.name || ':' || shares.name as share
35                 FROM shares
36                 JOIN hosts on hostid = hosts.id
37                 ORDER BY share
38         } );
39         $sth->execute();
40         push @ret, { 'id' => '', 'share' => '-'};       # dummy any
41
42         while ( my $row = $sth->fetchrow_hashref() ) {
43                 push @ret, $row;
44         }
45         return @ret;
46 }
47
48 sub epoch_to_iso {
49         my $t = shift || return;
50         my $iso = BackupPC::Lib::timeStamp(undef, $t);
51         $iso =~ s/\s/ /g;
52         return $iso;
53 }
54
55 sub dates_from_form($) {
56         my $param = shift || return;
57
58         sub mk_epoch_date($$) {
59                 my ($name,$suffix) = @_;
60
61                 my $yyyy = $param->{ $name . '_year_' . $suffix} || return undef;
62                 my $mm .= $param->{ $name . '_month_' . $suffix} ||
63                         ( $suffix eq 'from' ? 1 : 12);
64                 my $dd .= $param->{ $name . '_day_' . $suffix} ||
65                         ( $suffix eq 'from' ? 1 : 31);
66
67                 $yyyy =~ s/\D//g;
68                 $mm =~ s/\D//g;
69                 $dd =~ s/\D//g;
70
71                 my $dt = new DateTime(
72                         year => $yyyy,
73                         month => $mm,
74                         day => $dd
75                 );
76                 print STDERR "mk_epoch_date($name,$suffix) [$yyyy-$mm-$dd] = " . $dt->ymd . " " . $dt->hms . "\n";
77                 return $dt->epoch || 'NULL';
78         }
79
80         my @ret = (
81                 mk_epoch_date('search_backup', 'from'),
82                 mk_epoch_date('search_backup', 'to'),
83                 mk_epoch_date('search', 'from'),
84                 mk_epoch_date('search', 'to'),
85         );
86
87         return @ret;
88
89 }
90
91
92 sub getWhere($) {
93         my $param = shift || return;
94
95         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
96
97         my @conditions;
98         push @conditions, qq{ backups.date >= $backup_from } if ($backup_from);
99         push @conditions, qq{ backups.date <= $backup_to } if ($backup_to);
100         push @conditions, qq{ files.date >= $files_from } if ($files_from);
101         push @conditions, qq{ files.date <= $files_to } if ($files_to);
102
103         print STDERR "backup: $backup_from - $backup_to files: $files_from - $files_to cond:" . join(" | ",@conditions);
104
105         push( @conditions, ' files.shareid = ' . $param->{'search_share'} ) if ($param->{'search_share'});
106         push (@conditions, " upper(files.path) LIKE upper('%".$param->{'search_filename'}."%')") if ($param->{'search_filename'});
107
108         return join(" and ", @conditions);
109 }
110
111
112 sub getFiles($) {
113         my ($param) = @_;
114
115         my $offset = $param->{'offset'} || 0;
116         $offset *= $on_page;
117
118         my $dbh = get_dbh();
119
120         my $sql_cols = qq{
121                 files.id                        AS fid,
122                 hosts.name                      AS hname,
123                 shares.name                     AS sname,
124                 files.backupnum                 AS backupnum,
125                 files.path                      AS filepath,
126                 files.date                      AS date,
127                 files.type                      AS type,
128                 files.size                      AS size
129         };
130
131         my $sql_from = qq{
132                 FROM files 
133                         INNER JOIN shares       ON files.shareID=shares.ID
134                         INNER JOIN hosts        ON hosts.ID = shares.hostID
135                         INNER JOIN backups      ON backups.num = files.backupnum and backups.hostID = hosts.ID AND backups.shareID = files.shareID
136         };
137
138         my $sql_where;
139         my $where = getWhere($param);
140         $sql_where = " WHERE ". $where if ($where);
141
142         my $sql_order = qq{
143                 ORDER BY files.date
144                 LIMIT $on_page
145                 OFFSET ?
146         };
147
148         my $sql_count = qq{ select count(files.id) $sql_from $sql_where };
149         my $sql_results = qq{ select $sql_cols $sql_from $sql_where $sql_order };
150
151         my $sth = $dbh->prepare($sql_count);
152         $sth->execute();
153         my ($results) = $sth->fetchrow_array();
154
155         $sth = $dbh->prepare($sql_results);
156         $sth->execute( $offset );
157
158         if ($sth->rows != $results) {
159                 my $bug = "$0 BUG: [[ $sql_count ]] = $results while [[ $sql_results ]] = " . $sth->rows;
160                 $bug =~ s/\s+/ /gs;
161                 print STDERR "$bug\n";
162         }
163
164         my @ret;
165       
166         while (my $row = $sth->fetchrow_hashref()) {
167                 push @ret, $row;
168         }
169      
170         $sth->finish();
171         return ($results, \@ret);
172 }
173
174 sub getHyperEstraier_url($) {
175         my ($use_hest) = @_;
176
177         return unless $use_hest;
178
179         use HyperEstraier;
180         my ($index_path, $index_node_url);
181
182         if ($use_hest =~ m#^http://#) {
183                 $index_node_url = $use_hest;
184         } else {
185                 $index_path = $TopDir . '/' . $index_path;
186                 $index_path =~ s#//#/#g;
187         }
188         return ($index_path, $index_node_url);
189 }
190
191 sub getFilesHyperEstraier($) {
192         my ($param) = @_;
193
194         my $offset = $param->{'offset'} || 0;
195         $offset *= $on_page;
196
197         die "no index_path?" unless ($hest_index_path);
198
199         use HyperEstraier;
200
201         my ($index_path, $index_node_url) = getHyperEstraier_url($hest_index_path);
202
203         # open the database
204         my $db;
205         if ($index_path) {
206                 $db = HyperEstraier::Database->new();
207                 $db->open($index_path, $HyperEstraier::ESTDBREADER);
208         } elsif ($index_node_url) {
209                 $db ||= HyperEstraier::Node->new($index_node_url);
210                 $db->set_auth('admin', 'admin');
211         } else {
212                 die "BUG: unimplemented";
213         }
214
215         # create a search condition object
216         my $cond = HyperEstraier::Condition->new();
217
218         my $q = $param->{'search_filename'};
219         my $shareid = $param->{'search_share'};
220
221         if (length($q) > 0) {
222                 # exact match
223                 $cond->add_attr("filepath ISTRINC $q");
224
225                 $q =~ s/(.)/$1 /g;
226                 # set the search phrase to the search condition object
227                 $cond->set_phrase($q);
228         }
229
230         my ($backup_from, $backup_to, $files_from, $files_to) = dates_from_form($param);
231
232         $cond->add_attr("backup_date NUMGE $backup_from") if ($backup_from);
233         $cond->add_attr("backup_date NUMLE $backup_to") if ($backup_to);
234
235         $cond->add_attr("date NUMGE $files_from") if ($files_from);
236         $cond->add_attr("date NUMLE $files_to") if ($files_to);
237
238         $cond->add_attr("shareid NUMEQ $shareid") if ($shareid);
239
240 #       $cond->set_max( $offset + $on_page );
241         $cond->set_options( $HyperEstraier::Condition::SURE );
242         $cond->set_order( 'date NUMA' );
243
244         # get the result of search
245         my @res;
246         my ($result, $hits);
247
248         if ($index_path) {
249                 $result = $db->search($cond, 0);
250                 $hits = $result->size;
251         } elsif ($index_node_url) {
252                 $result = $db->search($cond, 0);
253                 $hits = $result->doc_num;
254         } else {
255                 die "BUG: unimplemented";
256         }
257
258         # for each document in result
259         for my $i ($offset .. ($offset + $on_page - 1)) {
260                 last if ($i >= $hits);
261
262                 my $doc;
263                 if ($index_path) {
264                         my $id = $result->get($i);
265                         $doc = $db->get_doc($id, 0);
266                 } elsif ($index_node_url) {
267                         $doc = $result->get_doc($i);
268                 } else {
269                         die "BUG: unimplemented";
270                 }
271
272                 my $row;
273                 foreach my $c (qw/fid hname sname backupnum fiilename filepath date type size/) {
274                         $row->{$c} = $doc->attr($c);
275                 }
276                 push @res, $row;
277         }
278
279         return ($hits, \@res);
280 }
281
282 sub getGzipName($$$)
283 {
284         my ($host, $share, $backupnum) = @_;
285         my $ret = $Conf{GzipSchema};
286         
287         $share =~ s/\//_/g;
288         $ret =~ s/\\h/$host/ge;
289         $ret =~ s/\\s/$share/ge;
290         $ret =~ s/\\n/$backupnum/ge;
291         
292         return $ret;
293         
294 }
295
296 sub getBackupsNotBurned() {
297
298         my $dbh = get_dbh();
299
300         my $sql = q{
301                 SELECT 
302                         backups.hostID AS hostID,
303                         hosts.name AS host,
304                         shares.name AS share,
305                         backups.id AS backupnum,
306                         backups.type AS type,
307                         backups.date AS date,
308                         backups.size AS size
309                 FROM backups 
310                 INNER JOIN shares       ON backups.shareID=shares.ID
311                 INNER JOIN hosts        ON backups.hostID = hosts.ID
312                 LEFT OUTER JOIN archive_backup ON archive_backup.backup_id = backups.id 
313                 WHERE backups.size > 0 AND archive_backup.backup_id IS NULL
314                 GROUP BY
315                         backups.hostID,
316                         hosts.name,
317                         shares.name,
318                         backups.num,
319                         backups.shareid,
320                         backups.id,
321                         backups.type,
322                         backups.date,
323                         backups.size
324                 ORDER BY backups.date
325         };
326         my $sth = $dbh->prepare( $sql );
327         my @ret;
328         $sth->execute();
329
330         while ( my $row = $sth->fetchrow_hashref() ) {
331                 $row->{'age'} = sprintf("%0.1f", ( (time() - $row->{'date'}) / 86400 ) );
332                 $row->{'size'} = sprintf("%0.2f", $row->{'size'} / 1024 / 1024);
333                 my (undef,undef,undef,undef,undef,undef,undef,$fs_size,undef,undef,undef,undef,undef) = 
334                         stat( $Conf{InstallDir}.'/'.$Conf{GzipTempDir}.'/'.
335                                 getGzipName($row->{'host'}, $row->{share}, $row->{'backupnum'}));
336                 $row->{'fs_size'} = $fs_size;
337                 push @ret, $row;
338         }
339       
340         return @ret;      
341 }
342
343 sub displayBackupsGrid() {
344
345         my $retHTML .= q{
346                 <form id="forma" method="POST" action="}.$MyURL.q{?action=burn">
347         };
348
349         $retHTML .= <<'EOF3';
350 <style type="text/css">
351 <!--
352 DIV#fixedBox {
353         position: absolute;
354         top: 50em;
355         left: -24%;
356         padding: 0.5em;
357         width: 20%;
358         background-color: #E0F0E0;
359         border: 1px solid #00C000;
360 }
361
362 DIV#fixedBox, DIV#fixedBox INPUT, DIV#fixedBox TEXTAREA {
363         font-size: 10pt;
364 }
365
366 FORM>DIV#fixedBox {
367         position: fixed !important;
368         left: 0.5em !important;
369         top: auto !important;
370         bottom: 1em !important;
371         width: 15% !important;
372 }
373
374 DIV#fixedBox INPUT[type=text], DIV#fixedBox TEXTAREA {
375         border: 1px solid #00C000;
376 }
377
378 DIV#fixedBox #note {
379         display: block;
380         width: 100%;
381 }
382
383 DIV#fixedBox #submitBurner {
384         display: block;
385         width: 100%;
386         margin-top: 0.5em;
387         cursor: pointer;
388 }
389
390 * HTML {
391         overflow-y: hidden;
392 }
393
394 * HTML BODY {
395         overflow-y: auto;
396         height: 100%;
397         font-size: 100%;
398 }
399
400 * HTML DIV#fixedBox {
401         position: absolute;
402 }
403
404 #mContainer, #gradient, #mask, #progressIndicator {
405         display: block;
406         width: 100%;
407         font-size: 10pt;
408         font-weight: bold;
409         text-align: center;
410         vertical-align: middle;
411         padding: 1px;
412 }
413
414 #gradient, #mask, #progressIndicator {
415         left: 0;
416         border-width: 1px;
417         border-style: solid;
418         border-color: #000000;
419         color: #404040;
420         margin: 0.4em;
421         position: absolute;
422         margin-left: -1px;
423         margin-top: -1px;
424         margin-bottom: -1px;
425         overflow: hidden;
426 }
427
428 #mContainer {
429         display: block;
430         position: relative;
431         padding: 0px;
432         margin-top: 0.4em;
433         margin-bottom: 0.5em;
434 }
435
436 #gradient {
437         z-index: 1;
438         background-color: #FFFF00;
439 }
440
441 #mask {
442         z-index: 2;
443         background-color: #FFFFFF;
444 }
445
446 #progressIndicator {
447         z-index: 3;
448         background-color: transparent;
449 }
450 -->
451 </style>
452 <script type="text/javascript">
453 <!--
454
455 var debug_div = null;
456 var media_size = 4400 * 1024;
457
458 function debug(msg) {
459 //      return; // Disable debugging
460
461         if (! debug_div) debug_div = document.getElementById('debug');
462
463         // this will create debug div if it doesn't exist.
464         if (! debug_div) {
465                 debug_div = document.createElement('div');
466                 if (document.body) document.body.appendChild(debug_div);
467                 else debug_div = null;
468         }
469         if (debug_div) {
470                 debug_div.appendChild(document.createTextNode(msg));
471                 debug_div.appendChild(document.createElement("br"));
472         }
473 }
474
475
476 var element_id_cache = Array();
477
478 function element_id(name,element) {
479         if (! element_id_cache[name]) {
480                 element_id_cache[name] = self.document.getElementById(name);
481         }
482         return element_id_cache[name];
483 }
484
485 function checkAll(location) {
486         var len = element_id('forma').elements.length;
487         var check_all = element_id('allFiles');
488         var suma = 0;
489
490         for (var i = 0; i < len; i++) {
491
492                 var e = element_id('forma').elements[i];
493                 if (e.name != 'all' && e.name.substr(0,3) == 'fcb') {
494                         if (check_all.checked) {
495                                 var el = element_id("fss" + e.name.substr(3));
496                                 var size = parseInt(el.value) || 0;
497                                 debug('suma: '+suma+' size: '+size);
498                                 if ((suma + size) < media_size) {
499                                         suma += size;
500                                         e.checked = true;
501                                 } else {
502                                         break;
503                                 }
504                         } else {
505                                 e.checked = false;
506                         }
507                 }
508         }
509         update_sum(suma);
510 }
511
512 function update_sum(suma) {
513         element_id('forma').totalsize.value = suma;
514         pbar_set(suma, media_size);
515         debug('total size: '+suma);
516 }
517
518 function sumiraj(e) {
519         var suma = parseInt(element_id('forma').totalsize.value) || 0;
520         var len = element_id('forma').elements.length;
521         if (e) {
522                 var size = parseInt( element_id("fss" + e.name.substr(3)).value);
523                 if (e.checked) {
524                         suma += size;
525                 } else {
526                         suma -= size;
527                 }
528         } else {
529                 suma = 0;
530                 for (var i = 0; i < len; i++) {
531                         var e = element_id('forma').elements[i];
532                         if (e.name != 'all' && e.checked && e.name.substr(0,3) == 'fcb') {
533                                 var el = element_id("fss" + e.name.substr(3));
534                                 if (el && el.value) suma += parseInt(el.value) || 0;
535                         }
536                 }
537         }
538         update_sum(suma);
539         return suma;
540 }
541
542 /* progress bar */
543
544 var _pbar_width = null;
545 var _pbar_warn = 10;    // change color in last 10%
546
547 function pbar_reset() {
548         element_id("mask").style.left = "0px";
549         _pbar_width = element_id("mContainer").offsetWidth - 2;
550         element_id("mask").style.width = _pbar_width + "px";
551         element_id("mask").style.display = "block";
552         element_id("progressIndicator").style.zIndex  = 10;
553         element_id("progressIndicator").innerHTML = "0";
554 }
555
556 function dec2hex(d) {
557         var hch = '0123456789ABCDEF';
558         var a = d % 16;
559         var q = (d - a) / 16;
560         return hch.charAt(q) + hch.charAt(a);
561 }
562
563 function pbar_set(amount, max) {
564         debug('pbar_set('+amount+', '+max+')');
565
566         if (_pbar_width == null) {
567                 var _mc = element_id("mContainer");
568                 if (_pbar_width == null) _pbar_width = parseInt(_mc.offsetWidth ? (_mc.offsetWidth - 2) : 0) || null;
569                 if (_pbar_width == null) _pbar_width = parseInt(_mc.clientWidth ? (_mc.clientWidth + 2) : 0) || null;
570                 if (_pbar_width == null) _pbar_width = 0;
571         }
572
573         var pcnt = Math.floor(amount * 100 / max);
574         var p90 = 100 - _pbar_warn;
575         var pcol = pcnt - p90;
576         if (Math.round(pcnt) <= 100) {
577                 if (pcol < 0) pcol = 0;
578                 var e = element_id("submitBurner");
579                 debug('enable_button');
580                 e.disabled = false;
581                 var a = e.getAttributeNode('disabled') || null;
582                 if (a) e.removeAttributeNode(a);
583         } else {
584                 debug('disable button');
585                 pcol = _pbar_warn;
586                 var e = element_id("submitBurner");
587                 if (!e.disabled) e.disabled = true;
588         }
589         var col_g = Math.floor((_pbar_warn - pcol) * 255 / _pbar_warn);
590         var col = '#FF' + dec2hex(col_g) + '00';
591
592         //debug('pcol: '+pcol+' g:'+col_g+' _pbar_warn:'+ _pbar_warn + ' color: '+col);
593         element_id("gradient").style.backgroundColor = col;
594
595         element_id("progressIndicator").innerHTML = pcnt + '%';
596         //element_id("progressIndicator").innerHTML = amount;
597
598         element_id("mask").style.clip = 'rect(' + Array(
599                 '0px',
600                 element_id("mask").offsetWidth + 'px',
601                 element_id("mask").offsetHeight + 'px',
602                 Math.round(_pbar_width * amount / max) + 'px'
603         ).join(' ') + ')';
604 }
605
606 if (!self.body) self.body = new Object();
607 self.onload = self.document.onload = self.body.onload = function() {
608         //pbar_reset();
609         sumiraj();
610 };
611
612 // -->
613 </script>
614 <div id="fixedBox">
615
616 Size: <input type="text" name="totalsize" size="7" readonly="readonly" style="text-align:right;" value="0" /> kB
617
618 <div id="mContainer">
619         <div id="gradient">&nbsp;</div>
620         <div id="mask">&nbsp;</div>
621         <div id="progressIndicator">0%</div>
622 </div>
623 <br/>
624
625 Note:
626 <textarea name="note" cols="10" rows="5" id="note"></textarea>
627
628 <input type="submit" id="submitBurner" value="Burn selected" name="submitBurner" />
629
630 </div>
631 <!--
632 <div id="debug" style="float: right; width: 10em; border: 1px #ff0000 solid; background-color: #ffe0e0; -moz-opacity: 0.7;">
633 no debug output yet
634 </div>
635 -->
636 EOF3
637         $retHTML .= q{
638                         <input type="hidden" value="burn" name="action">
639                         <input type="hidden" value="results" name="search_results">
640                         <table style="fview" border="0" cellspacing="0" cellpadding="2">
641                         <tr class="tableheader">
642                         <td class="tableheader">
643                                 <input type="checkbox" name="allFiles" id="allFiles" onClick="checkAll('allFiles');">
644                         </td>
645                         <td align="center">Share</td>
646                         <td align="center">Backup no</td>
647                         <td align="center">Type</td>
648                         <td align="center">date</td>
649                         <td align="center">age/days</td>
650                         <td align="center">size/MB</td>
651                         <td align="center">gzip size</td>
652                         </tr>
653
654         };
655
656         my @color = (' bgcolor="#e0e0e0"', '');
657
658         my $i = 0;
659         my $host = '';
660
661         foreach my $backup ( getBackupsNotBurned() ) {
662
663                 if ($host ne $backup->{'host'}) {
664                         $i++;
665                         $host = $backup->{'host'};
666                 }
667                 my $ftype = "";
668
669                 $retHTML .=
670                         '<tr' . $color[$i %2 ] . '>
671                         <td class="fview">';
672                 # FIXME
673                 $backup->{'fs_size'} = int($backup->{'size'} * 1024);
674                 if (($backup->{'fs_size'} || 0) > 0) {
675                         $retHTML .= '
676                         <input type="checkbox" name="fcb' .
677                         $backup->{'hostid'}.'_'.$backup->{'backupnum'} . 
678                         '" value="' . $backup->{'hostid'}.'_'.$backup->{'backupnum'} .
679                         '" onClick="sumiraj(this);">';
680                 }
681                 $retHTML .=
682                         '</td>' .
683                         '<td align="right">' . $backup->{'host'} . ':' . $backup->{'share'} . '</td>' .
684                         '<td align="center">' . $backup->{'backupnum'} . '</td>' .
685                         '<td align="center">' . $backup->{'type'} . '</td>' .
686                         '<td align="center">' . epoch_to_iso( $backup->{'date'} ) . '</td>' .
687                         '<td align="center">' . $backup->{'age'} . '</td>' .
688                         '<td align="right">' . $backup->{'size'} . '</td>' .
689                         '<td align="right">' . $backup->{'fs_size'} .
690                         '<input type="hidden" iD="fss'.$backup->{'hostid'}.'_'.$backup->{'backupnum'} . '" value="'. $backup->{'fs_size'} .'"></td>' .
691
692                         "</tr>\n";
693         }
694
695         $retHTML .= "</table>";
696         $retHTML .= "</form>";
697       
698         return $retHTML;
699 }      
700
701 sub displayGrid($) {
702         my ($param) = @_;
703
704         my $offset = $param->{'offset'};
705         my $hilite = $param->{'search_filename'};
706
707         my $retHTML = "";
708  
709         my $start_t = time();
710
711         my ($results, $files);
712         if ($param->{'use_hest'} && length($hilite) > 0) {
713                 ($results, $files) = getFilesHyperEstraier($param);
714         } else {
715                 ($results, $files) = getFiles($param);
716         }
717
718         my $dur_t = time() - $start_t;
719         my $dur = sprintf("%0.4fs", $dur_t);
720
721         my ($from, $to) = (($offset * $on_page) + 1, ($offset * $on_page) + $on_page);
722
723         if ($results <= 0) {
724                 $retHTML .= qq{
725                         <p style="color: red;">No results found...</p>
726                 };
727                 return $retHTML;
728         } else {
729                 # DEBUG
730                 #use Data::Dumper;
731                 #$retHTML .= '<pre>' . Dumper($files) . '</pre>';
732         }
733
734
735         $retHTML .= qq{
736         <div>
737         Found <b>$results files</b> showing <b>$from - $to</b> (took $dur)
738         </div>
739         <table style="fview" width="100%" border="0" cellpadding="2" cellspacing="0">
740                 <tr class="fviewheader"> 
741                 <td></td>
742                 <td align="center">Share</td>
743                 <td align="center">Type and Name</td>
744                 <td align="center">#</td>
745                 <td align="center">Size</td>
746                 <td align="center">Date</td>
747                 <td align="center">Media</td>
748                 </tr>
749         };
750
751         my $file;
752
753         sub hilite_html($$) {
754                 my ($html, $search) = @_;
755                 $html =~ s#($search)#<b>$1</b>#gis;
756                 return $html;
757         }
758
759         sub restore_link($$$$$$) {
760                 my $type = shift;
761                 my $action = 'RestoreFile';
762                 $action = 'browse' if (lc($type) eq 'dir');
763                 return sprintf(qq{<a href="?action=%s&host=%s&num=%d&share=%s&dir=%s">%s</a>}, $action, @_);
764         }
765
766         my $i = $offset * $on_page;
767
768         foreach $file (@{ $files }) {
769                 $i++;
770
771                 my $typeStr  = BackupPC::Attrib::fileType2Text(undef, $file->{'type'});
772                 $retHTML .= qq{<tr class="fviewborder">};
773
774                 $retHTML .= qq{<td class="fviewborder">$i</td>};
775
776                 $retHTML .=
777                         qq{<td class="fviewborder" align="right">} . $file->{'hname'} . ':' . $file->{'sname'} . qq{</td>} .
778                         qq{<td class="fviewborder"><img src="$Conf{CgiImageDirURL}/icon-$typeStr.gif" alt="$typeStr" align="middle">&nbsp;} . hilite_html( $file->{'filepath'}, $hilite ) . qq{</td>} .
779                         qq{<td class="fviewborder" align="center">} . restore_link( $typeStr, ${EscURI( $file->{'hname'} )}, $file->{'backupnum'}, ${EscURI( $file->{'sname'})}, ${EscURI( $file->{'filepath'} )}, $file->{'backupnum'} ) . qq{</td>} .
780                         qq{<td class="fviewborder" align="right">} . $file->{'size'} . qq{</td>} .
781                         qq{<td class="fviewborder">} . epoch_to_iso( $file->{'date'} ) . qq{</td>} .
782                         qq{<td class="fviewborder">} . '?' . qq{</td>};
783
784                 $retHTML .= "</tr>";
785         }
786         $retHTML .= "</table>";
787
788         # all variables which has to be transfered
789         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/) {
790                 $retHTML .= qq{<INPUT TYPE="hidden" NAME="$n" VALUE="$In{$n}">\n};
791         }
792
793         my $del = '';
794         my $max_page = int( $results / $on_page );
795         my $page = 0;
796
797         sub page_link($$$) {
798                 my ($param,$page,$display) = @_;
799
800                 $param->{'offset'} = $page;
801
802                 my $html = '<a href = "' . $MyURL;
803                 my $del = '?';
804                 foreach my $k (keys %{ $param }) {
805                         if ($param->{$k}) {
806                                 $html .= $del . $k . '=' . ${EscURI( $param->{$k} )};
807                                 $del = '&';
808                         }
809                 }
810                 $html .= '">' . $display . '</a>';
811         }
812
813         $retHTML .= '<div style="text-align: center;">';
814
815         if ($offset > 0) {
816                 $retHTML .= page_link($param, $offset - 1, '&lt;&lt;') . ' ';
817         }
818
819         while ($page <= $max_page) {
820                 if ($page == $offset) {
821                         $retHTML .= $del . '<b>' . ($page + 1) . '</b>';
822                 } else {
823                         $retHTML .= $del . page_link($param, $page, $page + 1);
824                 }
825
826                 if ($page < $offset - $pager_pages && $page != 0) {
827                         $retHTML .= " ... ";
828                         $page = $offset - $pager_pages;
829                         $del = '';
830                 } elsif ($page > $offset + $pager_pages && $page != $max_page) {
831                         $retHTML .= " ... ";
832                         $page = $max_page;
833                         $del = '';
834                 } else {
835                         $del = ' | ';
836                         $page++;
837                 }
838         }
839
840         if ($offset < $max_page) {
841                 $retHTML .= ' ' . page_link($param, $offset + 1, '&gt;&gt;');
842         }
843
844         $retHTML .= "</div>";
845
846         return $retHTML;
847 }
848
849 1;