refactor: 迁移至 bashly CLI 与功能域模块结构

- 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 与旧扁平模块
This commit is contained in:
Shuery committed 2026-08-14 22:41:28 +08:00
1 parent d978a13a83
commit 0df07ad0ae
48 files changed
+5813 -6812

No files matched your search

+113
View File
@@ -0,0 +1,113 @@
# 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"
}