use new runtime api

This commit is contained in:
SrGooglo 2025-03-06 03:51:05 +00:00
parent 3e24dfec7a
commit 0c49d7fcf8
4 changed files with 223 additions and 446 deletions

View File

@ -4,38 +4,35 @@ import NotificationUI from "./ui"
import NotificationFeedback from "./feedback" import NotificationFeedback from "./feedback"
export default class NotificationCore extends Core { export default class NotificationCore extends Core {
static namespace = "notifications" static namespace = "notifications"
static depenpencies = [ static depenpencies = ["api", "settings"]
"api",
"settings",
]
listenSockets = { listenSockets = {
"notifications": { notifications: {
"notification.new": (data) => { "notification.new": (data) => {
this.new(data) this.new(data)
}, },
"notification.broadcast": (data) => { "notification.broadcast": (data) => {
this.new(data) this.new(data)
}, },
} },
} }
public = { public = {
new: this.new, new: this.new,
close: this.close, close: this.close,
} }
async onInitialize() { async onInitialize() {
this.ctx.CORES.api.registerSocketListeners(this.listenSockets) this.ctx.cores.get("api").registerSocketListeners(this.listenSockets)
} }
async new(notification) { async new(notification) {
NotificationUI.notify(notification) NotificationUI.notify(notification)
NotificationFeedback.feedback(notification) NotificationFeedback.feedback(notification)
} }
async close(id) { async close(id) {
NotificationUI.close(id) NotificationUI.close(id)
} }
} }

View File

@ -1,140 +1,141 @@
import AudioPlayerStorage from "../player.storage" import AudioPlayerStorage from "../player.storage"
export default class Presets { export default class Presets {
constructor({ constructor({ storage_key, defaultPresetValue, onApplyValues }) {
storage_key, if (!storage_key) {
defaultPresetValue, throw new Error("storage_key is required")
onApplyValues, }
}) {
if (!storage_key) {
throw new Error("storage_key is required")
}
this.storage_key = storage_key this.storage_key = storage_key
this.defaultPresetValue = defaultPresetValue this.defaultPresetValue = defaultPresetValue
this.onApplyValues = onApplyValues this.onApplyValues = onApplyValues
return this return this
} }
get presets() { get presets() {
return AudioPlayerStorage.get(`${this.storage_key}_presets`) ?? { return (
default: this.defaultPresetValue AudioPlayerStorage.get(`${this.storage_key}_presets`) ?? {
} default: this.defaultPresetValue,
} }
)
}
set presets(presets) { set presets(presets) {
AudioPlayerStorage.set(`${this.storage_key}_presets`, presets) AudioPlayerStorage.set(`${this.storage_key}_presets`, presets)
return presets return presets
} }
set currentPresetKey(key) { set currentPresetKey(key) {
AudioPlayerStorage.set(`${this.storage_key}_current-key`, key) AudioPlayerStorage.set(`${this.storage_key}_current-key`, key)
return key return key
} }
get currentPresetKey() { get currentPresetKey() {
return AudioPlayerStorage.get(`${this.storage_key}_current-key`) ?? "default" return (
} AudioPlayerStorage.get(`${this.storage_key}_current-key`) ??
"default"
)
}
get currentPresetValues() { get currentPresetValues() {
if (!this.presets || !this.presets[this.currentPresetKey]) { if (!this.presets || !this.presets[this.currentPresetKey]) {
return this.defaultPresetValue return this.defaultPresetValue
} }
return this.presets[this.currentPresetKey] return this.presets[this.currentPresetKey]
} }
set currentPresetValues(values) { set currentPresetValues(values) {
const newData = this.presets const newData = this.presets
newData[this.currentPresetKey] = values newData[this.currentPresetKey] = values
this.presets = newData this.presets = newData
} }
applyValues() { applyValues() {
if (typeof this.onApplyValues === "function") { if (typeof this.onApplyValues === "function") {
this.onApplyValues(this.presets) this.onApplyValues(this.presets)
} }
} }
deletePreset(key) { deletePreset(key) {
if (key === "default") { if (key === "default") {
app.message.error("Cannot delete default preset") app.message.error("Cannot delete default preset")
return false return false
} }
// if current preset is deleted, change to default // if current preset is deleted, change to default
if (this.currentPresetKey === key) { if (this.currentPresetKey === key) {
this.changePreset("default") this.changePreset("default")
} }
let newData = this.presets let newData = this.presets
delete newData[key] delete newData[key]
this.presets = newData this.presets = newData
this.applyValues() this.applyValues()
return newData return newData
} }
createPreset(key, values) { createPreset(key, values) {
if (this.presets[key]) { if (this.presets[key]) {
app.message.error("Preset already exists") app.message.error("Preset already exists")
return false return false
} }
let newData = this.presets let newData = this.presets
newData[key] = values ?? this.defaultPresetValue newData[key] = values ?? this.defaultPresetValue
this.applyValues() this.applyValues()
this.presets = newData this.presets = newData
return newData return newData
} }
changePreset(key) { changePreset(key) {
// create new one // create new one
if (!this.presets[key]) { if (!this.presets[key]) {
this.presets[key] = this.defaultPresetValue this.presets[key] = this.defaultPresetValue
} }
this.currentPresetKey = key this.currentPresetKey = key
this.applyValues() this.applyValues()
return this.presets[key] return this.presets[key]
} }
setToCurrent(values) { setToCurrent(values) {
this.currentPresetValues = { this.currentPresetValues = {
...this.currentPresetValues, ...this.currentPresetValues,
...values, ...values,
} }
this.applyValues() this.applyValues()
return this.currentPresetValues return this.currentPresetValues
} }
async setCurrentPresetToDefault() { async setCurrentPresetToDefault() {
return await new Promise((resolve) => { return await new Promise((resolve) => {
app.layout.modal.confirm.confirm({ app.layout.modal.confirm({
title: "Reset to default values?", title: "Reset to default values?",
content: "Are you sure you want to reset to default values?", content: "Are you sure you want to reset to default values?",
onOk: () => { onConfirm: () => {
this.setToCurrent(this.defaultPresetValue) this.setToCurrent(this.defaultPresetValue)
resolve(this.currentPresetValues) resolve(this.currentPresetValues)
} },
}) })
}) })
} }
} }

