appfs-cache at [fdf89fd103]

File appfs-cache artifact dae13120c1 part of check-in fdf89fd103


#! /usr/bin/env bash

#
# Copyright (c) 2014  Roy Keene
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

PATH="${PATH}:$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

appfsd_options=()
if [ "$1" == "--cachedir" ]; then
	appfsd_options=("${appfsd_options[@]}" '--cachedir' "$2")

	shift; shift;
fi

function call_appfsd() {
	appfsd "${appfsd_options[@]}" "$@"
}

function invalidate() {
	call_appfsd --sqlite3 'UPDATE sites SET ttl = "0";'
}

function remove_site() {
	local site

	site="$1"

	call_appfsd --sqlite3 'DELETE FROM sites WHERE hostname = '"'$site'"'; DELETE FROM packages WHERE hostname = '"'$site'"';' || return 1

	clean
}

function mirror() {
	local site destinationDir
	local baseURL index
	local packageList

	local marker

	site="$1"
	destinationDir="$2"

	baseURL="http://${site}/appfs"
	index="${baseURL}/index"

	if [ ! -e "${destinationDir}" ]; then
		mkdir "${destinationDir}" || return 1
	fi

	marker="$(openssl rand 20 -hex)"

	(
		cd "${destinationDir}" || exit 1

		# XXX:TODO: This does not validate the signature
		curl -sSL "${index}" > "index.${marker}"

		packageListHash="$(cat "index.${marker}" | cut -f 1 -d ,)"
		packageListHashMethod="$(cat "index.${marker}" | cut -f 2 -d ,)"
		#curl -sSL "${...}"

		mv "index.${marker}" index
	) || return 1

	return 0
}

function clean() {
	call_appfsd --tcl "$(cat <<\_EOF_
		unset -nocomplain row
		::appfs::db eval {SELECT sha1, hostname FROM packages;} row {
			set hostname [::appfs::db onecolumn {SELECT hostname FROM sites WHERE hostname = $row(hostname) LIMIT 1;}]
			if {$hostname == ""} {
				continue
			}

			set valid_sha1($row(sha1)) 1
			::appfs::db eval {SELECT file_sha1 FROM files WHERE file_sha1 IS NOT NULL AND file_sha1 != '' AND package_sha1 = $row(sha1);} subrow {
				set valid_sha1($subrow(file_sha1)) 1
			}
		}

		foreach file [glob -nocomplain -tails -directory $::appfs::cachedir {[0-9a-f][0-9a-f]/*/*/*/*}] {
			set sha1 [string map [list "/" "" "\\" ""] $file]
			set file [file join $::appfs::cachedir $file]

			if {[info exists valid_sha1($sha1)]} {
				continue
			}

			puts "Cleaning $file"
			file delete -force -- $file
		}
_EOF_
	)"
}

function clear() {
	local package

	package="$1"

	if [ -n "${package}" ]; then
		echo "not implemented" >&2

		return 1
	fi

	call_appfsd --tcl 'file delete -force -- {*}[glob -directory $::appfs::cachedir {[0-9a-f][0-9a-f]}]' || return 1
	call_appfsd --sqlite3 'DELETE FROM sites; DELETE FROM packages; DELETE FROM files; VACUUM;' || return 1
}

function install() {
	local site packages
	local package packagedir
	local includeLib

	includeLib='0'
	if [ "$1" = '-lib' ]; then
		shift
		includeLib='1'
	fi

	site="$1"
	shift

	packages=("$@")

	if [ -z "${site}" -o -z "${packages[*]}" ]; then
		echo "usage: appfs-cache install <site> <package>..." >&2

		return 1
	fi

	for package in "${packages[@]}"; do
		packagedir="/opt/appfs/${site}/${package}/platform/latest"

		## XXX:TODO: Allow installation to other locations
		ln -fs "${packagedir}"/bin/* /bin/
		if [ "${includeLib}" = '1' ]; then
			ln -fs "${packagedir}"/lib/* /lib/
		fi
	done

	return 0
}

operation="$1"
shift
case "$operation" in
	install)
		install "$@" || exit 1
		exit 0
		;;
	invalidate)
		invalidate || exit 1
		;;
	remove-site)
		remove_site "$@" || exit 1
		;;
	clean)
		clean || exit 1
		;;
	clear)
		clear "$@" || exit 1
		;;
	hoard)
		echo "not implemented" >&2
		exit 1

		hoard "$@" || exit 1
		exit 0
		;;
	mirror)
		echo "not implemented" >&2
		exit 1

		mirror "$@" || exit 1
		exit 0
		;;
	*)
		echo "Usage: appfs-cache {invalidate|clean|clear|clear <package>|remove-site <site>|hoard <package>|mirror <site> <dir>}" >&2

		exit 1
		;;
esac

pkill -HUP appfsd

exit 0