gitoriaLog in with ident

tracker

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Main branchmain5cb85d75deploy.sh: a backup taken while a background job writes (tar exit 1) is a warning; archive checked with gzip -tmremain/tools/sync-tmdb.hl

5.4 KB

  1. // tools/sync-tmdb.hl — THE TMDB SYNC FROM THE COMMAND LINE (tracker.worldapi.org#9): the same per-show sync
  2. // the app runs every day (lib/sync.hl), for every followed show, one after the other, waiting between shows
  3. // so TMDB's 40 requests / 10 s (and TVmaze's 20 / 10 s) are never exceeded. Idempotent: a second run adds nothing (prints 0 new).
  4. //
  5. // STOP THE APP FIRST (or run it on a COPY): hl:mpackdb does not coordinate two processes on one storage
  6. // directory (hybriel#21). The running app does this itself every day (jobs.hl `syncTick`) — this tool is
  7. // for tests, the real-data proof and a manual catch-up.
  8. //
  9. // set -a; . ./.env; set +a # TMDB_READ_TOKEN — never print it
  10. // TRACKER_STORAGE=/abs/path/storage/mpackdb ./bin/hybriel tools/sync-tmdb.hl [--limit N] [--delta [--since YYYY-MM-DD] [--plan]]
  11. //
  12. // Default: the FULL WALK (every followed show). --delta (tracker#18): what the app's daily run does — the change lists since
  13. // the last run (dailysync.hl; `--since` overrides the stored day), then only the changed titles of ours; stores the day at the
  14. // end like the app (not with --since or --limit). --plan: only the change lists and the queue they give, no title synced.
  15. //
  16. // Prints one line per show and the totals: shows, newSeasons, newEpisodes, updatedEpisodes, updatedSeasons,
  17. // posters, ids (external ids filled, tracker.worldapi.org#12), requests, tvmazeRequests, errors, seconds, and (#15) what
  18. // TVmaze added: tvmazeEpisodes, tvmazeSeasons, tvmazeFilled (episodes whose empty title/air date it filled), tvmazeSkipped
  19. // (shows TVmaze numbers differently, left alone).
  20. import { env, args, exit } from 'hl:proc'
  21. import { now, sleep, timestamp } from 'hl:time'
  22. import { syncEnabled, pauseMsAfter } from '../lib/tmdb.hl'
  23. import { persistSync } from '../lib/sync.hl'
  24. import { emptyTotals, addTotals, totalsLine } from '../lib/sync-helpers.hl'
  25. import { allFollowedShowIds } from '../lib/follows.hl'
  26. import { applySynced, persistDetails } from '../lib/details.hl'
  27. import { planStart, planStep, planIds, planLine, markSynced, isFollowedInRun, isStatusOnlyInRun, syncRunStep } from '../lib/dailysync.hl'
  28. storageDir = env('TRACKER_STORAGE')
  29. if (storageDir == null || !storageDir.startsWith('/')) { console.log('REFUSED: set TRACKER_STORAGE, absolute (app stopped, or a copy)') exit(2) }
  30. if (!syncEnabled) { console.log('REFUSED: TMDB_READ_TOKEN is not set') exit(2) }
  31. limit = -1
  32. delta = false
  33. planOnly = false
  34. since = null
  35. argv = args()
  36. i = 0
  37. while (i < argv.length) {
  38. if (argv[i] == '--limit' && i + 1 < argv.length) { limit = toNumber(argv[i + 1]) }
  39. if (argv[i] == '--delta') { delta = true }
  40. if (argv[i] == '--plan') { planOnly = true }
  41. if (argv[i] == '--since' && i + 1 < argv.length) { since = argv[i + 1] }
  42. i = i + 1
  43. }
  44. started = now()
  45. totals = emptyTotals()
  46. today = timestamp(started).slice(0, 10)
  47. runMode = 'full'
  48. changeRequests = 0
  49. ids = []
  50. if (delta) {
  51. // --since: as if the last run had been on that day (the stored day stays as it is)
  52. p = planStart(today, since)
  53. runMode = p.mode
  54. console.log('tmdb sync: ' + p.mode + ' (' + p.why + ')')
  55. if (p.mode == 'delta') {
  56. t0 = now()
  57. let done = false
  58. while (!done) {
  59. let ps = planStep()
  60. changeRequests = changeRequests + ps.requests + ps.tvmazeRequests
  61. totals.requests = totals.requests + ps.requests
  62. totals.tvmazeRequests = totals.tvmazeRequests + ps.tvmazeRequests
  63. done = ps.done
  64. if (ps.failed != '') { runMode = 'full' }
  65. sleep(pauseMsAfter(ps) / 1000)
  66. }
  67. console.log('tmdb sync: ' + planLine() + ' in ' + ((now() - t0) / 1000) + ' s')
  68. ids = runMode == 'delta' ? planIds() : allFollowedShowIds()
  69. } else { ids = allFollowedShowIds() }
  70. } else { ids = allFollowedShowIds() }
  71. if (planOnly) {
  72. console.log('tmdb sync plan: ' + ids.length + ' titles: ' + ids.join(' '))
  73. exit(0)
  74. }
  75. console.log('tmdb sync: ' + ids.length + (runMode == 'delta' ? ' changed titles' : ' followed shows') + (limit >= 0 ? ', limit ' + limit : ''))
  76. for (id of ids) {
  77. if (limit < 0 || totals.shows < limit) {
  78. // tracker#18: in a delta run a title nobody follows gets the light step (changed seasons, no TVmaze — sync.hl syncShowDelta);
  79. // tracker#35: one only on TVmaze's change list its TVmaze status (dailysync.hl syncRunStep)
  80. let light = runMode == 'delta' && !isFollowedInRun(id)
  81. let r = syncRunStep(id, runMode == 'delta')
  82. totals = addTotals(totals, r)
  83. // tracker#18 + #28: the record's own fields + the cast + crew from the same details answer
  84. let c = r.details != null ? applySynced(id, r.details) : { renamed = false cast = 0 crew = 0 people = 0 error = '' }
  85. console.log(' ' + id + (isStatusOnlyInRun(id) ? ' (TVmaze status)' : (light ? ' (light)' : '')) + (c.renamed ? ' (renamed)' : '') + ': +' + r.newSeasons + ' seasons +' + r.newEpisodes + ' episodes ~' + r.updatedEpisodes + ' updated, poster ' + r.posters + ', ids +' + r.ids
  86. + ', tvmaze +' + r.tvmazeEpisodes + ' episodes +' + r.tvmazeSeasons + ' seasons ~' + r.tvmazeFilled + ' filled' + (r.tvmazeSkipped > 0 ? ' SKIPPED (numbered differently)' : '') + ', cast ' + c.cast + ' crew ' + c.crew + ' +' + c.people + ' people' + (r.error != '' ? ' ERROR ' + r.error : '') + (c.error != '' ? ' ERROR ' + c.error : ''))
  87. if (totals.shows % 10 == 0) { persistDetails() }
  88. sleep(pauseMsAfter(r) / 1000)
  89. }
  90. }
  91. persistDetails()
  92. if (delta && since == null && limit < 0 && runMode == 'delta') { markSynced(today) }
  93. console.log('tmdb sync done: mode=' + runMode + ' changeRequests=' + changeRequests + ' ' + totalsLine(totals) + ' seconds=' + ((now() - started) / 1000))
  94. on Error(e) { console.log(' error absorbed: ' + e.message) }

