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
+}