fix: torrent client sometimes loading without settings on android

feat: persist files on android
feat: custom download directory on android
This commit is contained in:
ThaUnknown 2024-08-22 02:03:16 +02:00
parent c595dd7224
commit 94f8fafc6b
9 changed files with 442 additions and 535 deletions

View file

@ -54,6 +54,8 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-feature android:name="android.software.leanback" android:required="false" /> <uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" /> <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
</manifest> </manifest>

View file

@ -18,24 +18,25 @@
}, },
"devDependencies": { "devDependencies": {
"@capacitor/assets": "github:thaunknown/capacitor-assets", "@capacitor/assets": "github:thaunknown/capacitor-assets",
"@capacitor/cli": "^6.1.1", "@capacitor/cli": "^6.1.2",
"cordova-res": "^0.15.4", "cordova-res": "^0.15.4",
"nodejs-mobile-gyp": "^0.3.1", "nodejs-mobile-gyp": "^0.4.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"webpack-cli": "^5.1.4", "webpack-cli": "^5.1.4",
"webpack-merge": "^5.10.0" "webpack-merge": "^6.0.1"
}, },
"dependencies": { "dependencies": {
"@capacitor/android": "^6.1.1", "@capacitor/android": "^6.1.2",
"@capacitor/app": "^6.0.0", "@capacitor/app": "^6.0.1",
"@capacitor/browser": "^6.0.1", "@capacitor/browser": "^6.0.2",
"@capacitor/core": "^6.1.1", "@capacitor/core": "^6.1.2",
"@capacitor/device": "^6.0.1", "@capacitor/device": "^6.0.1",
"@capacitor/ios": "^6.1.1", "@capacitor/ios": "^6.1.2",
"@capacitor/local-notifications": "^6.0.0", "@capacitor/local-notifications": "^6.1.0",
"@capacitor/status-bar": "^6.0.0", "@capacitor/status-bar": "^6.0.1",
"capacitor-folder-picker": "^0.0.2",
"capacitor-nodejs": "https://github.com/funniray/Capacitor-NodeJS/releases/download/nodejs-18/capacitor-nodejs-1.0.0-beta.6.tgz", "capacitor-nodejs": "https://github.com/funniray/Capacitor-NodeJS/releases/download/nodejs-18/capacitor-nodejs-1.0.0-beta.6.tgz",
"capacitor-plugin-safe-area": "^2.0.6", "capacitor-plugin-safe-area": "^3.0.3",
"common": "workspace:*", "common": "workspace:*",
"cordova-plugin-navigationbar": "^1.0.31", "cordova-plugin-navigationbar": "^1.0.31",
"cordova-plugin-pip": "^0.0.2", "cordova-plugin-pip": "^0.0.2",

View file

@ -5,6 +5,8 @@ import { App } from '@capacitor/app'
import { Browser } from '@capacitor/browser' import { Browser } from '@capacitor/browser'
import { LocalNotifications } from '@capacitor/local-notifications' import { LocalNotifications } from '@capacitor/local-notifications'
import { Device } from '@capacitor/device' import { Device } from '@capacitor/device'
import { FolderPicker } from 'capacitor-folder-picker'
import { toast } from 'svelte-sonner'
import IPC from './ipc.js' import IPC from './ipc.js'
IPC.on('open', url => Browser.open({ url })) IPC.on('open', url => Browser.open({ url }))
@ -51,6 +53,30 @@ IPC.on('get-device-info', async () => {
IPC.emit('device-info', JSON.stringify(deviceInfo)) IPC.emit('device-info', JSON.stringify(deviceInfo))
}) })
const STORAGE_TYPE_MAP = {
primary: '/sdcard/',
secondary: '/sdcard/'
}
IPC.on('dialog', async () => {
const result = await FolderPicker.chooseFolder()
const normalizedPath = decodeURIComponent(result.path)
const [, uri, ...path] = normalizedPath.split(':')
const [,, app, subpath, type, ...rest] = uri.split('/')
if (app !== 'com.android.externalstorage.documents') return toast.error('Unverified app', { description: 'Expected com.android.externalstorage.documents, got: ' + app })
if (rest.length) return toast.error('Unsupported uri', { description: 'Unxpected access type, got: tree/' + rest.join('/') })
if (subpath !== 'tree') return toast.error('Unsupported subpath type', { description: 'Expected tree subpath, got: ' + subpath })
let base = STORAGE_TYPE_MAP[type]
if (!base) {
if (!/[a-z0-9]{4}-[a-z0-9]{4}/i.test(type)) return toast.error('Unsupported storage type')
base = `/storage/${type}/`
}
IPC.emit('path', base + path.join(''))
})
// schema: miru://key/value // schema: miru://key/value
const protocolMap = { const protocolMap = {
auth: token => sendToken(token), auth: token => sendToken(token),

View file

@ -24,11 +24,16 @@ channel.on('port-init', data => {
channel.send('ipc', { data }) channel.send('ipc', { data })
} }
} }
let storedSettings = {}
try {
storedSettings = JSON.parse(localStorage.getItem('settings')) || {}
} catch (error) {}
if (!globalThis.client) globalThis.client = new TorrentClient(channel, storageQuota, 'node', storedSettings.torrentPathNew || env.TMPDIR)
channel.on('ipc', a => port.onmessage(a)) channel.on('ipc', a => port.onmessage(a))
channel.emit('port', { channel.emit('port', {
ports: [port] ports: [port]
}) })
}) })
globalThis.client = new TorrentClient(channel, storageQuota, 'node', env.TMPDIR)

