[26439] users

source_changes at macosforge.org source_changes at macosforge.org
Sat Jun 23 06:10:12 PDT 2007


Revision: 26439
          http://trac.macosforge.org/projects/macports/changeset/26439
Author:   sfiera at macports.org
Date:     2007-06-23 06:10:09 -0700 (Sat, 23 Jun 2007)

Log Message:
-----------
Chris Pickel, GSoC project 2007
* Currently very messy; obviously this will change
* Lots of warnings still
* This can be safely built into MacPorts (as I have on my system), though I'm not yet putting it into trunk

Added Paths:
-----------
    users/sfiera/
    users/sfiera/registry2.0/
    users/sfiera/registry2.0/.gdb_history
    users/sfiera/registry2.0/:w
    users/sfiera/registry2.0/Makefile
    users/sfiera/registry2.0/README
    users/sfiera/registry2.0/centry.c
    users/sfiera/registry2.0/centry.h
    users/sfiera/registry2.0/entry.c
    users/sfiera/registry2.0/entry.h
    users/sfiera/registry2.0/entryobj.c
    users/sfiera/registry2.0/entryobj.h
    users/sfiera/registry2.0/foo.db
    users/sfiera/registry2.0/graph.c
    users/sfiera/registry2.0/graph.h
    users/sfiera/registry2.0/graphobj.c
    users/sfiera/registry2.0/graphobj.h
    users/sfiera/registry2.0/item.c
    users/sfiera/registry2.0/item.h
    users/sfiera/registry2.0/itemobj.c
    users/sfiera/registry2.0/itemobj.h
    users/sfiera/registry2.0/registry.c
    users/sfiera/registry2.0/registry.dylib
    users/sfiera/registry2.0/registry.h
    users/sfiera/registry2.0/sql.c
    users/sfiera/registry2.0/sql.h
    users/sfiera/registry2.0/test.db
    users/sfiera/registry2.0/tests/
    users/sfiera/registry2.0/tests/common.tcl
    users/sfiera/registry2.0/tests/entry.tcl
    users/sfiera/registry2.0/tests/item.tcl
    users/sfiera/registry2.0/util.c
    users/sfiera/registry2.0/util.h

Added: users/sfiera/registry2.0/.gdb_history
===================================================================
--- users/sfiera/registry2.0/.gdb_history	                        (rev 0)
+++ users/sfiera/registry2.0/.gdb_history	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,229 @@
+b ItemKeyHelperCmd
+run
+step
+step
+step
+p db
+step
+p db
+step
+p db
+b ItemDB
+run
+p db
+step
+p db
+step
+p db
+step
+p db
+step
+step
+p sqlite3_error(db)
+sqlite3_errmsg(db)
+p sqlite3_errmsg(db)
+p (char*)sqlite3_errmsg(db)
+step
+load registry.dylib
+run
+bt
+run
+bt
+b EntryObjPropCmd
+run
+next
+c
+n
+p result
+n
+p result
+n sqlite3_column_text(stmt, 0)
+n (char*)sqlite3_column_text(stmt, 0)
+p (char*)sqlite3_column_text(stmt, 0)
+reset
+run
+n
+print entry->rowid
+print entry->db
+print query
+n
+print query
+n
+print result
+print len
+print result
+print resultObj
+n
+print resultObj
+n
+print resultObj
+print Tcl_GetString(resultObj)
+print (char*)Tcl_GetString(resultObj)
+b EntryObjPropCmd
+run
+load registry.dylib
+run
+bt
+run tests/item.tcl
+run tests/item.tcl registry.dylib
+bt
+b item_search
+run tests/item.tcl registry.dylib
+b item_create
+run tests/item.tcl registry.dylib
+n
+c
+c
+b item_key
+dis item_create
+breakpoints
+dis 1
+run
+c
+d 2
+b item_search
+c
+bt
+b item_key
+run
+b item_key
+b item_key
+run tests/item.tcl registry.dylib
+b item_obj_key
+r
+c
+c
+c
+c
+c
+c
+c
+dis 1
+dis 2
+b item_search
+run
+en 2
+run
+c
+c
+c
+c
+c
+c
+c
+run
+c
+c
+c
+c
+c
+c
+n
+p value
+run
+c
+c
+c
+c
+c
+c
+n
+p value
+n
+n
+p key
+p value
+n
+n
+p value
+n
+n
+n
+p stmt
+run
+run
+b reg_all_objects
+b reg_entry_search
+c
+n
+n
+n
+n
+n
+n
+n
+n
+n
+bt
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+n
+p errPtr->description
+n
+p errPtr->description
+n
+p errPtr->description
+n
+c
+n
+n
+p (char*)sqlite3_column_text(stmt,0);
+p (char*)sqlite3_column_text(stmt,0)
+n
+p (char*)sqlite3_column_text(stmt,0)
+n
+n
+p (char*)sqlite3_column_text(stmt,0)
+n
+p (char*)sqlite3_column_text(stmt,0)
+p r
+n
+p (char*)sqlite3_column_text(stmt,0)
+p r
+n
+n
+n
+p (char*)sqlite3_column_text(stmt,0)
+n
+p r
+n
+b entry.c:282
+run
+n
+p db
+n
+p db
+n
+n
+p db
+n
+n
+n
+n
+n
+n
+p entries
+p **entries
+p (*entries)[1]
+p (*entries)[0]
+p *(entries[1])
+p *(entries[2])
+p entries[3]
+p entry_count
+s
+p objs
+p (char*)Tcl_GetString(objs[0])
+p (char*)Tcl_GetString(objs[1])
+p (char*)Tcl_GetString(objs[2])
+p entry_count

