Archive::Ipkg - Module for manipulation of iPKG archives


Archive-Ipkg documentation Contained in the Archive-Ipkg distribution.

Index


Code Index:

NAME

Top

Archive::Ipkg - Module for manipulation of iPKG archives

SYNOPSIS

Top

  use Archive::Ipkg;

  my $ipkg = Archive::Ipkg->new(
    name    => 'foo',
    version => '0.1');

  $ipkg->add_files(@filenames);
  $ipkg->add_file($filename, $name_in_archive);
  $ipkg->add_file_by_data($filename, $data);

  $ipkg->name("changedmymind");
  $ipkg->description("Useless package of mine\nDon't use it!");
  $ipkg->version("0.0");

  $ipkg->write;
  my $data = $ipkg->data;




DESCRIPTION

Top

This module aids in the construction of iPKG packages (See links below for description of the implemented package format). The interface is somewhat similar to that of Archive::Tar, but with a couple differences. I consider the module to be in a beta stage.

The typical workflow is as follows: Create a new object, add files to it, set the properties of the iPKG package and write it to a file (or get the data as a scalar). There are a number of properties, and many of them are compulsory and need to be in a certain format. The module only generates the final package if the specs are meet. See below for a description of all properties and how to circumvent some of the checks.

CLASS METHODS

Top

CONSTRUCTOR

$ipkg = Archive::Ipkg->new(...)

The new constructor creates a new object. You can pass any property to the constructor, if you want, or set the properties later.

ADDING FILES

The following functions add files to the internal data archive. All return undef on failure.

$ipkg->add_files(@filenames)

Is directly passed through to Archive::Tar to add several files.

$ipkg->add_file_by_data($filename, $data [, $properties_hash]);

Is directly passed through to Archive::Tar to add a file given its name, contents and optionally some properties. See Archive::Tar for a description of the properties available.

$ipkg-add_file($filename, $name_in_archive)>

A convenience function that lets you add a file with a new name. Reads the file's content and uses Archive::Tar's add_data to add the file.

WRITING THE PACKAGE

$ctl = $ipkg->control()

Returns the text of the control file that will be written. You just need this if you want to check the file.

$errmsg = $ipkg->verify()

Verifies whether the archive can be written by checking whether at least the required properties are set (see below). Returns an error message if unsuccessful, else undef (!).

$ipkg->write()

Writes the archive to disk, in the current directory. If no filename is given, it uses the internally generated default filename (see filename property). You can either set a filename of your own before writing or combine the default filename and a directory name to save the package in the directory of your choice. Returns undef on failure.

$ipkg->data()

Returns the archive data as a scalar. Unlike Archive::Tar's data, this does not return the internal data structures, but rather the archive's contents as they would be written to disk. Returns undef on failure.

PROPERTIES

The following properties are obligatory: name, version, architecture, maintainer, section and description. Some of them have defaults set by the module, though, so you don't need to set all of them. If a property needs to be in a certain format, the accessor function will set the value to undef to make your write fail if you set an invalid value.

Except of sloppy_checks and strict_checks, all the following functions will return undef if a new value is set and the syntax check fails, else the properties (new) value.

$ipkg->sloppy_checks

Will switch of all syntax checks in accessor functions. verify will still check whether all required properties are set.

$ipkg->strict_checks

Re-enables syntax checks in accessor functions (this is the default).

$ipkg->architecture("arch")

The architecture the module is supposed to work on. Currently either arm or all

Default: arm

$ipkg->config_files($array_ref)

Marks a number of files (names contained in $array_ref) as configuration files, so ipkg will not automatically overwrite them. Note that you still need to add the files. Will not check whether the files are actually contained in the archive.

Default: No config files

$ipkg->depends("pkg1,pkg2")

The packages this package depends on, as a comma-separated list of package names (ie, matching /^[a-z0-9.+-]+(,[a-z0-9.+-]+)*$/ ).

Default: No depends

$ipkg->description("text, even on\nmultiple lines")

A short description of the module. Note that data and write will take appropriate care of newlines, so you don't have to do it yourself. Just write \n, as you normally would The description should be non-empty.

Default: No description, you need to set one.

$ipkg->filename("../foo.ipk")

The filename (including directory) of the module.

Default: $name_$version_$arch.ipkg

