mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-29 16:07:40 +01:00
bookmark syncing
This commit is contained in:
parent
fa990d16b2
commit
ab4d72ed1a
6 changed files with 215 additions and 3 deletions
46
src/backend/accounts/bookmarks.ts
Normal file
46
src/backend/accounts/bookmarks.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { ofetch } from "ofetch";
|
||||
|
||||
import { getAuthHeaders } from "@/backend/accounts/auth";
|
||||
import { BookmarkResponse } from "@/backend/accounts/user";
|
||||
import { AccountWithToken } from "@/stores/auth";
|
||||
|
||||
export interface BookmarkInput {
|
||||
title: string;
|
||||
year: number;
|
||||
poster?: string;
|
||||
type: string;
|
||||
tmdbId: string;
|
||||
}
|
||||
|
||||
export async function addBookmark(
|
||||
url: string,
|
||||
account: AccountWithToken,
|
||||
input: BookmarkInput
|
||||
) {
|
||||
return ofetch<BookmarkResponse>(
|
||||
`/users/${account.userId}/bookmarks/${input.tmdbId}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: getAuthHeaders(account.token),
|
||||
baseURL: url,
|
||||
body: {
|
||||
meta: input,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function removeBookmark(
|
||||
url: string,
|
||||
account: AccountWithToken,
|
||||
id: string
|
||||
) {
|
||||
return ofetch<{ tmdbId: string }>(
|
||||
`/users/${account.userId}/bookmarks/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: getAuthHeaders(account.token),
|
||||
baseURL: url,
|
||||
}
|
||||
);
|
||||
}
|
|
@ -130,6 +130,7 @@ export function scrapePartsToProviderMetric(
|
|||
|
||||
export function useReportProviders() {
|
||||
const report = useCallback((items: ProviderMetric[]) => {
|
||||
if (items.length === 0) return;
|
||||
reportProviders(items);
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import "core-js/stable";
|
||||
import "./stores/__old/imports";
|
||||
import "@/setup/ga";
|
||||
import "@/setup/index.css";
|
||||
|
||||
import React, { Suspense } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
@ -13,13 +17,11 @@ import { MigrationPart } from "@/pages/parts/migrations/MigrationPart";
|
|||
import App from "@/setup/App";
|
||||
import { conf } from "@/setup/config";
|
||||
import i18n from "@/setup/i18n";
|
||||
import "@/setup/ga";
|
||||
import "@/setup/index.css";
|
||||
import { BookmarkSyncer } from "@/stores/bookmarks/BookmarkSyncer";
|
||||
import { useLanguageStore } from "@/stores/language";
|
||||
import { useThemeStore } from "@/stores/theme";
|
||||
|
||||
import { initializeChromecast } from "./setup/chromecast";
|
||||
import "./stores/__old/imports";
|
||||
import { initializeOldStores } from "./stores/__old/migrations";
|
||||
|
||||
// initialize
|
||||
|
@ -77,6 +79,7 @@ ReactDOM.render(
|
|||
<HelmetProvider>
|
||||
<Suspense fallback={<LoadingScreen type="lazy" />}>
|
||||
<ThemeProvider>
|
||||
<BookmarkSyncer />
|
||||
<TheRouter>
|
||||
<MigrationRunner />
|
||||
</TheRouter>
|
||||
|
|
78
src/stores/bookmarks/BookmarkSyncer.tsx
Normal file
78
src/stores/bookmarks/BookmarkSyncer.tsx
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { useEffect } from "react";
|
||||
|
||||
import { addBookmark, removeBookmark } from "@/backend/accounts/bookmarks";
|
||||
import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
|
||||
import { AccountWithToken, useAuthStore } from "@/stores/auth";
|
||||
import { BookmarkUpdateItem, useBookmarkStore } from "@/stores/bookmarks";
|
||||
|
||||
const syncIntervalMs = 5 * 1000;
|
||||
|
||||
async function syncBookmarks(
|
||||
items: BookmarkUpdateItem[],
|
||||
finish: (id: string) => void,
|
||||
url: string,
|
||||
account: AccountWithToken | null
|
||||
) {
|
||||
for (const item of items) {
|
||||
// complete it beforehand so it doesn't get handled while in progress
|
||||
finish(item.id);
|
||||
|
||||
if (!account) return; // not logged in, dont sync to server
|
||||
|
||||
try {
|
||||
if (item.action === "delete") {
|
||||
await removeBookmark(url, account, item.tmdbId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.action === "add") {
|
||||
await addBookmark(url, account, {
|
||||
poster: item.poster,
|
||||
title: item.title ?? "",
|
||||
tmdbId: item.tmdbId,
|
||||
type: item.type ?? "",
|
||||
year: item.year ?? NaN,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`Failed to sync bookmark: ${item.tmdbId} - ${item.action}`,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function BookmarkSyncer() {
|
||||
const clearUpdateQueue = useBookmarkStore((s) => s.clearUpdateQueue);
|
||||
const removeUpdateItem = useBookmarkStore((s) => s.removeUpdateItem);
|
||||
const url = useBackendUrl();
|
||||
|
||||
// when booting for the first time, clear update queue.
|
||||
// we dont want to process persisted update items
|
||||
useEffect(() => {
|
||||
clearUpdateQueue();
|
||||
}, [clearUpdateQueue]);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
(async () => {
|
||||
const state = useBookmarkStore.getState();
|
||||
const user = useAuthStore.getState();
|
||||
await syncBookmarks(
|
||||
state.updateQueue,
|
||||
removeUpdateItem,
|
||||
url,
|
||||
user.account
|
||||
);
|
||||
})();
|
||||
}, syncIntervalMs);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [removeUpdateItem, url]);
|
||||
|
||||
return null;
|
||||
}
|
|
@ -12,25 +12,59 @@ export interface BookmarkMediaItem {
|
|||
updatedAt: number;
|
||||
}
|
||||
|
||||
export interface BookmarkUpdateItem {
|
||||
tmdbId: string;
|
||||
title?: string;
|
||||
year?: number;
|
||||
id: string;
|
||||
poster?: string;
|
||||
type?: "show" | "movie";
|
||||
action: "delete" | "add";
|
||||
}
|
||||
|
||||
export interface BookmarkStore {
|
||||
bookmarks: Record<string, BookmarkMediaItem>;
|
||||
updateQueue: BookmarkUpdateItem[];
|
||||
addBookmark(meta: PlayerMeta): void;
|
||||
removeBookmark(id: string): void;
|
||||
replaceBookmarks(items: Record<string, BookmarkMediaItem>): void;
|
||||
clear(): void;
|
||||
clearUpdateQueue(): void;
|
||||
removeUpdateItem(id: string): void;
|
||||
}
|
||||
|
||||
let updateId = 0;
|
||||
|
||||
export const useBookmarkStore = create(
|
||||
persist(
|
||||
immer<BookmarkStore>((set) => ({
|
||||
bookmarks: {},
|
||||
updateQueue: [],
|
||||
removeBookmark(id) {
|
||||
set((s) => {
|
||||
updateId += 1;
|
||||
s.updateQueue.push({
|
||||
id: updateId.toString(),
|
||||
action: "delete",
|
||||
tmdbId: id,
|
||||
});
|
||||
|
||||
delete s.bookmarks[id];
|
||||
});
|
||||
},
|
||||
addBookmark(meta) {
|
||||
set((s) => {
|
||||
updateId += 1;
|
||||
s.updateQueue.push({
|
||||
id: updateId.toString(),
|
||||
action: "add",
|
||||
tmdbId: meta.tmdbId,
|
||||
type: meta.type,
|
||||
title: meta.title,
|
||||
year: meta.releaseYear,
|
||||
poster: meta.poster,
|
||||
});
|
||||
|
||||
s.bookmarks[meta.tmdbId] = {
|
||||
type: meta.type,
|
||||
title: meta.title,
|
||||
|
@ -48,6 +82,16 @@ export const useBookmarkStore = create(
|
|||
clear() {
|
||||
this.replaceBookmarks({});
|
||||
},
|
||||
clearUpdateQueue() {
|
||||
set((s) => {
|
||||
s.updateQueue = [];
|
||||
});
|
||||
},
|
||||
removeUpdateItem(id: string) {
|
||||
set((s) => {
|
||||
s.updateQueue = [...s.updateQueue.filter((v) => v.id !== id)];
|
||||
});
|
||||
},
|
||||
})),
|
||||
{
|
||||
name: "__MW::bookmarks",
|
||||
|
|
|
@ -35,6 +35,19 @@ export interface ProgressMediaItem {
|
|||
episodes: Record<string, ProgressEpisodeItem>;
|
||||
}
|
||||
|
||||
export interface ProgressUpdateItem {
|
||||
title?: string;
|
||||
year?: number;
|
||||
poster?: string;
|
||||
type?: "show" | "movie";
|
||||
progress?: ProgressItem;
|
||||
tmdbId: string;
|
||||
id: string;
|
||||
episodeId?: string;
|
||||
seasonId?: string;
|
||||
action: "upsert" | "delete";
|
||||
}
|
||||
|
||||
export interface UpdateItemOptions {
|
||||
meta: PlayerMeta;
|
||||
progress: ProgressItem;
|
||||
|
@ -42,18 +55,29 @@ export interface UpdateItemOptions {
|
|||
|
||||
export interface ProgressStore {
|
||||
items: Record<string, ProgressMediaItem>;
|
||||
updateQueue: ProgressUpdateItem[];
|
||||
updateItem(ops: UpdateItemOptions): void;
|
||||
removeItem(id: string): void;
|
||||
replaceItems(items: Record<string, ProgressMediaItem>): void;
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
let updateId = 0;
|
||||
|
||||
export const useProgressStore = create(
|
||||
persist(
|
||||
immer<ProgressStore>((set) => ({
|
||||
items: {},
|
||||
updateQueue: [],
|
||||
removeItem(id) {
|
||||
set((s) => {
|
||||
updateId += 1;
|
||||
s.updateQueue.push({
|
||||
id: updateId.toString(),
|
||||
action: "delete",
|
||||
tmdbId: id,
|
||||
});
|
||||
|
||||
delete s.items[id];
|
||||
});
|
||||
},
|
||||
|
@ -64,6 +88,22 @@ export const useProgressStore = create(
|
|||
},
|
||||
updateItem({ meta, progress }) {
|
||||
set((s) => {
|
||||
// add to updateQueue
|
||||
updateId += 1;
|
||||
s.updateQueue.push({
|
||||
tmdbId: meta.tmdbId,
|
||||
title: meta.title,
|
||||
year: meta.releaseYear,
|
||||
poster: meta.poster,
|
||||
type: meta.type,
|
||||
progress: { ...progress },
|
||||
id: updateId.toString(),
|
||||
episodeId: meta.episode?.tmdbId,
|
||||
seasonId: meta.season?.tmdbId,
|
||||
action: "upsert",
|
||||
});
|
||||
|
||||
// add to progress store
|
||||
if (!s.items[meta.tmdbId])
|
||||
s.items[meta.tmdbId] = {
|
||||
type: meta.type,
|
||||
|
|
Loading…
Reference in a new issue