about summary refs log tree commit diff stats
path: root/modules/home/conf/lf/commands/scripts/cow_cp.sh
blob: 98675b9e6a537090ddbcb0b756e4349b5d1f48b7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#!/usr/bin/env bash

# shellcheck source=/dev/null
SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH

# shellcheck disable=SC2269
f="$f"
# shellcheck disable=SC2269
fx="$fx"
# shellcheck disable=SC2269
fs="$fs"
# shellcheck disable=SC2269
id="$id"

# source: https://github.com/gokcehan/lf/wiki/Tips#use-copy-on-write-when-possible
#
# # FIXME: Add this. The hardest part is in checking, if a file can be reflinked, as fuse and bind mount are hard to
#          backtrack <2023-08-29>

# # This was very helpful for debugging:
# log_file="$HOME/lf-reflink-log-$(date +'%Y-%m-%d_%H-%M-%S')"
# [ -f "$log_file" ] || touch "$log_file"
# exec 1>> $log_file 2>&1
# set -x

# In theory, this may fail,
# but I tested it on selection with 10k files - everything worked (bash)
# FIXME: This will very likely fail on dash, when the file number > 255 <2023-08-29>
set -- "$(cat ~/.local/share/lf/files)"

mode="$1"
shift

if [ "$mode" = 'copy' ]; then
    # Reflink if all items of selection and the destination are on the
    # same mount point and it is CoW fs.
    # (to make sure reflink never fails in first place, so we don't have to
    # clean up)

    src_targets="$(df --output=target -- "$@" | sed '1d' | sort -u)"

    if [ "$(df --output=target -- "$PWD" | tail -n 1)" = \
        "$(echo "$src_targets" | tail -n 1)" ] &&
        (("$(echo "$src_targets" | wc -l)" == 1)) &&
        [[ "$(df --output=fstype -- "$PWD" | tail -n 1)" =~ ^(btrfs|xfs|zfs)$ ]]; then

        echo 'selected copy and cp reflink paste'

        start=$(date '+%s')

        # Handle same names in dst
        # TODO parallelism, idk - but exit/return/break won't stop the loop from subshell...
        for i in "$@"; do
            name="${i##*/}"
            original="$name"

            count=0
            while [ -w "$PWD/$name" ]; do
                count=$((count + 1))
                name="$original.~$count~"
            done

            set +e
            cp_out="$(cp -rn --reflink=always -- "$i" "$PWD/$name" 2>&1)"
            set -e

            if [ -n "$cp_out" ]; then
                lf -remote "send $id echoerr $cp_out"
                exit 0
            fi
        done

        finish=$(($(date '+%s') - start))
        t=''
        if ((finish > 2)); then
            t="${finish}s"
        fi

        # Or just skip a file when names are the same.
        # (A LOT faster if you e.g. pasting selection of 10k files)
        # cp -rn --reflink=always -- "$@" .

        lf -remote "send clear"

        green=$'\u001b[32m'
        reset=$'\u001b[0m'
        lf -remote "send $id echo ${green}reflinked!${reset} $t"
    else
        echo 'selected copy and lf native paste'
        lf -remote "send $id paste"
        lf -remote "send clear"
    fi

elif [ "$mode" = 'move' ]; then
    echo 'selected move and lf native paste'
    lf -remote "send $id paste"
    lf -remote "send clear"
fi

# # for debug
# set +x

lf -remote "send load"

# vim: ft=sh