mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-25 11:20:07 +08:00
refactor(preferences): rename and enhance multiple preferences retrieval
- Renamed `Preference_GetMultiple` to `Preference_GetMultipleRaw` in IpcChannel for clarity. - Introduced `MultiPreferencesResultType` to better map requested keys to their values or undefined in preferenceTypes. - Updated `PreferenceService` to implement `getMultipleRaw` for synchronous access to multiple preferences. - Adjusted related components and services to utilize the new method for fetching multiple preferences. - Cleaned up imports and ensured consistent usage across the application.
This commit is contained in:
parent
819f6de64d
commit
8ea550d566
@ -319,7 +319,7 @@ export enum IpcChannel {
|
||||
// Data: Preference
|
||||
Preference_Get = 'preference:get',
|
||||
Preference_Set = 'preference:set',
|
||||
Preference_GetMultiple = 'preference:get-multiple',
|
||||
Preference_GetMultipleRaw = 'preference:get-multiple-raw',
|
||||
Preference_SetMultiple = 'preference:set-multiple',
|
||||
Preference_GetAll = 'preference:get-all',
|
||||
Preference_Subscribe = 'preference:subscribe',
|
||||
|
||||
@ -3,6 +3,13 @@ import type { PreferenceSchemas } from './preferenceSchemas'
|
||||
export type PreferenceDefaultScopeType = PreferenceSchemas['default']
|
||||
export type PreferenceKeyType = keyof PreferenceDefaultScopeType
|
||||
|
||||
/**
|
||||
* Result type for getMultipleRaw - maps requested keys to their values or undefined
|
||||
*/
|
||||
export type MultiPreferencesResultType<K extends PreferenceKeyType> = {
|
||||
[P in K]: PreferenceDefaultScopeType[P] | undefined
|
||||
}
|
||||
|
||||
export type PreferenceUpdateOptions = {
|
||||
optimistic: boolean
|
||||
}
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { dbService } from '@data/db/DbService'
|
||||
import { loggerService } from '@logger'
|
||||
import { DefaultPreferences } from '@shared/data/preference/preferenceSchemas'
|
||||
import type { PreferenceDefaultScopeType, PreferenceKeyType } from '@shared/data/preference/preferenceTypes'
|
||||
import type {
|
||||
MultiPreferencesResultType,
|
||||
PreferenceDefaultScopeType,
|
||||
PreferenceKeyType
|
||||
} from '@shared/data/preference/preferenceTypes'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { BrowserWindow, ipcMain } from 'electron'
|
||||
@ -118,8 +122,6 @@ class PreferenceNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
type MultiPreferencesResultType<K extends PreferenceKeyType> = { [P in K]: PreferenceDefaultScopeType[P] | undefined }
|
||||
|
||||
const DefaultScope = 'default'
|
||||
/**
|
||||
* PreferenceService manages preference data storage and synchronization across multiple windows
|
||||
@ -153,6 +155,7 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Get the singleton instance of PreferenceService
|
||||
* @returns The singleton PreferenceService instance
|
||||
*/
|
||||
public static getInstance(): PreferenceService {
|
||||
if (!PreferenceService.instance) {
|
||||
@ -164,6 +167,7 @@ export class PreferenceService {
|
||||
/**
|
||||
* Initialize preference cache from database
|
||||
* Should be called once at application startup
|
||||
* @returns Promise that resolves when initialization completes
|
||||
*/
|
||||
public async initialize(): Promise<void> {
|
||||
if (this.initialized) {
|
||||
@ -194,6 +198,8 @@ export class PreferenceService {
|
||||
/**
|
||||
* Get a single preference value from memory cache
|
||||
* Fast synchronous access - no database queries after initialization
|
||||
* @param key The preference key to retrieve
|
||||
* @returns The preference value with defaults applied
|
||||
*/
|
||||
public get<K extends PreferenceKeyType>(key: K): PreferenceDefaultScopeType[K] {
|
||||
if (!this.initialized) {
|
||||
@ -208,6 +214,9 @@ export class PreferenceService {
|
||||
* Set a single preference value
|
||||
* Updates both database and memory cache, then broadcasts changes to all listeners
|
||||
* Optimized to skip database writes and notifications when value hasn't changed
|
||||
* @param key The preference key to update
|
||||
* @param value The new value to set
|
||||
* @returns Promise that resolves when update completes
|
||||
*/
|
||||
public async set<K extends PreferenceKeyType>(key: K, value: PreferenceDefaultScopeType[K]): Promise<void> {
|
||||
try {
|
||||
@ -247,8 +256,10 @@ export class PreferenceService {
|
||||
/**
|
||||
* Get multiple preferences at once from memory cache
|
||||
* Fast synchronous access - no database queries
|
||||
* @param keys Array of preference keys to retrieve
|
||||
* @returns Object with preference values for requested keys
|
||||
*/
|
||||
public getMultiple<K extends PreferenceKeyType>(keys: K[]): MultiPreferencesResultType<K> {
|
||||
public getMultipleRaw<K extends PreferenceKeyType>(keys: K[]): MultiPreferencesResultType<K> {
|
||||
if (!this.initialized) {
|
||||
logger.warn('Preference cache not initialized, returning defaults for multiple keys')
|
||||
const output: MultiPreferencesResultType<K> = {} as MultiPreferencesResultType<K>
|
||||
@ -274,10 +285,38 @@ export class PreferenceService {
|
||||
return output
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple preferences with custom key mapping
|
||||
* @param keys Object mapping local names to preference keys
|
||||
* @returns Object with mapped preference values
|
||||
* @example
|
||||
* ```typescript
|
||||
* const { host, port } = preferenceService.getMultiple({
|
||||
* host: 'feature.csaas.host',
|
||||
* port: 'feature.csaas.port'
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
public getMultiple<T extends Record<string, PreferenceKeyType>>(
|
||||
keys: T
|
||||
): { [P in keyof T]: PreferenceDefaultScopeType[T[P]] } {
|
||||
const preferenceKeys = Object.values(keys) as PreferenceKeyType[]
|
||||
const values = this.getMultipleRaw(preferenceKeys)
|
||||
const result = {} as { [P in keyof T]: PreferenceDefaultScopeType[T[P]] }
|
||||
|
||||
for (const key in keys) {
|
||||
result[key] = values[keys[key]]!
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple preferences at once
|
||||
* Updates both database and memory cache in a transaction, then broadcasts changes
|
||||
* Optimized to skip unchanged values and reduce database operations
|
||||
* @param updates Object containing preference key-value pairs to update
|
||||
* @returns Promise that resolves when all updates complete
|
||||
*/
|
||||
public async setMultiple(updates: Partial<PreferenceDefaultScopeType>): Promise<void> {
|
||||
try {
|
||||
@ -345,6 +384,8 @@ export class PreferenceService {
|
||||
/**
|
||||
* Subscribe a window to preference changes
|
||||
* Window will receive notifications for specified keys
|
||||
* @param windowId The ID of the BrowserWindow to subscribe
|
||||
* @param keys Array of preference keys to subscribe to
|
||||
*/
|
||||
public subscribeForWindow(windowId: number, keys: string[]): void {
|
||||
if (!this.subscriptions.has(windowId)) {
|
||||
@ -359,6 +400,7 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Unsubscribe a window from preference changes
|
||||
* @param windowId The ID of the BrowserWindow to unsubscribe
|
||||
*/
|
||||
public unsubscribeForWindow(windowId: number): void {
|
||||
this.subscriptions.delete(windowId)
|
||||
@ -369,7 +411,9 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Subscribe to preference changes in main process
|
||||
* Returns unsubscribe function for cleanup
|
||||
* @param key The preference key to watch for changes
|
||||
* @param callback Function to call when the preference changes
|
||||
* @returns Unsubscribe function for cleanup
|
||||
*/
|
||||
public subscribeChange<K extends PreferenceKeyType>(
|
||||
key: K,
|
||||
@ -386,7 +430,9 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Subscribe to multiple preference changes in main process
|
||||
* Returns unsubscribe function for cleanup
|
||||
* @param keys Array of preference keys to watch for changes
|
||||
* @param callback Function to call when any of the preferences change
|
||||
* @returns Unsubscribe function for cleanup
|
||||
*/
|
||||
public subscribeMultipleChanges(
|
||||
keys: PreferenceKeyType[],
|
||||
@ -417,6 +463,7 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Get main process listener count for debugging
|
||||
* @returns Total number of change listeners
|
||||
*/
|
||||
public getChangeListenerCount(): number {
|
||||
return this.notifier.getTotalSubscriptionCount()
|
||||
@ -424,6 +471,8 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Get subscription count for a specific preference key
|
||||
* @param key The preference key to check
|
||||
* @returns Number of listeners subscribed to this key
|
||||
*/
|
||||
public getKeyListenerCount(key: PreferenceKeyType): number {
|
||||
return this.notifier.getKeySubscriptionCount(key)
|
||||
@ -431,6 +480,7 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Get all subscribed preference keys
|
||||
* @returns Array of preference keys that have active subscriptions
|
||||
*/
|
||||
public getSubscribedKeys(): string[] {
|
||||
return this.notifier.getSubscribedKeys()
|
||||
@ -438,6 +488,7 @@ export class PreferenceService {
|
||||
|
||||
/**
|
||||
* Get detailed subscription statistics for debugging
|
||||
* @returns Record mapping preference keys to their listener counts
|
||||
*/
|
||||
public getSubscriptionStats(): Record<string, number> {
|
||||
return this.notifier.getSubscriptionStats()
|
||||
@ -446,6 +497,10 @@ export class PreferenceService {
|
||||
/**
|
||||
* Unified notification method for both main and renderer processes
|
||||
* Broadcasts preference changes to main process listeners and subscribed renderer windows
|
||||
* @param key The preference key that changed
|
||||
* @param value The new value
|
||||
* @param oldValue The previous value
|
||||
* @returns Promise that resolves when all notifications are sent
|
||||
*/
|
||||
private async notifyChange(key: string, value: any, oldValue?: any): Promise<void> {
|
||||
// 1. Notify main process listeners
|
||||
@ -512,6 +567,7 @@ export class PreferenceService {
|
||||
/**
|
||||
* Get all preferences from memory cache
|
||||
* Returns complete preference object for bulk operations
|
||||
* @returns Complete preference object with all values
|
||||
*/
|
||||
public getAll(): PreferenceDefaultScopeType {
|
||||
if (!this.initialized) {
|
||||
@ -523,7 +579,8 @@ export class PreferenceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all current subscriptions (for debugging)
|
||||
* Get all current window subscriptions (for debugging)
|
||||
* @returns Map of window IDs to their subscribed preference keys
|
||||
*/
|
||||
public getSubscriptions(): Map<number, Set<string>> {
|
||||
return new Map(this.subscriptions)
|
||||
@ -548,6 +605,9 @@ export class PreferenceService {
|
||||
/**
|
||||
* Deep equality check for preference values
|
||||
* Handles primitives, arrays, and plain objects
|
||||
* @param a First value to compare
|
||||
* @param b Second value to compare
|
||||
* @returns True if values are deeply equal, false otherwise
|
||||
*/
|
||||
private isEqual(a: any, b: any): boolean {
|
||||
// Handle strict equality (primitives, same reference)
|
||||
@ -603,8 +663,8 @@ export class PreferenceService {
|
||||
}
|
||||
)
|
||||
|
||||
ipcMain.handle(IpcChannel.Preference_GetMultiple, (_, keys: PreferenceKeyType[]) => {
|
||||
return instance.getMultiple(keys)
|
||||
ipcMain.handle(IpcChannel.Preference_GetMultipleRaw, (_, keys: PreferenceKeyType[]) => {
|
||||
return instance.getMultipleRaw(keys)
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.Preference_SetMultiple, async (_, updates: Partial<PreferenceDefaultScopeType>) => {
|
||||
|
||||
@ -61,7 +61,7 @@ export class ApiServerService {
|
||||
* Get current API server configuration from preference service
|
||||
*/
|
||||
getCurrentConfig(): ApiServerConfig {
|
||||
const config = preferenceService.getMultiple([
|
||||
const config = preferenceService.getMultipleRaw([
|
||||
'feature.csaas.enabled',
|
||||
'feature.csaas.host',
|
||||
'feature.csaas.port',
|
||||
|
||||
@ -101,7 +101,7 @@ class ClaudeCodeService implements AgentServiceInterface {
|
||||
return aiStream
|
||||
}
|
||||
|
||||
const apiConfig = preferenceService.getMultiple([
|
||||
const apiConfig = preferenceService.getMultipleRaw([
|
||||
'feature.csaas.host',
|
||||
'feature.csaas.port',
|
||||
'feature.csaas.api_key'
|
||||
|
||||
@ -7,6 +7,7 @@ import type { LogLevel, LogSourceWithContext } from '@shared/config/logger'
|
||||
import type { FileChangeEvent, WebviewKeyEvent } from '@shared/config/types'
|
||||
import type { CacheSyncMessage } from '@shared/data/cache/cacheTypes'
|
||||
import type {
|
||||
MultiPreferencesResultType,
|
||||
PreferenceDefaultScopeType,
|
||||
PreferenceKeyType,
|
||||
SelectionActionItem
|
||||
@ -553,8 +554,8 @@ const api = {
|
||||
ipcRenderer.invoke(IpcChannel.Preference_Get, key),
|
||||
set: <K extends PreferenceKeyType>(key: K, value: PreferenceDefaultScopeType[K]): Promise<void> =>
|
||||
ipcRenderer.invoke(IpcChannel.Preference_Set, key, value),
|
||||
getMultiple: (keys: PreferenceKeyType[]): Promise<Partial<PreferenceDefaultScopeType>> =>
|
||||
ipcRenderer.invoke(IpcChannel.Preference_GetMultiple, keys),
|
||||
getMultipleRaw: <K extends PreferenceKeyType>(keys: K[]): Promise<MultiPreferencesResultType<K>> =>
|
||||
ipcRenderer.invoke(IpcChannel.Preference_GetMultipleRaw, keys),
|
||||
setMultiple: (updates: Partial<PreferenceDefaultScopeType>) =>
|
||||
ipcRenderer.invoke(IpcChannel.Preference_SetMultiple, updates),
|
||||
getAll: (): Promise<PreferenceDefaultScopeType> => ipcRenderer.invoke(IpcChannel.Preference_GetAll),
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { DefaultPreferences } from '@shared/data/preference/preferenceSchemas'
|
||||
import type {
|
||||
MultiPreferencesResultType,
|
||||
PreferenceDefaultScopeType,
|
||||
PreferenceKeyType,
|
||||
PreferenceUpdateOptions
|
||||
@ -241,9 +242,9 @@ export class PreferenceService {
|
||||
/**
|
||||
* Get multiple preferences at once with caching and auto-subscription
|
||||
* @param keys Array of preference keys to retrieve
|
||||
* @returns Promise resolving to partial object with preference values
|
||||
* @returns Promise resolving to object with preference values for requested keys
|
||||
*/
|
||||
public async getMultipleRaw(keys: PreferenceKeyType[]): Promise<Partial<PreferenceDefaultScopeType>> {
|
||||
public async getMultipleRaw<K extends PreferenceKeyType>(keys: K[]): Promise<MultiPreferencesResultType<K>> {
|
||||
// Check which keys are already cached
|
||||
const cachedResults: Partial<PreferenceDefaultScopeType> = {}
|
||||
const uncachedKeys: PreferenceKeyType[] = []
|
||||
@ -260,7 +261,7 @@ export class PreferenceService {
|
||||
// Fetch uncached keys from main process
|
||||
if (uncachedKeys.length > 0) {
|
||||
try {
|
||||
const uncachedResults = await window.api.preference.getMultiple(uncachedKeys)
|
||||
const uncachedResults = await window.api.preference.getMultipleRaw(uncachedKeys)
|
||||
|
||||
// Update cache with new results
|
||||
for (const [key, value] of Object.entries(uncachedResults)) {
|
||||
@ -271,7 +272,7 @@ export class PreferenceService {
|
||||
await this.subscribeToKeyInternal([key as PreferenceKeyType])
|
||||
}
|
||||
|
||||
return { ...cachedResults, ...uncachedResults }
|
||||
return { ...cachedResults, ...uncachedResults } as MultiPreferencesResultType<K>
|
||||
} catch (error) {
|
||||
logger.error('Failed to get multiple preferences:', error as Error)
|
||||
|
||||
@ -283,11 +284,11 @@ export class PreferenceService {
|
||||
}
|
||||
}
|
||||
|
||||
return { ...cachedResults, ...defaultResults }
|
||||
return { ...cachedResults, ...defaultResults } as MultiPreferencesResultType<K>
|
||||
}
|
||||
}
|
||||
|
||||
return cachedResults
|
||||
return cachedResults as MultiPreferencesResultType<K>
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -57,7 +57,6 @@ const ApiServerSettings: FC = () => {
|
||||
return `cs-sk-${uuidv4()}`
|
||||
}
|
||||
|
||||
|
||||
const regenerateApiKey = () => {
|
||||
setApiServerConfig({ apiKey: generateApiKey() })
|
||||
window.toast.success(t('apiServer.messages.apiKeyRegenerated'))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user