mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-02-19 23:25:51 -05:00
* Implement dowload of mobile native deps - Add a spinner animation to `pnpm prep` - Change abandoned dependency @iarna/toml with smol-toml - Validate cargo config.toml after generating it from mustache template - Disabled HTTP2 downloads with udici, it is very broken * Initial ios native deps xcframework logic * Remove logic for handling dynamic iOS libs, using static libs now * Fix PATH in build-rust.sh - Remove app.json * Restore crates/images/src/pdf.rs * minor fix .editorconfig * Finally ios successfully compiles with ffmpeg enabled - Change SDCore.podspec to add extra libraries required by ffmpeg - Fix heif linking for ios in config.toml template - Add symlink logic for extra libraries required to compile ios in build-rust.sh
161 lines
4.4 KiB
JavaScript
161 lines
4.4 KiB
JavaScript
import * as fs from 'node:fs/promises'
|
|
import { dirname, join as joinPath } from 'node:path'
|
|
import { env } from 'node:process'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
import { getSystemProxy } from 'os-proxy-config'
|
|
import { fetch, Headers, Agent, ProxyAgent } from 'undici'
|
|
|
|
const CONNECT_TIMEOUT = 5 * 60 * 1000
|
|
const __debug = env.NODE_ENV === 'debug'
|
|
const __offline = env.OFFLINE === 'true'
|
|
const __filename = fileURLToPath(import.meta.url)
|
|
const __dirname = dirname(__filename)
|
|
const cacheDir = joinPath(__dirname, '.tmp')
|
|
|
|
/** @type {Agent.Options} */
|
|
const agentOpts = {
|
|
allowH2: !!env.HTTP2,
|
|
connect: { timeout: CONNECT_TIMEOUT },
|
|
connectTimeout: CONNECT_TIMEOUT,
|
|
autoSelectFamily: true,
|
|
}
|
|
|
|
const { proxyUrl } = (await getSystemProxy()) ?? {}
|
|
const dispatcher = proxyUrl
|
|
? new ProxyAgent({
|
|
...agentOpts,
|
|
proxyTls: { timeout: CONNECT_TIMEOUT },
|
|
requestTls: { timeout: CONNECT_TIMEOUT },
|
|
uri: proxyUrl,
|
|
})
|
|
: new Agent(agentOpts)
|
|
|
|
await fs.mkdir(cacheDir, { recursive: true, mode: 0o751 })
|
|
|
|
/**
|
|
* @param {string} resource
|
|
* @param {Headers} [headers]
|
|
* @returns {Promise<null | {data: Buffer, header: [string, string] | undefined}>}
|
|
*/
|
|
async function getCache(resource, headers) {
|
|
/** @type {Buffer | undefined} */
|
|
let data
|
|
/** @type {[string, string] | undefined} */
|
|
let header
|
|
|
|
// Don't cache in CI
|
|
if (env.CI === 'true' || env.NO_CACHE === 'true') return null
|
|
|
|
if (headers)
|
|
resource += Array.from(headers.entries())
|
|
.filter(([name]) => name !== 'If-None-Match' && name !== 'If-Modified-Since')
|
|
.flat()
|
|
.join(':')
|
|
try {
|
|
const cache = JSON.parse(
|
|
await fs.readFile(joinPath(cacheDir, Buffer.from(resource).toString('base64url')), {
|
|
encoding: 'utf8',
|
|
})
|
|
)
|
|
if (cache && typeof cache === 'object') {
|
|
if (cache.etag && typeof cache.etag === 'string') {
|
|
header = ['If-None-Match', cache.etag]
|
|
} else if (cache.modifiedSince && typeof cache.modifiedSince === 'string') {
|
|
header = ['If-Modified-Since', cache.modifiedSince]
|
|
}
|
|
|
|
if (cache.data && typeof cache.data === 'string')
|
|
data = Buffer.from(cache.data, 'base64')
|
|
}
|
|
} catch (error) {
|
|
if (__debug) {
|
|
console.warn(`CACHE MISS: ${resource}`)
|
|
console.error(error)
|
|
}
|
|
}
|
|
|
|
return data ? { data, header } : null
|
|
}
|
|
|
|
/**
|
|
* @param {import('undici').Response} response
|
|
* @param {string} resource
|
|
* @param {Buffer} [cachedData]
|
|
* @param {Headers} [headers]
|
|
* @returns {Promise<Buffer>}
|
|
*/
|
|
async function setCache(response, resource, cachedData, headers) {
|
|
const data = Buffer.from(await response.arrayBuffer())
|
|
|
|
// Don't cache in CI
|
|
if (env.CI === 'true') return data
|
|
|
|
const etag = response.headers.get('ETag') || undefined
|
|
const modifiedSince = response.headers.get('Last-Modified') || undefined
|
|
if (headers)
|
|
resource += Array.from(headers.entries())
|
|
.filter(([name]) => name !== 'If-None-Match' && name !== 'If-Modified-Since')
|
|
.flat()
|
|
.join(':')
|
|
|
|
if (response.status === 304 || (response.ok && data.length === 0)) {
|
|
// Cache hit
|
|
if (!cachedData) throw new Error('Empty cache hit ????')
|
|
return cachedData
|
|
}
|
|
|
|
try {
|
|
await fs.writeFile(
|
|
joinPath(cacheDir, Buffer.from(resource).toString('base64url')),
|
|
JSON.stringify({
|
|
etag,
|
|
modifiedSince,
|
|
data: data.toString('base64'),
|
|
}),
|
|
{ mode: 0o640, flag: 'w+' }
|
|
)
|
|
} catch (error) {
|
|
if (__debug) {
|
|
console.warn(`CACHE WRITE FAIL: ${resource}`)
|
|
console.error(error)
|
|
}
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* @param {URL | string} resource
|
|
* @param {Headers?} [headers]
|
|
* @param {boolean} [preferCache]
|
|
* @returns {Promise<Buffer>}
|
|
*/
|
|
export async function get(resource, headers, preferCache) {
|
|
if (headers == null) headers = new Headers()
|
|
if (resource instanceof URL) resource = resource.toString()
|
|
|
|
const cache = await getCache(resource, headers)
|
|
if (__offline) {
|
|
if (cache?.data == null)
|
|
throw new Error(`OFFLINE MODE: Cache for request ${resource} doesn't exist`)
|
|
return cache.data
|
|
}
|
|
if (preferCache && cache?.data != null) return cache.data
|
|
|
|
if (cache?.header) headers.append(...cache.header)
|
|
|
|
if (__debug) console.log(`Downloading ${resource} ${cache?.data ? ' (cached)' : ''}...`)
|
|
const response = await fetch(resource, { dispatcher, headers })
|
|
|
|
if (!response.ok) {
|
|
if (cache?.data) {
|
|
if (__debug) console.warn(`CACHE HIT due to fail: ${resource} ${response.statusText}`)
|
|
return cache.data
|
|
}
|
|
throw new Error(response.statusText)
|
|
}
|
|
|
|
return await setCache(response, resource, cache?.data, headers)
|
|
}
|