fix(downloads): second-round review fixes (fork on re-download, title fallbacks)
- redownload now ALWAYS rebuilds into a fresh private asset instead of ever reusing a shared cached file: the cache sig is salted with job_id + current asset_id, so the fork is distinct from both the dedup pool and the job's own current asset. This resolves the co-held silent no-op (finding #1) — a shared asset is left intact for its other holders while THIS job re-fetches its own copy. Resolving the fork BEFORE releasing the old hold (and the unique salted key) also removes the IntegrityError-rollback-after-file-delete window (finding #2). - format_sig gains an optional `salt` for that fork identity. - titles: don't consume the space after `|`, so a "stats | #hashtags" remainder keeps the leading whitespace _TRAILING_HASHTAGS needs — and fall back to the prefix+hashtag-stripped text, so neither the stats nor the SEO hashtags are resurrected when a title empties (#3). - worker: early-fill asset.title falls back to the video id so a title that cleaned to empty doesn't show a blank card while downloading (#5). - Tests for the salt fork and the hashtag-remainder case. (Finding #4 — quota on re-download — intentionally not changed: a re-download reuses the existing job and re-fetches ~the same bytes, so calling check_enqueue would wrongly reject on the job-count cap while footprint is unchanged.)
This commit is contained in:
+10
-5
@@ -25,9 +25,12 @@ import unicodedata
|
||||
_DROP_CATEGORIES = {"So", "Sk", "Cc", "Cf", "Cs", "Co", "Cn"}
|
||||
# Social engagement prefix some sources (Facebook) prepend, e.g. "1.6M views · 66K reactions | ".
|
||||
# Match one-or-more "<count> <label>" tokens (· / , separated) terminated by a pipe, then drop it.
|
||||
# Deliberately does NOT consume the space after `|`: leaving it lets a hashtag-only remainder
|
||||
# ("… | #foo #bar") keep the leading whitespace `_TRAILING_HASHTAGS` needs to strip it; the normal
|
||||
# case's leading space is removed later by the whitespace collapse.
|
||||
_ENGAGEMENT_PREFIX = re.compile(
|
||||
r"^\s*(?:\d[\d.,]*\s*[KMB]?\s*"
|
||||
r"(?:views?|reactions?|likes?|comments?|shares?|plays?|followers?)\s*[·,;]?\s*)+\|\s*",
|
||||
r"(?:views?|reactions?|likes?|comments?|shares?|plays?|followers?)\s*[·,;]?\s*)+\|",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
_LETTER_RUN = re.compile(r"[^\W\d_]+", re.UNICODE)
|
||||
@@ -61,6 +64,7 @@ def normalize_title(raw: str | None) -> str | None:
|
||||
stripped = _ENGAGEMENT_PREFIX.sub("", t)
|
||||
prefix_removed = stripped != t
|
||||
t = _TRAILING_HASHTAGS.sub("", stripped)
|
||||
base = t # after prefix + trailing-hashtag removal — the fallback if de-shouting empties it
|
||||
|
||||
multi = [r for r in _LETTER_RUN.findall(t) if len(r) >= 2]
|
||||
caps = [r for r in multi if r.isupper()]
|
||||
@@ -88,7 +92,8 @@ def normalize_title(raw: str | None) -> str | None:
|
||||
if ch.isalpha():
|
||||
t = t[:i] + ch.upper() + t[i + 1:]
|
||||
break
|
||||
# Nothing survived cleaning: fall back to the engagement-stripped text (never resurrect a
|
||||
# stats prefix). Only use the untouched raw when no prefix was there to strip — e.g. an
|
||||
# all-emoji title whose glyphs were dropped, where returning the original is the lesser evil.
|
||||
return t or stripped.strip() or ("" if prefix_removed else raw)
|
||||
# Nothing survived cleaning: fall back to the prefix+hashtag-stripped text (so neither the
|
||||
# stats prefix nor the SEO hashtags are resurrected). Only use the untouched raw when no prefix
|
||||
# was there to strip — e.g. an all-emoji title whose glyphs were dropped, where returning the
|
||||
# original is the lesser evil.
|
||||
return t or base.strip() or ("" if prefix_removed else raw)
|
||||
|
||||
Reference in New Issue
Block a user