$ipkg->maintainer("Bozo the Clown, bozo@clowns.org")

The maintainer's name and e-mail address (ie, matches /@/).

$ipkg->name("foo")

The package name. Matches /^[a-z0-9.+-]+$/.

Default: No name, you need to set one.

$ipkg->priority("optional")

The package's priority. One of required, standard, important, optional, extra.

Default: optional

$ipkg->section("misc")

The package's section. Currently, the sections given in the Zaurus and in the Familiar documentation are allowed. These are the following:

Zaurus: Games Multimedia Communcations Settings Utilities Applications Console Misc

Familiar: admin base comm editors extras graphics libs misc net text web x11

Default: misc

$ipkg->version("1.2")

The package's version, matching /^[a-zA-Z0-9.+]*$/ and having at least one digit.

Default: No version, you need to set one.

$ipkg->preinst_script($script_text)

(Shell) script to be executed before installation.

Default: No script

$ipkg->postinst_script($script_text)

(Shell) script to be executed after installation.

Default: No script

$ipkg->prerm_script($script_text)

(Shell) script to be executed before removal.

Default: No script

$ipkg->postrm_script($script_text)

(Shell) script to be executed after removal.

Default: No script

DEFAULTS METHODS

The following methods return the default values for the properties above.

my $val = $ipkg->default_architecture
my $val = $ipkg->default_priority
my $val = $ipkg->default_section
my $val = $ipkg->default_version

BUGS

Top

Can't read archives (yet).

AUTHOR

Top

Christian Renz <crenz@web42.com>

LICENSE

Top

Copyright 2003 Christian Renz <crenz@web42.com>

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

SEE ALSO

Top

http://handhelds.org/moin/moin.cgi/Ipkg?action=highlight&value=iPKG

http://handhelds.org/moin/moin.cgi/BuildingIpkgs?action=highlight&value=iPKG


Archive-Ipkg documentation Contained in the Archive-Ipkg distribution.

package Archive::Ipkg;

use strict;
use warnings;
use Carp;

use lib qw(.);

use Archive::Tar;
use Compress::Zlib;

our $VERSION = '0.04';

# constructor
sub new {
    my $class = shift;
    my %opts = @_;

    my $self = {};

    $self->{sloppy} = 0;
    $self->{sloppy} = $opts{sloppy}
        if exists $opts{sloppy};

    bless $self, $class;

    # initialize properties without defaults
    $self->config_files(@{$opts{config_files}})
        if exists $opts{config_files};
    $self->depends($opts{depends})
        if exists $opts{depends};
    $self->description($opts{description})
        if exists $opts{description};
    $self->filename($opts{filename})
        if exists $opts{filename};
    $self->maintainer($opts{maintainer})
        if exists $opts{maintainer};
    $self->name($opts{name})
        if exists $opts{name};
    $self->preinst_script($opts{preinst_script})
        if exists $opts{preinst_script};
    $self->postinst_script($opts{postinst_script})
        if exists $opts{postinst_script};
    $self->prerm_script($opts{prerm_script})
        if exists $opts{prerm_script};
    $self->postrm_script($opts{postrm_script})
        if exists $opts{postrm_script};

    # initialize properties with defaults
    $self->architecture(exists $opts{architecture} ?
        $opts{architecture} : $self->default_architecture);
    $self->priority(exists $opts{priority} ?
        $opts{priority} : $self->default_priority);
    $self->section(exists $opts{section} ?
        $opts{section} : $self->default_section);
    $self->version(exists $opts{version} ?
        $opts{version} : $self->default_version);

    # initialize internal archives
    $self->{TAR_CONTROL} = undef; # create when saving
    $self->{TAR_DATA} = Archive::Tar->new();
    $self->{TAR_IPKG} = undef; # create when saving

    return $self;
}

# control property checking
sub sloppy_checks {
    my $self = shift;
    
    $self->{sloppy} = 1;
}

sub strict_checks {
    my $self = shift;

    $self->{sloppy} = 0;
}


# default values for properties
sub default_architecture {
    return "arm";
}

sub default_priority {
    return "optional";
}

sub default_section {
    return "misc";
}

sub default_version {
    return "";
}

# accessor functions for properties
sub config_files {
    my $self = shift;
    my $config_files = shift;

    $self->{config_files} = $config_files
        if (defined $config_files && ref $config_files);
    return $self->{config_files};
}