Added: users/sfiera/registry2.0/:w
===================================================================
--- users/sfiera/registry2.0/:w	                        (rev 0)
+++ users/sfiera/registry2.0/:w	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,544 @@
+/*
+ * 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 <tcl.h>
+#include <sqlite3.h>
+
+#include "centry.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);
+ */
+static 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);
+}
+
+/**
+ * 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";
+            return NULL;
+    }
+}
+
+/**
+ * Sets `errPtr` according to the last error in `db`.
+ *
+ * TODO: pass an optional query along too
+ */
+static void reg_sqlite_error(sqlite3* db, reg_error* errPtr) {
+    errPtr->code = "registry::sqlite-error";
+    errPtr->description = sqlite3_errmsg(db);
+}
+
+/**
+ * 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)) {
+        sqlite_int64 rowid = sqlite3_last_insert_rowid(db);
+        reg_entry* entry = malloc(sizeof(reg_entry));
+        entry->rowid = rowid;
+        entry->db = db;
+        sqlite3_finalize(stmt);
+        return entry;
+    } else {
+        reg_sqlite_error(db, errPtr);
+        sqlite3_finalize(stmt);
+        return NULL;
+    }
+}
+
+/**
+ * returns the number actually deleted
+ */
+int reg_entry_delete(sqlite3* db, reg_entry** entries, int entry_count,
+        reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    /* BEGIN */
+    char* query = "DELETE FROM registry.ports WHERE rowid=?";
+    if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+        int i;
+        for (i=0; i<entry_count; i++) {
+            if ((sqlite3_bind_int64(stmt, 1, entries[i]->rowid) == SQLITE_OK)
+                    && (sqlite3_step(stmt) == SQLITE_DONE)) {
+                if (sqlite3_changes(db) == 0) {
+                    errPtr->code = "registry::invalid-entry";
+                    errPtr->description = "an invalid entry was passed";
+                    /* COMMIT */
+                    return i;
+                }
+            } else {
+                reg_sqlite_error(db, errPtr);
+                /* COMMIT */
+                return i;
+            }
+            sqlite3_reset(stmt);
+        }
+        /* COMMIT */
+        return entry_count;
+    } else {
+        reg_sqlite_error(db, errPtr);
+        /* ROLLBACK */
+        return 0;
+    }
+}
+
+/*
+ * Frees the entries in `entries`.
+ */
+void reg_entry_free(sqlite3* db, reg_entry** entries, int entry_count) {
+    int i;
+    for (i=0; i<entry_count; i++) {
+        free(entries[i]);
+    }
+}
+
+static int reg_stmt_to_entry(void* userdata, void** entry, void* stmt,
+        reg_error* errPtr) {
+    reg_entry* e = malloc(sizeof(reg_entry));
+    e->db = (sqlite3*)userdata;
+    e->rowid = sqlite3_column_int64(stmt, 0);
+    *entry = e;
+    return 1;
+}
+
+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) {
+        while (1) {
+            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 {
+                        del(NULL, results, result_count);
+                        free(results);
+                        sqlite3_reset(stmt);
+                        reg_sqlite_error(db, errPtr);
+                        return -1;
+                    }
+                case SQLITE_DONE:
+                    break;
+                default:
+                    del(NULL, results, result_count);
+                    free(results);
+                    sqlite3_reset(stmt);
+                    reg_sqlite_error(db, errPtr);
+                    return -1;
+            }
+        }
+        *objects = results;
+        return result_count;
+    } else {
+        reg_sqlite_error(db, errPtr);
+        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.
+ */
+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 = "SELECT rowid FROM registry.ports";
+    int query_len = 32;
+    int query_space = query_len;
+    int result;
+    /* get the strategy */
+    char* op = reg_strategy_op(strategy, errPtr);
+    if (op == NULL) {
+        return -1;
+    }
+    /* build the query */
+    for (i=0; i<key_count; i+=1) {
+        char* cond = sqlite3_mprintf("%s`%q`%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, reg_entry_free, errPtr);
+    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* keys[] = { "state", "name", "version" };
+    char* values[] = { "installed", NULL, NULL };
+    int key_count;
+    if (name == NULL) {
+        key_count = 1;
+    } else {
+        values[1] = name;
+        if (version == NULL) {
+            key_count = 2;
+        } else {
+            key_count = 3;
+            values[2] = version;
+        }
+    }
+    return reg_entry_search(db, keys, values, 0, key_count, entries, errPtr);
+}
+
+/**
+ */
+int reg_entry_active(sqlite3* db, char* name, char* version, 
+        reg_entry*** entries, reg_error* errPtr) {
+    char* keys[] = { "state", "name", "version" };
+    char* values[] = { "active", NULL, NULL };
+    int key_count;
+    if (name == NULL) {
+        key_count = 1;
+    } else {
+        values[1] = name;
+        if (version == NULL) {
+            key_count = 2;
+        } else {
+            key_count = 3;
+            values[2] = version;
+        }
+    }
+    return reg_entry_search(db, keys, values, 0, key_count, entries, errPtr);
+}
+
+int reg_entry_owner(sqlite3* db, char* path, reg_entry** entry,
+        reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    reg_entry* result;
+    char* query = "SELECT port_id FROM files 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:
+                result = malloc(sizeof(reg_entry));
+                result->rowid = sqlite3_column_int64(stmt, 0);
+                result->db = db;
+                sqlite3_finalize(stmt);
+                *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);
+        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.entries "
+            "WHERE rowid=%lld", key, entry->rowid);
+    if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+        int r = sqlite3_step(stmt);
+        const char* column;
+        int len;
+        switch (r) {
+            case SQLITE_ROW:
+                column = sqlite3_column_text(stmt, 0);
+                len = sqlite3_column_bytes(stmt, 0);
+                *value = malloc(1 + len);
+                strcpy(*value, column);
+                sqlite3_finalize(stmt);
+                return 1;
+            case SQLITE_DONE:
+                errPtr->code = "registry::invalid-entry";
+                errPtr->description = "an invalid entry was passed";
+                sqlite3_finalize(stmt);
+                return 0;
+            default:
+                reg_sqlite_error(db, errPtr);
+                sqlite3_finalize(stmt);
+                return 0;
+        }
+    } else {
+        reg_sqlite_error(db, errPtr);
+        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.entries SET `%q` = '%q' "
+            "WHERE rowid=%lld", key, value, entry->rowid);
+    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";
+                        sqlite3_finalize(stmt);
+                        return 0;
+                    default:
+                        reg_sqlite_error(db, errPtr);
+                        sqlite3_finalize(stmt);
+                        return 0;
+                }
+        }
+    } else {
+        reg_sqlite_error(db, errPtr);
+        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 (port_id, path) VALUES (?, ?)";
+    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 1, entry->rowid) == 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";
+                                sqlite3_finalize(stmt);
+                                return i;
+                            default:
+                                reg_sqlite_error(db, errPtr);
+                                sqlite3_finalize(stmt);
+                                return i;
+                        }
+                }
+            } else {
+                reg_sqlite_error(db, errPtr);
+                sqlite3_finalize(stmt);
+                return i;
+            }
+        }
+        sqlite3_finalize(stmt);
+        return file_count;
+    } else {
+        reg_sqlite_error(db, errPtr);
+        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 port_id=? AND path=?";
+    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 1, entry->rowid) == 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->code = "this entry does not own the given "
+                                "file";
+                            sqlite3_finalize(stmt);
+                            return i;
+                        } else {
+                            sqlite3_reset(stmt);
+                            continue;
+                        }
+                    default:
+                        reg_sqlite_error(db, errPtr);
+                        sqlite3_finalize(stmt);
+                        return i;
+                }
+            } else {
+                reg_sqlite_error(db, errPtr);
+                sqlite3_finalize(stmt);
+                return i;
+            }
+        }
+        sqlite3_finalize(stmt);
+        return file_count;
+    } else {
+        reg_sqlite_error(db, errPtr);
+        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 files WHERE port_id=?";
+    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 1, entry->rowid) == SQLITE_OK)) {
+        char** result = malloc(10*sizeof(char*));
+        int result_count = 0;
+        int result_size = 10;
+        while (1) {
+            char* element;
+            const char* column;
+            int len, i, r;
+            r = sqlite3_step(stmt);
+            switch (r) {
+                case SQLITE_ROW:
+                    column = sqlite3_column_text(stmt, 0);
+                    len = sqlite3_column_bytes(stmt, 0);
+                    element = malloc(1+len);
+                    memcpy(element, column, len+1);
+                    reg_listcat(&result, &result_count, &result_size, element);
+                    continue;
+                case SQLITE_DONE:
+                    break;
+                default:
+                    for (i=0; i<result_count; i++) {
+                        free(result[i]);
+                    }
+                    free(result);
+                    reg_sqlite_error(db, errPtr);
+                    sqlite3_finalize(stmt);
+                    return -1;
+            }
+        }
+        *files = result;
+        return result_count;
+    } else {
+        reg_sqlite_error(db, errPtr);
+        return -1;
+    }
+}
+

