mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 00:59:28 +01:00
impl cache create/delete with server create/delete
also impl test connection
This commit is contained in:
parent
52223e6979
commit
b7a05ca955
@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { LayoutRectangle, Pressable, PressableProps } from 'react-native'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { GestureResponderEvent, LayoutRectangle, Pressable, PressableProps, ViewStyle } from 'react-native'
|
||||
|
||||
type PressableOpacityProps = PressableProps & {
|
||||
ripple?: boolean
|
||||
@ -9,10 +9,11 @@ type PressableOpacityProps = PressableProps & {
|
||||
|
||||
const PressableOpacity: React.FC<PressableOpacityProps> = props => {
|
||||
const [opacity, setOpacity] = useState(1)
|
||||
const [disabledStyle, setDisabledStyle] = useState<ViewStyle>({})
|
||||
const [dimensions, setDimensions] = useState<LayoutRectangle | undefined>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
props.disabled === true ? setOpacity(0.3) : setOpacity(1)
|
||||
props.disabled === true ? setDisabledStyle({ opacity: 0.3 }) : setDisabledStyle({})
|
||||
}, [props.disabled])
|
||||
|
||||
props = {
|
||||
@ -20,10 +21,41 @@ const PressableOpacity: React.FC<PressableOpacityProps> = props => {
|
||||
unstable_pressDelay: props.unstable_pressDelay === undefined ? 60 : props.unstable_pressDelay,
|
||||
}
|
||||
|
||||
const onPressIn = useCallback<(event: GestureResponderEvent) => void>(
|
||||
data => {
|
||||
if (props.disabled) {
|
||||
return
|
||||
}
|
||||
setOpacity(0.4)
|
||||
props.onPressIn ? props.onPressIn(data) : null
|
||||
},
|
||||
[props],
|
||||
)
|
||||
const onPressOut = useCallback<(event: GestureResponderEvent) => void>(
|
||||
data => {
|
||||
if (props.disabled) {
|
||||
return
|
||||
}
|
||||
setOpacity(1)
|
||||
props.onPressOut ? props.onPressOut(data) : null
|
||||
},
|
||||
[props],
|
||||
)
|
||||
const onLongPress = useCallback<(event: GestureResponderEvent) => void>(
|
||||
data => {
|
||||
if (props.disabled) {
|
||||
return
|
||||
}
|
||||
setOpacity(1)
|
||||
props.onLongPress ? props.onLongPress(data) : null
|
||||
},
|
||||
[props],
|
||||
)
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
{...props}
|
||||
style={[{ justifyContent: 'center', alignItems: 'center' }, props.style as any, { opacity }]}
|
||||
style={[{ justifyContent: 'center', alignItems: 'center' }, props.style as any, { opacity }, disabledStyle]}
|
||||
android_ripple={
|
||||
props.ripple
|
||||
? {
|
||||
@ -34,22 +66,9 @@ const PressableOpacity: React.FC<PressableOpacityProps> = props => {
|
||||
: undefined
|
||||
}
|
||||
onLayout={event => setDimensions(event.nativeEvent.layout)}
|
||||
onPressIn={() => {
|
||||
if (!props.disabled) {
|
||||
setOpacity(0.4)
|
||||
}
|
||||
}}
|
||||
onPressOut={() => {
|
||||
if (!props.disabled) {
|
||||
setOpacity(1)
|
||||
}
|
||||
}}
|
||||
onLongPress={data => {
|
||||
if (!props.disabled) {
|
||||
setOpacity(1)
|
||||
props.onLongPress ? props.onLongPress(data) : null
|
||||
}
|
||||
}}>
|
||||
onPressIn={onPressIn}
|
||||
onPressOut={onPressOut}
|
||||
onLongPress={onLongPress}>
|
||||
{props.children}
|
||||
</Pressable>
|
||||
)
|
||||
|
||||
@ -42,7 +42,7 @@ const styles = StyleSheet.create({
|
||||
itemSubtitle: {
|
||||
fontFamily: font.regular,
|
||||
color: colors.text.secondary,
|
||||
fontSize: 15,
|
||||
fontSize: 14,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@ -72,9 +72,9 @@ export type HomeLists = { [key: string]: AlbumListItem[] }
|
||||
export type StarrableItemType = 'song' | 'album' | 'artist'
|
||||
|
||||
export enum CacheItemType {
|
||||
coverArt,
|
||||
artistArt,
|
||||
song,
|
||||
coverArt = 'coverArt',
|
||||
artistArt = 'artistArt',
|
||||
song = 'song',
|
||||
}
|
||||
|
||||
export type CacheItemTypeKey = keyof typeof CacheItemType
|
||||
|
||||
@ -8,28 +8,27 @@ import font from '@app/styles/font'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import md5 from 'md5'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { StyleSheet, Text, TextInput, View } from 'react-native'
|
||||
import { StyleSheet, Text, TextInput, ToastAndroid, View } from 'react-native'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
function replaceIndex<T>(array: T[], index: number, replacement: T): T[] {
|
||||
const start = array.slice(0, index)
|
||||
const end = array.slice(index + 1)
|
||||
return [...start, replacement, ...end]
|
||||
}
|
||||
|
||||
const ServerView: React.FC<{
|
||||
id?: string
|
||||
}> = ({ id }) => {
|
||||
const navigation = useNavigation()
|
||||
const activeServer = useStore(selectSettings.activeServer)
|
||||
const setActiveServer = useStore(selectSettings.setActiveServer)
|
||||
const servers = useStore(selectSettings.servers)
|
||||
const setServers = useStore(selectSettings.setServers)
|
||||
const addServer = useStore(selectSettings.addServer)
|
||||
const updateServer = useStore(selectSettings.updateServer)
|
||||
const removeServer = useStore(selectSettings.removeServer)
|
||||
const server = id ? servers.find(s => s.id === id) : undefined
|
||||
const pingServer = useStore(selectSettings.pingServer)
|
||||
|
||||
const [address, setAddress] = useState(server?.address || '')
|
||||
const [username, setUsername] = useState(server?.username || '')
|
||||
const [password, setPassword] = useState(server?.token ? 'password' : '')
|
||||
const [testing, setTesting] = useState(false)
|
||||
const [removing, setRemoving] = useState(false)
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
const validate = useCallback(() => {
|
||||
return !!address && !!username && !!password
|
||||
@ -47,11 +46,7 @@ const ServerView: React.FC<{
|
||||
}
|
||||
}, [navigation])
|
||||
|
||||
const save = useCallback(() => {
|
||||
if (!validate()) {
|
||||
return
|
||||
}
|
||||
|
||||
const createServer = useCallback<() => Server>(() => {
|
||||
const salt = server?.salt || uuidv4()
|
||||
let token: string
|
||||
if (password === 'password' && server?.token) {
|
||||
@ -60,47 +55,76 @@ const ServerView: React.FC<{
|
||||
token = md5(password + salt)
|
||||
}
|
||||
|
||||
const update: Server = {
|
||||
return {
|
||||
id: server?.id || uuidv4(),
|
||||
address,
|
||||
username,
|
||||
salt,
|
||||
token,
|
||||
}
|
||||
}, [address, password, server?.id, server?.salt, server?.token, username])
|
||||
|
||||
if (server) {
|
||||
setServers(
|
||||
replaceIndex(
|
||||
servers,
|
||||
servers.findIndex(s => s.id === id),
|
||||
update,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
setServers([...servers, update])
|
||||
const save = useCallback(() => {
|
||||
if (!validate()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!activeServer) {
|
||||
setActiveServer(update.id)
|
||||
}
|
||||
setSaving(true)
|
||||
const update = createServer()
|
||||
|
||||
exit()
|
||||
}, [activeServer, address, exit, id, password, server, servers, setActiveServer, setServers, username, validate])
|
||||
const waitForSave = async () => {
|
||||
try {
|
||||
if (id) {
|
||||
updateServer(update)
|
||||
} else {
|
||||
await addServer(update)
|
||||
}
|
||||
exit()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
waitForSave()
|
||||
}, [addServer, createServer, exit, id, updateServer, validate])
|
||||
|
||||
const remove = useCallback(() => {
|
||||
if (!canRemove()) {
|
||||
return
|
||||
}
|
||||
|
||||
const update = [...servers]
|
||||
update.splice(
|
||||
update.findIndex(s => s.id === id),
|
||||
1,
|
||||
)
|
||||
setRemoving(true)
|
||||
const waitForRemove = async () => {
|
||||
try {
|
||||
await removeServer(id as string)
|
||||
exit()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
setRemoving(false)
|
||||
}
|
||||
}
|
||||
waitForRemove()
|
||||
}, [canRemove, exit, id, removeServer])
|
||||
|
||||
setServers(update)
|
||||
exit()
|
||||
}, [canRemove, exit, id, servers, setServers])
|
||||
const test = useCallback(() => {
|
||||
setTesting(true)
|
||||
const potential = createServer()
|
||||
|
||||
const ping = async () => {
|
||||
const res = await pingServer(potential)
|
||||
if (res) {
|
||||
ToastAndroid.show(`Connection to ${potential.address} OK!`, ToastAndroid.SHORT)
|
||||
} else {
|
||||
ToastAndroid.show(`Connection to ${potential.address} failed, check settings or server`, ToastAndroid.SHORT)
|
||||
}
|
||||
setTesting(false)
|
||||
}
|
||||
ping()
|
||||
}, [createServer, pingServer, setTesting])
|
||||
|
||||
const disableControls = useCallback(() => {
|
||||
return !validate() || testing || removing || saving
|
||||
}, [validate, testing, removing, saving])
|
||||
|
||||
return (
|
||||
<GradientScrollView style={styles.scroll} contentContainerStyle={styles.scrollContentContainer}>
|
||||
@ -139,18 +163,19 @@ const ServerView: React.FC<{
|
||||
onChangeText={setPassword}
|
||||
/>
|
||||
<Button
|
||||
disabled={!validate()}
|
||||
disabled={disableControls()}
|
||||
style={styles.button}
|
||||
title="Test Connection"
|
||||
buttonStyle="hollow"
|
||||
onPress={() => {}}
|
||||
onPress={test}
|
||||
/>
|
||||
<Button
|
||||
disabled={disableControls()}
|
||||
style={[styles.button, styles.delete, { display: canRemove() ? 'flex' : 'none' }]}
|
||||
title="Delete"
|
||||
onPress={remove}
|
||||
/>
|
||||
<Button disabled={!validate()} style={styles.button} title="Save" onPress={save} />
|
||||
<Button disabled={disableControls()} style={styles.button} title="Save" onPress={save} />
|
||||
</View>
|
||||
</GradientScrollView>
|
||||
)
|
||||
|
||||
@ -1,23 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Text, View } from 'react-native'
|
||||
import RNFS from 'react-native-fs'
|
||||
import paths from '@app/util/paths'
|
||||
import { Store, useStore } from '@app/state/store'
|
||||
|
||||
async function mkdir(path: string): Promise<void> {
|
||||
const exists = await RNFS.exists(path)
|
||||
if (exists) {
|
||||
const isDir = (await RNFS.stat(path)).isDirectory()
|
||||
if (!isDir) {
|
||||
throw new Error(`path exists and is not a directory: ${path}`)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return await RNFS.mkdir(path)
|
||||
}
|
||||
|
||||
const selectHydrated = (store: Store) => store.hydrated
|
||||
|
||||
const SplashPage: React.FC<{}> = ({ children }) => {
|
||||
@ -27,9 +11,7 @@ const SplashPage: React.FC<{}> = ({ children }) => {
|
||||
const minSplashTime = new Promise(resolve => setTimeout(resolve, 1))
|
||||
|
||||
const prepare = async () => {
|
||||
await mkdir(paths.imageCache)
|
||||
await mkdir(paths.songCache)
|
||||
await mkdir(paths.songs)
|
||||
return
|
||||
}
|
||||
|
||||
const promise = Promise.all([prepare(), minSplashTime])
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { CacheFile, CacheItemTypeKey, CacheRequest } from '@app/models/music'
|
||||
import { CacheFile, CacheItemType, CacheItemTypeKey, CacheRequest } from '@app/models/music'
|
||||
import { mkdir, rmdir } from '@app/util/fs'
|
||||
import PromiseQueue from '@app/util/PromiseQueue'
|
||||
import produce from 'immer'
|
||||
import RNFS from 'react-native-fs'
|
||||
@ -33,6 +34,11 @@ export type CacheSlice = {
|
||||
cacheRequests: CacheRequestsByServer
|
||||
|
||||
fetchCoverArtFilePath: (coverArt: string) => Promise<string | undefined>
|
||||
|
||||
createCache: (serverId: string) => Promise<void>
|
||||
prepareCache: (serverId: string) => void
|
||||
pendingRemoval: Record<string, boolean>
|
||||
removeCache: (serverId: string) => Promise<void>
|
||||
}
|
||||
|
||||
export const selectCache = {
|
||||
@ -66,6 +72,10 @@ export const createCacheSlice = (set: SetState<Store>, get: GetState<Store>): Ca
|
||||
return
|
||||
}
|
||||
|
||||
if (get().pendingRemoval[activeServerId]) {
|
||||
return
|
||||
}
|
||||
|
||||
const inProgress = get().cacheRequests[activeServerId][key][itemId]
|
||||
if (inProgress && inProgress.promise !== undefined) {
|
||||
return await inProgress.promise
|
||||
@ -154,4 +164,82 @@ export const createCacheSlice = (set: SetState<Store>, get: GetState<Store>): Ca
|
||||
await get().cacheItem('coverArt', coverArt, () => client.getCoverArtUri({ id: coverArt }))
|
||||
return `file://${get().cacheFiles[activeServerId].coverArt[coverArt].path}`
|
||||
},
|
||||
|
||||
createCache: async serverId => {
|
||||
for (const type in CacheItemType) {
|
||||
await mkdir(`${RNFS.DocumentDirectoryPath}/servers/${serverId}/${type}`)
|
||||
}
|
||||
|
||||
set(
|
||||
produce<CacheSlice>(state => {
|
||||
state.cacheFiles[serverId] = {
|
||||
song: {},
|
||||
coverArt: {},
|
||||
artistArt: {},
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
get().prepareCache(serverId)
|
||||
},
|
||||
|
||||
prepareCache: serverId => {
|
||||
set(
|
||||
produce<CacheSlice>(state => {
|
||||
if (!state.cacheDirs[serverId]) {
|
||||
state.cacheDirs[serverId] = {
|
||||
song: `${RNFS.DocumentDirectoryPath}/servers/${serverId}/song`,
|
||||
coverArt: `${RNFS.DocumentDirectoryPath}/servers/${serverId}/coverArt`,
|
||||
artistArt: `${RNFS.DocumentDirectoryPath}/servers/${serverId}/artistArt`,
|
||||
}
|
||||
}
|
||||
if (!state.cacheRequests[serverId]) {
|
||||
state.cacheRequests[serverId] = {
|
||||
song: {},
|
||||
coverArt: {},
|
||||
artistArt: {},
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
||||
pendingRemoval: {},
|
||||
|
||||
removeCache: async serverId => {
|
||||
set(
|
||||
produce<CacheSlice>(state => {
|
||||
state.pendingRemoval[serverId] = true
|
||||
}),
|
||||
)
|
||||
|
||||
const cacheRequests = get().cacheRequests[serverId]
|
||||
const pendingRequests: Promise<void>[] = []
|
||||
|
||||
for (const type in CacheItemType) {
|
||||
const requests = Object.values(cacheRequests[type as CacheItemTypeKey])
|
||||
.filter(r => r.promise !== undefined)
|
||||
.map(r => r.promise) as Promise<void>[]
|
||||
pendingRequests.push(...requests)
|
||||
}
|
||||
|
||||
await Promise.all(pendingRequests)
|
||||
await rmdir(`${RNFS.DocumentDirectoryPath}/servers/${serverId}`)
|
||||
|
||||
set(
|
||||
produce<CacheSlice>(state => {
|
||||
delete state.pendingRemoval[serverId]
|
||||
|
||||
if (state.cacheDirs[serverId]) {
|
||||
delete state.cacheDirs[serverId]
|
||||
}
|
||||
if (state.cacheFiles[serverId]) {
|
||||
delete state.cacheFiles[serverId]
|
||||
}
|
||||
if (state.cacheRequests[serverId]) {
|
||||
delete state.cacheRequests[serverId]
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
@ -1,37 +1,25 @@
|
||||
import { CacheItemType } from '@app/models/music'
|
||||
import { AppSettings, Server } from '@app/models/settings'
|
||||
import { Store } from '@app/state/store'
|
||||
import { SubsonicApiClient } from '@app/subsonic/api'
|
||||
import produce from 'immer'
|
||||
import RNFS from 'react-native-fs'
|
||||
import { GetState, SetState } from 'zustand'
|
||||
|
||||
async function mkdir(path: string): Promise<void> {
|
||||
const exists = await RNFS.exists(path)
|
||||
if (exists) {
|
||||
const isDir = (await RNFS.stat(path)).isDirectory()
|
||||
if (!isDir) {
|
||||
throw new Error(`path exists and is not a directory: ${path}`)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return await RNFS.mkdir(path)
|
||||
}
|
||||
|
||||
export type SettingsSlice = {
|
||||
settings: AppSettings
|
||||
client?: SubsonicApiClient
|
||||
|
||||
setActiveServer: (id: string | undefined, force?: boolean) => Promise<void>
|
||||
getActiveServer: () => Server | undefined
|
||||
setServers: (servers: Server[]) => void
|
||||
addServer: (server: Server) => Promise<void>
|
||||
removeServer: (id: string) => Promise<void>
|
||||
updateServer: (server: Server) => void
|
||||
|
||||
setScrobble: (scrobble: boolean) => void
|
||||
setEstimateContentLength: (estimateContentLength: boolean) => void
|
||||
setMaxBitrateWifi: (maxBitrateWifi: number) => void
|
||||
setMaxBitrateMobile: (maxBitrateMobile: number) => void
|
||||
|
||||
pingServer: (server?: Server) => Promise<boolean>
|
||||
}
|
||||
|
||||
export const selectSettings = {
|
||||
@ -41,7 +29,9 @@ export const selectSettings = {
|
||||
setActiveServer: (state: SettingsSlice) => state.setActiveServer,
|
||||
|
||||
servers: (state: SettingsSlice) => state.settings.servers,
|
||||
setServers: (state: SettingsSlice) => state.setServers,
|
||||
addServer: (state: SettingsSlice) => state.addServer,
|
||||
removeServer: (state: SettingsSlice) => state.removeServer,
|
||||
updateServer: (state: SettingsSlice) => state.updateServer,
|
||||
|
||||
homeLists: (state: SettingsSlice) => state.settings.home.lists,
|
||||
|
||||
@ -55,6 +45,8 @@ export const selectSettings = {
|
||||
setMaxBitrateWifi: (state: SettingsSlice) => state.setMaxBitrateWifi,
|
||||
maxBitrateMobile: (state: SettingsSlice) => state.settings.maxBitrateMobile,
|
||||
setMaxBitrateMobile: (state: SettingsSlice) => state.setMaxBitrateMobile,
|
||||
|
||||
pingServer: (state: SettingsSlice) => state.pingServer,
|
||||
}
|
||||
|
||||
export const createSettingsSlice = (set: SetState<Store>, get: GetState<Store>): SettingsSlice => ({
|
||||
@ -84,50 +76,55 @@ export const createSettingsSlice = (set: SetState<Store>, get: GetState<Store>):
|
||||
return
|
||||
}
|
||||
|
||||
for (const type in CacheItemType) {
|
||||
await mkdir(`${RNFS.DocumentDirectoryPath}/servers/${id}/${type}`)
|
||||
}
|
||||
get().prepareCache(newActiveServer.id)
|
||||
|
||||
set(
|
||||
produce<Store>(state => {
|
||||
state.settings.activeServer = newActiveServer.id
|
||||
state.client = new SubsonicApiClient(newActiveServer)
|
||||
|
||||
if (!state.cacheDirs[newActiveServer.id]) {
|
||||
state.cacheDirs[newActiveServer.id] = {
|
||||
song: `${RNFS.DocumentDirectoryPath}/servers/${id}/song`,
|
||||
coverArt: `${RNFS.DocumentDirectoryPath}/servers/${id}/coverArt`,
|
||||
artistArt: `${RNFS.DocumentDirectoryPath}/servers/${id}/artistArt`,
|
||||
}
|
||||
}
|
||||
if (!state.cacheFiles[newActiveServer.id]) {
|
||||
state.cacheFiles[newActiveServer.id] = {
|
||||
song: {},
|
||||
coverArt: {},
|
||||
artistArt: {},
|
||||
}
|
||||
}
|
||||
if (!state.cacheRequests[newActiveServer.id]) {
|
||||
state.cacheRequests[newActiveServer.id] = {
|
||||
song: {},
|
||||
coverArt: {},
|
||||
artistArt: {},
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
||||
getActiveServer: () => get().settings.servers.find(s => s.id === get().settings.activeServer),
|
||||
|
||||
setServers: servers => {
|
||||
addServer: async server => {
|
||||
await get().createCache(server.id)
|
||||
|
||||
set(
|
||||
produce<SettingsSlice>(state => {
|
||||
state.settings.servers = servers
|
||||
state.settings.servers.push(server)
|
||||
}),
|
||||
)
|
||||
const activeServer = servers.find(s => s.id === get().settings.activeServer)
|
||||
get().setActiveServer(activeServer?.id)
|
||||
|
||||
if (get().settings.servers.length === 1) {
|
||||
get().setActiveServer(server.id)
|
||||
}
|
||||
},
|
||||
|
||||
removeServer: async id => {
|
||||
await get().removeCache(id)
|
||||
|
||||
set(
|
||||
produce<SettingsSlice>(state => {
|
||||
state.settings.servers = state.settings.servers.filter(s => s.id !== id)
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
||||
updateServer: server => {
|
||||
set(
|
||||
produce<SettingsSlice>(state => {
|
||||
state.settings.servers = replaceIndex(
|
||||
state.settings.servers,
|
||||
state.settings.servers.findIndex(s => s.id === server.id),
|
||||
server,
|
||||
)
|
||||
}),
|
||||
)
|
||||
if (get().settings.activeServer === server.id) {
|
||||
get().setActiveServer(server.id)
|
||||
}
|
||||
},
|
||||
|
||||
setScrobble: scrobble => {
|
||||
@ -168,4 +165,30 @@ export const createSettingsSlice = (set: SetState<Store>, get: GetState<Store>):
|
||||
get().rebuildQueue()
|
||||
}
|
||||
},
|
||||
|
||||
pingServer: async server => {
|
||||
let client: SubsonicApiClient
|
||||
if (server) {
|
||||
client = new SubsonicApiClient(server)
|
||||
} else {
|
||||
const currentClient = get().client
|
||||
if (!currentClient) {
|
||||
return false
|
||||
}
|
||||
client = currentClient
|
||||
}
|
||||
|
||||
try {
|
||||
await client.ping()
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function replaceIndex<T>(array: T[], index: number, replacement: T): T[] {
|
||||
const start = array.slice(0, index)
|
||||
const end = array.slice(index + 1)
|
||||
return [...start, replacement, ...end]
|
||||
}
|
||||
|
||||
@ -35,7 +35,6 @@ import {
|
||||
SubsonicResponse,
|
||||
} from '@app/subsonic/responses'
|
||||
import { Server } from '@app/models/settings'
|
||||
import paths from '@app/util/paths'
|
||||
import PromiseQueue from '@app/util/PromiseQueue'
|
||||
|
||||
export class SubsonicApiError extends Error {
|
||||
@ -212,11 +211,6 @@ export class SubsonicApiClient {
|
||||
// Media retrieval
|
||||
//
|
||||
|
||||
async getCoverArt(params: GetCoverArtParams): Promise<string> {
|
||||
const path = `${paths.songCache}/${params.id}`
|
||||
return await this.apiDownload('getCoverArt', path, params)
|
||||
}
|
||||
|
||||
getCoverArtUri(params?: GetCoverArtParams): string {
|
||||
return this.buildUrl('getCoverArt', params)
|
||||
}
|
||||
|
||||
19
app/util/fs.ts
Normal file
19
app/util/fs.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import RNFS from 'react-native-fs'
|
||||
|
||||
export async function mkdir(path: string): Promise<void> {
|
||||
const exists = await RNFS.exists(path)
|
||||
if (exists) {
|
||||
const isDir = (await RNFS.stat(path)).isDirectory()
|
||||
if (!isDir) {
|
||||
throw new Error(`path exists and is not a directory: ${path}`)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return await RNFS.mkdir(path)
|
||||
}
|
||||
|
||||
export async function rmdir(path: string): Promise<void> {
|
||||
return RNFS.unlink(path)
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
import RNFS from 'react-native-fs'
|
||||
|
||||
export default {
|
||||
imageCache: `${RNFS.DocumentDirectoryPath}/image_cache`,
|
||||
songCache: `${RNFS.DocumentDirectoryPath}/song_cache`,
|
||||
songs: `${RNFS.DocumentDirectoryPath}/songs`,
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user