# 读写 s3://$BUCKET/<channel>/manifest.json（私有桶，不加 public-read），用 ETag 做条件写入（乐观锁）。
# 照 innerken-gmbh/workflows 的 appcenter-publish/scripts/manifest-s3.sh 改的；由 publish-private.sh / rollout.sh source。
# 两次发布（或发布和回滚）同时读到同一份旧清单、各自写回时，后写的一方收到 412，重读重合并再写。

# manifest_get <channel> <输出文件>：设置 MANIFEST_ETAG；清单还不存在时写 {}、ETag 为空
manifest_get(){
  local out
  if out=$(aws s3api get-object --bucket "$BUCKET" --key "$1/manifest.json" "$2" --output json 2>&1); then
    MANIFEST_ETAG=$(printf '%s' "$out" | python3 -c 'import json,sys; print(json.load(sys.stdin)["ETag"])')
  elif printf '%s' "$out" | grep -q 'NoSuchKey'; then
    echo '{}' > "$2"; MANIFEST_ETAG=""
  else
    echo "读清单失败：$out" >&2; return 1
  fi
}

# manifest_put <channel> <文件> <etag>：0 = 写入成功；2 = 被别人抢先改过（412），要重读重试；其他 = 失败
manifest_put(){
  local cond out
  if [ -n "$3" ]; then cond=(--if-match "$3"); else cond=(--if-none-match '*'); fi
  if out=$(aws s3api put-object --bucket "$BUCKET" --key "$1/manifest.json" --body "$2" \
      --content-type application/json --cache-control "no-cache" "${cond[@]}" --output json 2>&1); then
    return 0
  fi
  if printf '%s' "$out" | grep -qE 'PreconditionFailed|ConditionalRequestConflict|\(412\)|\(409\)'; then
    echo "  清单在读写之间被别的发布改过，重新读取再合并" >&2; return 2
  fi
  echo "写清单失败：$out" >&2; return 1
}

# with_manifest <channel> <决定函数>：读 → 决定（函数读 $WORK/old.json 写 $WORK/new.json，不写 = 不改）→ 条件写回，最多 5 次
with_manifest(){
  local ch=$1 fn=$2 attempt rc
  for attempt in 1 2 3 4 5; do
    manifest_get "$ch" "$WORK/old.json"
    rm -f "$WORK/new.json"
    "$fn"
    [ -f "$WORK/new.json" ] || { echo "清单不用改"; return 0; }
    rc=0; manifest_put "$ch" "$WORK/new.json" "$MANIFEST_ETAG" || rc=$?
    [ "$rc" = 0 ] && { echo "已更新 s3://$BUCKET/$ch/manifest.json"; return 0; }
    [ "$rc" = 2 ] || return 1
    sleep $((RANDOM % 5 + 1))
  done
  echo "连续 5 次被抢先改写，放弃" >&2; return 1
}
