#!/bin/bash # omarchy:summary=Save incoming Taildrop files and announce them # omarchy:args=[--once] [directory] # omarchy:examples=omarchy tailscale receive | omarchy tailscale receive --once ~/Desktop set -euo pipefail once=false if [[ ${1:-} == "--once" ]]; then once=true shift fi dir="${1:-${XDG_DOWNLOAD_DIR:-$HOME/Downloads}}" # Taildrop lands in a staging directory next door rather than straight in the # downloads directory: waiting for a delivery can take hours, and everything # else that shows up meanwhile is somebody else's file. Same filesystem, so # handing the finished file over is a rename. staging="$dir/.omarchy-taildrop" mkdir -p "$staging" # Take the name by linking to it rather than by looking and then renaming. # link(2) refuses an existing name, so nothing can land on the chosen one in # the gap between the two. Staging shares the filesystem with the downloads # directory, so the link always resolves and unlinking the staged name # finishes the move. Prints the name it took. claim_path() { local staged="$1" name="${staged##*/}" base ext candidate index=0 base="${name%.*}" ext="${name#"$base"}" [[ -z $base ]] && { base="$name"; ext=""; } while (( index < 1000 )); do if (( index == 0 )); then candidate="$dir/$name" else candidate="$dir/$base-$index$ext" fi if ln -- "$staged" "$candidate" 2>/dev/null; then rm -f -- "$staged" printf '%s\n' "$candidate" return 0 fi # Only a taken name is worth another spin. Anything else failed the link # itself, and the file keeps its place in staging for the next run. [[ -e $candidate ]] || return 1 ((index++)) done return 1 } announce() { local path="$1" local name="${path##*/}" local args=("Received $name" "Saved to ${dir/#$HOME/~}" -u critical) case "${name,,}" in *.png | *.jpg | *.jpeg | *.gif | *.webp | *.avif | *.bmp | *.tif | *.tiff) args+=(--image "$path") ;; *) args+=(-g 󰒊) ;; esac # Announcing is best-effort: the file is already delivered, and under `set -e` # a notification outage would otherwise kill the long-running receiver. omarchy-notification-send "${args[@]}" --exec xdg-open "$path" || true } deliver() { local staged target while IFS= read -r staged; do target=$(claim_path "$staged") || continue announce "$target" done < <(find "$staging" -mindepth 1 -maxdepth 1) } # Anything left staged by an interrupted run still deserves delivering. deliver while true; do if ! tailscale file get --wait --conflict=rename "$staging"; then $once && exit 1 sleep 10 continue fi deliver $once && exit 0 done