#!/usr/bin/env bash set -ueo pipefail [ "${DEBUG:-0}" = "1" ] && set -x # usage: # - init - create an empty database # - import - import $FILE or import $DIR, both can be multiple values # - autoimport ($REGEX|video) $FOLDER - will import all files in $FOLDER with a $REGEX filter, # or one of the preset filters: # - video # - add (tag|filename) $VALUE - adds a new tag or filename of $VALUE # - list (tags|files) - lists all the values in a table # - bytag $TAG - list all files by tag label # - tag $FILENAME $TAG - tag $FILENAME with $TAG, $TAG can be repeated # - listtags $FILENAME - list all tags for $FILENAME SCRIPT_DIR=$(dirname $(readlink "$0")) SCRIPT_NAME=$(basename "$0") DB_FILE="${PWD}/tags.db" [ "${1:-}" = "--db" ] && DB_FILE="$2" && shift 2 DB_SCHEMA="${SCRIPT_DIR}/database.sql" declare -A FILTERS FILTERS[video]="avi|flv|mkv|mov|mp4|mpg|ogv|webm|wmv" fail() { echo "$1" >&2; exit 1 } log() { echo "$1" } init() { # DB_NAME [ -f "$1" ] && fail "Database file \"$1\" already exists. Aborting." cat "$DB_SCHEMA" | sqlite3 "$1" log "Empty database file \"$1\" created." } enumerate_positional_parameters() { log "Positional parameters in $FUNCNAME:" CNT=0 for VALUE in "$@"; do let CNT++ || true echo "${CNT}: $VALUE" done } listtags() { # $FILENAME $LIMIT=ALL [ -z "$1" ] && fail "No filename supplied." FILENAME="$1" shift ! file_exists_in_db "$FILENAME" && fail "File '$FILENAME' does not exist in database." LIMIT="${1:-0}" ADDITIONAL_QUERY="" [ $LIMIT -gt 0 ] && ADDITIONAL_QUERY="LIMIT $LIMIT" sqlite_query \ "SELECT filename, label from files \ INNER JOIN tags_ties ON tags_ties.fid = files.id \ INNER JOIN tags ON tags.id = tags_ties.tid \ WHERE filename = \"$FILENAME\"\ $ADDITIONAL_QUERY" } add() { # $TYPE(tag|path|hash) $VALUE1..$VALUEN [ "$1" = "tag" ] && shift && add_tag "$@" && exit 0 [ "$1" = "path" ] && shift && add_path "$@" && exit 0 [ "$1" = "hash" ] && shift && add_hash "$@" && exit 0 [ "$1" = "file" ] && fail "Use \"$SCRIPT_NAME add path\" instead." fail "Usage: $SCRIPT_NAME add tag/path/hash" } sqlite_query() { # $QUERY sqlite3 "$DB_FILE" "$1" } sqlite_lastrows() { # $TABLE $LIMIT sqlite_query "SELECT * FROM \"$1\" ORDER BY id DESC LIMIT ${2}" } sqlite_insert_single() { # $TABLE $COLUMN $VALUES # $VALUES can be comma-delimited [ -z "$1" ] && fail "No table specified." TABLE="$1" [ -z "$2" ] && fail "No column(s) supplied." COLUMN="$2" [ -z "$3" ] && fail "No column values supplied." shift 2 VALUES="(\"$1\")" if [[ ! -z "${2:-}" ]]; then while true; do shift [ -z "${1:-}" ] && break VALUES+=",(\"$1\")" done fi QUERY="PRAGMA foreign_keys=ON;" QUERY+="INSERT INTO $TABLE ($COLUMN) VALUES ${VALUES} RETURNING *;" sqlite_query "$QUERY" } sqlite_insert_multi() { # $TABLE $COLUMN $VALUE1 $VALUE2 [ -z "$1" ] && fail "No table specified." TABLE="$1" [ -z "$2" ] && fail "No column(s) supplied." COLUMN="$2" [[ -z "$3" || -z "$4" ]] && fail "No column values supplied." shift 2 VALUES="($1,$2)" if [[ ! -z "${3:-}" && ! -z "${4:-}" ]]; then while true; do shift [ -z "${1:-}" && -z "${2:-}" ] && break VALUES+=",($1,$2)" done fi QUERY="PRAGMA foreign_keys=ON;" QUERY+="INSERT INTO $TABLE ($COLUMN) VALUES ${VALUES} RETURNING *;" sqlite_query "$QUERY" } add_tag() { # $TAG1...$TAGN TABLE="tags" COLUMN="label" sqlite_insert_single "$TABLE" "$COLUMN" "$@" } add_path() { # $PATH1..$PATHN TABLE="files" COLUMN="filename" for FILE in "$@"; do file_exists_in_db "$FILE" && fail "File '$FILENAME' already exists in database." done sqlite_insert_single "$TABLE" "$COLUMN" "$@" } add_hash() { # $FILE $HASH TABLE="hashes" COLUMN="fid,md5" local FILENAME="${1:-}" [ -z "$FILENAME" ] && fail "No file specified." ! file_exists_in_db "$FILENAME" && fail "File \"$FILENAME\" does not exist in database." sqlite_insert_multi "$TABLE" "$COLUMN" $RESULT "'$2'" } add_path_auto() { # $REGEX $FOLDER find "${2}" -type f -regextype posix-extended -iregex "$1" -exec "$0" import "{}" + } tag_exists_in_db() { # $TAGLABEL TAG=${1:-} RESULT=$(sqlite_query "SELECT id FROM tags WHERE label = '$TAG'") if [[ -z "$RESULT" ]]; then return 1 else return 0 fi } file_exists_in_db() { # $FILENAME FILENAME=${1:-} RESULT=$(sqlite_query "SELECT id FROM files WHERE filename = '$FILENAME'") if [[ -z "$RESULT" ]]; then return 1 else return 0 fi } main() { [ -z "${1:-}" ] && fail "Usage: tag init/add/import/autoimport/list/listtags" if [[ "$1" = "init" ]]; then init "${2:-$DB_FILE}" exit 0 fi if [[ "$1" = "import" ]]; then shift add "path" "$@" fi if [[ "$1" = "autoimport" ]]; then shift if [[ "${1:-}" =~ "${!FILTERS[@]}" ]]; then add_path_auto ".*(${FILTERS[$1]})" "$2" else add_path_auto "$1" "$2" fi fi if [[ "$1" = "add" ]]; then shift add "$@" fi if [[ "$1" = "list" ]]; then [ -z "$2" ] && fail "No table supplied." TABLE_NAME="$2" sqlite3 -table "$DB_FILE" "SELECT * FROM \"${TABLE_NAME}\"" exit 0 fi if [[ "$1" = "bytag" ]]; then [ -z "$2" ] && fail "No tag supplied." TAG_NAME="$2" sqlite3 -table "$DB_FILE" "SELECT filename FROM files WHERE id = (SELECT id FROM tags_ties WHERE id = (SELECT id FROM tags WHERE label = \"${TAG_NAME}\"))" exit 0 fi if [[ "$1" = "tag" ]]; then shift [ -z "${1:-}" ] && fail "No filename supplied." FILENAME="$1" ! file_exists_in_db "$FILENAME" && fail "File '$FILENAME' does not exist in database." shift [ -z "${1:-}" ] && fail "No tag supplied." COUNTER=0 while true; do LABEL="$1" ! tag_exists_in_db "$LABEL" && fail "Tag '$TAG' does not exist in database." shift sqlite_query "INSERT INTO tags_ties (fid, tid) VALUES ((SELECT id FROM files WHERE filename = \"$FILENAME\"),(SELECT id FROM tags WHERE label = \"$LABEL\"))" COUNTER=$((COUNTER++)) [ -z "${1:-}" ] && break done listtags "$FILENAME" "$COUNTER" exit 0 fi if [[ "$1" = "listtags" ]]; then [ -z "${2:-}" ] && fail "Usage: $SCRIPT_NAME listtags filename" listtags "$2" exit 0 fi } main "$@"