/[BackupPC]/trunk/lib/BackupPC/SearchLib.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Diff of /trunk/lib/BackupPC/SearchLib.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

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

Legend:
Removed from v.24  
changed lines
  Added in v.254

  ViewVC Help
Powered by ViewVC 1.1.26