Index: .fossil-settings/ignore-glob
==================================================================
--- .fossil-settings/ignore-glob
+++ .fossil-settings/ignore-glob
@@ -1,2 +1,5 @@
 appfs
 appfs.o
+appfs-test
+appfs-test.o
+appfs.tcl.h

Index: Makefile
==================================================================
--- Makefile
+++ Makefile
@@ -1,24 +1,44 @@
 CC = gcc
 PKG_CONFIG = pkg-config
-CFLAGS = $(shell $(PKG_CONFIG) --cflags fuse)
-LIBS = $(shell $(PKG_CONFIG) --libs fuse)
+TCL_CFLAGS =
+TCL_LDFLAGS =
+TCL_LIBS = -ltcl
+CFLAGS = -Wall -g3 $(shell $(PKG_CONFIG) --cflags fuse) $(TCL_CFLAGS)
+LDFLAGS = $(TCL_LDFLAGS)
+LIBS = $(shell $(PKG_CONFIG) --libs fuse) $(TCL_LIBS)
 PREFIX = /usr/local
 prefix = $(PREFIX)
 bindir = $(prefix)/bin
 
 all: appfs
 
 appfs: appfs.o
 	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o appfs appfs.o $(LIBS)
 
-appfs.o: appfs.c
+appfs-test: appfs-test.o
+	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o appfs-test appfs-test.o $(LIBS)
+
+appfs.o: appfs.c appfs.tcl.h
+	$(CC) $(CPPFLAGS) $(CFLAGS) -o appfs.o -c appfs.c
+
+appfs-test.o: appfs.c appfs.tcl.h
+	$(CC) $(CPPFLAGS) $(CFLAGS) -DAPPFS_TEST_DRIVER=1 -o appfs-test.o -c appfs.c
+
+appfs.tcl.h: appfs.tcl stringify.tcl
+	./stringify.tcl appfs.tcl > appfs.tcl.h.new
+	mv appfs.tcl.h.new appfs.tcl.h
 
 install: appfs
 	cp appfs $(bindir)
 
+test: appfs-test
+	./appfs-test
+
 clean:
 	rm -f appfs appfs.o
+	rm -f appfs-test appfs-test.o
+	rm -f appfs.tcl.h
 
 distclean: clean
 
-.PHONY: all clean distclean install
+.PHONY: all test clean distclean install

Index: README.md
==================================================================
--- README.md
+++ README.md
@@ -7,21 +7,22 @@
 -----
 AppFS should normally be mounted on "/opt/appfs".
 
 /opt/appfs/hostname
 	Fetches: http://hostname/appfs/index
-	Contains CSV file: type,extraData
-		type == package; extraData = package,version,os,cpuArch,sha1
-		type == latest; extradata = package,version,os,cpuArch
+	Contains CSV file: hash,extraData
+
+	Fetches: http://hostname/appfs/sha1/<hash>
+	Contains CSV file: package,version,os,cpuArch,sha1,isLatest
 
 /opt/appfs/hostname/package/os-cpuArch/version
 /opt/appfs/hostname/sha1/
-	Fetches: http://hostname/appfs/<sha1>
+	Fetches: http://hostname/appfs/sha1/<sha1>
 	Contains CSV file:
 		type,time,extraData,name
 		type == directory; extraData = (null)
 		type == symlink; extraData = source
 		type == file; extraData = size,sha1
 
 /opt/appfs/hostname/{sha1,package/os-cpuArch/version}/file
-	Fetches: http://hostname/appfs/<sha1>
+	Fetches: http://hostname/appfs/sha1/<sha1>
 

Index: appfs.c
==================================================================
--- appfs.c
+++ appfs.c
@@ -1,56 +1,232 @@
 #define FUSE_USE_VERSION 26
 
-#include <fuse.h>
+#include <string.h>
 #include <errno.h>
-#include <string.h>
 #include <fcntl.h>
