Files

94 lines
3.0 KiB
Bash
Executable File

#!/bin/bash
#
# trash — move files to the Trash following the FreeDesktop.org Trash Spec 1.0.
#
# Chooses the correct trash directory regardless of distro / desktop:
# * Items on the same filesystem as $HOME go to the "home trash":
# ${XDG_DATA_HOME:-$HOME/.local/share}/Trash
# * Items on a different filesystem go to a top-dir trash on that mount:
# <mount>/.Trash/$uid (if it is a valid, sticky, non-symlink dir)
# <mount>/.Trash-$uid (fallback, always valid to create)
#
# Also writes the matching .trashinfo metadata so file managers actually
# display and can restore the trashed items.
trash() {
local uid=$UID
local home_trash="${XDG_DATA_HOME:-$HOME/.local/share}/Trash"
local home_top
home_top=$(df --output=target -- "$HOME" 2>/dev/null | tail -n 1)
local item abs top trash_dir files_dir info_dir base stem ext candidate n
local deletion_date info_path
for item in "$@"; do
if [ ! -e "$item" ] && [ ! -L "$item" ]; then
echo "Error: '$item' does not exist." >&2
continue
fi
# Resolve to an absolute path for the .trashinfo Path field.
abs=$(readlink -f -- "$item") || abs=$item
# Decide which trash to use based on the filesystem the item lives on.
top=$(df --output=target -- "$item" 2>/dev/null | tail -n 1)
if [ -z "$top" ] || [ "$top" = "$home_top" ]; then
trash_dir=$home_trash
else
if [ -d "$top/.Trash" ] && [ ! -L "$top/.Trash" ] &&
[ "$(( $(stat -c %a -- "$top/.Trash" 2>/dev/null || echo 0) & 3777 ))" -eq 1777 ]; then
trash_dir="$top/.Trash/$uid"
else
trash_dir="$top/.Trash-$uid"
fi
# If we can't create a top-dir trash on this mount (e.g. read-only /
# root or restricted top dir), fall back to the home trash, which is
# always writable. This matches what GVFS does.
if ! mkdir -p -- "$trash_dir/files" "$trash_dir/info" 2>/dev/null; then
trash_dir=$home_trash
fi
fi
files_dir=$trash_dir/files
info_dir=$trash_dir/info
if ! mkdir -p -- "$files_dir" "$info_dir"; then
echo "Error: cannot create trash dir '$trash_dir' for '$item'." >&2
continue
fi
# Pick a unique name, preserving the extension (and handling none).
base=$(basename -- "$item")
if [[ $base == *.* ]]; then
stem=${base%.*}
ext=.${base##*.}
else
stem=$base
ext=
fi
candidate=$base
n=1
while [ -e "$files_dir/$candidate" ] || [ -e "$info_dir/$candidate.trashinfo" ]; do
candidate=${stem}_$n$ext
n=$((n + 1))
done
# Write the .trashinfo first; if mv fails, remove it to stay consistent.
deletion_date=$(date +%Y-%m-%dT%H:%M:%S)
info_path=$info_dir/$candidate.trashinfo
{
printf '[Trash Info]\n'
printf 'Path=%s\n' "$abs"
printf 'DeletionDate=%s\n' "$deletion_date"
} > "$info_path"
if mv -f -- "$item" "$files_dir/$candidate"; then
echo "Moved '$item' to '$files_dir/$candidate'"
else
echo "Error: failed to move '$item'." >&2
rm -f -- "$info_path"
fi
done
}
trash "$@"