api command test

This commit is contained in:
FTCHD
2026-01-30 20:21:45 +07:00
parent 4a05ef3701
commit 74bde4d3ff
5 changed files with 308 additions and 8 deletions
+21
View File
@@ -16,6 +16,27 @@ const RE_WINDOWS_EXTENDED_PATH = /(\/\/\?\/|\\\\\?\\)/
const RE_WINDOWS_DRIVE_ROOT = /^:local:[a-zA-Z]:\/$/
const RE_WINDOWS_DRIVE_LETTER = /^[a-zA-Z]:$/
export async function startDryRun<T>(operation: () => Promise<T>): Promise<T> {
await rclone('/options/set', {
params: {
query: {
main: { DryRun: true },
},
},
})
try {
return await operation()
} finally {
await rclone('/options/set', {
params: {
query: {
main: { DryRun: false },
},
},
})
}
}
function serializeOptions(
remotePath: string,
options: {
+73 -2
View File
@@ -12,7 +12,7 @@ import {
} from '@heroui/react'
import { useMutation } from '@tanstack/react-query'
import { invoke } from '@tauri-apps/api/core'
import { message } from '@tauri-apps/plugin-dialog'
import { ask, message } from '@tauri-apps/plugin-dialog'
import { platform } from '@tauri-apps/plugin-os'
import cronstrue from 'cronstrue'
import { AnimatePresence, motion } from 'framer-motion'
@@ -20,6 +20,7 @@ import {
AlertOctagonIcon,
ClockIcon,
CopyIcon,
EyeIcon,
FilterIcon,
FoldersIcon,
PlayIcon,
@@ -31,7 +32,7 @@ import { useSearchParams } from 'react-router-dom'
import { getOptionsSubtitle } from '../../lib/flags'
import { useFlags } from '../../lib/hooks'
import notify from '../../lib/notify'
import { startCopy } from '../../lib/rclone/api'
import { startCopy, startDryRun } from '../../lib/rclone/api'
import { RCLONE_CONFIG_DEFAULTS } from '../../lib/rclone/constants'
import { openWindow } from '../../lib/window'
import { useHostStore } from '../../store/host'
@@ -178,6 +179,47 @@ export default function Copy() {
},
})
const dryRunMutation = useMutation({
mutationFn: async () => {
if (!sources || sources.length === 0 || !dest) {
throw new Error('Please select both a source and destination path')
}
return startDryRun(() =>
startCopy({
sources,
destination: dest,
options: {
config: configOptions,
copy: copyOptions,
filter: filterOptions,
remotes: remoteOptions,
},
})
)
},
onSuccess: async () => {
const result = await ask(
'Dry run started, you can check the results in the Transfers screen',
{
title: 'Preview (Dry Run)',
kind: 'info',
okLabel: 'Open Transfers',
cancelLabel: 'OK',
}
)
if (result) {
await openWindow({ name: 'Transfers', url: '/transfers' })
}
},
onError: async (error) => {
console.error('Error starting dry run:', error)
await message(error instanceof Error ? error.message : 'Failed to start dry run', {
title: 'Dry Run',
kind: 'error',
})
},
})
useEffect(() => {
startTransition(() => {
setConfigOptionsJsonString(JSON.stringify(RCLONE_CONFIG_DEFAULTS.config, null, 2))
@@ -535,6 +577,35 @@ export default function Copy() {
)}
</AnimatePresence>
<ButtonGroup variant="flat">
<Tooltip
content="Preview (Dry Run)"
placement="top"
size="lg"
color="foreground"
>
<Button
size="lg"
type="button"
color="primary"
isIconOnly={true}
isLoading={dryRunMutation.isPending}
onPress={() => {
if (
dryRunMutation.isPending ||
!!jsonError ||
!sources ||
sources.length === 0 ||
!dest ||
sources[0] === dest
) {
return
}
setTimeout(() => dryRunMutation.mutate(), 100)
}}
>
<EyeIcon className="size-6" />
</Button>
</Tooltip>
<Tooltip content="Schedule task" placement="top" size="lg" color="foreground">
<Button
size="lg"
+69 -2
View File
@@ -14,13 +14,14 @@ import {
import * as Sentry from '@sentry/browser'
import { useMutation, useQuery } from '@tanstack/react-query'
import { invoke } from '@tauri-apps/api/core'
import { message } from '@tauri-apps/plugin-dialog'
import { ask, message } from '@tauri-apps/plugin-dialog'
import { platform } from '@tauri-apps/plugin-os'
import cronstrue from 'cronstrue'
import { AnimatePresence, motion } from 'framer-motion'
import {
AlertOctagonIcon,
ClockIcon,
EyeIcon,
FilterIcon,
FoldersIcon,
PlayIcon,
@@ -32,9 +33,10 @@ import { getOptionsSubtitle } from '../../lib/flags'
import { getRemoteName } from '../../lib/format'
import { useFlags } from '../../lib/hooks'
import notify from '../../lib/notify'
import { startDelete } from '../../lib/rclone/api'
import { startDelete, startDryRun } from '../../lib/rclone/api'
import rclone from '../../lib/rclone/client'
import { RCLONE_CONFIG_DEFAULTS, SUPPORTS_PURGE } from '../../lib/rclone/constants'
import { openWindow } from '../../lib/window'
import { useHostStore } from '../../store/host'
import type { FlagValue } from '../../types/rclone'
import CommandInfoButton from '../components/CommandInfoButton'
@@ -198,6 +200,44 @@ export default function Delete() {
},
})
const dryRunMutation = useMutation({
mutationFn: async () => {
if (!sourceFs) {
throw new Error('Please select a source path to delete')
}
return startDryRun(() =>
startDelete({
sources: [sourceFs],
options: {
filter: filterOptions,
config: configOptions,
},
})
)
},
onSuccess: async () => {
const result = await ask(
'Dry run started, you can check the results in the Transfers screen',
{
title: 'Preview (Dry Run)',
kind: 'info',
okLabel: 'Open Transfers',
cancelLabel: 'OK',
}
)
if (result) {
await openWindow({ name: 'Transfers', url: '/transfers' })
}
},
onError: async (error) => {
console.error('Error starting dry run:', error)
await message(error instanceof Error ? error.message : 'Failed to start dry run', {
title: 'Dry Run',
kind: 'error',
})
},
})
const buttonText = useMemo(() => {
if (startDeleteMutation.isPending) return 'STARTING...'
if (!sourceFs || sourceFs.length === 0) return 'Please select a source path'
@@ -423,6 +463,33 @@ export default function Delete() {
)}
</AnimatePresence>
<ButtonGroup variant="flat">
<Tooltip
content="Preview (Dry Run)"
placement="top"
size="lg"
color="foreground"
>
<Button
size="lg"
type="button"
color="primary"
isIconOnly={true}
isLoading={dryRunMutation.isPending}
onPress={() => {
if (
dryRunMutation.isPending ||
!!jsonError ||
!sourceFs ||
sourceFs.length === 0
) {
return
}
setTimeout(() => dryRunMutation.mutate(), 100)
}}
>
<EyeIcon className="size-6" />
</Button>
</Tooltip>
<Tooltip content="Schedule task" placement="top" size="lg" color="foreground">
<Button
size="lg"
+73 -2
View File
@@ -12,13 +12,14 @@ import {
} from '@heroui/react'
import { useMutation } from '@tanstack/react-query'
import { invoke } from '@tauri-apps/api/core'
import { message } from '@tauri-apps/plugin-dialog'
import { ask, message } from '@tauri-apps/plugin-dialog'
import { platform } from '@tauri-apps/plugin-os'
import cronstrue from 'cronstrue'
import { AnimatePresence, motion } from 'framer-motion'
import {
AlertOctagonIcon,
ClockIcon,
EyeIcon,
FilterIcon,
FoldersIcon,
MoveIcon,
@@ -31,7 +32,7 @@ import { useSearchParams } from 'react-router-dom'
import { getOptionsSubtitle } from '../../lib/flags'
import { useFlags } from '../../lib/hooks'
import notify from '../../lib/notify'
import { startMove } from '../../lib/rclone/api'
import { startDryRun, startMove } from '../../lib/rclone/api'
import { RCLONE_CONFIG_DEFAULTS } from '../../lib/rclone/constants'
import { openWindow } from '../../lib/window'
import { useHostStore } from '../../store/host'
@@ -205,6 +206,47 @@ export default function Move() {
},
})
const dryRunMutation = useMutation({
mutationFn: async () => {
if (!sources || sources.length === 0 || !dest) {
throw new Error('Please select both a source and destination path')
}
return startDryRun(() =>
startMove({
sources,
destination: dest,
options: {
config: configOptions,
move: moveOptions,
filter: filterOptions,
remotes: remoteOptions,
},
})
)
},
onSuccess: async () => {
const result = await ask(
'Dry run started, you can check the results in the Transfers screen',
{
title: 'Preview (Dry Run)',
kind: 'info',
okLabel: 'Open Transfers',
cancelLabel: 'OK',
}
)
if (result) {
await openWindow({ name: 'Transfers', url: '/transfers' })
}
},
onError: async (error) => {
console.error('Error starting dry run:', error)
await message(error instanceof Error ? error.message : 'Failed to start dry run', {
title: 'Dry Run',
kind: 'error',
})
},
})
useEffect(() => {
startTransition(() => {
setConfigOptionsJsonString(JSON.stringify(RCLONE_CONFIG_DEFAULTS.config, null, 2))
@@ -557,6 +599,35 @@ export default function Move() {
)}
</AnimatePresence>
<ButtonGroup variant="flat">
<Tooltip
content="Preview (Dry Run)"
placement="top"
size="lg"
color="foreground"
>
<Button
size="lg"
type="button"
color="primary"
isIconOnly={true}
isLoading={dryRunMutation.isPending}
onPress={() => {
if (
dryRunMutation.isPending ||
!!jsonError ||
!sources ||
sources.length === 0 ||
!dest ||
sources[0] === dest
) {
return
}
setTimeout(() => dryRunMutation.mutate(), 100)
}}
>
<EyeIcon className="size-6" />
</Button>
</Tooltip>
<Tooltip content="Schedule task" placement="top" size="lg" color="foreground">
<Button
size="lg"
+72 -2
View File
@@ -13,13 +13,14 @@ import {
import * as Sentry from '@sentry/browser'
import { useMutation } from '@tanstack/react-query'
import { invoke } from '@tauri-apps/api/core'
import { message } from '@tauri-apps/plugin-dialog'
import { ask, message } from '@tauri-apps/plugin-dialog'
import { platform } from '@tauri-apps/plugin-os'
import cronstrue from 'cronstrue'
import { AnimatePresence, motion } from 'framer-motion'
import {
AlertOctagonIcon,
ClockIcon,
EyeIcon,
FilterIcon,
FolderSyncIcon,
FoldersIcon,
@@ -32,7 +33,7 @@ import { useSearchParams } from 'react-router-dom'
import { getOptionsSubtitle } from '../../lib/flags'
import { useFlags } from '../../lib/hooks'
import notify from '../../lib/notify'
import { startSync } from '../../lib/rclone/api'
import { startDryRun, startSync } from '../../lib/rclone/api'
import { RCLONE_CONFIG_DEFAULTS } from '../../lib/rclone/constants'
import { openWindow } from '../../lib/window'
import { useHostStore } from '../../store/host'
@@ -172,6 +173,47 @@ export default function Sync() {
},
})
const dryRunMutation = useMutation({
mutationFn: async () => {
if (!source || !dest) {
throw new Error('Please select both a source and destination path')
}
return startDryRun(() =>
startSync({
source,
destination: dest,
options: {
config: configOptions,
sync: syncOptions,
filter: filterOptions,
remotes: remoteOptions,
},
})
)
},
onSuccess: async () => {
const result = await ask(
'Dry run started, you can check the results in the Transfers screen',
{
title: 'Preview (Dry Run)',
kind: 'info',
okLabel: 'Open Transfers',
cancelLabel: 'OK',
}
)
if (result) {
await openWindow({ name: 'Transfers', url: '/transfers' })
}
},
onError: async (error) => {
console.error('Error starting dry run:', error)
await message(error instanceof Error ? error.message : 'Failed to start dry run', {
title: 'Dry Run',
kind: 'error',
})
},
})
const buttonText = useMemo(() => {
if (startSyncMutation.isPending) return 'STARTING...'
if (!source) return 'Please select a source path'
@@ -539,6 +581,34 @@ export default function Sync() {
)}
</AnimatePresence>
<ButtonGroup variant="flat">
<Tooltip
content="Preview (Dry Run)"
placement="top"
size="lg"
color="foreground"
>
<Button
size="lg"
type="button"
color="primary"
isIconOnly={true}
isLoading={dryRunMutation.isPending}
onPress={() => {
if (
dryRunMutation.isPending ||
!!jsonError ||
!source ||
!dest ||
source === dest
) {
return
}
setTimeout(() => dryRunMutation.mutate(), 100)
}}
>
<EyeIcon className="size-6" />
</Button>
</Tooltip>
<Tooltip content="Schedule task" placement="top" size="lg" color="foreground">
<Button
size="lg"