+#include <stdio.h>
+#include <fuse.h>
+#include <tcl.h>
+
+#define APPFS_DEBUG(x...) { fprintf(stderr, "%i:%s: ", __LINE__, __func__); fprintf(stderr, x); fprintf(stderr, "\n"); }
+
+Tcl_Interp *interp;
+
+typedef enum {
+	APPFS_OS_UNKNOWN,
+	APPFS_OS_ALL,
+	APPFS_OS_LINUX,
+	APPFS_OS_MACOSX,
+	APPFS_OS_FREEBSD,
+	APPFS_OS_OPENBSD,
+	APPFS_OS_SOLARIS
+} appfs_os_t;
+
+typedef enum {
+	APPFS_CPU_UNKNOWN,
+	APPFS_CPU_ALL,
+	APPFS_CPU_AMD64,
+	APPFS_CPU_I386,
+	APPFS_CPU_ARM
+} appfs_cpuArch_t;
+
+struct appfs_package {
+	char name[128];
+	char version[64];
+	appfs_os_t os;
+	appfs_cpuArch_t cpuArch;
+	int isLatest;
+};
+
+static appfs_os_t appfs_convert_os_fromString(const char *os) {
+	if (strcasecmp(os, "Linux") == 0) {
+		return(APPFS_OS_LINUX);
+	}
+
+	if (strcasecmp(os, "Darwin") == 0 || strcasecmp(os, "Mac OS") == 0 || strcasecmp(os, "Mac OS X") == 0) {
+		return(APPFS_OS_MACOSX);
+	}
+
+	if (strcasecmp(os, "noarch") == 0) {
+		return(APPFS_OS_ALL);
+	}
+
+	return(APPFS_OS_UNKNOWN);
+}
+
+static const char *appfs_convert_os_toString(appfs_os_t os) {
+	switch (os) {
+		case APPFS_OS_ALL:
+			return("noarch");
+		case APPFS_OS_LINUX:
+			return("linux");
+		case APPFS_OS_MACOSX:
+			return("macosx");
+		case APPFS_OS_FREEBSD:
+			return("freebsd");
+		case APPFS_OS_OPENBSD:
+			return("openbsd");
+		case APPFS_OS_SOLARIS:
+			return("freebsd");
+		case APPFS_CPU_UNKNOWN:
+			return("unknown");
+	}
+
+	return("unknown");
+}
+
+static appfs_cpuArch_t appfs_convert_cpu_fromString(const char *cpu) {
+	if (strcasecmp(cpu, "amd64") == 0 || strcasecmp(cpu, "x86_64") == 0) {
+		return(APPFS_CPU_AMD64);
+	}
+
+	if (strcasecmp(cpu, "i386") == 0 || \
+	    strcasecmp(cpu, "i486") == 0 || \
+	    strcasecmp(cpu, "i586") == 0 || \
+	    strcasecmp(cpu, "i686") == 0 || \
+	    strcasecmp(cpu, "ix86") == 0) {
+		return(APPFS_CPU_I386);
+	}
+
+	if (strcasecmp(cpu, "arm") == 0) {
+		return(APPFS_CPU_ARM);
+	}
+
+	if (strcasecmp(cpu, "noarch") == 0) {
+		return(APPFS_CPU_ALL);
+	}
+
+	return(APPFS_CPU_UNKNOWN);
+}
+
+static const char *appfs_convert_cpu_toString(appfs_cpuArch_t cpu) {
+	switch (cpu) {
+		case APPFS_CPU_ALL:
+			return("noarch");
+		case APPFS_CPU_AMD64:
+			return("amd64");
+		case APPFS_CPU_I386:
+			return("ix86");
+		case APPFS_CPU_ARM:
+			return("arm");
+		case APPFS_CPU_UNKNOWN:
+			return("unknown");
+	}
+
+	return("unknown");
+}
+
+static struct appfs_package *appfs_getindex(const char *hostname, int *package_count_p) {
+	Tcl_Obj *objv[2];
+	int tcl_ret;
+
+	if (package_count_p == NULL) {
+		return(NULL);
+	}
+
+	objv[0] = Tcl_NewStringObj("::appfs::getindex", -1);
+	objv[1] = Tcl_NewStringObj(hostname, -1);
+
+	tcl_ret = Tcl_EvalObjv(interp, 2, &objv, 0);
+	if (tcl_ret != TCL_OK) {
+		APPFS_DEBUG("Call to ::appfs::getindex failed: %s", Tcl_GetStringResult(interp));
+
+		return(NULL);
+	}
+
+	printf("result: %s\n", Tcl_GetStringResult(interp));
+
+	return(NULL);
+}
+
+static int appfs_getfile(const char *hostname, const char *sha1) {
+}
+
+static int appfs_getmanifest(const char *hostname, const char *sha1) {
+}
 
 static int appfs_getattr(const char *path, struct stat *stbuf) {
 	int res = 0;
 
+	APPFS_DEBUG("Enter (path = %s, ...)", path);
+
 	memset(stbuf, 0, sizeof(struct stat));
-	if (strcmp(path, "/") == 0) {
-		stbuf->st_mode = S_IFDIR | 0755;
-		stbuf->st_nlink = 2;
-	} else {
-		res = -ENOENT;
-	}
+
+	stbuf->st_mode = S_IFDIR | 0755;
+	stbuf->st_nlink = 2;
 
 	return res;
 }
 
 static int appfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) {
-	if (strcmp(path, "/") != 0) {
-		return(-ENOENT);
-	}
+	APPFS_DEBUG("Enter (path = %s, ...)", path);
 
 	filler(buf, ".", NULL, 0);
 	filler(buf, "..", NULL, 0);
 
 	return 0;
 }
 
 static int appfs_open(const char *path, struct fuse_file_info *fi) {
 	return(-ENOENT);
-
-	if ((fi->flags & 3) != O_RDONLY)
-		return -EACCES;
-
-	return 0;
 }
 
 static int appfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
 	return(-ENOENT);
 }
