mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-11 08:33:46 +08:00
feat(settings): configure mounted directory cleanup
This commit is contained in:
@@ -2394,6 +2394,9 @@ export default {
|
||||
organizeAndScrapDesc: 'Set rename format, scraping options, etc.',
|
||||
scrapSource: 'Scraping Data Source',
|
||||
scrapSourceHint: 'Metadata source for scraping',
|
||||
mountedLocalDiskDeleteEmptyDirs: 'Delete Empty Directories on Mounted Drives',
|
||||
mountedLocalDiskDeleteEmptyDirsHint:
|
||||
'When disabled, move operations keep source directories on local network or FUSE mounts.',
|
||||
movieRenameFormat: 'Movie Rename Format',
|
||||
movieRenameFormatHint:
|
||||
'Using Jinja2 syntax, format reference: https://jinja.palletsprojects.com/en/3.0.x/templates',
|
||||
|
||||
@@ -2348,6 +2348,8 @@ export default {
|
||||
organizeAndScrapDesc: '设置重命名格式、刮削选项等。',
|
||||
scrapSource: '刮削数据源',
|
||||
scrapSourceHint: '刮削时的元数据来源',
|
||||
mountedLocalDiskDeleteEmptyDirs: '挂载盘删除空目录',
|
||||
mountedLocalDiskDeleteEmptyDirsHint: '关闭后,移动整理不会清理网络或 FUSE 挂载的本地源目录。',
|
||||
movieRenameFormat: '电影重命名格式',
|
||||
movieRenameFormatHint: '使用Jinja2语法,格式参考:https://jinja.palletsprojects.com/en/3.0.x/templates',
|
||||
tvRenameFormat: '电视剧重命名格式',
|
||||
|
||||
@@ -2347,6 +2347,8 @@ export default {
|
||||
organizeAndScrapDesc: '設置重命名格式、刮削選項等。',
|
||||
scrapSource: '刮削數據源',
|
||||
scrapSourceHint: '刮削時的元數據來源',
|
||||
mountedLocalDiskDeleteEmptyDirs: '掛載盤刪除空目錄',
|
||||
mountedLocalDiskDeleteEmptyDirsHint: '關閉後,移動整理不會清理網路或 FUSE 掛載的本地源目錄。',
|
||||
movieRenameFormat: '電影重命名格式',
|
||||
movieRenameFormatHint: '使用Jinja2語法,格式參考:https://jinja.palletsprojects.com/en/3.0.x/templates',
|
||||
tvRenameFormat: '電視劇重命名格式',
|
||||
|
||||
@@ -66,6 +66,10 @@ const SystemSettings = ref<any>({
|
||||
},
|
||||
})
|
||||
|
||||
// 挂载型本地盘删除空目录开关
|
||||
const mountedLocalDiskDeleteEmptyDirs = ref(true)
|
||||
const mountedLocalDiskDeleteEmptyDirsKey = 'MountedLocalDiskDeleteEmptyDirs'
|
||||
|
||||
// 编辑器主题
|
||||
// Ace 跟随 Vuetify 当前生效主题,auto 模式下也按实际明暗色渲染。
|
||||
const editorTheme = computed(() => (globalTheme.current.value.dark ? 'github_dark' : 'github_light_default'))
|
||||
@@ -120,6 +124,18 @@ async function loadSystemSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载挂载盘空目录清理设置
|
||||
async function loadMountedLocalDiskDeleteEmptyDirs() {
|
||||
try {
|
||||
const result: { data?: { value?: boolean | null } } = await api.get(
|
||||
`system/setting/${mountedLocalDiskDeleteEmptyDirsKey}`,
|
||||
)
|
||||
mountedLocalDiskDeleteEmptyDirs.value = result.data?.value ?? true
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 移动结束
|
||||
function orderDirectoryCards() {
|
||||
// 更新所有目录的优先级
|
||||
@@ -256,8 +272,11 @@ function removeStorage(storage: StorageConf) {
|
||||
// 保存设置
|
||||
async function saveSystemSettings(value: any) {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/env', value)
|
||||
if (result.success) {
|
||||
const [envResult, mountedDiskResult] = await Promise.all([
|
||||
api.post('system/env', value),
|
||||
api.post(`system/setting/${mountedLocalDiskDeleteEmptyDirsKey}`, mountedLocalDiskDeleteEmptyDirs.value),
|
||||
])
|
||||
if (envResult.success && mountedDiskResult.success) {
|
||||
$toast.success(t('setting.directory.organizeSaveSuccess'))
|
||||
} else $toast.error(t('setting.directory.organizeSaveFailed'))
|
||||
} catch (error) {
|
||||
@@ -266,7 +285,13 @@ async function saveSystemSettings(value: any) {
|
||||
}
|
||||
|
||||
async function loadPageData() {
|
||||
await Promise.all([loadDirectories(), loadStorages(), loadMediaCategories(), loadSystemSettings()])
|
||||
await Promise.all([
|
||||
loadDirectories(),
|
||||
loadStorages(),
|
||||
loadMediaCategories(),
|
||||
loadSystemSettings(),
|
||||
loadMountedLocalDiskDeleteEmptyDirs(),
|
||||
])
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
@@ -395,6 +420,16 @@ useSilentSettingRefresh(loadPageData, {
|
||||
prepend-inner-icon="mdi-database"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="mountedLocalDiskDeleteEmptyDirs"
|
||||
:label="t('setting.directory.mountedLocalDiskDeleteEmptyDirs')"
|
||||
:hint="t('setting.directory.mountedLocalDiskDeleteEmptyDirsHint')"
|
||||
color="primary"
|
||||
persistent-hint
|
||||
inset
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<div class="rename-format-editor">
|
||||
<div class="rename-format-editor__label">
|
||||
|
||||
118
src/views/setting/__tests__/AccountSettingDirectory.spec.ts
Normal file
118
src/views/setting/__tests__/AccountSettingDirectory.spec.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import AccountSettingDirectory from '@/views/setting/AccountSettingDirectory.vue'
|
||||
import { fireEvent, screen, waitFor, within } from '@testing-library/vue'
|
||||
import { renderWithProviders } from '@tests/support/render'
|
||||
import { defineComponent } from 'vue'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
apiGet: vi.fn(),
|
||||
apiPost: vi.fn(),
|
||||
toastError: vi.fn(),
|
||||
toastSuccess: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/api', () => ({
|
||||
default: {
|
||||
get: mocks.apiGet,
|
||||
post: mocks.apiPost,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('vue-toastification', () => ({
|
||||
useToast: () => ({
|
||||
error: mocks.toastError,
|
||||
success: mocks.toastSuccess,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useSilentSettingRefresh', () => ({
|
||||
useSilentSettingRefresh: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/components/cards/DirectoryCard.vue', async () => {
|
||||
const { defineComponent } = await import('vue')
|
||||
return { default: defineComponent({ name: 'DirectoryCardStub', template: '<div />' }) }
|
||||
})
|
||||
|
||||
vi.mock('@/components/cards/StorageCard.vue', async () => {
|
||||
const { defineComponent } = await import('vue')
|
||||
return { default: defineComponent({ name: 'StorageCardStub', template: '<div />' }) }
|
||||
})
|
||||
|
||||
vi.mock('vuedraggable', async () => {
|
||||
const { defineComponent, h } = await import('vue')
|
||||
|
||||
return {
|
||||
default: defineComponent({
|
||||
name: 'DraggableStub',
|
||||
setup(_props, { slots }) {
|
||||
return () => h('div', slots.default?.())
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const AceEditorStub = defineComponent({
|
||||
name: 'VAceEditor',
|
||||
props: {
|
||||
value: { type: String, default: '' },
|
||||
},
|
||||
template: '<div />',
|
||||
})
|
||||
|
||||
function mockSettings(settingValue: boolean | null) {
|
||||
mocks.apiGet.mockImplementation((endpoint: string) => {
|
||||
if (endpoint === 'system/setting/public/Directories') return { data: { value: [] } }
|
||||
if (endpoint === 'system/setting/public/Storages') return { data: { value: [] } }
|
||||
if (endpoint === 'media/category') return {}
|
||||
if (endpoint === 'system/env') return { data: {}, success: true }
|
||||
if (endpoint === 'system/setting/MountedLocalDiskDeleteEmptyDirs') {
|
||||
return { data: { value: settingValue }, success: true }
|
||||
}
|
||||
throw new Error(`Unexpected GET ${endpoint}`)
|
||||
})
|
||||
mocks.apiPost.mockResolvedValue({ success: true })
|
||||
}
|
||||
|
||||
async function renderDirectorySettings() {
|
||||
return renderWithProviders(AccountSettingDirectory, {
|
||||
global: {
|
||||
stubs: {
|
||||
VAceEditor: AceEditorStub,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe('mounted local disk empty directory cleanup setting', () => {
|
||||
beforeEach(() => {
|
||||
mocks.apiGet.mockReset()
|
||||
mocks.apiPost.mockReset()
|
||||
mocks.toastError.mockReset()
|
||||
mocks.toastSuccess.mockReset()
|
||||
})
|
||||
|
||||
it('defaults to enabled when no saved value exists', async () => {
|
||||
mockSettings(null)
|
||||
|
||||
await renderDirectorySettings()
|
||||
|
||||
expect(await screen.findByRole('checkbox', { name: '挂载盘删除空目录' })).toBeChecked()
|
||||
})
|
||||
|
||||
it('saves the disabled value with the organization settings', async () => {
|
||||
mockSettings(true)
|
||||
await renderDirectorySettings()
|
||||
const cleanupSwitch = await screen.findByRole('checkbox', { name: '挂载盘删除空目录' })
|
||||
await fireEvent.click(cleanupSwitch)
|
||||
|
||||
const organizeCard = screen.getByText('整理 & 刮削').closest('.v-card')
|
||||
expect(organizeCard).not.toBeNull()
|
||||
await fireEvent.click(within(organizeCard as HTMLElement).getByRole('button', { name: '保存' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.apiPost).toHaveBeenCalledWith('system/setting/MountedLocalDiskDeleteEmptyDirs', false)
|
||||
})
|
||||
expect(mocks.toastSuccess).toHaveBeenCalledWith('整理选项设置保存成功')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user