DarkPAN-Indexer

 view release on metacpan or  search on metacpan

lib/DarkPAN/Indexer/Storage.pm  view on Meta::CPAN

use Role::Tiny;

# --- construction -----------------------------------------------------
#
# new($class, $entry)
#   Build an engine from a merged config entry (see the config schema).
#   Each engine reads its own settings from $entry->{storage}; the S3
#   engine additionally honors a legacy $entry->{AWS} block, storage
#   winning on collision. Other engines are greenfield -- storage only.
requires 'new';

# --- enumeration (domain-aware, backend-specific) ---------------------
#
# list_distributions() -> list of keys (relative to the storage root)
#   that are distribution tarballs. This is the ONE seam method that
#   legitimately understands the tree: it decides what counts as a
#   distribution (e.g. the .tar.gz filter) and where to scan. Everything
#   else here is dumb byte movement.
requires 'list_distributions';

# --- object primitives (generic, key-in / bytes-out) ------------------
#
# fetch_object($key) -> raw bytes, or undef if the key is genuinely
#   absent. A real I/O error throws (fail-fast). Keys are always
#   relative to the storage root; the engine resolves them (bucket+key,
#   root+path, etc).
requires 'fetch_object';

# save_object($key, $bytes, %opts) -> writes bytes at $key. %opts may
#   carry content_type. Content is already in final form (the domain
#   layer gzips before calling); this moves bytes only. Throws on failure.
requires 'save_object';

# has_object($key) -> boolean. Existence check (the head-object case).
requires 'has_object';

# --- mutual exclusion -------------------------------------------------
#
# lock($key, %opts) -> a GUARD object on success, false on failure.
#   The guard is true, and releases the lock in DESTROY when it leaves
#   scope (including on die). Callers MUST bind it to a lexical for the
#   whole critical section:
#
#       my $guard = $storage->lock($key, ttl => 300, wait => 60)
#         or croak 'could not acquire lock';
#       ... fetch -> mutate -> save ...
#       # $guard drops here -> release
#
#   Discarding the return releases immediately and defeats the mutex.
#   %opts: ttl (seconds a held lock stays fresh), wait (seconds to block
#   trying to acquire; 0 = don't wait). Mechanism is engine-specific
#   (S3 conditional-PUT vs flock); the contract above is not.
requires 'lock';

# --- read path --------------------------------------------------------
#
# base_url() -> the public base URL from which a read-only consumer (the
#   resolver) reaches this DarkPAN, or undef if it has none (e.g. a
#   purely local store with no web front). The resolver fetches
#   "{base_url}/{index path}". This is derived from config (`url`), not
#   from the storage backend's own endpoint -- an S3 store fronted by
#   CloudFront reads from the CloudFront domain, not the bucket.
requires 'base_url';

########################################################################
sub _slurp {
########################################################################
  my ( $self, $file ) = @_;

  local $RS = undef;

  my $content;

  open my $fh, '<', $file
    or croak "ERROR: could not open $file for reading\n$OS_ERROR";

  binmode $fh;

  $content = <$fh>;
  close $fh;

  return $content;
}

########################################################################
sub publish_index {
########################################################################
  my ( $self, $temp_index, $index ) = @_;

  my $uncompressed_content = $self->_slurp($temp_index);
  my $content              = q{};

  my $is_zipped = $index =~ /[.]gz$/xsm;

  if ($is_zipped) {
    require IO::Compress::Gzip;

    IO::Compress::Gzip::gzip( \$uncompressed_content, \$content )
      or croak "ERROR: could not gzip $temp_index\n";
  }
  else {
    $content = $uncompressed_content;
  }

  $self->save_object( $index, $content, content_type => $is_zipped ? 'application/gzip' : 'application/octet-stream' );

  return;
}

########################################################################
sub retrieve_index {
########################################################################
  my ( $self, $index ) = @_;

  my $is_zipped = $index =~ /[.]gz$/xsm;

  my $compressed_content = $self->fetch_object($index);
  my $content            = q{};

  if ($is_zipped) {
    require IO::Uncompress::Gunzip;



( run in 0.641 second using v1.01-cache-2.11-cpan-cb04b646d8c )