blob: aaa19f37710a33b4c6e27d46d39dc714b40fcaa5 [file] [log] [blame]
#**************************************************************
#
# 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;