# ================================================================== # Gossamer Links - enhanced directory management system # # Website : http://gossamer-threads.com/ # Support : http://gossamer-threads.com/scripts/support/ # CVS Info : 087,071,086,086,085 # Revision : $Id: Update.pm,v 1.11 2009/05/08 19:56:50 brewt Exp $ # # Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved. # Redistribution in part or in whole strictly prohibited. Please # see LICENSE file for full details. # ================================================================== # package Links::Update; use strict; use Links qw/$CFG $IN %STASH/; use GT::Update qw/:severity/; use GT::File::Tools qw/basename/; use GT::Config; use constant CACHE_TIMEOUT => 5*60; # Only check the server at most once every 5 minutes sub _updater { $STASH{updates} ||= GT::Config->load("$CFG->{admin_root_path}/Links/Config/Updates.pm", { debug => $CFG->{debug_level} }); return $STASH{updater} if $STASH{updater}; (my $cgi_path = $CFG->{admin_root_path}) =~ s{[\\/]+admin[\\/]*$}//; $STASH{updater} = GT::Update->new( product => 'Links', version => $CFG->{version}, reg_number => $CFG->{reg_number}, init_path => $CFG->{admin_root_path}, perl_path => $CFG->{path_to_perl}, backup_path => "$CFG->{admin_root_path}/updates", paths => { script => { cgi => $cgi_path, admin => $CFG->{admin_root_path} }, library => $CFG->{admin_root_path}, template => $CFG->{admin_root_path} . '/templates', static => { static => $CFG->{build_static_path}, }, fixed => { static => $CFG->{build_static_path}, cool => $CFG->{build_cool_path}, detail => $CFG->{build_detail_path}, new => $CFG->{build_new_path}, ratings => $CFG->{build_ratings_path}, build => $CFG->{build_root_path}, }, version => $CFG->{admin_root_path} }, replacements => { library => { '' => { 'Links.pm' => { '<%VERSION%>' => $CFG->{version} } } } }, installed => ($STASH{updates}->{installed} ||= {}), testing => $STASH{updates}->{testing} ); } sub check { my $updater = _updater; my ($cached, @updates); if (my $cache = $STASH{updates}->{cache} and !$STASH{updates}->{testing}) { if ($cache->{version} == $GT::Update::VERSION and $cache->{time} > time - CACHE_TIMEOUT) { # Only check at most once every 5 minutes @updates = @{$cache->{updates}}; $cached = 1; } } unless ($cached) { @updates = $updater->check; if (@updates == 1 and not defined $updates[0]) { my $error = $updater->error; my ($error_code, $error_message) = $error =~ /error code: (\d{3})\s*(.*)/; return { error => $error, update_error_code => $error_code, update_error_message => $error_message }; } $STASH{updates}->{cache} = { time => time, version => $GT::Update::VERSION, updates => \@updates }; $STASH{updates}->save; } my %ret; my %available = map { $_->id => $_ } @updates; for my $update (@updates) { my $id = $update->id; my $severity = $update->severity; my $update_type = $severity == CRITICAL ? 'critical' : $severity == RECOMMENDED ? 'recommended' : $severity == VERSION ? 'version' : 'optional'; my $info = { id => $id, title => $update->title, description => \($update->description), severity => $severity, files => [$update->files], reversible => $update->reversible, unique => $update->unique, deps => [$update->deps], revdeps => [$update->revdeps], requires => [$update->requires], deps_first => $update->deps_first, update_type => $update_type, installed => $update->installed }; push @{$ret{$update_type}}, $info; $ret{update}->{$id} = $info; } for (sort { $a <=> $b } keys %{$STASH{updates}->{installed}->{$CFG->{version}}}) { next if $available{$_}; my %info = %{$STASH{updates}->{installed}->{$CFG->{version}}->{$_}}; $info{id} = $_; my $severity = $info{severity}; my $update_type = $severity == CRITICAL ? 'critical' : $severity == RECOMMENDED ? 'recommended' : $severity == VERSION ? 'version' : 'optional'; push @{$ret{$update_type}}, \%info; } for (qw/critical recommended optional version/) { $ret{"${_}_total"} = @{$ret{$_} ||= []}; $ret{"${_}_installed"} = $ret{"${_}_installable"} = 0; for my $update (@{$ret{$_}}) { next unless $available{$update->{id}}; if ($available{$update->{id}}->{installed}) { $ret{"${_}_installed"}++; } elsif (!$available{$update->{id}}->{impossible}) { $ret{"${_}_installable"}++; } } push @{$ret{update_types}}, { update_type => $_, updates => $ret{$_} }; } my @historic = sort { _numeric_version($a) <=> _numeric_version($b) } keys %{$STASH{updates}->{installed}}; $ret{historic} = \@historic; \%ret; } sub check_historic { my $updater = _updater; my $version = shift || $CFG->{version}; my @updates = $updater->check($version); my %ret = (historic_version => $version, current_version => $CFG->{version}); for (@updates) { my @files = $_->files; my $severity = $_->severity; my $update_type = $severity == CRITICAL ? 'critical' : $severity == RECOMMENDED ? 'recommended' : $severity == VERSION ? 'version' : 'optional'; my $id = $_->id; my %info = ( id => $id, title => $_->title, description => \($_->description), severity => $severity, files => \@files, reversible => ($version eq $CFG->{version} ? $_->reversible : 0), unique => $_->unique, deps => [$_->deps], revdeps => [$_->revdeps], requires => [$_->requires], revdeps_first => $_->revdeps_first, update_type => $update_type, installed => $_->installed ); push @{$ret{$update_type}}, \%info; $ret{update}->{$id} = \%info; } for (qw/critical recommended optional version/) { push @{$ret{update_types}}, { update_type => $_, updates => $ret{$_} }; } my @historic = sort { _numeric_version($a) <=> _numeric_version($b) } grep keys %{$STASH{updates}->{installed}->{$_}}, keys %{$STASH{updates}->{installed}}; $ret{historic} = \@historic; \%ret; } # Takes a version such as 1.3.7 and converts it to 1.0307. sub _numeric_version { my @v = split /\./, (shift =~ /^(\d+(?:\.\d+)*)/)[0]; my $numeric = 0; for (0 .. $#v) { $numeric += $v[$_] * 100**-$_ } $numeric; } sub browser_install { my @updates = $IN->param('install'); my ($status, $errors) = install(@updates); if (!$status) { $errors->{updates_selected} = \@updates; return $errors; } my %ret = (update_success => 1, update_status => $status, updates_selected => []); if ($status == 2) { my $id = $errors; my $path; for (@{$STASH{updates}->{installed}->{$CFG->{version}}->{$updates[0]}->{files}}) { if (basename($_->{file}) eq 'install.cgi') { $path = $_->{file} . "?upgrade_choice=Yes;install_dir=" . $IN->escape($CFG->{admin_root_path}); last; } } $ret{continue_url} = $path; } return \%ret; } # Installs updates passed in. Returns (0, \%error_hash) on failure, 1 on # success of normal updates, (2, $id) on the success of version upgrade files. sub install { my @updates = @_; my $updater = _updater; my $v = $updater->verify(@updates); return 0, { %$v, update_failed => 1, verify_failed => 1 } if ref $v eq 'HASH'; @updates = @$v; my $success = $updater->install_verified(@updates); if (!$success) { my $error = $updater->error; return 0, { update_failed => 1, error => "Update failed: $error" }; } $STASH{updates}->{installed} = { $updater->installed }; delete $STASH{updates}->{cache}; $STASH{updates}->save; if (@updates == 1 and $STASH{updates}->{installed}->{$CFG->{version}}->{$updates[0]}->{severity} == VERSION) { # We just installed a version upgrade return (2, $updates[0]); } return 1; } sub browser_uninstall { my @updates = $IN->param('uninstall'); my ($status, $errors) = uninstall(@updates); if (!$status) { $errors->{updates_selected} = \@updates; return $errors; } return { uninstall_success => 1, updates_selected => [] }; } sub uninstall { my @updates = @_; my $updater = _updater; my $v = $updater->verify_uninstall(@updates); return { %$v, update_failed => 1, verify_failed => 1 } if ref $v eq 'HASH'; @updates = @$v; my $success = $updater->uninstall_verified(@updates); if (!$success) { my $error = $updater->error; return 0, { uninstall_failed => 1, error => "Update uninstall failed: $error" }; } $STASH{updates}->{installed} = { $updater->installed }; delete $STASH{updates}->{cache}; $STASH{updates}->save; return 1; } # Takes a string, such as '/foo/bar/blah/sdffffffddddddddddddddddddddddddddddd' # and replaces a part of it with ... # The arguments are: # - string # - number of characters before the ... # - number of characters after the ... sub shorten { my ($string, $leading, $trailing) = @_; if (length($string) <= ($leading + $trailing + 3)) { return $string; } else { return substr($string, 0, $leading) . ' ... ' . substr($string, -$trailing); } } 1;