mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-04-21 10:51:57 +00:00
feat: enable rescan and deletion
perf: in-world messageport
This commit is contained in:
parent
e7b168ca09
commit
ced4296fae
6 changed files with 1277 additions and 32 deletions
|
|
@ -42,6 +42,7 @@
|
||||||
"svelte-radix": "^1.1.1",
|
"svelte-radix": "^1.1.1",
|
||||||
"svelte-sonner": "^0.3.28",
|
"svelte-sonner": "^0.3.28",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
|
"torrent-client": "github:hayase-app/torrent-client",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
"vaul-svelte": "^0.3.2",
|
"vaul-svelte": "^0.3.2",
|
||||||
"vite": "^5.4.11",
|
"vite": "^5.4.11",
|
||||||
|
|
|
||||||
1193
pnpm-lock.yaml
1193
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -112,16 +112,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const { filterValue } = pluginStates.filter
|
const { filterValue } = pluginStates.filter
|
||||||
const { selectedDataIds } = pluginStates.select
|
const { selectedDataIds, someRowsSelected } = pluginStates.select
|
||||||
|
|
||||||
function getSelected () {
|
function getSelected () {
|
||||||
return Object.keys($selectedDataIds).map(id => $lib[id as unknown as number]?.hash).filter(e => e) as string[]
|
return Object.keys($selectedDataIds).map(id => $lib[id as unknown as number]?.hash).filter(e => e) as string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: enable
|
|
||||||
function rescanTorrents () {
|
function rescanTorrents () {
|
||||||
return null
|
|
||||||
// eslint-disable-next-line no-unreachable
|
|
||||||
toast.promise(native.rescanTorrents(getSelected()), {
|
toast.promise(native.rescanTorrents(getSelected()), {
|
||||||
loading: 'Rescanning torrents...',
|
loading: 'Rescanning torrents...',
|
||||||
success: 'Rescan complete',
|
success: 'Rescan complete',
|
||||||
|
|
@ -129,21 +126,23 @@
|
||||||
console.error(e)
|
console.error(e)
|
||||||
return 'Failed to rescan torrents\n' + ('stack' in (e as object) ? (e as Error).stack : 'Unknown error')
|
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 () {
|
function deleteTorrents () {
|
||||||
return null
|
toast.promise(
|
||||||
// eslint-disable-next-line no-unreachable
|
native.deleteTorrents(getSelected())
|
||||||
toast.promise(native.deleteTorrents(getSelected()), {
|
.then(() => server.updateLibrary()
|
||||||
loading: 'Deleting torrents...',
|
.then(() => pluginStates.select.allPageRowsSelected.set(false))
|
||||||
success: 'Torrents deleted',
|
), {
|
||||||
error: e => {
|
loading: 'Deleting torrents...',
|
||||||
console.error(e)
|
success: 'Torrents deleted',
|
||||||
return 'Failed to delete torrents\n' + ('stack' in (e as object) ? (e as Error).stack : 'Unknown error')
|
error: e => {
|
||||||
},
|
console.error(e)
|
||||||
description: 'This may take a while depending on the number of torrents.'
|
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
|
// TODO once new resolver is implemented
|
||||||
|
|
@ -158,10 +157,10 @@
|
||||||
bind:value={$filterValue} />
|
bind:value={$filterValue} />
|
||||||
<MagnifyingGlass class='h-4 w-4 shrink-0 opacity-50 absolute left-3 text-muted-foreground z-10 pointer-events-none' />
|
<MagnifyingGlass class='h-4 w-4 shrink-0 opacity-50 absolute left-3 text-muted-foreground z-10 pointer-events-none' />
|
||||||
</div>
|
</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')} />
|
<FolderSync class={cn('size-4')} />
|
||||||
</Button>
|
</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')} />
|
<Trash class={cn('size-4')} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import SUPPORTS from './settings/supports'
|
||||||
|
|
||||||
import type { AuthResponse, Native, TorrentInfo } from 'native'
|
import type { AuthResponse, Native, TorrentInfo } from 'native'
|
||||||
|
|
||||||
|
import electron from '$lib/modules/torrent/electron'
|
||||||
import { sleep } from '$lib/utils'
|
import { sleep } from '$lib/utils'
|
||||||
|
|
||||||
const rnd = (range = 100) => Math.floor(Math.random() * range)
|
const rnd = (range = 100) => Math.floor(Math.random() * range)
|
||||||
|
|
@ -50,7 +51,7 @@ const dummyFiles = [
|
||||||
// dummyPeerInfo.push(makeRandomPeer())
|
// dummyPeerInfo.push(makeRandomPeer())
|
||||||
// }
|
// }
|
||||||
|
|
||||||
export default Object.assign<Native, Partial<Native>>({
|
const native: Native = {
|
||||||
authAL: (url: string) => {
|
authAL: (url: string) => {
|
||||||
return new Promise<AuthResponse>((resolve, reject) => {
|
return new Promise<AuthResponse>((resolve, reject) => {
|
||||||
const popup = open(url, 'authframe', SUPPORTS.isAndroid ? 'popup' : 'popup,width=382,height=582')
|
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',
|
version: async () => 'v6.4.4',
|
||||||
updateSettings: async () => undefined,
|
updateSettings: async () => undefined,
|
||||||
setDOH: async () => undefined,
|
setDOH: async () => undefined,
|
||||||
cachedTorrents: async () => ['40a9047de61859035659e449d7b84286934486b0'],
|
cachedTorrents: async () => [],
|
||||||
spawnPlayer: () => sleep(rnd(100_000)),
|
spawnPlayer: async () => undefined,
|
||||||
setHideToTray: async () => undefined,
|
setHideToTray: async () => undefined,
|
||||||
transparency: async () => undefined,
|
transparency: async () => undefined,
|
||||||
setZoom: async () => undefined,
|
setZoom: async () => undefined,
|
||||||
|
|
@ -162,6 +163,10 @@ export default Object.assign<Native, Partial<Native>>({
|
||||||
defaultTransparency: () => false,
|
defaultTransparency: () => false,
|
||||||
errors: async () => undefined,
|
errors: async () => undefined,
|
||||||
debug: async () => undefined,
|
debug: async () => undefined,
|
||||||
profile: async () => undefined
|
profile: async () => undefined,
|
||||||
// @ts-expect-error idk
|
// @ts-expect-error idk
|
||||||
}, globalThis.native as Partial<Native>)
|
...globalThis.native as Partial<Native>,
|
||||||
|
...electron
|
||||||
|
}
|
||||||
|
|
||||||
|
export default native
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import Debug from 'debug'
|
import Debug from 'debug'
|
||||||
import { readable, writable } from 'simple-store-svelte'
|
import { writable } from 'simple-store-svelte'
|
||||||
import { get } from 'svelte/store'
|
import { get } from 'svelte/store'
|
||||||
import { persisted } from 'svelte-persisted-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>>()
|
active = writable<Promise<{ media: Media, id: string, episode: number, files: TorrentFile[] } | null>>()
|
||||||
downloaded = writable(this.cachedSet())
|
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) {
|
_timedSafeStore<T> (defaultData: T, fn: (id: string) => Promise<T>, duration = SUPPORTS.isUnderPowered ? 15000 : 5000) {
|
||||||
return readable<T>(defaultData, set => {
|
return writable<T>(defaultData, set => {
|
||||||
let listener = 0
|
let listener = 0
|
||||||
|
|
||||||
const update = async () => {
|
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 () {
|
async cachedSet () {
|
||||||
debug('fetching cached torrents')
|
debug('fetching cached torrents')
|
||||||
return new Set(await native.cachedTorrents())
|
return new Set(await native.cachedTorrents())
|
||||||
|
|
|
||||||
43
src/lib/modules/torrent/electron.ts
Normal file
43
src/lib/modules/torrent/electron.ts
Normal 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
|
||||||
Loading…
Reference in a new issue