View File

@ -1,230 +0,0 @@
import { EventBus } from "@ragestudio/vessel"
export default class ChunkedUpload {
constructor(params) {
const {
endpoint,
file,
headers = {},
splitChunkSize = 1024 * 1024 * 10,
maxRetries = 3,
delayBeforeRetry = 5,
} = params
if (!endpoint) {
throw new Error("Missing endpoint")
}
if ((!file) instanceof File) {
throw new Error("Invalid or missing file")
}
if (typeof headers !== "object") {
throw new Error("Invalid headers")
}
if (splitChunkSize <= 0) {
throw new Error("Invalid splitChunkSize")
}
this.chunkCount = 0
this.retriesCount = 0
this.splitChunkSize = splitChunkSize
this.totalChunks = Math.ceil(file.size / splitChunkSize)
this.maxRetries = maxRetries
this.delayBeforeRetry = delayBeforeRetry
this.offline = this.paused = false
this.endpoint = endpoint
this.file = file
this.headers = {
...headers,
"uploader-original-name": encodeURIComponent(file.name),
"uploader-file-id": this.getFileUID(file),
"uploader-chunks-total": this.totalChunks,
"chunk-size": splitChunkSize,
Connection: "keep-alive",
"Cache-Control": "no-cache",
}
this.setupListeners()
this.nextSend()
console.debug("[Uploader] Created", {
splitChunkSize: splitChunkSize,
totalChunks: this.totalChunks,
totalSize: file.size,
})
}
_reader = new FileReader()
events = new EventBus()
setupListeners() {
window.addEventListener(
"online",
() =>
!this.offline &&
((this.offline = false),
this.events.emit("online"),
this.nextSend()),
)
window.addEventListener(
"offline",
() => ((this.offline = true), this.events.emit("offline")),
)
}
getFileUID(file) {
return (
Math.floor(Math.random() * 100000000) +
Date.now() +
file.size +
"_tmp"
)
}
loadChunk() {
return new Promise((resolve) => {
const start = this.chunkCount * this.splitChunkSize
const end = Math.min(start + this.splitChunkSize, this.file.size)
this._reader.onload = () =>
resolve(
new Blob([this._reader.result], {
type: "application/octet-stream",
}),
)
this._reader.readAsArrayBuffer(this.file.slice(start, end))
})
}
async sendChunk() {
const form = new FormData()
form.append("file", this.chunk)
this.headers["uploader-chunk-number"] = this.chunkCount
console.log(`[UPLOADER] Sending chunk ${this.chunkCount}`, {
currentChunk: this.chunkCount,
totalChunks: this.totalChunks,
})
try {
const res = await fetch(this.endpoint, {
method: "POST",
headers: this.headers,
body: form,
})
return res
} catch (error) {
this.manageRetries()
}
}
manageRetries() {
if (++this.retriesCount < this.maxRetries) {
setTimeout(() => this.nextSend(), this.delayBeforeRetry * 1000)
this.events.emit("fileRetry", {
message: `Retrying chunk ${this.chunkCount}`,
chunk: this.chunkCount,
retriesLeft: this.retries - this.retriesCount,
})
} else {
this.events.emit("error", {
message: `No more retries for chunk ${this.chunkCount}`,
})
}
}
async nextSend() {
if (this.paused || this.offline) {
return null
}
this.chunk = await this.loadChunk()
const res = await this.sendChunk()
const data = await res.json()
if ([200, 201, 204].includes(res.status)) {
console.log(`[UPLOADER] Chunk ${this.chunkCount} sent`)
this.chunkCount = this.chunkCount + 1
if (this.chunkCount < this.totalChunks) {
this.nextSend()
}
// check if is the last chunk, if so, handle sse events
if (this.chunkCount === this.totalChunks) {
if (data.eventChannelURL) {
console.log(
`[UPLOADER] Connecting to SSE channel >`,
data.eventChannelURL,
)
const eventSource = new EventSource(data.eventChannelURL)
eventSource.onerror = (error) => {
this.events.emit("error", error)
}
eventSource.onopen = () => {
console.log(`[UPLOADER] SSE channel opened`)
}
eventSource.onmessage = (event) => {
// parse json
const messageData = JSON.parse(event.data)
console.log(`[UPLOADER] SSE Event >`, messageData)
if (messageData.status === "done") {
this.events.emit("finish", messageData.result)
eventSource.close()
}
if (messageData.status === "error") {
this.events.emit("error", messageData.result)
}
if (messageData.status === "progress") {
this.events.emit("progress", {
percentProgress: messageData.progress,
})
}
}
} else {
this.events.emit("finish", data)
}
}
this.events.emit("progress", {
percentProgress: Math.round(
(100 / this.totalChunks) * this.chunkCount,
),
})
} else if ([408, 502, 503, 504].includes(res.status)) {
this.manageRetries()
} else {
this.events.emit("error", {
message: `[${res.status}] ${data.error ?? data.message}`,
})
}
}
togglePause() {
this.paused = !this.paused
if (!this.paused) {
return this.nextSend()
}
}
}

