Skip to content

Commit

Permalink
chore: implement staggered polling and client-side caching to reduce …
Browse files Browse the repository at this point in the history
…backend load + remove dead code
  • Loading branch information
louis030195 committed Feb 19, 2025
1 parent a373be7 commit 2333a23
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 101 deletions.
34 changes: 30 additions & 4 deletions screenpipe-app-tauri/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,37 @@ export default function Home() {
const { setIsOpen: setSettingsOpen } = useSettingsDialog();
const isProcessingRef = React.useRef(false);

// staggered polling with exponential backoff while maintaining responsiveness
// while reducing backend load
useEffect(() => {
const interval = setInterval(() => {
loadUser(settings.user?.token!);
}, 1000);
return () => clearInterval(interval);
let retries = 0;
const maxRetries = 5;
const minDelay = 1000; // 1s minimum
const maxDelay = 30000; // 30s maximum
let timeoutId: NodeJS.Timeout;

const loadUserWithBackoff = async () => {
if (!settings.user?.token) return;

try {
await loadUser(settings.user.token, false);
retries = 0;
} catch (err) {
console.error("failed to load user:", err);
retries = Math.min(retries + 1, maxRetries);
}

// calculate next delay with exponential backoff
const delay = Math.min(minDelay * Math.pow(2, retries), maxDelay);
timeoutId = setTimeout(loadUserWithBackoff, delay);
};

loadUserWithBackoff();

// cleanup
return () => {
if (timeoutId) clearTimeout(timeoutId);
};
}, [settings]);

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion screenpipe-app-tauri/components/onboarding/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const OnboardingLogin: React.FC<OnboardingLoginProps> = ({
const apiKey = new URL(url).searchParams.get("api_key");
if (apiKey) {
updateSettings({ user: { token: apiKey } });
loadUser(apiKey);
await loadUser(apiKey, true);
toast({
title: "logged in!",
description: "your api key has been set",
Expand Down
4 changes: 2 additions & 2 deletions screenpipe-app-tauri/components/settings/account-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function AccountSection() {
const apiKey = new URL(url).searchParams.get("api_key");
if (apiKey) {
updateSettings({ user: { token: apiKey } });
await loadUser(apiKey);
await loadUser(apiKey, true);

toast({
title: "logged in!",
Expand All @@ -124,7 +124,7 @@ export function AccountSection() {
stripe_connected: true,
},
});
loadUser(settings.user.token!);
loadUser(settings.user.token!, true);
}
toast({
title: "stripe connected!",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function CreditPurchaseDialog({
`${url}?client_reference_id=${settings.user?.id}&metadata[user_id]=${settings.user?.id}`
);
setTimeout(async () => {
await loadUser(settings.user?.token!);
await loadUser(settings.user?.token!, true);
onCreditsUpdated?.();
setShowRefreshHint(true);
setIsLoading(false);
Expand Down
58 changes: 56 additions & 2 deletions screenpipe-app-tauri/lib/api/store/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { invoke } from "@tauri-apps/api/core";
import localforage from "localforage";

export interface PipeStorePlugin {
id: string;
Expand Down Expand Up @@ -60,9 +61,15 @@ export interface CheckUpdateResponse {
latest_file_size: number;
}

interface CacheEntry<T> {
data: T;
timestamp: number;
}

export class PipeApi {
private baseUrl: string;
private authToken: string;
private cacheTTL = 60000; // 60 seconds cache

private constructor(authToken: string) {
this.baseUrl = "https://screenpi.pe";
Expand All @@ -87,6 +94,34 @@ export class PipeApi {
}
}

private async getCached<T>(key: string): Promise<T | null> {
try {
const entry = await localforage.getItem<CacheEntry<T>>(`pipe_api_${key}`);
if (!entry) return null;

if (Date.now() - entry.timestamp > this.cacheTTL) {
await localforage.removeItem(`pipe_api_${key}`);
return null;
}

return entry.data;
} catch (error) {
console.warn("cache read error:", error);
return null;
}
}

private async setCache<T>(key: string, data: T) {
try {
await localforage.setItem(`pipe_api_${key}`, {
data,
timestamp: Date.now(),
});
} catch (error) {
console.warn("cache write error:", error);
}
}

async getUserPurchaseHistory(): Promise<PurchaseHistoryResponse> {
try {
const response = await fetch(
Expand All @@ -102,15 +137,18 @@ export class PipeApi {
throw new Error(`failed to fetch purchase history: ${error}`);
}

const data = (await response.json()) as PurchaseHistoryResponse;
return data;
return (await response.json()) as PurchaseHistoryResponse;
} catch (error) {
console.error("error getting purchase history:", error);
throw error;
}
}

async listStorePlugins(): Promise<PipeStorePlugin[]> {
const cacheKey = "store-plugins";
const cached = await this.getCached<PipeStorePlugin[]>(cacheKey);
if (cached) return cached;

try {
const response = await fetch(`${this.baseUrl}/api/plugins/registry`, {
headers: {
Expand All @@ -122,6 +160,7 @@ export class PipeApi {
throw new Error(`failed to fetch plugins: ${error}`);
}
const data: PipeStorePlugin[] = await response.json();
await this.setCache(cacheKey, data);
return data;
} catch (error) {
console.error("error listing pipes:", error);
Expand Down Expand Up @@ -184,6 +223,10 @@ export class PipeApi {
pipeId: string,
version: string
): Promise<CheckUpdateResponse> {
const cacheKey = `update_${pipeId}_${version}`;
const cached = await this.getCached<CheckUpdateResponse>(cacheKey);
if (cached) return cached;

try {
const response = await fetch(`${this.baseUrl}/api/plugins/check-update`, {
method: "POST",
Expand All @@ -200,10 +243,21 @@ export class PipeApi {
}

const data = await response.json();
await this.setCache(cacheKey, data);
return data;
} catch (error) {
console.error("error checking for updates:", error);
throw error;
}
}

// method to force refresh cache if needed
async clearCache() {
const keys = await localforage.keys();
await Promise.all(
keys
.filter((key) => key.startsWith("pipe_api_"))
.map((key) => localforage.removeItem(key))
);
}
}
38 changes: 31 additions & 7 deletions screenpipe-app-tauri/lib/hooks/use-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import {
import { LazyStore, LazyStore as TauriStore } from "@tauri-apps/plugin-store";
import { localDataDir } from "@tauri-apps/api/path";
import { flattenObject, unflattenObject } from "../utils";
import { invoke } from "@tauri-apps/api/core";
import { useEffect } from "react";
import posthog from "posthog-js";
import localforage from "localforage";

export type VadSensitivity = "low" | "medium" | "high";

Expand Down Expand Up @@ -214,8 +214,8 @@ export function createDefaultSettingsObject(): Settings {
currentPlatform === "macos"
? "apple-native"
: currentPlatform === "windows"
? "windows-native"
: "tesseract";
? "windows-native"
: "tesseract";

defaultSettings.ocrEngine = ocrModel;
defaultSettings.fps = currentPlatform === "macos" ? 0.5 : 1;
Expand Down Expand Up @@ -327,15 +327,15 @@ export const store = createContextStore<StoreModel>(
{
storage: tauriStorage,
mergeStrategy: "mergeDeep",
},
),
}
)
);

export function useSettings() {
const settings = store.useStoreState((state) => state.settings);
const setSettings = store.useStoreActions((actions) => actions.setSettings);
const resetSettings = store.useStoreActions(
(actions) => actions.resetSettings,
(actions) => actions.resetSettings
);
const resetSetting = store.useStoreActions((actions) => actions.resetSetting);

Expand Down Expand Up @@ -371,8 +371,25 @@ export function useSettings() {
: `${homeDirPath}\\.screenpipe`;
};

const loadUser = async (token: string) => {
const loadUser = async (token: string, forceReload = false) => {
try {
// try to get from cache first (unless force reload)
const cacheKey = `user_data_${token}`;
if (!forceReload) {
const cached = await localforage.getItem<{
data: User;
timestamp: number;
}>(cacheKey);

// use cache if less than 30s old
if (cached && Date.now() - cached.timestamp < 30000) {
setSettings({
user: cached.data,
});
return;
}
}

const response = await fetch(`https://screenpi.pe/api/user`, {
method: "POST",
headers: {
Expand All @@ -397,11 +414,18 @@ export function useSettings() {
});
}

// cache the result
await localforage.setItem(cacheKey, {
data: userData,
timestamp: Date.now(),
});

setSettings({
user: userData,
});
} catch (err) {
console.error("failed to load user:", err);
throw err;
}
};

Expand Down
2 changes: 1 addition & 1 deletion screenpipe-app-tauri/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 2333a23

Please sign in to comment.