Files
rclone-ui/lib/hooks.ts
T
2026-07-11 22:19:24 +03:00

145 lines
5.2 KiB
TypeScript

import { useQuery } from '@tanstack/react-query'
import { useEffect, useState } from 'react'
import type { RcloneFeatures, RcloneFsInfo } from '../types/rclone'
import { sortByName } from './flags'
import rclone from './rclone/client'
import { SERVE_TYPES } from './rclone/constants'
// Wall-clock tick for values derived from "now" (relative timestamps, next cron occurrences).
// Memoizing such values without a time dep freezes them at their last dep change. Pass null to
// pause (e.g. while a drawer is closed); re-arming refreshes immediately.
export function useNow(intervalMs: number | null = 30_000): number {
const [now, setNow] = useState(() => Date.now())
useEffect(() => {
if (intervalMs === null) {
return
}
setNow(Date.now())
const id = setInterval(() => setNow(Date.now()), intervalMs)
return () => clearInterval(id)
}, [intervalMs])
return now
}
// Shared query options for a remote's `/config/get`. No default staleTime: most consumers rely on
// staleTime-0 refetch-on-mount for cross-window freshness (each webview has its own QueryClient);
// the handful that want caching spread `staleTime` per-site.
export function remoteConfigQueryOptions(remote: string | undefined | null) {
return {
queryKey: ['remote', remote, 'config'] as const,
queryFn: () => rclone('/config/get', { params: { query: { name: remote! } } }),
enabled: !!remote && remote !== 'UI_LOCAL_FS' && remote !== 'UI_FAVORITES',
}
}
export function useRemoteConfig(remote: string | undefined | null) {
return useQuery(remoteConfigQueryOptions(remote))
}
// Shared query options for a remote's `operations/fsinfo` — the authoritative, per-remote backend
// capability set (correct even for wrapping backends like crypt/alias/union, which the old static
// type lists could not express). fsinfo instantiates/connects the remote, so it's cached hard:
// capabilities are ~immutable per config and only change on a remote edit (explicit invalidation)
// or an rclone upgrade (self-heals on the 24h staleTime / next cold launch). retry: 1 overrides the
// app-wide 3-retry default so an unreachable remote costs one attempt, then error-caches.
export function fsInfoQueryOptions(remote: string | undefined | null) {
return {
queryKey: ['remote', remote, 'fsinfo'] as const,
queryFn: () =>
rclone('/operations/fsinfo', { params: { query: { fs: `${remote}:` } } }),
enabled: !!remote && remote !== 'UI_LOCAL_FS' && remote !== 'UI_FAVORITES',
staleTime: 1000 * 60 * 60 * 24,
retry: 1,
}
}
export function useFsInfo(remote: string | undefined | null) {
return useQuery(fsInfoQueryOptions(remote))
}
// undefined while loading/errored → false. Callers that need an optimistic default (e.g. create
// folder, which historically assumed "supported" until proven otherwise) handle that explicitly.
export function hasFeature(
fsInfo: RcloneFsInfo | undefined,
feature: keyof RcloneFeatures
): boolean {
return !!fsInfo?.Features?.[feature]
}
export function useFlags() {
const allFlagsQuery = useQuery({
queryKey: ['options', 'all'],
queryFn: async () => await rclone('/options/info'),
})
const globalFlagsQuery = useQuery({
queryKey: ['options', 'global'],
queryFn: async () => await rclone('/options/get'),
})
const globalFlags = globalFlagsQuery.data
const allFlags = allFlagsQuery.data
const filterFlags = allFlags?.filter
.filter((flag) => !flag.Groups?.includes('Metadata'))
.sort(sortByName)
const configFlags = allFlags?.main
.filter(
(flag) =>
flag.Groups?.includes('Performance') ||
flag.Groups?.includes('Listing') ||
flag.Groups?.includes('Networking') ||
flag.Groups?.includes('Check') ||
flag.Name === 'use_server_modtime'
)
.sort(sortByName)
const mountFlags = allFlags?.mount
.filter((flag) => !flag.Groups?.includes('Metadata'))
.sort(sortByName)
const vfsFlags = allFlags?.vfs
.filter((flag) => !flag.Groups?.includes('Metadata'))
.sort(sortByName)
const copyFlags = allFlags?.main
.filter((flag) => flag.Groups?.includes('Copy'))
.sort(sortByName)
const syncFlags = allFlags?.main
.filter((flag) => flag.Groups?.includes('Copy') || flag.Groups?.includes('Sync'))
.sort(sortByName)
const serveFlags = SERVE_TYPES.reduce(
(acc, type) => {
acc[type] = (allFlags?.[type] || [])
.map((flag: any) => ({
...flag,
FieldName: flag.Name,
DefaultStr:
flag.Name === 'addr'
? flag.DefaultStr.replace('[', '').replace(']', '')
: flag.DefaultStr,
}))
.sort((a: any, b: any) => a.Name.localeCompare(b.Name))
return acc
},
{} as Record<(typeof SERVE_TYPES)[number], any[]>
)
return {
allFlags,
globalFlags,
filterFlags,
configFlags,
mountFlags,
vfsFlags,
copyFlags,
syncFlags,
serveFlags,
}
}