support for torrent download

This commit is contained in:
SrGooglo 2024-06-30 05:50:10 +02:00
parent 93201a5c19
commit bd719202c1
11 changed files with 197 additions and 71 deletions

View File

@ -16,6 +16,7 @@
"dependencies": { "dependencies": {
"@foxify/events": "^2.1.0", "@foxify/events": "^2.1.0",
"adm-zip": "^0.5.12", "adm-zip": "^0.5.12",
"aria2": "^4.1.2",
"axios": "^1.6.8", "axios": "^1.6.8",
"checksum": "^1.0.0", "checksum": "^1.0.0",
"cli-color": "^2.0.4", "cli-color": "^2.0.4",

View File

@ -1,10 +1,13 @@
import path from "node:path" import path from "node:path"
import fs from "node:fs"
import parseStringVars from "../utils/parseStringVars" import parseStringVars from "../utils/parseStringVars"
//import downloadTorrent from "../helpers/downloadTorrent" import downloadTorrent from "../helpers/downloadTorrent"
export default async (pkg, step, logger, abortController) => { export default async (pkg, step, logger, abortController) => {
throw new Error("Not implemented") if (!step.magnet) {
throw new Error(`Magnet is required for torrent step`)
}
if (typeof step.path === "undefined") { if (typeof step.path === "undefined") {
step.path = `.` step.path = `.`

View File

@ -10,7 +10,7 @@ import Apply from "../handlers/apply"
const BaseLog = Logger.child({ service: "INSTALLER" }) const BaseLog = Logger.child({ service: "INSTALLER" })
export default async function install(manifest) { export default async function install(manifest, options = {}) {
let id = null let id = null
let abortController = new AbortController() let abortController = new AbortController()
@ -112,7 +112,7 @@ export default async function install(manifest) {
return false return false
} }
if (Array.isArray(manifest.installSteps)) { if (Array.isArray(manifest.installSteps) && !options.noInstallSteps) {
Log.info(`Executing generic install steps...`) Log.info(`Executing generic install steps...`)
global._relic_eventBus.emit(`pkg:update:state`, { global._relic_eventBus.emit(`pkg:update:state`, {

View File

@ -2,6 +2,7 @@ import fs from "node:fs"
import path from "node:path" import path from "node:path"
import cliProgress from "cli-progress" import cliProgress from "cli-progress"
import humanFormat from "human-format" import humanFormat from "human-format"
import aria2 from "aria2"
function convertSize(size) { function convertSize(size) {
return `${humanFormat(size, { return `${humanFormat(size, {
@ -30,51 +31,46 @@ export default async function downloadTorrent(
speedString: "0B/s", speedString: "0B/s",
} }
const client = new WebTorrent() const client = new aria2({
host: 'localhost',
port: 6800,
secure: false,
secret: '',
path: '/jsonrpc'
})
await new Promise((resolve, reject) => { await client.open()
client.add(magnet, (torrentInstance) => {
const progressBar = new cliProgress.SingleBar({
format: "[{bar}] {percentage}% | {total_formatted} | {speed}/s | {eta_formatted}",
barCompleteChar: "\u2588",
barIncompleteChar: "\u2591",
hideCursor: true
}, cliProgress.Presets.shades_classic)
let downloadId = await client.call(
"addUri",
[magnet],
{
dir: destination,
},
)
await new Promise(async (resolve, reject) => {
if (typeof onStart === "function") { if (typeof onStart === "function") {
onStart(torrentInstance) onStart()
} }
progressBar.start(tickProgress.total, 0, { progressInterval = setInterval(async () => {
speed: "0B/s", const data = await client.call("tellStatus", downloadId)
total_formatted: tickProgress.totalString, const isMetadata = data.totalLength === "0" && data.status === "active"
})
torrentInstance.on("done", () => { console.log(data)
progressBar.stop()
clearInterval(progressInterval)
if (typeof onDone === "function") { if (data.status === "complete") {
onDone(torrentInstance) if (Array.isArray(data.followedBy) && data.followedBy[0]) {
// replace downloadId
downloadId = data.followedBy[0]
}
} }
resolve(torrentInstance) tickProgress.total = parseInt(data.totalLength)
}) tickProgress.speed = parseInt(data.downloadSpeed)
tickProgress.transferred = parseInt(data.completedLength)
torrentInstance.on("error", (error) => { tickProgress.connections = data.connections
progressBar.stop()
clearInterval(progressInterval)
if (typeof onError === "function") {
onError(error)
} else {
reject(error)
}
})
progressInterval = setInterval(() => {
tickProgress.speed = torrentInstance.downloadSpeed
tickProgress.transferred = torrentInstance.downloaded
tickProgress.transferredString = convertSize(tickProgress.transferred) tickProgress.transferredString = convertSize(tickProgress.transferred)
tickProgress.totalString = convertSize(tickProgress.total) tickProgress.totalString = convertSize(tickProgress.total)
@ -83,11 +79,54 @@ export default async function downloadTorrent(
if (typeof onProgress === "function") { if (typeof onProgress === "function") {
onProgress(tickProgress) onProgress(tickProgress)
} }
progressBar.update(tickProgress.transferred, {
speed: tickProgress.speedString,
})
}, 1000) }, 1000)
})
}) client.on("onDownloadStart", async ([{ gid }]) => {
const data = await client.call("tellStatus", gid)
console.log(data)
if (typeof data.following !== "undefined") {
if (data.following === downloadId) {
downloadId = data.gid
}
}
})
client.on("onBtDownloadComplete", ([{ gid }]) => {
if (gid !== downloadId) {
return false
}
clearInterval(progressInterval)
if (typeof onDone === "function") {
onDone()
}
resolve({
downloadId,
})
return null
})
client.on("onDownloadError", ([{ gid }]) => {
if (gid !== downloadId) {
return false
}
clearInterval(progressInterval)
if (typeof onError === "function") {
onError()
} else {
reject()
}
})
})
await client.call("remove", downloadId)
return downloadId
} }

View File

@ -4,6 +4,7 @@ import { onExit } from "signal-exit"
import open from "open" import open from "open"
import SetupHelper from "./helpers/setup" import SetupHelper from "./helpers/setup"
import { execa } from "./libraries/execa"
import Logger from "./logger" import Logger from "./logger"
import Settings from "./classes/Settings" import Settings from "./classes/Settings"
@ -49,6 +50,15 @@ export default class RelicCore {
await Settings.set("packages_path", Vars.packages_path) await Settings.set("packages_path", Vars.packages_path)
} }
this.aria2c_instance = execa(
Vars.aria2_bin,
["--enable-rpc", "--rpc-listen-all=true", "--rpc-allow-origin-all", "--file-allocation=none"],
{
stdout: "inherit",
stderr: "inherit",
}
)
onExit(this.onExit) onExit(this.onExit)
} }
@ -56,6 +66,10 @@ export default class RelicCore {
if (fs.existsSync(Vars.cache_path)) { if (fs.existsSync(Vars.cache_path)) {
fs.rmSync(Vars.cache_path, { recursive: true, force: true }) fs.rmSync(Vars.cache_path, { recursive: true, force: true })
} }
if (this.aria2c_instance) {
this.aria2c_instance.kill("SIGINT")
}
} }
async setup() { async setup() {

View File

@ -0,0 +1,34 @@
import extractFile from "../../../utils/extractFile"
import { execa } from "../../../libraries/execa"
import Vars from "../../../vars"
export default class Extract {
async extractFull(file, dest, { password } = {}) {
const args = [
"x",
"-y",
]
if (password) {
args.push(`-p"${password}"`)
}
args.push(`-o"${dest}"`)
args.push(`"${file}"`)
const cmd = `${Vars.sevenzip_bin} ${args.join(" ")}`
console.log(cmd)
await execa(cmd, {
shell: true,
stdout: "inherit",
stderr: "inherit",
})
}
async autoExtract(file, dest) {
return await extractFile(file, dest)
}
}

View File

@ -36,4 +36,36 @@ export default class SecureFileSystem {
existsSync(...args) { existsSync(...args) {
return fs.existsSync(...args) return fs.existsSync(...args)
} }
async rename(from, to) {
this.checkOutsideJail(from)
this.checkOutsideJail(to)
return await fs.promises.rename(from, to)
}
async writeFile(path, data, options) {
this.checkOutsideJail(path)
return await fs.promises.writeFile(path, data, options)
}
async readDir(path) {
this.checkOutsideJail(path)
return await fs.promises.readdir(path)
}
async rm(path, options) {
this.checkOutsideJail(path)
return await fs.promises.rm(path, options)
}
async mkdir(path, options) {
this.checkOutsideJail(path)
return await fs.promises.mkdir(path, options)
}
async stat(path) {
this.checkOutsideJail(path)
return await fs.promises.stat(path)
}
} }

View File

@ -2,6 +2,7 @@ import Open from "./open"
import Path from "./path" import Path from "./path"
import Fs from "./fs" import Fs from "./fs"
import Auth from "./auth" import Auth from "./auth"
import Extract from "./extract"
// Third party libraries // Third party libraries
import Mcl from "./mcl" import Mcl from "./mcl"
@ -11,5 +12,6 @@ export default {
path: Path, path: Path,
open: Open, open: Open,
auth: Auth, auth: Auth,
mcl: Mcl extract: Extract,
mcl: Mcl,
} }

View File

@ -9,9 +9,11 @@ export default [
{ {
id: "7z-bin", id: "7z-bin",
finalBin: Vars.sevenzip_bin, finalBin: Vars.sevenzip_bin,
url: resolveRemoteBinPath(`${baseURL}/7zip-bin`, process.platform === "win32" ? "7za.exe" : "7za"), url: resolveRemoteBinPath(`${baseURL}/7z-full`, "7z.zip"),
destination: Vars.sevenzip_bin, destination: path.resolve(Vars.binaries_path, "7z.zip"),
extract: path.resolve(Vars.binaries_path, "7z-bin"),
rewriteExecutionPermission: true, rewriteExecutionPermission: true,
deleteBeforeExtract: true,
}, },
{ {
id: "git-bin", id: "git-bin",
@ -24,14 +26,13 @@ export default [
deleteBeforeExtract: true, deleteBeforeExtract: true,
}, },
{ {
id: "rclone-bin", id: "aria2",
finalBin: Vars.rclone_bin, finalBin: Vars.aria2_bin,
url: resolveRemoteBinPath(`${baseURL}/rclone`, "rclone-bin.zip"), url: async (os, arch) => {
destination: path.resolve(Vars.binaries_path, "rclone-bin.zip"), return `https://storage.ragestudio.net/rstudio/binaries/aria2/${os}/${arch}/${os === "win32" ? "aria2c.exe" : "aria2c"}`
extract: path.resolve(Vars.binaries_path, "rclone-bin"), },
requireOs: ["win32"], destination: Vars.aria2_bin,
rewriteExecutionPermission: true, rewriteExecutionPermission: Vars.aria2_bin,
deleteBeforeExtract: true,
}, },
{ {
id: "java22_jre_bin", id: "java22_jre_bin",

View File

@ -2,10 +2,10 @@ export default (pre, post) => {
let url = null let url = null
if (process.platform === "darwin") { if (process.platform === "darwin") {
url = `${pre}/mac/${process.arch}/${post}` url = `${pre}/darwin/${process.arch}/${post}`
} }
else if (process.platform === "win32") { else if (process.platform === "win32") {
url = `${pre}/win/${process.arch}/${post}` url = `${pre}/win32/${process.arch}/${post}`
} }
else { else {
url = `${pre}/linux/${process.arch}/${post}` url = `${pre}/linux/${process.arch}/${post}`

View File

@ -2,7 +2,7 @@ import path from "node:path"
import upath from "upath" import upath from "upath"
import resolveUserDataPath from "./utils/resolveUserDataPath" import resolveUserDataPath from "./utils/resolveUserDataPath"
const isWin = process.platform.includes("win") const isWin = process.platform.includes("win32")
const isMac = process.platform.includes("darwin") const isMac = process.platform.includes("darwin")
const runtimeName = "rs-relic" const runtimeName = "rs-relic"
@ -15,9 +15,9 @@ const binaries_path = upath.normalizeSafe(path.resolve(runtime_path, "binaries")
const db_path = upath.normalizeSafe(path.resolve(runtime_path, "db.json")) const db_path = upath.normalizeSafe(path.resolve(runtime_path, "db.json"))
const binaries = { const binaries = {
sevenzip_bin: upath.normalizeSafe(path.resolve(binaries_path, "7z-bin", isWin ? "7za.exe" : "7za")), sevenzip_bin: upath.normalizeSafe(path.resolve(binaries_path, "7z-bin", isWin ? "7z.exe" : "7zz")),
git_bin: upath.normalizeSafe(path.resolve(binaries_path, "git-bin", "bin", isWin ? "git.exe" : "git")), git_bin: upath.normalizeSafe(path.resolve(binaries_path, "git-bin", "bin", isWin ? "git.exe" : "git")),
rclone_bin: upath.normalizeSafe(path.resolve(binaries_path, "rclone-bin", isWin ? "rclone.exe" : "rclone")), aria2_bin: upath.normalizeSafe(path.resolve(binaries_path, "aria2", isWin ? "aria2c.exe" : "aria2c")),
java22_jre_bin: upath.normalizeSafe(path.resolve(binaries_path, "java22_jre_bin", (isMac ? "Contents/Home/bin/java" : (isWin ? "bin/java.exe" : "bin/java")))), java22_jre_bin: upath.normalizeSafe(path.resolve(binaries_path, "java22_jre_bin", (isMac ? "Contents/Home/bin/java" : (isWin ? "bin/java.exe" : "bin/java")))),
java17_jre_bin: upath.normalizeSafe(path.resolve(binaries_path, "java17_jre_bin", (isMac ? "Contents/Home/bin/java" : (isWin ? "bin/java.exe" : "bin/java")))), java17_jre_bin: upath.normalizeSafe(path.resolve(binaries_path, "java17_jre_bin", (isMac ? "Contents/Home/bin/java" : (isWin ? "bin/java.exe" : "bin/java")))),
} }