Skip to content

Commit

Permalink
Merge branch 'upstream_development' into development
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/renderer/store/modules/utils.js
#	yarn.lock
  • Loading branch information
MarmadileManteater committed Oct 26, 2022
2 parents ad945bd + 0e3d765 commit a3ffbda
Show file tree
Hide file tree
Showing 40 changed files with 972 additions and 348 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/flatpak.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,22 +77,22 @@ jobs:
date +"%Y-%m-%d" >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Update x64 File Location in yml File
uses: mikefarah/[email protected].1
uses: mikefarah/[email protected].2
with:
# The Command which should be run
cmd: yq w -i io.freetubeapp.FreeTube.yml modules[0].sources[0].url 'https://github.com/FreeTubeApp/FreeTube/releases/download/v${{ steps.sub.outputs.result }}-beta/freetube-${{ steps.sub.outputs.result }}-linux-portable-x64.zip'
- name: Update x64 Hash in yml File
uses: mikefarah/[email protected].1
uses: mikefarah/[email protected].2
with:
# The Command which should be run
cmd: yq w -i io.freetubeapp.FreeTube.yml modules[0].sources[0].sha256 ${{ env.HASH_X64 }}
- name: Update ARM File Location in yml File
uses: mikefarah/[email protected].1
uses: mikefarah/[email protected].2
with:
# The Command which should be run
cmd: yq w -i io.freetubeapp.FreeTube.yml modules[0].sources[1].url 'https://github.com/FreeTubeApp/FreeTube/releases/download/v${{ steps.sub.outputs.result }}-beta/freetube-${{ steps.sub.outputs.result }}-linux-portable-arm64.zip'
- name: Update ARM Hash in yml File
uses: mikefarah/[email protected].1
uses: mikefarah/[email protected].2
with:
# The Command which should be run
cmd: yq w -i io.freetubeapp.FreeTube.yml modules[0].sources[1].sha256 ${{ env.HASH_ARM64 }}
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:

# For bug reports
- name: New bug issue
uses: alex-page/[email protected].1
uses: alex-page/[email protected].2
if: contains(github.event.issue.labels.*.name, 'bug') && github.event.action == 'opened'
with:
project: Bug Reports
Expand All @@ -22,7 +22,7 @@ jobs:
action: update

- name: Bug issue closed
uses: alex-page/[email protected].1
uses: alex-page/[email protected].2
if: github.event.action == 'closed' || github.event.action == 'deleted'
with:
action: delete
Expand All @@ -31,7 +31,7 @@ jobs:
repo-token: ${{ secrets.PUSH_TOKEN }}

- name: Bug issue reopened
uses: alex-page/[email protected].1
uses: alex-page/[email protected].2
if: contains(github.event.issue.labels.*.name, 'bug') && github.event.action == 'reopened'
with:
project: Bug Reports
Expand All @@ -41,7 +41,7 @@ jobs:

# For feature requests
- name: New feature issue
uses: alex-page/[email protected].1
uses: alex-page/[email protected].2
if: contains(github.event.issue.labels.*.name, 'enhancement') && github.event.action == 'opened'
with:
project: Feature Requests
Expand All @@ -50,7 +50,7 @@ jobs:
action: update

- name: Feature request issue closed
uses: alex-page/[email protected].1
uses: alex-page/[email protected].2
if: github.event.action == 'closed' || github.event.action == 'deleted'
with:
action: delete
Expand All @@ -59,7 +59,7 @@ jobs:
repo-token: ${{ secrets.PUSH_TOKEN }}

- name: Feature request issue reopened
uses: alex-page/[email protected].1
uses: alex-page/[email protected].2
if: contains(github.event.issue.labels.*.name, 'enhancement') && github.event.action == 'reopened'
with:
project: Feature Requests
Expand Down
8 changes: 5 additions & 3 deletions _scripts/dev-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ async function restartElectron() {

electronProcess = spawn(electron, [
path.join(__dirname, '../dist/main.js'),
// '--enable-logging', Enable to show logs from all electron processes
// '--enable-logging', // Enable to show logs from all electron processes
remoteDebugging ? '--inspect=9222' : '',
remoteDebugging ? '--remote-debugging-port=9223' : '',
])
remoteDebugging ? '--remote-debugging-port=9223' : ''
],
// { stdio: 'inherit' } // required for logs to actually appear in the stdout
)

