wip debug settings

Seto Elkahfi committed Aug 2, 2024 at 17:06 UTC 3aa20839decb60d9ddef43890084c18421c86ddc
12 files changed +345 -64
frontend/splitfire-desktop/.gitignore
+3
@@ -18,3 +18,6 @@
18 npm-debug.log*
19 yarn-debug.log*
20 yarn-error.log*
21 +
22 +# Debug settings
23 +!app/debug
\ No newline at end of file
frontend/splitfire-desktop/app/_src/lib/db.ts
+83 -56
@@ -1,58 +1,62 @@
1 -import Dexie, { Table } from 'dexie';
2 -import { Mode, ModeDemucs } from '../components/player/models/Mode';
3 -import User from '../models/user';
4 -import { AudioFile } from '../components/player/models/AudioFile';
5 -import axios from './axios';
6 -import { Result } from '../components/player/models/Result';
7 -import { Logger } from 'tslog';
8 -import isTauri from './isTauri';
1 +import Dexie, { Table } from "dexie";
2 +import { Mode, ModeDemucs } from "../components/player/models/Mode";
3 +import User from "../models/user";
4 +import { AudioFile } from "../components/player/models/AudioFile";
5 +import axios from "./axios";
6 +import { Result } from "../components/player/models/Result";
7 +import { Logger } from "tslog";
8 +import isTauri from "./isTauri";
9
10 // Legacy Spleeter result file
11 export interface AudioFiles {
12 id?: number;
13 - audioFileId: string,
14 - type: Mode,
15 - file: Blob
13 + audioFileId: string;
14 + type: Mode;
15 + file: Blob;
16 }
17
18 export enum CurrentUserType {
19 - MAIN, GUEST
19 + MAIN,
20 + GUEST,
21 }
22
23 export interface CurrentUser {
23 - accessToken: string
24 - type: CurrentUserType
25 - mode: Mode
26 - user: User
24 + accessToken: string;
25 + type: CurrentUserType;
26 + mode: Mode;
27 + user: User;
28 }
29
30 // Splitfire result file
31 interface ResultFile {
32 id?: number;
32 - audioFileId: number,
33 - type: ModeDemucs,
34 - buffer: ArrayBuffer,
35 - blobType: string
33 + audioFileId: number;
34 + type: ModeDemucs;
35 + buffer: ArrayBuffer;
36 + blobType: string;
37 }
38
39 export interface BlobFile {
39 - file: Blob
40 - mode: ModeDemucs
40 + file: Blob;
41 + mode: ModeDemucs;
42 }
43
44 export class SplitfireDB extends Dexie {
45 + audioFiles!: Table<AudioFiles>;
46 + resultFiles!: Table<ResultFile>;
47 + currentUser!: Table<CurrentUser>;
48
45 - audioFiles!: Table<AudioFiles>
46 - resultFiles!: Table<ResultFile>
47 - currentUser!: Table<CurrentUser>
48 - log = new Logger({ name: 'SplitfireDB', type: isTauri() ? 'json' : 'pretty' });
49 + log = new Logger({
50 + name: "SplitfireDB",
51 + type: isTauri() ? "json" : "pretty",
52 + });
53
54 constructor() {
51 - super('splitfireDB');
52 - this.version(9).stores({
53 - audioFiles: '++id, [audioFileId+type]', // This is when I was using Spleeter
54 - resultFiles: '++id, [audioFileId+type]', // This is when I was using Demucs
55 - currentUser: '++id, [userId+type], type'
55 + super("splitfireDB");
56 + this.version(10).stores({
57 + audioFiles: "++id, [audioFileId+type]", // This is when I was using Spleeter
58 + resultFiles: "++id, [audioFileId+type]", // This is when I was using Demucs
59 + currentUser: "++id, [userId+type], type",
60 });
61 }
62
@@ -62,60 +66,76 @@ export class SplitfireDB extends Dexie {
66 return new Promise(async (resolve, reject) => {
67 try {
68 // Check modes of results
65 - const modes: ModeDemucs[] = audioFile.results.map(result => result.filename.split('-').shift() as ModeDemucs)
69 + const modes: ModeDemucs[] = audioFile.results.map(
70 + (result) => result.filename.split("-").shift() as ModeDemucs
71 + );
72
67 - this.log.error('modes', modes);
73 + this.log.error("modes", modes);
74 let resultFiles: BlobFile[] = [];
75 // Check if we have result file in the db
76 for (const mode of modes) {
77 const result = await this._getResultFile(audioFile, mode);
72 - resultFiles.push(result)
78 + resultFiles.push(result);
79 }
74 - this.log.debug('resultFiles', resultFiles);
75 - resolve(resultFiles)
80 + this.log.debug("resultFiles", resultFiles);
81 + resolve(resultFiles);
82 } catch (error) {
83 this.log.error(error);
78 - reject(error)
84 + reject(error);
85 }
80 - })
86 + });
87 }
88
83 - async _getResultFile(audioFile: AudioFile, mode: ModeDemucs): Promise<BlobFile> {
89 + async _getResultFile(
90 + audioFile: AudioFile,
91 + mode: ModeDemucs
92 + ): Promise<BlobFile> {
93 return new Promise(async (resolve, reject) => {
94 try {
95 // Check if we have result file in the db
87 - const result = await db.resultFiles.get({ audioFileId: audioFile.id, type: mode })
96 + const result = await db.resultFiles.get({
97 + audioFileId: audioFile.id,
98 + type: mode,
99 + });
100 // if we do, then return it
89 - this.log.debug('_getResultFile', result);
101 + this.log.debug("_getResultFile", result);
102 if (result?.buffer) {
91 - const blob = new Blob([result.buffer], { type: result.blobType })
92 - resolve({file: blob, mode})
103 + const blob = new Blob([result.buffer], { type: result.blobType });
104 + resolve({ file: blob, mode });
105 } else {
106 // otherwise, download the file and return it
107 const file = await this._downloadFile(audioFile, mode);
96 - resolve({file, mode})
108 + resolve({ file, mode });
109 }
110 } catch (error) {
99 - reject(error)
111 + reject(error);
112 }
113 });
114 }
115
116 async _downloadFile(audioFile: AudioFile, type: ModeDemucs): Promise<Blob> {
105 - this.log.debug('_downloadFile', type);
117 + this.log.debug("_downloadFile", type);
118 let result: Result | undefined;
119 switch (type) {
120 case ModeDemucs.Vocals:
109 - result = audioFile.results.find(obj => { return obj.filename.startsWith('vocals') });
121 + result = audioFile.results.find((obj) => {
122 + return obj.filename.startsWith("vocals");
123 + });
124 break;
125 case ModeDemucs.Bass:
112 - result = audioFile?.results.find(obj => { return obj.filename.startsWith('bass') });
126 + result = audioFile?.results.find((obj) => {
127 + return obj.filename.startsWith("bass");
128 + });
129 break;
130 case ModeDemucs.Drums:
115 - result = audioFile?.results.find(obj => { return obj.filename.startsWith('drums') });
131 + result = audioFile?.results.find((obj) => {
132 + return obj.filename.startsWith("drums");
133 + });
134 break;
135 case ModeDemucs.Other:
118 - result = audioFile?.results.find(obj => { return obj.filename.startsWith('other') });
136 + result = audioFile?.results.find((obj) => {
137 + return obj.filename.startsWith("other");
138 + });
139 break;
140 }
141
@@ -123,12 +143,19 @@ export class SplitfireDB extends Dexie {
143
144 return new Promise(async (resolve, reject) => {
145 try {
126 - const response = await axios({ url: result?.source_file, method: 'GET', responseType: 'blob' })
127 - const blob = new Blob([response.data], { type: 'audio/mp3' })
128 - const buffer = await blob.arrayBuffer()
146 + const response = await axios({
147 + url: result?.source_file,
148 + method: "GET",
149 + responseType: "blob",
150 + });
151 + const blob = new Blob([response.data], { type: "audio/mp3" });
152 + const buffer = await blob.arrayBuffer();
153 const item: ResultFile = {
130 - audioFileId: audioFile.id, type: type, buffer: buffer, blobType: blob.type
131 - }
154 + audioFileId: audioFile.id,
155 + type: type,
156 + buffer: buffer,
157 + blobType: blob.type,
158 + };
159 db.resultFiles.add(item);
160 resolve(blob);
161 } catch (error) {
@@ -139,4 +166,4 @@ export class SplitfireDB extends Dexie {
166 }
167 }
168
142 -export const db = new SplitfireDB();
\ No newline at end of file
169 +export const db = new SplitfireDB();
frontend/splitfire-desktop/app/_src/lib/tauriHandler.ts
+4
@@ -23,3 +23,7 @@ export const TAURI_CONTENT_READY_TO_PLAY = 'content_ready_to_play'
23 export const TAURI_CONTENT_SONG_BRIDGE_DETAIL = 'content_song_bridge_detail'
24 export const TAURI_CONTENT_TOP_VOTED = 'content_top_voted'
25
26 +// Settings
27 +export const TAURI_SET_ENVIRONMENT = 'set_environment'
28 +export const TAURI_GET_ENVIRONMENT = 'get_environment'
29 +
frontend/splitfire-desktop/app/_ui/global-nav.tsx
+14
@@ -72,6 +72,12 @@ export function GlobalNav() {
72 </div>
73
74 {user.user ? <LoggedInNav /> : <LoggedOutNav />}
75 +
76 + <div className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-gray-400/80">
77 + <div>{"Settings"}</div>
78 + </div>
79 +
80 + <SettingsNav />
81 </nav>
82 </div>
83 </div>
@@ -96,6 +102,14 @@ function LoggedInNav() {
102 );
103 }
104
105 +function SettingsNav() {
106 + return (
107 + <div className="space-y-1">
108 + <GlobalNavItem item={{ name: 'Debug', slug: 'debug' }} close={() => {}} />
109 + </div>
110 + );
111 +}
112 +
113 function GlobalNavItem({
114 item,
115 close,
frontend/splitfire-desktop/app/debug/layout.tsx new
+18
@@ -0,0 +1,18 @@
1 +import React from 'react';
2 +
3 +export default async function Layout({
4 + children,
5 +}: {
6 + children: React.ReactNode;
7 +}) {
8 + return (
9 + <div className="space-y-9">
10 + <div className="flex justify-between">
11 + <div className="self-start">
12 + <h1 className="text-3xl font-bold">Debug Settings</h1>
13 + </div>
14 + </div>
15 + <div>{children}</div>
16 + </div>
17 + );
18 +}
frontend/splitfire-desktop/app/debug/page.tsx new
+128
@@ -0,0 +1,128 @@
1 +"use client";
2 +
3 +import * as Form from "@radix-ui/react-form";
4 +import { invoke } from "@tauri-apps/api/tauri";
5 +import {
6 + TAURI_GET_ENVIRONMENT,
7 + TAURI_SET_ENVIRONMENT,
8 +} from "../_src/lib/tauriHandler";
9 +import { Button } from "../_ui/components/button";
10 +import { useEffect, useState } from "react";
11 +import { LoadingView } from "../_src/components/templates/LoadingView";
12 +import { TauriResponse } from "@/models/shared";
13 +import { useLogger } from "../_src/lib/logger";
14 +
15 +enum State {
16 + LOADING,
17 + LOADED,
18 + ERROR,
19 +}
20 +enum Environment {
21 + DEVELOPMENT = "development",
22 + PRODUCTION = "production",
23 +}
24 +
25 +interface UpdateSettingsResponse {
26 + status: TauriResponse;
27 +}
28 +
29 +export default function Page() {
30 + const log = useLogger("Debug settings");
31 + const [state, setState] = useState(State.LOADED);
32 + const [environment, setEnvironment] = useState<Environment>(
33 + Environment.PRODUCTION
34 + );
35 +
36 + const updateEnvironment = async () => {
37 + try {
38 + setState(State.LOADING);
39 + const payload = { environment };
40 + log.debug("payload", payload);
41 + const response: UpdateSettingsResponse = await invoke(
42 + TAURI_SET_ENVIRONMENT,
43 + payload
44 + );
45 + log.debug("response", response);
46 + setState(State.LOADED);
47 + } catch (error) {
48 + log.error("Error updating environment", error);
49 + setState(State.ERROR);
50 + }
51 + };
52 +
53 + const onSelectedEnvironmentChange = (value: string) => {
54 + log.debug("Selected environment", value);
55 + setEnvironment(value as Environment);
56 + };
57 +
58 + useEffect(() => {
59 + log.debug("Debug settings page loaded.");
60 + async function fetchData() {
61 + try {
62 + const environment: Environment = await invoke(TAURI_GET_ENVIRONMENT);
63 + log.debug("Current environment settings", environment);
64 + setEnvironment(environment);
65 + setState(State.LOADED);
66 + } catch (error) {
67 + log.error(error);
68 + setState(State.ERROR);
69 + }
70 + }
71 + fetchData();
72 + // eslint-disable-next-line react-hooks/exhaustive-deps
73 + }, []);
74 +
75 + if (state === State.LOADING) {
76 + return <LoadingView />;
77 + }
78 +
79 + return (
80 + <div className="prose prose-sm prose-invert max-w-none">
81 + <div className="flex grid-rows-1">
82 + <Form.Root
83 + className="FormRoot"
84 + onSubmit={(event) => {
85 + event.preventDefault();
86 + }}
87 + >
88 + <Form.Field className="FormField my-6" name="Environment">
89 + <div
90 + style={{
91 + display: "flex",
92 + alignItems: "baseline",
93 + justifyContent: "space-between",
94 + }}
95 + >
96 + <Form.Label className="FormLabel">Environment</Form.Label>
97 + </div>
98 + <Form.Control asChild className="my-6">
99 + <select
100 + onChange={(e) =>
101 + onSelectedEnvironmentChange(e.currentTarget.value)
102 + }
103 + >
104 + <option
105 + value={Environment.DEVELOPMENT}
106 + selected={environment === Environment.DEVELOPMENT}
107 + >
108 + Development
109 + </option>
110 + <option
111 + value={Environment.PRODUCTION}
112 + selected={environment === Environment.PRODUCTION}
113 + >
114 + Production
115 + </option>
116 + </select>
117 + </Form.Control>
118 + </Form.Field>
119 + <Form.Field className="FormField my-6" name="password">
120 + <Button onClick={updateEnvironment} variant={"outline"} size={"lg"}>
121 + Update
122 + </Button>
123 + </Form.Field>
124 + </Form.Root>
125 + </div>
126 + </div>
127 + );
128 +}
frontend/splitfire-desktop/src-tauri/src/command/constants.rs
+8 -7
@@ -1,4 +1,6 @@
1 +use once_cell::sync::OnceCell;
2 use url_builder::URLBuilder;
3 +use super::settings::api_host;
4
5 // Client ID and Client Secret
6 #[cfg(debug_assertions)]
@@ -10,12 +12,6 @@ pub const SF_CLIENT_SECRET: &str = "secretttttttt";
12 #[cfg(not(debug_assertions))]
13 pub const SF_CLIENT_SECRET: &str = "secretttttttt";
14
13 -// BASE_URL
14 -#[cfg(debug_assertions)]
15 -pub const API_HOST: &str = "localhost:3001";
16 -#[cfg(not(debug_assertions))]
17 -pub const API_HOST: &str = "api.musik88.com";
18 -
15 // Paths
16 pub const PATH_AUDIO: &str = "api/v1/splitfire";
17
@@ -36,8 +32,13 @@ pub fn base_url_builder() -> URLBuilder {
32 let mut url_builder = URLBuilder::new();
33 url_builder
34 .set_protocol("https")
39 - .set_host(API_HOST)
35 + .set_host(api_host())
36 .add_param("client_id", SF_CLIENT_ID)
37 .add_param("client_secret", SF_CLIENT_SECRET);
38 url_builder
39 }
40 +
41 +pub static mut CURRENT_API_HOST: OnceCell<&str> = OnceCell::new();
42 +// DEFAULT BASE_URL
43 +pub const API_HOST_PRODUCTION: &str = "api.musik88.com";
44 +pub const API_HOST_DEVELOPMENT: &str = "localhost:3001";
\ No newline at end of file
frontend/splitfire-desktop/src-tauri/src/command/mod.rs
+1
@@ -6,3 +6,4 @@ pub mod player_prepare;
6 pub mod player_record;
7 pub mod player_set_volume;
8 pub mod player_unmount;
9 +pub mod settings;
frontend/splitfire-desktop/src-tauri/src/command/settings.rs new
+52
@@ -0,0 +1,52 @@
1 +use log::{debug, warn};
2 +use crate::{command::constants::API_HOST_DEVELOPMENT, models::{player::TauriResponse, settings::{Environment, SetEnvironmentResponse}}};
3 +use super::constants::{API_HOST_PRODUCTION, CURRENT_API_HOST};
4 +
5 +#[tauri::command]
6 +pub fn set_environment(environment: Environment) -> SetEnvironmentResponse {
7 + debug!("set_environment: {}", environment);
8 + let api_host = match environment {
9 + Environment::Development => &API_HOST_DEVELOPMENT,
10 + Environment::Production => &API_HOST_PRODUCTION,
11 +
12 + };
13 + debug!("Will set API host to: {}", api_host);
14 + unsafe {
15 + match CURRENT_API_HOST.set(api_host) {
16 + Ok(_) => debug!("Did set CURRENT_API_HOST."),
17 + Err(e) => warn!("Failed to set CURRENT_API_HOST: {:?}", e),
18 + };
19 + };
20 +
21 + SetEnvironmentResponse {
22 + status: TauriResponse::Success,
23 + message: "CURRENT_API_HOST set done.".to_string(),
24 + }
25 +}
26 +
27 +#[tauri::command]
28 +pub fn get_environment() -> Option<Environment> {
29 + let env = match api_host() {
30 + API_HOST_DEVELOPMENT => Environment::Development,
31 + API_HOST_PRODUCTION => Environment::Production,
32 + _ => {
33 + warn!("Unknown api host!!!");
34 + return None;
35 + },
36 + };
37 + debug!("Current environment: {}", env);
38 + Some(env)
39 +}
40 +
41 +pub fn api_host() -> &'static str {
42 + match unsafe { CURRENT_API_HOST.get() } {
43 + Some(host) => {
44 + debug!("CURRENT_API_HOST: {}", host);
45 + host
46 + },
47 + None => {
48 + warn!("API host not set. Defaulting to production.");
49 + API_HOST_PRODUCTION
50 + },
51 + }
52 +}
\ No newline at end of file
frontend/splitfire-desktop/src-tauri/src/main.rs
+5
@@ -16,6 +16,9 @@ use app::{
16 },
17 player_set_volume::{__cmd__player_set_volume, player_set_volume},
18 player_unmount::{__cmd__player_unmount, player_unmount},
19 + settings::{
20 + __cmd__get_environment, __cmd__set_environment, get_environment, set_environment,
21 + },
22 },
23 rest::{
24 account::{
@@ -61,6 +64,8 @@ fn main() {
64 Ok(())
65 })
66 .invoke_handler(tauri::generate_handler![
67 + set_environment,
68 + get_environment,
69 account_login,
70 account_logout,
71 account_register,
frontend/splitfire-desktop/src-tauri/src/models/mod.rs
+2 -1
@@ -1,3 +1,4 @@
1 pub mod account;
2 pub mod player;
3 -pub mod content;
\ No newline at end of file
3 +pub mod content;
4 +pub mod settings;
\ No newline at end of file
frontend/splitfire-desktop/src-tauri/src/models/settings.rs new
+27
@@ -0,0 +1,27 @@
1 +use serde::{Deserialize, Serialize};
2 +use super::player::TauriResponse;
3 +
4 +#[derive(Serialize, Deserialize)]
5 +pub struct SetEnvironmentResponse {
6 + pub status: TauriResponse,
7 + pub message: String,
8 +}
9 +
10 +#[derive(Serialize, Deserialize)]
11 +#[derive(Debug)]
12 +pub enum Environment {
13 + #[serde(rename = "development")]
14 + Development,
15 + #[serde(rename = "production")]
16 + Production,
17 +}
18 +
19 +impl std::fmt::Display for Environment {
20 + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
21 + let env = match self {
22 + Environment::Development => "development",
23 + Environment::Production => "production",
24 + };
25 + write!(f, "{}", env)
26 + }
27 +}