Chandra

 view release on metacpan or  search on metacpan

include/chandra/chandra_store.h  view on Meta::CPAN


#ifdef CHANDRA_XS_IMPLEMENTATION

/* ============================================================================
 * JSON singleton
 * ============================================================================ */

static SV *_store_json_obj = NULL;

static SV *
chandra_store_get_json(pTHX)
{
    if (!_store_json_obj || !SvOK(_store_json_obj)) {
        _store_json_obj = eval_pv(
            "require Cpanel::JSON::XS;"
            "Cpanel::JSON::XS->new->utf8->pretty(1)->canonical(1)->allow_nonref",
            TRUE
        );
        SvREFCNT_inc_simple_void(_store_json_obj);
    }
    return _store_json_obj;
}

static SV *
chandra_store_encode(pTHX_ SV *val)
{
    dSP;
    SV *json = chandra_store_get_json(aTHX);
    int count;
    SV *result;
    ENTER; SAVETMPS;
    PUSHMARK(SP);
    XPUSHs(json);
    XPUSHs(val);
    PUTBACK;
    count = call_method("encode", G_SCALAR | G_EVAL);
    SPAGAIN;
    result = (count > 0 && !SvTRUE(ERRSV))
        ? newSVsv(POPs) : newSVpvs("{}");
    if (SvTRUE(ERRSV)) sv_setpvs(ERRSV, "");
    PUTBACK; FREETMPS; LEAVE;
    return result;
}

/* Returns new HV* on success, NULL on corrupt (emits warn) */
static HV *
chandra_store_decode(pTHX_ SV *json_sv, const char *path)
{
    dSP;
    SV *json = chandra_store_get_json(aTHX);
    int count;
    HV *result = NULL;
    ENTER; SAVETMPS;
    PUSHMARK(SP);
    XPUSHs(json);
    XPUSHs(json_sv);
    PUTBACK;
    count = call_method("decode", G_SCALAR | G_EVAL);
    SPAGAIN;
    if (count > 0 && !SvTRUE(ERRSV)) {
        SV *decoded = POPs;
        if (SvOK(decoded) && SvROK(decoded)
                && SvTYPE(SvRV(decoded)) == SVt_PVHV) {
            result = (HV *)SvRV(decoded);
            SvREFCNT_inc_simple_void((SV *)result);
        }
    }
    if (SvTRUE(ERRSV)) sv_setpvs(ERRSV, "");
    PUTBACK; FREETMPS; LEAVE;
    if (!result)
        warn("Chandra::Store: corrupt store '%s', starting fresh\n",
             path ? path : "unknown");
    return result;
}

/* ============================================================================
 * mkdir -p in C
 * ============================================================================ */

static int
chandra_store_mkdirp(const char *path, int mode)
{
    char tmp[4096];
    char *p;
    size_t len;

#ifdef _WIN32
    /* Use Win32 API to avoid Perl's stat/mkdir macro redefinitions */
    DWORD attr = GetFileAttributesA(path);
    if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY))
        return 0;
#else
    {
        struct stat st;
        if (stat(path, &st) == 0) return 0;
    }
#endif

    strncpy(tmp, path, sizeof(tmp) - 1);
    tmp[sizeof(tmp) - 1] = '\0';
    len = strlen(tmp);
    if (len > 0 && tmp[len - 1] == '/') tmp[len - 1] = '\0';
#ifdef _WIN32
    /* Also handle backslash separators */
    if (len > 0 && tmp[len - 1] == '\\') tmp[len - 1] = '\0';
#endif

    for (p = tmp + 1; *p; p++) {
#ifdef _WIN32
        if (*p == '/' || *p == '\\') {
#else
        if (*p == '/') {
#endif
            *p = '\0';
#ifdef _WIN32
            {
                DWORD a = GetFileAttributesA(tmp);
                if (a == INVALID_FILE_ATTRIBUTES || !(a & FILE_ATTRIBUTE_DIRECTORY))
                    if (!CreateDirectoryA(tmp, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
                        return -1;
            }
#else
            {
                struct stat st;

include/chandra/chandra_store.h  view on Meta::CPAN

    fclose(fh);

    SvREFCNT_dec(json_sv);

#ifdef _WIN32
    if (!MoveFileExA(tmp_path, path, MOVEFILE_REPLACE_EXISTING))
        croak("Chandra::Store: cannot rename '%s' to '%s': error %lu\n",
              tmp_path, path, (unsigned long)GetLastError());
#else
    if (rename(tmp_path, path) != 0)
        croak("Chandra::Store: cannot rename '%s' to '%s': %s\n",
              tmp_path, path, strerror(errno));
#endif
}

static void
chandra_store_reload_c(pTHX_ HV *self_hv)
{
    SV **path_svp = hv_fetchs(self_hv, "_path", 0);
    const char *path;
    FILE *fh;
    int fd;
    long size;
    char *buf;
    SV *json_sv;
    HV *new_data;

    if (!path_svp || !SvOK(*path_svp)) return;
    path = SvPV_nolen(*path_svp);

    fh = fopen(path, "rb");
    if (!fh) return;  /* File doesn't exist yet - start fresh */

    fd = fileno(fh);
    flock(fd, LOCK_SH);

    fseek(fh, 0, SEEK_END);
    size = ftell(fh);
    fseek(fh, 0, SEEK_SET);

    if (size <= 0) {
        flock(fd, LOCK_UN);
        fclose(fh);
        return;
    }

    Newx(buf, size + 1, char);
    fread(buf, 1, (size_t)size, fh);
    buf[size] = '\0';

    flock(fd, LOCK_UN);
    fclose(fh);

    json_sv = sv_2mortal(newSVpvn(buf, (STRLEN)size));
    Safefree(buf);

    new_data = chandra_store_decode(aTHX_ json_sv, path);
    if (new_data) {
        SV **old_svp = hv_fetchs(self_hv, "_data", 0);
        if (old_svp && SvROK(*old_svp)) {
            /* Replace _data with freshly decoded HV */
            (void)hv_stores(self_hv, "_data", newRV_noinc((SV *)new_data));
        } else {
            (void)hv_stores(self_hv, "_data", newRV_noinc((SV *)new_data));
        }
    }
    /* If new_data is NULL, we leave _data as-is (start fresh, already set in new) */
}

/* ============================================================================
 * Constructor helper - build default path from name
 * ============================================================================ */

static SV *
chandra_store_default_path(pTHX_ const char *name)
{
    const char *home = getenv("HOME");
    SV *path_sv;

#ifdef _WIN32
    if (!home || !*home) {
        home = getenv("USERPROFILE");
    }
    if (!home || !*home) {
        home = getenv("APPDATA");
    }
    if (!home || !*home) {
        home = "C:\\\\";
    }
#else
    if (!home || !*home) {
        /* Fall back to getpwuid */
        struct passwd *pw = getpwuid(getuid());
        home = pw ? pw->pw_dir : "/tmp";
    }
#endif

    path_sv = newSVpvf("%s/.chandra/%s/store.json", home, name);
    return path_sv;
}

#endif /* CHANDRA_XS_IMPLEMENTATION */
#endif /* CHANDRA_STORE_H */



( run in 0.626 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )