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

Legend:
Removed from v.58  
changed lines
  Added in v.230

  ViewVC Help
Powered by ViewVC 1.1.26