Apophis

 view release on metacpan or  search on metacpan

MANIFEST  view on Meta::CPAN

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)

PLAN.md  view on Meta::CPAN

                    +--------------------------+
                    |       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

PLAN.md  view on Meta::CPAN

    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.

PLAN.md  view on Meta::CPAN

- 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 )