#!/bin/bash
#
# Wildflower Sound - uninstall the plugins from this Mac.
#
# WHAT IT REMOVES
#   /Library/Audio/Plug-Ins/VST3/<Plugin>.vst3
#   /Library/Audio/Plug-Ins/Components/<Plugin>.component
#   /Library/Application Support/Avid/Audio/Plug-Ins/<Plugin>.aaxplugin
#   /Applications/Wildflower/<Plugin>.app
#   /Library/Application Support/Wildflower   (the manuals and factory presets
#                                              the installer put there)
#
# The .app line stays on purpose. Wildflower 1.2 and later install no standalone
# app at all, but 1.1 and earlier did, and this script has to clear a Mac that was
# set up with one of those. Nothing goes wrong if there is none to find.
#
# WHAT IT LEAVES ALONE
#   Your license, your saved presets, and anything else of yours under your own
#   home folder. Reinstalling picks up exactly where you left off. If you really
#   do want those gone as well, pass --all and it will say so before it starts.
#
# HOW TO RUN IT, in Terminal:
#   bash uninstall-mac.sh                remove the plugins
#   bash uninstall-mac.sh --dry-run      list what would go, change nothing
#   bash uninstall-mac.sh --all          also remove your license and your presets
#
# The plugins live in /Library and /Applications, which every account on the Mac
# shares, so removing them needs administrator rights. The script asks for your
# password once, through the standard sudo prompt, and does nothing else with it.
#
# Nothing here touches your account or your license keys on wildflowersound.io.
# You can install again any time from the downloads page.
#
# Questions, or something looks wrong: support@wildflowersound.io

set -euo pipefail

# The folders we install into. Every path this script deletes is built from one of
# these fixed values plus a single bundle name, so it can never widen into a folder
# we do not own.
PLUGIN_DIRS=(
  "Library/Audio/Plug-Ins/VST3"
  "Library/Audio/Plug-Ins/Components"
  "Library/Application Support/Avid/Audio/Plug-Ins"
  "Applications/Wildflower"
)
SHARE_DIR="Library/Application Support/Wildflower"

# The two folders that hold YOUR files. Only --all removes these.
USER_DIRS=(
  "Library/Wildflower"
  "Documents/Wildflower"
)

# WF_ROOT lets this script be exercised against a copy of the folder layout instead
# of the real machine. Leave it unset and everything below reads and writes the real
# paths. It is also the reason the sudo step is skipped in that mode: a copy in a
# scratch folder needs no administrator rights.
WF_ROOT="${WF_ROOT:-}"

DRY_RUN=0
ALL=0

usage() {
  cat <<'USAGE'
Wildflower Sound - uninstall the plugins from this Mac.

  bash uninstall-mac.sh              remove the plugins
  bash uninstall-mac.sh --dry-run    list what would go, change nothing
  bash uninstall-mac.sh --all        also remove your license and your presets
  bash uninstall-mac.sh --help       this message

Your license and your saved presets are kept unless you pass --all.
USAGE
}

for arg in ${1+"$@"}; do
  case "$arg" in
    --dry-run) DRY_RUN=1 ;;
    --all)     ALL=1 ;;
    -h|--help) usage; exit 0 ;;
    *)
      printf 'That is not an option this script knows: %s\n\n' "$arg" >&2
      usage >&2
      exit 2
      ;;
  esac
done

