From 70255af4588fd7473c9bfe74151a04a2b02b0b1b Mon Sep 17 00:00:00 2001 From: Innei Date: Thu, 12 Jun 2025 21:20:46 +0800 Subject: [PATCH] refactor(dock): implement PollingManager for unread count updates, fixed #3891 - Removed redundant polling logic from DockService and replaced it with a PollingManager class to handle polling operations more efficiently. - Updated the setting-sync provider to utilize the new polling mechanism for managing dock badge updates. - Cleaned up unused setDockBadge method from SettingService. Signed-off-by: Innei --- .../layer/main/src/ipc/services/dock.ts | 77 +++++++++++++++---- .../layer/main/src/ipc/services/setting.ts | 6 -- apps/desktop/layer/main/src/window.ts | 18 ----- .../renderer/src/providers/setting-sync.tsx | 10 +-- 4 files changed, 66 insertions(+), 45 deletions(-) diff --git a/apps/desktop/layer/main/src/ipc/services/dock.ts b/apps/desktop/layer/main/src/ipc/services/dock.ts index c24f0453c..05e764eb5 100644 --- a/apps/desktop/layer/main/src/ipc/services/dock.ts +++ b/apps/desktop/layer/main/src/ipc/services/dock.ts @@ -1,36 +1,78 @@ import { UNREAD_BACKGROUND_POLLING_INTERVAL } from "../../constants/app" import { apiClient } from "../../lib/api-client" import { setDockCount } from "../../lib/dock" -import { sleep } from "../../lib/utils" +import type { IpcContext } from "../base" import { IpcMethod, IpcService } from "../base" -const pollingMap = { - unread: false, +class PollingManager { + private abortController: AbortController | null = null + private isPolling = false + + async startPolling(pollingFn: () => Promise, interval: number): Promise { + if (this.isPolling) { + return // Already polling, prevent duplicate instances + } + + this.isPolling = true + this.abortController = new AbortController() + + try { + while (!this.abortController.signal.aborted) { + await pollingFn() + + // Use AbortSignal with sleep for proper cancellation + await this.sleepWithAbortSignal(interval, this.abortController.signal) + } + } catch (error) { + if (error instanceof Error && error.name !== "AbortError") { + console.error("Polling error:", error) + } + } finally { + this.isPolling = false + this.abortController = null + } + } + + stopPolling(): void { + if (this.abortController) { + this.abortController.abort() + } + } + + get active(): boolean { + return this.isPolling + } + + private async sleepWithAbortSignal(ms: number, signal: AbortSignal): Promise { + return new Promise((resolve, reject) => { + const timeoutId = setTimeout(resolve, ms) + + signal.addEventListener("abort", () => { + clearTimeout(timeoutId) + reject(new DOMException("Aborted", "AbortError")) + }) + }) + } } export class DockService extends IpcService { + private unreadPollingManager = new PollingManager() + constructor() { super("dock") } @IpcMethod() async pollingUpdateUnreadCount(): Promise { - if (pollingMap.unread) { - return - } - - pollingMap.unread = true - while (pollingMap.unread) { - await sleep(UNREAD_BACKGROUND_POLLING_INTERVAL) - if (pollingMap.unread) { - await this.updateUnreadCount() - } - } + await this.unreadPollingManager.startPolling( + () => this.updateUnreadCount(), + UNREAD_BACKGROUND_POLLING_INTERVAL, + ) } @IpcMethod() async cancelPollingUpdateUnreadCount(): Promise { - pollingMap.unread = false + this.unreadPollingManager.stopPolling() } @IpcMethod() @@ -38,4 +80,9 @@ export class DockService extends IpcService { const res = await apiClient.reads["total-count"].$get() setDockCount(res.data.count) } + + @IpcMethod() + setDockBadge(_context: IpcContext, count: number): void { + setDockCount(count) + } } diff --git a/apps/desktop/layer/main/src/ipc/services/setting.ts b/apps/desktop/layer/main/src/ipc/services/setting.ts index e28632580..e7f1b4628 100644 --- a/apps/desktop/layer/main/src/ipc/services/setting.ts +++ b/apps/desktop/layer/main/src/ipc/services/setting.ts @@ -2,7 +2,6 @@ import { createRequire } from "node:module" import { app, nativeTheme } from "electron" -import { setDockCount } from "../../lib/dock" import { setProxyConfig, updateProxy } from "../../lib/proxy" import { store } from "../../lib/store" import { getTrayConfig, setTrayConfig } from "../../lib/tray" @@ -65,11 +64,6 @@ export class SettingService extends IpcService { setTrayConfig(minimize) } - @IpcMethod() - setDockBadge(_context: IpcContext, count: number): void { - setDockCount(count) - } - @IpcMethod() getProxyConfig(_context: IpcContext) { const proxy = store.get("proxy") diff --git a/apps/desktop/layer/main/src/window.ts b/apps/desktop/layer/main/src/window.ts index e8a988df7..07a4a6041 100644 --- a/apps/desktop/layer/main/src/window.ts +++ b/apps/desktop/layer/main/src/window.ts @@ -12,7 +12,6 @@ import type { Event } from "electron/main" import { START_IN_TRAY_ARGS } from "./constants/app" import { isMacOS, isWindows, isWindows11 } from "./env" import { filePathToAppUrl, getIconPath } from "./helper" -import { services } from "./ipc" import { t } from "./lib/i18n" import { store } from "./lib/store" import { getTrayConfig } from "./lib/tray" @@ -312,23 +311,6 @@ export const createMainWindow = () => { } }) - window.on("show", () => { - services.dock.pollingUpdateUnreadCount() - - const caller = callWindowExpose(window) - - caller.onWindowShow() - }) - - window.on("hide", async () => { - const caller = callWindowExpose(window) - const settings = await caller.getUISettings() - - if (settings?.showDockBadge) { - services.dock.pollingUpdateUnreadCount() - } - }) - return window } diff --git a/apps/desktop/layer/renderer/src/providers/setting-sync.tsx b/apps/desktop/layer/renderer/src/providers/setting-sync.tsx index 5311c962c..c50f855a4 100644 --- a/apps/desktop/layer/renderer/src/providers/setting-sync.tsx +++ b/apps/desktop/layer/renderer/src/providers/setting-sync.tsx @@ -1,5 +1,4 @@ import { isMobile } from "@follow/components/hooks/useMobile.js" -import { unreadActions } from "@follow/store/unread/store" import i18next from "i18next" import { useEffect, useInsertionEffect, useLayoutEffect } from "react" @@ -38,12 +37,11 @@ const useUISettingSync = () => { useEffect(() => { if (setting.showDockBadge) { - return unreadActions.subscribeUnreadCount( - (count) => ipcServices?.setting.setDockBadge(count), - true, - ) + ipcServices?.dock.pollingUpdateUnreadCount() } else { - ipcServices?.setting.setDockBadge(0) + ipcServices?.dock.cancelPollingUpdateUnreadCount().then(() => { + ipcServices?.dock.setDockBadge(0) + }) } return }, [setting.showDockBadge])