diff options
Diffstat (limited to 'packages/meshbay-hub')
4 files changed, 59 insertions, 11 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/playlist-menu.js b/packages/meshbay-hub/src/meshbay_hub/static/playlist-menu.js index 70c105d..64d1c62 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/playlist-menu.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/playlist-menu.js @@ -203,7 +203,7 @@ function PlaylistMenuButton({ userId, lists, reload, onPlayQueue, onSync, cached // the lie this whole report exists to stop. const big = (r && r.tooLarge) || []; say(big.length - ? t('playlists.err_too_large', { name: big[0], n: 1000 }) + ? t('playlists.err_too_large', { name: big[0], n: 500 }) : (r && r.ok ? `${t('playlists.synced')} (${r.pushed || 0}↑ ${r.pulled || 0}↓)` : (why === 'no_key' diff --git a/packages/meshbay-hub/src/meshbay_hub/static/playlists.js b/packages/meshbay-hub/src/meshbay_hub/static/playlists.js index af359f3..bac9b3d 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/playlists.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/playlists.js @@ -41,9 +41,14 @@ import { * * 62 KB leaves room for the frame around the bytes — the four-byte length * prefix and the msgpack map that carries the kind, the revision and a request - * id. Measured, a thousand tracks seal to 53 276 bytes and each further track - * costs about 50, so the ceiling is near **1200 tracks in one playlist** - * (`test_playlist_store.py`, which fails if that stops being true). + * id. + * + * How many tracks that is depends entirely on how much the metadata repeats, + * because repetition is all deflate has to work with. Measured both ways: a + * library where artists and albums recur costs ~50 bytes a track and tops out + * near **1200**; one with no repetition at all costs ~91 and tops out near + * **660**. The sentence shown to a reader says 500, which holds in both worlds + * — `test_playlist_store.py` fails if it stops holding in the harsher one. * * Checked *after* sealing rather than estimated before it: compression makes * the length of a playlist a poor guide to the length of its blob, and a guess diff --git a/packages/meshbay-hub/tests/harness/playlist_store_probe.py b/packages/meshbay-hub/tests/harness/playlist_store_probe.py index 83c00ad..6421426 100755 --- a/packages/meshbay-hub/tests/harness/playlist_store_probe.py +++ b/packages/meshbay-hub/tests/harness/playlist_store_probe.py @@ -395,7 +395,31 @@ function fakeNode() { bodyKind('measure'), USER, key); sizes[n] = sealed.length; } - steps.push({ step: 'what a playlist costs sealed', sizes }); + + // The same, with nothing to compress. Fourteen words of vocabulary is a + // library that repeats itself; a real one does not, and deflate's whole + // saving here comes from repetition. This is the pessimistic bound, and the + // number a reader is told has to sit under *it*, not under the friendly one + // — the two differ by enough to matter. + const noise = (n) => [...crypto.getRandomValues(new Uint8Array(n))] + .map((b) => 'abcdefghijklmnopqrstuvwxyz '[b % 27]).join(''); + const diverseTrack = (i) => { + const artist = noise(18); + const album = noise(22); + const title = noise(26); + return { id: hex(16), g: 'g1', hv: 1, n: `${i % 20} - ${title}.flac`, + s: 30000000 + i, p: `${artist}/${album}`, t: title, a: artist, + b: album, d: 180 + (i % 300), tn: (i % 20) + 1 }; + }; + const diverse = {}; + for (const n of [100, 500, 1000]) { + const tracks = []; + for (let i = 0; i < n; i++) tracks.push(diverseTrack(i)); + const sealed = await seal({ v: 1, id: 'measure', rev: 1, device: 'aaa', tracks }, + bodyKind('measure'), USER, key); + diverse[n] = sealed.length; + } + steps.push({ step: 'what a playlist costs sealed', sizes, diverse }); // ── a playlist too large for one frame is named, not swallowed ──────── // diff --git a/packages/meshbay-hub/tests/test_playlist_store.py b/packages/meshbay-hub/tests/test_playlist_store.py index ce094c4..701de41 100644 --- a/packages/meshbay-hub/tests/test_playlist_store.py +++ b/packages/meshbay-hub/tests/test_playlist_store.py @@ -286,17 +286,36 @@ def test_what_a_playlist_costs_sealed_is_measured_not_quoted(steps): """The ceiling the UI promises comes from here, not from the design doc. §4 measured ~270 bytes a track *raw* and ~60 *sealed*; the raw figure was - then read for the sealed one and "about 225 tracks" written down, which is - five times too strict. So the number is measured where it is used. + read for the sealed one and "about 225 tracks" written down, which is five + times too strict. So it is measured where it is used. """ sizes = steps["what a playlist costs sealed"]["sizes"] per_track = (sizes["1000"] - sizes["100"]) / 900 assert 30 < per_track < 90, f"a track now costs {per_track:.0f} sealed bytes" - # The sentence shown to a reader is "playlists of about 1000 tracks". If - # compression regresses, or a field is added to what is stored, that - # sentence becomes false — and this is what says so. assert sizes["1000"] < 62 * 1024, ( - f"a thousand tracks no longer fit in one frame: {sizes['1000']} bytes") + f"a thousand ordinary tracks no longer fit in one frame: {sizes['1000']}") + + +def test_the_promised_count_holds_for_a_library_that_never_repeats(steps): + """The number in the message has to survive the worst compression, not the + friendly one. + + Everything deflate saves here comes from repetition — the same artist, the + same album, the same folder, over and over. A library that never repeats + costs nearly twice as much a track, and the two ceilings are 1200 and 660. + A reader told to split at 1000 would hit the wall again at 660; the message + says 500, and this is what keeps it true. + """ + diverse = steps["what a playlist costs sealed"]["diverse"] + per_track = (diverse["1000"] - diverse["100"]) / 900 + assert per_track > ( + (steps["what a playlist costs sealed"]["sizes"]["1000"] + - steps["what a playlist costs sealed"]["sizes"]["100"]) / 900), ( + "the harsh fixture compresses as well as the ordinary one — " + "it is not measuring the worst case any more") + assert diverse["500"] < 62 * 1024, ( + f"500 tracks of wholly unrepeating metadata no longer fit: " + f"{diverse['500']} bytes, at {per_track:.0f} a track") def test_a_playlist_too_large_for_a_frame_is_named(steps): |