Move local API player cache internals into the main process (#5068)

This commit is contained in:
absidue 2024-05-11 20:43:57 +02:00 committed by GitHub
parent 71b7c60b68
commit 7685d75e79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 50 additions and 59 deletions

View File

@ -4,8 +4,6 @@ const IpcChannels = {
DISABLE_PROXY: 'disable-proxy',
OPEN_EXTERNAL_LINK: 'open-external-link',
GET_SYSTEM_LOCALE: 'get-system-locale',
GET_USER_DATA_PATH: 'get-user-data-path',
GET_USER_DATA_PATH_SYNC: 'get-user-data-path-sync',
GET_PICTURES_PATH: 'get-pictures-path',
SHOW_OPEN_DIALOG: 'show-open-dialog',
SHOW_SAVE_DIALOG: 'show-save-dialog',
@ -26,7 +24,10 @@ const IpcChannels = {
SYNC_PLAYLISTS: 'sync-playlists',
GET_REPLACE_HTTP_CACHE: 'get-replace-http-cache',
TOGGLE_REPLACE_HTTP_CACHE: 'toggle-replace-http-cache'
TOGGLE_REPLACE_HTTP_CACHE: 'toggle-replace-http-cache',
PLAYER_CACHE_GET: 'player-cache-get',
PLAYER_CACHE_SET: 'player-cache-set'
}
const DBActions = {

View File

@ -199,10 +199,12 @@ function runApp() {
app.commandLine.appendSwitch('enable-features', 'VaapiVideoDecodeLinuxGL')
}
const userDataPath = app.getPath('userData')
// command line switches need to be added before the app ready event first
// that means we can't use the normal settings system as that is asynchronous,
// doing it synchronously ensures that we add it before the event fires
const REPLACE_HTTP_CACHE_PATH = `${app.getPath('userData')}/experiment-replace-http-cache`
const REPLACE_HTTP_CACHE_PATH = `${userDataPath}/experiment-replace-http-cache`
const replaceHttpCache = existsSync(REPLACE_HTTP_CACHE_PATH)
if (replaceHttpCache) {
// the http cache causes excessive disk usage during video playback
@ -211,6 +213,8 @@ function runApp() {
app.commandLine.appendSwitch('disable-http-cache')
}
const PLAYER_CACHE_PATH = `${userDataPath}/player_cache`
// See: https://stackoverflow.com/questions/45570589/electron-protocol-handler-not-working-on-windows
// remove so we can register each time as we run the app.
app.removeAsDefaultProtocolClient('freetube')
@ -866,14 +870,6 @@ function runApp() {
return app.getSystemLocale()
})
ipcMain.handle(IpcChannels.GET_USER_DATA_PATH, () => {
return app.getPath('userData')
})
ipcMain.on(IpcChannels.GET_USER_DATA_PATH_SYNC, (event) => {
event.returnValue = app.getPath('userData')
})
ipcMain.handle(IpcChannels.GET_PICTURES_PATH, () => {
return app.getPath('pictures')
})
@ -938,6 +934,35 @@ function runApp() {
relaunch()
})
function playerCachePathForKey(key) {
// Remove path separators and period characters,
// to prevent any files outside of the player_cache directory,
// from being read or written
const sanitizedKey = `${key}`.replaceAll(/[./\\]/g, '__')
return path.join(PLAYER_CACHE_PATH, sanitizedKey)
}
ipcMain.handle(IpcChannels.PLAYER_CACHE_GET, async (_, key) => {
const filePath = playerCachePathForKey(key)
try {
const contents = await asyncFs.readFile(filePath)
return contents.buffer
} catch (e) {
console.error(e)
return undefined
}
})
ipcMain.handle(IpcChannels.PLAYER_CACHE_SET, async (_, key, value) => {
const filePath = playerCachePathForKey(key)
await asyncFs.mkdir(PLAYER_CACHE_PATH, { recursive: true })
await asyncFs.writeFile(filePath, new Uint8Array(value))
})
// ************************************************* //
// DB related IPC calls
// *********** //

View File

@ -1,43 +1,21 @@
import fs from 'fs/promises'
import path from 'path'
import { IpcChannels } from '../../../constants'
import { pathExists } from '../filesystem'
// based off https://github.com/LuanRT/YouTube.js/blob/6caa679df6ddc77d25be02dcb7355b722ab268aa/src/utils/Cache.ts
// avoids errors caused by the fully dynamic `fs` and `path` module imports that youtubei.js's UniversalCache does
export class PlayerCache {
constructor(cacheDirectory) {
this.cacheDirectory = cacheDirectory
}
async get(key) {
const filePath = path.resolve(this.cacheDirectory, key)
try {
const contents = await fs.readFile(filePath)
return contents.buffer
} catch (e) {
if (e?.code === 'ENOENT') {
return undefined
}
throw e
if (process.env.IS_ELECTRON) {
const { ipcRenderer } = require('electron')
return await ipcRenderer.invoke(IpcChannels.PLAYER_CACHE_GET, key)
}
}
async set(key, value) {
await fs.mkdir(this.cacheDirectory, { recursive: true })
const filePath = path.resolve(this.cacheDirectory, key)
await fs.writeFile(filePath, new Uint8Array(value))
}
async remove(key) {
const filePath = path.resolve(this.cacheDirectory, key)
if (await pathExists(filePath)) {
try {
await fs.unlink(filePath)
} catch { }
if (process.env.IS_ELECTRON) {
const { ipcRenderer } = require('electron')
await ipcRenderer.invoke(IpcChannels.PLAYER_CACHE_SET, key, value)
}
}
async remove(_key) {
// no-op; YouTube.js only uses remove for the OAuth credentials, but we don't use that in FreeTube
}
}

View File

@ -1,6 +1,5 @@
import { ClientType, Endpoints, Innertube, Misc, UniversalCache, Utils, YT } from 'youtubei.js'
import Autolinker from 'autolinker'
import { join } from 'path'
import { SEARCH_CHAR_LIMIT } from '../../../constants'
import { PlayerCache } from './PlayerCache'
@ -9,7 +8,6 @@ import {
calculatePublishedDate,
escapeHTML,
extractNumberFromString,
getUserDataPath,
toLocalePublicationString
} from '../utils'
@ -41,8 +39,7 @@ async function createInnertube({ withPlayer = false, location = undefined, safet
let cache
if (withPlayer) {
if (process.env.IS_ELECTRON) {
const userData = await getUserDataPath()
cache = new PlayerCache(join(userData, 'player_cache'))
cache = new PlayerCache()
} else {
cache = new UniversalCache(false)
}

View File

@ -611,16 +611,6 @@ export async function getSystemLocale() {
return locale || 'en-US'
}
export async function getUserDataPath() {
if (process.env.IS_ELECTRON) {
const { ipcRenderer } = require('electron')
return await ipcRenderer.invoke(IpcChannels.GET_USER_DATA_PATH)
} else {
// TODO: implement getUserDataPath web compatible callback
return null
}
}
export async function getPicturesPath() {
if (process.env.IS_ELECTRON) {
const { ipcRenderer } = require('electron')