--- /dev/null
+MYMETA.json
+MYMETA.yml
+Makefile
+blib
+pm_to_blib
--- /dev/null
+Revision history for Perl extension IndexData::Utils.
+
+0.01 Mon Dec 8 12:18:26 2014
+ - original version; created by h2xs 1.23 with options
+ -X IndexData::Utils
+
--- /dev/null
+Changes
+MANIFEST
+Makefile.PL
+README
+lib/IndexData/Utils.pm
+lib/IndexData/Utils/PersistentCounter.pm
+t/01-IndexData-Utils.t
+t/02-IndexData-Utils-PersistentCounter.t
--- /dev/null
+use 5.018002;
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ NAME => 'IndexData::Utils',
+ VERSION_FROM => 'lib/IndexData/Utils.pm', # finds $VERSION
+ PREREQ_PM => {}, # e.g., Module::Name => 1.1
+ ($] >= 5.005 ? ## Add these new keywords supported since 5.005
+ (ABSTRACT_FROM => 'lib/IndexData/Utils.pm', # retrieve abstract from module
+ AUTHOR => 'Mike Taylor <mike@indexdata.com>') : ()),
+);
--- /dev/null
+IndexData-Utils
+===============
+
+This library contains utility functions that we at Index Data want to
+share between multiple applications -- for example, the persistent
+counter used by both IRSpy (see issue IR-350) and MKHome (IR-351).
+
+
+INSTALLATION
+
+To install this module type the following:
+
+ perl Makefile.PL
+ make
+ make test
+ make install
+
+COPYRIGHT AND LICENCE
+
+Copyright (C) 2014 by Index Data.
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.8.4 or,
+at your option, any later version of Perl 5 you may have available.
+
+
--- /dev/null
+package IndexData::Utils;
+
+use 5.018002;
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+
+use IndexData::Utils::PersistentCounter;
+
+1;
+__END__
+
+
+=head1 NAME
+
+IndexData::Utils - Utility Perl extension for Index Data applications
+
+=head1 SYNOPSIS
+
+ use IndexData::Utils;
+ # Use functions from the various submodules.
+
+=head1 DESCRIPTION
+
+This library contains utility functions that we at Index Data want to
+share between multiple applications -- for example, the persistent
+counter used by both IRSpy (see issue IR-350) and MKHome (IR-351).
+
+=head1 SEE ALSO
+
+IndexData::Utils::PersistentCounter
+
+=head1 AUTHOR
+
+Mike Taylor, E<lt>mike@indexdata.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2014 by Index Data.
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.8.4 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
--- /dev/null
+package IndexData::Utils::PersistentCounter;
+
+use 5.018002;
+use strict;
+use warnings;
+
+use IO::File;
+
+
+=head1 NAME
+
+IndexData::Utils::PersistentCounter - Perl extension imnlementing persistent counters
+
+=head1 SYNOPSIS
+
+ use IndexData::Utils::PersistentCounter;
+ $counter = new IndexData::Utils::PersistentCounter($file, 1);
+ $n = $counter->next();
+ $n = $counter->next();
+ # ...
+ $n = $counter->delete();
+
+=head1 DESCRIPTION
+
+This library provides a simple persistent counter class for
+maintaining a counter on disk across multiple runs of a program. It is
+safe against multiple concurrent accesses (i.e. will not issue the
+same value twice to different processes). It can be used for
+applications such as generating unique record IDs.
+
+=head1 METHODS
+
+=head2 new()
+
+ $old = new IndexData::Utils::PersistentCounter($file1);
+ $new = new IndexData::Utils::PersistentCounter($file2, 1);
+
+Creates a new counter object associated with a file which contains the
+persistent state of the counter. The purpose of the counter is to
+return consecutive integers on consecutive calls, even if those calls
+are made from multiple concurrent processes. The file stores the state
+across invocations.
+
+In the usual case (no second argument), the file must already exist;
+if it does not, it is not created, but an undefined value is returned.
+
+If a second argument is provided and its value is true, then a new
+counter file is created with initial value 1. Note that B<this will
+overwrite any existing file>, so use with caution.
+
+=cut
+
+sub new {
+ my $class = shift();
+ my($file, $create) = @_;
+
+ if (! -f $file) {
+ return undef if !$create;
+ # ### There is a bit of a race condition here, but it's not
+ # something that's going to crop up in real life.
+ my $fh = new IO::File(">$file") || return undef;
+ $fh->print("1\n");
+ $fh->close() or return undef;
+ }
+
+ my $this = bless {
+ file => $file,
+ }, $class;
+
+ return $this;
+}
+
+
+=head2 next()
+
+ $n = $counter->next();
+
+Returns the next available integer from the specified counter, and
+increments the counter ready for the next invocation (whether that
+invocation is in this process or a different one).
+
+The first call of C<next()> on a newly created counter returns 1, not
+0. Each subsequent call returns a value one higher than the previous
+call.
+
+=cut
+
+sub next {
+ my $this = shift();
+
+ my $fh = new IO::File('+<' . $this->{file}) || return undef;
+ flock($fh, 2) || die "can't lock file";
+ my $n = <$fh>;
+ $fh->seek(0, 0);
+ $fh->print($n+1, "\n");
+ $fh->close() or return undef;
+ return $n+0;
+}
+
+
+=head2 delete()
+
+ $ok = $counter->delete();
+
+Permanently deletes a counter file. Returns true if the deletion was
+successful, false otherwise.
+
+=cut
+
+sub delete {
+ my $this = shift();
+
+ unlink $this->{file} or return 0;
+ return 1;
+}
+
+
+=head1 SEE ALSO
+
+IndexData::Utils
+
+=head1 AUTHOR
+
+Mike Taylor, E<lt>mike@indexdata.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2014 by Index Data.
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.8.4 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
+
+1;
--- /dev/null
+use strict;
+use warnings;
+
+use Test::More tests => 1;
+BEGIN { use_ok('IndexData::Utils') };
+
+# Nothing more to test for this, unless perhaps that the submodules
+# have been loaded.
--- /dev/null
+use strict;
+use warnings;
+
+use Test::More tests => 17;
+BEGIN { use_ok('IndexData::Utils::PersistentCounter') };
+
+my $file = "/tmp/id-u-pc-$$";
+my $counter = new IndexData::Utils::PersistentCounter($file);
+ok(!defined $counter, "can't open non-existent counter");
+
+$counter = new IndexData::Utils::PersistentCounter("/x/$file", 1);
+ok(!defined $counter, "can't create counter in silly place");
+
+$counter = new IndexData::Utils::PersistentCounter($file, 1);
+my $detail = defined $counter ? '' : ": $!@";
+ok(defined $counter, "created new counter$detail");
+
+foreach my $i (1..5) {
+ my $n = $counter->next();
+ ok(defined $n, "n is defined");
+ ok($n == $i, "n has correct value $i");
+}
+
+# Three processes making five accesses each
+for (my $i = 0; $i < 3; $i ++) {
+ my $pid = fork();
+ if ($pid == 0) {
+ # child
+ foreach my $j (1..5) {
+ my $n = $counter->next();
+ print "# child ", $i+1, ", access ", $j+1, ": value is $n\n";
+ }
+ exit 0;
+ } else {
+ print "# process $pid started\n";
+ }
+}
+
+while ((my $pid = wait()) > 0) {
+ print "# process $pid completed\n";
+}
+
+my $n = $counter->next();
+ok($n == 21, "n == 21 on 21 total access (n=$n)");
+
+my $ok = $counter->delete();
+ok($ok, "deleted counter file");
+$counter = new IndexData::Utils::PersistentCounter($file);
+ok(!defined $counter, "can't open deleted counter");