+
+#ifdef APPFS_TEST_DRIVER
+static int appfs_test_driver(void) {
+	struct appfs_package *packages;
+	int packages_count = 0;
+
+	packages = appfs_getindex("rkeene.org", &packages_count);
+	if (packages == NULL || packages_count == 0) {
+		fprintf(stderr, "Unable to fetch package index from rkeene.org.\n");
+
+		return(1);
+	}
+
+	return(0);
+}
+#endif
 
 static struct fuse_operations appfs_oper = {
 	.getattr	= appfs_getattr,
 	.readdir	= appfs_readdir,
 	.open		= appfs_open,
 	.read		= appfs_read,
 };
 
 int main(int argc, char **argv) {
+	int tcl_ret;
+
+	interp = Tcl_CreateInterp();
+	if (interp == NULL) {
+		fprintf(stderr, "Unable to create Tcl Interpreter.  Aborting.\n");
+
+		return(1);
+	}
+
+	tcl_ret = Tcl_Init(interp);
+	if (tcl_ret != TCL_OK) {
+		fprintf(stderr, "Unable to initialize Tcl.  Aborting.\n");
+
+		return(1);
+	}
+
+	tcl_ret = Tcl_Eval(interp, ""
+#include "appfs.tcl.h"
+	"");
+	if (tcl_ret != TCL_OK) {
+		fprintf(stderr, "Unable to initialize Tcl AppFS Script.  Aborting.\n");
+
+		return(1);
+	}
+
+#ifdef APPFS_TEST_DRIVER
+	return(appfs_test_driver());
+#else
 	return(fuse_main(argc, argv, &appfs_oper, NULL));
+#endif
 }
  

