feat: enable rescan and deletion

perf: in-world messageport
This commit is contained in:
ThaUnknown 2025-09-02 15:55:10 +02:00
parent e7b168ca09
commit ced4296fae
No known key found for this signature in database
6 changed files with 1277 additions and 32 deletions

View file

@ -42,6 +42,7 @@
"svelte-radix": "^1.1.1",
"svelte-sonner": "^0.3.28",
"tailwindcss": "^3.4.17",
"torrent-client": "github:hayase-app/torrent-client",
"typescript": "^5.9.2",
"vaul-svelte": "^0.3.2",
"vite": "^5.4.11",

File diff suppressed because it is too large Load diff

View file

@ -112,16 +112,13 @@
}
const { filterValue } = pluginStates.filter
const { selectedDataIds } = pluginStates.select
const { selectedDataIds, someRowsSelected } = pluginStates.select
function getSelected () {
return Object.keys($selectedDataIds).map(id => $lib[id as unknown as number]?.hash).filter(e => e) as string[]
}
// TODO: enable
function rescanTorrents () {
return null
// eslint-disable-next-line no-unreachable
toast.promise(native.rescanTorrents(getSelected()), {
loading: 'Rescanning torrents...',
success: 'Rescan complete',
@ -129,21 +126,23 @@
console.error(e)
return 'Failed to rescan torrents\n' + ('stack' in (e as object) ? (e as Error).stack : 'Unknown error')
},
description: 'This may take a long while depending on the number of torrents.'
description: 'This may take a VERY long while depending on the number of torrents.'
})
}
function deleteTorrents () {
return null
// eslint-disable-next-line no-unreachable
toast.promise(native.deleteTorrents(getSelected()), {
loading: 'Deleting torrents...',
success: 'Torrents deleted',
error: e => {
console.error(e)
return 'Failed to delete torrents\n' + ('stack' in (e as object) ? (e as Error).stack : 'Unknown error')
},
description: 'This may take a while depending on the number of torrents.'
})
toast.promise(
native.deleteTorrents(getSelected())
.then(() => server.updateLibrary()
.then(() => pluginStates.select.allPageRowsSelected.set(false))
), {
loading: 'Deleting torrents...',
success: 'Torrents deleted',
error: e => {
console.error(e)
return 'Failed to delete torrents\n' + ('stack' in (e as object) ? (e as Error).stack : 'Unknown error')
},
description: 'This may take a while depending on the library size.'
})
}
// TODO once new resolver is implemented
@ -158,10 +157,10 @@
bind:value={$filterValue} />
<MagnifyingGlass class='h-4 w-4 shrink-0 opacity-50 absolute left-3 text-muted-foreground z-10 pointer-events-none' />
</div>
<Button variant='secondary' size='icon' class='border-0 animated-icon !pointer-events-auto cursor-not-allowed' disabled on:click={rescanTorrents}>
<Button variant='secondary' size='icon' class='border-0 animated-icon' on:click={rescanTorrents} disabled={!$someRowsSelected}>
<FolderSync class={cn('size-4')} />
</Button>
<Button variant='destructive' size='icon' class='border-0 animated-icon !pointer-events-auto cursor-not-allowed' disabled on:click={deleteTorrents}>
<Button variant='destructive' size='icon' class='border-0 animated-icon' on:click={deleteTorrents} disabled={!$someRowsSelected}>
<Trash class={cn('size-4')} />
</Button>
</div>

View file