View file

@ -5,11 +5,7 @@ export const SUPPORTS = {
update: false, update: false,
angle: false, angle: false,
doh: false, doh: false,
dht: true,
discord: false, discord: false,
torrentPort: true,
torrentPath: false,
torrentPersist: false,
keybinds: false, keybinds: false,
isAndroid: true, isAndroid: true,
externalPlayer: false, externalPlayer: false,

View file

@ -5,11 +5,7 @@ export const SUPPORTS = {
update: true, update: true,
angle: true, angle: true,
doh: true, doh: true,
dht: true,
discord: true, discord: true,
torrentPort: true,
torrentPath: true,
torrentPersist: true,
keybinds: true, keybinds: true,
extensions: true, extensions: true,
isAndroid: false, isAndroid: false,

View file

@ -37,12 +37,6 @@ const ANNOUNCE = [
atob('aHR0cDovL3RyYWNrZXIuYW5pcmVuYS5jb206ODAvYW5ub3VuY2U=') atob('aHR0cDovL3RyYWNrZXIuYW5pcmVuYS5jb206ODAvYW5ub3VuY2U=')
] ]
let storedSettings = {}
try {
storedSettings = JSON.parse(localStorage.getItem('settings')) || {}
} catch (error) {}
export default class TorrentClient extends WebTorrent { export default class TorrentClient extends WebTorrent {
static excludedErrorMessages = ['WebSocket', 'User-Initiated Abort, reason=', 'Connection failed.'] static excludedErrorMessages = ['WebSocket', 'User-Initiated Abort, reason=', 'Connection failed.']
@ -54,6 +48,12 @@ export default class TorrentClient extends WebTorrent {
ipc ipc
constructor (ipc, storageQuota, serverMode, torrentPath, controller) { constructor (ipc, storageQuota, serverMode, torrentPath, controller) {
let storedSettings = {}
try {
storedSettings = JSON.parse(localStorage.getItem('settings')) || {}
} catch (error) {}
const settings = { ...defaults, ...storedSettings } const settings = { ...defaults, ...storedSettings }
debug('Initializing TorrentClient with settings: ' + JSON.stringify(settings)) debug('Initializing TorrentClient with settings: ' + JSON.stringify(settings))
super({ super({

View file

@ -140,25 +140,25 @@
{/if} {/if}
<h4 class='mb-10 font-weight-bold'>Client Settings</h4> <h4 class='mb-10 font-weight-bold'>Client Settings</h4>
{#if SUPPORTS.torrentPath} <SettingCard title='Torrent Download Location' description='Path to the folder used to store torrents. By default this is the TMP folder, which might lose data when your OS tries to reclaim storage. {SUPPORTS.isAndroid ? 'RESTART IS REQUIRED. /sdcard/ is internal storage, not external SD Cards. /storage/AB12-34CD/ is external storage, not internal. Thank you Android!' : ''}'>
<SettingCard title='Torrent Download Location' description='Path to the folder used to store torrents. By default this is the TMP folder, which might lose data when your OS tries to reclaim storage.'> <div
<div class='input-group w-300 mw-full'>
class='input-group w-300 mw-full'> <div class='input-group-prepend'>
<div class='input-group-prepend'> <button type='button' use:click={handleFolder} class='btn btn-primary input-group-append'>Select Folder</button>
<button type='button' use:click={handleFolder} class='btn btn-primary input-group-append'>Select Folder</button>
</div>
<input type='url' class='form-control bg-dark' readonly value={settings.torrentPathNew} placeholder='/tmp' />
</div> </div>
</SettingCard> {#if !SUPPORTS.isAndroid}
{/if} <input type='url' class='form-control bg-dark' readonly bind:value={settings.torrentPathNew} placeholder='/tmp' />
{#if SUPPORTS.torrentPersist} {:else}
<SettingCard title='Persist Files' description="Keeps torrents files instead of deleting them after a new torrent is played. This doesn't seed the files, only keeps them on your drive. This will quickly fill up your storage."> <input type='text' class='form-control bg-dark' bind:value={settings.torrentPathNew} placeholder='/tmp' />
<div class='custom-switch'> {/if}
<input type='checkbox' id='torrent-persist' bind:checked={settings.torrentPersist} /> </div>
<label for='torrent-persist'>{settings.torrentPersist ? 'On' : 'Off'}</label> </SettingCard>
</div> <SettingCard title='Persist Files' description="Keeps torrents files instead of deleting them after a new torrent is played. This doesn't seed the files, only keeps them on your drive. This will quickly fill up your storage.">
</SettingCard> <div class='custom-switch'>
{/if} <input type='checkbox' id='torrent-persist' bind:checked={settings.torrentPersist} />
<label for='torrent-persist'>{settings.torrentPersist ? 'On' : 'Off'}</label>
</div>
</SettingCard>
<SettingCard title='Streamed Download' description="Only downloads the single file that's currently being watched, instead of downloading an entire batch of episodes. Saves bandwidth and reduces strain on the peer swarm."> <SettingCard title='Streamed Download' description="Only downloads the single file that's currently being watched, instead of downloading an entire batch of episodes. Saves bandwidth and reduces strain on the peer swarm.">
<div class='custom-switch'> <div class='custom-switch'>
<input type='checkbox' id='torrent-streamed-download' bind:checked={settings.torrentStreamedDownload} /> <input type='checkbox' id='torrent-streamed-download' bind:checked={settings.torrentStreamedDownload} />
@ -176,22 +176,18 @@
<SettingCard title='Max Number of Connections' description='Number of peers per torrent. Higher values will increase download speeds but might quickly fill up available ports if your ISP limits the maximum allowed number of open connections.'> <SettingCard title='Max Number of Connections' description='Number of peers per torrent. Higher values will increase download speeds but might quickly fill up available ports if your ISP limits the maximum allowed number of open connections.'>
<input type='number' inputmode='numeric' pattern='[0-9]*' bind:value={settings.maxConns} min='1' max='512' class='form-control text-right bg-dark w-100 mw-full' /> <input type='number' inputmode='numeric' pattern='[0-9]*' bind:value={settings.maxConns} min='1' max='512' class='form-control text-right bg-dark w-100 mw-full' />
</SettingCard> </SettingCard>
{#if SUPPORTS.torrentPort} <SettingCard title='Torrent Port' description='Port used for Torrent connections. 0 is automatic.'>
<SettingCard title='Torrent Port' description='Port used for Torrent connections. 0 is automatic.'> <input type='number' inputmode='numeric' pattern='[0-9]*' bind:value={settings.torrentPort} min='0' max='65536' class='form-control text-right bg-dark w-150 mw-full' />
<input type='number' inputmode='numeric' pattern='[0-9]*' bind:value={settings.torrentPort} min='0' max='65536' class='form-control text-right bg-dark w-150 mw-full' /> </SettingCard>
</SettingCard> <SettingCard title='DHT Port' description='Port used for DHT connections. 0 is automatic.'>
{/if} <input type='number' inputmode='numeric' pattern='[0-9]*' bind:value={settings.dhtPort} min='0' max='65536' class='form-control text-right bg-dark w-150 mw-full' />
{#if SUPPORTS.dht} </SettingCard>
<SettingCard title='DHT Port' description='Port used for DHT connections. 0 is automatic.'> <SettingCard title='Disable DHT' description='Disables Distributed Hash Tables for use in private trackers to improve privacy. Might greatly reduce the amount of discovered peers.'>
<input type='number' inputmode='numeric' pattern='[0-9]*' bind:value={settings.dhtPort} min='0' max='65536' class='form-control text-right bg-dark w-150 mw-full' /> <div class='custom-switch'>
</SettingCard> <input type='checkbox' id='torrent-dht' bind:checked={settings.torrentDHT} />
<SettingCard title='Disable DHT' description='Disables Distributed Hash Tables for use in private trackers to improve privacy. Might greatly reduce the amount of discovered peers.'> <label for='torrent-dht'>{settings.torrentDHT ? 'On' : 'Off'}</label>
<div class='custom-switch'> </div>
<input type='checkbox' id='torrent-dht' bind:checked={settings.torrentDHT} /> </SettingCard>
<label for='torrent-dht'>{settings.torrentDHT ? 'On' : 'Off'}</label>
</div>
</SettingCard>
{/if}
<SettingCard title='Disable PeX' description='Disables Peer Exchange for use in private trackers to improve privacy. Might greatly reduce the amount of discovered peers.'> <SettingCard title='Disable PeX' description='Disables Peer Exchange for use in private trackers to improve privacy. Might greatly reduce the amount of discovered peers.'>
<div class='custom-switch'> <div class='custom-switch'>
<input type='checkbox' id='torrent-pex' bind:checked={settings.torrentPeX} /> <input type='checkbox' id='torrent-pex' bind:checked={settings.torrentPeX} />

File diff suppressed because it is too large Load diff