tags/tag.sh

278 lines
7.2 KiB
Bash
Executable File

#!/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 LIKE \"%$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
dbfile_exists || fail "Database file '$DB_FILE' does not exist. Use '$SCRIPT_NAME init' to create default one, or specify filename with '--db'."
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 2
[[ -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,path"
FILES=()
for FILE in "$@"; do
FILENAME=$(basename "$FILE")
FILEPATH=$(dirname "$FILE")
file_exists_in_db "$FILE" && fail "File '$FILE' already exists in database."
FILES+=("'$FILENAME'")
FILES+=("'$FILEPATH'")
done
sqlite_insert_multi "$TABLE" "$COLUMN" "${FILES[@]}"
}
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=$(basename "${1:-}")
FILEPATH=$(dirname "${1:-}")
RESULT=$(sqlite_query "SELECT id FROM files WHERE filename = '$FILENAME' AND path = '$FILEPATH'")
if [[ -z "$RESULT" ]]; then
return 1
else
return 0
fi
}
dbfile_exists() {
if [[ ! -f "$DB_FILE" ]]; 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"
sqlite_query "SELECT * FROM \"${TABLE_NAME}\""
exit 0
fi
if [[ "$1" = "bytag" ]]; then
[ -z "$2" ] && fail "No tag supplied."
TAG_NAME="$2"
sqlite_query "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 "$@"