Added: users/sfiera/registry2.0/Makefile
===================================================================
--- users/sfiera/registry2.0/Makefile	                        (rev 0)
+++ users/sfiera/registry2.0/Makefile	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,15 @@
+OBJS=       registry.o util.o sql.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: users/sfiera/registry2.0/README
===================================================================
--- users/sfiera/registry2.0/README	                        (rev 0)
+++ users/sfiera/registry2.0/README	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,8 @@
+Bad assumptions in registry2.0:
+
+* sqlite3_step returns SQLITE_ERROR only on constraint violations, and never
+  returns SQLITE_BUSY or SQLITE_MISUSE. In actuality there are more
+  possibilities, but they generally come up only when multiple sqlite3*
+  instances are accessing the db.
+
+* 

Added: users/sfiera/registry2.0/centry.c
===================================================================
--- users/sfiera/registry2.0/centry.c	                        (rev 0)
+++ users/sfiera/registry2.0/centry.c	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,574 @@
+/*
+ * 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"
+
+/**
+ * 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;
+}
+
+void reg_error_destruct(reg_error* errPtr) {
+    if (errPtr->free) {
+        errPtr->free(errPtr->description);
+    }
+}
+
+/**
+ * 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;
+    }
+}
+
+/**
+ * Sets `errPtr` according to the last error in `db`.
+ *
+ * TODO: implement error_free so this won't leak
+ */
+void reg_sqlite_error(sqlite3* db, reg_error* errPtr, char* query) {
+    errPtr->code = "registry::sqlite-error";
+    errPtr->free = 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);
+    }
+}
+
+/**
+ * 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)) {
+        sqlite_int64 rowid = sqlite3_last_insert_rowid(db);
+        reg_entry* entry = malloc(sizeof(reg_entry));
+        entry->rowid = rowid;
+        entry->db = db;
+        sqlite3_finalize(stmt);
+        return entry;
+    } else {
+        reg_sqlite_error(db, errPtr, query);
+        sqlite3_finalize(stmt);
+        return NULL;
+    }
+}
+
+/**
+ * returns the number actually deleted
+ */
+int reg_entry_delete(sqlite3* db, reg_entry** entries, int entry_count,
+        reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    /* BEGIN */
+    char* query = "DELETE FROM registry.ports WHERE rowid=?";
+    if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+        int i;
+        for (i=0; i<entry_count; i++) {
+            if ((sqlite3_bind_int64(stmt, 1, entries[i]->rowid) == SQLITE_OK)
+                    && (sqlite3_step(stmt) == SQLITE_DONE)) {
+                if (sqlite3_changes(db) == 0) {
+                    errPtr->code = "registry::invalid-entry";
+                    errPtr->description = "an invalid entry was passed";
+                    errPtr->free = NULL;
+                    reg_entry_free(db, entries, i);
+                    /* COMMIT */
+                    return i;
+                }
+            } else {
+                reg_sqlite_error(db, errPtr, query);
+                reg_entry_free(db, entries, i);
+                /* COMMIT */
+                return i;
+            }
+            sqlite3_reset(stmt);
+        }
+        reg_entry_free(db, entries, entry_count);
+        /* COMMIT */
+        return entry_count;
+    } else {
+        reg_sqlite_error(db, errPtr, query);
+        /* ROLLBACK */
+        return 0;
+    }
+}
+
+/*
+ * Frees the entries in `entries`.
+ */
+void reg_entry_free(sqlite3* db UNUSED, reg_entry** entries, int entry_count) {
+    int i;
+    for (i=0; i<entry_count; i++) {
+        free(entries[i]);
+    }
+}
+
+static int reg_stmt_to_entry(void* userdata, void** entry, void* stmt,
+        reg_error* errPtr UNUSED) {
+    reg_entry* e = malloc(sizeof(reg_entry));
+    e->db = (sqlite3*)userdata;
+    e->rowid = sqlite3_column_int64(stmt, 0);
+    *entry = e;
+    return 1;
+}
+
+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 = SQLITE_ROW;
+    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) {
+        while (r != SQLITE_DONE) {
+            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 {
+                        del(NULL, results, result_count);
+                        free(results);
+                        sqlite3_finalize(stmt);
+                        return -1;
+                    }
+                case SQLITE_DONE:
+                    break;
+                default:
+                    del(NULL, results, result_count);
+                    free(results);
+                    sqlite3_reset(stmt);
+                    reg_sqlite_error(db, errPtr, query);
+                    return -1;
+            }
+        }
+        *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 = 0;
+    int query_space = 32;
+    int result;
+    /* get the strategy */
+    char* op = reg_strategy_op(strategy, errPtr);
+    if (op == NULL) {
+        return -1;
+    }
+    query = malloc(33);
+    reg_strcat(&query, &query_len, &query_space,
+            "SELECT rowid FROM registry.ports");
+    /* build the query */
+    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);
+    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* keys[] = { "state", "name", "version" };
+    char* values[] = { "installed", NULL, NULL };
+    int key_count;
+    if (name == NULL) {
+        key_count = 1;
+    } else {
+        values[1] = name;
+        if (version == NULL) {
+            key_count = 2;
+        } else {
+            key_count = 3;
+            values[2] = version;
+        }
+    }
+    return reg_entry_search(db, keys, values, 0, key_count, entries, errPtr);
+}
+
+/**
+ */
+int reg_entry_active(sqlite3* db, char* name, char* version, 
+        reg_entry*** entries, reg_error* errPtr) {
+    char* keys[] = { "state", "name", "version" };
+    char* values[] = { "active", NULL, NULL };
+    int key_count;
+    if (name == NULL) {
+        key_count = 1;
+    } else {
+        values[1] = name;
+        if (version == NULL) {
+            key_count = 2;
+        } else {
+            key_count = 3;
+            values[2] = version;
+        }
+    }
+    return reg_entry_search(db, keys, values, 0, key_count, entries, errPtr);
+}
+
+int reg_entry_owner(sqlite3* db, char* path, reg_entry** entry,
+        reg_error* errPtr) {
+    sqlite3_stmt* stmt;
+    reg_entry* result;
+    char* query = "SELECT port_id FROM files 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:
+                result = malloc(sizeof(reg_entry));
+                result->rowid = sqlite3_column_int64(stmt, 0);
+                result->db = db;
+                sqlite3_finalize(stmt);
+                *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.entries "
+            "WHERE rowid=%lld", key, entry->rowid);
+    if (sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK) {
+        int r = sqlite3_step(stmt);
+        const char* column;
+        int len;
+        switch (r) {
+            case SQLITE_ROW:
+                column = sqlite3_column_text(stmt, 0);
+                len = sqlite3_column_bytes(stmt, 0);
+                *value = malloc(1 + len);
+                strcpy(*value, column);
+                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.entries SET `%q` = '%q' "
+            "WHERE rowid=%lld", key, value, entry->rowid);
+    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 (port_id, path) VALUES (?, ?)";
+    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 1, entry->rowid) == 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 port_id=? AND path=?";
+    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 1, entry->rowid) == 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->code = "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 files WHERE port_id=?";
+    if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 1, entry->rowid) == SQLITE_OK)) {
+        char** result = malloc(10*sizeof(char*));
+        int result_count = 0;
+        int result_space = 10;
+        while (1) {
+            char* element;
+            const char* column;
+            int len, i, r;
+            r = sqlite3_step(stmt);
+            switch (r) {
+                case SQLITE_ROW:
+                    column = sqlite3_column_text(stmt, 0);
+                    len = sqlite3_column_bytes(stmt, 0);
+                    element = malloc(1+len);
+                    memcpy(element, column, len+1);
+                    reg_listcat((void*)&result, &result_count, &result_space,
+                            element);
+                    continue;
+                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;
+            }
+        }
+        sqlite3_finalize(stmt);
+        *files = result;
+        return result_count;
+    } else {
+        reg_sqlite_error(db, errPtr, query);
+        sqlite3_finalize(stmt);
+        return -1;
+    }
+}
+

