mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-22 04:26:23 +08:00
fix: old workspace migration (#14403)
fix #14395 #### PR Dependency Tree * **PR #14403** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added ability to enumerate and list local workspaces. * Improved workspace ID persistence with Electron global-state storage, automatic fallback to legacy storage, and one-time migration to consolidate IDs. * **Tests** * Added unit test validating listing behavior (includes/excludes workspaces based on presence of workspace DB file). <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -48,15 +48,44 @@ import { WorkspaceImpl } from '../../workspace/impls/workspace';
|
||||
import { getWorkspaceProfileWorker } from './out-worker';
|
||||
|
||||
export const LOCAL_WORKSPACE_LOCAL_STORAGE_KEY = 'affine-local-workspace';
|
||||
export const LOCAL_WORKSPACE_GLOBAL_STATE_KEY =
|
||||
'workspace-engine:local-workspace-ids:v1';
|
||||
const LOCAL_WORKSPACE_CHANGED_BROADCAST_CHANNEL_KEY =
|
||||
'affine-local-workspace-changed';
|
||||
|
||||
const logger = new DebugLogger('local-workspace');
|
||||
|
||||
export function getLocalWorkspaceIds(): string[] {
|
||||
type GlobalStateStorageLike = {
|
||||
ready: Promise<void>;
|
||||
get<T>(key: string): T | undefined;
|
||||
set<T>(key: string, value: T): void;
|
||||
};
|
||||
|
||||
function normalizeWorkspaceIds(ids: unknown): string[] {
|
||||
if (!Array.isArray(ids)) {
|
||||
return [];
|
||||
}
|
||||
return ids.filter((id): id is string => typeof id === 'string');
|
||||
}
|
||||
|
||||
function getElectronGlobalStateStorage(): GlobalStateStorageLike | null {
|
||||
if (!BUILD_CONFIG.isElectron) {
|
||||
return null;
|
||||
}
|
||||
const sharedStorage = (
|
||||
globalThis as {
|
||||
__sharedStorage?: { globalState?: GlobalStateStorageLike };
|
||||
}
|
||||
).__sharedStorage;
|
||||
return sharedStorage?.globalState ?? null;
|
||||
}
|
||||
|
||||
function getLegacyLocalWorkspaceIds(): string[] {
|
||||
try {
|
||||
return JSON.parse(
|
||||
localStorage.getItem(LOCAL_WORKSPACE_LOCAL_STORAGE_KEY) ?? '[]'
|
||||
return normalizeWorkspaceIds(
|
||||
JSON.parse(
|
||||
localStorage.getItem(LOCAL_WORKSPACE_LOCAL_STORAGE_KEY) ?? '[]'
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error('Failed to get local workspace ids', e);
|
||||
@@ -64,21 +93,98 @@ export function getLocalWorkspaceIds(): string[] {
|
||||
}
|
||||
}
|
||||
|
||||
export function getLocalWorkspaceIds(): string[] {
|
||||
const globalState = getElectronGlobalStateStorage();
|
||||
if (globalState) {
|
||||
const value = globalState.get(LOCAL_WORKSPACE_GLOBAL_STATE_KEY);
|
||||
if (value !== undefined) {
|
||||
return normalizeWorkspaceIds(value);
|
||||
}
|
||||
}
|
||||
|
||||
return getLegacyLocalWorkspaceIds();
|
||||
}
|
||||
|
||||
export function setLocalWorkspaceIds(
|
||||
idsOrUpdater: string[] | ((ids: string[]) => string[])
|
||||
) {
|
||||
localStorage.setItem(
|
||||
LOCAL_WORKSPACE_LOCAL_STORAGE_KEY,
|
||||
JSON.stringify(
|
||||
typeof idsOrUpdater === 'function'
|
||||
? idsOrUpdater(getLocalWorkspaceIds())
|
||||
: idsOrUpdater
|
||||
)
|
||||
const next = normalizeWorkspaceIds(
|
||||
typeof idsOrUpdater === 'function'
|
||||
? idsOrUpdater(getLocalWorkspaceIds())
|
||||
: idsOrUpdater
|
||||
);
|
||||
const deduplicated = [...new Set(next)];
|
||||
|
||||
const globalState = getElectronGlobalStateStorage();
|
||||
if (globalState) {
|
||||
globalState.set(LOCAL_WORKSPACE_GLOBAL_STATE_KEY, deduplicated);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
localStorage.setItem(
|
||||
LOCAL_WORKSPACE_LOCAL_STORAGE_KEY,
|
||||
JSON.stringify(deduplicated)
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error('Failed to set local workspace ids', e);
|
||||
}
|
||||
}
|
||||
|
||||
class LocalWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
|
||||
constructor(private readonly framework: FrameworkProvider) {}
|
||||
constructor(private readonly framework: FrameworkProvider) {
|
||||
if (BUILD_CONFIG.isElectron) {
|
||||
void this.ensureWorkspaceIdsMigrated();
|
||||
}
|
||||
}
|
||||
|
||||
private migration: Promise<void> | null = null;
|
||||
|
||||
private ensureWorkspaceIdsMigrated() {
|
||||
if (!BUILD_CONFIG.isElectron) {
|
||||
return;
|
||||
}
|
||||
if (this.migration) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.migration = (async () => {
|
||||
const electronApi = this.framework.get(DesktopApiService);
|
||||
await electronApi.sharedStorage.globalState.ready;
|
||||
|
||||
const persistedIds = normalizeWorkspaceIds(
|
||||
electronApi.sharedStorage.globalState.get(
|
||||
LOCAL_WORKSPACE_GLOBAL_STATE_KEY
|
||||
)
|
||||
);
|
||||
const legacyIds = getLegacyLocalWorkspaceIds();
|
||||
|
||||
let scannedIds: string[] = [];
|
||||
try {
|
||||
scannedIds =
|
||||
await electronApi.handler.workspace.listLocalWorkspaceIds();
|
||||
} catch (e) {
|
||||
logger.error('Failed to scan local workspace ids', e);
|
||||
}
|
||||
|
||||
setLocalWorkspaceIds(currentIds => {
|
||||
return [
|
||||
...new Set([
|
||||
...currentIds,
|
||||
...persistedIds,
|
||||
...legacyIds,
|
||||
...scannedIds,
|
||||
]),
|
||||
];
|
||||
});
|
||||
})()
|
||||
.catch(e => {
|
||||
logger.error('Failed to migrate local workspace ids', e);
|
||||
})
|
||||
.finally(() => {
|
||||
this.notifyChannel.postMessage(null);
|
||||
});
|
||||
}
|
||||
|
||||
readonly flavour = 'local';
|
||||
readonly notifyChannel = new BroadcastChannel(
|
||||
@@ -242,6 +348,9 @@ class LocalWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
|
||||
);
|
||||
isRevalidating$ = new LiveData(false);
|
||||
revalidate(): void {
|
||||
if (BUILD_CONFIG.isElectron) {
|
||||
void this.ensureWorkspaceIdsMigrated();
|
||||
}
|
||||
// notify livedata to re-scan workspaces
|
||||
this.notifyChannel.postMessage(null);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import { GlobalState } from '../storage';
|
||||
import { WorkspaceFlavoursProvider } from '../workspace';
|
||||
import { CloudWorkspaceFlavoursProvider } from './impls/cloud';
|
||||
import {
|
||||
LOCAL_WORKSPACE_LOCAL_STORAGE_KEY,
|
||||
LocalWorkspaceFlavoursProvider,
|
||||
setLocalWorkspaceIds,
|
||||
} from './impls/local';
|
||||
|
||||
export { base64ToUint8Array, uint8ArrayToBase64 } from './utils/base64';
|
||||
@@ -25,12 +25,5 @@ export function configureBrowserWorkspaceFlavours(framework: Framework) {
|
||||
* Used after copying sqlite database file to appdata folder
|
||||
*/
|
||||
export function _addLocalWorkspace(id: string) {
|
||||
const allWorkspaceIDs: string[] = JSON.parse(
|
||||
localStorage.getItem(LOCAL_WORKSPACE_LOCAL_STORAGE_KEY) ?? '[]'
|
||||
);
|
||||
allWorkspaceIDs.push(id);
|
||||
localStorage.setItem(
|
||||
LOCAL_WORKSPACE_LOCAL_STORAGE_KEY,
|
||||
JSON.stringify(allWorkspaceIDs)
|
||||
);
|
||||
setLocalWorkspaceIds(ids => (ids.includes(id) ? ids : [...ids, id]));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user