From 0aa3d58dbcaec3e9f99ea91dac88fbeb4d03234e Mon Sep 17 00:00:00 2001 From: aleksey-hoffman Date: Tue, 30 Jun 2026 16:31:47 +0300 Subject: [PATCH] Refine: virtual locations - Refactor: Make drive list change reactively --- .../use-file-browser-navigation.ts | 32 ++++++----- src/utils/__tests__/virtual-locations.test.ts | 54 +++++++++++++++++++ src/utils/virtual-locations.ts | 52 ++++++++++++++++-- 3 files changed, 123 insertions(+), 15 deletions(-) diff --git a/src/modules/navigator/components/file-browser/composables/use-file-browser-navigation.ts b/src/modules/navigator/components/file-browser/composables/use-file-browser-navigation.ts index 060f0b82..264f16c7 100644 --- a/src/modules/navigator/components/file-browser/composables/use-file-browser-navigation.ts +++ b/src/modules/navigator/components/file-browser/composables/use-file-browser-navigation.ts @@ -33,6 +33,9 @@ import { getNavigableParentPath, isVirtualLocationPath, resolveDirectoryContents, + buildLocationsDirectoryFromDrives, + getLocationsDriveListDisplaySignature, + getLocationsEntriesDisplaySignature, } from '@/utils/virtual-locations'; import { sharedDrives } from '@/modules/home/composables/use-drives'; import { shouldIncludeItemCountsForSort } from '@/modules/navigator/components/file-browser/utils/file-browser-sort-columns'; @@ -46,13 +49,6 @@ interface DirChangePayload { const DIRECTORY_DWELL_TIME_MS = 3000; const WATCHER_DEBOUNCE_MS = 500; -function getDrivePathListSignature(paths: string[]): string { - return paths - .map(path => normalizePath(path)) - .sort() - .join('\0'); -} - function dirWatcherDiagEnabled(): boolean { return import.meta.env.DEV && localStorage.getItem('SFM_DIR_WATCHER_DIAG') === '1'; } @@ -266,10 +262,10 @@ export function useFileBrowserNavigation( return; } - const displayedDrivePaths = dirContents.value.entries.map(entry => entry.path); - const liveDrivePaths = sharedDrives.value.map(drive => drive.path); + const displayedSignature = getLocationsEntriesDisplaySignature(dirContents.value.entries); + const liveSignature = getLocationsDriveListDisplaySignature(sharedDrives.value); - if (getDrivePathListSignature(displayedDrivePaths) === getDrivePathListSignature(liveDrivePaths)) { + if (displayedSignature === liveSignature) { return; } @@ -290,14 +286,22 @@ export function useFileBrowserNavigation( const canGoForward = computed(() => historyIndex.value < history.value.length - 1); async function silentRefresh(): Promise { - if (!currentPath.value) { + const refreshPath = currentPath.value; + + if (!refreshPath) { return; } isRefreshing.value = true; try { - const result = await loadDirectoryContents(currentPath.value, createReadDirOptions()); + const result = isVirtualLocationPath(refreshPath) + ? buildLocationsDirectoryFromDrives(sharedDrives.value) + : await loadDirectoryContents(refreshPath, createReadDirOptions()); + + if (currentPath.value !== refreshPath) { + return; + } dirContents.value = result; invalidateDirectoryLinkMetadata(result); @@ -319,6 +323,10 @@ export function useFileBrowserNavigation( } } catch { + if (currentPath.value !== refreshPath) { + return; + } + await navigateToNearestExistingAncestor(); } finally { diff --git a/src/utils/__tests__/virtual-locations.test.ts b/src/utils/__tests__/virtual-locations.test.ts index a0e74c30..60a1e295 100644 --- a/src/utils/__tests__/virtual-locations.test.ts +++ b/src/utils/__tests__/virtual-locations.test.ts @@ -5,8 +5,11 @@ import { describe, expect, it } from 'vitest'; import { LOCATIONS_VIRTUAL_PATH, + buildLocationsDirectoryFromDrives, createLocationsDirEntry, driveInfoToDirEntry, + getLocationsDriveListDisplaySignature, + getLocationsEntriesDisplaySignature, getNavigableParentPath, getVirtualLocationDisplayName, isVirtualLocationPath, @@ -60,6 +63,57 @@ describe('virtual-locations', () => { expect(driveEntry.item_count).toBeNull(); }); + it('builds locations directory contents from drive snapshots', () => { + const drivePayload: DriveInfo = { + name: 'Local Disk (C:)', + path: 'C:/', + mount_point: 'C:\\', + file_system: 'NTFS', + drive_type: 'SSD', + total_space: 1000, + available_space: 500, + used_space: 500, + percent_used: 50, + is_removable: false, + is_read_only: false, + is_mounted: true, + device_path: '\\\\.\\C:', + }; + + const contents = buildLocationsDirectoryFromDrives([drivePayload]); + + expect(contents.path).toBe(LOCATIONS_VIRTUAL_PATH); + expect(contents.entries).toHaveLength(1); + expect(contents.entries[0]?.path).toBe('C:/'); + }); + + it('detects drive metadata changes in locations display signatures', () => { + const baseDrive: DriveInfo = { + name: 'Local Disk (C:)', + path: 'C:/', + mount_point: 'C:\\', + file_system: 'NTFS', + drive_type: 'SSD', + total_space: 1000, + available_space: 500, + used_space: 500, + percent_used: 50, + is_removable: false, + is_read_only: false, + is_mounted: true, + device_path: '\\\\.\\C:', + }; + + const renamedDrive = { ...baseDrive, name: 'System (C:)' }; + const resizedDrive = { ...baseDrive, total_space: 2000 }; + const entries = buildLocationsDirectoryFromDrives([baseDrive]).entries; + const baseSignature = getLocationsDriveListDisplaySignature([baseDrive]); + + expect(getLocationsEntriesDisplaySignature(entries)).toBe(baseSignature); + expect(getLocationsDriveListDisplaySignature([renamedDrive])).not.toBe(baseSignature); + expect(getLocationsDriveListDisplaySignature([resizedDrive])).not.toBe(baseSignature); + }); + describe('getNavigableParentPath', () => { it('returns null for the virtual locations path', () => { expect(getNavigableParentPath('sfm://locations', 'windows')).toBeNull(); diff --git a/src/utils/virtual-locations.ts b/src/utils/virtual-locations.ts index 19785aa0..fa82e5c2 100644 --- a/src/utils/virtual-locations.ts +++ b/src/utils/virtual-locations.ts @@ -4,7 +4,7 @@ import { invoke } from '@tauri-apps/api/core'; import type { DirContents, DirEntry, ReadDirOptions } from '@/types/dir-entry'; -import type { DriveInfo } from '@/types/drive-info'; +import type { DriveEntryMetadata, DriveInfo } from '@/types/drive-info'; import normalizePath, { getParentPath, getPathDisplayName, @@ -74,8 +74,7 @@ export function createLocationsDirEntry(): DirEntry { }; } -export async function readLocationsDirectory(): Promise { - const drives = await invoke('get_system_drives'); +export function buildLocationsDirectoryFromDrives(drives: DriveInfo[]): DirContents { const entries = drives.map(drive => driveInfoToDirEntry(drive)); const directoryCount = entries.length; @@ -93,6 +92,53 @@ export async function readLocationsDirectory(): Promise { }; } +function formatLocationsDriveDisplaySignaturePart( + path: string, + name: string, + size: number, + driveMetadata: DriveEntryMetadata | undefined, +): string { + return [ + normalizePath(path), + name, + String(size), + driveMetadata?.drive_type ?? '', + driveMetadata?.is_removable ? '1' : '0', + ].join('\x1f'); +} + +export function getLocationsDriveListDisplaySignature(drives: DriveInfo[]): string { + return drives + .map((drive) => { + const entry = driveInfoToDirEntry(drive); + return formatLocationsDriveDisplaySignaturePart( + entry.path, + entry.name, + entry.size, + entry.drive_metadata, + ); + }) + .sort() + .join('\0'); +} + +export function getLocationsEntriesDisplaySignature(entries: DirEntry[]): string { + return entries + .map(entry => formatLocationsDriveDisplaySignaturePart( + entry.path, + entry.name, + entry.size, + entry.drive_metadata, + )) + .sort() + .join('\0'); +} + +export async function readLocationsDirectory(): Promise { + const drives = await invoke('get_system_drives'); + return buildLocationsDirectoryFromDrives(drives); +} + export async function resolveDirectoryContents( path: string, options?: ReadDirOptions,