home *** CD-ROM | disk | FTP | other *** search
- package PPM::Repository::PPM3Server;
-
- use strict;
- use SOAP::Lite 0.51;
- use Data::Dumper;
- use File::Basename qw(basename);
- use Digest::MD5 ();
-
- use PPM::Config;
- use PPM::Sysinfo;
- use PPM::Result qw(Ok Error Warning List);
- use PPM::PPD;
-
- use base qw(PPM::Repository);
- our $VERSION = '3.06';
- our $PROTOCOL_VERSION = 3;
-
- #==============================================================================
- # Note: the server exports this interface:
- # describe('language', 'package name');
- # getppd('language', 'package name');
- # search('language', 'query', 'case-insensitive');
- # uptodate('language', 'package name', 'osd-version');
- # ppm_protocol();
- #
- # Note: definition of package_entity:
- # package_entity {
- # repository_url
- # target_type
- # target_name
- # package_name
- # package_version
- # }
- #
- # This part of the interface is for profile management:
- # profile_create('name');
- # profile_delete('name');
- # profile_save('name', package_entity(s));
- # profile_info('name');
- # profile_target_rename('name', 'oldtarget', 'newtarget');
- # profile_rename('oldname', 'newname'); # XXX: emulated on client-side!
- #
- # This part is for profile tracking:
- # installed(package_entity);
- # removed(package_entity);
- # upgraded(package_entity);
- #==============================================================================
-
- sub init {
- my $o = shift;
- my $file = PPM::Config::get_license_file();
-
- $o->{licfile} = $file;
- $o->{mtime} = 0;
- $o->{license} = undef;
-
- 1;
- }
-
- sub check_license {
- my $o = $_[0];
- my $file = $o->{licfile};
-
- # Reset state unless the license file exists
- goto &init unless -f $file;
-
- # Used the cached license unless the license file has changed on disk
- my $f_mtime = ((stat($file))[9]);
- my $l_mtime = $o->{mtime};
- return unless ($f_mtime > $l_mtime or $file ne $o->{liclast});
-
- # Update the cache from the disk
- if (open (my $LICENSE, $file)) {
- $o->{mtime} = $f_mtime;
- $o->{license} = do { local $/; <$LICENSE> };
- $o->{liclast} = $file;
- close ($LICENSE) or die "can't close license file $file: $!";
- }
- }
-
- sub search {
- my $o = shift;
- my $target = shift;
- my $query = shift;
- my $casei = shift;
-
- # Get all my arguments together:
- my $target_t = $target->config_get("TARGET_TYPE")->result;
- my @headers = $o->mkheaders(target => $target);
- my $response = eval {
- $o->{client}->search($target_t, $query, $casei, @headers)->result;
- };
- my $err = $o->errors($response);
- return $err unless $err->ok;
-
- my @results;
- for my $res (@{$response->{'results'}}) {
- my $h = {
- name => $res->[0],
- version => $res->[1],
- abstract => $res->[2],
- };
- my $ppd = PPM::Repository::PPM3Server::PPD->new($h, 0);
- @$ppd{qw(rep id from)} = ($o, $ppd->name, 'repository');
- push @results, $ppd;
- }
- List(@results);
- }
-
- sub describe {
- my $o = shift;
- my $target = shift;
- my $pkg = shift;
-
- my $target_t = $target->config_get("TARGET_TYPE")->result;
- my @headers = $o->mkheaders(target => $target);
- my $response = eval {
- $o->{client}->describe($target_t, $pkg, @headers)->result;
- };
- my $err = $o->errors($response);
- return $err unless $err->ok;
-
- my $ppd = PPM::Repository::PPM3Server::PPD->new($response->{'results'}, 1);
- @$ppd{qw(rep id from)} = ($o, $ppd->name, 'repository');
- Ok($ppd);
- }
-
- sub getppd_obj {
- my $o = shift;
- my $target = shift;
- my $pkg = shift;
- my $ppd_txt = $o->getppd($target, $pkg);
- return $ppd_txt unless $ppd_txt->ok;
- my $obj = PPM::PPD::->new($ppd_txt->result, $o, $pkg);
- $obj->{from} = 'repository';
- Ok($obj);
- }
-
- sub getppd {
- my $o = shift;
- my $target = shift;
- my $pkg = shift;
-
- my $target_t = $target->config_get("TARGET_TYPE")->result;
- my @headers = $o->mkheaders(target => $target);
- my $response = eval {
- $o->{client}->getppd($target_t, $pkg, @headers)->result;
- };
- my $err = $o->errors($response);
- return $err unless $err->ok;
- Ok($response->{'results'});
- }
-
- sub uptodate {
- my $o = shift;
- my $target = shift;
- my $pkg = shift;
- my $version = shift;
-
- my $target_t = $target->config_get("TARGET_TYPE")->result;
- my @headers = $o->mkheaders(target => $target);
- my $response = eval {
- # call the more efficient uptodate2 method.
- $o->{client}->uptodate2($target_t, $pkg, $version, @headers)->result;
- };
- my $err = $o->errors($response);
- return $err unless $err->ok;
- List($response->{results}[0], $response->{results}[1]{version});
- # # If the status is false (it's out of date) return the version on the
- # # server as the new version.
- # my $newversion = '';
- # unless ($response->{'results'}) {
- # my $ppd = $o->describe($target, $pkg)->result;
- # $newversion = $ppd->version;
- # }
- # List($response->{'results'}, $newversion);
- }
-
- sub batch {
- my $o = shift;
- # The batch() method was introduced in PPM protocol version 303.
- return $o->SUPER::batch(@_) unless $o->protocol >= 303;
- my $targ = shift;
- my @tasks = @_;
- my $targ_t = $targ->config_get("TARGET_TYPE")->result;
-
- # Every task needs the target as the first argument, so add it.
- for my $task (@tasks) {
- splice @$task, 1, 0, $targ_t;
- }
- my @headers = $o->mkheaders($targ => $targ);
- my $response = eval {
- $o->{client}->batch($targ_t, @tasks, @headers)->result;
- };
- my $err = $o->errors($response);
- return $err unless $err->ok;
- my @responses = @{$response->{results}};
- for my $i (0 .. $#tasks) {
- my $resp = $responses[$i];
- my $meth = $tasks[$i][0];
- my $err = $o->errors($resp);
- if ($err->ok) {
- my $clean_method = "cleanup_$meth";
- $responses[$i] = $o->can($clean_method)
- ? $o->$clean_method($resp->{results})
- : Ok($resp->{results});
- }
- else {
- $responses[$i] = $err;
- }
- }
- List(@responses);
- }
-
- #=============================================================================
- # Cleanup functions for the batch() method.
- #=============================================================================
- sub cleanup_uptodate2 {
- my ($o, $result) = @_;
- my $ppd = PPM::Repository::PPM3Server::PPD->new($result->[1], 0);
- @$ppd{qw(rep id from)} = ($o, $ppd->name, 'repository');
- $result->[1] = $ppd;
- Ok($result);
- }
-
- #=============================================================================
- # Profiles
- #=============================================================================
- sub profile_list {
- my $o = shift;
- my @headers = $o->mkheaders;
- my $response = eval {
- $o->{client}->profile_list(@headers)->result;
- };
- my $err = $o->errors($response);
- return $err unless $err->ok;
-
- my @profiles = @{$response->{'results'}};
- List(@profiles);
- }
-
- sub profile_add {
- my $o = shift;
- my $name = shift;
- my @headers = $o->mkheaders;
- my $response = eval {
- $o->{client}->profile_create($name, @headers)->result;
- };
- my $err = $o->errors($response,"profile_create");
- return $err unless $err->ok;
- Ok();
- }
-
- sub profile_del {
- my $o = shift;
- my $name = shift;
- my @headers = $o->mkheaders;
- my $response = eval {
- $o->{client}->profile_delete($name, @headers)->result;
- };
- my $err = $o->errors($response,"profile_delete");
- return $err unless $err->ok;
- Ok();
- }
-
- sub profile_save {
- my $o = shift;
- my $name = shift;
- my @entries = @_;
- my @headers = $o->mkheaders;
- my $response = eval {
- $o->{client}->profile_save($name, \@entries, @headers)->result;
- };
- my $err = $o->errors($response);
- return $err unless $err->ok;
- Ok();
- }
-
- sub profile_info {
- my $o = shift;
- my $name = shift;
- my @headers = $o->mkheaders;
- my $response = eval {
- $o->{client}->profile_info($name, @headers)->result;
- };
- my $err = $o->errors($response);
- return $err unless $err->ok;
- my $entries = $response->{'results'};
- List(@$entries);
- }
-
- sub profile_target_rename {
- my $o = shift;
- my $profile = shift;
- my $oldname = shift;
- my $newname = shift;
- my @headers = $o->mkheaders;
- my $response = eval {
- my @args = ($profile, $oldname, $newname);
- $o->{client}->profile_target_rename(@args, @headers)->result;
- };
- my $err = $o->errors($response);
- return $err unless $err->ok;
- Ok();
- }
-
- # This is a bit of a temporary hack: the server doesn't actually expose a
- # profile_rename() method, so I emulate it by retrieving the doomed profile,
- # saving it as the new profile, and deleting the other. I suspect this will be
- # moved over to the server in future, because it can be lightening-fast if
- # done directly in the database.
- sub profile_rename {
- my $o = shift;
- my $oldprof = shift;
- my $newprof = shift;
-
- # Retrieve the old profile:
- my $info = $o->profile_info($oldprof);
- return $info unless $info->ok;
-
- # Delete the new one, but don't croak if it returns an error. This allows
- # us to rename over old profiles.
- my $purge = $o->profile_del($newprof);
-
- # Create the new one:
- my $new = $o->profile_add($newprof);
- return $new unless $new->ok;
-
- # Save the new one:
- my $save = $o->profile_save($newprof, $info->result_l);
- return $save unless $save->ok;
-
- # Delete the old one:
- my $del = $o->profile_del($oldprof);
- return $del unless $del->ok;
-
- Ok();
- }
-
- #=============================================================================
- # Profile Tracking:
- #=============================================================================
- sub installed {
- my $o = shift;
- my $profile = shift;
- my @l = @_;
- my @headers = $o->mkheaders;
- my $response = eval {
- $o->{client}->profile_pkgs_installed($profile, \@l, @headers)->result;
- };
- $o->errors($response);
- }
-
- sub upgraded {
- my $o = shift;
- my $profile = shift;
- my @l = @_;
- my @headers = $o->mkheaders;
- my $response = eval {
- $o->{client}->profile_pkgs_upgraded($profile, \@l, @headers)->result;
- };
- $o->errors($response);
- }
-
- sub removed {
- my $o = shift;
- my $profile = shift;
- my @l = @_;
- my @headers = $o->mkheaders;
- my $response = eval {
- $o->{client}->profile_pkgs_removed($profile, \@l, @headers)->result;
- };
- $o->errors($response);
- }
-
- # Calculate a hash of the current user, plus the host and the install time.
- # This is useful for tracking how many "users" are using each installation.
- my $userhash = PPM::Sysinfo::generate_user_key();
- my $insthash = PPM::Sysinfo::inst_key();
-
- # This little helper builds SOAP Headers we can use to send along with the
- # SOAP request. The license and other information is sent along with it.
- sub mkheaders {
- my $o = shift;
- my %args = @_;
- my @headers;
-
- # By checking the license each time, we can auto-detect new licenses
- # without a re-start of PPM:
- $o->check_license;
- push @headers, SOAP::Header->name('license', $o->{license});
-
- # Push on the ID of this installation of PPM:
- push @headers, SOAP::Header->name('ppm_install_id', $insthash);
-
- # Push on a hash of the current user plus hostname & install time. Note
- # that we specifically don't want to use all the same elements that went
- # into the install_id of this host (hostname, insttime, os, ip_addr). We
- # want to have each user get a unique string, but to munge enough extra
- # uniqueness that the usernames can't be guessed just by a simple
- # dictionary attack against someone sniffing the MD5 keys.
- push @headers, SOAP::Header->name('user_hash', $userhash);
-
- # Push on the client's protocol version and "real" version:
- push @headers, SOAP::Header->name('client_version', $VERSION);
- push @headers, SOAP::Header->name('ppm_protocol',
- $PROTOCOL_VERSION);
-
- # This information has to be grabbed from the piece of software actually
- # interacting with the user. Currently, there's no way to do that cleanly.
- push @headers, SOAP::Header->name('useragent', 'PPM');
- push @headers, SOAP::Header->name('useragent_vers', '3.0');
-
- # Push on target-specific stuff:
- for my $k (keys %args) {
- if ($k eq 'target') {
- my $t = $args{$k};
- push(@headers,
- SOAP::Header->name('archname',
- $t->config_get("ARCHITECTURE")->result),
- SOAP::Header->name('os', $t->config_get("OSVALUE")->result),
- SOAP::Header->name('osvers',
- $t->config_get("OSVERSION")->result),
- );
- }
- }
- @headers;
- }
-
- sub type_printable { "PPMServer 3.0" }
-
- sub errors {
- my $o = shift;
- my $response = shift;
-
- # assuming that method name here and method name on server are usually
- # equivalent. if not, use an optional second argument to supply method
- # name.
- my $method = shift || (split '::', (caller(1))[3])[-1];
-
- if ($@) {
- chomp $@;
- return PPM::Repository::Result::->new("$method exception: $@.",
- $o->location,
- );
- }
- elsif (not defined $response) {
- return
- PPM::Repository::Result::->new(
- "$method returned undefined results.",
- $o->location,
- );
- }
- elsif ($response->{'status'} != 0) {
- return PPM::Repository::Result::->new($response->{'message'},
- $o->location,
- $response->{'status'}
- );
- }
- Ok();
- }
-
- package PPM::Repository::PPM3Server::PPD;
- our @ISA = qw(PPM::PPD);
-
- sub new {
- my $this = shift;
- my $class = ref($this) || $this;
- my $self = bless {}, $class;
- my $server_ppd = shift;
- my $complete = shift;
-
- $self->{is_complete} = $complete;
-
- # Author: "authorname (authoremail)"
- if (defined $server_ppd->{'authorname'}) {
- $self->{parsed}{AUTHOR} = $server_ppd->{'authorname'};
- if (defined $server_ppd->{'authoremail'}) {
- $self->{parsed}{AUTHOR} .= " ($server_ppd->{'authoremail'})";
- }
- }
-
- # Name, title, version, abstract:
- for my $field (qw(title abstract version name)) {
- $self->{parsed}{"\U$field"} = $server_ppd->{$field};
- }
-
- # Implementations:
- for my $impl (@{$server_ppd->{implementation}}) {
- my $i = bless { ARCHITECTURE => $impl },
- 'PPM::Repository::PPM3Server::PPD::Implementation';
- push @{$self->{parsed}{IMPLEMENTATION}}, $i;
-
- # Dependencies:
- for my $dep (@{$server_ppd->{dependency}}) {
- my $dep = bless { NAME => $dep->{name},
- VERSION => $dep->{version} },
- 'PPM::Repository::PPM3Server::PPD::Dependency';
- push @{$i->{DEPENDENCY}}, $dep;
- }
- }
- return $self;
- }
-
- sub version {
- my $o = shift;
- $o->version_osd;
- }
-
- package PPM::Repository::PPM3Server::PPD::Implementation;
- our @ISA = qw(PPM::PPD::Implementation);
-
- package PPM::Repository::PPM3Server::PPD::Dependency;
- our @ISA = qw(PPM::PPD::Dependency);
-
- sub version {
- my $o = shift;
- $o->version_osd;
- }
-