Branches

Latest commits

  • 5cb85d75deploy.sh: a backup taken while a background job writes (tar exit 1) is a warning; archive checked with gzip -tmre
  • 9abda75dtracker: report 035mre
  • 12595e47mission 035: theme re-vendored from layouts.worldapi.org 0222f67 (two corner radii: radiusSmall 5px, radiusLarge 10px); the tracker's 18 own radii -> radiusSmall/radiusLarge (--layout-radius is gone); check-theme 0; gates 379/0, 32/0, 53/0, 229/0, 26/0; real copy: every computed radius in {0, 5px, 10px, 50%}mre
  • e7305014tracker: report 032 (art + photos)mre
  • fbb903cctracker#33/#37 (mission 032): a title without a TMDB poster gets its backdrop (w780, posterFromBackdrop, shown 2:3 centre-cropped); movies store runtime, the page shows release date + runtime; art backfill (public titles without poster file / movies without runtime) and person photo backfill (tmdbProfile / photoCheck) as the last start jobs (TRACKER_ART, TRACKER_PHOTOS; off in every gate start); gates 379/0, 32/0, 53/0, 229/0, 26/0, check-theme 0; live copy: art 4977 titles in 42 min (115 posters, 15 backdrops, 4609 runtimes), The Remaining shows its backdrop + 7 minmre
  • 4ecc67b2tracker: STATUS/LOG for the t38 + t40 merge (gates 374/0, 32/0, 53/0, 229/0, 26/0, check-theme 0; live copy checks)mre
  • e5945d2fMerge t40 (tracker#40 curated franchises, Franchises menu, superseded collections) into main: deploy.sh lists all six gates (browser, kinds, franchises, pager, franchiseseed, check-theme); search.hl keeps #37's personPhotoOf + #40's importWithCredits; pager gate runs with TRACKER_FRANCHISE_SEED=0; gates 374/0, 32/0, 53/0, 229/0, 26/0, check-theme 0mre
  • b638e99dMerge t38 (tracker#38 pagination, #35 Returning/Airing label) into main: LOG/STATUS keep both sides; pager gate follows #37's /people (everyone, last updated first: seed updatedAt); gates pager 229/0, browser 374/0mre
  • f8ffa4dbtracker: report 032 + The Remaining + #39mre
  • 7565a863tracker: LOG timemre
  • b10f00c8tracker#39: double episodes — migrated episodes whose TMDB id TMDB replaced are adopted by their number in the sync (old id -> migratedTmdbId); merge.hl step 3 merges each season's doubles at start (keeper: most watches > synced > first; watches moved/parked; tombstones into mergedEpisodes, nothing deleted); tools/count-duplicate-episodes.hl; gate fixture + paths-m039; live copy 850 -> 0 in 64 s; gates 373/0, 32/0, 52/0mre
  • 8f1d4542tracker#40: superseded collections — a TMDB collection timeline whose titles are all in one curated timeline is hidden (supersededBy; kept: own page + editor finder), set by the collection seed when it makes one and by the curated build (lifted when the cover is gone); partly covered ones join that franchise; no second widget (First Contact: only Star Trek — Prime); gate franchiseseed 26/0, browser 365/0, kinds 32/0, franchises 53/0, check-theme 0; real copy 24 supersededmre
  • 9448d643tracker#35 follow-up (mission 033): TVmaze 'Running' is labelled 'Returning', or 'Airing' while a non-special episode of the two newest seasons is released within today +-7 days (data unchanged); gate fixtures Running/Airing/Aired + a special; browser 366/0, kinds 32/0, franchises 52/0, pager 229/0, check-theme 0mre
  • 7d4b293dtracker#40: "Franchises" in the main menu (desktop header after People, phone sidebar) → /franchises, marked on franchise and timeline pages; gates 365/0, 32/0, 53/0, 24/0, check-theme 0mre
  • 8751adb8tracker: report 032mre
  • 9bce1f65tracker mission 032: STATUS gate files + the hour-boundary flakemre
  • 718bfb89tracker#37 (mission 032): /people = everyone, last updated first (updatedAt stamped by the person fill; view built at boot, touched people first at once), photo + name tiles (person colour) with the /movies pagination, /people/<letter> removed; photo = our file, tmdbProfile, a cast/crew entry's profile (in-memory map at boot), else the new 'no photo' placeholder; new cast/crew/created_by people keep tmdbProfile; search people rows with the photo; /settings = the heading only; util.hl sortDesc starts from sorted runs (same result, 105k: 1.6 s -> 0.15 s); gates 369/0, 32/0, 52/0, check-theme 0; README/STATUS/LOGmre
  • 93dfb0batracker#40 (mission 034): the curated franchises — data/franchises.json (17 franchises, 31 timelines, 285 TMDB titles, movies + series, in-universe/release order, 12 TMDB collections attached); lib/franchiseseed.hl + jobs.hl franchiseSeedTick (last start job, imports missing titles via details.hl importWithCredits = the search's Add, one per step paced, then one build; franchiseseed.db: editor changes win, the creator's same-name franchise adopted / timeline left alone, 404 remembered, resumable, idempotent); timeline heads 'N titles · in-universe order' (orderKind) and wrap on a phone; series pages show the widget; new gate tests/franchiseseed.mjs (5th in deploy.sh), the others run with TRACKER_FRANCHISE_SEED=0; gates 365/0, 32/0, 52/0, 24/0, check-theme 0; real copy 196 imported, 0 failed, 7 min, restart unchanged=31mre
  • 03ec792ftracker#38 (mission 033): pagination goes exactly to the clicked page — tilelist read the clicked button's text after pagination.hl's own handler had rebuilt the buttons (real clicks only); now li.current, else the button's own text; new gate tests/pager.mjs (5 lists x 11 pages, 390/1280, real + script clicks, Back/Forward) 229/0; browser 365/0, kinds 32/0, franchises 52/0, check-theme 0mre
  • 96ba683adeploy.sh: a gate without a 'passed,' line (check-theme) no longer ends the scriptmre