From 67be5965c4c3fa076a309b19e0f41cdc11220af2 Mon Sep 17 00:00:00 2001 From: FTCHD <144691102+FTCHD@users.noreply.github.com> Date: Fri, 12 Sep 2025 18:55:16 +0200 Subject: [PATCH] Mount (on startup & fixes) Signed-off-by: FTCHD <144691102+FTCHD@users.noreply.github.com> --- lib/menu.ts | 137 ++++++++++++++++++++++++-------------------- lib/rclone/api.ts | 10 +++- lib/store.ts | 4 -- main.ts | 14 +++-- src/pages/Mount.tsx | 4 +- 5 files changed, 94 insertions(+), 75 deletions(-) diff --git a/lib/menu.ts b/lib/menu.ts index b86fb8b..95e5575 100644 --- a/lib/menu.ts +++ b/lib/menu.ts @@ -8,21 +8,25 @@ import { platform } from '@tauri-apps/plugin-os' import { exit } from '@tauri-apps/plugin-process' import { isDirectoryEmpty } from './fs' import notify from './notify' -import { cleanupRemote, deleteRemote, mountRemote, unmountRemote } from './rclone/api' +import { cleanupRemote, deleteRemote, listMounts, mountRemote, unmountRemote } from './rclone/api' import { dialogGetMountPlugin, needsMountPlugin } from './rclone/mount' import { usePersistedStore, useStore } from './store' import { getLoadingTray, getMainTray, rebuildTrayMenu } from './tray' import { lockWindows, openFullWindow, openWindow, unlockWindows } from './window' async function parseRemotes(remotes: string[]) { - console.log('[parseRemotes]') + console.log('[parseRemotes] remotes', remotes) - const storeState = useStore.getState() const persistedStoreState = usePersistedStore.getState() + console.log('[parseRemotes] listing mounts') + const currentMounts = await listMounts() + console.log('[parseRemotes] currentMounts', currentMounts) + const parsedRemotes: Record = {} for (const remote of remotes) { + console.log('[parseRemotes] remote', remote) const remoteConfig = persistedStoreState.remoteConfigList?.[remote] if (remoteConfig?.disabledActions?.includes('tray')) { continue @@ -30,64 +34,7 @@ async function parseRemotes(remotes: string[]) { const submenuItems: (MenuItem | Submenu | PredefinedMenuItem)[] = [] - const alreadyMounted = storeState.mountedRemotes[remote] - - if (alreadyMounted) { - const unmountMenuItem = await MenuItem.new({ - id: `unmount-${remote}`, - text: 'Unmount', - action: async () => { - try { - const mountPoint = storeState.mountedRemotes[remote] - if (!mountPoint) { - console.error(`No mount point found for remote ${remote}`) - return - } - await unmountRemote({ mountPoint }) - delete storeState.mountedRemotes[remote] - await rebuildTrayMenu() - await message(`Successfully unmounted ${remote} from ${mountPoint}`, { - title: 'Success', - }) - } catch (error) { - Sentry.captureException(error) - console.error('Unmount operation failed:', error) - await message(`Failed to unmount ${remote}: ${error}`, { - kind: 'error', - title: 'Unmount Error', - }) - } - }, - }) - submenuItems.push(unmountMenuItem) - - // Add "Show Location" option for mounted remotes - const mountPoint = storeState.mountedRemotes[remote] - console.log('Adding Show Location', mountPoint) - const showLocationItem = await MenuItem.new({ - id: `open-${remote}`, - - text: 'Show Location', - action: async () => { - console.log('Show Location', mountPoint) - if (mountPoint) { - try { - await openPath(mountPoint) - } catch (error) { - Sentry.captureException(error) - console.error('Error opening path:', error) - await message(`Failed to open ${mountPoint} (${error})`, { - title: 'Open Error', - kind: 'error', - }) - } - } - }, - }) - submenuItems.push(showLocationItem) - } - - if (!alreadyMounted && !remoteConfig?.disabledActions?.includes('tray-mount')) { + if (!remoteConfig?.disabledActions?.includes('tray-mount')) { const mountMenuItem = await MenuItem.new({ id: `mount-${remote}`, text: 'Quick Mount', @@ -180,7 +127,6 @@ async function parseRemotes(remotes: string[]) { mountOptions: remoteConfig?.mountDefaults, vfsOptions: remoteConfig?.vfsDefaults, }) - storeState.mountedRemotes[remote] = selectedPath await notify({ title: 'Mounted', @@ -299,6 +245,73 @@ async function parseRemotes(remotes: string[]) { submenuItems.push(removeMenuItem) } + const currentRemoteMounts = currentMounts.filter( + (mount) => mount.Fs.split(':')[0] === remote + ) + + console.log('[parseRemotes] currentRemoteMounts', currentRemoteMounts) + + for (const currentMount of currentRemoteMounts) { + console.log( + '[parseRemotes] Adding Unmount (' + + currentMount.MountPoint.split('/').pop() + + ') for ', + remote + ) + const unmountMenuItem = await MenuItem.new({ + id: `unmount-${remote}-${currentMount.MountPoint}`, + text: 'Unmount (' + currentMount.MountPoint.split('/').pop() + ')', + action: async () => { + try { + await unmountRemote({ mountPoint: currentMount.MountPoint }) + await rebuildTrayMenu() + await message( + `Successfully unmounted ${remote} from ${currentMount.MountPoint.split('/').pop()}`, + { + title: 'Success', + } + ) + } catch (error) { + Sentry.captureException(error) + console.error('Unmount operation failed:', error) + await message(`Failed to unmount ${remote}: ${error}`, { + kind: 'error', + title: 'Unmount Error', + }) + } + }, + }) + submenuItems.push(unmountMenuItem) + + console.log( + '[parseRemotes] Adding Open (' + + currentMount.MountPoint.split('/').pop() + + ') for ', + remote + ) + const showLocationItem = await MenuItem.new({ + id: `open-${remote}-${currentMount.MountPoint}`, + + text: 'Open (' + currentMount.MountPoint.split('/').pop() + ')', + action: async () => { + console.log( + '[parseRemotes] Opening (' + currentMount.MountPoint.split('/').pop() + ')' + ) + try { + await openPath(currentMount.MountPoint) + } catch (error) { + Sentry.captureException(error) + console.error('Error opening path:', error) + await message(`Failed to open ${currentMount.MountPoint} (${error})`, { + title: 'Open Error', + kind: 'error', + }) + } + }, + }) + submenuItems.push(showLocationItem) + } + parsedRemotes[remote] = submenuItems } diff --git a/lib/rclone/api.ts b/lib/rclone/api.ts index a1c816b..1909ddd 100644 --- a/lib/rclone/api.ts +++ b/lib/rclone/api.ts @@ -170,8 +170,8 @@ export async function cleanupRemote(remote: string) { // console.log(JSON.stringify(r, null, 2)) } -export async function getMountPoints() { - console.log('[getMountPoints]') +export async function listMounts() { + console.log('[listMounts]') const r = await fetch('http://localhost:5572/mount/listmounts', { method: 'POST', @@ -179,7 +179,11 @@ export async function getMountPoints() { }).then( (res) => res.json() as Promise<{ - mountPoints: string[] + mountPoints: { + Fs: string + MountPoint: string + MountedOn: string + }[] }> ) diff --git a/lib/store.ts b/lib/store.ts index 4c29198..545bfef 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -32,8 +32,6 @@ interface State { rcloneAuth: string rcloneAuthHeader: string - mountedRemotes: Record - serveList: { pid: number; protocol: string; remote: string }[] setServeList: (serve: { pid: number; protocol: string; remote: string }) => void removeServeList: (pid: number) => void @@ -126,8 +124,6 @@ export const useStore = create()( rcloneAuth: '', rcloneAuthHeader: '', - mountedRemotes: {}, - serveList: [], setServeList: (serve: { pid: number; protocol: string; remote: string }) => set((state) => ({ serveList: [...state.serveList, serve] })), diff --git a/main.ts b/main.ts index 026fadf..7d3db01 100644 --- a/main.ts +++ b/main.ts @@ -8,6 +8,7 @@ import { exit, relaunch } from '@tauri-apps/plugin-process' import { check } from '@tauri-apps/plugin-updater' import { CronExpressionParser } from 'cron-parser' import { defaultOptions } from 'tauri-plugin-sentry-api' +import { isDirectoryEmpty } from './lib/fs' import { validateLicense } from './lib/license' import notify from './lib/notify' import { @@ -221,15 +222,18 @@ async function startRclone() { async function startupMounts() { const remoteConfigList = usePersistedStore.getState().remoteConfigList + const remotes = useStore.getState().remotes - if (!remoteConfigList) { - return - } - - for (const remote in remoteConfigList) { + for (const remote in remotes) { const remoteConfig = remoteConfigList[remote] + if (!remoteConfig) continue if (remoteConfig.mountOnStart && remoteConfig.defaultMountPoint) { try { + const isEmpty = await isDirectoryEmpty(remoteConfig.defaultMountPoint) + if (!isEmpty) { + continue + } + await mountRemote({ remotePath: `${remote}:${remoteConfig?.defaultRemotePath || ''}`, mountPoint: remoteConfig.defaultMountPoint, diff --git a/src/pages/Mount.tsx b/src/pages/Mount.tsx index 57acfa7..395df02 100644 --- a/src/pages/Mount.tsx +++ b/src/pages/Mount.tsx @@ -164,7 +164,7 @@ export default function Mount() { (!('VolumeName' in _mountOptions) || !_mountOptions.VolumeName) && ['windows', 'macos'].includes(platform()) ) { - _mountOptions.VolumeName = `${source.split(sep()).pop()}${Math.random().toString(36).substring(2, 3).toUpperCase()}` + _mountOptions.VolumeName = `${source.split(sep()).pop()}-${Math.random().toString(36).substring(2, 3).toUpperCase()}` } let directoryExists: boolean | undefined @@ -220,6 +220,8 @@ export default function Mount() { }) setIsMounted(true) + + await triggerTrayRebuild() } catch (err) { console.error('[Mount] Failed to start mount:', err) const errorMessage =