sub depends {
    my $self = shift;
    my $depends = shift;
    
    return $self->{depends} unless defined $depends;

    if ($self->{sloppy} || $depends =~ /^[a-z0-9.+-]+(,[a-z0-9.+-]+)*$/) {
        $self->{depends} = $depends;
    } else {
        $self->{depends} = undef;
    }
    
    return $self->{depends};
}

sub description {
    my $self = shift;
    my $description = shift;
    
    return $self->{description} unless defined $description;

    if ($self->{sloppy} || $description =~ /\S/) {
        $self->{description} = $description;
    } else {
        $self->{description} = undef;
    }
    
    return $self->{description};
}

sub filename {
    my $self = shift;
    my $filename = shift;
    
    if (defined $filename) {
        $self->{filename} = $filename;
    } elsif (!defined $self->{filename}) {
        if (defined $self->{name}) {
	    $self->{filename} = $self->{name};

	    $self->{filename} .= "_" . $self->{version}
                if (defined $self->{version} && 
		    $self->{version} =~ /\S/);
	    $self->{filename} .= "_" . $self->{architecture}
                if (defined $self->{architecture} && 
		    $self->{architecture} =~ /\S/);
	    $self->{filename} .= ".ipk";
	}
    }
    
    return $self->{filename};
}

sub maintainer {
    my $self = shift;
    my $maintainer = shift;

    return $self->{maintainer} unless defined $maintainer;
    
    if ($self->{sloppy} || $maintainer =~ /\@/) {
        $self->{maintainer} = $maintainer;
    } else {
        $self->{maintainer} = undef;
    }
    
    return $self->{maintainer};
}

sub name {
    my $self = shift;
    my $name = shift;

    return $self->{name} unless defined $name;

    if ($self->{sloppy} || $name =~ /^[a-z0-9.+-]+$/) {
        $self->{name} = $name;
    } else {
        $self->{name} = undef;
    }

    return $self->{name};
}

sub preinst_script {
    my $self = shift;
    my $preinst_script = shift;

    $self->{preinst_script} = $preinst_script if defined $preinst_script;
    return $self->{preinst_script};
}

sub postinst_script {
    my $self = shift;
    my $postinst_script = shift;

    $self->{postinst_script} = $postinst_script if defined $postinst_script;
    return $self->{postinst_script};
}

sub prerm_script {
    my $self = shift;
    my $prerm_script = shift;

    $self->{prerm_script} = $prerm_script if defined $prerm_script;
    return $self->{prerm_script};
}

sub postrm_script {
    my $self = shift;
    my $postrm_script = shift;

    $self->{postrm_script} = $postrm_script if defined $postrm_script;
    return $self->{postrm_script};
}

sub architecture {
    my $self = shift;
    my $architecture = shift;

    return $self->{architecture} unless defined $architecture;

    if ($self->{sloppy} || $architecture =~ /^arm|all$/) {
        $self->{architecture} = $architecture;
    } else {
        $self->{architecture} = undef;
    }

    return $self->{architecture};
}

sub priority {
    my $self = shift;
    my $priority = shift;

    return $self->{priority} unless defined $priority;

    if ($self->{sloppy} || $priority =~ /^required|standard|important|optional|extra$/) {
        $self->{priority} = $priority;
    } else {
        $self->{priority} = undef;
    }

    return $self->{priority};
}

sub section {
    my $self = shift;
    my $section = shift;

    my @zaurus_sections = qw(Games Multimedia Communications Settings
                             Utilities Applications Console Misc);
    my @familiar_sections = qw(admin base comm editors extras graphics libs
                               misc net text web x11);
    my $regex = '^' . join('|', @zaurus_sections, @familiar_sections) . '$';

    return $self->{section} unless defined $section;

    if ($self->{sloppy} || $section =~ $regex) {
        $self->{section} = $section;
    } else {
        $self->{section} = undef;
    }

    return $self->{section};
}

sub version {
    my $self = shift;
    my $version = shift;

    return $self->{version} unless defined $version;

    if ($self->{sloppy} ||
	($version =~ /^[a-zA-Z0-9.+]*$/ && $version =~ /\d/)) {
        $self->{version} = $version;
    } elsif ($version =~ /^\s*$/) {
	$self->{version} = "";
    } else {
        $self->{version} = undef;
    }

    return $self->{version};
}

