#************************************************************** # # 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::ReleasesList; use XML::Parser; use File::Spec; use strict; =head1 NAME package installer::patch::ReleasesList - Functions for accessing the instsetoo_native/data/releases.xml file =cut my $Instance = undef; =head2 Instance() Return the singleton instance. =cut sub Instance() { if ( ! defined $Instance) { $Instance = new installer::patch::ReleasesList( File::Spec->catfile($ENV{'SRC_ROOT'}, "instsetoo_native", "data", "releases.xml")); } return $Instance; } =head2 new($class, $filename) Internal constructor. Don't call. =cut sub new ($$) { my ($class, $filename) = @_; my $self = { 'releases' => [] }; bless($self, $class); $self->Read($filename); return $self; } =head2 GetFirstChild ($node, $child_name) Internal function that returns the first child. Use only when the first child is the (expected) only child in a list. =cut sub GetFirstChild ($$) { my ($node, $child_name) = @_; if ( ! defined $node) { return undef; } else { my $value = $node->{$child_name}; if (ref($value) eq 'ARRAY') { return $value->[0]; } else { return $value; } } } =head2 GetText ($node) Internal function that returns the trimmed text content of a node. =cut sub GetText ($;$) { my ($node, $default_text) = @_; if ( ! defined $node) { if (defined $default_text) { return $default_text; } else { return ""; } } else { my $text = $node->{'__text__'}; $text =~ s/(^\s+|\s+$)//g; return $text; } } sub GetAttribute ($$) { my ($node, $attribute_name) = @_; my $attributes = $node->{'__attributes__'}; if ( ! defined $attributes) { return undef; } else { return $attributes->{$attribute_name}; } } sub PrintNode($$); sub ReadDomTree ($) { my ($filename) = @_; my $root = {}; my $data = { 'current_node' => $root, 'node_stack' => [] }; my $parser = new XML::Parser( 'Handlers' => { 'Start' => sub {HandleStartTag($data, @_)}, 'End' => sub{HandleEndTag($data, @_)}, 'Char' => sub{HandleText($data, @_)} }); $parser->parsefile($filename); # PrintNode("", $root); return $root; } sub PrintNode($$) { my ($indentation, $node) = @_; if (defined $node->{'__attributes__'}) { while (my ($name,$attribute) = each(%{$node->{'__attributes__'}})) { printf(" %s%s -> %s\n", $indentation, $name, $attribute); } } while (my ($key,$value) = each(%$node)) { if ($key eq '__text__') { printf("%stext '%s'\n", $indentation, $value); } elsif ($key eq '__attributes__') { next; } elsif (ref($value) eq "ARRAY") { foreach my $item (@$value) { printf("%s%s {\n", $indentation, $key); PrintNode($indentation." ", $item); printf("%s}\n", $indentation); } } else { printf("%s%s {\n", $indentation, $key); PrintNode($indentation." ", $value); printf("%s}\n", $indentation); } } } sub HandleStartTag ($$$@) { my ($data, $expat, $element, @attributes) = @_; # Create new node with attributes. my $node = {'__attributes__' => {@attributes}}; # Append it to the list of $element objects. my $current_node = $data->{'current_node'}; $current_node->{$element} = [] unless defined $current_node->{$element}; push @{$current_node->{$element}}, $node; # Make the new node the current node. push @{$data->{'node_stack'}}, $current_node; $data->{'current_node'} = $node; } sub HandleEndTag ($$$) { my ($data, $expat, $element) = @_; # Restore the parent node as current node. $data->{'current_node'} = pop @{$data->{'node_stack'}}; } sub HandleText ($$$) { my ($data, $expat, $text) = @_; if ($text !~ /^\s*$/) { $data->{'current_node'}->{'__text__'} .= $text; } } =head2 Read($self, $filename) Read the releases.xml file as doctree and parse its content. =cut sub Read ($$) { my ($self, $filename) = @_; my $document = ReadDomTree($filename); foreach my $release_node (@{$document->{'releases'}->[0]->{'release'}}) { my $version_node = GetFirstChild($release_node, "version"); my $version_major = GetText(GetFirstChild($version_node, "major")); my $version_minor = GetText(GetFirstChild($version_node, "minor"), "0"); my $version_micro = GetText(GetFirstChild($version_node, "micro"), "0"); my $version = sprintf("%d.%d.%d", $version_major, $version_minor, $version_micro); die "could not read version from releases.xml" if $version eq ""; push @{$self->{'releases'}}, $version; my $download_node = GetFirstChild($release_node, "downloads"); my $package_format = GetText(GetFirstChild($download_node, "package-format")); my $url_template = GetText(GetFirstChild($download_node, "url-template")); my $upgrade_code = GetText(GetFirstChild($download_node, "upgrade-code")); my $build_id = GetText(GetFirstChild($download_node, "build-id")); die "could not read package format from releases.xml" if $package_format eq ""; $self->{$version}->{$package_format}->{'upgrade-code'} = $upgrade_code; $self->{$version}->{$package_format}->{'build-id'} = $build_id; foreach my $item_node (@{$download_node->{'item'}}) { my ($language, $download_data) = ParseDownloadData($item_node, $url_template); if (defined $download_data && defined $language) { $self->{$version}->{$package_format}->{$language} = $download_data; } } } } =head2 ParseDownloadData ($download_node) Parse the data for one set of download data (there is one per release and package format). =cut sub ParseDownloadData ($$) { my ($item_node, $url_template) = @_; my $language = GetText(GetFirstChild($item_node, "language")); my $checksum_node = GetFirstChild($item_node, "checksum"); if ( ! defined $checksum_node) { print STDERR "releases data file corrupt (item has no 'checksum' node)\n"; return undef; } my $checksum_type = GetAttribute($checksum_node, "type"); my $checksum_value = GetText($checksum_node); my $file_size = GetText(GetFirstChild($item_node, "size")); my $product_code = GetText(GetFirstChild($item_node, "product-code")); my $url = $url_template; $url =~ s/\%L/$language/g; return ( $language, { 'URL' => $url, 'checksum-type' => $checksum_type, 'checksum-value' => $checksum_value, 'file-size' => $file_size, 'product-code' => $product_code }); } =head2 GetPreviousVersion($version) Look up $version in the sorted list of released versions. Return the previous element. Whe $version is not found then return the last element (under the assumption that $version will be the next released version). =cut sub GetPreviousVersion ($) { my ($current_version) = @_; my $release_data = installer::patch::ReleasesList::Instance(); my $previous_version = undef; foreach my $version (@{$release_data->{'releases'}}) { if ($version eq $current_version) { return $previous_version; } else { $previous_version = $version; } } return $previous_version; } 1;