Compare commits

...

6 Commits

1 changed files with 96 additions and 27 deletions

123
tag.sh
View File

@ -1,27 +1,63 @@
#!/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
: <<'DOCS'
=head1 NAME
# TODO: adjust sqlite_insert_multiple to allow defining columns per insert, currently it's hardcoded to 2
# TODO: merge sqlite_insert_single and sqlite_insert_multiple probably as result of the above
tags is a tool for keeping file tags in a SQLite database.
=head1 SYNOPSIS
tag [OPTIONS] <init|import|add|list|bytag|listtags>
=head1 OPTIONS
=over 4
=item B<init>
Create an empty database.
=item B<import> I<filename>
Import filename, can be single value or multiple values.
=item B<autoimport> I<regexp> I<path>
Import all files in I<path>. Can be filtered using custom I<regexp>, or one of the regular expression presets (currently only "video").
=item B<add> I<type> I<value>
Adds a new item of the specified I<type>: C<tag>, C<file>, C<hash>.
=item B<list> I<type> I<[--bytag|--byfile]>
List C<tags>, C<files>, C<hashes> (these are all table names, internally).
=item B<tag> I<[-i|--interactive]> I<--file|--id> I<tag>
Tags I<filename> with one or more I<tag>.
=back
=head1 TODO
* adjust sqlite_insert_multiple to allow defining columns per insert, currently it's hardcoded to 2
* merge sqlite_insert_single and sqlite_insert_multiple probably as result of the above
* listtags doesn't work with full path
* listtags doesn't work after tagging a file
=head1 LICENSE AND COPYRIGHT
=cut
DOCS
SCRIPT_DIR=$(dirname "$(readlink "$0")")
SCRIPT_NAME=$(basename "$0")
DB_FILE="${PWD}/tags.db"
[ "${1:-}" = "--db" ] && DB_FILE=$(realpath "${2/#~/$HOME}") && shift 2
[ "${1:-}" = "--db" ] && DB_FILE=$(readlink -f "${2/#~/$HOME}") && shift 2
DB_SCHEMA="${SCRIPT_DIR}/database.sql"
@ -80,7 +116,8 @@ add() {
[ "$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"
pod2usage "$0"
exit 1
}
sqlite_query() {
@ -118,11 +155,11 @@ sqlite_insert_single() {
sqlite_insert_multi() {
# $TABLE $COLUMN $VALUE1 $VALUE2
[ -z "$1" ] && fail "No table specified."
[ -z "${1:-}" ] && fail "No table specified."
TABLE="$1"
[ -z "$2" ] && fail "No column(s) supplied."
[ -z "${2:-}" ] && { pod2usage "$0"; exit 1 ; }
COLUMN="$2"
[[ -z "$3" || -z "$4" ]] && fail "No column values supplied."
[[ -z "${3:-}" || -z "${4:-}" ]] && { pod2usage "$0"; exit 1 ; }
shift 2
VALUES="($1,$2)"
if [[ ! -z "${3:-}" && ! -z "${4:-}" ]]; then
@ -152,7 +189,7 @@ add_path() {
for FILE in "$@"; do
[ ! -f "$FILE" ] && fail "File '$FILE' does not exist in the specified path."
local FILENAME=$(basename "$FILE")
local FILEPATH=$(realpath "$(dirname "$FILE")")
local FILEPATH=$(readlink -f "$(dirname "$FILE")")
file_exists_in_db "$FILE" && fail "File '$FILE' already exists in database."
FILES+=("\"$FILENAME\"")
FILES+=("\"$FILEPATH\"")
@ -166,7 +203,7 @@ add_hash() {
local COLUMN="fid,md5"
local FILENAME="${1:-}"
[ -z "$FILENAME" ] && fail "No file specified."
local FID=$(file_by_filename "$FILENAME")
local FID=$(id_by_filename "$FILENAME")
[[ "$FID" -eq 0 ]] && fail "File \"$FILENAME\" does not exist in database."
sqlite_insert_multi "$TABLE" "$COLUMN" $FID "'$2'"
}
@ -189,7 +226,7 @@ tag_exists_in_db() {
file_exists_in_db() {
# $FILENAME
local RESULT=$(file_by_filename "$1")
local RESULT=$(id_by_filename "$1")
if [[ "$RESULT" -eq 0 ]]; then
return 1
else
@ -197,10 +234,10 @@ file_exists_in_db() {
fi
}
file_by_filename() {
id_by_filename() {
# FILENAME
local FILENAME=$(basename "${1:-}")
local FILEPATH=$(realpath "$(dirname "${1:-}")")
local FILEPATH=$(readlink -f "$(dirname "${1:-}")")
local RESULT=0
RESULT=$(sqlite_query "SELECT id FROM files WHERE filename = \"$FILENAME\" AND path = \"$FILEPATH\"")
echo $RESULT
@ -216,13 +253,15 @@ dbfile_exists() {
main() {
[ -z "${1:-}" ] && fail "Usage: tag init/add/import/autoimport/list/listtags"
[ -z "${1:-}" ] && { pod2usage "$0"; exit 1; }
if [[ "$1" = "init" ]]; then
init "${2:-$DB_FILE}"
exit 0
fi
[ ! -f "$DB_FILE" ] && fail "Database file \"$DB_FILE\" does not exist."
if [[ "$1" = "import" ]]; then
shift
add "path" "$@"
@ -243,7 +282,7 @@ main() {
fi
if [[ "$1" = "list" ]]; then
[ -z "$2" ] && fail "No table supplied."
[ -z "${2:-}" ] && { pod2usage "$0"; exit 1 ; }
TABLE_NAME="$2"
sqlite_query "SELECT * FROM \"${TABLE_NAME}\""
exit 0
@ -258,6 +297,12 @@ main() {
if [[ "$1" = "tag" ]]; then
shift
if [[ "${1:-}" = "-i" || "${1:-}" = "--interactive" ]]; then
"$0" --db "$DB_FILE" tagfid \
$(sqlite_query "SELECT id,filename,path FROM files" | fzf | xargs -I{} echo '{}' | awk -F'|' '{print $1}') \
$(sqlite_query "SELECT label FROM tags" | fzf --multi | xargs -I{} echo '{}' | awk -F'|' '{print $1}')
exit 0
fi
[ -z "${1:-}" ] && fail "No filename supplied."
FILENAME="$1"
! file_exists_in_db "$FILENAME" && fail "File '$FILENAME' does not exist in database."
@ -268,7 +313,8 @@ main() {
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\"))"
local FID=$(id_by_filename "$FILENAME")
sqlite_query "INSERT INTO tags_ties (fid, tid) VALUES ($FID,(SELECT id FROM tags WHERE label = \"$LABEL\"))"
COUNTER=$((COUNTER++))
[ -z "${1:-}" ] && break
done
@ -276,11 +322,34 @@ main() {
exit 0
fi
if [[ "$1" = "tagfid" ]]; then
shift
[ -z "${1:-}" ] && fail "No file ID supplied."
local FID="$1"
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 ($FID,(SELECT id FROM tags WHERE label = \"$LABEL\"))"
COUNTER=$((COUNTER++))
[ -z "${1:-}" ] && break
done
exit 0
fi
if [[ "$1" = "listtags" ]]; then
[ -z "${2:-}" ] && fail "Usage: $SCRIPT_NAME listtags filename"
[ -z "${2:-}" ] && { pod2usage "$0"; exit 1 ; }
listtags "$2"
exit 0
fi
if [[ "$1" = "listbyfile" ]]; then
sqlite_query "SELECT id,filename FROM files" |\
fzf --delimiter="|" --preview "sqlite3 $DB_FILE 'SELECT label FROM tags WHERE id IN (SELECT tid FROM tags_ties WHERE fid ={2})'"
fi
}
main "$@"