aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
Diffstat (limited to 'src/features')
-rw-r--r--src/features/messages.js42
1 files changed, 27 insertions, 15 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index ef4819d..8f6eb2d 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -9609,7 +9609,7 @@ export function messagesFeature(ctx) {
const key = mediaIdxKey(feedId);
let r = mediaIdx.get(key);
if (r) return r;
- r = { key, images: new Map(), videos: new Map(), checked: new Set(), until: 0, end: false, loaded: false };
+ r = { key, images: new Map(), videos: new Map(), checked: new Set(), until: 0, end: false, empty: 0, loaded: false };
mediaIdx.set(key, r);
loadMediaIndex(key).then(async (rec) => {
if (rec) {
@@ -9617,7 +9617,9 @@ export function messagesFeature(ctx) {
if (rec.until && (!r.until || rec.until < r.until)) r.until = rec.until;
// the bottom of a feed is looked for again after a day (a relay
// added, a follow back-filled)
- if (rec.end && Date.now() - rec.end < 86_400_000) r.end = r.end || rec.end;
+ // (only an end confirmed by repeated empty answers: one empty
+ // answer was a timeout as often as the bottom, and held for a day)
+ if (rec.end && rec.endSure && Date.now() - rec.end < 86_400_000) r.end = r.end || rec.end;
}
// kept frames first, so the first paint of the grid is pictures
await warmVideoThumbs([...r.videos.values()]);
@@ -9630,7 +9632,7 @@ export function messagesFeature(ctx) {
clearTimeout(r.saveT);
r.saveT = setTimeout(() => {
const list = (m) => [...m.values()].sort((a, b) => b.created_at - a.created_at);
- saveMediaIndex(r.key, { images: list(r.images), videos: list(r.videos), until: r.until, end: r.end });
+ saveMediaIndex(r.key, { images: list(r.images), videos: list(r.videos), until: r.until, end: r.end, endSure: !!r.end });
}, 1500);
}
// file the posts that carry media; each post is looked at once
@@ -9657,18 +9659,28 @@ export function messagesFeature(ctx) {
if (!r.loaded || r.end || mediaCrawling) return mediaCrawling || Promise.resolve();
mediaCrawling = (async () => {
scheduleRepaint();
- const from = [r.until, c.notes.at(-1)?.created_at].filter(Boolean);
- const until = (from.length ? Math.min(...from) : Math.floor(Date.now() / 1000) + 1) - 1;
- let oldest = Infinity, saw = 0;
- const sink = (ev) => {
- saw++;
- if (ev.created_at < oldest) oldest = ev.created_at;
- noteForSpam(ev);
- indexMedia(r, [ev]);
- };
- try { await feedPass({ until, limit: 100 }, { sink }, c); } catch {}
- if (!saw) r.end = Date.now();
- else r.until = Math.min(oldest, until);
+ // Clips are rare (one post in a hundred, on a busy relay), so a crawl
+ // keeps reading pages until it has found a few, or read several.
+ const kind = mediaMode === 'videos' ? 'videos' : 'images';
+ const had = r[kind].size;
+ for (let pass = 0; pass < 5 && r[kind].size - had < 6; pass++) {
+ const from = [r.until, c.notes.at(-1)?.created_at].filter(Boolean);
+ const until = (from.length ? Math.min(...from) : Math.floor(Date.now() / 1000) + 1) - 1;
+ let oldest = Infinity, saw = 0;
+ const sink = (ev) => {
+ saw++;
+ if (ev.created_at < oldest) oldest = ev.created_at;
+ noteForSpam(ev);
+ indexMedia(r, [ev]);
+ };
+ try { await feedPass({ until, limit: 100 }, { sink }, c); } catch {}
+ if (c.stopped) break;
+ if (saw) { r.empty = 0; r.until = Math.min(oldest, until); continue; }
+ // an empty answer is the bottom only when it repeats: a relay that
+ // timed out, or a socket the phone froze, answers empty too
+ if (++r.empty >= 3) { r.end = Date.now(); break; }
+ await new Promise((ok) => setTimeout(ok, 1500 * r.empty));
+ }
saveMediaIdx(r);
})().finally(() => { mediaCrawling = null; scheduleRepaint(); });
return mediaCrawling;