App-DistSync

 view release on metacpan or  search on metacpan

lib/App/DistSync.pm  view on Meta::CPAN

=head2 sync

    $ds->sync or die ("Sync error");

Synchronization of the specified directory with the remote resources (mirrors)

=head2 ua

    my $ua = $ds->ua;

Returns the UserAgent instance (LWP::UserAgent)

=head2 verbose

    warn "Error details\n" if $ds->verbose;

This method returns verbose flag

=head1 HISTORY

See C<Changes> file

=head1 TO DO

See C<TODO> file

=head1 SEE ALSO

L<LWP::Simple>

=head1 AUTHOR

Serż Minus (Sergey Lepenkov) L<https://www.serzik.com> E<lt>abalama@cpan.orgE<gt>

=head1 COPYRIGHT

Copyright (C) 1998-2026 D&D Corporation

=head1 LICENSE

This program is distributed under the terms of the Artistic License Version 2.0

See the C<LICENSE> file or L<https://opensource.org/license/artistic-2-0> for details

=cut

our $VERSION = '1.11';

our $DEBUG //= !!$ENV{DISTSYNC_DEBUG};

use Carp;
use Cwd qw/abs_path getcwd/;
use FindBin qw($Script);
use File::Basename qw/dirname/;
use File::Copy qw/mv/;
use File::Spec;
use File::Path qw/mkpath/;
use Sys::Hostname;
use URI;
use LWP::UserAgent qw//;
use HTTP::Request qw//;
use HTTP::Date qw//;
use HTTP::Status qw//;

use App::DistSync::Util qw/
        debug qrreconstruct touch spew slurp
        fdelete read_yaml write_yaml
        maniread manifind maniwrite
    /;

use constant {
    TEMPFILE    => sprintf("distsync_%s.tmp", $$),
    TIMEOUT     => 30,
    METAFILE    => 'META',
    MANIFEST    => 'MANIFEST',
    MANISKIP    => 'MANIFEST.SKIP',
    MANITEMP    => 'MANIFEST.TEMP',
    MANILOCK    => 'MANIFEST.LOCK',
    MANIDEL     => 'MANIFEST.DEL',
    MIRRORS     => 'MIRRORS',
    README      => 'README',
    SKIPFILES   => [qw/
        META MANIFEST MIRRORS README
        MANIFEST.SKIP MANIFEST.LOCK MANIFEST.TEMP MANIFEST.DEL
    /],
    SKIPMODE    => 1,
    LIMIT       => '+1m', # '+1m' Limit gt and lt
    EXPIRE      => '+3d', # '+3d' For deleting
    FREEZE      => '+1d', # '+1d' For META test
};

# Methods
sub new {
    my $class = shift;
    my %props = @_;

    # Check directory
    my $dir = $props{dir} // getcwd();
    croak("Directory '$dir' not exists") unless length($dir) && (-d $dir or -l $dir);
    $props{dir} = $dir = abs_path($dir);

    # General
    $props{started} = $props{stamp} = time;
    $props{pid} ||= $$;
    $props{timeout} //= TIMEOUT;
    $props{verbose} ||= 0;
    $props{insecure} ||= 0;
    $props{proxy} //= '';
    $props{url} = '';
    $props{hostname} = hostname();

    # Files
    $props{file_meta}       = File::Spec->catfile($dir, METAFILE);
    $props{file_manifest}   = File::Spec->catfile($dir, MANIFEST);
    $props{file_maniskip}   = File::Spec->catfile($dir, MANISKIP);
    $props{file_manilock}   = File::Spec->catfile($dir, MANILOCK);
    $props{file_manitemp}   = File::Spec->catfile($dir, MANITEMP);
    $props{file_manidel}    = File::Spec->catfile($dir, MANIDEL);
    $props{file_mirrors}    = File::Spec->catfile($dir, MIRRORS);
    $props{file_readme}     = File::Spec->catfile($dir, README);
    $props{file_temp}       = File::Spec->catfile(File::Spec->tmpdir(), TEMPFILE);

lib/App/DistSync.pm  view on Meta::CPAN

    return 0 unless maniwrite($self->{file_manifest}, $new_manifest);

    # Creating new META file
    debug("Generating new META file");
    # NOTE! The status in the META file is set only after the final directory structure
    # has been successfully generated. This change distinguishes already "working"
    # resources from those that have just been initialized.
    my $now = time;
    my $new_meta = {
            project     => ref($self),
            version     => $self->VERSION,
            hostname    => $self->{hostname},
            directory   => $self->dir,
            script      => $Script,
            start       => $self->{stamp},
            finish      => $now,
            pid         => $self->pid,
            uri         => $self->{url} || 'localhost',
            url         => $self->{url} || 'localhost',
            date        => scalar(localtime(time)),
            'time'      => $now - $self->{stamp},
            status      => 1,
        };
    return 0 unless write_yaml($self->{file_meta}, $new_meta);

    # Return
    return $status;
}
sub fetch { # Returns structire {status, mtime, size, code, url}
    my $self = shift;
    my $url = shift; # Base url
    my $obj = shift; # The tail of path
    my $file = shift // ''; # File to download
    my $ua = $self->ua;

    # Empty response
    my $ret = {
            status  => 0, # Status
            mtime   => 0, # Last-Modified in ctime format or 0
            size    => 0, # Content-length
            code    => 0, # Status code
            message => '',
            url     => '',
        };

    # Check file
    unless (length($file)) {
        carp "File path to download is not specified";
        return $ret;
    }

    # Make new URI
    my $uri = URI->new($url);
    my $curpath = $uri->path();
    my $newpath = $curpath . (defined $obj ? "/$obj" : '');
       $newpath =~ s/\/{2,}/\//;
    $uri->path($newpath);
    $ret->{url} = $uri->as_string;

    # First request: get HEAD information
    my $request = HTTP::Request->new(HEAD => $uri);
    my $response = $ua->request($request);
    my $content_type = scalar $response->header('Content-Type');
    my $document_length = scalar $response->header('Content-Length');
    my $modified_time = HTTP::Date::str2time($response->header('Last-Modified'));
    my $expires = HTTP::Date::str2time($response->header('Expires'));
    my $server = scalar $response->header('Server');
    $ret->{code} = $response->code;
    $ret->{message} = $response->message;
    if ($self->verbose) {
        if (!$DEBUG && !$response->is_success) {
            say sprintf "> HEAD %s", $uri->as_string;
            say sprintf "< %s", $response->status_line;
        }
        debug("> HEAD %s", $uri->as_string);
        debug("< %s", $response->status_line);
        if ($response->is_success) {
            debug("< Content-Type   : %s", $content_type // '');
            debug("< Content-Length : %s", $document_length || 0);
            debug("< Last-Modified  : %s", $modified_time ? scalar(localtime($modified_time)) : '');
            debug("< Expires        : %s", $expires ? scalar(localtime($expires)) : '');
            debug("< Server         : %s", $server // '');
        } else {
            debug("< Empty response");
        }
    }

    # Status
    unless ($response->is_success) {
        debug("Can't fetch %s. %s", $uri->as_string, $response->status_line);
        return $ret;
    }

    # Size
    $ret->{size} = $document_length || 0;

    # Modified time
    $ret->{mtime} = $modified_time // 0;
    unless ($ret->{mtime}) {
        debug("Can't fetch %s. Header 'Last-Modified' not received", $uri->as_string);
        return $ret;
    }

    # Safe file mirroring
    my $temp = sprintf "%s.tmp", $file;
    if (-e $file) {
        unless (mv($file, $temp)) {
            printf STDERR "Can't move file \"%s\" to \"%s\": %s\n", $file, $temp, $!;
            return $ret;
        }
    }

    # Request
    $response = $ua->mirror($uri, $file);
    $ret->{code} = $response->code;;
    $ret->{message} = $response->message;
    if ($self->verbose) {
        debug("> GET %s", $uri->as_string) or say sprintf "> GET %s", $uri->as_string;
        debug("< %s", $response->status_line) or say sprintf "< %s", $response->status_line;
    }
    if ($response->is_success) {



( run in 2.434 seconds using v1.01-cache-2.11-cpan-140bd7fdf52 )