# verification

sub verify {
    my $self = shift;
    my $verify = undef;
    
# required: package, version, architecture, maintainer, section, description
    $verify .= "No package name\n" unless (defined $self->{name});
    # version should be at least empty
    $verify .= "No version\n" unless (defined $self->{architecture});
    $verify .= "No architecture\n" unless (defined $self->{architecture});
    $verify .= "No maintainer\n" unless (defined $self->{maintainer});
    $verify .= "No section\n" unless (defined $self->{section});

    $verify .= "No description\n" unless (defined $self->{description});

    return $verify;
}

# file handling

sub add_files {
    my $self = shift;
    
    return $self->{TAR_DATA}->add_files(@_);
}

sub add_file {
    my $self = shift;
    my ($filename, $new_filename) = @_;

    $new_filename = $filename unless defined $new_filename;
    $new_filename =~ s|^/?|./|;
    
    return undef unless open(ADDFILE, "<$filename");
    binmode ADDFILE;
    local $/; undef $/;
    $self->{TAR_DATA}->add_data($new_filename, <ADDFILE>, { mode => 0100644 });
    close(ADDFILE);

    return 1;
}

sub add_file_by_data {
    my $self = shift;
    my ($filename, $data, $opts) = @_;

    $filename =~ s|^/?|./|;

    $opts = { mode => 0100644 }
      unless (defined $opts && ref $opts && exists $opts->{mode});

    return $self->{TAR_DATA}->add_data($filename, $data, $opts);
}

# whole archive handling

# returns "control" file contents
sub control {
    my $self = shift;

    my $control = '';

    $control .= "Package: " . $self->{name} . "\n";
    $control .= "Priority: " . $self->{priority} . "\n"
        if (defined $self->{priority});
    $control .= "Section: " . $self->{section} . "\n";
    $control .= "Version: " . $self->{version} . "\n";
    $control .= "Architecture: " . $self->{architecture} . "\n";
    $control .= "Maintainer: " . $self->{maintainer} . "\n";
    $control .= "Depends: " . $self->{depends} . "\n"
        if (defined $self->{depends});

    my $desc = $self->{description};
    # start lines with space
    $desc =~ s/\n/ \n/g;
    # start empty lines with space and .
    $desc =~ s/\n \n/\n .\n/g;
    $control .= "Description: $desc\n";

    return $control;
}

sub data {
    my $self = shift;
    
    return undef if $self->verify;
    # make control package
    $self->{TAR_CONTROL} = Archive::Tar->new();

    $self->{TAR_CONTROL}->add_data("./control", $self->control, { mode => 0100644 });
    foreach (qw(preinst postinst prerm postrm)) {
        $self->{TAR_CONTROL}->add_data("./$_", $self->{$_},
            {mode => 0100755}) if (defined $self->{$_});
    }

    $self->{TAR_CONTROL}->add_data("./conffiles",
        join"\n", $self->{config_files}, { mode => 0100644 })
        if (defined $self->{config_files} && ref $self->{config_files});

    # make package
    $self->{TAR_IPKG} = Archive::Tar->new();
    $self->{TAR_IPKG}->add_data("./debian-binary", "2.0\n", { mode => 0100644 });

    my ($tar_data);

    $tar_data = Compress::Zlib::memGzip($self->{TAR_DATA}->write());
    return undef unless defined $tar_data;
    $self->{TAR_IPKG}->add_data("./data.tar.gz",
        $tar_data, { mode => 0100644 });

    $tar_data = Compress::Zlib::memGzip($self->{TAR_CONTROL}->write());

    return undef unless defined $tar_data;
    $self->{TAR_IPKG}->add_data("./control.tar.gz",
        $tar_data, { mode => 0100644 });

    $tar_data = Compress::Zlib::memGzip($self->{TAR_IPKG}->write());
    return $tar_data;
}

sub write {
    my $self = shift;

    my $filename = $self->filename;
    my $data = $self->data;
    
    return undef unless ($filename && defined $data);

    open IPKG, ">$filename" or carp "Can't write iPKG '$filename': $!";
    binmode IPKG;
    print IPKG $data;
    close IPKG;
}

1;
__END__