Added: users/sfiera/registry2.0/centry.h
===================================================================
--- users/sfiera/registry2.0/centry.h	                        (rev 0)
+++ users/sfiera/registry2.0/centry.h	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sqlite3.h>
+
+typedef void reg_error_destructor(char* description);
+
+typedef struct {
+    char* code;
+    const char* description;
+    reg_error_destructor* free;
+} reg_error;
+
+typedef struct {
+    sqlite_int64 rowid;
+    sqlite3* db;
+} reg_entry;
+
+typedef int (cast_function)(void* userdata, void** dst, void* src,
+        reg_error* errPtr);
+typedef void (free_function)(void* userdata, void** list, int count);
+
+void reg_error_destruct(reg_error* errPtr);
+
+reg_entry* reg_entry_create(sqlite3* db, char* name, char* version,
+        char* revision, char* variants, char* epoch, reg_error* errPtr);
+
+int reg_entry_delete(sqlite3* db, reg_entry** entries, int entry_count,
+        reg_error* errPtr);
+
+void reg_entry_free(sqlite3* db, reg_entry** entries, int entry_count);
+
+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, char* version, 
+        reg_entry*** entries, reg_error* errPtr);
+
+int reg_entry_owner(sqlite3* db, char* path, reg_entry** entry,
+        reg_error* errPtr);

