Apophis
view release on metacpan or search on metacpan
PLAN.md
ppport.h
README
t/00-load.t
t/01-identify.t
t/02-namespace.t
t/03-store-fetch.t
t/04-sharding.t
t/05-exists-remove.t
t/06-verify.t
t/07-streaming.t
t/08-bulk.t
t/09-metadata.t
t/10-edge-cases.t
t/11-custom-ops.t
t/manifest.t
t/pod-coverage.t
t/pod.t
META.yml Module YAML meta-data (added by MakeMaker)
META.json Module JSON meta-data (added by MakeMaker)
+--------------------------+
| Apophis.pm |
| XSLoader only, no logic |
+-----------+--------------+
|
+-----------+--------------+
| Apophis.xs |
| ALL logic in C/XS: |
| - new() constructor |
| - identify / identify_ |
| file (streaming) |
| - store / fetch / |
| exists / remove |
| - path_for / verify |
| - store_many / |
| find_missing |
| - metadata (JSON in C) |
+-----------+--------------+
|
+-----------+--------------+
| Horus (C headers) |
| horus_uuid_v5() |
| horus_sha1_*() streaming |
| horus_parse_uuid() |
| horus_format_uuid() |
+--------------------------+
```
## File Layout
```
Apophis/
Makefile.PL
Apophis.pm # XSLoader + POD only
Apophis.xs # ALL logic: OO interface, file I/O, hashing, storage
t/
00-load.t
01-identify.t # determinism, format, different content
02-namespace.t # namespace isolation
03-store-fetch.t # round-trip, dedup, atomic write
04-sharding.t # path_for correctness
05-exists-remove.t # existence check, removal + .meta cleanup
06-verify.t # integrity verification
07-streaming.t # identify_file matches identify for same content
08-bulk.t # store_many, find_missing
09-metadata.t # metadata write/read
10-edge-cases.t # empty content, binary with nulls, unicode
```
## XS Internal C Functions
### `apophis_parse_ns_uuid(ns_hex) -> 16 bytes`
Uses `horus_parse_uuid()` to convert a 36-char namespace UUID string back to 16 raw bytes.
- Tests: t/01-identify.t, t/02-namespace.t
### Phase 3: Storage
- path_for(), apophis_mkdir_p(), apophis_store_file()
- store(), fetch(), exists(), remove()
- Tests: t/03-store-fetch.t, t/04-sharding.t, t/05-exists-remove.t
### Phase 4: Streaming
- apophis_identify_stream() with PerlIO_read in 64KB chunks
- identify_file()
- Tests: t/07-streaming.t
### Phase 5: Integrity + Bulk
- verify(), store_many(), find_missing()
- Tests: t/06-verify.t, t/08-bulk.t
### Phase 6: Metadata
- apophis_meta_write(), apophis_meta_read()
- Integration into store/remove
- Tests: t/09-metadata.t
lib/Apophis.pm view on Meta::CPAN
use Apophis;
my $ca = Apophis->new(
namespace => 'myapp-files',
store_dir => '/var/store',
);
# Identify content â deterministic UUID v5
my $id = $ca->identify(\$content);
my $id = $ca->identify_file('/path/to/large-file'); # streaming, O(1) memory
# Store (atomic write, CAS dedup)
my $id = $ca->store(\$content);
# Retrieve
my $data = $ca->fetch($id);
# Check / remove
if ($ca->exists($id)) { ... }
$ca->remove($id);
lib/Apophis.xs view on Meta::CPAN
#define apophis_stat_t Stat_t
/* Horus UUID library - pure C, no Perl deps */
#define HORUS_FATAL(msg) croak("%s", (msg))
#include "horus_core.h"
/* ------------------------------------------------------------------ */
/* Constants */
/* ------------------------------------------------------------------ */
#define APOPHIS_STREAM_BUF 65536 /* 64KB read chunks for streaming */
#define APOPHIS_PATH_MAX 4096
/* ------------------------------------------------------------------ */
/* Internal: namespace UUID generation */
/* ------------------------------------------------------------------ */
/* Derive a namespace UUID from a human-readable string via v5(DNS, name) */
static void
apophis_derive_namespace(unsigned char *ns_out,
const char *name, STRLEN name_len)
lib/Apophis.xs view on Meta::CPAN
/* Identify in-memory content: v5(namespace, content) */
static void
apophis_identify_content(unsigned char *uuid_out,
const unsigned char *ns_bytes,
const char *content, STRLEN content_len)
{
horus_uuid_v5(uuid_out, ns_bytes,
(const unsigned char *)content, (size_t)content_len);
}
/* Identify via streaming SHA-1 â O(1) memory */
static void
apophis_identify_stream(pTHX_ unsigned char *uuid_out,
const unsigned char *ns_bytes,
PerlIO *fh)
{
horus_sha1_ctx ctx;
unsigned char buf[APOPHIS_STREAM_BUF];
unsigned char digest[20];
SSize_t nread;
lib/Apophis.xs view on Meta::CPAN
PUTBACK;
return NORMAL;
}
/*
* pp_apophis_verify - Custom op: fused re-read + re-hash + compare
*
* Stack input: self_sv, id_sv
* Stack output: bool_sv
*
* Fuses: path computation + open + streaming SHA-1 + format + memcmp.
*/
static OP *
pp_apophis_verify(pTHX) {
dSP;
SV *id_sv = POPs;
SV *self_sv = POPs;
HV *hv;
const unsigned char *ns;
const char *store_dir;
STRLEN store_dir_len;
lib/Apophis.xs view on Meta::CPAN
}
SvCUR_set(content, (STRLEN)nread);
*SvEND(content) = '\0';
RETVAL = newRV_noinc(content);
}
OUTPUT:
RETVAL
# op_verify($self, $id) -> bool
# Fused read + streaming SHA-1 + compare â single call.
bool
op_verify(self, id)
SV *self
SV *id
PREINIT:
HV *hv;
const unsigned char *ns;
const char *store_dir;
STRLEN store_dir_len;
t/07-streaming.t view on Meta::CPAN
use strict;
use warnings;
use Test::More tests => 4;
use File::Temp qw(tempfile tempdir);
use Apophis;
my $ca = Apophis->new(namespace => 'test-streaming');
# identify_file matches identify for same content
my $content = 'streaming test content here';
my ($fh, $filename) = tempfile(UNLINK => 1);
binmode $fh;
print $fh $content;
close $fh;
my $id_mem = $ca->identify(\$content);
my $id_file = $ca->identify_file($filename);
is($id_mem, $id_file, 'identify_file matches identify for same content');
# Large content (> one 64KB buffer)
my $large = 'x' x 100_000;
my ($fh2, $filename2) = tempfile(UNLINK => 1);
binmode $fh2;
print $fh2 $large;
close $fh2;
my $id_large_mem = $ca->identify(\$large);
my $id_large_file = $ca->identify_file($filename2);
is($id_large_mem, $id_large_file, 'streaming matches in-memory for large content');
# Binary content
my $binary = join('', map { chr($_) } 0..255) x 100;
my ($fh3, $filename3) = tempfile(UNLINK => 1);
binmode $fh3;
print $fh3 $binary;
close $fh3;
my $id_bin_mem = $ca->identify(\$binary);
my $id_bin_file = $ca->identify_file($filename3);
is($id_bin_mem, $id_bin_file, 'streaming matches in-memory for binary content');
# Empty file
my ($fh4, $filename4) = tempfile(UNLINK => 1);
close $fh4;
my $empty = '';
my $id_empty_mem = $ca->identify(\$empty);
my $id_empty_file = $ca->identify_file($filename4);
is($id_empty_mem, $id_empty_file, 'streaming matches in-memory for empty content');
( run in 0.795 second using v1.01-cache-2.11-cpan-140bd7fdf52 )