The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -w

# Copyright 2009, 2010, 2012 Kevin Ryde

# This file is part of Upfiles.
#
# Upfiles is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3, or (at your option) any later
# version.
#
# Upfiles is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with Upfiles.  If not, see <http://www.gnu.org/licenses/>.

use 5.010;
use strict;
use warnings;
use Data::Dumper;

# uncomment this to run the ### lines
#use Smart::Comments;

{
  require Tie::StdHandle;
  tie *FH, 'Tie::StdHandle', '/etc/TextConfig.orig';
  print "size ",-s FH // '[undef]',"\n";
  print "tell ",tell(FH) // '[undef]',"\n";
  print "tied ",tied(*FH) // '[undef]',"\n";
  exit 0;
}

{
  require Tie::StdHandle;
  require Symbol;
  my $fh = Symbol::gensym();
  tie *$fh, 'Tie::StdHandle';
  open $fh, '<', '/etc/TextConfig.orig' or die "$!";
  print "size ",-s $fh // '[undef]',"\n";
  print "tell ",tell($fh) // '[undef]',"\n";
  print "tied ",tied(*$fh) // '[undef]',"\n";
  exit 0;
}

{
  {
    package App::Upfiles::Tie::Handle::Throttle;
    use strict;
    use warnings;
    use Time::HiRes;
    use List::Util 'min';
    use Hash::Util::FieldHash 'register','id';
    use Tie::StdHandle;

    our $VERSION = 4;
    our @ISA = ('Tie::StdHandle');

    Hash::Util::FieldHash::fieldhashes (\(my %blocksize),
                                        \(my %period),
                                        \(my %upto),
                                        \(my %last_time));

    sub TIEHANDLE {
      my ($class, %options) = shift;

      my $self    = $class->SUPER::TIEHANDLE;
      my $id = id $self;
      register($self, \%blocksize, \%period, \%upto, \%last_time);
      $blocksize{$id} = $options{'blocksize'} || 4096;
      $period{$id}    = $options{'period'} || 4096;
      $upto{$id}      = 0;
      $last_time{$id} = Time::HiRes::time();
      return $self;


      # \do { local *HANDLE};
      # bless $self,$class;
      # 
      # return bless { blocksize => 4096,
      #                period    => 1,
      #                upto      => 0,
      #                last_time =>
      #                @_ }, $class;
    }
    # sub OPEN {
    #   my ($self) = @_;
    #   if ($self->{'fh'}) {
    #     $self->CLOSE;
    #   }
    #   return (@_ == 2
    #           ? open($self->{'fh'}, $_[1])
    #           : open($self->{'fh'}, $_[1], $_[2]));
    # }
    # sub CLOSE {
    #   my ($self) = @_;
    #   return close ($self->{'fh'});
    # }
    # sub EOF     {
    #   my ($self) = @_;
    #   return eof($self->{'fh'});
    # }
    # sub TELL    {
    #   my ($self) = @_;
    #   return tell($self->{'fh'});
    # }
    # sub FILENO  {
    #   my ($self) = @_;
    #   return fileno($self->{'fh'});
    # }
    # sub SEEK    {
    #   my ($self) = @_;
    #   return seek($self->{'fh'},$_[1],$_[2]);
    # }
    # sub BINMODE {
    #   my ($self) = @_;
    #   return binmode($self->{'fh'});
    # }

    sub READ {
      my $self = $_[0];
      my $len = $_[2];
      $self->choke;
      my $remaining = $self->{'blocksize'} - $self->{'upto'};
      $len = min ($len, $remaining);
      my $ret = read($self,$_[1],$len);
      if (defined $ret) {
        $self->{'upto'} += $ret;
      }
      return $ret;
    }

    sub READLINE {
      my ($self) = @_;
      $self->choke;
      my $fh = $self; # ->{'fh'};
      my $ret = <$fh>;
      if (defined $ret) {
        $self->{'upto'} += length($ret);
      }
      return $ret;
    }
    sub GETC {
      my ($self) = @_;
      $self->choke;
      my $ret = getc($self);
      if (defined $ret) {
        $self->{'upto'}++;
      }
      return $ret;
    }

    sub choke {
      my ($self) = @_;
      my $remaining = $self->{'blocksize'} - $self->{'upto'};
      if ($remaining <= 0) {
        my $now = Time::HiRes::time();
        my $sleep = $self->{'last_time'} + $self->{'period'} - $now;
        ### $sleep
        if ($sleep > 0 && $sleep < 5) {
          Time::HiRes::sleep ($sleep);
        }
        $self->{'upto'} = 0;
        $self->{'last_time'} = $now;
      }
    }
    sub WRITE {
      die;
    }
  }

  print "VERSION ",App::Upfiles::Tie::Handle::Throttle->VERSION,"\n";
  print "isa ",App::Upfiles::Tie::Handle::Throttle->isa('Tie::Handle'),"\n";

  require Symbol;
  my $fh = Symbol::gensym();
  tie *$fh, 'App::Upfiles::Tie::Handle::Throttle',
    blocksize => 64,
    period => 2;
  $| = 1;
  open $fh, '<', '/etc/TextConfig.orig' or die "$!";
  print "size ",-s $fh // '[undef]',"\n";

  while (read ($fh, my $buf, 3)) {
    Time::HiRes::sleep (3/(64/3));
    print $buf;
  }
  # while (defined (my $c = getc $fh)) {
  #   print $c;
  # }
  # while (defined (my $line = <$fh>)) {
  #   print $line;
  # }
  exit 0;
}

{
  my $local = \*main::STDOUT;
  print $local // 'undef',"\n";
  print Data::Dumper->new([\$local],['local'])->Sortkeys(1)->Dump;

  my $localfd = ref($local) || ref(\$local) eq "GLOB";
  print Data::Dumper->new([\$localfd],['localfd'])->Sortkeys(1)->Dump;
}