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/plugins/fs/server.hl

4.5 KB

  1. file(String path) {
  2. return __native("fs.file", path)
  3. }
  4. readFile(String path) {
  5. return __native("fs.read_file", path)
  6. }
  7. \* Read a whole file as Bytes (ticket #88): every byte as it is on disk,
  8. nothing decoded — the way back for what writeFile stored from a Bytes. *\
  9. readBytes(String path) {
  10. return toBytes(__native("fs.read_file", path))
  11. }
  12. listDir(String path) {
  13. return __native("fs.list_dir", path)
  14. }
  15. \* Does this path exist? (mission 077) The one call in hl:fs that cannot fail:
  16. it answers true/false, never an Error event — probing a path is a question,
  17. not an operation. Relative paths resolve against the SCRIPT's directory like
  18. every other hl:fs path. *\
  19. exists(String path) {
  20. return __native("fs.exists", path)
  21. }
  22. \* Watch a directory tree for changes (the dev-reload bell): an ordinary
  23. event-loop source — register it and every save arrives as an event.
  24. __native("eventloop.register", watch("."), "changed")
  25. on changed(ev) { … ev.name … }
  26. `exclude` (2026-08-30) names directories the descent must NOT enter — a
  27. path string, or a list of them. Omit it and the whole tree is watched, as
  28. it always was. The paths are compared RESOLVED (relative ones against the
  29. script's directory, like every hl:fs path) and normalised, so `./sessions`,
  30. `sessions` and `var/../sessions` are the same directory, and a sibling
  31. named `sessions-old` is NOT inside `sessions`.
  32. watch(".", "./sessions")
  33. watch(".", ["./sessions", "/var/cache/app"])
  34. A program that writes to a directory it also watches needs this: without
  35. it the only escape was that the walker skips dot-directories (which it
  36. still does — `.git` is the reason), i.e. renaming the directory. *\
  37. watch(String path, exclude) {
  38. return __native("fs.watch", path, exclude)
  39. }
  40. \* Write a whole file, ATOMICALLY (2026-08-28). Everything above this line
  41. reads; hl:fs could report a file's `chmod` and never set one, which is why
  42. nothing in this language could persist anything until now.
  43. The write goes to a sibling temp file, is fsynced, and is then renamed over
  44. the target — so a concurrent reader sees the whole OLD file or the whole NEW
  45. one, never a half of either. That matters more than it sounds: the first
  46. caller is the session store, and a truncated session file does not read as
  47. an error, it reads as a logged-out user.
  48. `mode` defaults to 0600 — a file this program wrote, for this program. Pass
  49. one to loosen it; nothing here consults the umask on your behalf.
  50. `contents` is a String or a Bytes (ticket #88): a Bytes is written raw,
  51. byte for byte — an uploaded image stays an image. *\
  52. writeFile(
  53. String path
  54. contents \\ a String or a Bytes
  55. Number mode
  56. ) {
  57. if (hlTypeName(contents) == 'Bytes') {
  58. return __native("fs.write_file_hex", path, contents.hex(), mode)
  59. }
  60. return __native("fs.write_file", path, contents, mode)
  61. }
  62. \* Append to a file — the log's shape, not the session store's (2026-09-27, a
  63. creator ask after routger's two). Opened O_APPEND, never truncated: the file
  64. is created (at `mode`, default 0600, same as writeFile) if it is not there
  65. yet, and every call after that lands its bytes after whatever is already on
  66. disk. `writeFile`'s atomicity is a RENAME over a temp file — there is no
  67. "old" or "new" whole file to swap here, since an append MUTATES the one
  68. that is already there — but O_APPEND carries its own, narrower guarantee:
  69. the kernel positions a single write() at the CURRENT end of file and
  70. performs it as one operation, so two processes each appending one line
  71. never interleave their bytes mid-line, and a reader never sees a torn
  72. append. That is what "a single write of a line lands whole" buys — call it
  73. once per line (or per record) you want that guarantee for.
  74. `contents` is a String or a Bytes (ticket #88), same as writeFile: a Bytes
  75. is appended raw, byte for byte. *\
  76. appendFile(
  77. String path
  78. contents \\ a String or a Bytes
  79. Number mode
  80. ) {
  81. if (hlTypeName(contents) == 'Bytes') {
  82. return __native("fs.append_file_hex", path, contents.hex(), mode)
  83. }
  84. return __native("fs.append_file", path, contents, mode)
  85. }
  86. \* Make a directory and every missing parent. Already-there is SUCCESS: the
  87. caller wants the directory to exist, not to be the one who created it.
  88. `mode` defaults to 0700. *\
  89. mkDir(String path, Number mode) {
  90. return __native("fs.mk_dir", path, mode)
  91. }
  92. \* Delete a path. IDEMPOTENT: true when the path is gone on return, and a path
  93. that was already gone counts. A sweep must not fail because something else
  94. deleted the file first. *\
  95. remove(String path) {
  96. return __native("fs.remove", path)
  97. }

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