Added: users/sfiera/registry2.0/entry.c
===================================================================
--- users/sfiera/registry2.0/entry.c	                        (rev 0)
+++ users/sfiera/registry2.0/entry.c	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,514 @@
+/*
+ * 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"
+
+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;
+}
+
+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, &clientData, 1);
+}
+
+static int set_entry(Tcl_Interp* interp, char* name, reg_entry* entry,
+        reg_error* errPtr) {
+    return set_object(interp, name, entry, "entry", entry_obj_cmd, delete_entry,
+                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) {
+            char* name = unique_name(interp, "registry::entry");
+            if (set_entry(interp, name, entry, &error)) {
+                Tcl_Obj* res = Tcl_NewStringObj(name, -1);
+                Tcl_SetObjResult(interp, res);
+                free(name);
+                return TCL_OK;
+            } else {
+                reg_error ignored;
+                free(name);
+                reg_entry_delete(db, &entry, 1, &ignored);
+            }
+        }
+        return registry_failed(interp, &error);
+    }
+}
+
+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;
+    }
+}
+
+/**
+ * TODO: don't create procs for entries that already have them
+ */
+static int entry_to_obj(Tcl_Interp* interp, Tcl_Obj** obj, reg_entry* entry,
+        reg_error* errPtr) {
+    sqlite3* db = registry_db(interp, 0);
+    if (db == NULL) {
+        return 0;
+    } else {
+        sqlite3_stmt* stmt;
+        char* query = "SELECT proc FROM entry_procs WHERE entry_id=?";
+        if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+                && (sqlite3_bind_int64(stmt, 1, entry->rowid) == SQLITE_OK)) {
+            int r = sqlite3_step(stmt);
+            char* name;
+            switch (r) {
+                case SQLITE_ROW:
+                    name = sqlite3_column_text(stmt, 0);
+                    *obj = Tcl_NewStringObj(name,
+                            sqlite3_column_bytes(stmt, 0));
+                    sqlite3_finalize(stmt);
+                    return 1;
+                case SQLITE_DONE:
+                    name = unique_name(interp, "registry::entry");
+                    sqlite3_finalize(stmt);
+                    if (set_entry(interp, name, entry, errPtr)) {
+                        /* insert record (not currently error-checked)
+                         * TODO: check it */
+                        sqlite3_prepare(db, "INSERT INTO entry_procs (entry_id,"
+                                "proc) VALUES (?,?)", -1, &stmt, NULL);
+                        sqlite3_bind_int64(stmt, 1, entry->rowid);
+                        sqlite3_bind_text(stmt, 2, name, -1, SQLITE_STATIC);
+                        sqlite3_step(stmt);
+                        sqlite3_finalize(stmt);
+                        *obj = Tcl_NewStringObj(name, -1);
+                        free(name);
+                        return 1;
+                    }
+                    free(name);
+                    break;
+                default:
+                    reg_sqlite_error(db, errPtr, query);
+                    break;
+            }
+        } else {
+            reg_sqlite_error(db, errPtr, query);
+        }
+        sqlite3_finalize(stmt);
+        return 0;
+    }
+}
+
+/**
+ * registry::entry delete ?entry ...?
+ *
+ * Deletes an entry from the registry (then closes it).
+ *
+ * TODO: ensure that other open instances of the entry are invalidated, because
+ * they won't work once the entry is deleted.
+ */
+static int entry_delete(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]) {
+    sqlite3* db = registry_db(interp, 1);
+    if (db == NULL) {
+        return TCL_ERROR;
+    } else {
+        reg_entry** entries;
+        reg_error error;
+        if (recast(interp, obj_to_entry, NULL, &entries, &(objv[2]), objc-2,
+                    &error)) {
+            if (reg_entry_delete(db, entries, objc-2, &error)) {
+                free(entries);
+                return TCL_OK;
+            }
+            free(entries);
+        }
+        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 || objc > 8) {
+        Tcl_WrongNumArgs(interp, 1, objv, "open portname version revision "
+                "variants epoch ?name?");
+        return TCL_ERROR;
+    } else if (db == NULL) {
+        return TCL_ERROR;
+    } else {
+        sqlite3_stmt* stmt;
+        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]);
+        char* query = "SELECT rowid FROM registry.ports 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)) {
+            if (sqlite3_step(stmt) == SQLITE_ROW) {
+                sqlite_int64 entry = sqlite3_column_int64(stmt, 0);
+                sqlite3_finalize(stmt);
+                if (objc == 8) {
+                    \* registry::entry open ... name *\
+                    char* name = Tcl_GetString(objv[7]);
+                    if (set_entry(interp, name, entry) == TCL_OK){
+                        Tcl_SetObjResult(interp, objv[7]);
+                        return TCL_OK;
+                    }
+                } else {
+                    \* registry::entry open ... *\
+                    char* name = unique_name(interp, "registry::entry");
+                    if (set_entry(interp, name, entry) == TCL_OK) {
+                        Tcl_Obj* res = Tcl_NewStringObj(name, -1);
+                        Tcl_SetObjResult(interp, res);
+                        free(name);
+                        return TCL_OK;
+                    }
+                    free(name);
+                }
+            } else {
+                Tcl_SetResult(interp, "entry not found", TCL_STATIC);
+            }
+        } else {
+            set_sqlite_result(interp, db, query);
+            sqlite3_finalize(stmt);
+        }
+    }
+    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[]) {
+    int i;
+    for (i=2; i<objc; i++) {
+        reg_error error;
+        char* proc = Tcl_GetString(objv[i]);
+        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+=2) {
+            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;
+            recast(interp, entry_to_obj, NULL, &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* query;
+        Tcl_Obj* result = Tcl_NewListObj(0, NULL);
+        Tcl_SetObjResult(interp, result);
+        if (objc == 2) {
+            query = sqlite3_mprintf("SELECT rowid FROM registry.ports "
+                    "WHERE (state == 'installed' OR state == 'active')");
+        } else if (objc == 3) {
+            char* name = Tcl_GetString(objv[2]);
+            query = sqlite3_mprintf("SELECT rowid FROM registry.ports "
+                    "WHERE (state == 'installed' OR state == 'active') "
+                    "AND name='%q'", name);
+        } else {
+            char* name = Tcl_GetString(objv[2]);
+            char* version = Tcl_GetString(objv[3]);
+            query = sqlite3_mprintf("SELECT rowid FROM registry.ports "
+                    "WHERE (state == 'installed' OR state == 'active') "
+                    "AND name='%q' AND version='%q'", name, version);
+        }
+        if (all_objects(interp, db, query, "registry::entry", set_entry)
+                == TCL_OK) {
+            free(query);
+            \* error if list length == 0 *\
+            return TCL_OK;
+        }
+        free(query);
+    }
+    return TCL_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* query;
+        if (objc == 2) {
+            query = sqlite3_mprintf("SELECT rowid FROM registry.ports "
+                    "WHERE state == 'active'");
+        } else {
+            char* name = Tcl_GetString(objv[2]);
+            query = sqlite3_mprintf("SELECT rowid FROM registry.ports "
+                    "WHERE state == 'active' AND name='%q'", name);
+        }
+        if (all_objects(interp, db, query, "registry::entry", set_entry)
+                == TCL_OK) {
+            free(query);
+            \* error if list length == 0 *\
+            return TCL_OK;
+        }
+        free(query);
+    }
+    return TCL_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 {
+        sqlite3_stmt* stmt;
+        int len;
+        char* path = Tcl_GetStringFromObj(objv[2], &len);
+        char* query = "SELECT port_id FROM files WHERE path=?";
+        if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+                && (sqlite3_bind_text(stmt, 1, path, len, SQLITE_STATIC)
+                    == SQLITE_OK)) {
+            if (sqlite3_step(stmt) == SQLITE_ROW) {
+                sqlite_int64 rowid = sqlite3_column_int64(stmt, 0);
+                Tcl_Obj* resultObj = Tcl_NewStringObj(result, len);
+                Tcl_SetObjResult(interp, resultObj);
+                sqlite3_finalize(stmt);
+            } else {
+                Tcl_Obj* resultObj = Tcl_NewStringObj("", 0);
+                Tcl_SetObjResult(interp, resultObj);
+                sqlite3_finalize(stmt);
+            }
+            return TCL_OK;
+        } else {
+            set_sqlite_result(interp, db, query);
+            sqlite3_finalize(stmt);
+        }
+        return TCL_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 },
+    */
+    { 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: users/sfiera/registry2.0/entry.h
===================================================================
--- users/sfiera/registry2.0/entry.h	                        (rev 0)
+++ users/sfiera/registry2.0/entry.h	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,36 @@
+/*
+ * 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
+
+#include <tcl.h>
+
+int entry_cmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]);
+
+#endif /* _ENTRY_H */

Added: users/sfiera/registry2.0/entryobj.c
===================================================================
--- users/sfiera/registry2.0/entryobj.c	                        (rev 0)
+++ users/sfiera/registry2.0/entryobj.c	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,270 @@
+/*
+ * 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 "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 name ?value? */
+static int entry_obj_prop(Tcl_Interp* interp, entry_t* entry, int objc,
+        Tcl_Obj* CONST objv[]) {
+    int index;
+    if (objc == 2) {
+        /* ${entry} prop name; return the current value */
+        if (Tcl_GetIndexFromObj(interp, objv[1], entry_props, "prop", 0, &index)
+                == TCL_OK) {
+            sqlite3_stmt* stmt;
+            char* prop = Tcl_GetString(objv[1]);
+            char* query = sqlite3_mprintf("SELECT %s FROM registry.ports WHERE "
+                    "rowid='%lld'", prop, entry->rowid);
+            if ((sqlite3_prepare(entry->db, query, -1, &stmt, NULL)
+                        == SQLITE_OK)
+                    && (sqlite3_step(stmt) == SQLITE_ROW)) {
+                /* eliminate compiler warning about signedness */
+                const char* result = sqlite3_column_text(stmt, 0);
+                int len = sqlite3_column_bytes(stmt, 0);
+                Tcl_Obj* resultObj = Tcl_NewStringObj(result, len);
+                Tcl_SetObjResult(interp, resultObj);
+                sqlite3_finalize(stmt);
+                return TCL_OK;
+            } else {
+                set_sqlite_result(interp, entry->db, query);
+                sqlite3_finalize(stmt);
+            }
+        }
+    } else if (objc == 3) {
+        /* ${entry} prop name value; set a new value */
+        if (Tcl_GetIndexFromObj(interp, objv[1], entry_props, "prop", 0, &index)
+                == TCL_OK) {
+            sqlite3_stmt* stmt;
+            char* prop = Tcl_GetString(objv[1]);
+            char* value = Tcl_GetString(objv[2]);
+            char* query = sqlite3_mprintf("UPDATE registry.ports SET %s='%q' "
+                    "WHERE rowid='%lld'", prop, value, entry->rowid);
+            if ((sqlite3_prepare(entry->db, query, -1, &stmt, NULL)
+                        == SQLITE_OK)
+                    && (sqlite3_step(stmt) == SQLITE_DONE)) {
+                sqlite3_finalize(stmt);
+                return TCL_OK;
+            } else {
+                set_sqlite_result(interp, entry->db, query);
+                sqlite3_finalize(stmt);
+            }
+        }
+    } else {
+        Tcl_WrongNumArgs(interp, 2, objv, "?value?");
+    }
+    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, entry_t* entry, int objc,
+        Tcl_Obj* CONST objv[]) {
+    sqlite3_stmt* stmt;
+    char* query = "INSERT INTO files (port_id, path) VALUES (?, ?)";
+    /* BEGIN */
+    if ((sqlite3_prepare(entry->db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int(stmt, 1, entry->rowid) == SQLITE_OK)) {
+        int i;
+        for (i=2; i<objc; i++) {
+            int len;
+            char* path = Tcl_GetStringFromObj(objv[i], &len);
+            if ((sqlite3_bind_text(stmt, 2, path, len, SQLITE_STATIC)
+                        != SQLITE_OK)
+                    || (sqlite3_step(stmt) != SQLITE_DONE)) {
+                set_sqlite_result(interp, entry->db, query);
+                sqlite3_finalize(stmt);
+                /* END or ROLLBACK? */
+                return TCL_ERROR;
+            }
+            sqlite3_reset(stmt);
+        }
+        sqlite3_finalize(stmt);
+        /* END */
+        return TCL_OK;
+    } else {
+        set_sqlite_result(interp, entry->db, query);
+        sqlite3_finalize(stmt);
+        /* END */
+        return TCL_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, entry_t* entry, int objc,
+        Tcl_Obj* CONST objv[]) {
+    sqlite3_stmt* stmt;
+    char* query = "DELETE FROM files WHERE port_id=? AND path=?";
+    /* BEGIN */
+    if (sqlite3_prepare(entry->db, query, -1, &stmt, NULL) == SQLITE_OK) {
+        int i;
+        for (i=2; i<objc; i++) {
+            int len;
+            char* path = Tcl_GetStringFromObj(objv[i], &len);
+            if ((sqlite3_bind_int(stmt, 1, entry->rowid) != SQLITE_OK)
+                    || (sqlite3_bind_text(stmt, 2, path, len, SQLITE_STATIC)
+                        != SQLITE_OK)
+                    || (sqlite3_step(stmt) != SQLITE_DONE)) {
+                if (sqlite3_reset(stmt) == SQLITE_CONSTRAINT) {
+                    Tcl_AppendResult(interp, "an existing port owns \"", path,
+                            "\"", NULL);
+                } else {
+                    set_sqlite_result(interp, entry->db, query);
+                }
+                sqlite3_finalize(stmt);
+                /* END or ROLLBACK? */
+                return TCL_ERROR;
+            }
+            if (sqlite3_changes(entry->db) == 0) {
+                Tcl_AppendResult(interp, Tcl_GetString(objv[i]), " is not "
+                        "mapped to this entry", NULL);
+                sqlite3_finalize(stmt);
+                /* END or ROLLBACK? */
+                return TCL_ERROR;
+            }
+            sqlite3_reset(stmt);
+        }
+        sqlite3_finalize(stmt);
+        /* END */
+        return TCL_OK;
+    } else {
+        set_sqlite_result(interp, entry->db, query);
+        sqlite3_finalize(stmt);
+        /* END */
+        return TCL_ERROR;
+    }
+}
+
+static int entry_obj_files(Tcl_Interp* interp, entry_t* entry, int objc,
+        Tcl_Obj* CONST objv[]) {
+    sqlite3_stmt* stmt;
+    char* query = "SELECT path FROM files WHERE port_id=?";
+    if (objc != 2) {
+        Tcl_WrongNumArgs(interp, 1, objv, "files");
+        return TCL_ERROR;
+    }
+    if ((sqlite3_prepare(entry->db, query, -1, &stmt, NULL) == SQLITE_OK)
+            && (sqlite3_bind_int64(stmt, 1, entry->rowid) == SQLITE_OK)) {
+        Tcl_Obj* result = Tcl_NewListObj(0, NULL);
+        while (sqlite3_step(stmt) == SQLITE_ROW) {
+            char* value = sqlite3_column_text(stmt, 0);
+            int len = sqlite3_column_bytes(stmt, 0);
+            Tcl_Obj* element = Tcl_NewStringObj(value, len);
+            Tcl_ListObjAppendElement(interp, result, element);
+        }
+        sqlite3_finalize(stmt);
+        Tcl_SetObjResult(interp, result);
+        return TCL_OK;
+    } else {
+        set_sqlite_result(interp, entry->db, query);
+        sqlite3_finalize(stmt);
+        return TCL_ERROR;
+    }
+}
+
+typedef struct {
+    char* name;
+    int (*function)(Tcl_Interp* interp, entry_t* 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, (entry_t*)clientData, objc, objv);
+    }
+    return TCL_ERROR;
+}

Added: users/sfiera/registry2.0/entryobj.h
===================================================================
--- users/sfiera/registry2.0/entryobj.h	                        (rev 0)
+++ users/sfiera/registry2.0/entryobj.h	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,44 @@
+/*
+ * 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
+
+#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: users/sfiera/registry2.0/foo.db
===================================================================
(Binary files differ)


Property changes on: users/sfiera/registry2.0/foo.db
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: users/sfiera/registry2.0/graph.c
===================================================================
--- users/sfiera/registry2.0/graph.c	                        (rev 0)
+++ users/sfiera/registry2.0/graph.c	2007-06-23 13:10:09 UTC (rev 26439)
@@ -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: users/sfiera/registry2.0/graph.h
===================================================================
--- users/sfiera/registry2.0/graph.h	                        (rev 0)
+++ users/sfiera/registry2.0/graph.h	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,36 @@
+/*
+ * 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
+
+#include <tcl.h>
+
+int GraphCmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]);
+
+#endif /* _GRAPH_CMD_H */

Added: users/sfiera/registry2.0/graphobj.c
===================================================================
--- users/sfiera/registry2.0/graphobj.c	                        (rev 0)
+++ users/sfiera/registry2.0/graphobj.c	2007-06-23 13:10:09 UTC (rev 26439)
@@ -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: users/sfiera/registry2.0/graphobj.h
===================================================================
--- users/sfiera/registry2.0/graphobj.h	                        (rev 0)
+++ users/sfiera/registry2.0/graphobj.h	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,40 @@
+/*
+ * 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
+
+#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: users/sfiera/registry2.0/item.c
===================================================================
--- users/sfiera/registry2.0/item.c	                        (rev 0)
+++ users/sfiera/registry2.0/item.c	2007-06-23 13:10:09 UTC (rev 26439)
@@ -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: users/sfiera/registry2.0/item.h
===================================================================
--- users/sfiera/registry2.0/item.h	                        (rev 0)
+++ users/sfiera/registry2.0/item.h	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,36 @@
+/*
+ * 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
+
+#include <tcl.h>
+
+int item_cmd(ClientData clientData UNUSED, Tcl_Interp* interp, int objc,
+        Tcl_Obj* CONST objv[]);
+
+#endif /* _ITEM_CMD_H */

Added: users/sfiera/registry2.0/itemobj.c
===================================================================
--- users/sfiera/registry2.0/itemobj.c	                        (rev 0)
+++ users/sfiera/registry2.0/itemobj.c	2007-06-23 13:10:09 UTC (rev 26439)
@@ -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: users/sfiera/registry2.0/itemobj.h
===================================================================
--- users/sfiera/registry2.0/itemobj.h	                        (rev 0)
+++ users/sfiera/registry2.0/itemobj.h	2007-06-23 13:10:09 UTC (rev 26439)
@@ -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 _ITEM_OBJ_CMD_H
+#define _ITEM_OBJ_CMD_H
+
+#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: users/sfiera/registry2.0/registry.c
===================================================================
--- users/sfiera/registry2.0/registry.c	                        (rev 0)
+++ users/sfiera/registry2.0/registry.c	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,185 @@
+/*
+ * 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"
+
+/**
+ * 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 UNUSED) {
+    if (sqlite3_close((sqlite3*)db) != SQLITE_OK) {
+        fprintf(stderr, "error: registry db not closed correctly (%s)\n",
+                sqlite3_errmsg((sqlite3*)db));
+    }
+}
+
+/**
+ * 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) {
+        if (sqlite3_open(NULL, &db) == SQLITE_OK) {
+            if (init_db(interp, db) == TCL_OK) {
+                Tcl_SetAssocData(interp, "registry::db", delete_db, db);
+            } else {
+                sqlite3_close(db);
+                db = NULL;
+            }
+        } else {
+            set_sqlite_result(interp, db, NULL);
+        }
+    }
+    if (attached) {
+        if (!Tcl_GetAssocData(interp, "registry::attached", 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 {
+        int needsInit = (Tcl_FSAccess(objv[1], F_OK) != 0);
+        char* file = Tcl_GetString(objv[1]);
+        /*
+         * If registry needs initialization, we need write access. However, it
+         * seems you can't test W_OK for a file that doesn't yet exist. We need
+         * a better way to test if a location is writable, then. Maybe strip off
+         * the last path component?
+         */
+        /* if (!needsInit || (Tcl_FSAccess(objv[1], W_OK) == 0)) { */
+        if (1) {
+            sqlite3* db = registry_db(interp, 0);
+            sqlite3_stmt* stmt;
+            char* query = sqlite3_mprintf("ATTACH DATABASE '%q' AS registry",
+                    file);
+            if ((sqlite3_prepare(db, query, -1, &stmt, NULL) == SQLITE_OK)
+                    && (sqlite3_step(stmt) == SQLITE_DONE)) {
+                if (!needsInit || (create_tables(interp, db) == TCL_OK)) {
+                    Tcl_SetAssocData(interp, "registry::attached", NULL,
+                            (void*)1);
+                    return TCL_OK;
+                }
+            } else {
+                set_sqlite_result(interp, db, query);
+            }
+        } else {
+            Tcl_ResetResult(interp);
+            Tcl_AppendResult(interp, "port registry doesn't exist at ", file,
+                    " and couldn't write layout to this location", NULL);
+        }
+    }
+    return TCL_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) {
+            Tcl_SetResult(interp, "registry is not open", TCL_STATIC);
+        } else {
+            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);
+                Tcl_SetAssocData(interp, "registry::attached", NULL, (void*)0);
+                return TCL_OK;
+            } else {
+                set_sqlite_result(interp, db, query);
+                sqlite3_finalize(stmt);
+            }
+        }
+    }
+    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: users/sfiera/registry2.0/registry.dylib
===================================================================
(Binary files differ)


