| #************************************************************** |
| # |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| # |
| #************************************************************** |
| |
| package installer::patch::MsiTable; |
| |
| =head1 NAME |
| |
| package installer::patch::MsiTable - Class that represents one table of an Msi file. |
| |
| =cut |
| |
| use installer::patch::MsiRow; |
| |
| use strict; |
| |
| =head new ($class, $filename, $table_name) |
| |
| Create a new MsiTable object from the output of a previous |
| msidb.exe run. The table is named $table_name, its data is read |
| from $filename. |
| |
| =cut |
| sub new ($$$) |
| { |
| my ($class, $filename, $table_name) = @_; |
| |
| my $self = { |
| 'name' => $table_name, |
| 'filename' => $filename, |
| 'columns' => undef, |
| 'column_specs' => undef, |
| 'codepage' => undef, |
| 'is_valid' => 1, |
| 'is_modified' => 0 |
| }; |
| bless($self, $class); |
| |
| if (defined $filename && -f $filename) |
| { |
| $self->ReadFile($filename); |
| } |
| return $self; |
| } |
| |
| |
| |
| |
| sub SetColumnData ($@) |
| { |
| my ($self, @data) = @_; |
| |
| if (((scalar @data) % 2) != 0) |
| { |
| installer::logger::PrintError("column data has to have an even number of elements: (<column-name> <data-spec>)+)\n"); |
| $self->{'is_valid'} = 0; |
| return; |
| } |
| |
| $self->{'columns'} = []; |
| $self->{'column_specs'} = []; |
| while (scalar @data > 0) |
| { |
| my $name = shift @data; |
| my $spec = shift @data; |
| push @{$self->{'columns'}}, $name; |
| push @{$self->{'column_specs'}}, $spec; |
| } |
| } |
| |
| |
| |
| |
| sub SetIndexColumns ($@) |
| { |
| my ($self, @index_columns) = @_; |
| |
| $self->{'index_columns'} = [@index_columns]; |
| } |
| |
| |
| |
| |
| sub SetCodepage ($$) |
| { |
| my ($self, $codepage) = @_; |
| |
| $self->{'codepage'} = $codepage; |
| } |
| |
| |
| |
| |
| sub IsValid ($) |
| { |
| my ($self) = @_; |
| return $self->{'is_valid'}; |
| } |
| |
| |
| |
| |
| sub Trim ($) |
| { |
| my $line = shift; |
| |
| $line =~ s/(^\s+|\s+$)//g; |
| |
| return $line; |
| } |
| |
| |
| |
| =head2 ReadFile($self, $filename) |
| |
| Read the content of the table from the specified .idt file. |
| For each row a MsiRow object is appended to $self->{'rows'}. |
| |
| =cut |
| sub ReadFile ($$) |
| { |
| my ($self, $filename) = @_; |
| |
| if ( ! (-f $filename && -r $filename)) |
| { |
| printf STDERR ("can not open idt file %s for reading\n", $filename); |
| $self->{'is_valid'} = 0; |
| return; |
| } |
| |
| open my $in, "<", $filename; |
| |
| my $columns = Trim(<$in>); |
| $self->{'columns'} = [split(/\t/, $columns)]; |
| |
| my $column_specs = Trim(<$in>); |
| $self->{'column_specs'} = [split(/\t/, $column_specs)]; |
| |
| # Table name, index columns. |
| my $line = Trim(<$in>); |
| my @items = split(/\t/, $line); |
| my $item_count = scalar @items; |
| if ($item_count>=1 && $items[0] eq $self->{'name'}) |
| { |
| # No codepage. |
| } |
| elsif ($item_count>=2 && $items[1] eq $self->{'name'}) |
| { |
| $self->{'codepage'} = shift @items; |
| } |
| else |
| { |
| printf STDERR ("reading wrong table data for table '%s' (got %s)\n", $self->{'name'}, $items[0]); |
| $self->{'is_valid'} = 0; |
| return; |
| } |
| shift @items; |
| $self->{'index_columns'} = [@items]; |
| $self->{'index_column_index'} = $self->GetColumnIndex($items[0]); |
| |
| my $rows = []; |
| while (<$in>) |
| { |
| # Remove all trailing returns and newlines. Keep trailing spaces and tabs. |
| s/[\r\n]+$//g; |
| |
| my @items = split(/\t/, $_); |
| push @$rows, new installer::patch::MsiRow($self, @items); |
| } |
| $self->{'rows'} = $rows; |
| |
| return $self; |
| } |
| |
| |
| |
| |
| =head WriteFile($self, $filename) |
| |
| Write a text file containing the current table content. |
| |
| =cut |
| sub WriteFile ($$) |
| { |
| my ($self, $filename) = @_; |
| |
| open my $out, ">".$self->{'filename'}; |
| |
| print $out join("\t", @{$self->{'columns'}})."\r\n"; |
| print $out join("\t", @{$self->{'column_specs'}})."\r\n"; |
| if (defined $self->{'codepage'}) |
| { |
| print $out $self->{'codepage'} . "\t"; |
| } |
| print $out $self->{'name'} . "\t"; |
| print $out join("\t",@{$self->{'index_columns'}})."\r\n"; |
| |
| foreach my $row (@{$self->{'rows'}}) |
| { |
| print $out $row->Format("\t")."\r\n"; |
| } |
| |
| close $out; |
| } |
| |
| |
| |
| |
| sub UpdateTimestamp ($) |
| { |
| my $self = shift; |
| |
| utime(undef,undef, $self->{'filename'}); |
| } |
| |
| |
| |
| |
| sub GetName ($) |
| { |
| my $self = shift; |
| |
| return $self->{'name'}; |
| } |
| |
| |
| |
| |
| =head2 GetColumnCount($self) |
| |
| Return the number of columns in the table. |
| |
| =cut |
| sub GetColumnCount ($) |
| { |
| my ($self) = @_; |
| |
| return scalar @{$self->{'columns'}}; |
| } |
| |
| |
| |
| |
| =head2 GetRowCount($self) |
| |
| Return the number of rows in the table. |
| |
| =cut |
| sub GetRowCount ($) |
| { |
| my ($self) = @_; |
| |
| return scalar @{$self->{'rows'}}; |
| } |
| |
| |
| |
| |
| =head2 GetColumnIndx($self, $column_name) |
| |
| Return the 0 based index of the column named $column_name. Use |
| this to speed up (slightly) access to column values when accessing |
| many or all rows of a table. |
| |
| =cut |
| sub GetColumnIndex ($$) |
| { |
| my ($self, $column_name) = @_; |
| |
| my $index = 0; |
| foreach my $name (@{$self->{'columns'}}) |
| { |
| if ($name eq $column_name) |
| { |
| return $index; |
| } |
| ++$index; |
| } |
| |
| printf STDERR ("did not find column %s in %s\n", $column_name, join(" and ", @{$self->{'columns'}})); |
| return -1; |
| } |
| |
| |
| |
| =head2 GetRowIndex($self, $index_column_index, $index_column_value) |
| |
| Return the index, starting at 0, of the (first) row that has value $index_column_value |
| in column with index $index_column_index. |
| |
| Return -1 if now such row is found. |
| |
| =cut |
| sub GetRowIndex ($$$) |
| { |
| my ($self, $index_column_index, $index_column_value) = @_; |
| |
| my $rows = $self->{'rows'}; |
| for (my ($row_index,$row_count)=(0,scalar @$rows); $row_index<$row_count; ++$row_index) |
| { |
| my $row = $rows->[$row_index]; |
| if ($row->GetValue($index_column_index) eq $index_column_value) |
| { |
| return $row_index; |
| } |
| } |
| |
| return -1; |
| } |
| |
| |
| |
| |
| =head2 GetValue($self, $selector_column, $selector_column_value, $value_column) |
| |
| Find the row in which the $selector_column has value |
| $selector_column_value and return its value in the $value_column. |
| |
| =cut |
| |
| sub GetValue ($$$$) |
| { |
| my ($self, $selector_column, $selector_column_value, $value_column) = @_; |
| |
| my $row = $self->GetRow($selector_column, $selector_column_value); |
| if (defined $row) |
| { |
| return $row->GetValue($value_column); |
| } |
| else |
| { |
| return undef; |
| } |
| } |
| |
| |
| |
| |
| =head2 GetRow($self, $column, $value) |
| |
| Return the (first) row which has $value in $column. |
| |
| =cut |
| sub GetRow ($$$) |
| { |
| my ($self, $column, $value) = @_; |
| |
| my $column_index = $self->GetColumnIndex($column); |
| if ($column_index<0) |
| { |
| printf STDERR "ERROR: unknown column $column in table $self->{'name'}\n"; |
| return undef; |
| } |
| |
| foreach my $row (@{$self->{'rows'}}) |
| { |
| if ($row->GetValue($column_index) eq $value) |
| { |
| return $row; |
| } |
| } |
| |
| printf STDERR ("ERROR: did not find row for %s->%s in %s\n", |
| $column, |
| $value, |
| table $self->{'name'}); |
| |
| return undef; |
| } |
| |
| |
| |
| |
| =head2 GetAllRows ($self) |
| |
| Return the reference to an array that contains all rows of the table. |
| |
| =cut |
| |
| sub GetAllRows ($) |
| { |
| my $self = shift; |
| |
| return $self->{'rows'}; |
| } |
| |
| |
| |
| |
| =head2 SetRow($self, {$key, $value}*) |
| |
| Replace an existing row. If no matching row is found then add the row. |
| |
| The row is defined by a set of key/value pairs. Their order is defined by the keys (column names) |
| and their indices as defined in $self->{'columns'}. |
| |
| Rows are compared by their values of the index column. By default this is the first element of |
| $self->{'index_columns'} but is overruled by the last key that starts with a '*'. |
| |
| =cut |
| sub SetRow ($@) |
| { |
| my $self = shift; |
| my @data = @_; |
| |
| my @items = (); |
| my $index_column = $self->{'index_columns'}->[0]; |
| |
| # Key/Value has to have an even number of entries. |
| MsiTools::Die("invalid arguments given to MsiTable::SetRow()\n") if (scalar @data%2) != 0; |
| |
| # Find column indices for column names. |
| while (scalar @data > 0) |
| { |
| my $column_name = shift @data; |
| if ($column_name =~ /^\*(.*)$/) |
| { |
| # Column name starts with a '*'. Use it as index column. |
| $column_name = $1; |
| $index_column = $1; |
| } |
| my $value = shift @data; |
| my $column_index = $self->GetColumnIndex($column_name); |
| $items[$column_index] = $value; |
| } |
| |
| my $index_column_index = $self->GetColumnIndex($index_column); |
| my $row_index = $self->GetRowIndex($index_column_index, $items[$index_column_index]); |
| |
| if ($row_index < 0) |
| { |
| # Row does not yet exist. Add it. |
| push @{$self->{'rows'}}, installer::patch::MsiRow->new($self, @items); |
| } |
| else |
| { |
| # Row does already exist. Replace it. |
| $self->{'rows'}->[$row_index] = installer::patch::MsiRow->new($self, @items); |
| } |
| |
| $self->MarkAsModified(); |
| } |
| |
| |
| |
| |
| sub MarkAsModified ($) |
| { |
| my $self = shift; |
| |
| $self->{'is_modified'} = 1; |
| } |
| |
| |
| |
| |
| sub MarkAsUnmodified ($) |
| { |
| my $self = shift; |
| |
| $self->{'is_modified'} = 0; |
| } |
| |
| |
| |
| |
| sub IsModified ($) |
| { |
| my $self = shift; |
| |
| return $self->{'is_modified'}; |
| } |
| |
| |
| 1; |