~Yv-O4s1_k@?f4#q>FnQ(Ht)=NoMYEq2
z^=k$%yJKx*^QTJU?Gz7{X95?u%6(Zq``52u@4oEbmwkO*X;gIdcI8EJ{atey9;~Ra
zxp(X0#g8X$ZOxv$;dj^z<%SR~Q|0NWZJ8d_+pK$^pdz$$MVIeKrAr-LivMQ#Emvjw%@PQ^O@>C&5QYQi5c+PU-R^Md-x7x(vSpQ-uw;>8Q$w!}AUSPtB+`Cp*<^fSY^
zZ;5gL_KIqUtyvuwopZZRq(Qv9H&gRzzLVJA%%3&?%0HytE;_bpO?}wvN1%p>@PX~M
zFXD@5{FFRwV7Hp(!QXEmN_ItF{~9lFQTD?FaRPqs$`vVN$h?j
zm1jz^*Vi+B(Ck08nYH-h%l-dD%W`&+fn8RTm5D25=s*FyKjE_SnLo!9w<5
zPNS@_Q;lZFlygoU@yroV0}It9+X|iY^Yc|sOuTQa%ChmhM`2o@@60)-1t*>tB-@@9-)-e4KpF(D(B?g
z`&h4Z`E0Y?TYq0)U#}lx={4IX_0*JTP$PK5PwQowc5)MPuN`~*@j9c-v&+h53YTqwG5Z!tm?%MtlO5G&sQO^OYnerZQYq%my$)9vkp7{j(ZT?P-1m=
z^7-fUKivK~v$D``{{Ns0Z)3M+g~oZk{Hi!W+M@6C(h3{B*|TQFNl8j-Rs|M-T9e8((#9L}l_#FqXXo#_+~BkL<32~#(
z*H?d7p?Wmw0I8Je9sB5&qwv-kPtx%~2E+2cE<57dQL+!39YI`^UhXdrLDi;H>4!@&K?&x8*N
zMD05q^NU0A$lK5jhou?hR^GY#T}v?XXj0+Z=yE*f(>w}sXU
zZV1qjdA#}adbLH(I;VdLW<7|?^}W3B+?j6a3;*S3TNFMr&sw->Tdux{(;Ie$=g(&~
z2rPQl8Q=7St6)NZs=Y$Xjk_ji_h0W4PcfRgF=*u%&$PH}4(I-FdTi0I8@+8!fVSwl
zw9VbJ$7lMw?2`>rTGW4SFN45DdCe(aE?;U-Z{jGvK@l3#n&^8fFdLIM}L_~WkSUR_tMAl+CJTQ79L
zJw|i70DIczo9%(mRVJT2tTlUC*4t?hdKvsS=)B%mwSD5c*S{+SZrbHXUVD>u-FCuZ
z2bYr7>{*p;QVpl{cHY#GpS+H<u@S~B^dL65(|=cf!`CWKv7S!C>cdC&U5oJ%)vTG~qWMitED4!pdjL~Zg(GZ&T#
zCsWuMcEmop)FQBaQKNzH=X*&TZyY}JTp)0U*cG-JKXcOFWvV=9Z^$sIlDebrRBOcX
zoZp8*tK#F&C)$Z;w?3P_oR_EP*0$Q20!l~EEDsUNWN~FXaB^dWPU`J%hc?LV`qZ6b
zWXUME#94WfbaR$-!tqA!$cC^(we$W@);+z8t(QAgY+)a3fk=aLKANr*e%bDDp#8<>D)ZoM@^kWk#97!N4Bd@0v_dbro@(*DB?*QT6*KK*cQ*7j`O(^i@*%2;Lwin)Bz
zX2@ez2-KSD$GBm`MeEd~hVNJZIe2EKak++A_h!a3EE~8_Np>t_oY7LGG--R@*1I8+
zH}A7-U^~DyNl~U<5zT9@H>*ppKI0YU&00Tr{C{dwoGkt
z(aC0~huUx7z76pDZca0}!~NhWtGKlYSL^1`>meb{5jtht*w67R
z?b3R&jYW^)V?|=bv;7PV+mQgll)Sv8Wc>e5^CC4*&GX^0ArIAOEvprl%Q>C)%=^#;0aX$4<`BKp8yI~`Bd^Su
zQdFilz1p=jwDr0~o8jh3Co7vyFETrlwDHKM)YDAU|Fz!eg1@*`X5*9m|t2}<`;f9>>(>?=88eQDp1TgUTB$jmQ$(ij~6dq
zs4|3A2V9wwVNx~o)h;gor20kL4}9&~%xBG>T`#w4TWdhB|Hlu%UnEXTtt{%>S5%g0
zXMA3Lk;$p=$4VdCf4{dr`B|Nkl)|F$nLj@!ZT#`&z<~oRik5!7eDkJczuRK&8I}4<
zzZe?Motpnm_uqBLDZ(qiF;?&%5I*zA|Ms@{?%(++6Cvd3fy0n)Pg|7sI{Qd-v{n
z=uNMdycaWP=d78}bF$MtmIfKm_{n{y#-hRU#<#VXS<~v+o;sUsuefUYy0^q?uGUnq
z`Ra=nGkn?cX1+GV9o?24hO^J^+p~6|y`U{Szu8}g3SGJW?>e!&wya=zU~9nhc>m0w
zW&O`$_-}8k6<|oW`TzgU&f>+*&CTvcpFcA2eMp@3grQpX`mCd~Z)eT9^J`FP~%jz{=pL
zDqh*s;kRP?oS%<3yltQH^E$&b)(}?q19P1^PH(#T=5aLVUw*Ykziqqz9b-7NNhiBf
zB6nfy*2z44eCzs;KNe)zd(Wk@#3MTT_F89W=fhL|c9dslzt(RRm>z1CWOCq{8pE6$
zw{AsANl8Tot-t7ypP$ci;3q>}{85eU)fd?t_>De){8;fPc%zU*)n*;3R<=wQ~DgXOq#i#d1YPDspE6o;fd!>)`zDy(PC=u;I1zn#W681p3v5I)84xCl-7C
z`_v%4Jd@|_1-Uoo?pI@BVEFX&&rOZ`>$xhoS_JAnRfLMOwlbbfDJo|uUpS{}%|wIQ
zvu2%J-P(1ng<(zP=Cqa$JN^YX5@a*G1Rgb}813|48+yl}XQs;+hEqG)r8|X;>-U9S
zy4)F6xq4ZqowJL*<730A9q&L*mh2Nhmm7SJ|1ke{nRMc>Z_2l~O~2w*>#Do=&boE$
z!A@SssM*zWDQ+;ml?o?d2>7{tJ9Qa4L;=?Ln)XZfWoMUMDUBl~rClnf~4P6ERaU
zme0EVjd^QWVPJRuZIiS6nSb~(_ZdxY6}Zp6R@~#~V=c@7vo+#Vgr=Q%6TS7tA5&?@
zJ)z}?4c@Jgxw=+OHSM2t1N+RMo(yGr)1!GEUcY&B$55(w8AA=rf#ZyC`0M?I&u!It
z-D)7IJkjHfpp(s8=j!7fFY40j7!FLTv6~;eIqj@Rs+EPKS0;y|9n*tkmlBIrHS?Nh
zzTc4h+vCcMZ*OPai=FPbT-{r9e!?^Uu!n`K8DmbKx!!Be;=laz+@MpRXZ%cI(3QLP
z)PGv4tmvl9o3dwWTBcu?W#Hy+J#^?}Ubi2(RcI-8V5UZV`!jQWb5ql|p{LD*uKxKJ
z6?-A5Gexmo4|*MQNe%#Zo!dsVogbf=hiazu}(9yXixo@3VVJ
z8!Jv7IB?)p?%Z2aZ6D*$)R>)eKlS&7;f6^a&(b&F{1^3Y{;nJG@i(*oN$j~@qQrQ|
z--X-u&N@6r?Q!a7fYtSj}qMdV|2Cw+ToAxcq`Eu0hO=X8H
zjAegzEPMScYsda8tY>Pb<=&qDJMd(QmF?BSIg=S`L=X7sb!iD)RGiwHp~1)@crxXa
zh0?A>>0471pT(P)o8J%U{W)jd>+le(McOR_+?#H`dDO6Y{oW;0`l|!39G#Z>vupcGWe{kT92vXHpgyh$1h*gn#DBkSxd4yN7yt{4RY@R;GvhNJe=1N>4xz1^_vYY@{1KF}PH^W`nI9T{wb
zo|;ehyScl|SHHF{;%Ipl$B^e1ke-v1Q}i_N=qk{hf$Z^{ExG>F`54NhrIUj1U=y?ie=)d5%?|
z&b)c`O-(6kOUA`TlU}iTF-S>nE>!9n8Fa4J}zm^59%*@-)C_FKWfrnA>E7u`K2A6f`Qf0N4#w>pQ%b?*p
zLrQ0cX@ecZdgcgkmu43e6DupLC7H8iq@}g@lxl4P%{dkq7f*g{v1sYirBi1{&)n(j
zwX<0uNnP_PkJ2lLpWEio3_7J52VIZWuq^X$ke7bfET#mxhRQR0mL6sVP53Y{Y>eR9
zwJtxB-=gpHw?L^gJkuY2@ngEMTxb0<1}=u6Q*F|`wl^481~3K4A9-B3eRgA3-`Uyb
zt4$eH8MqiCc9mob9+2j}^5m;ys$q}@BZv0iRcpCa8F;;#*LJP`xnGFk=xs%%-Ho%d
zwY#+$1ODu0(8-;$&FHf+c->#Vd5gezCW*|3Wqn?n@58`L1EpvBq$xLeODBAZk2qiX
zZ7PGtHc8*hdJG>zWc`chv3{sceJOC$Wp7i##j97ZF3Y@im*LyG%@gYjH%9!q0vZm}
z)&{LQWf)e!THv|5^TnPNqn-1M>O>he?k#sv7wWV%@tv)|bdI}=
zJlh1mpC0o?oovpYInSXuEoJjf)r<|f=e!t1>ih-m&YaoIct&N>T*fmj7vi3-+QXQ@
zYP?>+X^+~{nsp&swzdWa8yq7&IU5$8T5|d2$)Hm+&&*-jpe*P+HONoY>CKTd!83PG
z?T8AnW3pTMY4f&I_w(1D+RwS*$eG11CN3`7f#=jeuo {
+const RecreateDbButton: React.FC<{ db: DbStorage, title: string }> = ({ db, title }) => {
+ const [inProgress, setInProgress] = useState(false);
- const recreateMusicDb = async () => {
- try { await musicDb.deleteDb(); } catch {}
- await musicDb.createDb();
- }
-
- const recreateSettingsDb = async () => {
- try { await settingsDb.deleteDb(); } catch {}
- await settingsDb.createDb();
+ const recreateDb = async () => {
+ setInProgress(true);
+ try{
+ try { await db.deleteDb(); } catch {}
+ await db.createDb();
+ } finally {
+ setInProgress(false);
+ }
}
+ return (
+
+ )
+}
+
+const DbControls = () => {
return (
-
-
+
+
+ );
+}
+
+const ServerSettingsView = () => {
+ const [servers, setServers] = useRecoilState(serversState);
+ const [appSettings, setAppSettings] = useRecoilState(appSettingsState);
+
+ const bootstrapServer = () => {
+ if (servers.length !== 0) {
+ return;
+ }
+
+ const id = uuidv4();
+ const salt = uuidv4();
+ const address = 'http://demo.subsonic.org';
+
+ setServers([{
+ id, salt, address,
+ username: 'guest',
+ token: md5('guest' + salt),
+ }]);
+
+ setAppSettings({
+ server: id,
+ });
+ };
+
+ return (
+
+
+ {servers.map(s => (
+
+ {s.address}
+ {s.username}
+
+ ))}
);
}
@@ -31,6 +82,9 @@ const DbControls = () => {
const SettingsView = () => (
+ Loading...}>
+
+
)
diff --git a/src/state/artists.ts b/src/state/artists.ts
index fc71af6..9d8c24a 100644
--- a/src/state/artists.ts
+++ b/src/state/artists.ts
@@ -1,16 +1,11 @@
import md5 from 'md5';
-import { atom, selector, useSetRecoilState } from 'recoil';
+import { atom, selector, useRecoilValue, useSetRecoilState } from 'recoil';
import { SubsonicApiClient } from '../subsonic/api';
import { MusicDb } from '../storage/music';
+import { activeServer } from './settings'
const db = new MusicDb();
-const password = 'test';
-const salt = 'salty';
-const token = md5(password + salt);
-
-const client = new SubsonicApiClient('http://navidrome.home', 'austin', token, salt);
-
export interface ArtistState {
id: string;
name: string;
@@ -37,7 +32,14 @@ export const artistsState = atom({
export const useUpdateArtists = () => {
const setArtists = useSetRecoilState(artistsState);
+ const server = useRecoilValue(activeServer);
+
return async () => {
+ if (!server) {
+ return;
+ }
+
+ const client = new SubsonicApiClient(server.address, server.username, server.token, server.salt);
const response = await client.getArtists();
setArtists(response.data.artists.map(i => ({
diff --git a/src/state/settings.ts b/src/state/settings.ts
new file mode 100644
index 0000000..d8e54d3
--- /dev/null
+++ b/src/state/settings.ts
@@ -0,0 +1,47 @@
+import { atom, DefaultValue, selector } from 'recoil';
+import { settingsDb } from '../clients';
+import { AppSettings, ServerSettings } from '../storage/settings';
+
+export const serversState = atom({
+ key: 'serverState',
+ default: selector({
+ key: 'serversState/default',
+ get: () => settingsDb.getServers(),
+ }),
+ effects_UNSTABLE: [
+ ({ onSet }) => {
+ onSet((newValue) => {
+ if (!(newValue instanceof DefaultValue)) {
+ settingsDb.updateServers(newValue);
+ }
+ });
+ }
+ ],
+});
+
+export const appSettingsState = atom({
+ key: 'appSettingsState',
+ default: selector({
+ key: 'appSettingsState/default',
+ get: () => settingsDb.getApp(),
+ }),
+ effects_UNSTABLE: [
+ ({ onSet }) => {
+ onSet((newValue, oldValue) => {
+ if (!(newValue instanceof DefaultValue)) {
+ settingsDb.updateApp(newValue);
+ }
+ });
+ }
+ ],
+});
+
+export const activeServer = selector({
+ key: 'activeServer',
+ get: ({get}) => {
+ const appSettings = get(appSettingsState);
+ const servers = get(serversState);
+
+ return servers.find(x => x.id == appSettings.server);
+ }
+});
diff --git a/src/storage/db.ts b/src/storage/db.ts
index b9473f9..017009f 100644
--- a/src/storage/db.ts
+++ b/src/storage/db.ts
@@ -9,9 +9,9 @@ export abstract class DbStorage {
this.dbParams = dbParams;
}
- // abstract createDb(): Promise
+ abstract createDb(): Promise
- protected async createDb(scope: (tx: Transaction) => void): Promise {
+ protected async initDb(scope: (tx: Transaction) => void): Promise {
await this.transaction(tx => {
tx.executeSql(`
CREATE TABLE db_version (
diff --git a/src/storage/music.ts b/src/storage/music.ts
index 0ba3f05..8d868bc 100644
--- a/src/storage/music.ts
+++ b/src/storage/music.ts
@@ -6,7 +6,7 @@ export class MusicDb extends DbStorage {
}
async createDb(): Promise {
- super.createDb(tx => {
+ await this.initDb(tx => {
tx.executeSql(`
CREATE TABLE artists (
id TEXT PRIMARY KEY NOT NULL,
diff --git a/src/storage/settings.ts b/src/storage/settings.ts
index bc81f6d..682c721 100644
--- a/src/storage/settings.ts
+++ b/src/storage/settings.ts
@@ -18,7 +18,7 @@ export class SettingsDb extends DbStorage {
}
async createDb(): Promise {
- super.createDb(tx => {
+ await this.initDb(tx => {
tx.executeSql(`
CREATE TABLE servers (
id TEXT PRIMARY KEY NOT NULL,
@@ -56,32 +56,32 @@ export class SettingsDb extends DbStorage {
return (await this.getServers()).find(x => x.id === id);
}
- async addServer(server: ServerSettings): Promise {
- await this.executeSql(`
- INSERT INTO servers (id, address, username, token, salt)
- VALUES (?, ?, ?, ?, ?);
- `, [server.id, server.address, server.username, server.token, server.salt]);
- }
+ // async addServer(server: ServerSettings): Promise {
+ // await this.executeSql(`
+ // INSERT INTO servers (id, address, username, token, salt)
+ // VALUES (?, ?, ?, ?, ?);
+ // `, [server.id, server.address, server.username, server.token, server.salt]);
+ // }
- async removeServer(id: string): Promise {
- await this.executeSql(`
- DELETE FROM servers
- WHERE id = ?;
- `, [id]);
- }
+ // async removeServer(id: string): Promise {
+ // await this.executeSql(`
+ // DELETE FROM servers
+ // WHERE id = ?;
+ // `, [id]);
+ // }
- async updateServer(server: ServerSettings): Promise {
- await this.executeSql(`
- UPDATE servers SET
- address = ?,
- username = ?,
- token = ?,
- salt = ?
- WHERE id = ?;
- `, [
- server.address, server.username, server.token, server.salt,
- server.id,
- ]);
+ async updateServers(servers: ServerSettings[]): Promise {
+ await this.transaction((tx) => {
+ tx.executeSql(`
+ DELETE FROM servers
+ `);
+ for (const s of servers) {
+ tx.executeSql(`
+ INSERT INTO servers (id, address, username, token, salt)
+ VALUES (?, ?, ?, ?, ?);
+ `, [s.id, s.address, s.username, s.token, s.salt]);
+ }
+ });
}
async getApp(): Promise {