Property changes on: users/sfiera/registry2.0/registry.dylib
___________________________________________________________________
Name: svn:executable
   + 
Name: svn:mime-type
   + application/octet-stream

Added: users/sfiera/registry2.0/registry.h
===================================================================
--- users/sfiera/registry2.0/registry.h	                        (rev 0)
+++ users/sfiera/registry2.0/registry.h	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,36 @@
+/*
+ * 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
+
+#include <tcl.h>
+#include <sqlite3.h>
+
+sqlite3* registry_db(Tcl_Interp* interp, int attached);
+
+#endif /* _REGISTRY_H */

Added: users/sfiera/registry2.0/sql.c
===================================================================
--- users/sfiera/registry2.0/sql.c	                        (rev 0)
+++ users/sfiera/registry2.0/sql.c	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,271 @@
+/*
+ * 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 "util.h"
+
+/**
+ * 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, const char *versionB) {
+	const char *ptrA, *ptrB;
+	const char *eptrA, *eptrB;
+
+	/* if versions equal, return zero */
+	if(!strcmp(versionA, versionB))
+		return 0;
+
+	ptrA = versionA;
+	ptrB = versionB;
+	while (*ptrA != '\0' && *ptrB != '\0') {
+		/* skip all non-alphanumeric characters */
+		while (*ptrA != '\0' && !isalnum(*ptrA))
+			ptrA++;
+		while (*ptrB != '\0' && !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 != '\0' && isalpha(*eptrA))
+				eptrA++;
+
+			while (*eptrB != '\0' && isalpha(*eptrB))
+				eptrB++;
+		} else {
+			int countA = 0, countB = 0;
+			while (*eptrA != '\0' && isdigit(*eptrA)) {
+				countA++;
+				eptrA++;
+			}
+			while (*eptrB != '\0' && 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 == '\0' && *ptrB == '\0')
+		return 0;
+
+	/* If A has unchecked characters, return 1
+	 * Otherwise, if B has remaining unchecked characters,
+	 * return -1 */
+	if (*ptrA != '\0')
+		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 UNUSED, const void* a,
+        int blen UNUSED, const void* b) {
+    /* I really hope that the strings are null-terminated. sqlite doesn't
+     * describe this API well.
+     */
+    return rpm_vercomp((const char*)a, (const char*)b);
+}
+
+/**
+ * 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(Tcl_Interp* interp, sqlite3* db) {
+    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 ("
+            "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 (port_id, path UNIQUE, mtime)",
+        "CREATE INDEX registry.file_port ON files (port_id)",
+
+        "END",
+        NULL
+    };
+    return do_queries(interp, db, queries);
+}
+
+/**
+ * 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(Tcl_Interp* interp, sqlite3* db) {
+    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 => proc mapping */
+        "CREATE TEMPORARY TABLE entry_procs (entry_id UNIQUE, proc UNIQUE)",
+
+        "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(interp, db, queries);
+}
+