ADDED   appfs.tcl
Index: appfs.tcl
==================================================================
--- /dev/null
+++ appfs.tcl
@@ -0,0 +1,117 @@
+#! /usr/bin/env tclsh
+
+package require http
+
+namespace eval ::appfs {
+	variable sites [list]
+	variable cachedir "/tmp/appfs-cache"
+
+	proc _hash_sep {hash {seps 4}} {
+		for {set idx 0} {$idx < $seps} {incr idx} {
+			append retval "[string range $hash [expr {$idx * 2}] [expr {($idx * 2) + 1}]]/"
+		}
+		append retval "[string range $hash [expr {$idx * 2}] end]"
+
+		return $retval
+	}
+
+	proc _cachefile {url key {keyIsHash 1}} {
+		if {$keyIsHash} {
+			set key [_hash_sep $key]
+		}
+
+		set file [file join $::appfs::cachedir $key]
+
+		file mkdir -- [file dirname $file]
+
+		if {![file exists $file]} {
+			set tmpfile "${file}.new"
+
+			set fd [open $tmpfile "w"]
+
+			set token [::http::geturl $url -channel $fd]
+			set ncode [::http::ncode $token]
+			::http::reset $token
+			close $fd
+
+			if {$ncode == "200"} {
+				file rename -force -- $tmpfile $file
+			} else {
+				file delete -force -- $tmpfile
+			}
+		}
+
+		return $file
+	}
+
+	proc getindex {hostname} {
+		if {[string match "*\[/~\]*" $hostname]} {
+			return -code error "Invalid hostname"
+		}
+
+		set url "http://$hostname/appfs/index"
+
+		set indexcachefile [_cachefile $url "SERVERS/[string tolower $hostname]" 0]
+
+		if {![file exists $indexcachefile]} {
+			return -code error "Unable to fetch $url"
+		}
+
+		set fd [open $indexcachefile]
+		gets $fd indexhash_data
+		set indexhash [lindex [split $indexhash_data ","] 0]
+		close $fd
+
+		set file [download $hostname $indexhash]
+		set fd [open $file]
+		set data [read $fd]
+		close $fd
+
+		array set packages [list]
+		foreach line [split $data "\n"] {
+			set line [string trim $line]
+
+			if {[string match "*/*" $line]} {
+				continue
+			}
+
+			if {$line == ""} {
+				continue
+			}
+
+			set work [split $line ","]
+
+			unset -nocomplain pkgInfo
+			set pkgInfo(package)  [lindex $work 0]
+			set pkgInfo(version)  [lindex $work 1]
+			set pkgInfo(os)       [lindex $work 2]
+			set pkgInfo(cpuArch)  [lindex $work 3]
+			set pkgInfo(hash)     [string tolower [lindex $work 4]]
+			set pkgInfo(hash_type) "sha1"
+			set pkgInfo(isLatest) [expr {!![lindex $work 5]}]
+
+			if {[string length $pkgInfo(hash)] != 40} {
+				continue
+			}
+
+			if {![regexp {^[0-9a-f]*$} $pkgInfo(hash)]} {
+				continue
+			}
+
+			set packages($pkgInfo(package)) [array get pkgInfo]
+		}
+
+		return [array get packages]
+	}
+
+	proc download {hostname hash {method sha1}} {
+		set url "http://$hostname/appfs/$method/$hash"
+		set file [_cachefile $url $hash]
+
+		if {![file exists $file]} {
+			return -code error "Unable to fetch"
+		}
+
+		return $file
+	}
+}

ADDED   stringify.tcl
Index: stringify.tcl
==================================================================
--- /dev/null
+++ stringify.tcl
@@ -0,0 +1,27 @@
+#! /usr/bin/env tclsh
+
+proc stringifyfile {filename {key 0}} {
+	catch {
+		set fd [open $filename r]
+	}
+
+	if {![info exists fd]} {
+		return ""
+	}
+
+	set data [read -nonewline $fd]
+	close $fd
+
+	foreach line [split $data \n] {
+		set line [string map [list "\\" "\\\\" "\"" "\\\""] $line]
+		append ret "	\"$line\\n\"\n"
+	}
+
+	return $ret
+}
+
+foreach file $argv {
+	puts -nonewline [stringifyfile $file]
+}
+
+exit 0