Compare commits
3 Commits
15ea565601
...
34e024979f
Author | SHA1 | Date | |
---|---|---|---|
34e024979f | |||
f05febe5d1 | |||
beca906979 |
135
albumCover.go
Normal file
135
albumCover.go
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/adrg/xdg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stores information about the album covers cache
|
||||||
|
type AlbumCoverInfo struct {
|
||||||
|
Cached bool
|
||||||
|
Error error
|
||||||
|
WaitGroup *sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caches info about the album covers
|
||||||
|
var albumCoverCacheInfo = make(map[string]*AlbumCoverInfo)
|
||||||
|
var cacheMutex = sync.RWMutex{}
|
||||||
|
|
||||||
|
func generateCacheFilename(albumId string) string {
|
||||||
|
albumCacheFile, err := xdg.CacheFile(fmt.Sprintf("%s/%s", appname, albumId))
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprint("error creating cacheFile url: ", err))
|
||||||
|
}
|
||||||
|
return albumCacheFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loads a single album cover and caches it in XDG_CACHE_HOME
|
||||||
|
// First it checks if the cover already exists in XDG_CACHE_HOME
|
||||||
|
// If it doesn't, loads it and stores it
|
||||||
|
func loadAlbumCover(albumId string) {
|
||||||
|
// Check cache info
|
||||||
|
cacheMutex.Lock()
|
||||||
|
_, ok := albumCoverCacheInfo[albumId]
|
||||||
|
cacheMutex.Unlock()
|
||||||
|
if ok {
|
||||||
|
log.Print("album cover: cache hit: ", albumId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
albumCacheFile := generateCacheFilename(albumId)
|
||||||
|
|
||||||
|
// Attempt to read file
|
||||||
|
if _, err := os.Stat(albumCacheFile); err == nil {
|
||||||
|
// File exists
|
||||||
|
log.Print("album cover: cache hit (disk): ", albumId)
|
||||||
|
cacheMutex.Lock()
|
||||||
|
albumCoverCacheInfo[albumId] = &AlbumCoverInfo{
|
||||||
|
Cached: true,
|
||||||
|
WaitGroup: &sync.WaitGroup{},
|
||||||
|
}
|
||||||
|
cacheMutex.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load cover from network
|
||||||
|
log.Print("load album cover for ", albumId)
|
||||||
|
coverInfo := AlbumCoverInfo{
|
||||||
|
Cached: false,
|
||||||
|
WaitGroup: &sync.WaitGroup{},
|
||||||
|
}
|
||||||
|
coverInfo.WaitGroup.Add(1)
|
||||||
|
defer coverInfo.WaitGroup.Done()
|
||||||
|
|
||||||
|
cacheMutex.Lock()
|
||||||
|
albumCoverCacheInfo[albumId] = &coverInfo
|
||||||
|
cacheMutex.Unlock()
|
||||||
|
|
||||||
|
response, err := client.R().
|
||||||
|
// TODO: replace `fernando` with the username
|
||||||
|
Get(fmt.Sprintf(
|
||||||
|
"%s/rest/getCoverArt.view?id=%s&u=%s&s=12e7f3&t=%s&v=1.13.0&c=wmusic&size=300",
|
||||||
|
serverUrl,
|
||||||
|
albumId,
|
||||||
|
"fernando",
|
||||||
|
"d7bbe92d7da363aa202ae16136887adc",
|
||||||
|
))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Print("error loadAlbumCover: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !response.IsSuccess() {
|
||||||
|
log.Print("error loadAlbumCover")
|
||||||
|
log.Printf("%s", response.Body())
|
||||||
|
coverInfo.Error = errors.New("error loading")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
imgBytes := response.Body()
|
||||||
|
|
||||||
|
// Write the image to cache
|
||||||
|
log.Printf("write %s: %+v", albumId, imgBytes[:10])
|
||||||
|
err = os.WriteFile(albumCacheFile, imgBytes, 0644)
|
||||||
|
if err != nil {
|
||||||
|
coverInfo.Error = errors.New("error writing album cover to disk")
|
||||||
|
log.Fatalf("Error writing to cache file for album cover: %s", albumCacheFile)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Print("Loading albumCover for ", albumId, " successful")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tries to load the album cover
|
||||||
|
func (a *App) GetAlbumCover(albumId string) ([]byte, error) {
|
||||||
|
cacheMutex.Lock()
|
||||||
|
coverInfo, ok := albumCoverCacheInfo[albumId]
|
||||||
|
cacheMutex.Unlock()
|
||||||
|
if !ok {
|
||||||
|
panic("Illegal state: Tried to load an album, but it wasn't on memory cache")
|
||||||
|
}
|
||||||
|
|
||||||
|
coverInfo.WaitGroup.Wait()
|
||||||
|
|
||||||
|
if coverInfo.Error != nil {
|
||||||
|
return nil, coverInfo.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the file
|
||||||
|
filename := generateCacheFilename(albumId)
|
||||||
|
log.Print("reading: ", filename)
|
||||||
|
bytes, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("error getting album cover: ", err)
|
||||||
|
return nil, errors.New("error reading cover file")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%s : %+v", albumId, bytes[:10])
|
||||||
|
return bytes, nil
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { For, createResource, createSignal, onMount } from "solid-js";
|
import { For, createMemo, createResource, createSignal, onMount } from "solid-js";
|
||||||
import { GetRandomAlbums } from "../../wailsjs/go/main/App";
|
import { GetAlbumCover, GetRandomAlbums } from "../../wailsjs/go/main/App";
|
||||||
import { main } from "../../wailsjs/go/models";
|
import { main } from "../../wailsjs/go/models";
|
||||||
|
|
||||||
export function Home() {
|
export function Home() {
|
||||||
@ -11,10 +11,10 @@ export function Home() {
|
|||||||
setTimeout(() => setHidden(false), 150);
|
setTimeout(() => setHidden(false), 150);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={`min-h-screen ${hidden() ? "opacity-0" : "opacity-100"} transition-opacity`}>
|
<div class={`min-h-screen ${hidden() ? "opacity-0" : "opacity-100"} transition-opacity`}>
|
||||||
<div class="py-10 h-64 overflow-scroll whitespace-nowrap">
|
<h1 class="font-black text-2xl pt-6 pb-4 pl-2">Random albums</h1>
|
||||||
|
<div class="pb-4 overflow-scroll whitespace-nowrap">
|
||||||
<For each={albums()}>
|
<For each={albums()}>
|
||||||
{(album) => <Album album={album} />}
|
{(album) => <Album album={album} />}
|
||||||
</For>
|
</For>
|
||||||
@ -24,11 +24,21 @@ export function Home() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Album(props: { album: main.Album }) {
|
function Album(props: { album: main.Album }) {
|
||||||
|
const [coverBytes] = createResource(async() => await GetAlbumCover(props.album.id));
|
||||||
|
|
||||||
|
const base64Image = createMemo(() => {
|
||||||
|
if (coverBytes.state !== "ready") return "";
|
||||||
|
|
||||||
|
// At runtime this is a string, not a number array
|
||||||
|
const bytes = coverBytes() as unknown as string;
|
||||||
|
return `data:;base64,${bytes}`;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="inline-block mx-2 p-1 w-32 rounded bg-zinc-900">
|
<div class="inline-block mx-2 p-1 w-32 rounded bg-zinc-900">
|
||||||
<img
|
<img
|
||||||
class="inline-block rounded w-30 h-30 min-w-30 min-h-30"
|
class="inline-block rounded w-30 h-30 min-w-30 min-h-30"
|
||||||
src={props.album.mediumImageUrl}
|
src={base64Image()}
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
|
9
frontend/vite.config.mts
Normal file
9
frontend/vite.config.mts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import solidPlugin from "vite-plugin-solid";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [solidPlugin()],
|
||||||
|
build: {
|
||||||
|
target: "esnext",
|
||||||
|
},
|
||||||
|
});
|
@ -1,9 +0,0 @@
|
|||||||
import { defineConfig } from 'vite';
|
|
||||||
import solidPlugin from 'vite-plugin-solid';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [solidPlugin()],
|
|
||||||
build: {
|
|
||||||
target: 'esnext',
|
|
||||||
},
|
|
||||||
});
|
|
2
frontend/wailsjs/go/main/App.d.ts
vendored
2
frontend/wailsjs/go/main/App.d.ts
vendored
@ -2,6 +2,8 @@
|
|||||||
// This file is automatically generated. DO NOT EDIT
|
// This file is automatically generated. DO NOT EDIT
|
||||||
import {main} from '../models';
|
import {main} from '../models';
|
||||||
|
|
||||||
|
export function GetAlbumCover(arg1:string):Promise<Array<number>>;
|
||||||
|
|
||||||
export function GetRandomAlbums():Promise<Array<main.Album>>;
|
export function GetRandomAlbums():Promise<Array<main.Album>>;
|
||||||
|
|
||||||
export function Greet(arg1:string):Promise<string>;
|
export function Greet(arg1:string):Promise<string>;
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
// This file is automatically generated. DO NOT EDIT
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export function GetAlbumCover(arg1) {
|
||||||
|
return window['go']['main']['App']['GetAlbumCover'](arg1);
|
||||||
|
}
|
||||||
|
|
||||||
export function GetRandomAlbums() {
|
export function GetRandomAlbums() {
|
||||||
return window['go']['main']['App']['GetRandomAlbums']();
|
return window['go']['main']['App']['GetRandomAlbums']();
|
||||||
}
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -5,6 +5,7 @@ go 1.21
|
|||||||
toolchain go1.22.3
|
toolchain go1.22.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/adrg/xdg v0.4.0
|
||||||
github.com/go-resty/resty/v2 v2.13.1
|
github.com/go-resty/resty/v2 v2.13.1
|
||||||
github.com/wailsapp/wails/v2 v2.8.2
|
github.com/wailsapp/wails/v2 v2.8.2
|
||||||
)
|
)
|
||||||
|
3
go.sum
3
go.sum
@ -1,3 +1,5 @@
|
|||||||
|
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
||||||
|
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
||||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -96,6 +98,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
20
session.go
20
session.go
@ -13,14 +13,20 @@ type AuthError struct {
|
|||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name of the app, used to create directories in XDG_<folder> and store files inside
|
||||||
|
const appname = "wMusic"
|
||||||
|
|
||||||
var LoggedUser AuthSuccess
|
var LoggedUser AuthSuccess
|
||||||
var randomAlbumWaitGroup sync.WaitGroup
|
var randomAlbumWaitGroup sync.WaitGroup
|
||||||
var randomAlbums []Album
|
var randomAlbums []Album
|
||||||
|
|
||||||
|
var serverUrl = ""
|
||||||
|
var client = resty.New()
|
||||||
|
|
||||||
// (Tries to) login to a remote navidrome server
|
// (Tries to) login to a remote navidrome server
|
||||||
func (a *App) Login(server, username, password string) (bool, error) {
|
func (a *App) Login(server, username, password string) (bool, error) {
|
||||||
log.Print("begin Login to server")
|
log.Print("begin Login to server")
|
||||||
client := resty.New()
|
// client := resty.New()
|
||||||
|
|
||||||
// TODO: check server for leading https and trailing /, normalize
|
// TODO: check server for leading https and trailing /, normalize
|
||||||
|
|
||||||
@ -42,6 +48,10 @@ func (a *App) Login(server, username, password string) (bool, error) {
|
|||||||
if response.IsSuccess() {
|
if response.IsSuccess() {
|
||||||
log.Printf("%+v", successData)
|
log.Printf("%+v", successData)
|
||||||
|
|
||||||
|
// Set the auth header globally
|
||||||
|
client.SetHeader("x-nd-authorization", successData.Token)
|
||||||
|
serverUrl = server
|
||||||
|
|
||||||
// Set global session
|
// Set global session
|
||||||
LoggedUser = successData
|
LoggedUser = successData
|
||||||
// Begin to load the list of albums on the background
|
// Begin to load the list of albums on the background
|
||||||
@ -89,7 +99,11 @@ func loadAlbums(serverUrl string) {
|
|||||||
log.Print("Get albums success")
|
log.Print("Get albums success")
|
||||||
// TODO: Begin to load album artwork in the background
|
// TODO: Begin to load album artwork in the background
|
||||||
// Album artwork comes from the url /rest/getCoverArt.view
|
// Album artwork comes from the url /rest/getCoverArt.view
|
||||||
}
|
// Cache album images in XDG_CACHE_HOME
|
||||||
|
for _, album := range randomAlbums {
|
||||||
|
albumId := album.ID
|
||||||
|
|
||||||
// TODO: Do the loading
|
go loadAlbumCover(albumId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user