1#**************************************************************
2#
3#  Licensed to the Apache Software Foundation (ASF) under one
4#  or more contributor license agreements.  See the NOTICE file
5#  distributed with this work for additional information
6#  regarding copyright ownership.  The ASF licenses this file
7#  to you under the Apache License, Version 2.0 (the
8#  "License"); you may not use this file except in compliance
9#  with the License.  You may obtain a copy of the License at
10#
11#    http://www.apache.org/licenses/LICENSE-2.0
12#
13#  Unless required by applicable law or agreed to in writing,
14#  software distributed under the License is distributed on an
15#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16#  KIND, either express or implied.  See the License for the
17#  specific language governing permissions and limitations
18#  under the License.
19#
20#**************************************************************
21
22package installer::patch::MsiTable;
23
24=head1 NAME
25
26    package installer::patch::MsiTable - Class that represents one table of an Msi file.
27
28=cut
29
30use installer::patch::MsiRow;
31
32use strict;
33
34=head new ($class, $filename, $table_name)
35
36    Create a new MsiTable object from the output of a previous
37    msidb.exe run.  The table is named $table_name, its data is read
38    from $filename.
39
40=cut
41sub new ($$$)
42{
43    my ($class, $filename, $table_name) = @_;
44
45    my $self = {
46        'name' => $table_name,
47        'filename' => $filename,
48        'columns' => undef,
49        'column_specs' => undef,
50        'codepage' => undef,
51        'is_valid' => 1,
52        'is_modified' => 0
53    };
54    bless($self, $class);
55
56    if (defined $filename &&  -f $filename)
57    {
58        $self->ReadFile($filename);
59    }
60    return $self;
61}
62
63
64
65
66sub SetColumnData ($@)
67{
68    my ($self, @data) = @_;
69
70    if (((scalar @data) % 2) != 0)
71    {
72        installer::logger::PrintError("column data has to have an even number of elements: (<column-name> <data-spec>)+)\n");
73        $self->{'is_valid'} = 0;
74        return;
75    }
76
77    $self->{'columns'} = [];
78    $self->{'column_specs'} = [];
79    while (scalar @data > 0)
80    {
81        my $name = shift @data;
82        my $spec = shift @data;
83        push @{$self->{'columns'}}, $name;
84        push @{$self->{'column_specs'}}, $spec;
85    }
86}
87
88
89
90
91sub SetIndexColumns ($@)
92{
93    my ($self, @index_columns) = @_;
94
95    $self->{'index_columns'} = [@index_columns];
96}
97
98
99
100
101sub SetCodepage ($$)
102{
103    my ($self, $codepage) = @_;
104
105    $self->{'codepage'} = $codepage;
106}
107
108
109
110
111sub IsValid ($)
112{
113    my ($self) = @_;
114    return $self->{'is_valid'};
115}
116
117
118
119
120sub Trim ($)
121{
122    my $line = shift;
123
124    $line =~ s/(^\s+|\s+$)//g;
125
126    return $line;
127}
128
129
130
131=head2 ReadFile($self, $filename)
132
133    Read the content of the table from the specified .idt file.
134    For each row a MsiRow object is appended to $self->{'rows'}.
135
136=cut
137sub ReadFile ($$)
138{
139    my ($self, $filename) = @_;
140
141    if ( ! (-f $filename && -r $filename))
142    {
143        printf STDERR ("can not open idt file %s for reading\n", $filename);
144        $self->{'is_valid'} = 0;
145        return;
146    }
147
148    open my $in, "<", $filename;
149
150    my $columns = Trim(<$in>);
151    $self->{'columns'} = [split(/\t/, $columns)];
152
153    my $column_specs = Trim(<$in>);
154    $self->{'column_specs'} = [split(/\t/, $column_specs)];
155
156    # Table name, index columns.
157    my $line = Trim(<$in>);
158    my @items = split(/\t/, $line);
159    my $item_count = scalar @items;
160    if ($item_count>=1 && $items[0] eq $self->{'name'})
161    {
162        # No codepage.
163    }
164    elsif ($item_count>=2 && $items[1] eq $self->{'name'})
165    {
166        $self->{'codepage'} = shift @items;
167    }
168    else
169    {
170        printf STDERR ("reading wrong table data for table '%s' (got %s)\n", $self->{'name'}, $items[0]);
171        $self->{'is_valid'} = 0;
172        return;
173    }
174    shift @items;
175    $self->{'index_columns'} = [@items];
176    $self->{'index_column_index'} = $self->GetColumnIndex($items[0]);
177
178    my $rows = [];
179    while (<$in>)
180    {
181        # Remove all trailing returns and newlines.  Keep trailing spaces and tabs.
182        s/[\r\n]+$//g;
183
184        my @items = split(/\t/, $_);
185        push @$rows, new installer::patch::MsiRow($self, @items);
186    }
187    $self->{'rows'} = $rows;
188
189    return $self;
190}
191
192
193
194
195=head WriteFile($self, $filename)
196
197    Write a text file containing the current table content.
198
199=cut
200sub WriteFile ($$)
201{
202    my ($self, $filename) = @_;
203
204    open my $out, ">".$self->{'filename'};
205
206    print $out join("\t", @{$self->{'columns'}})."\r\n";
207    print $out join("\t", @{$self->{'column_specs'}})."\r\n";
208    if (defined $self->{'codepage'})
209    {
210        print $out $self->{'codepage'} . "\t";
211    }
212    print $out $self->{'name'} . "\t";
213    print $out join("\t",@{$self->{'index_columns'}})."\r\n";
214
215    foreach my $row (@{$self->{'rows'}})
216    {
217        print $out $row->Format("\t")."\r\n";
218    }
219
220    close $out;
221}
222
223
224
225
226sub UpdateTimestamp ($)
227{
228    my $self = shift;
229
230    utime(undef,undef, $self->{'filename'});
231}
232
233
234
235
236sub GetName ($)
237{
238    my $self = shift;
239
240    return $self->{'name'};
241}
242
243
244
245
246=head2 GetColumnCount($self)
247
248    Return the number of columns in the table.
249
250=cut
251sub GetColumnCount ($)
252{
253    my ($self) = @_;
254
255    return scalar @{$self->{'columns'}};
256}
257
258
259
260
261=head2 GetRowCount($self)
262
263    Return the number of rows in the table.
264
265=cut
266sub GetRowCount ($)
267{
268    my ($self) = @_;
269
270    return scalar @{$self->{'rows'}};
271}
272
273
274
275
276=head2 GetColumnIndx($self, $column_name)
277
278    Return the 0 based index of the column named $column_name.  Use
279    this to speed up (slightly) access to column values when accessing
280    many or all rows of a table.
281
282=cut
283sub GetColumnIndex ($$)
284{
285    my ($self, $column_name) = @_;
286
287    my $index = 0;
288    foreach my $name (@{$self->{'columns'}})
289    {
290        if ($name eq $column_name)
291        {
292            return $index;
293        }
294        ++$index;
295    }
296
297    printf STDERR ("did not find column %s in %s\n", $column_name, join(" and ", @{$self->{'columns'}}));
298    return -1;
299}
300
301
302
303=head2 GetRowIndex($self, $index_column_index, $index_column_value)
304
305    Return the index, starting at 0, of the (first) row that has value $index_column_value
306    in column with index $index_column_index.
307
308    Return -1 if now such row is found.
309
310=cut
311sub GetRowIndex ($$$)
312{
313    my ($self, $index_column_index, $index_column_value) = @_;
314
315    my $rows = $self->{'rows'};
316    for (my ($row_index,$row_count)=(0,scalar @$rows); $row_index<$row_count; ++$row_index)
317    {
318        my $row = $rows->[$row_index];
319        if ($row->GetValue($index_column_index) eq $index_column_value)
320        {
321            return $row_index;
322        }
323    }
324
325    return -1;
326}
327
328
329
330
331=head2 GetValue($self, $selector_column, $selector_column_value, $value_column)
332
333    Find the row in which the $selector_column has value
334    $selector_column_value and return its value in the $value_column.
335
336=cut
337
338sub GetValue ($$$$)
339{
340    my ($self, $selector_column, $selector_column_value, $value_column) = @_;
341
342    my $row = $self->GetRow($selector_column, $selector_column_value);
343    if (defined $row)
344    {
345        return $row->GetValue($value_column);
346    }
347    else
348    {
349        return undef;
350    }
351}
352
353
354
355
356=head2 GetRow($self, $column, $value)
357
358    Return the (first) row which has $value in $column.
359
360=cut
361sub GetRow ($$$)
362{
363    my ($self, $column, $value) = @_;
364
365    my $column_index = $self->GetColumnIndex($column);
366    if ($column_index<0)
367    {
368        printf STDERR "ERROR: unknown column $column in table $self->{'name'}\n";
369        return undef;
370    }
371
372    foreach my $row (@{$self->{'rows'}})
373    {
374        if ($row->GetValue($column_index) eq $value)
375        {
376            return $row;
377        }
378    }
379
380    printf STDERR ("ERROR: did not find row for %s->%s in %s\n",
381        $column,
382        $value,
383        table $self->{'name'});
384
385    return undef;
386}
387
388
389
390
391=head2 GetAllRows ($self)
392
393    Return the reference to an array that contains all rows of the table.
394
395=cut
396
397sub GetAllRows ($)
398{
399    my $self = shift;
400
401    return $self->{'rows'};
402}
403
404
405
406
407=head2 SetRow($self, {$key, $value}*)
408
409    Replace an existing row.  If no matching row is found then add the row.
410
411    The row is defined by a set of key/value pairs.  Their order is defined by the keys (column names)
412    and their indices as defined in $self->{'columns'}.
413
414    Rows are compared by their values of the index column.  By default this is the first element of
415    $self->{'index_columns'} but is overruled by the last key that starts with a '*'.
416
417=cut
418sub SetRow ($@)
419{
420    my $self = shift;
421    my @data = @_;
422
423    my @items = ();
424    my $index_column = $self->{'index_columns'}->[0];
425
426    # Key/Value has to have an even number of entries.
427    MsiTools::Die("invalid arguments given to MsiTable::SetRow()\n") if (scalar @data%2) != 0;
428
429    # Find column indices for column names.
430    while (scalar @data > 0)
431    {
432        my $column_name = shift @data;
433        if ($column_name =~ /^\*(.*)$/)
434        {
435            # Column name starts with a '*'.  Use it as index column.
436            $column_name = $1;
437            $index_column = $1;
438        }
439        my $value = shift @data;
440        my $column_index = $self->GetColumnIndex($column_name);
441        $items[$column_index] = $value;
442    }
443
444    my $index_column_index = $self->GetColumnIndex($index_column);
445    my $row_index = $self->GetRowIndex($index_column_index, $items[$index_column_index]);
446
447    if ($row_index < 0)
448    {
449        # Row does not yet exist.  Add it.
450        push @{$self->{'rows'}}, installer::patch::MsiRow->new($self, @items);
451    }
452    else
453    {
454        # Row does already exist.  Replace it.
455        $self->{'rows'}->[$row_index] = installer::patch::MsiRow->new($self, @items);
456    }
457
458    $self->MarkAsModified();
459}
460
461
462
463
464sub MarkAsModified ($)
465{
466    my $self = shift;
467
468    $self->{'is_modified'} = 1;
469}
470
471
472
473
474sub MarkAsUnmodified ($)
475{
476    my $self = shift;
477
478    $self->{'is_modified'} = 0;
479}
480
481
482
483
484sub IsModified ($)
485{
486    my $self = shift;
487
488    return $self->{'is_modified'};
489}
490
491
4921;
493