Added: users/sfiera/registry2.0/sql.h
===================================================================
--- users/sfiera/registry2.0/sql.h	                        (rev 0)
+++ users/sfiera/registry2.0/sql.h	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,36 @@
+/*
+ * 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
+
+#include <sqlite3.h>
+
+int create_tables(Tcl_Interp* interp, sqlite3* db);
+int init_db(Tcl_Interp* interp, sqlite3* db);
+
+#endif /* _SQL_H */

Added: users/sfiera/registry2.0/test.db
===================================================================
(Binary files differ)


Property changes on: users/sfiera/registry2.0/test.db
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: users/sfiera/registry2.0/tests/common.tcl
===================================================================
--- users/sfiera/registry2.0/tests/common.tcl	                        (rev 0)
+++ users/sfiera/registry2.0/tests/common.tcl	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,41 @@
+# Common functions for test cases
+
+proc test {condition} {
+    uplevel 1 "\
+        if {\[catch {
+                if {$condition} { \n\
+                } else { \n\
+                    puts {Assertion failed: $condition} \n\
+                    exit 1 \n\
+                } \n\
+            } msg\]} { \n\
+                puts \"Caught error: \$msg\" \n\
+                puts {While executing: $condition}\n\
+                exit 1 \n\
+            }"
+}
+
+proc test_equal {statement value} {
+    uplevel 1 "\
+        if {\[catch {
+                if {$statement == {$value}} { \n\
+                } else { \n\
+                    puts {Assertion failed: $statement == {$value}} \n\
+                    puts \"Actual value: $statement\" \n\
+                    exit 1 \n\
+                } \n\
+            } msg\]} { \n\
+                puts \"Caught error: \$msg\" \n\
+                puts {While executing: $statement}\n\
+                exit 1 \n\
+            }"
+}
+
+proc check_throws {statement} {
+    uplevel 1 "\
+        if \{!\[catch $statement\]\} \{ \n\
+            puts \{Did not error: $statement\} \n\
+            exit 1 \n\
+        \}"
+}
+

Added: users/sfiera/registry2.0/tests/entry.tcl
===================================================================
--- users/sfiera/registry2.0/tests/entry.tcl	                        (rev 0)
+++ users/sfiera/registry2.0/tests/entry.tcl	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,51 @@
+# Test file for registry::item
+# Syntax:
+# tclsh item.tcl <Pextlib name>
+
+proc main {pextlibname} {
+    load $pextlibname
+
+	file delete -force test.db
+
+    check_throws {registry::entry search}
+    registry::open test.db
+
+    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]
+
+    $vim1 state installed
+    $vim2 state installed
+    $vim3 state active
+    $zlib state active
+    $pcre state installed
+
+    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]
+
+    test_equal {[llength $installed]} 5
+    test_equal {[llength $active]} 2
+
+    registry::close
+    check_throws {registry::entry search}
+
+    registry::open test.db
+
+    set vim [registry::entry active vim]
+    test_equal {[$vim version]} 7.1.002
+
+    registry::close
+
+	file delete -force test.db
+}
+
+source tests/common.tcl
+main $argv