View File

@ -1,108 +1,117 @@
import { Core } from "@ragestudio/vessel" import { Core } from "@ragestudio/vessel"
import ChunkedUpload from "./chunkedUpload" import ChunkedUpload from "@classes/ChunkedUpload"
import SessionModel from "@models/session" import SessionModel from "@models/session"
export default class RemoteStorage extends Core { export default class RemoteStorage extends Core {
static namespace = "remoteStorage" static namespace = "remoteStorage"
static depends = ["api", "tasksQueue"] static depends = ["api", "tasksQueue"]
public = { public = {
uploadFile: this.uploadFile, uploadFile: this.uploadFile,
getFileHash: this.getFileHash, getFileHash: this.getFileHash,
binaryArrayToFile: this.binaryArrayToFile, binaryArrayToFile: this.binaryArrayToFile,
} }
binaryArrayToFile(bin, filename) { binaryArrayToFile(bin, filename) {
const { format, data } = bin const { format, data } = bin
const filenameExt = format.split("/")[1] const filenameExt = format.split("/")[1]
filename = `${filename}.${filenameExt}` filename = `${filename}.${filenameExt}`
const byteArray = new Uint8Array(data) const byteArray = new Uint8Array(data)
const blob = new Blob([byteArray], { type: data.type }) const blob = new Blob([byteArray], { type: data.type })
return new File([blob], filename, { return new File([blob], filename, {
type: format, type: format,
}) })
} }
async getFileHash(file) { async getFileHash(file) {
const buffer = await file.arrayBuffer() const buffer = await file.arrayBuffer()
const hash = await crypto.subtle.digest("SHA-256", buffer) const hash = await crypto.subtle.digest("SHA-256", buffer)
const hashArray = Array.from(new Uint8Array(hash)) const hashArray = Array.from(new Uint8Array(hash))
const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("") const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, "0"))
.join("")
return hashHex return hashHex
} }
async uploadFile( async uploadFile(
file, file,
{ {
onProgress = () => { }, onProgress = () => {},
onFinish = () => { }, onFinish = () => {},
onError = () => { }, onError = () => {},
service = "standard", service = "standard",
headers = {}, headers = {},
} = {}, } = {},
) { ) {
return await new Promise((_resolve, _reject) => { return await new Promise((_resolve, _reject) => {
const fn = async () => new Promise((resolve, reject) => { const fn = async () =>
const uploader = new ChunkedUpload({ new Promise((resolve, reject) => {
endpoint: `${app.cores.api.client().mainOrigin}/upload/chunk`, const uploader = new ChunkedUpload({
splitChunkSize: 5 * 1024 * 1024, endpoint: `${app.cores.api.client().mainOrigin}/upload/chunk`,
file: file, splitChunkSize: 5 * 1024 * 1024,
service: service, file: file,
headers: { service: service,
...headers, headers: {
"provider-type": service, ...headers,
"Authorization": `Bearer ${SessionModel.token}`, "provider-type": service,
}, Authorization: `Bearer ${SessionModel.token}`,
}) },
})
uploader.events.on("error", ({ message }) => { uploader.events.on("error", ({ message }) => {
this.console.error("[Uploader] Error", message) this.console.error("[Uploader] Error", message)
app.cores.notifications.new({ app.cores.notifications.new(
title: "Could not upload file", {
description: message title: "Could not upload file",
}, { description: message,
type: "error" },
}) {
type: "error",
},
)
if (typeof onError === "function") { if (typeof onError === "function") {
onError(file, message) onError(file, message)
} }
reject(message) reject(message)
_reject(message) _reject(message)
}) })
uploader.events.on("progress", ({ percentProgress }) => { uploader.events.on("progress", ({ percentProgress }) => {
if (typeof onProgress === "function") { if (typeof onProgress === "function") {
onProgress(file, percentProgress) onProgress(file, percentProgress)
} }
}) })
uploader.events.on("finish", (data) => { uploader.events.on("finish", (data) => {
this.console.debug("[Uploader] Finish", data) this.console.debug("[Uploader] Finish", data)
app.cores.notifications.new({ app.cores.notifications.new(
title: "File uploaded", {
}, { title: "File uploaded",
type: "success" },
}) {
type: "success",
},
)
if (typeof onFinish === "function") { if (typeof onFinish === "function") {
onFinish(file, data) onFinish(file, data)
} }
resolve(data) resolve(data)
_resolve(data) _resolve(data)
}) })
}) })
app.cores.tasksQueue.appendToQueue(`upload_${file.name}`, fn) app.cores.tasksQueue.appendToQueue(`upload_${file.name}`, fn)
}) })
} }
} }