aboutsummaryrefslogblamecommitdiffstatshomepage
path: root/src/db.sh
blob: 6d733ef45e2913241651ba2ba14c78d820ef829e (plain) (tree)








































































































































































































                                                                                                                                       
# Copyright (c) 2016 Egor Tensin <Egor.Tensin@gmail.com>
# This file is part of the "Configuration file sharing" project.
# For details, see https://github.com/egor-tensin/config-links.
# Distributed under the MIT License.

# Shared directory settings

shared_dir="$( pwd )"

update_shared_dir() {
    if [ "$#" -ne 1 ]; then
        echo "usage: ${FUNCNAME[0]} DIR" >&2
        return 1
    fi

    local new_shared_dir
    new_shared_dir="$( traverse_path --exist --directory -- "$1" )"

    [ "$db_path" == "$shared_dir/$default_db_name" ] \
        && db_path="$new_shared_dir/$default_db_name"

    shared_dir="$new_shared_dir"
}

# Database maintenance

readonly default_db_name='links.bin'
db_path="$shared_dir/$default_db_name"
declare -A database

update_database_path() {
    if [ "$#" -ne 1 ]; then
        echo "usage: ${FUNCNAME[0]} PATH" >&2
        return 1
    fi

    db_path="$( traverse_path --file -- "$1" )"

    local db_dir
    db_dir="$( dirname -- "$db_path" )"
    mkdir -p -- "$db_dir"
}

ensure_database_exists() {
    [ -f "$db_path" ] || is_dry_run || > "$db_path"
}

read_database() {
    [ ! -f "$db_path" ] && is_dry_run && return 0

    local entry
    while IFS= read -d '' -r entry; do
        database[$entry]=1
    done < "$db_path"
}

write_database() {
    is_dry_run && return 0

    > "$db_path"

    local entry
    for entry in "${!database[@]}"; do
        printf -- '%s\0' "$entry" >> "$db_path"
    done
}

delete_obsolete_dirs() {
    if [ "$#" -ne 2 ]; then
        echo "usage: ${FUNCNAME[0]} BASE_DIR DIR" >&2
        return 1
    fi

    is_dry_run && return 0

    local base_dir="$1"
    local dir="$2"

    base_dir="$( traverse_path --exist --directory -- "$base_dir" )"
    dir="$( traverse_path --exist --directory -- "$dir" )"

    [ "$base_dir" == "$dir" ] && return 0

    local subpath="${dir##$base_dir/}"

    if [ "$subpath" == "$dir" ]; then
        dump "base directory: $base_dir"    >&2
        dump "... is not a parent of: $dir" >&2
        return 1
    fi

    ( cd -- "$base_dir/" && rmdir -p --ignore-fail-on-non-empty -- "$subpath" )
}

delete_obsolete_entries() {
    local entry
    for entry in "${!database[@]}"; do
        dump "entry: $entry"
        unset -v 'database[$entry]'

        local var_name
        var_name="$( extract_variable_name "$entry" )" || continue
        cache_variable "$var_name"

        local symlink_var_dir
        symlink_var_dir="$( resolve_variable "$var_name" )" || continue
        local shared_var_dir="$shared_dir/%$var_name%"
        local subpath="${entry#%$var_name%/}"
        local symlink_path="$symlink_var_dir/$subpath"
        local shared_path="$shared_var_dir/$subpath"

        dump "    shared file path: $shared_path"
        dump "    symlink path: $symlink_path"

        if [ ! -L "$shared_path" ] && [ ! -e "$shared_path" ]; then
            dump '    the shared file is missing, so going to delete the symlink'
            is_dry_run && continue

            if [ ! -L "$symlink_path" ]; then
                dump "    not a symlink or doesn't exist, so won't delete"
                continue
            fi

            local target_path
            target_path="$( traverse_path -- "$symlink_path" )"

            if [ "$shared_path" != "$target_path" ]; then
                dump "    doesn't point to the shared file, so won't delete"
                continue
            fi

            rm -f -- "$symlink_path"

            local symlink_dir
            symlink_dir="$( dirname -- "$symlink_path" )"
            delete_obsolete_dirs "$symlink_var_dir" "$symlink_dir" || true

            continue
        fi

        if [ ! -L "$symlink_path" ]; then
            dump "    not a symlink or doesn't exist"
            continue
        fi

        local target_path
        target_path="$( traverse_path -- "$symlink_path" )"

        if [ "$target_path" != "$shared_path" ]; then
            dump "    doesn't point to the shared file"
            continue
        fi

        dump '    ... points to the shared file'
        database[$entry]=1
    done
}

discover_new_entries() {
    local shared_var_dir
    while IFS= read -d '' -r shared_var_dir; do
        dump "shared directory: $shared_dir/$shared_var_dir"

        local var_name
        var_name="$( extract_variable_name "$shared_var_dir" )"
        cache_variable "$var_name"

        shared_var_dir="$shared_dir/$shared_var_dir"

        local symlink_var_dir
        symlink_var_dir="$( resolve_variable "$var_name" )" || continue
        dump "    symlinks directory: $symlink_var_dir"

        local shared_path
        while IFS= read -d '' -r shared_path; do
            dump "        shared file path: $shared_path"

            local entry="%$var_name%/${shared_path:${#shared_var_dir}}"

            if [ -n "${database[$entry]+x}" ]; then
                dump '        ... already has a symlink'
                continue
            fi

            local subpath="${shared_path:${#shared_var_dir}}"
            local symlink_path="$symlink_var_dir/$subpath"

            dump "        symlink path: $symlink_path"

            is_dry_run && continue

            local symlink_dir
            symlink_dir="$( dirname -- "$symlink_path" )"
            mkdir -p -- "$symlink_dir"
            ln -f -s --no-target-directory -- "$shared_path" "$symlink_path"

            database[$entry]=1
        done < <( find "$shared_var_dir" -type f -print0 )

    done < <( find "$shared_dir" -regextype posix-basic -mindepth 1 -maxdepth 1 -type d -regex ".*/$var_name_regex\$" -printf '%P/\0' )
}