@ -2,6 +2,7 @@ import SUPPORTS from './settings/supports'
import type { AuthResponse, Native, TorrentInfo } from 'native'
import electron from '$lib/modules/torrent/electron'
import { sleep } from '$lib/utils'
const rnd = (range = 100) => Math.floor(Math.random() * range)
@ -50,7 +51,7 @@ const dummyFiles = [
// dummyPeerInfo.push(makeRandomPeer())
// }
export default Object.assign<Native, Partial<Native>>({
const native: Native = {
authAL: (url: string) => {
return new Promise<AuthResponse>((resolve, reject) => {
const popup = open(url, 'authframe', SUPPORTS.isAndroid ? 'popup' : 'popup,width=382,height=582')
@ -130,8 +131,8 @@ export default Object.assign<Native, Partial<Native>>({
version: async () => 'v6.4.4',
updateSettings: async () => undefined,
setDOH: async () => undefined,
cachedTorrents: async () => ['40a9047de61859035659e449d7b84286934486b0'],
spawnPlayer: () => sleep(rnd(100_000)),
cachedTorrents: async () => [],
spawnPlayer: async () => undefined,
setHideToTray: async () => undefined,
transparency: async () => undefined,
setZoom: async () => undefined,
@ -162,6 +163,10 @@ export default Object.assign<Native, Partial<Native>>({
defaultTransparency: () => false,
errors: async () => undefined,
debug: async () => undefined,
profile: async () => undefined
profile: async () => undefined,
// @ts-expect-error idk
}, globalThis.native as Partial<Native>)
...globalThis.native as Partial<Native>,
...electron
}
export default native

View file

@ -1,5 +1,5 @@
import Debug from 'debug'
import { readable, writable } from 'simple-store-svelte'
import { writable } from 'simple-store-svelte'
import { get } from 'svelte/store'
import { persisted } from 'svelte-persisted-store'
@ -30,18 +30,18 @@ export const server = new class ServerClient {
active = writable<Promise<{ media: Media, id: string, episode: number, files: TorrentFile[] } | null>>()
downloaded = writable(this.cachedSet())
stats = this._timedSafeReadable(defaultTorrentInfo, native.torrentInfo, SUPPORTS.isUnderPowered ? 3000 : 200)
stats = this._timedSafeStore(defaultTorrentInfo, native.torrentInfo, SUPPORTS.isUnderPowered ? 3000 : 200)
protocol = this._timedSafeReadable(defaultProtocolStatus, native.protocolStatus)
protocol = this._timedSafeStore(defaultProtocolStatus, native.protocolStatus)
peers = this._timedSafeReadable([], native.peerInfo)
peers = this._timedSafeStore([], native.peerInfo)
files = this._timedSafeReadable([], native.fileInfo)
files = this._timedSafeStore([], native.fileInfo)
library = this._timedSafeReadable([], native.library, 120_000)
library = this._timedSafeStore([], native.library, 120_000)
_timedSafeReadable<T> (defaultData: T, fn: (id: string) => Promise<T>, duration = SUPPORTS.isUnderPowered ? 15000 : 5000) {
return readable<T>(defaultData, set => {
_timedSafeStore<T> (defaultData: T, fn: (id: string) => Promise<T>, duration = SUPPORTS.isUnderPowered ? 15000 : 5000) {
return writable<T>(defaultData, set => {
let listener = 0
const update = async () => {
@ -71,6 +71,12 @@ export const server = new class ServerClient {
})
}
async updateLibrary () {
const library = native.library()
this.downloaded.value = library.then(lib => new Set(lib.map(({ hash }) => hash)))
this.library.value = await library
}
async cachedSet () {
debug('fetching cached torrents')
return new Set(await native.cachedTorrents())

View file

@ -0,0 +1,43 @@
import { proxy, type Remote } from 'abslink'
import { wrap } from 'abslink/w3c'
import type { Native } from 'native'
import type TorrentClient from 'torrent-client'
const torrent = new Promise<Remote<TorrentClient>>(resolve => {
window.addEventListener('message', ({ data, ports, source }) => {
if (data === 'TORRENT_PORT' && ports[0] && source === window) {
ports[0].start()
resolve(wrap<TorrentClient>(ports[0]) as unknown as Remote<TorrentClient>)
}
})
// @ts-expect-error custom
window.sendPort?.()
})
const electronNative: Partial<Native> =
// @ts-expect-error custom
window.sendPort
? {
checkAvailableSpace: async () => await (await torrent).checkAvailableSpace(),
checkIncomingConnections: async (port) => await (await torrent).checkIncomingConnections(port),
updatePeerCounts: async (hashes) => await (await torrent).scrape(hashes),
playTorrent: async (id, mediaID, episode) => await (await torrent).playTorrent(id, mediaID, episode),
rescanTorrents: async (hashes) => await (await torrent).rescanTorrents(hashes),
deleteTorrents: async (hashes) => await (await torrent).deleteTorrents(hashes),
library: async () => await (await torrent).library(),
attachments: async (hash, id) => await (await torrent).attachments.attachments(hash, id),
tracks: async (hash, id) => await (await torrent).attachments.tracks(hash, id),
subtitles: async (hash, id, cb) => await (await torrent).attachments.subtitle(hash, id, proxy(cb)),
errors: async (cb) => await (await torrent).errors(proxy(cb)),
chapters: async (hash, id) => await (await torrent).attachments.chapters(hash, id),
torrentInfo: async (hash) => await (await torrent).torrentInfo(hash),
peerInfo: async (hash) => await (await torrent).peerInfo(hash),
fileInfo: async (hash) => await (await torrent).fileInfo(hash),
protocolStatus: async (hash) => await (await torrent).protocolStatus(hash),
cachedTorrents: async () => await (await torrent).cached(),
debug: async (levels) => await (await torrent).debug(levels)
}
: {}
export default electronNative