mirror of
https://github.com/OpenListTeam/OpenList-Desktop.git
synced 2025-11-25 19:27:33 +08:00
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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -414,7 +414,8 @@
|
||||
"mount": "挂载",
|
||||
"unmount": "卸载",
|
||||
"edit": "编辑",
|
||||
"delete": "删除"
|
||||
"delete": "删除",
|
||||
"openInExplorer": "在文件管理器中打开"
|
||||
},
|
||||
"status": {
|
||||
"status": "状态",
|
||||
@@ -462,7 +463,8 @@
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"autoMount": "自动挂载"
|
||||
"autoMount": "自动挂载",
|
||||
"openInExplorer": "点击在文件管理器中打开"
|
||||
},
|
||||
"empty": {
|
||||
"title": "无远程配置",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user