[27518] trunk/base/src
source_changes at macosforge.org
source_changes at macosforge.org
Mon Aug 6 13:12:22 PDT 2007
Revision: 27518
http://trac.macosforge.org/projects/macports/changeset/27518
Author: sfiera at macports.org
Date: 2007-08-06 13:12:22 -0700 (Mon, 06 Aug 2007)
Log Message:
-----------
Committing registry2.0 to local branch
Added Paths:
-----------
trunk/base/src/registry2.0/
trunk/base/src/registry2.0/Makefile
trunk/base/src/registry2.0/centry.c
trunk/base/src/registry2.0/centry.h
trunk/base/src/registry2.0/creg.c
trunk/base/src/registry2.0/creg.h
trunk/base/src/registry2.0/entry.c
trunk/base/src/registry2.0/entry.h
trunk/base/src/registry2.0/entryobj.c
trunk/base/src/registry2.0/entryobj.h
trunk/base/src/registry2.0/graph.c
trunk/base/src/registry2.0/graph.h
trunk/base/src/registry2.0/graphobj.c
trunk/base/src/registry2.0/graphobj.h
trunk/base/src/registry2.0/item.c
trunk/base/src/registry2.0/item.h
trunk/base/src/registry2.0/itemobj.c
trunk/base/src/registry2.0/itemobj.h
trunk/base/src/registry2.0/registry.c
trunk/base/src/registry2.0/registry.h
trunk/base/src/registry2.0/sql.c
trunk/base/src/registry2.0/sql.h
trunk/base/src/registry2.0/tests/
trunk/base/src/registry2.0/tests/common.tcl
trunk/base/src/registry2.0/tests/entry.tcl
trunk/base/src/registry2.0/tests/item.tcl
trunk/base/src/registry2.0/util.c
trunk/base/src/registry2.0/util.h
Added: trunk/base/src/registry2.0/Makefile
===================================================================
--- trunk/base/src/registry2.0/Makefile (rev 0)
+++ trunk/base/src/registry2.0/Makefile 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,15 @@
+OBJS= registry.o util.o sql.o \
+ creg.o centry.o \
+ entry.o entryobj.o
+ #graph.o graphobj.o
+SHLIB_NAME= registry${SHLIB_SUFFIX}
+INSTALLDIR= ${DESTDIR}${datadir}/macports/Tcl/registry2.0
+export MACOSX_DEPLOYMENT_TARGET=10.3
+
+include ../../Mk/macports.autoconf.mk
+include ../../Mk/macports.tea.mk
+
+.PHONY: test
+
+test:: ${SHLIB_NAME}
+ ${TCLSH} tests/entry.tcl ${SHLIB_NAME}
Added: trunk/base/src/registry2.0/centry.c
===================================================================
--- trunk/base/src/registry2.0/centry.c (rev 0)
+++ trunk/base/src/registry2.0/centry.c 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,700 @@
+/*
+ * centry.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <sqlite3.h>
+
+#include "centry.h"
+#include "creg.h"
+
+/**
+ * Concatenates `src` to string `dst`.
+ *
+ * Simple concatenation. Only guaranteed to work with strings that have been
+ * allocated with `malloc`. Amortizes cost of expanding string buffer for O(N)
+ * concatenation and such. Uses `memcpy` in favor of `strcpy` in hopes it will
+ * perform a bit better. If passing in a static string to dst, make sure
+ * dst_space starts at dst_len. Also make sure dst_space is never 0 (so don't
+ * use "" as the starter string, allocate some space);
+ */
+void reg_strcat(char** dst, int* dst_len, int* dst_space, char* src) {
+ int src_len = strlen(src);
+ if (*dst_len + src_len >= *dst_space) {
+ char* old_dst = *dst;
+ char* new_dst = malloc(*dst_space * 2 * sizeof(char));
+ *dst_space *= 2;
+ memcpy(new_dst, old_dst, *dst_len);
+ *dst = new_dst;
+ free(old_dst);
+ }
+ memcpy(&((*dst)[*dst_len]), src, src_len+1);
+ *dst_len += src_len;
+}
+
+/**
+ * Appends `src` to the list `dst`.
+ *
+ * It's like `reg_strcat`, except `src` represents an element and not a sequence
+ * of `char`s.
+ */
+static void reg_listcat(void*** dst, int* dst_len, int* dst_space, void* src) {
+ if (*dst_len == *dst_space) {
+ void** old_dst = *dst;
+ void** new_dst = malloc(*dst_space * 2 * sizeof(void*));
+ *dst_space *= 2;
+ memcpy(new_dst, old_dst, *dst_len);
+ *dst = new_dst;
+ free(old_dst);
+ }
+ (*dst)[*dst_len] = src;
+ (*dst_len)++;
+}
+
+/**
+ * Returns the operator to use for the given strategy.
+ */
+static char* reg_strategy_op(int strategy, reg_error* errPtr) {
+ switch (strategy) {
+ case 0:
+ return "=";
+ case 1:
+ return " GLOB ";
+ case 2:
+ return " REGEXP ";
+ default:
+ errPtr->code = "registry::invalid-strategy";
+ errPtr->description = "invalid matching strategy specified";
+ errPtr->free = NULL;
+ return NULL;
+ }
+}
+
+static int reg_stmt_to_entry(void* userdata, void** entry, void* stmt,
+ reg_error* errPtr UNUSED) {
+ if (sqlite3_column_type(stmt, 1) == SQLITE_NULL) {
+ reg_entry* e = malloc(sizeof(reg_entry));
+ e->db = (sqlite3*)userdata;
+ e->id = sqlite3_column_int64(stmt, 0);
+ e->saved = 0;
+ e->proc = NULL;
+ *entry = e;
+ } else {
+ *entry = *(reg_entry**)sqlite3_column_blob(stmt, 1);
+ }
+ return 1;
+}
+
+static int reg_save_addresses(sqlite3* db, reg_entry** entries,
+ int entry_count, reg_error* errPtr) {
+ sqlite3_stmt* stmt;
+ int i;
+ char* query = "INSERT INTO entries (id, address) VALUES (?, ?)";
+ /* avoid unnecessarily making queries */
+ for (i=0; i<entry_count; i++) {
+ if (!entries[i]->saved) {
+ break;
+ }
+ }
+ if (i == entry_count) {
+ return 1;
+ }
+ /* BEGIN */
+ if (sqlite3_prepare(db, query, -1, &stmt, NULL)
+ == SQLITE_OK) {
+ for (i=0; i<entry_count; i++) {
+ if (entries[i]->saved) {
+ continue;
+ }
+ if ((sqlite3_bind_int64(stmt, 1, entries[i]->id) == SQLITE_OK)
+ && (sqlite3_bind_blob(stmt, 2, &entries[i],
+ sizeof(reg_entry*), SQLITE_TRANSIENT) == SQLITE_OK)
+ && (sqlite3_step(stmt) == SQLITE_DONE)) {
+ sqlite3_reset(stmt);
+ } else {
+ sqlite3_finalize(stmt);
+ reg_sqlite_error(db, errPtr, query);
+ }
+ }
+ sqlite3_finalize(stmt);
+ return 1;
+ } else {
+ sqlite3_finalize(stmt);
+ reg_sqlite_error(db, errPtr, query);
+ }
+ return 0;
+}
+
+/**
+ * registry::entry create portname version revision variants epoch ?name?
+ *
+ * Unlike the old registry::new_entry, revision, variants, and epoch are all
+ * required. That's OK because there's only one place this function is called,
+ * and it's called with all of them there.
+ */
+reg_entry* reg_entry_create(sqlite3* db, char* name, char* version,
+ char* revision, char* variants, char* epoch, reg_error* errPtr) {
+ sqlite3_stmt* stmt;
+ char* query = "INSERT INTO registry.ports "
+ "(name, version, revision, variants, epoch) VALUES (?, ?, ?, ?, ?)";
+ if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+ && (sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC)
+ == SQLITE_OK)
+ && (sqlite3_bind_text(stmt, 2, version, -1, SQLITE_STATIC)
+ == SQLITE_OK)
+ && (sqlite3_bind_text(stmt, 3, revision, -1, SQLITE_STATIC)
+ == SQLITE_OK)
+ && (sqlite3_bind_text(stmt, 4, variants, -1, SQLITE_STATIC)
+ == SQLITE_OK)
+ && (sqlite3_bind_text(stmt, 5, epoch, -1, SQLITE_STATIC)
+ == SQLITE_OK)
+ && (sqlite3_step(stmt) == SQLITE_DONE)) {
+ char* query = "INSERT INTO entries (id, address) VALUES (?, ?)";
+ reg_entry* entry = malloc(sizeof(reg_entry));
+ entry->id = sqlite3_last_insert_rowid(db);
+ entry->db = db;
+ entry->proc = NULL;
+ sqlite3_finalize(stmt);
+ if ((sqlite3_prepare(db, query, -1, &stmt, NULL)
+ == SQLITE_OK)
+ && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)
+ && (sqlite3_bind_blob(stmt, 2, &entry, sizeof(reg_entry*),
+ SQLITE_TRANSIENT) == SQLITE_OK)
+ && (sqlite3_step(stmt) == SQLITE_DONE)) {
+ return entry;
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ }
+ free(entry);
+ return NULL;
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ return NULL;
+ }
+}
+
+reg_entry* reg_entry_open(sqlite3* db, char* name, char* version,
+ char* revision, char* variants, char* epoch, reg_error* errPtr) {
+ sqlite3_stmt* stmt;
+ char* query = "SELECT registry.ports.id, entries.address "
+ "FROM registry.ports LEFT OUTER JOIN entries USING (id) "
+ "WHERE name=? AND version=? AND revision=? AND variants=? AND epoch=?";
+ if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+ && (sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC)
+ == SQLITE_OK)
+ && (sqlite3_bind_text(stmt, 2, version, -1, SQLITE_STATIC)
+ == SQLITE_OK)
+ && (sqlite3_bind_text(stmt, 3, revision, -1, SQLITE_STATIC)
+ == SQLITE_OK)
+ && (sqlite3_bind_text(stmt, 4, variants, -1, SQLITE_STATIC)
+ == SQLITE_OK)
+ && (sqlite3_bind_text(stmt, 5, epoch, -1, SQLITE_STATIC)
+ == SQLITE_OK)) {
+ int r = sqlite3_step(stmt);
+ reg_entry* entry;
+ switch (r) {
+ case SQLITE_ROW:
+ if (reg_stmt_to_entry(db, (void**)&entry, stmt, errPtr)) {
+ sqlite3_finalize(stmt);
+ if (reg_save_addresses(db, &entry, 1, errPtr)) {
+ return entry;
+ }
+ }
+ case SQLITE_DONE:
+ errPtr->code = "registry::not-found";
+ errPtr->description = "no matching port found";
+ errPtr->free = NULL;
+ break;
+ default:
+ reg_sqlite_error(db, errPtr, query);
+ break;
+ }
+ sqlite3_finalize(stmt);
+ return NULL;
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ return NULL;
+ }
+}
+
+/**
+ * deletes an entry; still needs to be freed
+ */
+int reg_entry_delete(sqlite3* db, reg_entry* entry, reg_error* errPtr) {
+ sqlite3_stmt* stmt;
+ char* query = "DELETE FROM registry.ports WHERE id=?";
+ if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+ && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)
+ && (sqlite3_step(stmt) == SQLITE_DONE)) {
+ if (sqlite3_changes(db) > 0) {
+ sqlite3_finalize(stmt);
+ query = "DELETE FROM entries WHERE id=?";
+ if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+ && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)
+ && (sqlite3_step(stmt) == SQLITE_DONE)) {
+ sqlite3_finalize(stmt);
+ return 1;
+ }
+ } else {
+ errPtr->code = "registry::invalid-entry";
+ errPtr->description = "an invalid entry was passed";
+ errPtr->free = NULL;
+ }
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ }
+ sqlite3_finalize(stmt);
+ return 0;
+}
+
+/*
+ * Frees the entries in `entries`.
+ */
+void reg_entry_free(sqlite3* db UNUSED, reg_entry* entry) {
+ sqlite3_stmt* stmt;
+ if (entry->proc != NULL) {
+ free(entry->proc);
+ }
+ free(entry);
+ sqlite3_prepare(entry->db, "DELETE FROM entries WHERE address=?", -1, &stmt,
+ NULL);
+ sqlite3_bind_blob(stmt, 1, &entry, sizeof(reg_entry*), SQLITE_TRANSIENT);
+ sqlite3_step(stmt);
+}
+
+static int reg_all_objects(sqlite3* db, char* query, int query_len,
+ void*** objects, cast_function* fn, free_function* del,
+ reg_error* errPtr) {
+ int r;
+ reg_entry* entry;
+ void** results = malloc(10*sizeof(void*));
+ int result_count = 0;
+ int result_space = 10;
+ sqlite3_stmt* stmt;
+ if (sqlite3_prepare(db, query, query_len, &stmt, NULL) == SQLITE_OK) {
+ do {
+ int i;
+ r = sqlite3_step(stmt);
+ switch (r) {
+ case SQLITE_ROW:
+ if (fn(db, (void**)&entry, stmt, errPtr)) {
+ reg_listcat(&results, &result_count, &result_space,
+ entry);
+ continue;
+ } else {
+ int i;
+ sqlite3_finalize(stmt);
+ for (i=0; i<result_count; i++) {
+ del(NULL, results[i]);
+ }
+ free(results);
+ return -1;
+ }
+ case SQLITE_DONE:
+ break;
+ default:
+ for (i=0; i<result_count; i++) {
+ del(NULL, results[i]);
+ }
+ free(results);
+ sqlite3_finalize(stmt);
+ reg_sqlite_error(db, errPtr, query);
+ return -1;
+ }
+ } while (r != SQLITE_DONE);
+ *objects = results;
+ return result_count;
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ free(results);
+ return -1;
+ }
+}
+
+/*
+ * Searches the registry for ports for which each key's value is equal to the
+ * given value. To find all ports, pass 0 key-value pairs.
+ *
+ * Vulnerable to SQL-injection attacks in the `keys` field. Pass it valid keys,
+ * please.
+ */
+int reg_entry_search(sqlite3* db, char** keys, char** vals, int key_count,
+ int strategy, reg_entry*** entries, reg_error* errPtr) {
+ int i;
+ char* kwd = " WHERE ";
+ char* query;
+ int query_len = 32;
+ int query_space = 32;
+ int result;
+ /* get the strategy */
+ char* op = reg_strategy_op(strategy, errPtr);
+ if (op == NULL) {
+ return -1;
+ }
+ /* build the query */
+ query = strdup("SELECT registry.ports.id, entries.address "
+ "FROM registry.ports LEFT OUTER JOIN entries USING (id)");
+ for (i=0; i<key_count; i+=1) {
+ char* cond = sqlite3_mprintf("%s%s%s'%q'", kwd, keys[i], op, vals[i]);
+ reg_strcat(&query, &query_len, &query_space, cond);
+ sqlite3_free(cond);
+ kwd = " AND ";
+ }
+ /* do the query */
+ result = reg_all_objects(db, query, query_len, (void***)entries,
+ reg_stmt_to_entry, (free_function*)reg_entry_free, errPtr);
+ if (result > 0) {
+ if (!reg_save_addresses(db, *entries, result, errPtr)) {
+ free(entries);
+ return 0;
+ }
+ }
+ free(query);
+ return result;
+}
+
+/**
+ * TODO: fix this to return ports where state=active too
+ * TODO: add more arguments (epoch, revision, variants), maybe
+ */
+int reg_entry_installed(sqlite3* db, char* name, char* version,
+ reg_entry*** entries, reg_error* errPtr) {
+ char* format;
+ char* query;
+ int result;
+ char* select = "SELECT registry.ports.id, entries.address FROM "
+ "registry.ports LEFT OUTER JOIN entries USING (id)";
+ if (name == NULL) {
+ format = "%s WHERE (state='installed' OR state='active')";
+ } else if (version == NULL) {
+ format = "%s WHERE (state='installed' OR state='active') AND name='%q'";
+ } else {
+ format = "%s WHERE (state='installed' OR state='active') AND name='%q' "
+ "AND version='%q'";
+ }
+ query = sqlite3_mprintf(format, select, name, version);
+ result = reg_all_objects(db, query, -1, (void***)entries,
+ reg_stmt_to_entry, (free_function*)reg_entry_free, errPtr);
+ if (result > 0) {
+ if (!reg_save_addresses(db, *entries, result, errPtr)) {
+ free(entries);
+ return 0;
+ }
+ }
+ sqlite3_free(query);
+ return result;
+}
+
+/**
+ */
+int reg_entry_active(sqlite3* db, char* name, reg_entry*** entries,
+ reg_error* errPtr) {
+ char* format;
+ char* query;
+ int result;
+ char* select = "SELECT registry.ports.id, entries.address FROM "
+ "registry.ports LEFT OUTER JOIN entries USING (id)";
+ if (name == NULL) {
+ format = "%s WHERE state='active'";
+ } else {
+ format = "%s WHERE state='active' AND name='%q'";
+ }
+ query = sqlite3_mprintf(format, select, name);
+ result = reg_all_objects(db, query, -1, (void***)entries,
+ reg_stmt_to_entry, (free_function*)reg_entry_free, errPtr);
+ if (result > 0) {
+ if (!reg_save_addresses(db, *entries, result, errPtr)) {
+ free(entries);
+ return 0;
+ }
+ }
+ sqlite3_free(query);
+ return result;
+}
+
+int reg_entry_owner(sqlite3* db, char* path, reg_entry** entry,
+ reg_error* errPtr) {
+ sqlite3_stmt* stmt;
+ reg_entry* result;
+ char* query = "SELECT registry.files.id, entries.address "
+ "FROM registry.files LEFT OUTER JOIN entries USING (id) "
+ "WHERE path=?";
+ if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+ && (sqlite3_bind_text(stmt, 1, path, -1, SQLITE_STATIC)
+ == SQLITE_OK)) {
+ int r = sqlite3_step(stmt);
+ switch (r) {
+ case SQLITE_ROW:
+ if (reg_stmt_to_entry(db, (void**)&result, stmt, errPtr)) {
+ sqlite3_finalize(stmt);
+ if (reg_save_addresses(db, &result, 1, errPtr)) {
+ *entry = result;
+ return 1;
+ }
+ }
+ case SQLITE_DONE:
+ sqlite3_finalize(stmt);
+ *entry = NULL;
+ return 1;
+ default:
+ /* barf */
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+}
+
+int reg_entry_propget(sqlite3* db, reg_entry* entry, char* key, char** value,
+ reg_error* errPtr) {
+ sqlite3_stmt* stmt;
+ char* query = sqlite3_mprintf("SELECT %q FROM registry.ports "
+ "WHERE id=%lld", key, entry->id);
+ if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+ int r = sqlite3_step(stmt);
+ switch (r) {
+ case SQLITE_ROW:
+ *value = strdup(sqlite3_column_text(stmt, 0));
+ sqlite3_finalize(stmt);
+ return 1;
+ case SQLITE_DONE:
+ errPtr->code = "registry::invalid-entry";
+ errPtr->description = "an invalid entry was passed";
+ errPtr->free = NULL;
+ sqlite3_finalize(stmt);
+ return 0;
+ default:
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ return 0;
+ }
+}
+
+int reg_entry_propset(sqlite3* db, reg_entry* entry, char* key, char* value,
+ reg_error* errPtr) {
+ sqlite3_stmt* stmt;
+ char* query = sqlite3_mprintf("UPDATE registry.ports SET %q = '%q' "
+ "WHERE id=%lld", key, value, entry->id);
+ if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+ int r = sqlite3_step(stmt);
+ switch (r) {
+ case SQLITE_DONE:
+ sqlite3_finalize(stmt);
+ return 1;
+ default:
+ switch (sqlite3_reset(stmt)) {
+ case SQLITE_CONSTRAINT:
+ errPtr->code = "registry::constraint";
+ errPtr->description = "a constraint was disobeyed";
+ errPtr->free = NULL;
+ sqlite3_finalize(stmt);
+ return 0;
+ default:
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+ }
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ return 0;
+ }
+}
+
+int reg_entry_map(sqlite3* db, reg_entry* entry, char** files, int file_count,
+ reg_error* errPtr) {
+ sqlite3_stmt* stmt;
+ char* query = "INSERT INTO registry.files (id, path) VALUES (?, ?)";
+ if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+ && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)) {
+ int i;
+ for (i=0; i<file_count; i++) {
+ if (sqlite3_bind_text(stmt, 2, files[i], -1, SQLITE_STATIC)
+ == SQLITE_OK) {
+ int r = sqlite3_step(stmt);
+ switch (r) {
+ case SQLITE_DONE:
+ sqlite3_reset(stmt);
+ continue;
+ case SQLITE_ERROR:
+ switch (sqlite3_reset(stmt)) {
+ case SQLITE_CONSTRAINT:
+ errPtr->code = "registry::already-owned";
+ errPtr->description = "mapped file is already "
+ "owned by another entry";
+ errPtr->free = NULL;
+ sqlite3_finalize(stmt);
+ return i;
+ default:
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ return i;
+ }
+ }
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ return i;
+ }
+ }
+ sqlite3_finalize(stmt);
+ return file_count;
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+}
+
+int reg_entry_unmap(sqlite3* db, reg_entry* entry, char** files, int file_count,
+ reg_error* errPtr) {
+ sqlite3_stmt* stmt;
+ char* query = "DELETE FROM registry.files WHERE id=? AND path=?";
+ if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+ && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)) {
+ int i;
+ for (i=0; i<file_count; i++) {
+ if (sqlite3_bind_text(stmt, 2, files[i], -1, SQLITE_STATIC)
+ == SQLITE_OK) {
+ int r = sqlite3_step(stmt);
+ switch (r) {
+ case SQLITE_DONE:
+ if (sqlite3_changes(db) == 0) {
+ errPtr->code = "registry::not-owned";
+ errPtr->description = "this entry does not own the "
+ "given file";
+ errPtr->free = NULL;
+ sqlite3_finalize(stmt);
+ return i;
+ } else {
+ sqlite3_reset(stmt);
+ continue;
+ }
+ default:
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ return i;
+ }
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ return i;
+ }
+ }
+ sqlite3_finalize(stmt);
+ return file_count;
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+}
+
+int reg_entry_files(sqlite3* db, reg_entry* entry, char*** files,
+ reg_error* errPtr) {
+ sqlite3_stmt* stmt;
+ char* query = "SELECT path FROM registry.files WHERE id=? ORDER BY path";
+ if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+ && (sqlite3_bind_int64(stmt, 1, entry->id) == SQLITE_OK)) {
+ char** result = malloc(10*sizeof(char*));
+ int result_count = 0;
+ int result_space = 10;
+ int r;
+ do {
+ char* element;
+ int i;
+ r = sqlite3_step(stmt);
+ switch (r) {
+ case SQLITE_ROW:
+ element = strdup(sqlite3_column_text(stmt, 0));
+ reg_listcat((void*)&result, &result_count, &result_space,
+ element);
+ break;
+ case SQLITE_DONE:
+ break;
+ default:
+ for (i=0; i<result_count; i++) {
+ free(result[i]);
+ }
+ free(result);
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ return -1;
+ }
+ } while (r != SQLITE_DONE);
+ sqlite3_finalize(stmt);
+ *files = result;
+ return result_count;
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ return -1;
+ }
+}
+
+int reg_all_entries(sqlite3* db, reg_entry*** entries, reg_error* errPtr) {
+ reg_entry* entry;
+ void** results = malloc(10*sizeof(void*));
+ int result_count = 0;
+ int result_space = 10;
+ sqlite3_stmt* stmt;
+ char* query = "SELECT address FROM entries";
+ if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+ int r;
+ do {
+ r = sqlite3_step(stmt);
+ switch (r) {
+ case SQLITE_ROW:
+ entry = *(reg_entry**)sqlite3_column_blob(stmt, 0);
+ reg_listcat(&results, &result_count, &result_space, entry);
+ break;
+ case SQLITE_DONE:
+ break;
+ default:
+ reg_sqlite_error(db, errPtr, query);
+ free(results);
+ return -1;
+ }
+ } while (r != SQLITE_DONE);
+ }
+ *entries = (reg_entry**)results;
+ return result_count;
+}
Added: trunk/base/src/registry2.0/centry.h
===================================================================
--- trunk/base/src/registry2.0/centry.h (rev 0)
+++ trunk/base/src/registry2.0/centry.h 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,86 @@
+/*
+ * centry.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _CENTRY_H
+#define _CENTRY_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sqlite3.h>
+#include "creg.h"
+
+typedef struct {
+ sqlite_int64 id; /* rowid in database */
+ sqlite3* db; /* database */
+ int saved; /* have we recorded &entry in database? */
+ char* proc; /* name of Tcl proc, if using Tcl */
+} reg_entry;
+
+typedef int (cast_function)(void* userdata, void** dst, void* src,
+ reg_error* errPtr);
+typedef void (free_function)(void* userdata, void* item);
+
+reg_entry* reg_entry_create(sqlite3* db, char* name, char* version,
+ char* revision, char* variants, char* epoch, reg_error* errPtr);
+
+reg_entry* reg_entry_open(sqlite3* db, char* name, char* version,
+ char* revision, char* variants, char* epoch, reg_error* errPtr);
+
+int reg_entry_delete(sqlite3* db, reg_entry* entry, reg_error* errPtr);
+
+void reg_entry_free(sqlite3* db, reg_entry* entry);
+
+int reg_entry_search(sqlite3* db, char** keys, char** vals, int key_count,
+ int strategy, reg_entry*** entries, reg_error* errPtr);
+
+int reg_entry_installed(sqlite3* db, char* name, char* version,
+ reg_entry*** entries, reg_error* errPtr);
+
+int reg_entry_active(sqlite3* db, char* name, reg_entry*** entries,
+ reg_error* errPtr);
+
+int reg_entry_owner(sqlite3* db, char* path, reg_entry** entry,
+ reg_error* errPtr);
+
+int reg_entry_propget(sqlite3* db, reg_entry* entry, char* key, char** value,
+ reg_error* errPtr);
+int reg_entry_propset(sqlite3* db, reg_entry* entry, char* key, char* value,
+ reg_error* errPtr);
+
+int reg_entry_map(sqlite3* db, reg_entry* entry, char** files, int file_count,
+ reg_error* errPtr);
+int reg_entry_unmap(sqlite3* db, reg_entry* entry, char** files, int file_count,
+ reg_error* errPtr);
+
+int reg_entry_files(sqlite3* db, reg_entry* entry, char*** files,
+ reg_error* errPtr);
+
+int reg_all_entries(sqlite3* db, reg_entry*** entries, reg_error* errPtr);
+
+#endif /* _CENTRY_H */
Added: trunk/base/src/registry2.0/creg.c
===================================================================
--- trunk/base/src/registry2.0/creg.c (rev 0)
+++ trunk/base/src/registry2.0/creg.c 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,165 @@
+/*
+ * reg.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sqlite3.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#include "graph.h"
+#include "item.h"
+#include "entry.h"
+#include "util.h"
+#include "sql.h"
+
+void reg_error_destruct(reg_error* errPtr) {
+ if (errPtr->free) {
+ errPtr->free(errPtr->description);
+ }
+}
+
+/**
+ * Sets `errPtr` according to the last error in `db`.
+ */
+void reg_sqlite_error(sqlite3* db, reg_error* errPtr, char* query) {
+ errPtr->code = "registry::sqlite-error";
+ errPtr->free = (reg_error_destructor*)sqlite3_free;
+ if (query == NULL) {
+ errPtr->description = sqlite3_mprintf("sqlite error: %s",
+ sqlite3_errmsg(db));
+ } else {
+ errPtr->description = sqlite3_mprintf("sqlite error: %s while "
+ "executing query: %s", sqlite3_errmsg(db), query);
+ }
+}
+
+int reg_open(sqlite3** dbPtr, reg_error* errPtr) {
+ if (sqlite3_open(NULL, dbPtr) == SQLITE_OK) {
+ if (init_db(*dbPtr, errPtr)) {
+ return 1;
+ } else {
+ sqlite3_close(*dbPtr);
+ *dbPtr = NULL;
+ }
+ } else {
+ reg_sqlite_error(*dbPtr, errPtr, NULL);
+ sqlite3_close(*dbPtr);
+ *dbPtr = NULL;
+ }
+ return 0;
+}
+
+int reg_close(sqlite3* db, reg_error* errPtr) {
+ if (sqlite3_close((sqlite3*)db) == SQLITE_OK) {
+ return 1;
+ } else {
+ errPtr->code = "registry::not-closed";
+ errPtr->description = sqlite3_mprintf("error: registry db not closed "
+ "correctly (%s)\n", sqlite3_errmsg((sqlite3*)db));
+ errPtr->free = (reg_error_destructor*)sqlite3_free;
+ return 0;
+ }
+}
+
+int reg_attach(sqlite3* db, const char* path, reg_error* errPtr) {
+ struct stat sb;
+ int needsInit = 0; /* registry doesn't yet exist */
+ int canWrite = 1; /* can write to this location */
+ if (stat(path, &sb) != 0) {
+ if (errno == ENOENT) {
+ needsInit = 1;
+ } else {
+ canWrite = 0;
+ }
+ }
+ if (!needsInit || canWrite) {
+ sqlite3_stmt* stmt;
+ char* query = sqlite3_mprintf("ATTACH DATABASE '%q' AS registry", path);
+ if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+ && (sqlite3_step(stmt) == SQLITE_DONE)) {
+ sqlite3_finalize(stmt);
+ if (!needsInit || (create_tables(db, errPtr))) {
+ return 1;
+ }
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ }
+ } else {
+ errPtr->code = "registry::cannot-init";
+ errPtr->description = sqlite3_mprintf("port registry doesn't exist at \"%q\" and couldn't write to this location", path);
+ errPtr->free = (reg_error_destructor*)sqlite3_free;
+ }
+ return 0;
+}
+
+int reg_detach(sqlite3* db, reg_error* errPtr) {
+ sqlite3_stmt* stmt;
+ char* query = "DETACH DATABASE registry";
+ if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+ && (sqlite3_step(stmt) == SQLITE_DONE)) {
+ sqlite3_finalize(stmt);
+ query = "SELECT address FROM entries";
+ if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+ int r;
+ reg_entry* entry;
+ do {
+ r = sqlite3_step(stmt);
+ switch (r) {
+ case SQLITE_ROW:
+ entry = *(reg_entry**)sqlite3_column_blob(stmt, 0);
+ reg_entry_free(db, entry);
+ break;
+ case SQLITE_DONE:
+ break;
+ default:
+ reg_sqlite_error(db, errPtr, query);
+ return 0;
+ }
+ } while (r != SQLITE_DONE);
+ }
+ sqlite3_finalize(stmt);
+ query = "DELETE FROM entries";
+ if ((sqlite3_prepare(db, query, -1, &stmt, NULL) != SQLITE_OK)
+ || (sqlite3_step(stmt) != SQLITE_DONE)) {
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+ return 1;
+ } else {
+ reg_sqlite_error(db, errPtr, query);
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+}
+
Added: trunk/base/src/registry2.0/creg.h
===================================================================
--- trunk/base/src/registry2.0/creg.h (rev 0)
+++ trunk/base/src/registry2.0/creg.h 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,55 @@
+/*
+ * creg.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _CREG_H
+#define _CREG_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sqlite3.h>
+
+typedef void reg_error_destructor(const char* description);
+
+typedef struct {
+ char* code;
+ const char* description;
+ reg_error_destructor* free;
+} reg_error;
+
+void reg_sqlite_error(sqlite3* db, reg_error* errPtr, char* query);
+
+void reg_error_destruct(reg_error* errPtr);
+
+int reg_open(sqlite3** dbPtr, reg_error* errPtr);
+int reg_close(sqlite3* db, reg_error* errPtr);
+
+int reg_attach(sqlite3* db, const char* path, reg_error* errPtr);
+int reg_detach(sqlite3* db, reg_error* errPtr);
+
+#endif /* _CREG_H */
Added: trunk/base/src/registry2.0/entry.c
===================================================================
--- trunk/base/src/registry2.0/entry.c (rev 0)
+++ trunk/base/src/registry2.0/entry.c 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,431 @@
+/*
+ * entry.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "entry.h"
+#include "entryobj.h"
+#include "registry.h"
+#include "util.h"
+
+static reg_entry* get_entry(Tcl_Interp* interp, char* name, reg_error* errPtr) {
+ return (reg_entry*)get_object(interp, name, "entry", entry_obj_cmd, errPtr);
+}
+
+static void delete_entry(ClientData clientData) {
+ reg_entry_free(NULL, (reg_entry*)clientData);
+}
+
+static int set_entry(Tcl_Interp* interp, char* name, reg_entry* entry,
+ reg_error* errPtr) {
+ if (set_object(interp, name, entry, "entry", entry_obj_cmd, delete_entry,
+ errPtr)) {
+ int size = strlen(name) + 1;
+ entry->proc = malloc(size*sizeof(char));
+ memcpy(entry->proc, name, size);
+ return 1;
+ }
+ return 0;
+}
+
+static int obj_to_entry(Tcl_Interp* interp, reg_entry** entry, Tcl_Obj* obj,
+ reg_error* errPtr) {
+ reg_entry* result = get_entry(interp, Tcl_GetString(obj), errPtr);
+ if (result == NULL) {
+ return 0;
+ } else {
+ *entry = result;
+ return 1;
+ }
+}
+
+static int list_obj_to_entry(Tcl_Interp* interp, reg_entry*** entries,
+ const Tcl_Obj** objv, int objc, reg_error* errPtr) {
+ return recast(interp, (cast_function*)obj_to_entry, NULL, (void***)entries,
+ (void**)objv, objc, errPtr);
+}
+
+static int entry_to_obj(Tcl_Interp* interp, Tcl_Obj** obj, reg_entry* entry,
+ reg_error* errPtr) {
+ if (entry->proc == NULL) {
+ char* name = unique_name(interp, "registry::entry");
+ if (!set_entry(interp, name, entry, errPtr)) {
+ free(name);
+ return 0;
+ }
+ free(name);
+ }
+ *obj = Tcl_NewStringObj(entry->proc, -1);
+ return 1;
+}
+
+static int list_entry_to_obj(Tcl_Interp* interp, Tcl_Obj*** objs,
+ reg_entry** entries, int entry_count, reg_error* errPtr) {
+ return recast(interp, (cast_function*)entry_to_obj, NULL, (void***)objs,
+ (void**)entries, entry_count, errPtr);
+}
+
+
+/**
+ * registry::entry create portname version revision variants epoch
+ *
+ * Unlike the old registry::new_entry, revision, variants, and epoch are all
+ * required. That's OK because there's only one place this function is called,
+ * and it's called with all of them there.
+ */
+static int entry_create(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+ sqlite3* db = registry_db(interp, 1);
+ if (objc != 7) {
+ Tcl_WrongNumArgs(interp, 2, objv, "name version revision variants "
+ "epoch");
+ return TCL_ERROR;
+ } else if (db == NULL) {
+ return TCL_ERROR;
+ } else {
+ char* name = Tcl_GetString(objv[2]);
+ char* version = Tcl_GetString(objv[3]);
+ char* revision = Tcl_GetString(objv[4]);
+ char* variants = Tcl_GetString(objv[5]);
+ char* epoch = Tcl_GetString(objv[6]);
+ reg_error error;
+ reg_entry* entry = reg_entry_create(db, name, version, revision,
+ variants, epoch, &error);
+ if (entry != NULL) {
+ Tcl_Obj* result;
+ if (entry_to_obj(interp, &result, entry, &error)) {
+ Tcl_SetObjResult(interp, result);
+ return TCL_OK;
+ }
+ reg_entry_free(db, entry);
+ }
+ return registry_failed(interp, &error);
+ }
+}
+
+/**
+ * registry::entry delete entry
+ *
+ * Deletes an entry from the registry (then closes it).
+ */
+static int entry_delete(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+ sqlite3* db = registry_db(interp, 1);
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "delete entry");
+ return TCL_ERROR;
+ } if (db == NULL) {
+ return TCL_ERROR;
+ } else {
+ reg_entry* entry;
+ reg_error error;
+ if (obj_to_entry(interp, &entry, objv[2], &error)) {
+ if (reg_entry_delete(db, entry, &error)) {
+ Tcl_DeleteCommand(interp, Tcl_GetString(objv[2]));
+ return TCL_OK;
+ }
+ }
+ return registry_failed(interp, &error);
+ }
+}
+
+/**
+ * registry::entry open portname version revision variants epoch ?name?
+ *
+ *
+ */
+static int entry_open(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+ sqlite3* db = registry_db(interp, 1);
+ if (objc != 7) {
+ Tcl_WrongNumArgs(interp, 1, objv, "open portname version revision "
+ "variants epoch");
+ return TCL_ERROR;
+ } else if (db == NULL) {
+ return TCL_ERROR;
+ } else {
+ char* name = Tcl_GetString(objv[2]);
+ char* version = Tcl_GetString(objv[3]);
+ char* revision = Tcl_GetString(objv[4]);
+ char* variants = Tcl_GetString(objv[5]);
+ char* epoch = Tcl_GetString(objv[6]);
+ reg_error error;
+ reg_entry* entry = reg_entry_open(db, name, version, revision, variants,
+ epoch, &error);
+ if (entry != NULL) {
+ Tcl_Obj* result;
+ if (entry_to_obj(interp, &result, entry, &error)) {
+ Tcl_SetObjResult(interp, result);
+ return TCL_OK;
+ }
+ }
+ return registry_failed(interp, &error);
+ }
+ return TCL_ERROR;
+}
+
+/*
+ * registry::entry close entry
+ *
+ * Closes an entry. It will remain in the registry until next time.
+ */
+static int entry_close(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "delete entry");
+ return TCL_ERROR;
+ } else {
+ reg_error error;
+ char* proc = Tcl_GetString(objv[2]);
+ reg_entry* entry = get_entry(interp, proc, &error);
+ if (entry == NULL) {
+ return registry_failed(interp, &error);
+ } else {
+ Tcl_DeleteCommand(interp, proc);
+ return TCL_OK;
+ }
+ }
+}
+
+/*
+ * registry::entry search ?key value ...?
+ *
+ * Searches the registry for ports for which each key's value is equal to the
+ * given value. To find all ports, call `entry search` with no key-value pairs.
+ *
+ * TODO: allow selection of -exact, -glob, and -regexp matching.
+ */
+static int entry_search(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+ int i;
+ sqlite3* db = registry_db(interp, 1);
+ if (objc % 2 == 1) {
+ Tcl_WrongNumArgs(interp, 2, objv, "search ?key value ...?");
+ return TCL_ERROR;
+ } else if (db == NULL) {
+ return TCL_ERROR;
+ } else {
+ char** keys;
+ char** vals;
+ int key_count = objc/2 - 1;
+ reg_entry** entries;
+ reg_error error;
+ int entry_count;
+ /* ensure that valid search keys were used */
+ for (i=2; i<objc; i+=2) {
+ int index;
+ if (Tcl_GetIndexFromObj(interp, objv[i], entry_props, "search key",
+ 0, &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+ keys = malloc(key_count * sizeof(char*));
+ vals = malloc(key_count * sizeof(char*));
+ for (i=0; i<key_count; i+=1) {
+ keys[i] = Tcl_GetString(objv[2*i+2]);
+ vals[i] = Tcl_GetString(objv[2*i+3]);
+ }
+ entry_count = reg_entry_search(db, keys, vals, key_count, 0, &entries,
+ &error);
+ if (entry_count >= 0) {
+ Tcl_Obj* resultObj;
+ Tcl_Obj** objs;
+ list_entry_to_obj(interp, &objs, entries, entry_count,
+ &error);
+ resultObj = Tcl_NewListObj(entry_count, objs);
+ Tcl_SetObjResult(interp, resultObj);
+ free(entries);
+ return TCL_OK;
+ }
+ return registry_failed(interp, &error);
+ }
+}
+
+/**
+ * registry::entry exists name
+ *
+ * Note that this is <i>not</i> the same as entry_exists from registry1.0. This
+ * simply checks if the given string is a valid entry object in the current
+ * interp. No query to the database will be made.
+ */
+static int entry_exists(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+ reg_error error;
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "name");
+ return TCL_ERROR;
+ }
+ if (get_entry(interp, Tcl_GetString(objv[2]), &error) == NULL) {
+ reg_error_destruct(&error);
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0));
+ } else {
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(1));
+ }
+ return TCL_OK;
+}
+
+/**
+ * registry::entry installed ?name? ?version?
+ *
+ * Returns a list of all installed ports. If `name` is specified, only returns
+ * ports with that name, and if `version` is specified, only with that version.
+ * Remember, the variants can still be different.
+ *
+ * TODO: add more arguments (epoch, revision, variants), maybe
+ */
+static int entry_installed(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]){
+ sqlite3* db = registry_db(interp, 1);
+ if (objc > 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?name? ?version?");
+ return TCL_ERROR;
+ } else if (db == NULL) {
+ return TCL_ERROR;
+ } else {
+ char* name = (objc >= 3) ? Tcl_GetString(objv[2]) : NULL;
+ char* version = (objc == 4) ? Tcl_GetString(objv[3]) : NULL;
+ reg_entry** entries;
+ reg_error error;
+ int entry_count = reg_entry_installed(db, name, version, &entries,
+ &error);
+ if (entry_count >= 0) {
+ Tcl_Obj* resultObj;
+ Tcl_Obj** objs;
+ list_entry_to_obj(interp, &objs, entries, entry_count,
+ &error);
+ resultObj = Tcl_NewListObj(entry_count, objs);
+ Tcl_SetObjResult(interp, resultObj);
+ free(entries);
+ return TCL_OK;
+ }
+ return registry_failed(interp, &error);
+ }
+}
+
+/**
+ * registry::entry active ?name?
+ *
+ * Returns a list of all active ports. If `name` is specified, only returns the
+ * active port named, still in a list.
+ */
+static int entry_active(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]){
+ sqlite3* db = registry_db(interp, 1);
+ if (objc > 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?name?");
+ return TCL_ERROR;
+ } else if (db == NULL) {
+ return TCL_ERROR;
+ } else {
+ char* name = (objc == 3) ? Tcl_GetString(objv[2]) : NULL;
+ reg_entry** entries;
+ reg_error error;
+ int entry_count = reg_entry_active(db, name, &entries,
+ &error);
+ if (entry_count >= 0) {
+ Tcl_Obj* resultObj;
+ Tcl_Obj** objs;
+ list_entry_to_obj(interp, &objs, entries, entry_count,
+ &error);
+ resultObj = Tcl_NewListObj(entry_count, objs);
+ Tcl_SetObjResult(interp, resultObj);
+ free(entries);
+ return TCL_OK;
+ }
+ return registry_failed(interp, &error);
+ }
+}
+
+
+/**
+ */
+static int entry_owner(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+ sqlite3* db = registry_db(interp, 1);
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "path");
+ return TCL_ERROR;
+ } else if (db == NULL) {
+ return TCL_ERROR;
+ } else {
+ char* path = Tcl_GetString(objv[2]);
+ reg_entry* entry;
+ reg_error error;
+ if (reg_entry_owner(db, path, &entry, &error)) {
+ if (entry == NULL) {
+ return TCL_OK;
+ } else {
+ Tcl_Obj* result;
+ if (entry_to_obj(interp, &result, entry, &error)) {
+ Tcl_SetObjResult(interp, result);
+ return TCL_OK;
+ }
+ }
+ }
+ return registry_failed(interp, &error);
+ }
+}
+
+typedef struct {
+ char* name;
+ int (*function)(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
+} entry_cmd_type;
+
+static entry_cmd_type entry_cmds[] = {
+ /* Global commands */
+ { "create", entry_create },
+ { "delete", entry_delete },
+ { "open", entry_open },
+ { "close", entry_close },
+ { "search", entry_search },
+ { "exists", entry_exists },
+ { "installed", entry_installed },
+ { "active", entry_active },
+ { "owner", entry_owner },
+ { NULL, NULL }
+};
+
+/**
+ * registry::entry cmd ?arg ...?
+ *
+ * Commands manipulating port entries in the registry. This could be called
+ * `registry::port`, but that could be misleading, because `registry::item`
+ * represents ports too, but not those in the registry.
+ */
+int entry_cmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+ Tcl_Obj* CONST objv[]) {
+ int cmd_index;
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObjStruct(interp, objv[1], entry_cmds,
+ sizeof(entry_cmd_type), "cmd", 0, &cmd_index) == TCL_OK) {
+ entry_cmd_type* cmd = &entry_cmds[cmd_index];
+ return cmd->function(interp, objc, objv);
+ }
+ return TCL_ERROR;
+}
Added: trunk/base/src/registry2.0/entry.h
===================================================================
--- trunk/base/src/registry2.0/entry.h (rev 0)
+++ trunk/base/src/registry2.0/entry.h 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,40 @@
+/*
+ * entry.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _ENTRY_H
+#define _ENTRY_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+
+int entry_cmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+ Tcl_Obj* CONST objv[]);
+
+#endif /* _ENTRY_H */
Added: trunk/base/src/registry2.0/entryobj.c
===================================================================
--- trunk/base/src/registry2.0/entryobj.c (rev 0)
+++ trunk/base/src/registry2.0/entryobj.c 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,224 @@
+/*
+ * entryobj.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "entryobj.h"
+#include "registry.h"
+#include "util.h"
+
+const char* entry_props[] = {
+ "name",
+ "portfile",
+ "url",
+ "location",
+ "epoch",
+ "version",
+ "revision",
+ "variants",
+ "date",
+ "state",
+ NULL
+};
+
+/* ${entry} prop ?value? */
+static int entry_obj_prop(Tcl_Interp* interp, reg_entry* entry, int objc,
+ Tcl_Obj* CONST objv[]) {
+ int index;
+ sqlite3* db = registry_db(interp, 1);
+ if (objc > 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?value?");
+ return TCL_ERROR;
+ } else if (db == NULL) {
+ return TCL_ERROR;
+ }
+ if (objc == 2) {
+ /* ${entry} prop; return the current value */
+ if (Tcl_GetIndexFromObj(interp, objv[1], entry_props, "prop", 0, &index)
+ == TCL_OK) {
+ char* key = Tcl_GetString(objv[1]);
+ char* value;
+ reg_error error;
+ if (reg_entry_propget(db, entry, key, &value, &error)) {
+ Tcl_Obj* result = Tcl_NewStringObj(value, -1);
+ Tcl_SetObjResult(interp, result);
+ return TCL_OK;
+ }
+ return registry_failed(interp, &error);
+ }
+ return TCL_ERROR;
+ } else {
+ /* ${entry} prop name value; set a new value */
+ if (Tcl_GetIndexFromObj(interp, objv[1], entry_props, "prop", 0, &index)
+ == TCL_OK) {
+ char* key = Tcl_GetString(objv[1]);
+ char* value = Tcl_GetString(objv[2]);
+ reg_error error;
+ if (reg_entry_propset(db, entry, key, value, &error)) {
+ return TCL_OK;
+ }
+ return registry_failed(interp, &error);
+ }
+ return TCL_ERROR;
+ }
+}
+
+/*
+ * ${entry} map ?file ...?
+ *
+ * Maps the listed files to the port represented by ${entry}. This will throw an
+ * error if a file is mapped to an already-existing file, but not a very
+ * descriptive one.
+ *
+ * TODO: more descriptive error on duplicated file
+ */
+static int entry_obj_map(Tcl_Interp* interp, reg_entry* entry, int objc,
+ Tcl_Obj* CONST objv[]) {
+ sqlite3* db = registry_db(interp, 1);
+ if (db == NULL) {
+ return TCL_ERROR;
+ } else {
+ char** files;
+ reg_error error;
+ if (list_obj_to_string(&files, objv+2, objc-2, &error)) {
+ if (reg_entry_map(db, entry, files, objc-2, &error) == objc-2) {
+ return TCL_OK;
+ }
+ }
+ return registry_failed(interp, &error);
+ }
+}
+
+/*
+ * ${entry} unmap ?file ...?
+ *
+ * Unmaps the listed files from the given port. Will throw an error if a file
+ * that is not mapped to the port is attempted to be unmapped.
+ */
+static int entry_obj_unmap(Tcl_Interp* interp, reg_entry* entry, int objc,
+ Tcl_Obj* CONST objv[]) {
+ sqlite3* db = registry_db(interp, 1);
+ if (db == NULL) {
+ return TCL_ERROR;
+ } else {
+ char** files;
+ reg_error error;
+ if (list_obj_to_string(&files, objv+2, objc-2, &error)) {
+ if (reg_entry_unmap(db, entry, files, objc-2, &error) == objc-2) {
+ return TCL_OK;
+ }
+ }
+ return registry_failed(interp, &error);
+ }
+}
+
+static int entry_obj_files(Tcl_Interp* interp, reg_entry* entry, int objc,
+ Tcl_Obj* CONST objv[]) {
+ sqlite3* db = registry_db(interp, 1);
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "files");
+ return TCL_ERROR;
+ } else if (db == NULL) {
+ return TCL_ERROR;
+ } else {
+ char** files;
+ reg_error error;
+ int file_count = reg_entry_files(db, entry, &files, &error);
+ if (file_count >= 0) {
+ int i;
+ Tcl_Obj** objs;
+ if (list_string_to_obj(&objs, files, file_count, &error)) {
+ for (i=0; i<file_count; i++) {
+ free(files[i]);
+ }
+ free(files);
+ Tcl_Obj* result = Tcl_NewListObj(file_count, objs);
+ Tcl_SetObjResult(interp, result);
+ return TCL_OK;
+ }
+ for (i=0; i<file_count; i++) {
+ free(files[i]);
+ }
+ free(files);
+ }
+ return registry_failed(interp, &error);
+ }
+}
+
+typedef struct {
+ char* name;
+ int (*function)(Tcl_Interp* interp, reg_entry* entry, int objc,
+ Tcl_Obj* CONST objv[]);
+} entry_obj_cmd_type;
+
+static entry_obj_cmd_type entry_cmds[] = {
+ { "name", entry_obj_prop },
+ { "portfile", entry_obj_prop },
+ { "url", entry_obj_prop },
+ { "location", entry_obj_prop },
+ { "epoch", entry_obj_prop },
+ { "version", entry_obj_prop },
+ { "revision", entry_obj_prop },
+ { "variants", entry_obj_prop },
+ { "date", entry_obj_prop },
+ { "state", entry_obj_prop },
+ { "map", entry_obj_map },
+ { "unmap", entry_obj_unmap },
+ { "files", entry_obj_files },
+ { NULL, NULL }
+};
+
+/* ${entry} cmd ?arg ...? */
+/* This function implements the command that will be called when an entry
+ * created by `registry::entry` is used as a procedure. Since all data is kept
+ * in a temporary sqlite3 database that is created for the current interpreter,
+ * none of the sqlite3 functions used have any error checking. That should be a
+ * safe assumption, since nothing outside of registry:: should ever have the
+ * chance to touch it.
+ */
+int entry_obj_cmd(ClientData clientData, Tcl_Interp* interp, int objc,
+ Tcl_Obj* CONST objv[]) {
+ int cmd_index;
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObjStruct(interp, objv[1], entry_cmds,
+ sizeof(entry_obj_cmd_type), "cmd", 0, &cmd_index) == TCL_OK) {
+ entry_obj_cmd_type* cmd = &entry_cmds[cmd_index];
+ return cmd->function(interp, (reg_entry*)clientData, objc, objv);
+ }
+ return TCL_ERROR;
+}
Added: trunk/base/src/registry2.0/entryobj.h
===================================================================
--- trunk/base/src/registry2.0/entryobj.h (rev 0)
+++ trunk/base/src/registry2.0/entryobj.h 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,48 @@
+/*
+ * entryobj.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _ENTRY_OBJ_CMD_H
+#define _ENTRY_OBJ_CMD_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+#include <sqlite3.h>
+
+typedef struct {
+ sqlite_int64 rowid;
+ sqlite3* db;
+} entry_t;
+
+extern const char* entry_props[];
+
+int entry_obj_cmd(ClientData clientData, Tcl_Interp* interp, int objc,
+ Tcl_Obj* CONST objv[]);
+
+#endif /* _ENTRY_OBJ_CMD_H */
Added: trunk/base/src/registry2.0/graph.c
===================================================================
--- trunk/base/src/registry2.0/graph.c (rev 0)
+++ trunk/base/src/registry2.0/graph.c 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,198 @@
+/*
+ * graph.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "graph.h"
+#include "graphobj.h"
+#include "registry.h"
+#include "util.h"
+
+void DeleteGraph(graph* g) {
+ sqlite3_stmt* stmt;
+ if ((sqlite3_prepare(g->db, "DETACH DATABASE registry", -1, &stmt, NULL)
+ != SQLITE_OK)
+ || (sqlite3_step(stmt) != SQLITE_DONE)) {
+ fprintf(stderr, "error: registry db not detached correctly (%s)\n",
+ sqlite3_errmsg(g->db));
+ }
+ free(g);
+}
+
+graph* GetGraph(Tcl_Interp* interp, char* name) {
+ return GetCtx(interp, name, "graph", GraphObjCmd);
+}
+
+int SetGraph(Tcl_Interp* interp, char* name, graph* g) {
+ return SetCtx(interp, name, g, "graph", GraphObjCmd,
+ (Tcl_CmdDeleteProc*)DeleteGraph);
+}
+
+/* graph create dbfile ?name? */
+int GraphCreateCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+ if (objc > 4 || objc < 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "dbfile ?name?");
+ return TCL_ERROR;
+ } else {
+ sqlite3* db = RegistryDB(interp);
+ sqlite3_stmt* stmt;
+ int needsInit = 0;
+ int len;
+ char* file = Tcl_GetStringFromObj(objv[2], &len);
+ char* query = sqlite3_mprintf("ATTACH DATABASE '%q' AS registry", file);
+
+ if (Tcl_FSAccess(objv[2], F_OK) != 0) {
+ needsInit = 1;
+ printf("initializing\n");
+ }
+
+ if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+ && (sqlite3_step(stmt) == SQLITE_DONE)) {
+ sqlite3_finalize(stmt);
+ if (!needsInit
+ || ((sqlite3_prepare(db, "CREATE TABLE registry.ports "
+ "(name, portfile, url, location, epoch, "
+ "version, revision, variants, state)", -1,
+ &stmt, NULL)
+ == SQLITE_OK)
+ && (sqlite3_step(stmt) == SQLITE_DONE))) {
+ graph* g = malloc(sizeof(graph));
+ g->db = db;
+ sqlite3_free(query);
+ if (objc == 4) {
+ /* graph create dbfile name */
+ if (SetGraph(interp, Tcl_GetString(objv[3]), g) == TCL_OK) {
+ Tcl_SetObjResult(interp, objv[3]);
+ return TCL_OK;
+ }
+ } else {
+ /* graph create dbfile; generate a name */
+ char* name = UniqueName(interp, "registry::graph");
+ if (SetGraph(interp, name, g) == TCL_OK) {
+ Tcl_Obj* res = Tcl_NewStringObj(name, -1);
+ Tcl_SetObjResult(interp, res);
+ free(name);
+ return TCL_OK;
+ }
+ free(name);
+ }
+ free(g);
+ }
+ } else {
+ set_sqlite_result(interp, db, query);
+ sqlite3_free(query);
+ }
+ sqlite3_finalize(stmt);
+ return TCL_ERROR;
+ }
+}
+
+/* graph delete ?name ...? */
+int GraphDeleteCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+ int i;
+ for (i=2; i<objc; i++) {
+ graph* g;
+ char* proc = Tcl_GetString(objv[i]);
+ g = GetGraph(interp, proc);
+ if (g == NULL) {
+ return TCL_ERROR;
+ } else {
+ Tcl_DeleteCommand(interp, proc);
+ }
+ }
+ return TCL_OK;
+}
+
+/* graph exists name */
+int GraphExistsCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "name");
+ return TCL_ERROR;
+ }
+ if (GetGraph(interp, Tcl_GetString(objv[2])) == NULL) {
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0));
+ } else {
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(1));
+ }
+ return TCL_OK;
+}
+
+typedef struct {
+ char* name;
+ int (*function)(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
+} GraphCmdType;
+
+static GraphCmdType graph_cmds[] = {
+ /* commands usable only by `graph` itself */
+ { "create", GraphCreateCmd },
+ { "delete", GraphDeleteCmd },
+ { "exists", GraphExistsCmd },
+ /* commands usable by `graph` or an instance thereof */
+ /* { "install", GraphInstallCmd }, */
+ /* { "uninstall", GraphUninstallCmd }, */
+ /* { "activate", GraphActivateCmd }, */
+ /* { "deactivate", GraphDeactivateCmd }, */
+ /* { "upgrade", GraphUpgradeCmd }, */
+ /* { "changed", GraphChangedCmd }, */
+ /* { "warnings", GraphWarningsCmd }, */
+ /* { "errors", GraphErrorsCmd }, */
+ /* { "commit", GraphCommitCmd }, */
+ /* { "rollback", GraphRollbackCmd }, */
+ /* { "active", GraphActiveCmd }, */
+ /* { "installed", GraphInstalledCmd }, */
+ /* { "location", GraphLocationCmd }, */
+ /* { "map", GraphMapCmd }, */
+ /* { "unmap", GraphUnmapCmd }, */
+ /* { "contents", GraphContentsCmd }, */
+ /* { "provides", GraphProvidesCmd }, */
+ { NULL, NULL }
+};
+
+/* graph cmd ?arg ...? */
+int GraphCmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+ Tcl_Obj* CONST objv[]) {
+ int cmd_index;
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObjStruct(interp, objv[1], graph_cmds,
+ sizeof(GraphCmdType), "cmd", 0, &cmd_index) == TCL_OK) {
+ GraphCmdType* cmd = &graph_cmds[cmd_index];
+ return cmd->function(interp, objc, objv);
+ }
+ return TCL_ERROR;
+}
Added: trunk/base/src/registry2.0/graph.h
===================================================================
--- trunk/base/src/registry2.0/graph.h (rev 0)
+++ trunk/base/src/registry2.0/graph.h 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,40 @@
+/*
+ * graph.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _GRAPH_CMD_H
+#define _GRAPH_CMD_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+
+int GraphCmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+ Tcl_Obj* CONST objv[]);
+
+#endif /* _GRAPH_CMD_H */
Added: trunk/base/src/registry2.0/graphobj.c
===================================================================
--- trunk/base/src/registry2.0/graphobj.c (rev 0)
+++ trunk/base/src/registry2.0/graphobj.c 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,147 @@
+/*
+ * graphobj.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "graphobj.h"
+#include "util.h"
+
+/* ${graph} install registry::item */
+int GraphObjInstallCmd(Tcl_Interp* interp, graph* g, int objc,
+ Tcl_Obj* CONST objv[]) {
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "install registry::item");
+ return TCL_ERROR;
+ } else {
+ printf("installing %s to %p\n", Tcl_GetString(objv[2]), (void*)g);
+ }
+ return TCL_OK;
+}
+
+/* ${graph} uninstall registry::item */
+int GraphObjUninstallCmd(Tcl_Interp* interp, graph* g, int objc,
+ Tcl_Obj* CONST objv[]) {
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "uninstall registry::item");
+ return TCL_ERROR;
+ } else {
+ printf("uninstalling %s to %p\n", Tcl_GetString(objv[2]), (void*)g);
+ }
+ return TCL_OK;
+}
+
+/* ${graph} activate registry::item */
+int GraphObjActivateCmd(Tcl_Interp* interp, graph* g, int objc,
+ Tcl_Obj* CONST objv[]) {
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "activate registry::item");
+ return TCL_ERROR;
+ } else {
+ printf("activating %s to %p\n", Tcl_GetString(objv[2]), (void*)g);
+ }
+ return TCL_OK;
+}
+
+/* ${graph} deactivate registry::item */
+int GraphObjDeactivateCmd(Tcl_Interp* interp, graph* g, int objc,
+ Tcl_Obj* CONST objv[]) {
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "deactivate registry::item");
+ return TCL_ERROR;
+ } else {
+ printf("deactivating %s to %p\n", Tcl_GetString(objv[2]), (void*)g);
+ }
+ return TCL_OK;
+}
+
+enum {
+ BUBBLE_UP = 1,
+ BUBBLE_DOWN = 2
+};
+
+/* ${graph} upgrade ?-bubble-up? ?-bubble-down? ?--? porturl */
+int GraphObjUpgradeCmd(Tcl_Interp* interp, graph* g, int objc,
+ Tcl_Obj* CONST objv[]) {
+ option_spec options[] = {
+ { "--", END_FLAGS },
+ { "-bubble-up", BUBBLE_UP },
+ { "-bubble-down", BUBBLE_DOWN },
+ { NULL, 0 }
+ };
+ int flags;
+ int start=2;
+ if (ParseFlags(interp, objc, objv, &start, options, &flags) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (objc - start != 1) {
+ Tcl_WrongNumArgs(interp, 1, objv, "upgrade ?-bubble-up? "
+ "?--bubble-down? ?--? registry::item");
+ return TCL_ERROR;
+ } else {
+ printf("upgrading %s to %p (flags %x)\n", Tcl_GetString(objv[start]),
+ (void*)g, flags);
+ }
+ return TCL_OK;
+}
+
+typedef struct {
+ char* name;
+ int (*function)(Tcl_Interp*, graph*, int, Tcl_Obj* CONST objv[]);
+} GraphObjCmdType;
+
+static GraphObjCmdType graph_obj_cmds[] = {
+ { "install", GraphObjInstallCmd },
+ { "uninstall", GraphObjUninstallCmd },
+ { "activate", GraphObjActivateCmd },
+ { "deactivate", GraphObjDeactivateCmd },
+ { "upgrade", GraphObjUpgradeCmd },
+ { NULL, NULL }
+};
+
+/* ${graph} cmd ?arg ...? */
+int GraphObjCmd(ClientData clientData, Tcl_Interp* interp, int objc,
+ Tcl_Obj* CONST objv[]) {
+ int cmd_index;
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObjStruct(interp, objv[1], graph_obj_cmds,
+ sizeof(GraphObjCmdType), "cmd", 0, &cmd_index) == TCL_OK) {
+ GraphObjCmdType* cmd = &graph_obj_cmds[cmd_index];
+ return cmd->function(interp, clientData, objc, objv);
+ }
+ return TCL_ERROR;
+}
Added: trunk/base/src/registry2.0/graphobj.h
===================================================================
--- trunk/base/src/registry2.0/graphobj.h (rev 0)
+++ trunk/base/src/registry2.0/graphobj.h 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,44 @@
+/*
+ * graphobj.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _GRAPH_OBJ_CMD_H
+#define _GRAPH_OBJ_CMD_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+
+typedef struct {
+ sqlite3* db;
+} graph;
+
+int GraphObjCmd(ClientData clientData, Tcl_Interp* interp, int objc,
+ Tcl_Obj* CONST objv[]);
+
+#endif /* _GRAPH_OBJ_CMD_H */
Added: trunk/base/src/registry2.0/item.c
===================================================================
--- trunk/base/src/registry2.0/item.c (rev 0)
+++ trunk/base/src/registry2.0/item.c 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,264 @@
+/*
+ * item.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "item.h"
+#include "itemobj.h"
+#include "util.h"
+#include "registry.h"
+
+static void delete_item(ClientData clientData) {
+ sqlite_int64 rowid = ((item_t*)clientData)->rowid;
+ sqlite3* db = ((item_t*)clientData)->db;
+ sqlite3_stmt* stmt;
+ sqlite3_prepare(db, "DELETE FROM items WHERE rowid=?", -1, &stmt, NULL);
+ sqlite3_bind_int(stmt, rowid, 1);
+ sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ free(clientData);
+}
+
+static item_t* get_item(Tcl_Interp* interp, char* name) {
+ return (item_t*)get_object(interp, name, "item", item_obj_cmd);
+}
+
+static int set_item(Tcl_Interp* interp, char* name, sqlite_int64 rowid) {
+ sqlite3* db = registry_db(interp, 0);
+ item_t* new_item = malloc(sizeof(item_t));
+ new_item->rowid = rowid;
+ new_item->db = db;
+ if (set_object(interp, name, new_item, "item", item_obj_cmd, delete_item)
+ == TCL_OK) {
+ sqlite3_stmt* stmt;
+ /* record the proc name in case we need to return it in a search */
+ if ((sqlite3_prepare(db, "UPDATE items SET proc=? WHERE rowid=?", -1,
+ &stmt, NULL) == SQLITE_OK)
+ && (sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC)
+ == SQLITE_OK)
+ && (sqlite3_bind_int64(stmt, 2, rowid) == SQLITE_OK)
+ && (sqlite3_step(stmt) == SQLITE_DONE)) {
+ sqlite3_finalize(stmt);
+ return TCL_OK;
+ }
+ Tcl_DeleteCommand(interp, name);
+ sqlite3_finalize(stmt);
+ }
+ free(new_item);
+ return TCL_ERROR;
+}
+
+/* item create ?name? */
+static int item_create(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+ sqlite_int64 item;
+ sqlite3* db = registry_db(interp, 0);
+ if (objc > 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?name?");
+ return TCL_ERROR;
+ } else if (db == NULL) {
+ return TCL_ERROR;
+ }
+ sqlite3_exec(db, "INSERT INTO items (refcount) VALUES (1)", NULL, NULL,
+ NULL);
+ item = sqlite3_last_insert_rowid(db);
+ if (objc == 3) {
+ /* item create name */
+ char* name = Tcl_GetString(objv[2]);
+ if (set_item(interp, name, item) == TCL_OK) {
+ Tcl_SetObjResult(interp, objv[2]);
+ return TCL_OK;
+ }
+ } else {
+ /* item create */
+ char* name = unique_name(interp, "registry::item");
+ if (set_item(interp, name, item) == TCL_OK) {
+ Tcl_Obj* res = Tcl_NewStringObj(name, -1);
+ Tcl_SetObjResult(interp, res);
+ free(name);
+ return TCL_OK;
+ }
+ free(name);
+ }
+ return TCL_ERROR;
+}
+
+/* item release ?name ...? */
+static int item_release(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+ int i;
+ for (i=2; i<objc; i++) {
+ char* proc = Tcl_GetString(objv[i]);
+ item_t* item = get_item(interp, proc);
+ if (item == NULL) {
+ return TCL_ERROR;
+ } else {
+ /* decref */
+ }
+ }
+ return TCL_OK;
+}
+
+static const char* searchKeys[] = {
+ "name",
+ "url",
+ "path",
+ "worker",
+ "options",
+ "variants",
+ NULL
+};
+
+/**
+ * item search ?{key value} ...?
+ *
+ * TODO: rip this out and adapt `entry search`
+ */
+static int item_search(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+ int i, r;
+ sqlite3* db = registry_db(interp, 0);
+ sqlite3_stmt* stmt;
+ Tcl_Obj* result;
+ /* 40 + 20 per clause is safe */
+ char* query = (char*)malloc((20*objc)*sizeof(char));
+ char* insert;
+ if (db == NULL) {
+ return TCL_ERROR;
+ }
+ strcpy(query, "SELECT proc FROM items");
+ insert = query + strlen("SELECT proc FROM items");
+ for (i=2; i<objc; i++) {
+ int len;
+ int index;
+ char* key;
+ Tcl_Obj* keyObj;
+ /* ensure each search clause is a 2-element list */
+ if (Tcl_ListObjLength(interp, objv[i], &len) != TCL_OK || len != 2) {
+ free(query);
+ Tcl_AppendResult(interp, "search clause \"", Tcl_GetString(objv[i]),
+ "\" is not a list with 2 elements", NULL);
+ return TCL_ERROR;
+ }
+ /* this should't fail if Tcl_ListObjLength didn't */
+ Tcl_ListObjIndex(interp, objv[i], 0, &keyObj);
+ /* ensure that a valid search key was used */
+ if (Tcl_GetIndexFromObj(interp, keyObj, searchKeys, "search key", 0,
+ &index) != TCL_OK) {
+ free(query);
+ return TCL_ERROR;
+ }
+ key = Tcl_GetString(keyObj);
+ if (i == 2) {
+ sprintf(insert, " WHERE %s=?", key);
+ insert += 9 + strlen(key);
+ } else {
+ sprintf(insert, " AND %s=?", key);
+ insert += 7 + strlen(key);
+ }
+ }
+ r = sqlite3_prepare(db, query, -1, &stmt, NULL);
+ free(query);
+ for (i=2; i<objc; i++) {
+ char* val;
+ Tcl_Obj* valObj;
+ Tcl_ListObjIndex(interp, objv[i], 1, &valObj);
+ val = Tcl_GetString(valObj);
+ sqlite3_bind_text(stmt, i-1, val, -1, SQLITE_STATIC);
+ }
+ result = Tcl_NewListObj(0, NULL);
+ r = sqlite3_step(stmt);
+ while (r == SQLITE_ROW) {
+ /* avoid signedness warning */
+ const char* proc = sqlite3_column_text(stmt, 0);
+ int len = sqlite3_column_bytes(stmt, 0);
+ Tcl_Obj* procObj = Tcl_NewStringObj(proc, len);
+ Tcl_ListObjAppendElement(interp, result, procObj);
+ r = sqlite3_step(stmt);
+ }
+ Tcl_SetObjResult(interp, result);
+ return TCL_OK;
+}
+
+/* item exists name */
+static int item_exists(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "name");
+ return TCL_ERROR;
+ }
+ if (get_item(interp, Tcl_GetString(objv[2])) == NULL) {
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(0));
+ } else {
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(1));
+ }
+ return TCL_OK;
+}
+
+typedef struct {
+ char* name;
+ int (*function)(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
+} item_cmd_type;
+
+static item_cmd_type item_cmds[] = {
+ /* Global commands */
+ { "create", item_create },
+ { "search", item_search },
+ { "exists", item_exists },
+ /* Instance commands */
+ /*
+ { "retain", item_retain },
+ { "release", item_release },
+ { "name", item_name },
+ { "url", item_url },
+ { "path", item_path },
+ { "worker", item_worker },
+ { "options", item_options },
+ { "variants", item_variants },
+ */
+ { NULL, NULL }
+};
+
+/* item cmd ?arg ...? */
+int item_cmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+ Tcl_Obj* CONST objv[]) {
+ int cmd_index;
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObjStruct(interp, objv[1], item_cmds,
+ sizeof(item_cmd_type), "cmd", 0, &cmd_index) == TCL_OK) {
+ item_cmd_type* cmd = &item_cmds[cmd_index];
+ return cmd->function(interp, objc, objv);
+ }
+ return TCL_ERROR;
+}
Added: trunk/base/src/registry2.0/item.h
===================================================================
--- trunk/base/src/registry2.0/item.h (rev 0)
+++ trunk/base/src/registry2.0/item.h 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,40 @@
+/*
+ * item.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _ITEM_CMD_H
+#define _ITEM_CMD_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+
+int item_cmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+ Tcl_Obj* CONST objv[]);
+
+#endif /* _ITEM_CMD_H */
Added: trunk/base/src/registry2.0/itemobj.c
===================================================================
--- trunk/base/src/registry2.0/itemobj.c (rev 0)
+++ trunk/base/src/registry2.0/itemobj.c 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,184 @@
+/*
+ * item.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "itemobj.h"
+#include "registry.h"
+#include "util.h"
+
+/* ${item} retain */
+/* Increments the refcount of the item. Calls to retain should be balanced by
+ * calls to release. The refcount starts at 1 and needn't be retained by the
+ * creator.
+ */
+static int item_obj_retain(Tcl_Interp* interp, item_t* item, int objc,
+ Tcl_Obj* CONST objv[]) {
+ sqlite3_stmt* stmt;
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ sqlite3_prepare(item->db, "UPDATE items SET refcount = refcount+1 WHERE "
+ "rowid=?", -1, &stmt, NULL);
+ sqlite3_bind_int64(stmt, 1, item->rowid);
+ sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ Tcl_SetObjResult(interp, objv[0]);
+ return TCL_OK;
+}
+
+/* ${item} release */
+/* Decrements the refcount of the item. If this is called after all retains have
+ * been balanced with releases, the object will be freed.
+ */
+static int item_obj_release(Tcl_Interp* interp, item_t* item, int objc,
+ Tcl_Obj* CONST objv[]) {
+ sqlite3_stmt* stmt;
+ int refcount;
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, "");
+ return TCL_ERROR;
+ }
+ sqlite3_prepare(item->db, "UPDATE items SET refcount = refcount-1 "
+ "WHERE rowid=?", -1, &stmt, NULL);
+ sqlite3_bind_int64(stmt, 1, item->rowid);
+ sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ sqlite3_prepare(item->db, "SELECT refcount FROM items WHERE rowid=?", -1,
+ &stmt, NULL);
+ sqlite3_bind_int64(stmt, 1, item->rowid);
+ sqlite3_step(stmt);
+ refcount = sqlite3_column_int(stmt, 0);
+ sqlite3_finalize(stmt);
+ if (refcount <= 0) {
+ Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
+ }
+ return TCL_OK;
+}
+
+/* ${item} key name ?value? */
+static int item_obj_key(Tcl_Interp* interp, item_t* item, int objc,
+ Tcl_Obj* CONST objv[]) {
+ static const char* keys[] = {
+ "name", "url", "path", "worker", "options", "variants",
+ NULL
+ };
+ if (objc == 3) {
+ /* ${item} key name; return the current value */
+ int index;
+ if (Tcl_GetIndexFromObj(interp, objv[2], keys, "key", 0, &index)
+ != TCL_OK) {
+ /* objv[2] is not a valid key */
+ return TCL_ERROR;
+ } else {
+ sqlite3_stmt* stmt;
+ char query[64];
+ char* key = Tcl_GetString(objv[2]);
+ int len;
+ const char* result;
+ Tcl_Obj* resultObj;
+ sprintf(query, "SELECT %s FROM items WHERE rowid=?", key);
+ sqlite3_prepare(item->db, query, -1, &stmt, NULL);
+ sqlite3_bind_int64(stmt, 1, item->rowid);
+ sqlite3_step(stmt);
+ /* eliminate compiler warning about signedness */
+ result = sqlite3_column_text(stmt, 0);
+ len = sqlite3_column_bytes(stmt, 0);
+ resultObj = Tcl_NewStringObj(result, len);
+ Tcl_SetObjResult(interp, resultObj);
+ sqlite3_finalize(stmt);
+ }
+ } else if (objc == 4) {
+ /* ${item} key name value; set a new value */
+ int index;
+ if (Tcl_GetIndexFromObj(interp, objv[2], keys, "key", 0, &index)
+ != TCL_OK) {
+ /* objv[2] is not a valid key */
+ return TCL_ERROR;
+ } else {
+ sqlite3_stmt* stmt;
+ char query[64];
+ char* key = Tcl_GetString(objv[2]);
+ char* value = Tcl_GetString(objv[3]);
+ sprintf(query, "UPDATE items SET %s=? WHERE rowid=?", key);
+ sqlite3_prepare(item->db, query, -1, &stmt, NULL);
+ sqlite3_bind_text(stmt, 1, value, -1, SQLITE_STATIC);
+ sqlite3_bind_int64(stmt, 2, item->rowid);
+ sqlite3_step(stmt);
+ sqlite3_finalize(stmt);
+ }
+ } else {
+ Tcl_WrongNumArgs(interp, 1, objv, "key name ?value?");
+ return TCL_ERROR;
+ }
+ return TCL_OK;
+}
+
+typedef struct {
+ char* name;
+ int (*function)(Tcl_Interp* interp, item_t* item, int objc,
+ Tcl_Obj* CONST objv[]);
+} item_obj_cmd_type;
+
+static item_obj_cmd_type item_cmds[] = {
+ { "retain", item_obj_retain },
+ { "release", item_obj_release },
+ { "key", item_obj_key },
+ { NULL, NULL }
+};
+
+/* ${item} cmd ?arg ...? */
+/* This function implements the command that will be called when an item created
+ * by `registry::item` is used as a procedure. Since all data is kept in a
+ * temporary sqlite3 database that is created for the current interpreter, none
+ * of the sqlite3 functions used have any error checking. That should be a safe
+ * assumption, since nothing outside of registry:: should ever have the chance
+ * to touch it.
+ */
+int item_obj_cmd(ClientData clientData, Tcl_Interp* interp, int objc,
+ Tcl_Obj* CONST objv[]) {
+ int cmd_index;
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "cmd ?arg ...?");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObjStruct(interp, objv[1], item_cmds,
+ sizeof(item_obj_cmd_type), "cmd", 0, &cmd_index) == TCL_OK) {
+ item_obj_cmd_type* cmd = &item_cmds[cmd_index];
+ return cmd->function(interp, (item_t*)clientData, objc, objv);
+ }
+ return TCL_ERROR;
+}
Added: trunk/base/src/registry2.0/itemobj.h
===================================================================
--- trunk/base/src/registry2.0/itemobj.h (rev 0)
+++ trunk/base/src/registry2.0/itemobj.h 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,46 @@
+/*
+ * itemobj.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _ITEM_OBJ_CMD_H
+#define _ITEM_OBJ_CMD_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+#include <sqlite3.h>
+
+typedef struct {
+ sqlite_int64 rowid;
+ sqlite3* db;
+} item_t;
+
+int item_obj_cmd(ClientData clientData, Tcl_Interp* interp, int objc,
+ Tcl_Obj* CONST objv[]);
+
+#endif /* _ITEM_OBJ_CMD_H */
Added: trunk/base/src/registry2.0/registry.c
===================================================================
--- trunk/base/src/registry2.0/registry.c (rev 0)
+++ trunk/base/src/registry2.0/registry.c 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,188 @@
+/*
+ * registry.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "graph.h"
+#include "item.h"
+#include "entry.h"
+#include "util.h"
+#include "sql.h"
+
+int registry_failed(Tcl_Interp* interp, reg_error* errPtr) {
+ Tcl_Obj* result = Tcl_NewStringObj(errPtr->description, -1);
+ Tcl_SetObjResult(interp, result);
+ Tcl_SetErrorCode(interp, errPtr->code, NULL);
+ reg_error_destruct(errPtr);
+ return TCL_ERROR;
+}
+
+int registry_tcl_detach(Tcl_Interp* interp, sqlite3* db, reg_error* errPtr) {
+ reg_entry** entries;
+ int entry_count = reg_all_entries(db, &entries, errPtr);
+ if (entry_count >= 0) {
+ int i;
+ for (i=0; i<entry_count; i++) {
+ Tcl_DeleteCommand(interp, entries[i]->proc);
+ }
+ if (reg_detach(db, errPtr)) {
+ Tcl_SetAssocData(interp, "registry::attached", NULL, (void*)0);
+ return 1;
+ }
+ }
+ return registry_failed(interp, errPtr);
+}
+
+/**
+ * Deletes the sqlite3 DB associated with interp.
+ *
+ * This function will close an interp's associated DB, although there doesn't
+ * seem to be a way of verifying that it happened properly. This will be a
+ * problem if we get lazy and forget to finalize a sqlite3_stmt somewhere, so
+ * this function will be noisy and complain if we do.
+ *
+ * Then it will leak memory :(
+ */
+static void delete_db(ClientData db, Tcl_Interp* interp) {
+ reg_error error;
+ if (Tcl_GetAssocData(interp, "registry::attached", NULL)) {
+ if (!registry_tcl_detach(interp, (sqlite3*)db, &error)) {
+ fprintf(stderr, error.description);
+ reg_error_destruct(&error);
+ }
+ }
+ if (!reg_close((sqlite3*)db, &error)) {
+ fprintf(stderr, error.description);
+ reg_error_destruct(&error);
+ }
+}
+
+/**
+ * Returns the sqlite3 DB associated with interp.
+ *
+ * The registry keeps its state in a sqlite3 database that is keyed to the
+ * current interpreter context. Different interps will have different instances
+ * of the connection, although I don't know if the Apple-provided sqlite3 lib
+ * was compiled with thread-safety, so I can't be certain that it's safe to use
+ * the registry from multiple threads. I'm pretty sure it's unsafe to alias a
+ * registry function into a different thread.
+ *
+ * If `attached` is set to true, then this function will additionally check if
+ * a real registry database has been attached. If not, then it will return NULL.
+ *
+ * This function sets its own Tcl result.
+ */
+sqlite3* registry_db(Tcl_Interp* interp, int attached) {
+ sqlite3* db = Tcl_GetAssocData(interp, "registry::db", NULL);
+ if (db == NULL) {
+ reg_error error;
+ if (reg_open(&db, &error)) {
+ Tcl_SetAssocData(interp, "registry::db", delete_db, db);
+ } else {
+ registry_failed(interp, &error);
+ return NULL;
+ }
+ }
+ if (attached) {
+ if (!Tcl_GetAssocData(interp, "registry::attached", NULL)) {
+ Tcl_SetErrorCode(interp, "registry::not-open", NULL);
+ Tcl_SetResult(interp, "registry is not open", TCL_STATIC);
+ db = NULL;
+ }
+ }
+ return db;
+}
+
+static int registry_open(ClientData clientData UNUSED, Tcl_Interp* interp,
+ int objc, Tcl_Obj* CONST objv[]) {
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "db-file");
+ return TCL_ERROR;
+ } else {
+ char* path = Tcl_GetString(objv[1]);
+ sqlite3* db = registry_db(interp, 0);
+ reg_error error;
+ if (reg_attach(db, path, &error)) {
+ Tcl_SetAssocData(interp, "registry::attached", NULL,
+ (void*)1);
+ return TCL_OK;
+ } else {
+ return registry_failed(interp, &error);
+ }
+ }
+}
+
+static int registry_close(ClientData clientData UNUSED, Tcl_Interp* interp,
+ int objc, Tcl_Obj* CONST objv[]) {
+ if (objc != 1) {
+ Tcl_WrongNumArgs(interp, 1, objv, NULL);
+ return TCL_ERROR;
+ } else {
+ sqlite3* db = registry_db(interp, 1);
+ if (db == NULL) {
+ return TCL_ERROR;
+ } else {
+ reg_error error;
+ if (registry_tcl_detach(interp, db, &error)) {
+ return TCL_OK;
+ }
+ return registry_failed(interp, &error);
+ }
+ }
+ return TCL_ERROR;
+}
+
+/**
+ * Initializer for the registry lib.
+ *
+ * This function is called automatically by Tcl upon loading of registry.dylib.
+ * It creates the global commands made available in the registry namespace.
+ */
+int Registry_Init(Tcl_Interp* interp) {
+ if (Tcl_InitStubs(interp, "8.3", 0) == NULL) {
+ return TCL_ERROR;
+ }
+ Tcl_CreateObjCommand(interp, "registry::open", registry_open, NULL,
+ NULL);
+ Tcl_CreateObjCommand(interp, "registry::close", registry_close, NULL,
+ NULL);
+ /* Tcl_CreateObjCommand(interp, "registry::graph", GraphCmd, NULL, NULL); */
+ /* Tcl_CreateObjCommand(interp, "registry::item", item_cmd, NULL, NULL); */
+ Tcl_CreateObjCommand(interp, "registry::entry", entry_cmd, NULL, NULL);
+ if (Tcl_PkgProvide(interp, "registry", "2.0") != TCL_OK) {
+ return TCL_ERROR;
+ }
+ return TCL_OK;
+}
Added: trunk/base/src/registry2.0/registry.h
===================================================================
--- trunk/base/src/registry2.0/registry.h (rev 0)
+++ trunk/base/src/registry2.0/registry.h 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,42 @@
+/*
+ * itemobj.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _REGISTRY_H
+#define _REGISTRY_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+#include <sqlite3.h>
+#include <centry.h>
+
+sqlite3* registry_db(Tcl_Interp* interp, int attached);
+int registry_failed(Tcl_Interp* interp, reg_error* errPtr);
+
+#endif /* _REGISTRY_H */
Added: trunk/base/src/registry2.0/sql.c
===================================================================
--- trunk/base/src/registry2.0/sql.c (rev 0)
+++ trunk/base/src/registry2.0/sql.c 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,300 @@
+/*
+ * sql.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+#include <sqlite3.h>
+#include <string.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "creg.h"
+#include "util.h"
+
+/**
+ * Executes a null-terminated list of queries.
+ *
+ * Pass it a list of queries, it'll execute them. This is mainly intended for
+ * initialization, when you have a number of standard queries to execute.
+ */
+int do_queries(sqlite3* db, char** queries, reg_error* errPtr) {
+ char** query;
+ for (query = queries; *query != NULL; query++) {
+ sqlite3_stmt* stmt;
+ if ((sqlite3_prepare(db, *query, -1, &stmt, NULL) != SQLITE_OK)
+ || (sqlite3_step(stmt) != SQLITE_DONE)) {
+ reg_sqlite_error(db, errPtr, *query);
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+ sqlite3_finalize(stmt);
+ }
+ return 1;
+}
+
+/**
+ * REGEXP function for sqlite3.
+ *
+ * Takes two arguments; the first is the value and the second the pattern. If
+ * the pattern is invalid, errors out. Otherwise, returns true if the value
+ * matches the pattern and false otherwise.
+ *
+ * This function is available in sqlite3 as the REGEXP operator.
+ */
+static void sql_regexp(sqlite3_context* context, int argc UNUSED,
+ sqlite3_value** argv) {
+ const char* value = sqlite3_value_text(argv[0]);
+ const char* pattern = sqlite3_value_text(argv[1]);
+ switch (Tcl_RegExpMatch(NULL, value, pattern)) {
+ case 0:
+ sqlite3_result_int(context, 0);
+ break;
+ case 1:
+ sqlite3_result_int(context, 1);
+ break;
+ case -1:
+ sqlite3_result_error(context, "invalid pattern", -1);
+ break;
+ }
+}
+
+/**
+ * NOW function for sqlite3.
+ *
+ * Takes no arguments. Returns the unix timestamp of now.
+ */
+static void sql_now(sqlite3_context* context, int argc UNUSED,
+ sqlite3_value** argv UNUSED) {
+ sqlite3_result_int(context, time(NULL));
+}
+
+static int rpm_vercomp (const char *versionA, int lengthA, const char *versionB,
+ int lengthB) {
+ const char *endA, *endB;
+ const char *ptrA, *ptrB;
+ const char *eptrA, *eptrB;
+
+ if (lengthA < 0)
+ lengthA = strlen(versionA);
+ if (lengthB < 0)
+ lengthB = strlen(versionB);
+
+ /* if versions equal, return zero */
+ if(lengthA == lengthB && !strncmp(versionA, versionB, lengthA))
+ return 0;
+
+ ptrA = versionA;
+ ptrB = versionB;
+ endA = versionA + lengthA;
+ endB = versionB + lengthB;
+ while (ptrA != endA && ptrB != endB) {
+ /* skip all non-alphanumeric characters */
+ while (ptrA != endB && !isalnum(*ptrA))
+ ptrA++;
+ while (ptrB != endB && !isalnum(*ptrB))
+ ptrB++;
+
+ eptrA = ptrA;
+ eptrB = ptrB;
+
+ /* Somewhat arbitrary rules as per RPM's implementation.
+ * This code could be more clever, but we're aiming
+ * for clarity instead. */
+
+ /* If versionB's segment is not a digit segment, but
+ * versionA's segment IS a digit segment, return 1.
+ * (Added for redhat compatibility. See redhat bugzilla
+ * #50977 for details) */
+ if (!isdigit(*ptrB)) {
+ if (isdigit(*ptrA))
+ return 1;
+ }
+
+ /* Otherwise, if the segments are of different types,
+ * return -1 */
+
+ if ((isdigit(*ptrA) && isalpha(*ptrB)) || (isalpha(*ptrA) && isdigit(*ptrB)))
+ return -1;
+
+ /* Find the first segment composed of entirely alphabetical
+ * or numeric members */
+ if (isalpha(*ptrA)) {
+ while (eptrA != endA && isalpha(*eptrA))
+ eptrA++;
+
+ while (eptrB != endB && isalpha(*eptrB))
+ eptrB++;
+ } else {
+ int countA = 0, countB = 0;
+ while (eptrA != endA && isdigit(*eptrA)) {
+ countA++;
+ eptrA++;
+ }
+ while (eptrB != endB && isdigit(*eptrB)) {
+ countB++;
+ eptrB++;
+ }
+
+ /* skip leading '0' characters */
+ while (ptrA != eptrA && *ptrA == '0') {
+ ptrA++;
+ countA--;
+ }
+ while (ptrB != eptrB && *ptrB == '0') {
+ ptrB++;
+ countB--;
+ }
+
+ /* If A is longer than B, return 1 */
+ if (countA > countB)
+ return 1;
+
+ /* If B is longer than A, return -1 */
+ if (countB > countA)
+ return -1;
+ }
+ /* Compare strings lexicographically */
+ while (ptrA != eptrA && ptrB != eptrB && *ptrA == *ptrB) {
+ ptrA++;
+ ptrB++;
+ }
+ if (ptrA != eptrA && ptrB != eptrB)
+ return *ptrA - *ptrB;
+
+ ptrA = eptrA;
+ ptrB = eptrB;
+ }
+
+ /* If both pointers are null, all alphanumeric
+ * characters were identical and only seperating
+ * characters differed. According to RPM, these
+ * version strings are equal */
+ if (ptrA == endA && ptrB == endB)
+ return 0;
+
+ /* If A has unchecked characters, return 1
+ * Otherwise, if B has remaining unchecked characters,
+ * return -1 */
+ if (ptrA != endA)
+ return 1;
+ else
+ return -1;
+}
+
+/**
+ * VERSION collation for sqlite3.
+ *
+ * This function collates text according to pextlib's rpm-vercomp function. This
+ * allows direct comparison and sorting of version columns, such as port.version
+ * and port.revision.
+ *
+ * TODO: share rpm-vercomp properly with pextlib. Currently it's copy-pasted in.
+ */
+static int sql_version(void* userdata UNUSED, int alen, const void* a, int blen,
+ const void* b) {
+ return rpm_vercomp((const char*)a, alen, (const char*)b, blen);
+}
+
+/**
+ * Creates tables in the registry.
+ *
+ * This function is called upon an uninitialized database to create the tables
+ * needed to record state between invocations of `port`.
+ */
+int create_tables(sqlite3* db, reg_error* errPtr) {
+ static char* queries[] = {
+ "BEGIN",
+
+ /* metadata table */
+ "CREATE TABLE registry.metadata (key UNIQUE, value)",
+ "INSERT INTO registry.metadata (key, value) VALUES ('version', 1.000)",
+ "INSERT INTO registry.metadata (key, value) VALUES ('created', NOW())",
+
+ /* ports table */
+ "CREATE TABLE registry.ports ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+ "name, portfile, url, location, epoch, version COLLATE VERSION, "
+ "revision COLLATE VERSION, variants, state, date, "
+ "UNIQUE (name, epoch, version, revision, variants), "
+ "UNIQUE (url, epoch, version, revision, variants)"
+ ")",
+ "CREATE INDEX registry.port_name ON ports "
+ "(name, epoch, version, revision, variants)",
+ "CREATE INDEX registry.port_url ON ports "
+ "(url, epoch, version, revision, variants)",
+ "CREATE INDEX registry.port_state ON ports (state)",
+
+ /* file map */
+ "CREATE TABLE registry.files (id, path UNIQUE, mtime)",
+ "CREATE INDEX registry.file_port ON files (id)",
+
+ "END",
+ NULL
+ };
+ return do_queries(db, queries, errPtr);
+}
+
+/**
+ * Initializes database connection.
+ *
+ * This function creates all the temporary tables used by the registry. It also
+ * registers the user functions and collations declared in "sql.h", making them
+ * available.
+ */
+int init_db(sqlite3* db, reg_error* errPtr) {
+ static char* queries[] = {
+ "BEGIN",
+
+ /* items cache */
+ "CREATE TEMPORARY TABLE items (refcount, proc UNIQUE, name, url, path, "
+ "worker, options, variants)",
+
+ /* indexes list */
+ "CREATE TEMPORARY TABLE indexes (file, name, attached)",
+
+ /* entry addresses */
+ "CREATE TEMPORARY TABLE entries (id, address)",
+
+ "END",
+ NULL
+ };
+
+ /* I'm not error-checking these. I don't think I need to. */
+ sqlite3_create_function(db, "REGEXP", 2, SQLITE_UTF8, NULL, sql_regexp,
+ NULL, NULL);
+ sqlite3_create_function(db, "NOW", 0, SQLITE_ANY, NULL, sql_now, NULL,
+ NULL);
+
+ sqlite3_create_collation(db, "VERSION", SQLITE_UTF8, NULL, sql_version);
+
+ return do_queries(db, queries, errPtr);
+}
+
Added: trunk/base/src/registry2.0/sql.h
===================================================================
--- trunk/base/src/registry2.0/sql.h (rev 0)
+++ trunk/base/src/registry2.0/sql.h 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,42 @@
+/*
+ * sql.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _SQL_H
+#define _SQL_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sqlite3.h>
+
+#include "creg.h"
+
+int create_tables(sqlite3* db, reg_error* errPtr);
+int init_db(sqlite3* db, reg_error* errPtr);
+
+#endif /* _SQL_H */
Added: trunk/base/src/registry2.0/tests/common.tcl
===================================================================
--- trunk/base/src/registry2.0/tests/common.tcl (rev 0)
+++ trunk/base/src/registry2.0/tests/common.tcl 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,51 @@
+# Common functions for test cases
+
+proc test {condition} {
+ uplevel 1 "\
+ puts -nonewline {checking if $condition... }
+ if {\[catch {
+ if {$condition} { \n\
+ puts yes
+ } else { \n\
+ puts no \n\
+ exit 1 \n\
+ } \n\
+ } msg\]} { \n\
+ puts \"caught error: \$msg\" \n\
+ exit 1 \n\
+ }"
+}
+
+proc test_equal {statement value} {
+ uplevel 1 "\
+ puts -nonewline {checking if $statement == \"$value\"... }
+ if {\[catch {
+ set actual $statement
+ if {\$actual == \[subst {$value}\]} { \n\
+ puts yes
+ } else { \n\
+ puts \"no (was \$actual)\" \n\
+ exit 1 \n\
+ } \n\
+ } msg\]} { \n\
+ puts \"caught error: \$msg\" \n\
+ exit 1 \n\
+ }"
+}
+
+proc test_throws {statement error} {
+ uplevel 1 "\
+ puts -nonewline {checking if $statement throws $error... }
+ if {\[catch {$statement} error\]} { \n\
+ if {\$::errorCode == {$error}} {
+ puts yes
+ } else {
+ puts \"no (threw \$::errorCode instead)\" \n\
+ exit 1 \n\
+ } \n\
+ } else { \n\
+ puts {no (did not throw)} \n\
+ exit 1 \n\
+ }"
+}
+
Added: trunk/base/src/registry2.0/tests/entry.tcl
===================================================================
--- trunk/base/src/registry2.0/tests/entry.tcl (rev 0)
+++ trunk/base/src/registry2.0/tests/entry.tcl 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,99 @@
+# Test file for registry::item
+# Syntax:
+# tclsh item.tcl <Pextlib name>
+
+proc main {pextlibname} {
+ load $pextlibname
+
+ file delete -force test.db
+
+ # can't use registry before it's opened
+ test_throws {registry::entry search} registry::not-open
+ registry::open test.db
+
+ # create some (somewhat contrived) ports to play with
+ set vim1 [registry::entry create vim 7.1.000 0 {multibyte +} 0]
+ set vim2 [registry::entry create vim 7.1.002 0 {} 0]
+ set vim3 [registry::entry create vim 7.1.002 0 {multibyte +} 0]
+ set zlib [registry::entry create zlib 1.2.3 1 {} 0]
+ set pcre [registry::entry create pcre 7.1 1 {utf8 +} 0]
+
+ # check that their properties can be set
+ $vim1 state installed
+ $vim2 state installed
+ $vim3 state active
+ $zlib state active
+ $pcre state installed
+
+ # check that their properties can be retrieved
+ test_equal {[$vim1 name]} vim
+ test_equal {[$vim2 epoch]} 0
+ test_equal {[$vim3 version]} 7.1.002
+ test_equal {[$zlib revision]} 1
+ test_equal {[$pcre variants]} {utf8 +}
+
+ set installed [registry::entry installed]
+ set active [registry::entry active]
+
+ # check that installed and active give correct results
+ # have to sort these because their orders aren't defined
+ test_equal {[lsort $installed]} {[lsort "$vim1 $vim2 $vim3 $zlib $pcre"]}
+ test_equal {[lsort $active]} {[lsort "$vim3 $zlib"]}
+
+ # try mapping files and checking their owners
+ $vim3 map
+ $vim3 map /opt/local/bin/vim
+ $vim3 map /opt/local/bin/vimdiff /opt/local/bin/vimtutor
+ test_equal {[registry::entry owner /opt/local/bin/vimtutor]} {$vim3}
+ test_equal {[registry::entry owner /opt/local/bin/emacs]} {}
+
+ test_equal {[$vim3 files]} {/opt/local/bin/vim /opt/local/bin/vimdiff /opt/local/bin/vimtutor}
+ test_equal {[$zlib files]} {}
+
+ # try unmapping and remapping
+ $vim3 unmap /opt/local/bin/vim
+ test_equal {[registry::entry owner /opt/local/bin/vim]} {}
+ $vim3 map /opt/local/bin/vim
+ test_equal {[registry::entry owner /opt/local/bin/vim]} {$vim3}
+
+ # make sure you can't map an already-owned file or unmap one you don't
+ test_throws {$zlib map /opt/local/bin/vim} registry::already-owned
+ test_throws {$zlib unmap /opt/local/bin/vim} registry::not-owned
+ test_throws {$zlib unmap /opt/local/bin/emacs} registry::not-owned
+
+ # delete pcre
+ test_equal {[registry::entry installed pcre]} {$pcre}
+ registry::entry delete $pcre
+ test_throws {[registry::entry open pcre 7.1 1 {utf8 +} 0]} registry::not-found
+ test {![registry::entry exists $pcre]}
+
+ # close vim1
+ test {[registry::entry exists $vim1]}
+ registry::entry close $vim1
+ test {![registry::entry exists $vim1]}
+
+ # close the registry; make sure the registry isn't usable after being closed
+ # and ensure state persists between open sessions
+ registry::close
+ test_throws {registry::entry search} registry::not-open
+ test {![registry::entry exists $vim3]}
+ registry::open test.db
+
+ # check that the same vim is active from before
+ set vim3 [registry::entry active vim]
+ test_equal {[$vim3 version]} 7.1.002
+
+ # find the zlib we inserted before
+ set zlib [registry::entry open zlib 1.2.3 1 {} 0]
+ test {[registry::entry exists $zlib]}
+
+ # check that pcre is gone
+ test_throws {[registry::entry open pcre 7.1 1 {utf8 +} 0]} registry::not-found
+
+ registry::close
+
+ file delete -force test.db
+}
+
+source tests/common.tcl
+main $argv
Added: trunk/base/src/registry2.0/tests/item.tcl
===================================================================
--- trunk/base/src/registry2.0/tests/item.tcl (rev 0)
+++ trunk/base/src/registry2.0/tests/item.tcl 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,51 @@
+# Test file for registry::item
+# Syntax:
+# tclsh item.tcl <Pextlib name>
+
+proc main {pextlibname} {
+ load $pextlibname
+
+ set aesc [registry::item create]
+ set wynn [registry::item create]
+ set eth [registry::item create]
+ set thorn [registry::item create]
+
+ test {[registry::item exists $aesc]}
+ test {![registry::item exists kumquat]}
+ test {![registry::item exists string]}
+
+ $aesc key name aesc
+ $wynn key name wynn
+ $eth key name eth
+ $thorn key name thorn
+
+ test_equal {[$aesc key name]} "aesc"
+ test_equal {[$thorn key name]} "thorn"
+
+ $aesc key variants {}
+ $wynn key variants {}
+ $eth key variants {{big +} {small -}}
+ $thorn key variants {{big +} {small -}}
+
+ test_equal {[registry::item search {name aesc}]} "$aesc"
+ test_equal {[registry::item search {variants {}}]} "$aesc $wynn"
+ test_equal {[registry::item search {variants {{big +}}}]} ""
+ test_equal {[registry::item search {variants {{big +} {small -}}}]} "$eth $thorn"
+ test_equal {[registry::item search {name wynn} {variants {}}]} "$wynn"
+
+ $aesc release
+ $wynn retain
+ $wynn release
+
+ test {![registry::item exists $aesc]}
+ test {[registry::item exists $wynn]}
+
+ $wynn release
+
+ test {![registry::item exists $wynn]}
+
+ file delete -force test.db
+}
+
+source tests/common.tcl
+main $argv
Added: trunk/base/src/registry2.0/util.c
===================================================================
--- trunk/base/src/registry2.0/util.c (rev 0)
+++ trunk/base/src/registry2.0/util.c 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,270 @@
+/*
+ * util.c
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <tcl.h>
+
+#include "util.h"
+
+/**
+ * Generates a unique proc name starting with prefix.
+ *
+ * This function loops through the integers trying to find a name
+ * "<prefix><int>" such that no command with that name exists within the given
+ * Tcl interp context. This behavior is similar to that of the builtin
+ * `interp create` command, and is intended to generate names for created
+ * objects of a similar nature.
+ *
+ * TODO: add a int* parameter so that functions which need large numbers of
+ * unique names can keep track of the lower bound between calls,thereby turning
+ * N^2 to N. It'll be alchemy for the 21st century.
+ */
+char* unique_name(Tcl_Interp* interp, char* prefix) {
+ char* result = malloc(strlen(prefix) + TCL_INTEGER_SPACE + 1);
+ Tcl_CmdInfo info;
+ int i;
+ for (i=0; ; i++) {
+ sprintf(result, "%s%d", prefix, i);
+ if (Tcl_GetCommandInfo(interp, result, &info) == 0) {
+ break;
+ }
+ }
+ return result;
+}
+
+/**
+ * Parses flags given to a Tcl command.
+ *
+ * Starting at `objv[start]`, this function will loop through the remaining
+ * arguments until a non-flag argument is found, or an END_FLAGS flag is found,
+ * or an invalid flag is found. In the first two cases, TCL_OK will be returned
+ * and `start` will be moved to the first non-flag argument; in the third,
+ * TCL_ERROR will be returned.
+ *
+ * It is recommended that all callers of this function include the entry
+ * `{ "--", END_FLAGS }` in the NULL-terminated list `options`. For other,
+ * non-zero flag values in `options`, flags will be bitwise or'ed by that value.
+ *
+ * Note that `alpha -beta gamma -delta epsilon` will be recognized as three
+ * arguments following one flag. This could be changed but would make things
+ * much more difficult.
+ *
+ * TODO: support flags of the form ?-flag value?. No functions currently have a
+ * use for this yet, so it's not a priority, but it should be there for
+ * completeness.
+ */
+int parse_flags(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[], int* start,
+ option_spec options[], int* flags) {
+ int i;
+ int index;
+ *flags = 0;
+ for (i=*start; i<objc; i++) {
+ if (Tcl_GetString(objv[i])[0] != '-') {
+ break;
+ }
+ if (Tcl_GetIndexFromObjStruct(interp, objv[i], options,
+ sizeof(option_spec), "option", 0, &index) == TCL_OK) {
+ if (options[index].flag == END_FLAGS) {
+ i++;
+ break;
+ } else {
+ *flags |= options[index].flag;
+ }
+ } else {
+ return TCL_ERROR;
+ }
+ }
+ *start = i;
+ return TCL_OK;
+}
+
+/**
+ * Retrieves the object whose proc is named by `name`.
+ *
+ * A common design pattern is to have an object be a proc whose clientData
+ * points to the object and whose function points to an object function. This
+ * function retrieves such an object.
+ *
+ * `proc` is used to verify that a proc names an instance of the object. If not,
+ * `type` is used to construct an appropriate error message before it returns
+ * NULL.
+ */
+void* get_object(Tcl_Interp* interp, char* name, char* type,
+ Tcl_ObjCmdProc* proc, reg_error* errPtr) {
+ Tcl_CmdInfo info;
+ if (Tcl_GetCommandInfo(interp, name, &info) && info.objProc == proc){
+ return info.objClientData;
+ } else {
+ errPtr->code = "registry::not-found";
+ errPtr->description = sqlite3_mprintf("could not find %s \"%s\"", type,
+ name);
+ errPtr->free = (reg_error_destructor*)sqlite3_free;
+ return NULL;
+ }
+}
+
+/**
+ * Sets the object whose proc is named by `name`.
+ *
+ * See the documentation for `get_object`. This function registers such an
+ * object, and additionally requires the `deleteProc` argument, which will be
+ * used to free the object.
+ *
+ * TODO: cause the error used here not to leak memory. This probably needs to be
+ * addressed as a generic "reg_error_free" routine
+ */
+int set_object(Tcl_Interp* interp, char* name, void* value, char* type,
+ Tcl_ObjCmdProc* proc, Tcl_CmdDeleteProc* deleteProc, reg_error* errPtr){
+ Tcl_CmdInfo info;
+ if (Tcl_GetCommandInfo(interp, name, &info) && info.objProc == proc) {
+ errPtr->code = "registry::duplicate-object";
+ errPtr->description = sqlite3_mprintf("%s named \"%s\" already exists, "
+ "cannot create", type, name);
+ errPtr->free = (reg_error_destructor*)sqlite3_free;
+ return 0;
+ }
+ Tcl_CreateObjCommand(interp, name, proc, value, deleteProc);
+ return 1;
+}
+
+/**
+ * Reports a sqlite3 error to Tcl.
+ *
+ * Queries the database for the most recent error message and sets it as the
+ * result of the given interpreter. If a query is optionally passed, also
+ * records what it was.
+ */
+void set_sqlite_result(Tcl_Interp* interp, sqlite3* db, const char* query) {
+ Tcl_ResetResult(interp);
+ Tcl_SetErrorCode(interp, "registry::sqlite-error", NULL);
+ if (query == NULL) {
+ Tcl_AppendResult(interp, "sqlite error: ", sqlite3_errmsg(db), NULL);
+ } else {
+ Tcl_AppendResult(interp, "sqlite error executing \"", query, "\": ",
+ sqlite3_errmsg(db), NULL);
+ }
+}
+
+/**
+ * Sets the result of the interpreter to all objects returned by a query.
+ *
+ * This function executes `query` on `db` It expects that the query will return
+ * records of a single column, `rowid`. It will then use `prefix` to construct
+ * unique names for these records, and call `setter` to construct their proc
+ * objects. The result of `interp` will be set to a list of all such objects.
+ *
+ * If TCL_OK is returned, then a list is in the result. If TCL_ERROR is, then an
+ * error is there.
+ */
+int all_objects(Tcl_Interp* interp, sqlite3* db, char* query, char* prefix,
+ set_object_function* setter) {
+ sqlite3_stmt* stmt;
+ if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+ Tcl_Obj* result = Tcl_NewListObj(0, NULL);
+ Tcl_SetObjResult(interp, result);
+ while (sqlite3_step(stmt) == SQLITE_ROW) {
+ sqlite_int64 rowid = sqlite3_column_int64(stmt, 0);
+ char* name = unique_name(interp, prefix);
+ if (setter(interp, name, rowid) == TCL_OK) {
+ Tcl_Obj* element = Tcl_NewStringObj(name, -1);
+ Tcl_ListObjAppendElement(interp, result, element);
+ free(name);
+ } else {
+ free(name);
+ return TCL_ERROR;
+ }
+ }
+ return TCL_OK;
+ } else {
+ sqlite3_free(query);
+ set_sqlite_result(interp, db, query);
+ return TCL_ERROR;
+ }
+ return TCL_ERROR;
+}
+
+int recast(void* userdata, cast_function* fn, free_function* del, void*** outv,
+ void** inv, int inc, reg_error* errPtr) {
+ void** result = malloc(inc*sizeof(void*));
+ int i;
+ for (i=0; i<inc; i++) {
+ if (!fn(userdata, &result[i], inv[i], errPtr)) {
+ if (del != NULL) {
+ for ( ; i>=0; i--) {
+ del(userdata, result[i]);
+ }
+ }
+ free(result);
+ return 0;
+ }
+ }
+ *outv = result;
+ return 1;
+}
+
+static int obj_to_string(void* userdata UNUSED, char** string, Tcl_Obj* obj,
+ reg_error* errPtr UNUSED) {
+ int length;
+ char* value = Tcl_GetStringFromObj(obj, &length);
+ *string = malloc((length+1)*sizeof(char));
+ memcpy(*string, value, length+1);
+ return 1;
+}
+
+void free_string(void* userdata UNUSED, char* string) {
+ free(string);
+}
+
+int list_obj_to_string(char*** strings, const Tcl_Obj** objv, int objc,
+ reg_error* errPtr) {
+ return recast(NULL, (cast_function*)obj_to_string,
+ (free_function*)free_string, (void***)strings, (void**)objv, objc,
+ errPtr);
+}
+
+static int string_to_obj(void* userdata UNUSED, Tcl_Obj** obj, char* string,
+ reg_error* errPtr UNUSED) {
+ *obj = Tcl_NewStringObj(string, -1);
+ return 1;
+}
+
+static void free_obj(void* userdata UNUSED, Tcl_Obj* obj) {
+ Tcl_DecrRefCount(obj);
+}
+
+int list_string_to_obj(Tcl_Obj*** objv, const char** strings, int objc,
+ reg_error* errPtr) {
+ return recast(NULL, (cast_function*)string_to_obj,
+ (free_function*)free_obj, (void***)objv, (void**)strings, objc,
+ errPtr);
+}
Added: trunk/base/src/registry2.0/util.h
===================================================================
--- trunk/base/src/registry2.0/util.h (rev 0)
+++ trunk/base/src/registry2.0/util.h 2007-08-06 20:12:22 UTC (rev 27518)
@@ -0,0 +1,74 @@
+/*
+ * util.h
+ * $Id: $
+ *
+ * Copyright (c) 2007 Chris Pickel <sfiera at macports.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _UTIL_H
+#define _UTIL_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <tcl.h>
+#include <sqlite3.h>
+
+#include "centry.h"
+
+typedef struct {
+ char* option;
+ int flag;
+} option_spec;
+
+#define END_FLAGS 0
+
+char* unique_name(Tcl_Interp* interp, char* prefix);
+
+int parse_flags(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[], int* start,
+ option_spec options[], int* flags);
+
+void* get_object(Tcl_Interp* interp, char* name, char* type,
+ Tcl_ObjCmdProc* proc, reg_error* errPtr);
+int set_object(Tcl_Interp* interp, char* name, void* value, char* type,
+ Tcl_ObjCmdProc* proc, Tcl_CmdDeleteProc* deleteProc, reg_error* errPtr);
+
+void set_sqlite_result(Tcl_Interp* interp, sqlite3* db, const char* query);
+
+typedef int set_object_function(Tcl_Interp* interp, char* name,
+ sqlite_int64 rowid);
+int all_objects(Tcl_Interp* interp, sqlite3* db, char* query, char* prefix,
+ set_object_function* setter);
+
+int recast(void* userdata, cast_function* fn, free_function* del, void*** outv,
+ void** inv, int inc, reg_error* errPtr);
+
+void free_strings(void* userdata UNUSED, char** strings, int count);
+
+int list_obj_to_string(char*** strings, const Tcl_Obj** objv, int objc,
+ reg_error* errPtr);
+int list_string_to_obj(Tcl_Obj*** objv, const char** strings, int objc,
+ reg_error* errPtr);
+
+#endif /* _UTIL_H */
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/macports-changes/attachments/20070806/0281b34a/attachment.html
More information about the macports-changes
mailing list