EV-Telegram-TDLib

 view release on metacpan or  search on metacpan

lib/EV/Telegram/TDLib/Files.pm  view on Meta::CPAN

package EV::Telegram::TDLib::Files;

use strict;
use warnings;

our $VERSION = '0.04';

=head1 NAME

EV::Telegram::TDLib::Files - file methods for EV::Telegram::TDLib

=head1 DESCRIPTION

One of the mixins L<EV::Telegram::TDLib> inherits from. It has no
interface of its own and is not meant to be used directly: loading the
main module loads this one, and its methods are called on a client.

They are documented together with the rest of the API, under
L<EV::Telegram::TDLib/"Files mixin">.

=cut

sub CLONE_SKIP { 1 }

our %UPDATES = (
    updateFile => \&update_file,
);

sub downloads { $_[0]{cache}{downloads} ||= {} }

sub uploads { $_[0]{cache}{uploads} ||= {} }


sub update_file {
    my ($self, $obj) = @_;
    my $file = $obj->{file} or return;
    # an id-less file would key both registries under the empty string and
    # collide with a later lookup
    return unless defined $file->{id};
    # TDLib re-sends updateFile for reasons unrelated to our download, so
    # only registered ids are followed and a completed one is dropped at once.
    # One update can reach both registries for the same id, so every callback
    # is guarded: a die in an earlier one would strand the other's watcher.
    if (my $dl = $self->downloads->{ $file->{id} }) {
        if (my $cb = $dl->{on_progress}) { $self->guarded($cb, $file) }
        my $local = $file->{local};
        if ($local && $local->{is_downloading_completed}) {
            delete $self->downloads->{ $file->{id} };
            $self->guarded($dl->{cb}, $file, undef);
        }
        elsif ($local && $local->{is_downloading_active}) {
            $dl->{started} = 1;
        }
        # started, then neither active nor completed: a permanent failure,
        # signalled only via updateFile; an inactive update before the
        # start is just the file's current state, not a failure
        elsif ($local && $dl->{started}) {
            delete $self->downloads->{ $file->{id} };
            $self->guarded($dl->{cb}, undef,
                            { '@type' => 'error', code => -1,
                              message => 'download failed' });
        }
    }
    if (my $up = $self->uploads->{ $file->{id} }) {
        my $remote = $file->{remote} || {};
        $up->{started} = 1 if $remote->{is_uploading_active};
        # completed, or stopped after it had started: either way the last
        # update this watcher will ever get. Only completion used to count, so
        # every failed or cancelled upload left its watcher behind for good.
        # An inactive update before the start is the file's current state, as
        # for downloads.
        my $last = $up->{last} = $remote->{is_uploading_completed}
                || ($up->{started} && !$remote->{is_uploading_active});
        $self->guarded($up->{cb}, $file);
        # read the registry directly: the callback may have closed the client,
        # which drops it, or registered this id again, which is not ours
        my $reg = $self->{cache}{uploads};
        delete $reg->{ $file->{id} }
            if $last && $reg && ($reg->{ $file->{id} } // 0) == $up;
    }
}

sub download {
    my ($self, @args) = @_;
    # a trailing on_progress => $p is the option, not the callback
    my $cb = ref $args[-1] eq 'CODE' && ($args[-2] // '') ne 'on_progress'
           ? pop @args : sub {};
    my ($file_id, @rest) = @args;
    need('file_id', $file_id);
    my %opt = opts(@rest);
    my $id = 0 + $file_id;
    # one registration per file id: overwriting would silently drop the
    # first caller's callback, so the second call fails instead --
    # synchronously, like a parse_mode error, since nothing is sent



( run in 0.457 second using v1.01-cache-2.11-cpan-57ff5998125 )