# Later installers also leave a copy of this script inside the shared folder that it
# removes. Deleting a script while it is still being read is asking for trouble, so
# in that one case it runs from a copy in a temporary folder instead.
SELF="$0"
case "$SELF" in
  */"$SHARE_DIR"/*)
    if [ -z "${WF_SELF_COPY:-}" ]; then
      COPY="$(mktemp "${TMPDIR:-/tmp}/wfuninstall.XXXXXX")"
      cp "$SELF" "$COPY"
      export WF_SELF_COPY=1
      status=0
      bash "$COPY" ${1+"$@"} || status=$?
      rm -f "$COPY"
      exit $status
    fi
    ;;
esac

# Removing shared folders needs administrator rights, so the script asks for them
# once, up front, rather than failing part way through with half the plugins gone.
if [ -z "$WF_ROOT" ] && [ "$(id -u)" != "0" ]; then
  printf 'Administrator rights are needed to remove plugins from /Library.\n'
  printf 'You will be asked for your password once.\n\n'
  exec sudo -p 'Password: ' /bin/bash "$0" ${1+"$@"}
fi

# Where your own files live. Under sudo the home folder to look at is the person who
# typed the password, not root.
home_dir() {
  if [ -n "${SUDO_USER:-}" ]; then
    dscl . -read "/Users/$SUDO_USER" NFSHomeDirectory 2>/dev/null \
      | sed 's/^NFSHomeDirectory: //' | head -1
  else
    printf '%s\n' "${HOME:-}"
  fi
}

# A name we recognize: every plugin we have ever shipped is called Wild-something or
# WF-something, and arrives in one of four bundle shapes.
is_our_bundle_name() {
  case "$1" in
    Wild*|WF*) ;;
    *) return 1 ;;
  esac
  case "$1" in
    *.vst3|*.component|*.app|*.aaxplugin) return 0 ;;
    *) return 1 ;;
  esac
}

# A second check, used only when there are no installer receipts to read: the bundle
# has to identify itself as one of ours from the inside before it is touched.
is_our_bundle_inside() {
  [ -f "$1/Contents/Info.plist" ] || return 1
  LC_ALL=C grep -q 'com.wildflower' "$1/Contents/Info.plist" 2>/dev/null
}

LIST="$(mktemp "${TMPDIR:-/tmp}/wfuninstall.XXXXXX")"
RECEIPTS="$(mktemp "${TMPDIR:-/tmp}/wfuninstall.XXXXXX")"
trap 'rm -f "$LIST" "$RECEIPTS"' EXIT
: > "$LIST"
: > "$RECEIPTS"

# THE FILE LIST IS READ OFF THE MACHINE, NOT WRITTEN DOWN HERE. The installer leaves
# one receipt per plugin, and each receipt knows every file it laid down, so a plugin
# released after this script was written is still removed correctly.
if command -v pkgutil >/dev/null 2>&1 && [ -z "$WF_ROOT" ]; then
  pkgutil --pkgs 2>/dev/null | grep "^com\.wildflower\.pkg\." > "$RECEIPTS" || true
fi

if [ -s "$RECEIPTS" ]; then
  while IFS= read -r pkg_id; do
    [ -n "$pkg_id" ] || continue
    while IFS= read -r rel; do
      for dir in "${PLUGIN_DIRS[@]}"; do
        case "$rel" in
          "$dir"/*)
            rest="${rel#$dir/}"
            name="${rest%%/*}"
            if is_our_bundle_name "$name" && [ -e "$WF_ROOT/$dir/$name" ]; then
              printf '%s\n' "/$dir/$name" >> "$LIST"
            fi
            ;;
        esac
      done
    done < <(pkgutil --files "$pkg_id" 2>/dev/null || true)
  done < "$RECEIPTS"
fi

# Always ALSO look in the four folders by hand, not only when there are no receipts.
# 1.2 reuses the receipt ids, so after an upgrade the receipts list only 1.2's files and
# a standalone app that 1.1 installed would survive a receipt-only pass (found in the
# 1.2 review). Only bundles that carry one of our names AND say they are ours inside are
# considered, and sort -u below removes the duplicates the receipts already listed.
if true; then
  for dir in "${PLUGIN_DIRS[@]}"; do
    full="$WF_ROOT/$dir"
    [ -d "$full" ] || continue
    for path in "$full"/*; do
      [ -e "$path" ] || continue
      name="$(basename "$path")"
      is_our_bundle_name "$name" || continue
      is_our_bundle_inside "$path" || continue
      printf '%s\n' "/$dir/$name" >> "$LIST"
    done
  done
fi

sort -u "$LIST" -o "$LIST"

# The shared folder is ours whole, so it goes as one item rather than file by file.
SHARE_FULL="$WF_ROOT/$SHARE_DIR"
HAVE_SHARE=0
if [ -d "$SHARE_FULL" ]; then HAVE_SHARE=1; fi

# The two folders of your own files, only ever listed when --all was passed.
USER_LIST="$(mktemp "${TMPDIR:-/tmp}/wfuninstall.XXXXXX")"
trap 'rm -f "$LIST" "$RECEIPTS" "$USER_LIST"' EXIT
: > "$USER_LIST"
if [ "$ALL" = "1" ]; then
  HOME_DIR="$(home_dir)"
  if [ -n "$HOME_DIR" ]; then
    for rel in "${USER_DIRS[@]}"; do
      if [ -d "$WF_ROOT$HOME_DIR/$rel" ]; then
        printf '%s\n' "$HOME_DIR/$rel" >> "$USER_LIST"
      fi
    done
  fi
fi

COUNT="$(wc -l < "$LIST" | tr -d ' ')"
USER_COUNT="$(wc -l < "$USER_LIST" | tr -d ' ')"

printf '\nWildflower Sound uninstaller\n\n'

if [ "$COUNT" = "0" ] && [ "$HAVE_SHARE" = "0" ] && [ "$USER_COUNT" = "0" ]; then
  printf 'Nothing to remove. No Wildflower Sound plugins were found on this Mac.\n\n'
  exit 0
fi

if [ "$COUNT" != "0" ]; then
  printf 'Plugins to remove (%s):\n' "$COUNT"
  while IFS= read -r p; do printf '  %s\n' "$p"; done < "$LIST"
  printf '\n'
fi

if [ "$HAVE_SHARE" = "1" ]; then
  printf 'Manuals and factory presets to remove:\n  /%s\n\n' "$SHARE_DIR"
fi

if [ "$ALL" = "1" ]; then
  if [ "$USER_COUNT" != "0" ]; then
    printf 'Your own files, because you passed --all:\n'
    while IFS= read -r p; do printf '  %s\n' "$p"; done < "$USER_LIST"
    printf '\nThat includes your license and the presets you have saved. You will need\n'
    printf 'to paste your key in again the next time you install.\n\n'
  fi
else
  printf 'Your license and your saved presets are being kept.\n\n'
fi

if [ "$DRY_RUN" = "1" ]; then
  printf 'This was a dry run. Nothing was changed.\n\n'
  exit 0
fi

printf 'Remove these now? [y/N] '
reply=""
read -r reply || reply=""
case "$reply" in
  y|Y|yes|Yes|YES) ;;
  *) printf '\nStopped. Nothing was changed.\n\n'; exit 0 ;;
esac
printf '\n'

# Each path is checked one last time, immediately before it is removed, so a name
# that stopped matching between the listing and now is left where it is.
remove_bundle() {
  target="$WF_ROOT$1"
  name="$(basename "$target")"
  is_our_bundle_name "$name" || return 0
  [ -e "$target" ] || return 0
  rm -rf "$target"
  printf '  removed  %s\n' "$1"
}

while IFS= read -r p; do
  [ -n "$p" ] || continue
  remove_bundle "$p"
done < "$LIST"

# /Applications/Wildflower is ours, but only tidy it away when it is empty, in case
# something else has been put there.
APPS_DIR="$WF_ROOT/Applications/Wildflower"
if [ -d "$APPS_DIR" ]; then
  rmdir "$APPS_DIR" 2>/dev/null && printf '  removed  /Applications/Wildflower\n' || true
fi

if [ "$HAVE_SHARE" = "1" ] && [ -d "$SHARE_FULL" ]; then
  rm -rf "$SHARE_FULL"
  printf '  removed  /%s\n' "$SHARE_DIR"
fi

if [ "$ALL" = "1" ] && [ "$USER_COUNT" != "0" ]; then
  while IFS= read -r p; do
    [ -n "$p" ] || continue
    case "$p" in
      */Wildflower) ;;
      *) continue ;;
    esac
    if [ -d "$WF_ROOT$p" ]; then
      rm -rf "$WF_ROOT$p"
      printf '  removed  %s\n' "$p"
    fi
  done < "$USER_LIST"
fi

# Clear the installer receipts last, so the Mac no longer believes the plugins are
# there. Doing this before the files were gone would leave a wrong record behind if
# anything above had failed.
if [ -s "$RECEIPTS" ]; then
  while IFS= read -r pkg_id; do
    [ -n "$pkg_id" ] || continue
    pkgutil --forget "$pkg_id" >/dev/null 2>&1 || true
  done < "$RECEIPTS"
  printf '  cleared  installer records\n'
fi

printf '\nDone. '
if [ "$ALL" = "1" ]; then
  printf 'Everything Wildflower Sound put on this Mac has been removed.\n'
else
  printf 'Your license and your saved presets were left in place.\n'
fi
printf 'You can install again any time from your account at wildflowersound.io.\n\n'
