# shellcheck shell=bash ################################################################################ # TMDB API ################################################################################ # 指定语言的 tmdb_api 封装(语言重试链用):局部覆盖 TMDB_LANG 后调用。 # bash 动态作用域:tmdb_api 内读到的 $TMDB_LANG 为本次调用值; # 缓存 key/路径含语言段(cache_key 拼 lang、id 类路径带 .lang),语言切换天然隔离缓存。 # 参数:lang(如 en-US) + 原 tmdb_api 全部参数。 tmdb_api_lang() { local lang="$1" shift local TMDB_LANG="$lang" tmdb_api "$@" } # 调用 TMDB API 并缓存结果。端点形如 "/search/movie",额外参数以 key=value 传入。 # 先查缓存(命中且未过期直接返回),未命中才请求并写缓存。 # 返回码约定:0=成功(含空结果的有效响应);1=新鲜空哨兵跳过请求; # 2=请求失败(curl 错误/HTTP 非 2xx,递增重试耗尽)——不写空哨兵(失败≠无结果)。 # 返回原始 JSON 响应到 stdout。 tmdb_api() { local endpoint="$1" shift local key path is_search key=$(cache_key "$@") path=$(cache_path "$endpoint" "$key") is_search=0 [[ "$endpoint" == /search/* ]] && is_search=1 # --update-cache 修饰(整理时强制重取):跳过缓存读与空哨兵,直接请求并覆盖缓存 local cached if [[ "${UPDATE_CACHE:-false}" != "true" ]] && cached=$(cache_get "$path" "$is_search"); then echo "$cached" return 0 fi # 新鲜空哨兵:上次已确认空结果,CACHE_EMPTY_TTL_DAYS 内跳过请求(避免重复请求无效数据) if [[ "${UPDATE_CACHE:-false}" != "true" ]] && cache_empty_fresh "$path" "$is_search"; then [[ "$DEBUG_LEVEL" -ge 2 ]] && _log 调试 "缓存为空哨兵(新鲜),跳过请求: ${endpoint}" return 1 fi # 并发去重(识别池多 worker 可能同时 miss 同一查询): # flock 跨进程互斥(内核锁,进程退出自动释放,无残留)→ 锁内双检缓存。 # 锁文件放 /tmp(复用不删除;不污染缓存目录,干运行零持久化副作用)。 local lockfile lockfile="/tmp/mo_lock_$(cache_key_hash "$path")" local locked=false if [[ "${UPDATE_CACHE:-false}" != "true" ]]; then exec 9>"$lockfile" flock 9 locked=true # 双检:等待者可能在锁期间已写入缓存 if cached=$(cache_get "$path" "$is_search"); then exec 9>&- echo "$cached" return 0 fi if cache_empty_fresh "$path" "$is_search"; then exec 9>&- return 1 fi fi local curl_args=( "--get" "-s" "--connect-timeout" "${TMDB_CURL_CONNECT_TIMEOUT}" "--max-time" "${TMDB_CURL_MAX_TIME}" "-fS" ) if $TMDB_USE_BEARER; then curl_args+=("--header" "Authorization: Bearer ${TMDB_AUTH_TOKEN}") fi curl_args+=("--data-urlencode" "language=${TMDB_LANG}") if ! $TMDB_USE_BEARER; then curl_args+=("--data-urlencode" "api_key=${TMDB_AUTH_TOKEN}") fi local a for a in "$@"; do curl_args+=("--data-urlencode" "$a") done local full_url="${TMDB_API_BASE_URL}${endpoint}" [[ "$DEBUG_LEVEL" -ge 2 ]] && _log 调试 "TMDB 请求: ${full_url}?..." # 递增重试(2^n 封顶 16s):curl 的 --retry-delay 为固定延迟,这里手写循环 local result="" local attempt=0 delay=1 while true; do if result=$(curl "${curl_args[@]}" "$full_url" 2>&1); then break fi attempt=$((attempt + 1)) if ((attempt > TMDB_CURL_RETRY)); then _log 错误 "TMDB 请求失败(重试 ${TMDB_CURL_RETRY} 次后): ${full_url} 错误: ${result}" return 2 fi _log 警告 "TMDB 请求失败,${delay}s 后重试(${attempt}/${TMDB_CURL_RETRY}): ${full_url}" sleep "$delay" ((delay *= 2)) [[ $delay -gt 16 ]] && delay=16 done # 搜索空结果 → 写 3 天短 TTL 哨兵(查无此片;新片出现后自动重试);否则正常写缓存 if [[ "$is_search" == "1" ]] && ! echo "$result" | jq -e '.results | length > 0' >/dev/null 2>&1; then cache_put_empty "$path" "$is_search" "$key" else cache_put "$path" "$result" "$is_search" "$key" fi if $locked; then exec 9>&- fi echo "$result" }