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