Skip to content

Commit d065e69

Browse files
committed
v5.10.2 Floatplane Cache HotFix
1 parent a2f730e commit d065e69

5 files changed

Lines changed: 84 additions & 25 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "floatplane-plex-downloader",
3-
"version": "5.10.1",
3+
"version": "5.10.2",
44
"private": true,
55
"type": "module",
66
"scripts": {

src/float.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ process.on("SIGTERM", process.exit);
102102
if (settings.floatplane.waitForNewVideos === true) {
103103
const waitLoop = async () => {
104104
await downloadNewVideos();
105-
setTimeout(waitLoop, 10 * 1000);
105+
setTimeout(waitLoop, 5 * 60 * 1000);
106106
console.log("[" + new Date().toLocaleTimeString() + "]" + " Checking for new videos in 5 minutes...");
107107
};
108108
waitLoop();

src/lib/Caches.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { JSONSafeValue } from "@inrixia/db";
2+
import { writeFile } from "fs/promises";
3+
import { readFileSync } from "fs";
4+
5+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6+
type FetchItem<T> = (id: string, options?: any) => Promise<T>;
7+
8+
export class ItemCache<T extends JSONSafeValue> {
9+
private cache: Record<string, { i: T; t: number }>;
10+
private cachePath: string;
11+
private fetchItem: FetchItem<T>;
12+
private expireAgeMs: number;
13+
14+
constructor(cachePath: string, fetchItem: FetchItem<T>, expireAgeMins: number = 24 * 60) {
15+
this.cachePath = cachePath;
16+
this.fetchItem = fetchItem;
17+
this.expireAgeMs = expireAgeMins * 60 * 1000;
18+
19+
try {
20+
this.cache = JSON.parse(readFileSync(this.cachePath).toString());
21+
} catch (err) {
22+
this.cache = {};
23+
}
24+
}
25+
26+
private async writeOut() {
27+
try {
28+
await writeFile(this.cachePath, JSON.stringify(this.cache));
29+
} catch (err) {
30+
return;
31+
}
32+
}
33+
34+
private async set(key: string, i: T) {
35+
this.cache[key] = { t: Date.now(), i };
36+
await this.writeOut();
37+
return i;
38+
}
39+
40+
private deepCopy(i: T): T {
41+
return JSON.parse(JSON.stringify(i));
42+
}
43+
44+
public async get(id: string, options?: unknown, noCache: boolean = false): Promise<T> {
45+
const key = options !== undefined ? JSON.stringify([id, options]) : id;
46+
if (noCache) {
47+
delete this.cache[key];
48+
return this.get(id, options);
49+
}
50+
const cacheItem = this.cache[key];
51+
if (cacheItem !== undefined) {
52+
// Remove expired entries older than expireAge
53+
if (Date.now() - cacheItem.t > this.expireAgeMs) {
54+
delete this.cache[key];
55+
return this.get(id, options);
56+
}
57+
return this.deepCopy(cacheItem.i);
58+
}
59+
return this.set(key, await this.fetchItem(id, options));
60+
}
61+
}

src/lib/Subscription.ts

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import chalk from "chalk";
44
import { rm } from "fs/promises";
55

66
import type { ChannelOptions, SubscriptionSettings } from "./types.js";
7-
import type { ContentPost, VideoContent } from "floatplane/content";
7+
import type { ContentPost } from "floatplane/content";
88
import type { BlogPost } from "floatplane/creator";
99

1010
import { Video } from "./Video.js";
1111

1212
import { settings } from "./helpers.js";
13+
import { ItemCache } from "./Caches.js";
1314

1415
const removeRepeatedSentences = (postTitle: string, attachmentTitle: string) => {
1516
const separators = /(?:\s+|^)((?:[^.,;:!?-]+[\s]*[.,;:!?-]+)+)(?:\s+|$)/g;
@@ -24,16 +25,29 @@ const removeRepeatedSentences = (postTitle: string, attachmentTitle: string) =>
2425
return `${postTitle.trim()} - ${uniqueAttachmentTitleSentences.join("").trim()}`.trim().replace(/[\s]*[.,;:!?-]+[\s]*$/, "");
2526
};
2627

27-
const t24Hrs = 24 * 60 * 60 * 1000;
28-
28+
type BlogPosts = typeof fApi.creator.blogPostsIterable;
2929
export default class Subscription {
3030
public channels: SubscriptionSettings["channels"];
3131

3232
public readonly creatorId: string;
3333

34+
private static AttachmentsCache = new ItemCache("./db/attachmentCache.json", fApi.content.video, 24 * 60);
35+
36+
private static PostCache = new ItemCache("./db/postCache.json", fApi.creator.blogPosts, 60);
37+
private static async *PostIterable(creatorGUID: Parameters<BlogPosts>["0"], options: Parameters<BlogPosts>["1"]): ReturnType<BlogPosts> {
38+
let fetchAfter = 0;
39+
// First request should always not hit cache incase looking for new videos
40+
let blogPosts = await Subscription.PostCache.get(creatorGUID, { ...options, fetchAfter }, true);
41+
while (blogPosts.length > 0) {
42+
yield* blogPosts;
43+
fetchAfter += 20;
44+
// After that use the cached data
45+
blogPosts = await Subscription.PostCache.get(creatorGUID, { ...options, fetchAfter });
46+
}
47+
}
48+
3449
constructor(subscription: SubscriptionSettings) {
3550
this.creatorId = subscription.creatorId;
36-
3751
this.channels = subscription.channels;
3852
}
3953

@@ -67,22 +81,6 @@ export default class Subscription {
6781

6882
private static getIgnoreBeforeTimestamp = (channel: ChannelOptions) => Date.now() - (channel.daysToKeepVideos ?? 0) * 24 * 60 * 60 * 1000;
6983

70-
private static attachmentCache = new Map<string, { t: number; attachment: VideoContent }>();
71-
private static fetchAttachment = async (attachmentId: string): Promise<VideoContent> => {
72-
if (Subscription.attachmentCache.has(attachmentId)) {
73-
const { attachment, t } = Subscription.attachmentCache.get(attachmentId)!;
74-
// Remove expired entries older than 24hrs
75-
if (Date.now() - t > t24Hrs) {
76-
Subscription.attachmentCache.delete(attachmentId);
77-
return Subscription.fetchAttachment(attachmentId);
78-
}
79-
return attachment;
80-
}
81-
const attachment = await fApi.content.video(attachmentId);
82-
Subscription.attachmentCache.set(attachmentId, { t: Date.now(), attachment });
83-
return attachment;
84-
};
85-
8684
private async *matchChannel(blogPost: BlogPost): AsyncGenerator<Video> {
8785
if (blogPost.videoAttachments === undefined) return;
8886
let dateOffset = 0;
@@ -91,7 +89,7 @@ export default class Subscription {
9189
const post = { ...blogPost };
9290
if (blogPost.videoAttachments.length > 1) {
9391
dateOffset++;
94-
const { title: attachmentTitle } = await Subscription.fetchAttachment(attachment);
92+
const { title: attachmentTitle } = await Subscription.AttachmentsCache.get(attachment);
9593
post.title = removeRepeatedSentences(post.title, attachmentTitle);
9694
}
9795

@@ -168,7 +166,7 @@ export default class Subscription {
168166
public async *fetchNewVideos(): AsyncGenerator<Video> {
169167
if (settings.floatplane.videosToSearch === 0) return;
170168
let videosSearched = 0;
171-
for await (const blogPost of fApi.creator.blogPostsIterable(this.creatorId, { hasVideo: true })) {
169+
for await (const blogPost of Subscription.PostIterable(this.creatorId, { hasVideo: true })) {
172170
for await (const video of this.matchChannel(blogPost)) {
173171
if ((await video.getState()) !== Video.State.Muxed) yield video;
174172
}

src/lib/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import "dotenv/config";
1414
import json5 from "json5";
1515
const { parse } = json5;
1616

17-
export const DownloaderVersion = "5.10.1";
17+
export const DownloaderVersion = "5.10.2";
1818

1919
import type { PartialArgs, Settings } from "./types.js";
2020

0 commit comments

Comments
 (0)