Added: users/sfiera/registry2.0/tests/item.tcl
===================================================================
--- users/sfiera/registry2.0/tests/item.tcl	                        (rev 0)
+++ users/sfiera/registry2.0/tests/item.tcl	2007-06-23 13:10:09 UTC (rev 26439)
@@ -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: users/sfiera/registry2.0/util.c
===================================================================
--- users/sfiera/registry2.0/util.c	                        (rev 0)
+++ users/sfiera/registry2.0/util.c	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,252 @@
+/*
+ * 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 = 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;
+    Tcl_GetCommandInfo(interp, name, &info);
+    if (info.objProc == proc) {
+        errPtr->code = "registry::duplicate-object";
+        errPtr->description = sqlite3_mprintf("%s named \"%s\" already exists, "
+                "cannot create", type, name);
+        errPtr->free = sqlite3_free;
+        return 0;
+    }
+    Tcl_CreateObjCommand(interp, name, proc, value, deleteProc);
+    return 1;
+}
+
+/**
+ * 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(Tcl_Interp* interp, sqlite3* db, char** queries) {
+    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)) {
+            set_sqlite_result(interp, db, *query);
+            sqlite3_finalize(stmt);
+            return TCL_ERROR;
+        }
+        sqlite3_finalize(stmt);
+    }
+    return TCL_OK;
+}
+
+/**
+ * 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);
+    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) {
+                del(userdata, result, i);
+            }
+            free(result);
+            return 0;
+        }
+    }
+    *outv = result;
+    return 1;
+}

Added: users/sfiera/registry2.0/util.h
===================================================================
--- users/sfiera/registry2.0/util.h	                        (rev 0)
+++ users/sfiera/registry2.0/util.h	2007-06-23 13:10:09 UTC (rev 26439)
@@ -0,0 +1,65 @@
+/*
+ * 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
+
+#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);
+
+int do_queries(Tcl_Interp* interp, sqlite3* db, char** queries);
+
+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);
+
+#endif /* _UTIL_H */

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/macports-changes/attachments/20070623/92999c7a/attachment.html


More information about the macports-changes mailing list