electronProcess.on('exit', (code, _) => {
if (code === relaunchExitCode) {
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,18 @@
"https-proxy-agent": "^5.0.0",
"lodash.debounce": "^4.0.8",
"lodash.isequal": "^4.5.0",
"marked": "^4.0.17",
"marked": "^4.1.1",
"nedb-promises": "^6.2.1",
"opml-to-json": "^1.0.1",
"process": "^0.11.10",
"socks-proxy-agent": "^6.0.0",
"video.js": "7.18.1",
"videojs-contrib-quality-levels": "^2.1.0",
"videojs-http-source-selector": "^1.1.6",
"videojs-mobile-ui": "^0.8.0",
"videojs-overlay": "^2.1.4",
"videojs-vtt-thumbnails-freetube": "0.0.15",
"vue": "^2.7.10",
"vue": "^2.7.13",
"vue-i18n": "^8.27.2",
"vue-observe-visibility": "^1.0.0",
"vue-router": "^3.6.5",
Expand All @@ -95,7 +96,7 @@
"copy-webpack-plugin": "^11.0.0",
"cordova": "^11.0.0",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.1.0",
"css-minimizer-webpack-plugin": "^4.2.2",
"electron": "^21.1.1",
"electron-builder": "^23.6.0",
"eslint": "^7.32.0",
Expand All @@ -113,7 +114,7 @@
"lefthook": "^1.1.2",
"mini-css-extract-plugin": "^2.6.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.3.2",
"prettier": "^2.7.1",
"rimraf": "^3.0.2",
"sass": "^1.54.9",
"sass-loader": "^13.0.2",
Expand Down
73 changes: 73 additions & 0 deletions src/main/ImageCache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// cleanup expired images once every 5 mins
const CLEANUP_INTERVAL = 300_000

// images expire after 2 hours if no expiry information is found in the http headers
const FALLBACK_MAX_AGE = 7200

export class ImageCache {
constructor() {
this._cache = new Map()

setInterval(this._cleanup.bind(this), CLEANUP_INTERVAL)
}

add(url, mimeType, data, expiry) {
this._cache.set(url, { mimeType, data, expiry })
}

has(url) {
return this._cache.has(url)
}

get(url) {
const entry = this._cache.get(url)

if (!entry) {
// this should never happen as the `has` method should be used to check for the existence first
throw new Error(`No image cache entry for ${url}`)
}

return {
data: entry.data,
mimeType: entry.mimeType
}
}

_cleanup() {
// seconds since 1970-01-01 00:00:00
const now = Math.trunc(Date.now() / 1000)

for (const [key, entry] of this._cache.entries()) {
if (entry.expiry <= now) {
this._cache.delete(key)
}
}
}
}

/**
* Extracts the cache expiry timestamp of image from HTTP headers
* @param {Record<string, string>} headers
* @returns a timestamp in seconds
*/
export function extractExpiryTimestamp(headers) {
const maxAgeRegex = /max-age=([0-9]+)/

const cacheControl = headers['cache-control']
if (cacheControl && maxAgeRegex.test(cacheControl)) {
let maxAge = parseInt(cacheControl.match(maxAgeRegex)[1])

if (headers.age) {
maxAge -= parseInt(headers.age)
}

// we don't need millisecond precision, so we can store it as seconds to use less memory
return Math.trunc(Date.now() / 1000) + maxAge
} else if (headers.expires) {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expires

return Math.trunc(Date.parse(headers.expires) / 1000)
} else {
return Math.trunc(Date.now() / 1000) + FALLBACK_MAX_AGE
}
}
122 changes: 121 additions & 1 deletion src/main/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import {
app, BrowserWindow, dialog, Menu, ipcMain,
powerSaveBlocker, screen, session, shell, nativeTheme
powerSaveBlocker, screen, session, shell, nativeTheme, net, protocol
} from 'electron'
import path from 'path'
import cp from 'child_process'

import { IpcChannels, DBActions, SyncEvents } from '../constants'
import baseHandlers from '../datastores/handlers/base'
import { extractExpiryTimestamp, ImageCache } from './ImageCache'
import { existsSync } from 'fs'

if (process.argv.includes('--version')) {
app.exit()
Expand Down Expand Up @@ -49,6 +51,17 @@ function runApp() {
app.commandLine.appendSwitch('enable-file-cookies')
app.commandLine.appendSwitch('ignore-gpu-blacklist')

// 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 asynchonous,
// doing it synchronously ensures that we add it before the event fires
const replaceHttpCache = existsSync(`${app.getPath('userData')}/experiment-replace-http-cache`)
if (replaceHttpCache) {
// the http cache causes excessive disk usage during video playback
// we've got a custom image cache to make up for disabling the http cache
// experimental as it increases RAM use in favour of reduced disk use
app.commandLine.appendSwitch('disable-http-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')
Expand Down Expand Up @@ -149,6 +162,113 @@ function runApp() {
})
})

if (replaceHttpCache) {
// in-memory image cache

const imageCache = new ImageCache()

protocol.registerBufferProtocol('imagecache', (request, callback) => {
// Remove `imagecache://` prefix
const url = decodeURIComponent(request.url.substring(13))
if (imageCache.has(url)) {
const cached = imageCache.get(url)

// eslint-disable-next-line node/no-callback-literal
callback({
mimeType: cached.mimeType,
data: cached.data
})
return
}

const newRequest = net.request({
method: request.method,
url
})

// Electron doesn't allow certain headers to be set:
// https://www.electronjs.org/docs/latest/api/client-request#requestsetheadername-value
// also blacklist Origin and Referrer as we don't want to let YouTube know about them
const blacklistedHeaders = ['content-length', 'host', 'trailer', 'te', 'upgrade', 'cookie2', 'keep-alive', 'transfer-encoding', 'origin', 'referrer']

for (const header of Object.keys(request.headers)) {
if (!blacklistedHeaders.includes(header.toLowerCase())) {
newRequest.setHeader(header, request.headers[header])
}
}

newRequest.on('response', (response) => {
const chunks = []
response.on('data', (chunk) => {
chunks.push(chunk)
})

response.on('end', () => {
const data = Buffer.concat(chunks)

const expiryTimestamp = extractExpiryTimestamp(response.headers)
const mimeType = response.headers['content-type']

imageCache.add(url, mimeType, data, expiryTimestamp)

// eslint-disable-next-line node/no-callback-literal
callback({
mimeType,
data: data
})
})

response.on('error', (error) => {
console.error('image cache error', error)

// error objects don't get serialised properly
// https://stackoverflow.com/a/53624454

const errorJson = JSON.stringify(error, (key, value) => {
if (value instanceof Error) {
return {
// Pull all enumerable properties, supporting properties on custom Errors
...value,
// Explicitly pull Error's non-enumerable properties
name: value.name,
message: value.message,
stack: value.stack
}
}

return value
})

// eslint-disable-next-line node/no-callback-literal
callback({
statusCode: response.statusCode ?? 400,
mimeType: 'application/json',
data: Buffer.from(errorJson)
})
})
})

newRequest.end()
})

const imageRequestFilter = { urls: ['https://*/*', 'http://*/*'] }
session.defaultSession.webRequest.onBeforeRequest(imageRequestFilter, (details, callback) => {
// the requests made by the imagecache:// handler to fetch the image,
// are allowed through, as their resourceType is 'other'
if (details.resourceType === 'image') {
// eslint-disable-next-line node/no-callback-literal
callback({
redirectURL: `imagecache://${encodeURIComponent(details.url)}`
})
} else {
// eslint-disable-next-line node/no-callback-literal
callback({})
}
})

// --- end of `if experimentsDisableDiskCache` ---
}

await createWindow()

if (isDev) {
Expand Down
Loading

0 comments on commit a3ffbda

Please sign in to comment.