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 )