From eb5b85e007963a455efea260e1894c5fc3d9074e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Tue, 5 May 2026 00:08:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=EF=BC=9A=E5=A4=9A=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E9=A2=84=E8=A7=88Tab=E7=B3=BB=E7=BB=9F+=E8=84=8F?= =?UTF-8?q?=E6=A0=87=E8=AE=B0+=E5=85=B3=E9=97=AD=E7=A1=AE=E8=AE=A4+?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E9=BB=91=E5=90=8D=E5=8D=95=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - useMultiPreview composable管理多Tab状态、会话持久化 - 面包屑状态dot移除 - 放开Program Files目录访问限制 --- .../components/ConnectionIndicator.vue | 1 - .../FileSystem/components/FileEditorPanel.vue | 172 +++++++++++++ .../FileSystem/composables/useMultiPreview.ts | 240 ++++++++++++++++++ frontend/src/components/FileSystem/index.vue | 183 +++++++++---- frontend/src/types/file-system.ts | 4 + frontend/src/utils/constants.js | 1 + internal/filesystem/path_validator.go | 5 +- 7 files changed, 558 insertions(+), 48 deletions(-) create mode 100644 frontend/src/components/FileSystem/composables/useMultiPreview.ts diff --git a/frontend/src/components/FileSystem/components/ConnectionIndicator.vue b/frontend/src/components/FileSystem/components/ConnectionIndicator.vue index 43a2694..1c15b86 100644 --- a/frontend/src/components/FileSystem/components/ConnectionIndicator.vue +++ b/frontend/src/components/FileSystem/components/ConnectionIndicator.vue @@ -6,7 +6,6 @@
- {{ label }}
+ + +
+
+ + {{ getFileTypeIcon(tab.fileItem.name) }} + + + {{ tab.fileItem.name }} +
+ +
+
+
@@ -373,6 +401,8 @@ import type { FileEditorPanelConfig } from '@/types/file-system' import { renderMermaidDiagrams, rerenderMermaidDiagrams } from '@/utils/markedExtensions' import { useThemeStore } from '@/stores/theme' import { previewExcel, previewWord, previewCsv } from '@/utils/filePreviewHandlers' +import { getFileCategory } from '@/utils/fileTypeHelpers' +import { isDirty } from '../composables/useMultiPreview' import { connectionManager } from '@/api/connection-manager' // 异步加载 CodeEditor 组件,减少初始包大小 @@ -435,10 +465,26 @@ interface Emits { (e: 'imageLoad', dimensions: string): void (e: 'imageError'): void (e: 'openLocalFile', link: string): void + (e: 'switchTab', tabId: string): void + (e: 'closeTab', tabId: string): void } const emit = defineEmits() +// 多 tab 数据 +const previewTabs = computed(() => props.config.previewTabs || []) +const activeTabId = computed(() => props.config.activeTabId || null) + +// 文件类型图标(基于已有分类逻辑) +const CATEGORY_ICONS: Record = { + image: '🖼️', video: '🎬', audio: '🎵', pdf: '📕', + html: '🌐', markdown: '📝', text: '⚡', binary: '📦', + unknown: '📄' +} +const getFileTypeIcon = (filename: string): string => { + return CATEGORY_ICONS[getFileCategory(filename)] || '📄' +} + // HTML 预览 URL(实时从 connectionManager 读取,不缓存) function resolveHtmlPreviewBase(): string { if (!connectionManager.isRemote()) return 'http://localhost:2652' @@ -868,6 +914,7 @@ onUnmounted(() => { flex-direction: column; height: 100%; background: var(--color-bg-1); + position: relative; } .file-editor-panel:fullscreen { @@ -1430,4 +1477,129 @@ body[arco-theme*='dark'] .markdown-preview-content :deep(.hljs-link) { color: var(--hljs-link); text-decoration: underline; } + +/* ========== 多文件预览浮动 Tab 栏 ========== */ + +.preview-tabs { + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + display: flex; + flex-direction: column; + gap: 3px; + z-index: 20; + padding: 6px 4px; + pointer-events: auto; +} + +.preview-tab { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + width: 56px; + padding: 6px 4px 4px; + border-radius: 8px 0 0 8px; + background: color-mix(in srgb, var(--color-bg-3) 50%, transparent); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid color-mix(in srgb, var(--color-border-2) 40%, transparent); + border-right: none; + cursor: pointer; + transition: all 0.3s ease; + position: relative; + opacity: 0.68; +} + +.preview-tabs:hover .preview-tab { + opacity: 1; +} + +.preview-tab:hover { + background: var(--color-fill-2); +} + +.preview-tab.active { + background: var(--color-fill-2); + border-left-color: transparent; +} + +.preview-tab.active::before { + content: ''; + position: absolute; + left: -1px; + top: 0; + bottom: 0; + width: 3px; + background: rgb(var(--primary-6)); + border-radius: 3px 0 0 3px; +} + +.tab-icon { + font-size: 16px; + line-height: 1; + position: relative; +} + +.tab-dirty { + position: absolute; + top: -2px; + left: -14px; + width: 6px; + height: 6px; + background: rgb(var(--warning-6)); + border-radius: 50%; +} + +.tab-name { + font-size: 10px; + line-height: 1.2; + color: var(--color-text-2); + text-align: center; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: block; +} + +.preview-tab.active .tab-name { + color: rgb(var(--primary-6)); + font-weight: 500; +} + +.tab-actions { + position: absolute; + top: 1px; + right: 2px; + display: none; + align-items: center; + gap: 1px; +} + +.preview-tab:hover .tab-actions { + display: flex; +} + +.tab-close { + width: 14px; + height: 14px; + border: none; + background: transparent; + color: var(--color-text-4); + font-size: 12px; + line-height: 1; + cursor: pointer; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + padding: 0; +} + +.tab-close:hover:not(:disabled) { + background: var(--color-fill-3); + color: var(--color-text-1); +} diff --git a/frontend/src/components/FileSystem/composables/useMultiPreview.ts b/frontend/src/components/FileSystem/composables/useMultiPreview.ts new file mode 100644 index 0000000..203501c --- /dev/null +++ b/frontend/src/components/FileSystem/composables/useMultiPreview.ts @@ -0,0 +1,240 @@ +/** + * 多文件预览 Tab 管理 Composable + * 管理预览 Tab 列表、激活状态、内容缓存、会话持久化 + */ + +import { ref, computed, watch } from 'vue' +import { STORAGE_KEYS } from '@/utils/constants' +import type { FileItem } from '@/types/file-system' + +export interface PreviewTab { + /** 唯一标识(基于路径) */ + id: string + /** 文件信息 */ + fileItem: FileItem + /** 缓存的预览 URL */ + previewUrl: string + /** 缓存的文件内容 */ + fileContent: string + /** 缓存的原始内容 */ + originalContent: string + /** 缓存的编辑模式状态 */ + isEditMode: boolean + /** 缓存的渲染内容 (HTML/Markdown) */ + rendered: string + /** 是否已加载过内容 */ + loaded: boolean +} + +/** 持久化存储的精简结构 */ +interface PersistedTab { + path: string + active: boolean + /** 未保存的内容(有修改时才存) */ + unsavedContent?: string + originalContent?: string + isEditMode?: boolean +} + +const MAX_TABS = 10 +const STORAGE_KEY = STORAGE_KEYS.FILESYSTEM.MULTI_PREVIEW_TABS + +interface UnsavedEntry { + content: string + original: string + editMode: boolean +} + +interface RestoredSession { + paths: string[] + activePath: string | null + unsavedMap: Map +} + +function pathToId(path: string): string { + return path.replace(/\\/g, '/').toLowerCase() +} + +export function isDirty(tab: PreviewTab): boolean { + return !!(tab.fileContent && tab.originalContent !== undefined && tab.fileContent !== tab.originalContent) +} + +export function useMultiPreview() { + const tabs = ref([]) + const activeTabId = ref(null) + + const activeTab = computed(() => { + if (!activeTabId.value) return null + return tabs.value.find(t => t.id === activeTabId.value) || null + }) + + /** 创建一个新 tab */ + const createTab = (fileItem: FileItem): PreviewTab => ({ + id: pathToId(fileItem.path), + fileItem, + previewUrl: '', + fileContent: '', + originalContent: '', + isEditMode: false, + rendered: '', + loaded: false + }) + + // ========== 持久化 ========== + + /** 从 localStorage 恢复会话 */ + const restoreSession = (): RestoredSession => { + const unsavedMap = new Map() + let activePath: string | null = null + const paths: string[] = [] + + try { + const raw = localStorage.getItem(STORAGE_KEY) + if (!raw) return { paths, activePath, unsavedMap } + + const persisted: PersistedTab[] = JSON.parse(raw) + if (!Array.isArray(persisted)) return { paths, activePath, unsavedMap } + + for (const p of persisted) { + if (!p.path) continue + paths.push(p.path) + if (p.active) activePath = p.path + if (p.unsavedContent !== undefined) { + unsavedMap.set(pathToId(p.path), { + content: p.unsavedContent, + original: p.originalContent || '', + editMode: p.isEditMode || false + }) + } + } + } catch { + localStorage.removeItem(STORAGE_KEY) + } + + return { paths, activePath, unsavedMap } + } + + /** 保存会话到 localStorage */ + const persistSession = () => { + const persisted: PersistedTab[] = tabs.value.map(tab => { + const hasUnsaved = tab.fileContent && tab.originalContent !== undefined && tab.fileContent !== tab.originalContent + // 限制存储大小,超过 100KB 的内容不存入 localStorage + const canSave = hasUnsaved && tab.fileContent.length <= 100_000 + return { + path: tab.fileItem.path, + active: tab.id === activeTabId.value, + unsavedContent: canSave ? tab.fileContent : undefined, + originalContent: canSave ? tab.originalContent : undefined, + isEditMode: canSave ? tab.isEditMode : undefined + } + }) + + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(persisted)) + } catch { + // localStorage 满了,忽略 + } + } + + /** 恢复未保存内容到 tab */ + const applyUnsavedContent = (tab: PreviewTab, unsavedMap: Map) => { + const unsaved = unsavedMap.get(tab.id) + if (unsaved) { + tab.fileContent = unsaved.content + tab.originalContent = unsaved.original + tab.isEditMode = unsaved.editMode + tab.loaded = true // 标记为已加载,避免重新从磁盘读取覆盖 + } + } + + /** 添加或激活 tab,返回 { tab, isNew } */ + const addTab = (fileItem: FileItem): { tab: PreviewTab; isNew: boolean } => { + const id = pathToId(fileItem.path) + const existing = tabs.value.find(t => t.id === id) + if (existing) { + activeTabId.value = id + return { tab: existing, isNew: false } + } + + // 超出上限,移除非活跃的最早 tab + if (tabs.value.length >= MAX_TABS) { + const victimIdx = tabs.value.findIndex(t => t.id !== activeTabId.value && t.id !== id) + if (victimIdx !== -1) tabs.value.splice(victimIdx, 1) + } + + const tab = createTab(fileItem) + tabs.value.push(tab) + activeTabId.value = tab.id + return { tab, isNew: true } + } + + /** 切换 tab */ + const switchTab = (tabId: string) => { + if (tabs.value.find(t => t.id === tabId)) { + activeTabId.value = tabId + } + } + + /** 关闭 tab,返回关闭后应激活的 tabId */ + const removeTab = (tabId: string): string | null => { + const idx = tabs.value.findIndex(t => t.id === tabId) + if (idx === -1) return activeTabId.value + + tabs.value.splice(idx, 1) + + if (activeTabId.value === tabId) { + const nextIdx = Math.min(idx, tabs.value.length - 1) + const nextTab = tabs.value[nextIdx] || null + activeTabId.value = nextTab?.id || null + } + + return activeTabId.value + } + + /** 缓存当前 tab 的状态(切换前调用) */ + const cacheCurrentTab = (state: { + previewUrl: string + fileContent: string + originalContent: string + isEditMode: boolean + rendered: string + }) => { + if (!activeTabId.value) return + const tab = tabs.value.find(t => t.id === activeTabId.value) + if (tab) { + tab.previewUrl = state.previewUrl + tab.fileContent = state.fileContent + tab.originalContent = state.originalContent + tab.isEditMode = state.isEditMode + tab.rendered = state.rendered + tab.loaded = true + } + } + + /** 清空所有 tab */ + const clearAll = () => { + tabs.value = [] + activeTabId.value = null + } + + // 监听 tabs/activeTabId 变化,防抖持久化 + let _persistTimer: ReturnType | null = null + watch([tabs, activeTabId], () => { + if (_persistTimer) clearTimeout(_persistTimer) + _persistTimer = setTimeout(persistSession, 1000) + }, { deep: true }) + + return { + tabs, + activeTabId, + activeTab, + addTab, + switchTab, + removeTab, + cacheCurrentTab, + clearAll, + persistSession, + restoreSession, + applyUnsavedContent + } +} diff --git a/frontend/src/components/FileSystem/index.vue b/frontend/src/components/FileSystem/index.vue index 0562c09..6e35668 100644 --- a/frontend/src/components/FileSystem/index.vue +++ b/frontend/src/components/FileSystem/index.vue @@ -8,7 +8,7 @@ :config="sidebarConfig" @open-favorite="handleOpenFavorite" @remove-favorite="handleRemoveFavorite" - @toggle-pin="handleTogglePin" + @toggle-pin="handleToggleFavPin" @long-press-start="handleLongPressStart" @long-press-cancel="handleLongPressCancel" @drag-start="handleDragStart" @@ -75,6 +75,8 @@ @image-load="handleImageLoad" @image-error="handleImageError" @open-local-file="handleOpenLocalFile" + @switch-tab="switchToTab" + @close-tab="closeTab" /> @@ -130,6 +132,7 @@ import { usePathNavigation } from './composables/usePathNavigation' import { useFilePreview } from './composables/useFilePreview' import { useFileEdit } from './composables/useFileEdit' import { useCommonPaths } from './composables/useCommonPaths' +import { useMultiPreview, type PreviewTab, isDirty } from './composables/useMultiPreview' // 导入工具函数 import { getFileName, sortFileList } from '@/utils/fileUtils' @@ -311,6 +314,9 @@ const { fileContent, originalContent, isEditMode, fileContentHeight, contentChan currentDirectory: filePath }) +// 多文件预览 Tab +const multiPreview = useMultiPreview() + // ========== 计算属性 ========== const favoritePaths = computed(() => favorites.value.map(f => f.path)) @@ -413,7 +419,10 @@ const fileEditorPanelConfig = computed(() => { currentImageDimensions: currentImageDimensions.value, currentFileExtension, isBinaryFile: isBinaryFileRef.value, - fileMtime: selectedFileItem.value?.modified_time || '' + fileMtime: selectedFileItem.value?.modified_time || '', + // 多 tab + previewTabs: multiPreview.tabs.value, + activeTabId: multiPreview.activeTabId.value } }) @@ -580,7 +589,7 @@ const handleRemoveFavorite = (path: string) => { removeFav(path) } -const handleTogglePin = (path: string) => { +const handleToggleFavPin = (path: string) => { togglePin(path) } @@ -610,24 +619,14 @@ const handleDragEnd = () => { // 文件列表事件 const handleFileClick = async (file: FileItem) => { - // 正常文件系统浏览 if (file.isDir) { - // 目录:使用 navigate 函数,确保历史记录正确更新 await navigate(file.path) } else { - // 文件:选中并读取内容 - selectedFileItem.value = file - loadFileContent(file.path) + openFileAsTab(file) } } -const handleFileDoubleClick = async (file: FileItem) => { - if (file.isDir) { - await navigate(file.path) - } else { - selectFile(file.path) - } -} +const handleFileDoubleClick = handleFileClick const handleToggleFavorite = (file: FileItem) => { toggleFav(file) @@ -1064,38 +1063,108 @@ const isMediaPreviewable = (filename: string): boolean => { isPdfFile(filename) } +/** 激活 tab:设置选中项 + 加载或恢复内容 */ +const activateTab = async (tab: PreviewTab) => { + selectedFileItem.value = tab.fileItem + if (tab.loaded) { + restoreTabState(tab) + } else { + await loadFileContent(tab.fileItem.path) + tab.loaded = true + } +} + +/** 文件 → 添加到 tab 并激活 */ +const openFileAsTab = async (file: FileItem) => { + cacheCurrentTabState() + const { tab } = multiPreview.addTab(file) + await activateTab(tab) +} + +/** 切换 tab */ +const switchToTab = async (tabId: string) => { + cacheCurrentTabState() + multiPreview.switchTab(tabId) + const tab = multiPreview.activeTab.value + if (tab) await activateTab(tab) +} + +/** 关闭 tab */ +const closeTab = (tabId: string) => { + const tab = multiPreview.tabs.value.find(t => t.id === tabId) + + if (tab && isDirty(tab)) { + Modal.confirm({ + title: '未保存的修改', + content: `「${tab!.fileItem.name}」有未保存的修改,关闭后将丢失。`, + okText: '关闭', + cancelText: '取消', + onOk: () => doCloseTab(tabId) + }) + return + } + + doCloseTab(tabId) +} + +const doCloseTab = (tabId: string) => { + const newActiveId = multiPreview.removeTab(tabId) + if (newActiveId) { + const tab = multiPreview.activeTab.value + if (tab) activateTab(tab) + } else { + selectedFileItem.value = null + clearContent() + } +} + +/** 缓存当前 tab 的编辑状态 */ +const cacheCurrentTabState = () => { + multiPreview.cacheCurrentTab({ + previewUrl: previewUrl.value, + fileContent: fileContent.value, + originalContent: originalContent.value, + isEditMode: isEditMode.value, + rendered: computeRendered.value + }) +} + +/** 活跃 tab 内容实时同步(脏标记用,完整缓存由 cacheCurrentTabState 负责) */ +watch([fileContent, originalContent], () => { + const tab = multiPreview.activeTab.value + if (tab) { + tab.fileContent = fileContent.value + tab.originalContent = originalContent.value + } +}) + +/** 从 tab 缓存恢复编辑状态 */ +const restoreTabState = (tab: PreviewTab) => { + previewUrl.value = tab.previewUrl + fileContent.value = tab.fileContent + originalContent.value = tab.originalContent + isEditMode.value = tab.isEditMode + fileVersion.value++ +} + const selectFile = async (path: string) => { - // 后端已统一返回 / 路径,直接比较 const normalizedPath = path.toLowerCase() - // 尝试在当前文件列表中查找 - const file = fileList.value.find(f => { - const normalizedFilePath = f.path.toLowerCase() - return normalizedFilePath === normalizedPath - }) + const file = fileList.value.find(f => f.path.toLowerCase() === normalizedPath) if (file) { - // 在当前列表中找到的文件 - selectedFileItem.value = file + await openFileAsTab(file) } else { - // 不在当前列表中的文件(如收藏夹中的其他目录文件) - // 构造一个基本的 FileItem 对象 const fileName = getFileName(path) - selectedFileItem.value = { + await openFileAsTab({ path, name: fileName, isDir: false, size: 0, modified_time: '', is_favorite: isFavorite(path) - } + }) } - - // 加载文件内容 - await loadFileContent(path) - - // 记住上次打开的文件 - localStorage.setItem(STORAGE_KEYS.FILESYSTEM.LAST_OPENED_FILE, path) } const loadFileContent = async (path: string) => { @@ -1309,16 +1378,41 @@ onMounted(async () => { await loadDirectory(startPath) } - // 恢复上次打开的文件 - const lastFile = localStorage.getItem(STORAGE_KEYS.FILESYSTEM.LAST_OPENED_FILE) - if (lastFile) { - const normalized = lastFile.replace(/\\/g, '/').replace(/\/+$/, '') - const currentDir = filePath.value.replace(/\\/g, '/').replace(/\/+$/, '') - const lastFileDir = normalized.substring(0, normalized.lastIndexOf('/')) || '/' - if (lastFileDir.toLowerCase() === currentDir.toLowerCase()) { - const found = fileList.value.find(f => f.path.replace(/\\/g, '/').toLowerCase() === normalized.toLowerCase()) - if (found && !found.isDir) { - await selectFile(found.path) + // 恢复多文件预览会话 + const session = multiPreview.restoreSession() + if (session.paths.length > 0 && !connectionManager.isRemote()) { + for (const path of session.paths) { + const name = path.split(/[/\\]/).pop() || path + const { tab } = multiPreview.addTab({ path, name, isDir: false, size: 0, modified_time: '' }) + if (path === session.activePath || path.replace(/\\/g, '/').toLowerCase() === (session.activePath || '').replace(/\\/g, '/').toLowerCase()) { + multiPreview.activeTabId.value = tab.id + } + multiPreview.applyUnsavedContent(tab, session.unsavedMap) + } + + // 加载激活 tab 的内容 + const active = multiPreview.activeTab.value + if (active) { + selectedFileItem.value = active.fileItem + if (active.loaded) { + restoreTabState(active) + } else { + await loadFileContent(active.fileItem.path) + active.loaded = true + } + } + } else { + // 无会话记录,回退到旧逻辑:恢复上次打开的单个文件 + const lastFile = localStorage.getItem(STORAGE_KEYS.FILESYSTEM.LAST_OPENED_FILE) + if (lastFile) { + const normalized = lastFile.replace(/\\/g, '/').replace(/\/+$/, '') + const currentDir = filePath.value.replace(/\\/g, '/').replace(/\/+$/, '') + const lastFileDir = normalized.substring(0, normalized.lastIndexOf('/')) || '/' + if (lastFileDir.toLowerCase() === currentDir.toLowerCase()) { + const found = fileList.value.find(f => f.path.replace(/\\/g, '/').toLowerCase() === normalized.toLowerCase()) + if (found && !found.isDir) { + await selectFile(found.path) + } } } } @@ -1335,6 +1429,9 @@ onUnmounted(() => { window.removeEventListener('keydown', handleKeyDown) window.removeEventListener('click', hideContextMenu) window.removeEventListener('paste', handlePaste) + // 应用关闭时立即持久化当前会话 + cacheCurrentTabState() + multiPreview.persistSession() }) // 键盘快捷键 diff --git a/frontend/src/types/file-system.ts b/frontend/src/types/file-system.ts index 7d6b219..3d7a2e8 100644 --- a/frontend/src/types/file-system.ts +++ b/frontend/src/types/file-system.ts @@ -203,6 +203,10 @@ export interface FileEditorPanelConfig { isBinaryFile: boolean /** 文件修改时间(用于检测外部变更) */ fileMtime: string + /** 多文件预览 Tab 列表 */ + previewTabs?: any[] + /** 当前激活的 Tab ID */ + activeTabId?: string | null } /** diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index 8d904a6..c4cc980 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -31,6 +31,7 @@ export const STORAGE_KEYS = { COL_SETTINGS: 'app-filesystem-col-settings', // 列配置(显隐+顺序) SHOW_HEADER: 'app-filesystem-show-header', // 表头显隐 LAST_OPENED_FILE: 'app-filesystem-last-opened-file', // 上次打开的文件路径 + MULTI_PREVIEW_TABS: 'app-filesystem-multi-preview-tabs', // 多文件预览 tab 会话 }, // 设备测试模块 diff --git a/internal/filesystem/path_validator.go b/internal/filesystem/path_validator.go index 9b9f3dc..b9f93d9 100644 --- a/internal/filesystem/path_validator.go +++ b/internal/filesystem/path_validator.go @@ -123,12 +123,9 @@ func (v *DefaultPathValidator) checkWindowsSystemPaths(path string) *ValidationE if len(lowerPath) >= 3 && lowerPath[1] == ':' { driveLetter := lowerPath[0:1] - // 检查系统关键目录(仅保留最关键的系统目录) + // 检查系统核心目录 forbiddenDirs := []string{ driveLetter + ":\\windows", - driveLetter + ":\\program files", - driveLetter + ":\\program files (x86)", - driveLetter + ":\\program files (arm)", driveLetter + ":\\system volume information", driveLetter + ":\\boot", }