fix: fix mount bugs (#1)

* feat: add open in file explorer functionality

* fix: fix a bug that mount will execute twice
This commit is contained in:
Kuingsmile
2025-06-26 16:13:44 +08:00
committed by GitHub
parent 3a5eb44cad
commit 8ccd04b180
6 changed files with 127 additions and 12 deletions

View File

@@ -6,16 +6,49 @@ use crate::cmd::http_api::{get_process_list, start_process, stop_process};
use crate::object::structs::{AppState, FileItem};
use crate::utils::path::{get_openlist_binary_path, get_rclone_binary_path};
fn normalize_path(path: &str) -> String {
#[cfg(target_os = "windows")]
{
let normalized = path.replace('/', "\\");
if normalized.len() == 2 && normalized.chars().nth(1) == Some(':') {
format!("{}\\", normalized)
} else if normalized.len() > 2
&& normalized.chars().nth(1) == Some(':')
&& normalized.chars().nth(2) != Some('\\')
{
let drive = &normalized[..2];
let rest = &normalized[2..];
format!("{}\\{}", drive, rest)
} else {
normalized
}
}
#[cfg(not(target_os = "windows"))]
{
path.to_string()
}
}
#[tauri::command]
pub async fn open_folder(path: String) -> Result<bool, String> {
let path_buf = PathBuf::from(path);
let normalized_path = normalize_path(&path);
let path_buf = PathBuf::from(normalized_path);
if !path_buf.exists() {
return Err(format!("Path does not exist: {}", path_buf.display()));
}
open::that(path_buf.as_os_str()).map_err(|e| e.to_string())?;
Ok(true)
}
#[tauri::command]
pub async fn open_file(path: String) -> Result<bool, String> {
let path_buf = PathBuf::from(path);
let normalized_path = normalize_path(&path);
let path_buf = PathBuf::from(normalized_path);
if !path_buf.exists() {
return Err(format!("File does not exist: {}", path_buf.display()));
}
open::that_detached(path_buf.as_os_str()).map_err(|e| e.to_string())?;
Ok(true)
}

View File

@@ -414,7 +414,8 @@
"mount": "Mount",
"unmount": "Unmount",
"edit": "Edit",
"delete": "Delete"
"delete": "Delete",
"openInExplorer": "Open in Explorer"
},
"status": {
"status": "Status",
@@ -462,7 +463,8 @@
}
},
"meta": {
"autoMount": "Auto Mount"
"autoMount": "Auto Mount",
"openInExplorer": "Click to open in file explorer"
},
"empty": {
"title": "No Remote Configurations",

View File

@@ -414,7 +414,8 @@
"mount": "挂载",
"unmount": "卸载",
"edit": "编辑",
"delete": "删除"
"delete": "删除",
"openInExplorer": "在文件管理器中打开"
},
"status": {
"status": "状态",
@@ -462,7 +463,8 @@
}
},
"meta": {
"autoMount": "自动挂载"
"autoMount": "自动挂载",
"openInExplorer": "点击在文件管理器中打开"
},
"empty": {
"title": "无远程配置",

View File

@@ -60,7 +60,6 @@ export const useAppStore = defineStore('app', () => {
async function loadMountInfos() {
try {
mountInfos.value = await TauriAPI.getMountInfoList()
console.log('Loaded mount infos:', mountInfos.value)
} catch (err: any) {
error.value = 'Failed to load mount information'
console.error('Failed to load mount infos:', err)
@@ -216,6 +215,7 @@ export const useAppStore = defineStore('app', () => {
if (!config) {
throw new Error(`No configuration found for remote: ${name}`)
}
const processId = await getRcloneMountProcessId(name)
console.log(`Mounting remote ${name} with process ID:`, processId)
if (processId) {
@@ -228,6 +228,8 @@ export const useAppStore = defineStore('app', () => {
if (!startResult) {
throw new Error(`Failed to start mount process for remote: ${name}`)
}
await loadMountInfos()
return
} else {
console.log(`Remote ${name} is already mounted`)
return

View File

@@ -17,7 +17,8 @@ import {
RefreshCw,
Save,
X,
Settings
Settings,
FolderOpen
} from 'lucide-vue-next'
import type { RcloneFormConfig } from '../types'
import { useAppStore } from '@/stores/app'
@@ -280,15 +281,34 @@ const handleKeydown = (event: KeyboardEvent) => {
}
}
const openInFileExplorer = async (path?: string) => {
if (!path) {
console.warn('Mount point path is not available')
return
}
const normalizedPath = path.trim()
try {
await store.openFolder(normalizedPath)
} catch (error: any) {
console.error('Failed to open mount point in file explorer:', error)
const errorMessage = error.message || error.toString() || 'Unknown error'
if (errorMessage.includes('does not exist')) {
console.warn(`Mount point path does not exist: ${normalizedPath}`)
} else {
console.error(`Failed to open file explorer: ${errorMessage}`)
}
}
}
onMounted(async () => {
document.addEventListener('keydown', handleKeydown)
await rcloneStore.checkRcloneBackendStatus()
await store.loadRemoteConfigs()
await refreshMounts()
mountRefreshInterval = setInterval(refreshMounts, 30000)
mountRefreshInterval = setInterval(refreshMounts, 15000)
backendStatusCheckInterval = setInterval(() => {
rcloneStore.checkRcloneBackendStatus()
}, 10000)
}, 5000)
await rcloneStore.init()
})
@@ -430,7 +450,7 @@ onUnmounted(() => {
:is="getStatusIcon(getConfigStatus(config))"
class="status-icon"
:class="{
spinning: isConfigMounting(config),
spinning: isConfigMounting(config) || store.loading,
success: getConfigStatus(config) === 'mounted',
error: getConfigStatus(config) === 'error'
}"
@@ -441,7 +461,15 @@ onUnmounted(() => {
<div class="card-meta">
<div class="meta-tags">
<span class="meta-tag">{{ config.type }}</span>
<span v-if="config.mountPoint" class="meta-tag">{{ config.mountPoint }}</span>
<span
v-if="config.mountPoint"
class="meta-tag clickable-mount-point"
@click="openInFileExplorer(config.mountPoint)"
:title="t('mount.meta.openInExplorer')"
>
<FolderOpen class="mount-point-icon" />
{{ config.mountPoint }}
</span>
<span v-if="config.volumeName" class="meta-tag">{{ config.volumeName }}</span>
<span v-if="config.autoMount" class="meta-tag auto">{{ t('mount.meta.autoMount') }}</span>
</div>
@@ -482,6 +510,14 @@ onUnmounted(() => {
>
<Trash2 class="btn-icon" />
</button>
<button
v-if="isConfigMounted(config)"
@click="openInFileExplorer(config.mountPoint)"
class="secondary-btn"
:title="t('mount.actions.openInExplorer')"
>
<FolderOpen class="btn-icon" />
</button>
</div>
</div>
</div>

View File

@@ -602,6 +602,46 @@
color: white;
}
.meta-tag.clickable-mount-point {
cursor: pointer;
transition: all 0.2s ease;
background: var(--color-primary-50);
color: var(--color-primary-600);
border: 1px solid var(--color-primary-200);
gap: 4px;
}
.meta-tag.clickable-mount-point:hover {
background: var(--color-primary-100);
border-color: var(--color-primary-300);
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.meta-tag.clickable-mount-point:active {
transform: translateY(0);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.mount-point-icon {
width: 12px;
height: 12px;
flex-shrink: 0;
}
:root.dark .meta-tag.clickable-mount-point,
:root.auto.dark .meta-tag.clickable-mount-point {
background: var(--color-primary-900);
color: var(--color-primary-300);
border-color: var(--color-primary-700);
}
:root.dark .meta-tag.clickable-mount-point:hover,
:root.auto.dark .meta-tag.clickable-mount-point:hover {
background: var(--color-primary-800);
border-color: var(--color-primary-600);
}
.card-actions {
display: flex;
align-items: center;