- CLI 定义迁移至 src/bashly.yml(bashly 1.4 生成参数解析),入口收敛为 src/root_command.sh - 源码按功能域重组:config/storage/integrate/media/pipeline,消除扁平散落 - 新增零依赖测试框架 tests/(123 用例)与 CI 流水线(lint + test + build + 产物一致性) - build.sh 支持版本单一来源注入与 --check 产物校验;新增 lint.sh(bash -n + shellcheck 零容忍) - 删除旧单文件 media_organizer.sh 与旧扁平模块
114 lines
4.2 KiB
Bash
114 lines
4.2 KiB
Bash
# 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"
|
||
}
|