#!/usr/bin/env bash
# f2s - upload files of any size to https://file2share.us from the terminal
#
# Install:  curl -sLo ~/.local/bin/f2s https://file2share.us/f2s.sh && chmod +x ~/.local/bin/f2s
# Usage:    f2s FILE [FILE...]
#           F2S_DAYS=3 f2s FILE      # expire after 3 days (default and max: 14)
#
# Files up to 95 MB go up in one request. Bigger files are sent in 50 MB chunks,
# each retried up to 5 times, so a flaky connection does not restart the whole upload.
set -euo pipefail

BASE="${F2S_URL:-https://file2share.us}"
DAYS="${F2S_DAYS:-14}"
SINGLE_LIMIT=$((95 * 1024 * 1024))

die() { echo "f2s: $*" >&2; exit 1; }
command -v curl >/dev/null || die "curl is required"
[ $# -gt 0 ] || { sed -n '2,9p' "$0" | sed 's/^# \{0,1\}//'; exit 1; }

filesize() { stat -c %s "$1" 2>/dev/null || stat -f %z "$1"; }
urlencode() {
  local s="$1" out="" c i
  for ((i = 0; i < ${#s}; i++)); do
    c="${s:i:1}"
    case "$c" in [a-zA-Z0-9._~-]) out+="$c" ;; *) out+=$(printf '%%%02X' "'$c") ;; esac
  done
  printf '%s' "$out"
}
json_field() { sed -n "s/.*\"$1\":\"\{0,1\}\([^\",}]*\).*/\1/p"; }

upload() {
  local file="$1" name size
  [ -f "$file" ] || die "$file: not a file"
  name=$(basename "$file")
  size=$(filesize "$file")

  if [ "$size" -le "$SINGLE_LIMIT" ]; then
    curl -fsS --retry 3 -H "Max-Days: $DAYS" -T "$file" "$BASE/$(urlencode "$name")" -D /tmp/f2s.$$.h
  else
    local start upload_id part_size parts n offset attempt
    start=$(curl -fsS --retry 3 -X POST --data-urlencode "name=$name" -d "size=$size" "$BASE/api/v1/uploads") \
      || die "could not start upload"
    upload_id=$(echo "$start" | json_field upload_id)
    part_size=$(echo "$start" | json_field part_size)
    parts=$(echo "$start" | json_field parts)
    [ -n "$upload_id" ] || die "unexpected response: $start"

    for ((n = 1; n <= parts; n++)); do
      offset=$(( (n - 1) * part_size ))
      for attempt in 1 2 3 4 5; do
        if dd if="$file" bs=1048576 skip=$((offset / 1048576)) count=$((part_size / 1048576)) 2>/dev/null \
          | curl -fsS -X PUT -H "Content-Type: application/octet-stream" --data-binary @- \
              "$BASE/api/v1/uploads/$upload_id/$n" >/dev/null; then
          break
        fi
        [ "$attempt" -eq 5 ] && die "part $n failed after 5 attempts"
        sleep $((attempt * 2))
      done
      printf '\r  %s: %d%%' "$name" $((n * 100 / parts)) >&2
    done
    printf '\n' >&2
    curl -fsS --retry 3 -X POST -H "Max-Days: $DAYS" "$BASE/api/v1/uploads/$upload_id/complete" -D /tmp/f2s.$$.h
  fi

  grep -i '^x-delete-url:' /tmp/f2s.$$.h | sed 's/^[^:]*: */  delete: /' | tr -d '\r' >&2 || true
  rm -f /tmp/f2s.$$.h
}

for f in "$@"; do upload "$f"; done
