phase 3 draft
Modified: February 14, 2026 7:58 PM Category: Coding Notes List: Projects Created: September 9, 2025 10:26 PM Master Type: Notes Hide: No Starred: No Status: Unassigned
#!/bin/bash
set -euo pipefail
# ---------------------------
# Load config
# ---------------------------
TOP_ARG="${1:-}"
ROOT_VOLUME="${TOP_ARG:-}"
CONFIG_FILE="$ROOT_VOLUME/config.sh"
[ -f "$CONFIG_FILE" ] || { echo "Config not found at $CONFIG_FILE"; exit 1; }
. "$CONFIG_FILE"
# ---------------------------
# Determine working folder
# ---------------------------
TOP="$TOP_BASE"
if [ -n "$TOP_ARG" ] && [ -d "$TOP_ARG" ] && [ "$TOP_ARG" != "$ROOT_VOLUME" ]; then
TOP="$TOP_ARG"
fi
COLLECTION_DIR="$TOP/Collection"
DEFAULT_PROCESSED_DIR="$TOP/Processed"
DEST="$COLLECTION_DIR/Verso Positives"
IDLE_SECONDS=${PHASE3_IDLE_SECONDS}
SLEEP_INTERVAL=${PHASE3_SLEEP_INTERVAL}
[ -d "$COLLECTION_DIR" ] || echo "Warning: Collection folder not found: $COLLECTION_DIR"
echo "Processing Phase 3 from Processed roots into: $COLLECTION_DIR"
# ---------------------------
# Wait until folder idle
# ---------------------------
wait_until_idle() {
dir="$1"
while :; do
latest=$(find "$dir" -type f -print0 2>/dev/null | xargs -0 stat -f '%m' 2>/dev/null | sort -n | tail -n1 || echo 0)
now=$(date +%s)
[ "$latest" -eq 0 ] || [ $((now - latest)) -ge "$IDLE_SECONDS" ] && break
sleep "$SLEEP_INTERVAL"
done
}
# ---------------------------
# Build Processed roots from config (keep compatibility)
# - If PROCESSED_DIRS array exists, use those (under $TOP).
# - Otherwise, fall back to $TOP/Processed.
# - If $TOP/Processed is present, prefer scanning only that root
# (prevents duplicate scans of its subfolders if listed).
# ---------------------------
SRC_ROOTS=()
if declare -p PROCESSED_DIRS >/dev/null 2>&1; then
for suffix in "${PROCESSED_DIRS[@]}"; do
root="$TOP/$suffix"
[ -d "$root" ] && SRC_ROOTS+=("$root")
done
else
[ -d "$DEFAULT_PROCESSED_DIR" ] && SRC_ROOTS+=("$DEFAULT_PROCESSED_DIR")
fi
# Prefer single root if $TOP/Processed exists
if [ -d "$DEFAULT_PROCESSED_DIR" ]; then
SRC_ROOTS=("$DEFAULT_PROCESSED_DIR")
fi
# Idle wait on all sources + collection
for r in "${SRC_ROOTS[@]}"; do
[ -d "$r" ] && wait_until_idle "$r"
done
[ -d "$COLLECTION_DIR" ] && wait_until_idle "$COLLECTION_DIR"
# Ensure Verso Positives base folders (no PSD here)
mkdir -p "$DEST/JPG" "$DEST/TIFF"
# ---------------------------
# Helpers (Bash 3–safe)
# ---------------------------
to_lower() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; }
to_upper() { printf '%s' "$1" | tr '[:lower:]' '[:upper:]'; }
# ---------------------------
# Main move
# ---------------------------
SECONDS_IN_WEEK=$((7*24*60*60))
now=$(date +%s)
for ROOT in "${SRC_ROOTS[@]}"; do
# Scan all files under each root (depth doesn't matter)
while IFS= read -r -d '' file; do
[ -f "$file" ] || continue
fname=$(basename "$file")
ext="${fname##*.}"
name_noext="${fname%.*}"
ext_lc=$(to_lower "$ext")
fname_lc=$(to_lower "$fname")
# ---------------------------
# Verso Positives: v-A / p-A
# ---------------------------
case "$fname_lc" in
*v-a.*|*p-a.*)
case "$ext_lc" in
jpg|jpeg) mv -n "$file" "$DEST/JPG/$fname" ;;
tif|tiff) mv -n "$file" "$DEST/TIFF/$fname" ;;
*) mv -n "$file" "$DEST/$fname" ;; # do NOT create PSD here
esac
continue
;;
esac
# ---------------------------
# Enclosure parsing (use prefix before first hyphen only)
# A. HBYYYY + [no code] + NN/NNN -> binder (B)
# A. HBYYYY + B + NN/NNN -> binder (B)
# B. HBYYYY + AA + NN/NNN -> non-binder (AA)
# Everything else -> unparsed bucket
# ---------------------------
base_prefix="${fname%%-*}"
enclosure_long=""
enclosure_short=""
if [[ $base_prefix =~ ^HB[0-9]{4}B([0-9]{2,3})$ ]]; then
num="${BASH_REMATCH[1]}"
shortnum=$(printf '%s' "$num" | sed 's/^0*//'); [ -z "$shortnum" ] && shortnum=0
enclosure_long=$(printf 'B%03d' "$shortnum") # e.g., B002
enclosure_short="B$shortnum" # e.g., B2
elif [[ $base_prefix =~ ^HB[0-9]{4}([0-9]{2,3})$ ]]; then
num="${BASH_REMATCH[1]}"
shortnum=$(printf '%s' "$num" | sed 's/^0*//'); [ -z "$shortnum" ] && shortnum=0
enclosure_long=$(printf 'B%03d' "$shortnum") # binder without letter
enclosure_short="B$shortnum"
elif [[ $base_prefix =~ ^HB[0-9]{4}([A-Z]{2})([0-9]{2,3})$ ]]; then
code="${BASH_REMATCH[1]}"
num="${BASH_REMATCH[2]}"
shortnum=$(printf '%s' "$num" | sed 's/^0*//'); [ -z "$shortnum" ] && shortnum=0
# long folder keeps two digits if <=2, else three
if [ ${#num} -ge 3 ]; then
enclosure_long=$(printf '%s%03d' "$code" "$shortnum") # e.g., FL190
else
enclosure_long=$(printf '%s%02d' "$code" "$shortnum") # e.g., GP07, FL05
fi
short_letter=$(printf '%.1s' "$code") # FL->F, GP/GL->G, etc.
enclosure_short="${short_letter}${shortnum}" # e.g., F5, G10
else
target_dir="$COLLECTION_DIR/unparsed/_manual_check"
mkdir -p "$target_dir"
mv -n "$file" "$target_dir/$fname"
continue
fi
# ---------------------------
# Target subfolder from the *file's* extension (not its location in /Processed)
# ---------------------------
case "$ext_lc" in
jpg|jpeg)
mtime=$(stat -f %m "$file")
age=$(( now - mtime ))
[ "$age" -le "$SECONDS_IN_WEEK" ] && sub="Processing" || sub="JPG"
;;
tif|tiff) sub="TIFF" ;;
psd) sub="PSD" ;;
*) sub=$(to_upper "$ext_lc") ;;
esac
# Final destination: Collection/EnclosureLong/EnclosureShort⎵SUB/file.ext
dest="$COLLECTION_DIR/$enclosure_long/$enclosure_short $sub"
[ -d "$dest" ] || mkdir -p "$dest"
# Move without overwriting (prevents dupes)
mv -n "$file" "$dest/$fname"
done < <(find "$ROOT" -type f -print0)
done
echo "Phase 3 complete."