Compare commits
24 Commits
main
...
660d27aa2a
| Author | SHA1 | Date | |
|---|---|---|---|
| 660d27aa2a | |||
| c938b82b75 | |||
| 583f9105e5 | |||
| b8ca9734fb | |||
| bca5108a1a | |||
| d9b8747752 | |||
| 825352a2a5 | |||
| db69afcf74 | |||
| eef3ca5d56 | |||
| 607f7bbaa2 | |||
| 9964380cae | |||
| b475e48e94 | |||
| 09b0f438a4 | |||
| 8b2191bdcc | |||
| 39ecd8a708 | |||
| 7f3501ea77 | |||
| c249069439 | |||
| 358f850e02 | |||
| af35128330 | |||
| 581014a44d | |||
| ce4196cd62 | |||
| 3e00409a00 | |||
| ce6d4283b6 | |||
| 6bebd5c51c |
@@ -1,40 +0,0 @@
|
||||
|
||||
name: Build
|
||||
|
||||
#on:
|
||||
# release:
|
||||
# types: [published]
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
|
||||
jobs:
|
||||
# test:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - run: echo "The release ${{ gitea.ref }} ${{ gitea.ref_name }} was published"
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: https://github.com/docker/setup-buildx-action@v3
|
||||
with:
|
||||
config-inline: |
|
||||
[registry."gitea.sanplex.tech/sansan"]
|
||||
http = true
|
||||
insecure = true
|
||||
- name: Login to Gitea
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: gitea.sanplex.tech
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.TOKEN }}
|
||||
- name: Build
|
||||
uses: https://github.com/docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: false
|
||||
#tags: "gitea.sanplex.tech/sansan/musicserver:${{gitea.ref_name}}, gitea.sanplex.tech/sansan/musicserver:latest"
|
||||
@@ -1,13 +1,10 @@
|
||||
|
||||
name: Publish
|
||||
|
||||
run-name: ${{ gitea.actor }} is runs ci pipeline
|
||||
#on:
|
||||
# release:
|
||||
# types: [published]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*.*'
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
# test:
|
||||
@@ -15,8 +12,16 @@ jobs:
|
||||
# steps:
|
||||
# - run: echo "The release ${{ gitea.ref }} ${{ gitea.ref_name }} was published"
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
# if: gitea.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@v4
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: gitea.ref != 'refs/heads/main'
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
|
||||
6
.gitignore
vendored
@@ -6,9 +6,3 @@ conf.yaml
|
||||
output.css
|
||||
compose.yaml
|
||||
tailwindcss
|
||||
.env
|
||||
node_modules
|
||||
package.json
|
||||
package-lock.json
|
||||
cpu.pprof
|
||||
main
|
||||
|
||||
7
.idea/dataSources.xml
generated
@@ -25,12 +25,5 @@
|
||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:postgresql://ssh.sanplex.xyz:9432/music_prod</jdbc-url>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="music_test2@localhost" uuid="a423ab0a-55b0-42e1-8070-25d8ef34bfac">
|
||||
<driver-ref>postgresql</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:postgresql://localhost:5432/music_test2</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
19
Dockerfile
@@ -1,5 +1,5 @@
|
||||
FROM golang:1.23-alpine as build_go
|
||||
RUN apk add --no-cache curl npm
|
||||
RUN apk add --no-cache curl
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -7,12 +7,19 @@ COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
#RUN go install github.com/a-h/templ/cmd/templ@latest && \
|
||||
#templ generate && \
|
||||
#curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 -o tailwindcss && \
|
||||
#chmod +x tailwindcss && \
|
||||
#./tailwindcss-build -i cmd/web/assets/css/input.css -o cmd/web/assets/css/output.css
|
||||
|
||||
RUN go install github.com/a-h/templ/cmd/templ@latest
|
||||
RUN npm install tailwindcss @tailwindcss/cli
|
||||
|
||||
RUN templ generate
|
||||
RUN npx @tailwindcss/cli -i ./cmd/web/assets/css/input.css -o ./cmd/web/assets/css/output.css
|
||||
|
||||
# Install and make tailwindcss executable
|
||||
RUN curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/download/v3.4.17/tailwindcss-linux-x64 \
|
||||
&& mv tailwindcss-linux-x64 tailwindcss && chmod +x tailwindcss
|
||||
CMD ./tailwindcss -i cmd/web/assets/css/input.css -o cmd/web/assets/css/output.css --minify
|
||||
|
||||
RUN go build -o main cmd/main.go
|
||||
|
||||
@@ -21,16 +28,12 @@ FROM golang:1.23-alpine
|
||||
EXPOSE 8080
|
||||
VOLUME /sorted
|
||||
VOLUME /frontend
|
||||
VOLUME /characters
|
||||
|
||||
ENV PORT 8080
|
||||
ENV DB_HOST ""
|
||||
ENV DB_PORT ""
|
||||
ENV DB_USERNAME ""
|
||||
ENV DB_PASSWORD ""
|
||||
ENV DB_NAME ""
|
||||
ENV MUSIC_PATH ""
|
||||
ENV CHARACTERS_PATH ""
|
||||
|
||||
COPY --from=build_go /app/main .
|
||||
COPY ./songs/ ./songs/
|
||||
|
||||
131
cmd/docs/docs.go
@@ -1,131 +0,0 @@
|
||||
// Package docs GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
// This file was generated by swaggo/swag
|
||||
package docs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/swaggo/swag"
|
||||
)
|
||||
|
||||
var doc = `{
|
||||
"schemes": {{ marshal .Schemes }},
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "{{escape .Description}}",
|
||||
"title": "{{.Title}}",
|
||||
"contact": {},
|
||||
"version": "{{.Version}}"
|
||||
},
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/version": {
|
||||
"get": {
|
||||
"description": "get string by ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"accounts"
|
||||
],
|
||||
"summary": "Getting the version of the backend",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/backend.VersionData"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"backend.VersionData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"changelog": {
|
||||
"type": "string",
|
||||
"example": "account name"
|
||||
},
|
||||
"history": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/backend.VersionData"
|
||||
}
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"example": "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
type swaggerInfo struct {
|
||||
Version string
|
||||
Host string
|
||||
BasePath string
|
||||
Schemes []string
|
||||
Title string
|
||||
Description string
|
||||
}
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = swaggerInfo{
|
||||
Version: "",
|
||||
Host: "",
|
||||
BasePath: "",
|
||||
Schemes: []string{},
|
||||
Title: "",
|
||||
Description: "",
|
||||
}
|
||||
|
||||
type s struct{}
|
||||
|
||||
func (s *s) ReadDoc() string {
|
||||
sInfo := SwaggerInfo
|
||||
sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
|
||||
|
||||
t, err := template.New("swagger_info").Funcs(template.FuncMap{
|
||||
"marshal": func(v interface{}) string {
|
||||
a, _ := json.Marshal(v)
|
||||
return string(a)
|
||||
},
|
||||
"escape": func(v interface{}) string {
|
||||
// escape tabs
|
||||
str := strings.Replace(v.(string), "\t", "\\t", -1)
|
||||
// replace " with \", and if that results in \\", replace that with \\\"
|
||||
str = strings.Replace(str, "\"", "\\\"", -1)
|
||||
return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1)
|
||||
},
|
||||
}).Parse(doc)
|
||||
if err != nil {
|
||||
return doc
|
||||
}
|
||||
|
||||
var tpl bytes.Buffer
|
||||
if err := t.Execute(&tpl, sInfo); err != nil {
|
||||
return doc
|
||||
}
|
||||
|
||||
return tpl.String()
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register("swagger", &s{})
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"contact": {}
|
||||
},
|
||||
"paths": {
|
||||
"/version": {
|
||||
"get": {
|
||||
"description": "get string by ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"accounts"
|
||||
],
|
||||
"summary": "Getting the version of the backend",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/backend.VersionData"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"backend.VersionData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"changelog": {
|
||||
"type": "string",
|
||||
"example": "account name"
|
||||
},
|
||||
"history": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/backend.VersionData"
|
||||
}
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"example": "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
definitions:
|
||||
backend.VersionData:
|
||||
properties:
|
||||
changelog:
|
||||
example: account name
|
||||
type: string
|
||||
history:
|
||||
items:
|
||||
$ref: '#/definitions/backend.VersionData'
|
||||
type: array
|
||||
version:
|
||||
example: 1.0.0
|
||||
type: string
|
||||
type: object
|
||||
info:
|
||||
contact: {}
|
||||
paths:
|
||||
/version:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: get string by ID
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/backend.VersionData'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
type: string
|
||||
summary: Getting the version of the backend
|
||||
tags:
|
||||
- accounts
|
||||
swagger: "2.0"
|
||||
75
cmd/main.go
@@ -1,78 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"music-server/internal/db"
|
||||
"music-server/internal/server"
|
||||
"net/http"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
"embed"
|
||||
"music-server/pkg/conf"
|
||||
)
|
||||
|
||||
//
|
||||
// @Title Swagger Example API
|
||||
// @version 0.5
|
||||
// @description This is a sample server Petstore server.
|
||||
// @termsOfService http://swagger.io/terms/
|
||||
//go:embed swagger
|
||||
var swagger embed.FS
|
||||
|
||||
// @contact.name Sebastian Olsson
|
||||
// @contact.email zarnor91@gmail.com
|
||||
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
// @host localhost:8080
|
||||
func main() {
|
||||
/*f, perr := os.Create("cpu.pprof")
|
||||
if perr != nil {
|
||||
log.Fatal(perr)
|
||||
}
|
||||
pprof.StartCPUProfile(f)
|
||||
defer pprof.StopCPUProfile()*/
|
||||
conf.SetupDb()
|
||||
|
||||
server := server.NewServer()
|
||||
conf.SetupRestServer(swagger)
|
||||
|
||||
// Create a done channel to signal when the shutdown is complete
|
||||
done := make(chan bool, 1)
|
||||
|
||||
// Run graceful shutdown in a separate goroutine
|
||||
go gracefulShutdown(server, done)
|
||||
|
||||
log.Printf("Open http://localhost%s in the browser", server.Addr)
|
||||
err := server.ListenAndServe()
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
panic(fmt.Sprintf("http server error: %s", err))
|
||||
}
|
||||
|
||||
// Wait for the graceful shutdown to complete
|
||||
<-done
|
||||
log.Println("Graceful shutdown complete.")
|
||||
}
|
||||
|
||||
func gracefulShutdown(apiServer *http.Server, done chan bool) {
|
||||
// Create context that listens for the interrupt signal from the OS.
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
// Listen for the interrupt signal.
|
||||
<-ctx.Done()
|
||||
|
||||
log.Println("shutting down gracefully, press Ctrl+C again to force")
|
||||
db.CloseDb()
|
||||
|
||||
// The context is used to inform the server it has 5 seconds to finish
|
||||
// the request it is currently handling
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := apiServer.Shutdown(ctx); err != nil {
|
||||
log.Printf("Server forced to shutdown with error: %v", err)
|
||||
}
|
||||
|
||||
log.Println("Server exiting")
|
||||
|
||||
// Notify the main goroutine that the shutdown is complete
|
||||
done <- true
|
||||
conf.CloseDb()
|
||||
}
|
||||
|
||||
BIN
cmd/search/index.144x144.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
34
cmd/search/index.144x144.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b6bierb3v4yoq"
|
||||
path="res://.godot/imported/index.144x144.png-03ea9bc9a40782e35fa8c68f683d2623.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://web/index.144x144.png"
|
||||
dest_files=["res://.godot/imported/index.144x144.png-03ea9bc9a40782e35fa8c68f683d2623.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
cmd/search/index.180x180.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
34
cmd/search/index.180x180.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b0euyv6xwufjw"
|
||||
path="res://.godot/imported/index.180x180.png-9c97e3aaba17027b76e6e765bcdc0add.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://web/index.180x180.png"
|
||||
dest_files=["res://.godot/imported/index.180x180.png-9c97e3aaba17027b76e6e765bcdc0add.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
cmd/search/index.512x512.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
34
cmd/search/index.512x512.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c30mhkc1yernc"
|
||||
path="res://.godot/imported/index.512x512.png-4cda31773312dccaaf53a4ff122aad13.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://web/index.512x512.png"
|
||||
dest_files=["res://.godot/imported/index.512x512.png-4cda31773312dccaaf53a4ff122aad13.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
cmd/search/index.apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
34
cmd/search/index.apple-touch-icon.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d0tny2k1tdlnq"
|
||||
path="res://.godot/imported/index.apple-touch-icon.png-939ec69c79bbf504b92ad8ceed469d51.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://web/index.apple-touch-icon.png"
|
||||
dest_files=["res://.godot/imported/index.apple-touch-icon.png-939ec69c79bbf504b92ad8ceed469d51.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
213
cmd/search/index.audio.worklet.js
Normal file
@@ -0,0 +1,213 @@
|
||||
/**************************************************************************/
|
||||
/* audio.worklet.js */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
class RingBuffer {
|
||||
constructor(p_buffer, p_state, p_threads) {
|
||||
this.buffer = p_buffer;
|
||||
this.avail = p_state;
|
||||
this.threads = p_threads;
|
||||
this.rpos = 0;
|
||||
this.wpos = 0;
|
||||
}
|
||||
|
||||
data_left() {
|
||||
return this.threads ? Atomics.load(this.avail, 0) : this.avail;
|
||||
}
|
||||
|
||||
space_left() {
|
||||
return this.buffer.length - this.data_left();
|
||||
}
|
||||
|
||||
read(output) {
|
||||
const size = this.buffer.length;
|
||||
let from = 0;
|
||||
let to_write = output.length;
|
||||
if (this.rpos + to_write > size) {
|
||||
const high = size - this.rpos;
|
||||
output.set(this.buffer.subarray(this.rpos, size));
|
||||
from = high;
|
||||
to_write -= high;
|
||||
this.rpos = 0;
|
||||
}
|
||||
if (to_write) {
|
||||
output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from);
|
||||
}
|
||||
this.rpos += to_write;
|
||||
if (this.threads) {
|
||||
Atomics.add(this.avail, 0, -output.length);
|
||||
Atomics.notify(this.avail, 0);
|
||||
} else {
|
||||
this.avail -= output.length;
|
||||
}
|
||||
}
|
||||
|
||||
write(p_buffer) {
|
||||
const to_write = p_buffer.length;
|
||||
const mw = this.buffer.length - this.wpos;
|
||||
if (mw >= to_write) {
|
||||
this.buffer.set(p_buffer, this.wpos);
|
||||
this.wpos += to_write;
|
||||
if (mw === to_write) {
|
||||
this.wpos = 0;
|
||||
}
|
||||
} else {
|
||||
const high = p_buffer.subarray(0, mw);
|
||||
const low = p_buffer.subarray(mw);
|
||||
this.buffer.set(high, this.wpos);
|
||||
this.buffer.set(low);
|
||||
this.wpos = low.length;
|
||||
}
|
||||
if (this.threads) {
|
||||
Atomics.add(this.avail, 0, to_write);
|
||||
Atomics.notify(this.avail, 0);
|
||||
} else {
|
||||
this.avail += to_write;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GodotProcessor extends AudioWorkletProcessor {
|
||||
constructor() {
|
||||
super();
|
||||
this.threads = false;
|
||||
this.running = true;
|
||||
this.lock = null;
|
||||
this.notifier = null;
|
||||
this.output = null;
|
||||
this.output_buffer = new Float32Array();
|
||||
this.input = null;
|
||||
this.input_buffer = new Float32Array();
|
||||
this.port.onmessage = (event) => {
|
||||
const cmd = event.data['cmd'];
|
||||
const data = event.data['data'];
|
||||
this.parse_message(cmd, data);
|
||||
};
|
||||
}
|
||||
|
||||
process_notify() {
|
||||
if (this.notifier) {
|
||||
Atomics.add(this.notifier, 0, 1);
|
||||
Atomics.notify(this.notifier, 0);
|
||||
}
|
||||
}
|
||||
|
||||
parse_message(p_cmd, p_data) {
|
||||
if (p_cmd === 'start' && p_data) {
|
||||
const state = p_data[0];
|
||||
let idx = 0;
|
||||
this.threads = true;
|
||||
this.lock = state.subarray(idx, ++idx);
|
||||
this.notifier = state.subarray(idx, ++idx);
|
||||
const avail_in = state.subarray(idx, ++idx);
|
||||
const avail_out = state.subarray(idx, ++idx);
|
||||
this.input = new RingBuffer(p_data[1], avail_in, true);
|
||||
this.output = new RingBuffer(p_data[2], avail_out, true);
|
||||
} else if (p_cmd === 'stop') {
|
||||
this.running = false;
|
||||
this.output = null;
|
||||
this.input = null;
|
||||
this.lock = null;
|
||||
this.notifier = null;
|
||||
} else if (p_cmd === 'start_nothreads') {
|
||||
this.output = new RingBuffer(p_data[0], p_data[0].length, false);
|
||||
} else if (p_cmd === 'chunk') {
|
||||
this.output.write(p_data);
|
||||
}
|
||||
}
|
||||
|
||||
static array_has_data(arr) {
|
||||
return arr.length && arr[0].length && arr[0][0].length;
|
||||
}
|
||||
|
||||
process(inputs, outputs, parameters) {
|
||||
if (!this.running) {
|
||||
return false; // Stop processing.
|
||||
}
|
||||
if (this.output === null) {
|
||||
return true; // Not ready yet, keep processing.
|
||||
}
|
||||
const process_input = GodotProcessor.array_has_data(inputs);
|
||||
if (process_input) {
|
||||
const input = inputs[0];
|
||||
const chunk = input[0].length * input.length;
|
||||
if (this.input_buffer.length !== chunk) {
|
||||
this.input_buffer = new Float32Array(chunk);
|
||||
}
|
||||
if (!this.threads) {
|
||||
GodotProcessor.write_input(this.input_buffer, input);
|
||||
this.port.postMessage({ 'cmd': 'input', 'data': this.input_buffer });
|
||||
} else if (this.input.space_left() >= chunk) {
|
||||
GodotProcessor.write_input(this.input_buffer, input);
|
||||
this.input.write(this.input_buffer);
|
||||
} else {
|
||||
// this.port.postMessage('Input buffer is full! Skipping input frame.'); // Uncomment this line to debug input buffer.
|
||||
}
|
||||
}
|
||||
const process_output = GodotProcessor.array_has_data(outputs);
|
||||
if (process_output) {
|
||||
const output = outputs[0];
|
||||
const chunk = output[0].length * output.length;
|
||||
if (this.output_buffer.length !== chunk) {
|
||||
this.output_buffer = new Float32Array(chunk);
|
||||
}
|
||||
if (this.output.data_left() >= chunk) {
|
||||
this.output.read(this.output_buffer);
|
||||
GodotProcessor.write_output(output, this.output_buffer);
|
||||
if (!this.threads) {
|
||||
this.port.postMessage({ 'cmd': 'read', 'data': chunk });
|
||||
}
|
||||
} else {
|
||||
// this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); // Uncomment this line to debug output buffer.
|
||||
}
|
||||
}
|
||||
this.process_notify();
|
||||
return true;
|
||||
}
|
||||
|
||||
static write_output(dest, source) {
|
||||
const channels = dest.length;
|
||||
for (let ch = 0; ch < channels; ch++) {
|
||||
for (let sample = 0; sample < dest[ch].length; sample++) {
|
||||
dest[ch][sample] = source[sample * channels + ch];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static write_input(dest, source) {
|
||||
const channels = source.length;
|
||||
for (let ch = 0; ch < channels; ch++) {
|
||||
for (let sample = 0; sample < source[ch].length; sample++) {
|
||||
dest[sample * channels + ch] = source[ch][sample];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('godot-processor', GodotProcessor);
|
||||
200
cmd/search/index.html
Normal file
@@ -0,0 +1,200 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
|
||||
<title>MusicSearch</title>
|
||||
<style>
|
||||
html, body, #canvas {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
color: white;
|
||||
background-color: black;
|
||||
overflow: hidden;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
#canvas {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#canvas:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#status, #status-splash, #status-progress {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#status, #status-splash {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#status {
|
||||
background-color: #242424;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#status-splash {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#status-progress, #status-notice {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#status-progress {
|
||||
bottom: 10%;
|
||||
width: 50%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#status-notice {
|
||||
background-color: #5b3943;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #9b3943;
|
||||
color: #e0e0e0;
|
||||
font-family: 'Noto Sans', 'Droid Sans', Arial, sans-serif;
|
||||
line-height: 1.3;
|
||||
margin: 0 2rem;
|
||||
overflow: hidden;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
<link id="-gd-engine-icon" rel="icon" type="image/png" href="index.icon.png" />
|
||||
<link rel="apple-touch-icon" href="index.apple-touch-icon.png"/>
|
||||
<link rel="manifest" href="index.manifest.json">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas">
|
||||
Your browser does not support the canvas tag.
|
||||
</canvas>
|
||||
|
||||
<noscript>
|
||||
Your browser does not support JavaScript.
|
||||
</noscript>
|
||||
|
||||
<div id="status">
|
||||
<img id="status-splash" src="index.png" alt="">
|
||||
<progress id="status-progress"></progress>
|
||||
<div id="status-notice"></div>
|
||||
</div>
|
||||
|
||||
<script src="index.js"></script>
|
||||
<script>
|
||||
const GODOT_CONFIG = {"args":[],"canvasResizePolicy":2,"ensureCrossOriginIsolationHeaders":false,"executable":"index","experimentalVK":false,"fileSizes":{"index.pck":86816,"index.wasm":35376909},"focusCanvas":true,"gdextensionLibs":[],"serviceWorker":"index.service.worker.js"};
|
||||
const GODOT_THREADS_ENABLED = false;
|
||||
const engine = new Engine(GODOT_CONFIG);
|
||||
|
||||
(function () {
|
||||
const statusOverlay = document.getElementById('status');
|
||||
const statusProgress = document.getElementById('status-progress');
|
||||
const statusNotice = document.getElementById('status-notice');
|
||||
|
||||
let initializing = true;
|
||||
let statusMode = '';
|
||||
|
||||
function setStatusMode(mode) {
|
||||
if (statusMode === mode || !initializing) {
|
||||
return;
|
||||
}
|
||||
if (mode === 'hidden') {
|
||||
statusOverlay.remove();
|
||||
initializing = false;
|
||||
return;
|
||||
}
|
||||
statusOverlay.style.visibility = 'visible';
|
||||
statusProgress.style.display = mode === 'progress' ? 'block' : 'none';
|
||||
statusNotice.style.display = mode === 'notice' ? 'block' : 'none';
|
||||
statusMode = mode;
|
||||
}
|
||||
|
||||
function setStatusNotice(text) {
|
||||
while (statusNotice.lastChild) {
|
||||
statusNotice.removeChild(statusNotice.lastChild);
|
||||
}
|
||||
const lines = text.split('\n');
|
||||
lines.forEach((line) => {
|
||||
statusNotice.appendChild(document.createTextNode(line));
|
||||
statusNotice.appendChild(document.createElement('br'));
|
||||
});
|
||||
}
|
||||
|
||||
function displayFailureNotice(err) {
|
||||
console.error(err);
|
||||
if (err instanceof Error) {
|
||||
setStatusNotice(err.message);
|
||||
} else if (typeof err === 'string') {
|
||||
setStatusNotice(err);
|
||||
} else {
|
||||
setStatusNotice('An unknown error occured');
|
||||
}
|
||||
setStatusMode('notice');
|
||||
initializing = false;
|
||||
}
|
||||
|
||||
const missing = Engine.getMissingFeatures({
|
||||
threads: GODOT_THREADS_ENABLED,
|
||||
});
|
||||
|
||||
if (missing.length !== 0) {
|
||||
if (GODOT_CONFIG['serviceWorker'] && GODOT_CONFIG['ensureCrossOriginIsolationHeaders'] && 'serviceWorker' in navigator) {
|
||||
// There's a chance that installing the service worker would fix the issue
|
||||
Promise.race([
|
||||
navigator.serviceWorker.getRegistration().then((registration) => {
|
||||
if (registration != null) {
|
||||
return Promise.reject(new Error('Service worker already exists.'));
|
||||
}
|
||||
return registration;
|
||||
}).then(() => engine.installServiceWorker()),
|
||||
// For some reason, `getRegistration()` can stall
|
||||
new Promise((resolve) => {
|
||||
setTimeout(() => resolve(), 2000);
|
||||
}),
|
||||
]).catch((err) => {
|
||||
console.error('Error while registering service worker:', err);
|
||||
}).then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
} else {
|
||||
// Display the message as usual
|
||||
const missingMsg = 'Error\nThe following features required to run Godot projects on the Web are missing:\n';
|
||||
displayFailureNotice(missingMsg + missing.join('\n'));
|
||||
}
|
||||
} else {
|
||||
setStatusMode('progress');
|
||||
engine.startGame({
|
||||
'onProgress': function (current, total) {
|
||||
if (current > 0 && total > 0) {
|
||||
statusProgress.value = current;
|
||||
statusProgress.max = total;
|
||||
} else {
|
||||
statusProgress.removeAttribute('value');
|
||||
statusProgress.removeAttribute('max');
|
||||
}
|
||||
},
|
||||
}).then(() => {
|
||||
setStatusMode('hidden');
|
||||
}, displayFailureNotice);
|
||||
}
|
||||
}());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
BIN
cmd/search/index.icon.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
34
cmd/search/index.icon.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c8gblkbcsrrwo"
|
||||
path="res://.godot/imported/index.icon.png-c615af856eabc03ca93d51f6a8f07b60.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://web/index.icon.png"
|
||||
dest_files=["res://.godot/imported/index.icon.png-c615af856eabc03ca93d51f6a8f07b60.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
908
cmd/search/index.js
Normal file
1
cmd/search/index.manifest.json
Normal file
@@ -0,0 +1 @@
|
||||
{"background_color":"#000000","display":"standalone","icons":[{"sizes":"144x144","src":"index.144x144.png","type":"image/png"},{"sizes":"180x180","src":"index.180x180.png","type":"image/png"},{"sizes":"512x512","src":"index.512x512.png","type":"image/png"}],"name":"MusicSearch","orientation":"any","start_url":"./index.html"}
|
||||
41
cmd/search/index.offline.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>You are offline</title>
|
||||
<style>
|
||||
html {
|
||||
background-color: #000000;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-block: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
display: block;
|
||||
padding: 1rem 2rem;
|
||||
margin: 3rem auto 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>You are offline</h1>
|
||||
<p>This application requires an Internet connection to run for the first time.</p>
|
||||
<p>Press the button below to try reloading:</p>
|
||||
<button type="button">Reload</button>
|
||||
<script>
|
||||
document.querySelector('button').addEventListener('click', () => {
|
||||
window.location.reload();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
cmd/search/index.pck
Normal file
BIN
cmd/search/index.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
34
cmd/search/index.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bxh15ilo8hoq"
|
||||
path="res://.godot/imported/index.png-80964ad67552d78b1e33f58ad16188ff.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://web/index.png"
|
||||
dest_files=["res://.godot/imported/index.png-80964ad67552d78b1e33f58ad16188ff.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
166
cmd/search/index.service.worker.js
Normal file
@@ -0,0 +1,166 @@
|
||||
// This service worker is required to expose an exported Godot project as a
|
||||
// Progressive Web App. It provides an offline fallback page telling the user
|
||||
// that they need an Internet connection to run the project if desired.
|
||||
// Incrementing CACHE_VERSION will kick off the install event and force
|
||||
// previously cached resources to be updated from the network.
|
||||
/** @type {string} */
|
||||
const CACHE_VERSION = '1726998131|1051448710';
|
||||
/** @type {string} */
|
||||
const CACHE_PREFIX = 'MusicSearch-sw-cache-';
|
||||
const CACHE_NAME = CACHE_PREFIX + CACHE_VERSION;
|
||||
/** @type {string} */
|
||||
const OFFLINE_URL = 'index.offline.html';
|
||||
/** @type {boolean} */
|
||||
const ENSURE_CROSSORIGIN_ISOLATION_HEADERS = false;
|
||||
// Files that will be cached on load.
|
||||
/** @type {string[]} */
|
||||
const CACHED_FILES = ["index.html","index.js","index.offline.html","index.icon.png","index.apple-touch-icon.png","index.worker.js","index.audio.worklet.js"];
|
||||
// Files that we might not want the user to preload, and will only be cached on first load.
|
||||
/** @type {string[]} */
|
||||
const CACHABLE_FILES = ["index.wasm","index.pck"];
|
||||
const FULL_CACHE = CACHED_FILES.concat(CACHABLE_FILES);
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(CACHED_FILES)));
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(caches.keys().then(
|
||||
function (keys) {
|
||||
// Remove old caches.
|
||||
return Promise.all(keys.filter((key) => key.startsWith(CACHE_PREFIX) && key !== CACHE_NAME).map((key) => caches.delete(key)));
|
||||
}
|
||||
).then(function () {
|
||||
// Enable navigation preload if available.
|
||||
return ('navigationPreload' in self.registration) ? self.registration.navigationPreload.enable() : Promise.resolve();
|
||||
}));
|
||||
});
|
||||
|
||||
/**
|
||||
* Ensures that the response has the correct COEP/COOP headers
|
||||
* @param {Response} response
|
||||
* @returns {Response}
|
||||
*/
|
||||
function ensureCrossOriginIsolationHeaders(response) {
|
||||
if (response.headers.get('Cross-Origin-Embedder-Policy') === 'require-corp'
|
||||
&& response.headers.get('Cross-Origin-Opener-Policy') === 'same-origin') {
|
||||
return response;
|
||||
}
|
||||
|
||||
const crossOriginIsolatedHeaders = new Headers(response.headers);
|
||||
crossOriginIsolatedHeaders.set('Cross-Origin-Embedder-Policy', 'require-corp');
|
||||
crossOriginIsolatedHeaders.set('Cross-Origin-Opener-Policy', 'same-origin');
|
||||
const newResponse = new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: crossOriginIsolatedHeaders,
|
||||
});
|
||||
|
||||
return newResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls fetch and cache the result if it is cacheable
|
||||
* @param {FetchEvent} event
|
||||
* @param {Cache} cache
|
||||
* @param {boolean} isCacheable
|
||||
* @returns {Response}
|
||||
*/
|
||||
async function fetchAndCache(event, cache, isCacheable) {
|
||||
// Use the preloaded response, if it's there
|
||||
/** @type { Response } */
|
||||
let response = await event.preloadResponse;
|
||||
if (response == null) {
|
||||
// Or, go over network.
|
||||
response = await self.fetch(event.request);
|
||||
}
|
||||
|
||||
if (ENSURE_CROSSORIGIN_ISOLATION_HEADERS) {
|
||||
response = ensureCrossOriginIsolationHeaders(response);
|
||||
}
|
||||
|
||||
if (isCacheable) {
|
||||
// And update the cache
|
||||
cache.put(event.request, response.clone());
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
self.addEventListener(
|
||||
'fetch',
|
||||
/**
|
||||
* Triggered on fetch
|
||||
* @param {FetchEvent} event
|
||||
*/
|
||||
(event) => {
|
||||
const isNavigate = event.request.mode === 'navigate';
|
||||
const url = event.request.url || '';
|
||||
const referrer = event.request.referrer || '';
|
||||
const base = referrer.slice(0, referrer.lastIndexOf('/') + 1);
|
||||
const local = url.startsWith(base) ? url.replace(base, '') : '';
|
||||
const isCachable = FULL_CACHE.some((v) => v === local) || (base === referrer && base.endsWith(CACHED_FILES[0]));
|
||||
if (isNavigate || isCachable) {
|
||||
event.respondWith((async () => {
|
||||
// Try to use cache first
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
if (isNavigate) {
|
||||
// Check if we have full cache during HTML page request.
|
||||
/** @type {Response[]} */
|
||||
const fullCache = await Promise.all(FULL_CACHE.map((name) => cache.match(name)));
|
||||
const missing = fullCache.some((v) => v === undefined);
|
||||
if (missing) {
|
||||
try {
|
||||
// Try network if some cached file is missing (so we can display offline page in case).
|
||||
const response = await fetchAndCache(event, cache, isCachable);
|
||||
return response;
|
||||
} catch (e) {
|
||||
// And return the hopefully always cached offline page in case of network failure.
|
||||
console.error('Network error: ', e); // eslint-disable-line no-console
|
||||
return caches.match(OFFLINE_URL);
|
||||
}
|
||||
}
|
||||
}
|
||||
let cached = await cache.match(event.request);
|
||||
if (cached != null) {
|
||||
if (ENSURE_CROSSORIGIN_ISOLATION_HEADERS) {
|
||||
cached = ensureCrossOriginIsolationHeaders(cached);
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
// Try network if don't have it in cache.
|
||||
const response = await fetchAndCache(event, cache, isCachable);
|
||||
return response;
|
||||
})());
|
||||
} else if (ENSURE_CROSSORIGIN_ISOLATION_HEADERS) {
|
||||
event.respondWith((async () => {
|
||||
let response = await fetch(event.request);
|
||||
response = ensureCrossOriginIsolationHeaders(response);
|
||||
return response;
|
||||
})());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
// No cross origin
|
||||
if (event.origin !== self.origin) {
|
||||
return;
|
||||
}
|
||||
const id = event.source.id || '';
|
||||
const msg = event.data || '';
|
||||
// Ensure it's one of our clients.
|
||||
self.clients.get(id).then(function (client) {
|
||||
if (!client) {
|
||||
return; // Not a valid client.
|
||||
}
|
||||
if (msg === 'claim') {
|
||||
self.skipWaiting().then(() => self.clients.claim());
|
||||
} else if (msg === 'clear') {
|
||||
caches.delete(CACHE_NAME);
|
||||
} else if (msg === 'update') {
|
||||
self.skipWaiting().then(() => self.clients.claim()).then(() => self.clients.matchAll()).then((all) => all.forEach((c) => c.navigate(c.url)));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
BIN
cmd/search/index.wasm
Normal file
|
Before Width: | Height: | Size: 665 B After Width: | Height: | Size: 665 B |
|
Before Width: | Height: | Size: 628 B After Width: | Height: | Size: 628 B |
@@ -1,4 +1,6 @@
|
||||
@import "tailwindcss";
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
#search-container {
|
||||
text-align: center;
|
||||
|
||||
@@ -3,7 +3,4 @@ package web
|
||||
import "embed"
|
||||
|
||||
//go:embed "assets"
|
||||
var Assets embed.FS
|
||||
|
||||
//go:embed "swagger"
|
||||
var Swagger embed.FS
|
||||
var Files embed.FS
|
||||
|
||||
@@ -2,7 +2,7 @@ package web
|
||||
|
||||
import (
|
||||
"log"
|
||||
"music-server/internal/backend"
|
||||
"music-server/pkg/server"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -30,7 +30,7 @@ func FindGameWebHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func search(searchText string) {
|
||||
games_added = nil
|
||||
games := backend.GetAllGames()
|
||||
games := server.GetAllGames()
|
||||
for _, game := range games {
|
||||
if is_match_exact(searchText, game) {
|
||||
add_game(game)
|
||||
|
||||
6
db/efs.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package newDb
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed "migrations/*.sql"
|
||||
var MigrationsFs embed.FS
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
|
||||
package repository
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: game.sql
|
||||
|
||||
package repository
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
|
||||
package repository
|
||||
|
||||
@@ -15,7 +15,7 @@ type Game struct {
|
||||
Deleted *time.Time `json:"deleted"`
|
||||
LastChanged *time.Time `json:"last_changed"`
|
||||
Path string `json:"path"`
|
||||
TimesPlayed int32 `json:"times_played"`
|
||||
TimesPlayed *int32 `json:"times_played"`
|
||||
LastPlayed *time.Time `json:"last_played"`
|
||||
NumberOfSongs int32 `json:"number_of_songs"`
|
||||
Hash string `json:"hash"`
|
||||
@@ -25,7 +25,7 @@ type Song struct {
|
||||
GameID int32 `json:"game_id"`
|
||||
SongName string `json:"song_name"`
|
||||
Path string `json:"path"`
|
||||
TimesPlayed int32 `json:"times_played"`
|
||||
TimesPlayed *int32 `json:"times_played"`
|
||||
Hash string `json:"hash"`
|
||||
FileName *string `json:"file_name"`
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: song.sql
|
||||
|
||||
package repository
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: song_list.sql
|
||||
|
||||
package repository
|
||||
72
go.mod
@@ -5,52 +5,54 @@ go 1.23.0
|
||||
toolchain go1.24.2
|
||||
|
||||
require (
|
||||
github.com/MShekow/directory-checksum v1.4.9
|
||||
github.com/a-h/templ v0.3.937
|
||||
github.com/golang-migrate/migrate/v4 v4.18.3
|
||||
github.com/jackc/pgx/v5 v5.7.5
|
||||
github.com/labstack/echo/v4 v4.13.4
|
||||
github.com/MShekow/directory-checksum v1.4.6
|
||||
github.com/a-h/templ v0.3.865
|
||||
github.com/gin-contrib/static v1.1.2
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/golang-migrate/migrate/v4 v4.18.1
|
||||
github.com/jackc/pgtype v1.14.3
|
||||
github.com/jackc/pgx/v5 v5.5.5
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/panjf2000/ants/v2 v2.11.3
|
||||
github.com/spf13/afero v1.14.0
|
||||
github.com/swaggo/echo-swagger v1.4.1
|
||||
github.com/swaggo/swag v1.16.6
|
||||
github.com/spf13/afero v1.11.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/docker/docker v27.3.1+incompatible // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-errors/errors v1.5.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/swaggo/files/v2 v2.0.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.32.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/mod v0.26.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
)
|
||||
|
||||
348
go.sum
@@ -1,23 +1,30 @@
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/MShekow/directory-checksum v1.4.9 h1:olzWbrq9ylwfi7afuoivzHM8AV2z2KOaT7FJ6Ri2ppU=
|
||||
github.com/MShekow/directory-checksum v1.4.9/go.mod h1:LhNeWmPftlKTlc3TNurdihPK/whw9j76VnLaTRu2SkU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/MShekow/directory-checksum v1.4.6 h1:2fhlCYbpjEN1iH9S0tdmEM0p1wvNT9x5x0rIchGI7nE=
|
||||
github.com/MShekow/directory-checksum v1.4.6/go.mod h1:bMfFBkaIlNk7O9VgEi8D2X7Q2Jfk3c7d67z3t6cpIi4=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/a-h/templ v0.3.937 h1:Ta+0Tf9YuZplUyKTUxReV36FCRKtK6FRMWpmXERHDnM=
|
||||
github.com/a-h/templ v0.3.937/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/a-h/templ v0.3.865 h1:nYn5EWm9EiXaDgWcMQaKiKvrydqgxDUtT1+4zU2C43A=
|
||||
github.com/a-h/templ v0.3.865/go.mod h1:oLBbZVQ6//Q6zpvSMPTuBK0F3qOtBdFBcGRspcT+VNQ=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dhui/dktest v0.4.5 h1:uUfYBIVREmj/Rw6MvgmqNAYzTiKOHJak+enB5Di73MM=
|
||||
github.com/dhui/dktest v0.4.5/go.mod h1:tmcyeHDKagvlDrz7gDKq4UAJOLIfVZYkfD5OnHDwcCo=
|
||||
github.com/dhui/dktest v0.4.3 h1:wquqUxAFdcUgabAVLvSCOKOlag5cIZuaOjYIBOWdsR0=
|
||||
github.com/dhui/dktest v0.4.3/go.mod h1:zNK8IwktWzQRm6I/l2Wjp7MakiyaFWv4G1hjmodmMTs=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
|
||||
@@ -28,104 +35,192 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/static v1.1.2 h1:c3kT4bFkUJn2aoRU3s6XnMjJT8J6nNWJkR0NglqmlZ4=
|
||||
github.com/gin-contrib/static v1.1.2/go.mod h1:Fw90ozjHCmZBWbgrsqrDvO28YbhKEKzKp8GixhR4yLw=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4B/swMiAmDLs=
|
||||
github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY=
|
||||
github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
|
||||
github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
|
||||
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
|
||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
|
||||
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
|
||||
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgtype v1.14.3 h1:h6W9cPuHsRWQFTWUZMAKMgG5jSwQI0Zurzdvlx3Plus=
|
||||
github.com/jackc/pgtype v1.14.3/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||
github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU=
|
||||
github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
|
||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
|
||||
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=
|
||||
github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk=
|
||||
github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc=
|
||||
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
|
||||
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
|
||||
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94=
|
||||
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
|
||||
@@ -134,41 +229,122 @@ go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZk
|
||||
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
||||
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
||||
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetCharacterList() []string {
|
||||
charactersPath := os.Getenv("CHARACTERS_PATH")
|
||||
fmt.Printf("dir: %s\n", charactersPath)
|
||||
if !strings.HasSuffix(charactersPath, "/") {
|
||||
charactersPath += "/"
|
||||
}
|
||||
files, err := os.ReadDir(charactersPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var characters []string
|
||||
for _, file := range files {
|
||||
if isImage(file) {
|
||||
characters = append(characters, file.Name())
|
||||
}
|
||||
}
|
||||
return characters
|
||||
}
|
||||
|
||||
func GetCharacter(character string) string {
|
||||
charactersPath := os.Getenv("CHARACTERS_PATH")
|
||||
fmt.Printf("dir: %s\n", charactersPath)
|
||||
if !strings.HasSuffix(charactersPath, "/") {
|
||||
charactersPath += "/"
|
||||
}
|
||||
return charactersPath + character
|
||||
}
|
||||
|
||||
func isImage(entry os.DirEntry) bool {
|
||||
return !entry.IsDir() && (strings.HasSuffix(entry.Name(), ".jpg") || strings.HasSuffix(entry.Name(), ".jpeg") ||
|
||||
strings.HasSuffix(entry.Name(), ".png"))
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type giteaResponse struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Assets []assetResponse `json:"assets"`
|
||||
}
|
||||
|
||||
type assetResponse struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
DownloadUrl string `json:"browser_download_url"`
|
||||
}
|
||||
|
||||
func CheckLatest() string {
|
||||
resp, err := http.Get("https://gitea.sanplex.tech/api/v1/repos/sansan/MusicPlayer/releases/latest")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
//Create a variable of the same type as our model
|
||||
var cResp giteaResponse
|
||||
//Decode the data
|
||||
if err := json.NewDecoder(resp.Body).Decode(&cResp); err != nil {
|
||||
fmt.Println(err)
|
||||
log.Fatal("ooopsss! an error occurred, please try again")
|
||||
}
|
||||
log.Printf("Id: %v, Name: %v", cResp.Id, cResp.Name)
|
||||
return cResp.Name
|
||||
}
|
||||
|
||||
func ListAssetsOfLatest() []string {
|
||||
resp, err := http.Get("https://gitea.sanplex.tech/api/v1/repos/sansan/MusicPlayer/releases/latest")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
//Create a variable of the same type as our model
|
||||
var cResp giteaResponse
|
||||
//Decode the data
|
||||
if err := json.NewDecoder(resp.Body).Decode(&cResp); err != nil {
|
||||
fmt.Println(err)
|
||||
log.Fatal("ooopsss! an error occurred, please try again")
|
||||
}
|
||||
log.Printf("Id: %v, Name: %v", cResp.Id, cResp.Name)
|
||||
var assets []string
|
||||
for _, asset := range cResp.Assets {
|
||||
log.Printf("Id: %v, Name: %v, Asset: %v", cResp.Id, cResp.Name, asset.Name)
|
||||
assets = append(assets, asset.Name)
|
||||
}
|
||||
return assets
|
||||
}
|
||||
|
||||
func DownloadLatestWindows() string {
|
||||
resp, err := http.Get("https://gitea.sanplex.tech/api/v1/repos/sansan/MusicPlayer/releases/latest")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
//Create a variable of the same type as our model
|
||||
var cResp giteaResponse
|
||||
//Decode the data
|
||||
if err := json.NewDecoder(resp.Body).Decode(&cResp); err != nil {
|
||||
fmt.Println(err)
|
||||
log.Fatal("ooopsss! an error occurred, please try again")
|
||||
}
|
||||
log.Printf("Id: %v, Name: %v", cResp.Id, cResp.Name)
|
||||
for _, asset := range cResp.Assets {
|
||||
log.Printf("Id: %v, Name: %v, Asset: %v", cResp.Id, cResp.Name, asset.Name)
|
||||
if strings.HasSuffix(asset.Name, ".exe") {
|
||||
return asset.DownloadUrl
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func DownloadLatestLinux() string {
|
||||
resp, err := http.Get("https://gitea.sanplex.tech/api/v1/repos/sansan/MusicPlayer/releases/latest")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
//Create a variable of the same type as our model
|
||||
var cResp giteaResponse
|
||||
//Decode the data
|
||||
if err := json.NewDecoder(resp.Body).Decode(&cResp); err != nil {
|
||||
fmt.Println(err)
|
||||
log.Fatal("ooopsss! an error occurred, please try again")
|
||||
}
|
||||
log.Printf("Id: %v, Name: %v", cResp.Id, cResp.Name)
|
||||
for _, asset := range cResp.Assets {
|
||||
log.Printf("Id: %v, Name: %v, Asset: %v", cResp.Id, cResp.Name, asset.Name)
|
||||
if strings.HasSuffix(asset.Name, ".x86_64") {
|
||||
return asset.DownloadUrl
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"music-server/internal/db"
|
||||
"music-server/internal/db/repository"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SongInfo struct {
|
||||
Game string `json:"Game"`
|
||||
GamePlayed int32 `json:"GamePlayed"`
|
||||
Song string `json:"Song"`
|
||||
SongPlayed int32 `json:"SongPlayed"`
|
||||
CurrentlyPlaying bool `json:"CurrentlyPlaying"`
|
||||
SongNo int `json:"SongNo"`
|
||||
}
|
||||
|
||||
var currentSong = -1
|
||||
|
||||
var gamesNew []repository.Game
|
||||
|
||||
var songQueNew []repository.Song
|
||||
|
||||
var lastFetchedNew repository.Song
|
||||
var repo *repository.Queries
|
||||
|
||||
func initRepo() {
|
||||
if repo == nil {
|
||||
repo = repository.New(db.Dbpool)
|
||||
}
|
||||
}
|
||||
|
||||
func getAllGames() []repository.Game {
|
||||
if len(gamesNew) == 0 {
|
||||
initRepo()
|
||||
gamesNew, _ = repo.FindAllGames(db.Ctx)
|
||||
}
|
||||
return gamesNew
|
||||
|
||||
}
|
||||
|
||||
func GetSoundCheckSong() string {
|
||||
files, err := os.ReadDir("songs")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fileInfo := files[rand.Intn(len(files))]
|
||||
return "songs/" + fileInfo.Name()
|
||||
}
|
||||
|
||||
func Reset() {
|
||||
songQueNew = nil
|
||||
currentSong = -1
|
||||
initRepo()
|
||||
gamesNew, _ = repo.FindAllGames(db.Ctx)
|
||||
}
|
||||
|
||||
func AddLatestToQue() {
|
||||
if lastFetchedNew.Path != "" {
|
||||
currentSong = len(songQueNew)
|
||||
songQueNew = append(songQueNew, lastFetchedNew)
|
||||
lastFetchedNew = repository.Song{}
|
||||
}
|
||||
}
|
||||
|
||||
func AddLatestPlayed() {
|
||||
if len(songQueNew) == 0 {
|
||||
return
|
||||
}
|
||||
currentSongData := songQueNew[currentSong]
|
||||
|
||||
initRepo()
|
||||
repo.AddGamePlayed(db.Ctx, currentSongData.GameID)
|
||||
repo.AddSongPlayed(db.Ctx, repository.AddSongPlayedParams{GameID: currentSongData.GameID, SongName: currentSongData.SongName})
|
||||
}
|
||||
|
||||
func SetPlayed(songNumber int) {
|
||||
if len(songQueNew) == 0 || songNumber >= len(songQueNew) {
|
||||
return
|
||||
}
|
||||
songData := songQueNew[songNumber]
|
||||
initRepo()
|
||||
repo.AddGamePlayed(db.Ctx, songData.GameID)
|
||||
repo.AddSongPlayed(db.Ctx, repository.AddSongPlayedParams{GameID: songData.GameID, SongName: songData.SongName})
|
||||
}
|
||||
|
||||
func GetRandomSong() string {
|
||||
getAllGames()
|
||||
if len(gamesNew) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
song := getSongFromList(gamesNew)
|
||||
lastFetchedNew = song
|
||||
return song.Path
|
||||
}
|
||||
|
||||
func GetRandomSongLowChance() string {
|
||||
getAllGames()
|
||||
|
||||
var listOfGames []repository.Game
|
||||
|
||||
var averagePlayed = getAveragePlayed()
|
||||
|
||||
for _, data := range gamesNew {
|
||||
timesToAdd := averagePlayed - data.TimesPlayed
|
||||
if timesToAdd <= 0 {
|
||||
listOfGames = append(listOfGames, data)
|
||||
} else {
|
||||
for i := int32(0); i < timesToAdd; i++ {
|
||||
listOfGames = append(listOfGames, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
song := getSongFromList(listOfGames)
|
||||
|
||||
lastFetchedNew = song
|
||||
return song.Path
|
||||
|
||||
}
|
||||
|
||||
func GetRandomSongClassic() string {
|
||||
getAllGames()
|
||||
|
||||
var listOfAllSongs []repository.Song
|
||||
for _, game := range gamesNew {
|
||||
songList, _ := repo.FindSongsFromGame(db.Ctx, game.ID)
|
||||
listOfAllSongs = append(listOfAllSongs, songList...)
|
||||
}
|
||||
|
||||
songFound := false
|
||||
var song repository.Song
|
||||
for !songFound {
|
||||
song = listOfAllSongs[rand.Intn(len(listOfAllSongs))]
|
||||
gameData, err := repo.GetGameById(db.Ctx, song.GameID)
|
||||
|
||||
if err != nil {
|
||||
repo.RemoveBrokenSong(db.Ctx, song.Path)
|
||||
log.Printf("Song not found, song '%s' deleted from game '%s' FileName: %s\n", song.SongName, gameData.GameName, *song.FileName)
|
||||
continue
|
||||
}
|
||||
|
||||
//Check if file exists and open
|
||||
openFile, err := os.Open(song.Path)
|
||||
if err != nil || (song.FileName != nil && gameData.Path+*song.FileName != song.Path) {
|
||||
//File not found
|
||||
repo.RemoveBrokenSong(db.Ctx, song.Path)
|
||||
log.Printf("Song not found, song '%s' deleted from game '%s' FileName: %s\n", song.SongName, gameData.GameName, *song.FileName)
|
||||
} else {
|
||||
songFound = true
|
||||
}
|
||||
err = openFile.Close()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
lastFetchedNew = song
|
||||
return song.Path
|
||||
}
|
||||
|
||||
func GetSongInfo() SongInfo {
|
||||
if songQueNew == nil {
|
||||
return SongInfo{}
|
||||
}
|
||||
var currentSongData = songQueNew[currentSong]
|
||||
|
||||
currentGameData := getCurrentGame(currentSongData)
|
||||
|
||||
return SongInfo{
|
||||
Game: currentGameData.GameName,
|
||||
GamePlayed: currentGameData.TimesPlayed,
|
||||
Song: currentSongData.SongName,
|
||||
SongPlayed: currentSongData.TimesPlayed,
|
||||
CurrentlyPlaying: true,
|
||||
SongNo: currentSong,
|
||||
}
|
||||
}
|
||||
|
||||
func GetPlayedSongs() []SongInfo {
|
||||
var songList []SongInfo
|
||||
|
||||
for i, song := range songQueNew {
|
||||
gameData := getCurrentGame(song)
|
||||
songList = append(songList, SongInfo{
|
||||
Game: gameData.GameName,
|
||||
GamePlayed: gameData.TimesPlayed,
|
||||
Song: song.SongName,
|
||||
SongPlayed: song.TimesPlayed,
|
||||
CurrentlyPlaying: i == currentSong,
|
||||
SongNo: i,
|
||||
})
|
||||
}
|
||||
return songList
|
||||
}
|
||||
|
||||
func GetSong(song string) string {
|
||||
currentSong, _ = strconv.Atoi(song)
|
||||
if currentSong >= len(songQueNew) {
|
||||
currentSong = len(songQueNew) - 1
|
||||
} else if currentSong < 0 {
|
||||
currentSong = 0
|
||||
}
|
||||
songData := songQueNew[currentSong]
|
||||
return songData.Path
|
||||
}
|
||||
|
||||
func GetAllGames() []string {
|
||||
getAllGames()
|
||||
|
||||
var jsonArray []string
|
||||
for _, game := range gamesNew {
|
||||
jsonArray = append(jsonArray, game.GameName)
|
||||
}
|
||||
return jsonArray
|
||||
}
|
||||
|
||||
func GetAllGamesRandom() []string {
|
||||
getAllGames()
|
||||
|
||||
var jsonArray []string
|
||||
for _, game := range gamesNew {
|
||||
jsonArray = append(jsonArray, game.GameName)
|
||||
}
|
||||
rand.Shuffle(len(jsonArray), func(i, j int) { jsonArray[i], jsonArray[j] = jsonArray[j], jsonArray[i] })
|
||||
return jsonArray
|
||||
}
|
||||
|
||||
func GetNextSong() string {
|
||||
if songQueNew == nil {
|
||||
return ""
|
||||
}
|
||||
if currentSong == len(songQueNew)-1 || currentSong == -1 {
|
||||
songData := songQueNew[currentSong]
|
||||
return songData.Path
|
||||
} else {
|
||||
currentSong = currentSong + 1
|
||||
songData := songQueNew[currentSong]
|
||||
return songData.Path
|
||||
}
|
||||
}
|
||||
|
||||
func GetPreviousSong() string {
|
||||
if songQueNew == nil {
|
||||
return ""
|
||||
}
|
||||
if currentSong == -1 || currentSong == 0 {
|
||||
songData := songQueNew[0]
|
||||
return songData.Path
|
||||
} else {
|
||||
currentSong = currentSong - 1
|
||||
songData := songQueNew[currentSong]
|
||||
return songData.Path
|
||||
}
|
||||
}
|
||||
|
||||
func getSongFromList(games []repository.Game) repository.Song {
|
||||
songFound := false
|
||||
var song repository.Song
|
||||
for !songFound {
|
||||
game := getRandomGame(games)
|
||||
songs, _ := repo.FindSongsFromGame(db.Ctx, game.ID)
|
||||
if len(songs) == 0 {
|
||||
continue
|
||||
}
|
||||
song = songs[rand.Intn(len(songs))]
|
||||
log.Println("song = ", song)
|
||||
|
||||
//Check if file exists and open
|
||||
openFile, err := os.Open(song.Path)
|
||||
if err != nil || (song.FileName != nil && game.Path+*song.FileName != song.Path) || (song.FileName != nil && strings.HasSuffix(*song.FileName, ".wav")) {
|
||||
//File not found
|
||||
repo.RemoveBrokenSong(db.Ctx, song.Path)
|
||||
log.Printf("Song not found, song '%s' deleted from game '%s' FileName: %v\n", song.SongName, game.GameName, song.FileName)
|
||||
} else {
|
||||
songFound = true
|
||||
}
|
||||
|
||||
err = openFile.Close()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
return song
|
||||
}
|
||||
|
||||
func getCurrentGame(currentSongData repository.Song) repository.Game {
|
||||
for _, game := range gamesNew {
|
||||
if game.ID == currentSongData.GameID {
|
||||
return game
|
||||
}
|
||||
}
|
||||
return repository.Game{}
|
||||
}
|
||||
|
||||
func getAveragePlayed() int32 {
|
||||
getAllGames()
|
||||
var sum int32
|
||||
for _, data := range gamesNew {
|
||||
sum += data.TimesPlayed
|
||||
}
|
||||
return sum / int32(len(gamesNew))
|
||||
}
|
||||
|
||||
func getRandomGame(listOfGames []repository.Game) repository.Game {
|
||||
return listOfGames[rand.Intn(len(listOfGames))]
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
var Dbpool *pgxpool.Pool
|
||||
var Ctx = context.Background()
|
||||
|
||||
//go:embed "migrations/*.sql"
|
||||
var MigrationsFs embed.FS
|
||||
|
||||
func InitDB(host string, port string, user string, password string, dbname string) {
|
||||
|
||||
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s "+
|
||||
"password=%s dbname=%s sslmode=disable",
|
||||
host, port, user, password, dbname)
|
||||
|
||||
fmt.Println(psqlInfo)
|
||||
|
||||
var err error
|
||||
Dbpool, err = pgxpool.New(Ctx, psqlInfo)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var success string
|
||||
err = Dbpool.QueryRow(Ctx, "select 'Successfully connected!'").Scan(&success)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(success)
|
||||
}
|
||||
|
||||
func CloseDb() {
|
||||
fmt.Println("Closing connection to database")
|
||||
Dbpool.Close()
|
||||
}
|
||||
|
||||
func Testf() {
|
||||
rows, dbErr := Dbpool.Query(Ctx, "select game_name from game")
|
||||
if dbErr != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", dbErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
for rows.Next() {
|
||||
var gameName string
|
||||
dbErr = rows.Scan(&gameName)
|
||||
if dbErr != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", dbErr)
|
||||
}
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%v\n", gameName)
|
||||
}
|
||||
}
|
||||
|
||||
func ResetGameIdSeq() {
|
||||
_, err := Dbpool.Query(Ctx, "SELECT setval('game_id_seq', (SELECT MAX(id) FROM game)+1);")
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func createDb(host string, port string, user string, password string, dbname string) {
|
||||
conninfo := fmt.Sprintf("host=%s port=%s user=%s password=%s sslmode=disable", host, port, user, password)
|
||||
db, err := sql.Open("postgres", conninfo)
|
||||
defer db.Close()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
_, err = db.Exec("create database " + dbname)
|
||||
if err != nil {
|
||||
//handle the error
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Finished creating database")
|
||||
}
|
||||
|
||||
func Migrate_db(host string, port string, user string, password string, dbname string) {
|
||||
migrationInfo := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable",
|
||||
user, password, host, port, dbname)
|
||||
|
||||
fmt.Println("Migration Info: ", migrationInfo)
|
||||
|
||||
db, err := sql.Open("postgres", migrationInfo)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
_, err = db.Query("select * from game")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
createDb(host, port, user, password, dbname)
|
||||
|
||||
db, err = sql.Open("postgres", migrationInfo)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
files, err := iofs.New(MigrationsFs, "migrations")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
m, err := migrate.NewWithInstance("iofs", files, "postgres", driver)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
/*m, err := migrate.NewWithDatabaseInstance(
|
||||
"file://./db/migrations/",
|
||||
"postgres", driver)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}*/
|
||||
|
||||
version, _, err := m.Version()
|
||||
if err != nil {
|
||||
log.Println("Migration version err: ", err)
|
||||
}
|
||||
|
||||
fmt.Println("Migration version before: ", version)
|
||||
|
||||
//err = m.Force(1)
|
||||
//err = m.Up() // or m.Steps(2) if you want to explicitly set the number of migrations to run
|
||||
//if err != nil {
|
||||
// log.Println("Force err: ", err)
|
||||
//}
|
||||
|
||||
err = m.Migrate(2)
|
||||
//err = m.Up() // or m.Steps(2) if you want to explicitly set the number of migrations to run
|
||||
if err != nil {
|
||||
log.Println("Migration err: ", err)
|
||||
}
|
||||
|
||||
versionAfter, _, err := m.Version()
|
||||
if err != nil {
|
||||
log.Println("Migration version err: ", err)
|
||||
}
|
||||
|
||||
fmt.Println("Migration version after: ", versionAfter)
|
||||
|
||||
fmt.Println("Migration done")
|
||||
|
||||
db.Close()
|
||||
}
|
||||
|
||||
// Health checks the health of the database connection by pinging the database.
|
||||
// It returns a map with keys indicating various health statistics.
|
||||
func Health() map[string]string {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
stats := make(map[string]string)
|
||||
|
||||
// Ping the database
|
||||
//err := s.db.PingContext(ctx)
|
||||
err := Dbpool.Ping(ctx)
|
||||
if err != nil {
|
||||
stats["status"] = "down"
|
||||
stats["error"] = fmt.Sprintf("db down: %v", err)
|
||||
log.Fatalf("db down: %v", err) // Log the error and terminate the program
|
||||
return stats
|
||||
}
|
||||
|
||||
// Database is up, add more statistics
|
||||
stats["status"] = "up"
|
||||
stats["message"] = "It's healthy"
|
||||
|
||||
// Get database stats (like open connections, in use, idle, etc.)
|
||||
//dbStats := s.db.Stats()
|
||||
dbStats := Dbpool.Stat()
|
||||
//stats["open_connections"] = strconv.Itoa(dbStats.OpenConnections)
|
||||
stats["open_connections"] = strconv.Itoa(int(dbStats.NewConnsCount()))
|
||||
//stats["in_use"] = strconv.Itoa(dbStats.InUse)
|
||||
stats["in_use"] = strconv.Itoa(int(dbStats.AcquiredConns()))
|
||||
//stats["idle"] = strconv.Itoa(dbStats.Idle)
|
||||
stats["idle"] = strconv.Itoa(int(dbStats.IdleConns()))
|
||||
//stats["wait_count"] = strconv.FormatInt(dbStats.WaitCount, 10)
|
||||
stats["wait_count"] = strconv.FormatInt(dbStats.AcquireCount(), 10)
|
||||
//stats["wait_duration"] = dbStats.WaitDuration.String()
|
||||
stats["wait_duration"] = dbStats.AcquireDuration().String()
|
||||
//stats["max_idle_closed"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10)
|
||||
stats["max_idle_closed"] = strconv.FormatInt(dbStats.MaxIdleDestroyCount(), 10)
|
||||
//stats["max_lifetime_closed"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10)
|
||||
stats["max_lifetime_closed"] = strconv.FormatInt(dbStats.MaxLifetimeDestroyCount(), 10)
|
||||
|
||||
// Evaluate stats to provide a health message
|
||||
if int(dbStats.NewConnsCount()) > 40 { // Assuming 50 is the max for this example
|
||||
stats["message"] = "The database is experiencing heavy load."
|
||||
}
|
||||
|
||||
if dbStats.AcquireCount() > 1000 {
|
||||
stats["message"] = "The database has a high number of wait events, indicating potential bottlenecks."
|
||||
}
|
||||
|
||||
if dbStats.MaxIdleDestroyCount() > int64(dbStats.NewConnsCount())/2 {
|
||||
stats["message"] = "Many idle connections are being closed, consider revising the connection pool settings."
|
||||
}
|
||||
|
||||
if dbStats.MaxLifetimeDestroyCount() > int64(dbStats.NewConnsCount())/2 {
|
||||
stats["message"] = "Many connections are being closed due to max lifetime, consider increasing max lifetime or revising the connection usage pattern."
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
Alter table game
|
||||
alter column times_played set null;
|
||||
|
||||
Alter table song
|
||||
alter column times_played set null;
|
||||
@@ -1,5 +0,0 @@
|
||||
Alter table game
|
||||
alter column times_played set not null;
|
||||
|
||||
Alter table song
|
||||
alter column times_played set not null;
|
||||
@@ -1,41 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"log"
|
||||
"music-server/internal/backend"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type DownloadHandler struct {
|
||||
}
|
||||
|
||||
func NewDownloadHandler() *DownloadHandler {
|
||||
return &DownloadHandler{}
|
||||
}
|
||||
|
||||
func (d *DownloadHandler) checkLatest(ctx echo.Context) error {
|
||||
log.Println("Checking latest version")
|
||||
latest := backend.CheckLatest()
|
||||
return ctx.JSON(http.StatusOK, latest)
|
||||
}
|
||||
|
||||
func (d *DownloadHandler) listAssetsOfLatest(ctx echo.Context) error {
|
||||
log.Println("Listing assets")
|
||||
assets := backend.ListAssetsOfLatest()
|
||||
return ctx.JSON(http.StatusOK, assets)
|
||||
}
|
||||
|
||||
func (d *DownloadHandler) downloadLatestWindows(ctx echo.Context) error {
|
||||
log.Println("Downloading latest windows")
|
||||
asset := backend.DownloadLatestWindows()
|
||||
ctx.Response().Header().Set("Content-Type", "application/octet-stream")
|
||||
return ctx.Redirect(http.StatusFound, asset)
|
||||
}
|
||||
|
||||
func (d *DownloadHandler) downloadLatestLinux(ctx echo.Context) error {
|
||||
log.Println("Downloading latest linux")
|
||||
asset := backend.DownloadLatestLinux()
|
||||
ctx.Response().Header().Set("Content-Type", "application/octet-stream")
|
||||
return ctx.Redirect(http.StatusFound, asset)
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"music-server/internal/backend"
|
||||
"music-server/internal/db"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type IndexHandler struct {
|
||||
}
|
||||
|
||||
func NewIndexHandler() *IndexHandler {
|
||||
return &IndexHandler{}
|
||||
}
|
||||
|
||||
// GetVersion godoc
|
||||
//
|
||||
// @Summary Getting the version of the backend
|
||||
// @Description get string by ID
|
||||
// @Tags accounts
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} backend.VersionData
|
||||
// @Failure 404 {object} string
|
||||
// @Router /version [get]
|
||||
func (i *IndexHandler) GetVersion(ctx echo.Context) error {
|
||||
versionHistory := backend.GetVersionHistory()
|
||||
if versionHistory.Version == "" {
|
||||
return ctx.JSON(http.StatusNotFound, "version not found")
|
||||
}
|
||||
return ctx.JSON(http.StatusOK, versionHistory)
|
||||
}
|
||||
|
||||
func (i *IndexHandler) GetDBTest(ctx echo.Context) error {
|
||||
backend.TestDB()
|
||||
return ctx.JSON(http.StatusOK, "TestedDB")
|
||||
}
|
||||
|
||||
func (i *IndexHandler) HealthCheck(ctx echo.Context) error {
|
||||
return ctx.JSON(http.StatusOK, db.Health())
|
||||
}
|
||||
|
||||
func (i *IndexHandler) GetCharacterList(ctx echo.Context) error {
|
||||
characters := backend.GetCharacterList()
|
||||
return ctx.JSON(http.StatusOK, characters)
|
||||
}
|
||||
|
||||
func (i *IndexHandler) GetCharacter(ctx echo.Context) error {
|
||||
character := ctx.QueryParam("name")
|
||||
return ctx.File(backend.GetCharacter(character))
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"log"
|
||||
"music-server/internal/backend"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type MusicHandler struct {
|
||||
}
|
||||
|
||||
func NewMusicHandler() *MusicHandler {
|
||||
return &MusicHandler{}
|
||||
}
|
||||
|
||||
func (m *MusicHandler) GetSong(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
song := ctx.QueryParam("song")
|
||||
if song == "" {
|
||||
return ctx.String(http.StatusBadRequest, "song can't be empty")
|
||||
}
|
||||
songPath := backend.GetSong(song)
|
||||
file, err := os.Open(songPath)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, err)
|
||||
}
|
||||
defer file.Close()
|
||||
return ctx.Stream(http.StatusOK, "audio/mpeg", file)
|
||||
}
|
||||
|
||||
func (m *MusicHandler) GetSoundCheckSong(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
songPath := backend.GetSoundCheckSong()
|
||||
file, err := os.Open(songPath)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, err)
|
||||
}
|
||||
defer file.Close()
|
||||
return ctx.Stream(http.StatusOK, "audio/mpeg", file)
|
||||
}
|
||||
|
||||
func (m *MusicHandler) ResetMusic(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
backend.Reset()
|
||||
return ctx.NoContent(http.StatusOK)
|
||||
}
|
||||
|
||||
func (m *MusicHandler) GetRandomSong(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
songPath := backend.GetRandomSong()
|
||||
file, err := os.Open(songPath)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, err)
|
||||
}
|
||||
defer file.Close()
|
||||
return ctx.Stream(http.StatusOK, "audio/mpeg", file)
|
||||
}
|
||||
|
||||
func (m *MusicHandler) GetRandomSongLowChance(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
songPath := backend.GetRandomSongLowChance()
|
||||
file, err := os.Open(songPath)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, err)
|
||||
}
|
||||
defer file.Close()
|
||||
return ctx.Stream(http.StatusOK, "audio/mpeg", file)
|
||||
}
|
||||
|
||||
func (m *MusicHandler) GetRandomSongClassic(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
songPath := backend.GetRandomSongClassic()
|
||||
file, err := os.Open(songPath)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, err)
|
||||
}
|
||||
defer file.Close()
|
||||
return ctx.Stream(http.StatusOK, "audio/mpeg", file)
|
||||
}
|
||||
|
||||
func (m *MusicHandler) GetSongInfo(ctx echo.Context) error {
|
||||
song := backend.GetSongInfo()
|
||||
return ctx.JSON(http.StatusOK, song)
|
||||
}
|
||||
|
||||
func (m *MusicHandler) GetPlayedSongs(ctx echo.Context) error {
|
||||
songList := backend.GetPlayedSongs()
|
||||
return ctx.JSON(http.StatusOK, songList)
|
||||
}
|
||||
|
||||
func (m *MusicHandler) GetNextSong(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
songPath := backend.GetNextSong()
|
||||
file, err := os.Open(songPath)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, err)
|
||||
}
|
||||
defer file.Close()
|
||||
return ctx.Stream(http.StatusOK, "audio/mpeg", file)
|
||||
}
|
||||
|
||||
func (m *MusicHandler) GetPreviousSong(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
songPath := backend.GetPreviousSong()
|
||||
file, err := os.Open(songPath)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, err)
|
||||
}
|
||||
defer file.Close()
|
||||
return ctx.Stream(http.StatusOK, "audio/mpeg", file)
|
||||
}
|
||||
|
||||
func (m *MusicHandler) GetAllGames(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
gameList := backend.GetAllGames()
|
||||
return ctx.JSON(http.StatusOK, gameList)
|
||||
}
|
||||
|
||||
func (m *MusicHandler) GetAllGamesRandom(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
gameList := backend.GetAllGamesRandom()
|
||||
return ctx.JSON(http.StatusOK, gameList)
|
||||
}
|
||||
|
||||
func (m *MusicHandler) PutPlayed(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
song, err := strconv.Atoi(ctx.QueryParam("song"))
|
||||
if err != nil {
|
||||
return ctx.JSON(http.StatusBadRequest, err)
|
||||
}
|
||||
log.Println("song", song)
|
||||
backend.SetPlayed(song)
|
||||
return ctx.NoContent(http.StatusOK)
|
||||
}
|
||||
|
||||
func (m *MusicHandler) AddLatestToQue(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
backend.AddLatestToQue()
|
||||
return ctx.NoContent(http.StatusOK)
|
||||
}
|
||||
|
||||
func (m *MusicHandler) AddLatestPlayed(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
backend.AddLatestPlayed()
|
||||
return ctx.NoContent(http.StatusOK)
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"music-server/cmd/web"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
_ "music-server/cmd/docs"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
"github.com/swaggo/echo-swagger" // echo-swagger middleware
|
||||
//_ "github.com/swaggo/echo-swagger/example/docs" // docs is generated by Swag CLI, you have to import it.
|
||||
)
|
||||
|
||||
// @Title Swagger Example API
|
||||
// @version 0.5
|
||||
// @description This is a sample server Petstore server.
|
||||
// @termsOfService http://swagger.io/terms/
|
||||
|
||||
// @contact.name Sebastian Olsson
|
||||
// @contact.email zarnor91@gmail.com
|
||||
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
// @host localhost:8080
|
||||
func (s *Server) RegisterRoutes() http.Handler {
|
||||
e := echo.New()
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
|
||||
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||
AllowOrigins: []string{"https://*", "http://*"},
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"},
|
||||
AllowHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 300,
|
||||
}))
|
||||
|
||||
fileServer := http.FileServer(http.FS(web.Assets))
|
||||
e.GET("/assets/*", echo.WrapHandler(fileServer))
|
||||
|
||||
e.GET("/search", echo.WrapHandler(templ.Handler(web.HelloForm())))
|
||||
e.POST("/find", echo.WrapHandler(http.HandlerFunc(web.FindGameWebHandler)))
|
||||
|
||||
e.Static("/", "/frontend")
|
||||
|
||||
/*swagger := http.FileServer(http.FS(web.Swagger))
|
||||
e.GET("/swagger/*", echo.WrapHandler(swagger))*/
|
||||
|
||||
swaggerRedirect := func(c echo.Context) error {
|
||||
return c.Redirect(http.StatusMovedPermanently, "/swagger/index.html")
|
||||
}
|
||||
e.GET("/swagger", swaggerRedirect)
|
||||
e.GET("/swagger/", swaggerRedirect)
|
||||
e.GET("/swagger/*", echoSwagger.WrapHandler)
|
||||
|
||||
index := NewIndexHandler()
|
||||
e.GET("/version", index.GetVersion)
|
||||
e.GET("/dbtest", index.GetDBTest)
|
||||
e.GET("/health", index.HealthCheck)
|
||||
e.GET("/character", index.GetCharacter)
|
||||
e.GET("/characters", index.GetCharacterList)
|
||||
|
||||
download := NewDownloadHandler()
|
||||
e.GET("/download", download.checkLatest)
|
||||
e.GET("/download/list", download.listAssetsOfLatest)
|
||||
e.GET("/download/windows", download.downloadLatestWindows)
|
||||
e.GET("/download/linux", download.downloadLatestLinux)
|
||||
|
||||
sync := NewSyncHandler()
|
||||
syncGroup := e.Group("/sync")
|
||||
syncGroup.GET("", sync.SyncGamesNewOnlyChanges)
|
||||
syncGroup.GET("/progress", sync.SyncProgress)
|
||||
syncGroup.GET("/new", sync.SyncGamesNewOnlyChanges)
|
||||
syncGroup.GET("/full", sync.SyncGamesNewFull)
|
||||
syncGroup.GET("/new/full", sync.SyncGamesNewFull)
|
||||
syncGroup.GET("/quick", sync.SyncGamesNewOnlyChanges)
|
||||
syncGroup.GET("/reset", sync.ResetGames)
|
||||
|
||||
music := NewMusicHandler()
|
||||
musicGroup := e.Group("/music")
|
||||
musicGroup.GET("", music.GetSong)
|
||||
musicGroup.GET("/soundTest", music.GetSoundCheckSong)
|
||||
musicGroup.GET("/reset", music.ResetMusic)
|
||||
musicGroup.GET("/rand", music.GetRandomSong)
|
||||
musicGroup.GET("/rand/low", music.GetRandomSongLowChance)
|
||||
musicGroup.GET("/rand/classic", music.GetRandomSongClassic)
|
||||
musicGroup.GET("/info", music.GetSongInfo)
|
||||
musicGroup.GET("/list", music.GetPlayedSongs)
|
||||
musicGroup.GET("/next", music.GetNextSong)
|
||||
musicGroup.GET("/previous", music.GetPreviousSong)
|
||||
musicGroup.GET("/all", music.GetAllGamesRandom)
|
||||
musicGroup.GET("/all/order", music.GetAllGames)
|
||||
musicGroup.GET("/all/random", music.GetAllGamesRandom)
|
||||
musicGroup.PUT("/played", music.PutPlayed)
|
||||
musicGroup.GET("/addQue", music.AddLatestToQue)
|
||||
musicGroup.GET("/addPlayed", music.AddLatestPlayed)
|
||||
|
||||
routes := e.Routes()
|
||||
sort.Slice(routes, func(i, j int) bool {
|
||||
return routes[i].Path < routes[j].Path
|
||||
})
|
||||
for _, r := range routes {
|
||||
if (r.Method == "GET" || r.Method == "POST" || r.Method == "PUT" || r.Method == "DELETE") && !strings.Contains(r.Name, "github") {
|
||||
fmt.Printf(" %s %s\n", r.Method, r.Path)
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"music-server/internal/db"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
port int
|
||||
}
|
||||
|
||||
var (
|
||||
host = os.Getenv("DB_HOST")
|
||||
dbPort = os.Getenv("DB_PORT")
|
||||
dbName = os.Getenv("DB_NAME")
|
||||
username = os.Getenv("DB_USERNAME")
|
||||
password = os.Getenv("DB_PASSWORD")
|
||||
musicPath = os.Getenv("MUSIC_PATH")
|
||||
charactersPath = os.Getenv("CHARACTERS_PATH")
|
||||
)
|
||||
|
||||
func NewServer() *http.Server {
|
||||
|
||||
port, _ := strconv.Atoi(os.Getenv("PORT"))
|
||||
NewServer := &Server{
|
||||
port: port,
|
||||
}
|
||||
|
||||
fmt.Printf("host: %s, dbPort: %v, username: %s, password: %s, dbName: %s\n",
|
||||
host, dbPort, username, password, dbName)
|
||||
|
||||
log.Printf("musicPath: %s\n", musicPath)
|
||||
log.Printf("charactersPath: %s\n", charactersPath)
|
||||
|
||||
//conf.SetupDb()
|
||||
if host == "" || dbPort == "" || username == "" || password == "" || dbName == "" || musicPath == "" || charactersPath == "" {
|
||||
log.Fatal("Invalid settings")
|
||||
}
|
||||
|
||||
db.Migrate_db(host, dbPort, username, password, dbName)
|
||||
|
||||
db.InitDB(host, dbPort, username, password, dbName)
|
||||
|
||||
// Declare Server config
|
||||
server := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", NewServer.port),
|
||||
Handler: NewServer.RegisterRoutes(),
|
||||
IdleTimeout: time.Minute,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"log"
|
||||
"music-server/internal/backend"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type SyncHandler struct {
|
||||
}
|
||||
|
||||
func NewSyncHandler() *SyncHandler {
|
||||
return &SyncHandler{}
|
||||
}
|
||||
|
||||
func (s *SyncHandler) SyncProgress(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Getting progress")
|
||||
response := backend.SyncProgress()
|
||||
return ctx.JSON(http.StatusOK, response)
|
||||
}
|
||||
log.Println("Getting result")
|
||||
response := backend.SyncResult()
|
||||
return ctx.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
func (s *SyncHandler) SyncGamesNewOnlyChanges(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
log.Println("Start syncing games")
|
||||
go backend.SyncGamesNewOnlyChanges()
|
||||
return ctx.JSON(http.StatusOK, "Start syncing games")
|
||||
}
|
||||
|
||||
func (s *SyncHandler) SyncGamesNewFull(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
log.Println("Start syncing games full")
|
||||
go backend.SyncGamesNewFull()
|
||||
return ctx.JSON(http.StatusOK, "Start syncing games full")
|
||||
}
|
||||
|
||||
func (s *SyncHandler) ResetGames(ctx echo.Context) error {
|
||||
if backend.Syncing {
|
||||
log.Println("Syncing is in progress")
|
||||
return ctx.JSON(http.StatusLocked, "Syncing is in progress")
|
||||
}
|
||||
backend.ResetDB()
|
||||
return ctx.JSON(http.StatusOK, "Games and songs are deleted from the database")
|
||||
}
|
||||
37
justfile
@@ -1,5 +1,3 @@
|
||||
set dotenv-load
|
||||
|
||||
# Build the application
|
||||
all: build test
|
||||
|
||||
@@ -18,33 +16,14 @@ templ-install:
|
||||
fi; \
|
||||
fi
|
||||
|
||||
templ-build: templ-install
|
||||
@echo "Building templ..."
|
||||
@templ generate
|
||||
|
||||
tailwind-macos:
|
||||
@if [ ! -f tailwindcss ]; then curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-macos-arm64 -o tailwindcss; fi
|
||||
tailwind:
|
||||
@if [ ! -f tailwindcss ]; then curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-macos-x64 -o tailwindcss; fi
|
||||
@chmod +x tailwindcss
|
||||
|
||||
tailwind-linux:
|
||||
@if [ ! -f tailwindcss ]; then curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 -o tailwindcss; fi
|
||||
@chmod +x tailwindcss
|
||||
|
||||
tailwind-build:
|
||||
@echo "Building tailwind..."
|
||||
@./tailwindcss -i cmd/web/assets/css/input.css -o cmd/web/assets/css/output.css
|
||||
|
||||
sqlc-generate:
|
||||
@echo "Generating sqlc schema..."
|
||||
@sqlc generate
|
||||
|
||||
migrate-create name:
|
||||
@migrate create -ext sql -dir internal/db/migrations -seq {{name}}
|
||||
|
||||
[no-cd]
|
||||
build: sqlc-generate templ-build tailwind-build
|
||||
build:
|
||||
@echo "Building..."
|
||||
@swag init -g routes.go -d ./internal/server/,./internal/backend/ -o ./cmd/docs
|
||||
@templ generate
|
||||
@./tailwindcss -i cmd/web/assets/css/input.css -o cmd/web/assets/css/output.css
|
||||
@go build -o main cmd/main.go
|
||||
|
||||
run:
|
||||
@@ -60,12 +39,6 @@ clean:
|
||||
@echo "Cleaning..."
|
||||
@rm -f main
|
||||
|
||||
podman-run:
|
||||
@podman-compose up --build
|
||||
|
||||
podman-down:
|
||||
@podman-compose down
|
||||
|
||||
# Create DB container
|
||||
docker-run:
|
||||
@if docker compose up --build 2>/dev/null; then \
|
||||
|
||||
29
pkg/api/index.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"music-server/pkg/helpers"
|
||||
"music-server/pkg/server"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Index struct {
|
||||
}
|
||||
|
||||
func NewIndex() *Index {
|
||||
return &Index{}
|
||||
}
|
||||
|
||||
func (i *Index) GetVersion(ctx *gin.Context) {
|
||||
versionHistory := server.GetVersionHistory()
|
||||
if versionHistory.Version == "" {
|
||||
helpers.NewError(ctx, http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, versionHistory)
|
||||
}
|
||||
|
||||
func (i *Index) GetDBTest(ctx *gin.Context) {
|
||||
server.TestDB()
|
||||
ctx.JSON(http.StatusOK, "TestedDB")
|
||||
}
|
||||
100
pkg/api/music.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"music-server/pkg/helpers"
|
||||
"music-server/pkg/models"
|
||||
"music-server/pkg/server"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Music struct {
|
||||
}
|
||||
|
||||
func NewMusic() *Music {
|
||||
return &Music{}
|
||||
}
|
||||
|
||||
func (m *Music) GetSong(ctx *gin.Context) {
|
||||
song := ctx.Query("song")
|
||||
if song == "" {
|
||||
ctx.String(http.StatusBadRequest, "song can't be empty")
|
||||
}
|
||||
s := server.GetSong(song)
|
||||
helpers.SendSong(ctx, s)
|
||||
}
|
||||
|
||||
func (m *Music) GetSoundCheckSong(ctx *gin.Context) {
|
||||
song := server.GetSoundCheckSong()
|
||||
helpers.SendSong(ctx, song)
|
||||
}
|
||||
|
||||
func (m *Music) ResetMusic(ctx *gin.Context) {
|
||||
server.Reset()
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func (m *Music) GetRandomSong(ctx *gin.Context) {
|
||||
song := server.GetRandomSong()
|
||||
helpers.SendSong(ctx, song)
|
||||
}
|
||||
|
||||
func (m *Music) GetRandomSongLowChance(ctx *gin.Context) {
|
||||
song := server.GetRandomSongLowChance()
|
||||
helpers.SendSong(ctx, song)
|
||||
}
|
||||
|
||||
func (m *Music) GetRandomSongClassic(ctx *gin.Context) {
|
||||
song := server.GetRandomSongClassic()
|
||||
helpers.SendSong(ctx, song)
|
||||
}
|
||||
|
||||
func (m *Music) GetSongInfo(ctx *gin.Context) {
|
||||
song := server.GetSongInfo()
|
||||
ctx.JSON(http.StatusOK, song)
|
||||
}
|
||||
|
||||
func (m *Music) GetPlayedSongs(ctx *gin.Context) {
|
||||
songList := server.GetPlayedSongs()
|
||||
ctx.JSON(http.StatusOK, songList)
|
||||
}
|
||||
|
||||
func (m *Music) GetNextSong(ctx *gin.Context) {
|
||||
song := server.GetNextSong()
|
||||
helpers.SendSong(ctx, song)
|
||||
}
|
||||
|
||||
func (m *Music) GetPreviousSong(ctx *gin.Context) {
|
||||
song := server.GetPreviousSong()
|
||||
helpers.SendSong(ctx, song)
|
||||
}
|
||||
|
||||
func (m *Music) GetAllGames(ctx *gin.Context) {
|
||||
gameList := server.GetAllGames()
|
||||
ctx.JSON(http.StatusOK, gameList)
|
||||
}
|
||||
|
||||
func (m *Music) GetAllGamesRandom(ctx *gin.Context) {
|
||||
gameList := server.GetAllGamesRandom()
|
||||
ctx.JSON(http.StatusOK, gameList)
|
||||
}
|
||||
|
||||
func (m *Music) PutPlayed(ctx *gin.Context) {
|
||||
var played models.Played
|
||||
if err := ctx.ShouldBindJSON(&played); err != nil {
|
||||
helpers.NewError(ctx, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
server.SetPlayed(played.Song)
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func (m *Music) AddLatestToQue(ctx *gin.Context) {
|
||||
server.AddLatestToQue()
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func (m *Music) AddLatestPlayed(ctx *gin.Context) {
|
||||
server.AddLatestPlayed()
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
38
pkg/api/sync.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"music-server/pkg/server"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Sync struct {
|
||||
}
|
||||
|
||||
func NewSync() *Sync {
|
||||
return &Sync{}
|
||||
}
|
||||
|
||||
func (s *Sync) SyncGames(ctx *gin.Context) {
|
||||
server.SyncGames()
|
||||
server.Reset()
|
||||
ctx.JSON(http.StatusOK, "Games are synced")
|
||||
}
|
||||
|
||||
func (s *Sync) SyncGamesQuick(ctx *gin.Context) {
|
||||
server.SyncGamesQuick()
|
||||
server.Reset()
|
||||
ctx.JSON(http.StatusOK, "Games are synced")
|
||||
}
|
||||
|
||||
func (s *Sync) SyncGamesNew(ctx *gin.Context) {
|
||||
response := server.SyncGamesNew()
|
||||
server.Reset()
|
||||
ctx.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
func (s *Sync) ResetGames(ctx *gin.Context) {
|
||||
server.ResetDB()
|
||||
ctx.JSON(http.StatusOK, "Games and songs are deleted from the database")
|
||||
}
|
||||
146
pkg/conf/conf.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"music-server/cmd/web"
|
||||
"music-server/pkg/api"
|
||||
"music-server/pkg/db"
|
||||
"music-server/pkg/helpers"
|
||||
"music-server/pkg/server"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetupDb() {
|
||||
|
||||
/*err := server.ReadConf("conf.yaml")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}*/
|
||||
|
||||
// Get the value of an Environment Variable
|
||||
host := os.Getenv("DB_HOST")
|
||||
dbPort, dbPortErr := strconv.Atoi(os.Getenv("DB_PORT"))
|
||||
if dbPortErr != nil {
|
||||
dbPort = 0
|
||||
}
|
||||
username := os.Getenv("DB_USERNAME")
|
||||
password := os.Getenv("DB_PASSWORD")
|
||||
dbName := os.Getenv("DB_NAME")
|
||||
|
||||
fmt.Printf("host: %s, dbPort: %v, username: %s, password: %s, dbName: %s\n",
|
||||
host, dbPort, username, password, dbName)
|
||||
|
||||
if host == "" {
|
||||
host = "localhost"
|
||||
}
|
||||
if dbPort == 0 {
|
||||
dbPort = 5432
|
||||
}
|
||||
if username == "" {
|
||||
username = "postgres"
|
||||
}
|
||||
if password == "" {
|
||||
password = "postgres"
|
||||
}
|
||||
if dbName == "" {
|
||||
dbName = "music_test_local"
|
||||
}
|
||||
|
||||
db.Migrate_db(host, dbPort, username, password, dbName)
|
||||
|
||||
db.InitDB(host, dbPort, username, password, dbName)
|
||||
|
||||
var dir string
|
||||
if host != "localhost" {
|
||||
dir = "/sorted/"
|
||||
} else {
|
||||
dir = "/Users/sebastian/ResilioSync/Sorterat_test/"
|
||||
}
|
||||
|
||||
server.Conf = &server.Config{
|
||||
Host: host,
|
||||
Port: dbPort,
|
||||
User: username,
|
||||
Password: password,
|
||||
Dbname: dbName,
|
||||
Path: dir,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func CloseDb() {
|
||||
defer db.CloseDb()
|
||||
}
|
||||
|
||||
func SetupRestServer(swagger embed.FS) {
|
||||
|
||||
router := gin.Default()
|
||||
router.Use(helpers.SetCorsAndNoCacheHeaders())
|
||||
|
||||
sync := api.NewSync()
|
||||
syncGroup := router.Group("/sync")
|
||||
{
|
||||
syncGroup.GET("", sync.SyncGames)
|
||||
syncGroup.GET("/new", sync.SyncGamesNew)
|
||||
syncGroup.GET("/quick", sync.SyncGamesQuick)
|
||||
syncGroup.GET("/reset", sync.ResetGames)
|
||||
}
|
||||
|
||||
music := api.NewMusic()
|
||||
musicGroup := router.Group("/music")
|
||||
{
|
||||
musicGroup.GET("", music.GetSong)
|
||||
musicGroup.GET("soundTest", music.GetSoundCheckSong)
|
||||
musicGroup.GET("reset", music.ResetMusic)
|
||||
musicGroup.GET("rand", music.GetRandomSong)
|
||||
musicGroup.GET("rand/low", music.GetRandomSongLowChance)
|
||||
musicGroup.GET("rand/classic", music.GetRandomSongClassic)
|
||||
musicGroup.GET("info", music.GetSongInfo)
|
||||
musicGroup.GET("list", music.GetPlayedSongs)
|
||||
musicGroup.GET("next", music.GetNextSong)
|
||||
musicGroup.GET("previous", music.GetPreviousSong)
|
||||
musicGroup.GET("all", music.GetAllGamesRandom)
|
||||
musicGroup.GET("all/order", music.GetAllGames)
|
||||
musicGroup.GET("all/random", music.GetAllGamesRandom)
|
||||
musicGroup.PUT("played", music.PutPlayed)
|
||||
musicGroup.GET("addQue", music.AddLatestToQue)
|
||||
musicGroup.GET("addPlayed", music.AddLatestPlayed)
|
||||
}
|
||||
|
||||
index := api.NewIndex()
|
||||
router.GET("/version", index.GetVersion)
|
||||
router.GET("/health", index.GetDBTest)
|
||||
router.StaticFS("/swagger", helpers.EmbedFolder(swagger, "swagger", false))
|
||||
router.Use(static.Serve("/", static.LocalFile("/frontend", true)))
|
||||
router.Use(static.Serve("/new", static.LocalFile("/newFrontend", true)))
|
||||
|
||||
staticFiles, _ := fs.Sub(web.Files, "assets")
|
||||
router.StaticFS("/assets", http.FS(staticFiles))
|
||||
router.GET("/search", func(c *gin.Context) {
|
||||
templ.Handler(web.HelloForm()).ServeHTTP(c.Writer, c.Request)
|
||||
})
|
||||
|
||||
router.POST("/find", func(c *gin.Context) {
|
||||
web.FindGameWebHandler(c.Writer, c.Request)
|
||||
})
|
||||
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
log.Printf("Defaulting to port %s", port)
|
||||
}
|
||||
log.Printf("Open http://localhost:%s in the browser", port)
|
||||
err := router.Run(fmt.Sprintf(":%s", port))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
133
pkg/db/dbHelper.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
newDb "music-server/db"
|
||||
"os"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
var Dbpool *pgxpool.Pool
|
||||
var Ctx = context.Background()
|
||||
|
||||
func InitDB(host string, port int, user string, password string, dbname string) {
|
||||
|
||||
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
|
||||
"password=%s dbname=%s sslmode=disable",
|
||||
host, port, user, password, dbname)
|
||||
|
||||
fmt.Println(psqlInfo)
|
||||
|
||||
var err error
|
||||
Dbpool, err = pgxpool.New(Ctx, psqlInfo)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var success string
|
||||
err = Dbpool.QueryRow(Ctx, "select 'Successfully connected!'").Scan(&success)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
Testf()
|
||||
fmt.Println(success)
|
||||
}
|
||||
|
||||
func CloseDb() {
|
||||
Dbpool.Close()
|
||||
}
|
||||
|
||||
func Testf() {
|
||||
rows, dbErr := Dbpool.Query(Ctx, "select game_name from game")
|
||||
if dbErr != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", dbErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
for rows.Next() {
|
||||
var gameName string
|
||||
dbErr = rows.Scan(&gameName)
|
||||
if dbErr != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", dbErr)
|
||||
}
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%v\n", gameName)
|
||||
}
|
||||
}
|
||||
|
||||
func resetGameIdSeq() {
|
||||
_, err := Dbpool.Query(Ctx, "SELECT setval('game_id_seq', (SELECT MAX(id) FROM game)+1);")
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Migrate_db(host string, port int, user string, password string, dbname string) {
|
||||
migrationInfo := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable",
|
||||
user, password, host, port, dbname)
|
||||
|
||||
fmt.Println("Migration Info: ", migrationInfo)
|
||||
|
||||
db, err := sql.Open("postgres", migrationInfo)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
files, err := iofs.New(newDb.MigrationsFs, "migrations")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
m, err := migrate.NewWithInstance("iofs", files, "postgres", driver)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
/*m, err := migrate.NewWithDatabaseInstance(
|
||||
"file://./db/migrations/",
|
||||
"postgres", driver)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}*/
|
||||
|
||||
version, _, err := m.Version()
|
||||
if err != nil {
|
||||
log.Println("Migration version err: ", err)
|
||||
}
|
||||
|
||||
fmt.Println("Migration version before: ", version)
|
||||
|
||||
err = m.Force(1)
|
||||
//err = m.Up() // or m.Steps(2) if you want to explicitly set the number of migrations to run
|
||||
if err != nil {
|
||||
log.Println("Force err: ", err)
|
||||
}
|
||||
|
||||
err = m.Migrate(2)
|
||||
//err = m.Up() // or m.Steps(2) if you want to explicitly set the number of migrations to run
|
||||
if err != nil {
|
||||
log.Println("Migration err: ", err)
|
||||
}
|
||||
|
||||
versionAfter, _, err := m.Version()
|
||||
if err != nil {
|
||||
log.Println("Migration version err: ", err)
|
||||
}
|
||||
|
||||
fmt.Println("Migration version after: ", versionAfter)
|
||||
|
||||
fmt.Println("Migration done")
|
||||
|
||||
db.Close()
|
||||
}
|
||||
173
pkg/db/game.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"music-server/pkg/models"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
)
|
||||
|
||||
func GetGameName(gameId int) string {
|
||||
var gameName = ""
|
||||
err := Dbpool.QueryRow(Ctx,
|
||||
"SELECT game_name FROM game WHERE id = $1", gameId).Scan(&gameName)
|
||||
if err != nil {
|
||||
if compareError.Error() != err.Error() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return gameName
|
||||
}
|
||||
|
||||
func GetGameById(gameId int) (models.GameData, error) {
|
||||
var id, timesPlayed int
|
||||
var numberOfSongs pgtype.Int4
|
||||
var gameName, path string
|
||||
var added, deleted, lastChanged, lastPlayed pgtype.Timestamp
|
||||
err := Dbpool.QueryRow(Ctx,
|
||||
"SELECT id, game_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs "+
|
||||
"FROM game WHERE id = $1 AND deleted IS NULL ", gameId).Scan(&id, &gameName, &added, &deleted, &lastChanged, &path, ×Played, &lastPlayed, &numberOfSongs)
|
||||
if err != nil {
|
||||
if compareError.Error() != err.Error() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
return models.GameData{}, errors.New("Game not found")
|
||||
}
|
||||
return models.GameData{
|
||||
Id: id,
|
||||
GameName: gameName,
|
||||
Added: added.Time,
|
||||
Deleted: deleted.Time,
|
||||
LastChanged: lastChanged.Time,
|
||||
Path: path,
|
||||
TimesPlayed: timesPlayed,
|
||||
LastPlayed: lastPlayed.Time,
|
||||
NumberOfSongs: numberOfSongs.Int,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SetGameDeletionDate() {
|
||||
_, err := Dbpool.Exec(Ctx,
|
||||
"UPDATE game SET deleted=$1 WHERE deleted IS NULL", time.Now())
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func ClearGames() {
|
||||
_, err := Dbpool.Exec(Ctx, "DELETE FROM game")
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateGameName(id int, name string, path string) {
|
||||
_, err := Dbpool.Exec(Ctx,
|
||||
"UPDATE game SET game_name=$1, path=$2, last_changed=$3 WHERE id=$4",
|
||||
name, path, time.Now(), id)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveDeletionDate(id int) {
|
||||
_, err := Dbpool.Exec(Ctx,
|
||||
"UPDATE game SET deleted=null WHERE id=$1", id)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetIdByGameName(name string) int {
|
||||
var gameId = -1
|
||||
err := Dbpool.QueryRow(Ctx,
|
||||
"SELECT id FROM game WHERE game_name = $1", name).Scan(&gameId)
|
||||
if err != nil {
|
||||
if compareError.Error() != err.Error() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
return gameId
|
||||
}
|
||||
|
||||
func InsertGame(name string, path string) int {
|
||||
gameId := -1
|
||||
err := Dbpool.QueryRow(Ctx,
|
||||
"INSERT INTO game(game_name, path, added) VALUES ($1, $2, $3) RETURNING id",
|
||||
name, path, time.Now()).Scan(&gameId)
|
||||
if err != nil {
|
||||
if compareError.Error() != err.Error() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
resetGameIdSeq()
|
||||
err2 := Dbpool.QueryRow(Ctx,
|
||||
"INSERT INTO game(game_name, path, added) VALUES ($1, $2, $3) RETURNING id",
|
||||
name, path, time.Now()).Scan(&gameId)
|
||||
if err2 != nil {
|
||||
if compareError.Error() != err2.Error() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return gameId
|
||||
}
|
||||
|
||||
func InsertGameWithExistingId(id int, name string, path string) {
|
||||
_, err := Dbpool.Exec(Ctx,
|
||||
"INSERT INTO game(id, game_name, path, added) VALUES ($1, $2, $3, $4)",
|
||||
id, name, path, time.Now())
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func FindAllGames() []models.GameData {
|
||||
rows, err := Dbpool.Query(Ctx,
|
||||
"SELECT id, game_name, added, deleted, last_changed, path, times_played, last_played, number_of_songs "+
|
||||
"FROM game WHERE deleted IS NULL "+
|
||||
"ORDER BY game_name")
|
||||
if err != nil {
|
||||
if compareError.Error() != err.Error() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
var gameList []models.GameData
|
||||
for rows.Next() {
|
||||
var id, timesPlayed int
|
||||
var numberOfSongs pgtype.Int4
|
||||
var gameName, path string
|
||||
var added, deleted, lastChanged, lastPlayed pgtype.Timestamp
|
||||
err := rows.Scan(&id, &gameName, &added, &deleted, &lastChanged, &path, ×Played, &lastPlayed, &numberOfSongs)
|
||||
if err != nil {
|
||||
if compareError.Error() != err.Error() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
gameList = append(gameList, models.GameData{
|
||||
Id: id,
|
||||
GameName: gameName,
|
||||
Added: added.Time,
|
||||
Deleted: deleted.Time,
|
||||
LastChanged: lastChanged.Time,
|
||||
Path: path,
|
||||
TimesPlayed: timesPlayed,
|
||||
LastPlayed: lastPlayed.Time,
|
||||
NumberOfSongs: numberOfSongs.Int,
|
||||
})
|
||||
}
|
||||
return gameList
|
||||
}
|
||||
|
||||
func AddGamePlayed(id int) {
|
||||
_, err := Dbpool.Exec(Ctx,
|
||||
"UPDATE game SET times_played=times_played+1, last_played=now() WHERE id=$1", id)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
148
pkg/db/song.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"music-server/pkg/models"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var compareError = errors.New("no rows in result set")
|
||||
|
||||
func ClearSongs(gameId int) {
|
||||
if gameId == -1 {
|
||||
_, err := Dbpool.Exec(Ctx, "DELETE FROM song")
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
_, err := Dbpool.Exec(Ctx, "DELETE FROM song where game_id=$1", gameId)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func AddSong(song models.SongData) {
|
||||
_, err := Dbpool.Exec(Ctx,
|
||||
"INSERT INTO song(game_id, song_name, path, file_name) VALUES ($1, $2, $3, $4)",
|
||||
song.GameId, song.SongName, song.Path, song.FileName)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckSong(songPath string) bool {
|
||||
var path string
|
||||
err := Dbpool.QueryRow(Ctx,
|
||||
"SELECT path FROM song WHERE path = $1", songPath).Scan(&path)
|
||||
if err != nil {
|
||||
if compareError.Error() != err.Error() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
return path != ""
|
||||
}
|
||||
|
||||
func UpdateSong(songName string, fileName string, path string) {
|
||||
_, err := Dbpool.Exec(Ctx,
|
||||
"UPDATE song SET song_name=$1, file_name=$2 WHERE path = $3",
|
||||
songName, fileName, path)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func FindSongsFromGame(id int) []models.SongData {
|
||||
rows, err := Dbpool.Query(Ctx,
|
||||
"SELECT song_name, path, file_name, times_played FROM song WHERE game_id = $1", id)
|
||||
if err != nil {
|
||||
if compareError.Error() != err.Error() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var songDataList []models.SongData
|
||||
for rows.Next() {
|
||||
var songName string
|
||||
var path string
|
||||
var fileName string
|
||||
var timesPlayed int
|
||||
|
||||
err := rows.Scan(&songName, &path, &fileName, ×Played)
|
||||
if err != nil {
|
||||
if compareError.Error() != err.Error() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
songDataList = append(songDataList, models.SongData{
|
||||
GameId: id,
|
||||
SongName: songName,
|
||||
Path: path,
|
||||
FileName: fileName,
|
||||
TimesPlayed: timesPlayed,
|
||||
})
|
||||
}
|
||||
return songDataList
|
||||
}
|
||||
|
||||
func AddSongPlayed(id int, name string) {
|
||||
_, err := Dbpool.Exec(Ctx,
|
||||
"UPDATE song SET times_played=times_played+1 WHERE game_id=$1 AND song_name=$2", id, name)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func FetchAllSongs() []models.SongData {
|
||||
rows, err := Dbpool.Query(Ctx,
|
||||
"SELECT song_name, path FROM song")
|
||||
if err != nil {
|
||||
if compareError.Error() != err.Error() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var songDataList []models.SongData
|
||||
for rows.Next() {
|
||||
var songName string
|
||||
var path string
|
||||
|
||||
err := rows.Scan(&songName, &path)
|
||||
if err != nil {
|
||||
if compareError.Error() != err.Error() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
songDataList = append(songDataList, models.SongData{
|
||||
SongName: songName,
|
||||
Path: path,
|
||||
})
|
||||
}
|
||||
return songDataList
|
||||
}
|
||||
|
||||
func RemoveBrokenSong(song models.SongData) {
|
||||
_, err := Dbpool.Exec(Ctx, "DELETE FROM song where path=$1", song.Path)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveBrokenSongs(songs []models.SongData) {
|
||||
joined := ""
|
||||
for _, song := range songs {
|
||||
joined += "'" + song.Path + "',"
|
||||
}
|
||||
joined = strings.TrimSuffix(joined, ",")
|
||||
|
||||
_, err := Dbpool.Exec(Ctx, "DELETE FROM song where path in ($1)", joined)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Exec failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
47
pkg/db/songList.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"music-server/pkg/models"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func InsertSongInList(song models.SongListData) {
|
||||
_, err := Dbpool.Exec(Ctx,
|
||||
`INSERT INTO song_list (match_date, match_id, song_no, game_name, song_name) VALUES ($1, $2, $3, $4, $5)`,
|
||||
song.MatchDate, song.MatchId, song.SongNo, song.GameName, song.SongName)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetSongList(matchId int) []models.SongListData {
|
||||
rows, err := Dbpool.Query(Ctx,
|
||||
"SELECT match_date, match_id, song_no, game_name, song_name "+
|
||||
"FROM song_list WHERE match_date = $1"+
|
||||
"ORDER BY song_no DESC", matchId)
|
||||
if err != nil {
|
||||
if compareError.Error() != err.Error() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
var songList []models.SongListData
|
||||
for rows.Next() {
|
||||
var matchId, songNo int
|
||||
var matchDate time.Time
|
||||
var gameName, songName string
|
||||
err := rows.Scan(&matchDate, &matchId, &songNo, &gameName, &songName)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Scan failed: %v\n", err)
|
||||
}
|
||||
songList = append(songList, models.SongListData{
|
||||
MatchDate: matchDate,
|
||||
MatchId: matchId,
|
||||
SongNo: songNo,
|
||||
GameName: gameName,
|
||||
SongName: songName,
|
||||
})
|
||||
}
|
||||
return songList
|
||||
}
|
||||
16
pkg/helpers/error.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package helpers
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func NewError(ctx *gin.Context, status int, err error) {
|
||||
er := HTTPError{
|
||||
Code: status,
|
||||
Message: err.Error(),
|
||||
}
|
||||
ctx.JSON(status, er)
|
||||
}
|
||||
|
||||
type HTTPError struct {
|
||||
Code int `json:"code" example:"400"`
|
||||
Message string `json:"message" example:"status bad request"`
|
||||
}
|
||||
122
pkg/helpers/helpers.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SetCorsAndNoCacheHeaders() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
type embedFileSystem struct {
|
||||
http.FileSystem
|
||||
indexes bool
|
||||
}
|
||||
|
||||
func (e embedFileSystem) Exists(_ string, path string) bool {
|
||||
f, err := e.Open(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if indexing is allowed
|
||||
s, _ := f.Stat()
|
||||
if s.IsDir() && !e.indexes {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func EmbedFolder(fsEmbed embed.FS, targetPath string, index bool) static.ServeFileSystem {
|
||||
subFS, err := fs.Sub(fsEmbed, targetPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return embedFileSystem{
|
||||
FileSystem: http.FS(subFS),
|
||||
indexes: index,
|
||||
}
|
||||
}
|
||||
|
||||
func SendSong(ctx *gin.Context, Filename string) {
|
||||
fmt.Println("Client requests: " + Filename)
|
||||
|
||||
//Check if file exists and open
|
||||
openFile, err := os.Open(Filename)
|
||||
if err != nil {
|
||||
//File not found, send 404
|
||||
http.Error(ctx.Writer, "Song not found.", 404)
|
||||
return
|
||||
}
|
||||
defer func(openFile *os.File) {
|
||||
_ = openFile.Close()
|
||||
}(openFile) //Close after function return
|
||||
|
||||
//File is found, create and send the correct headers
|
||||
|
||||
//Get the Content-Type of the file
|
||||
//Create a buffer to store the header of the file in
|
||||
FileHeader := make([]byte, 512)
|
||||
//Copy the headers into the FileHeader buffer
|
||||
_, _ = openFile.Read(FileHeader)
|
||||
//Get content type of file
|
||||
//FileContentType := http.DetectContentType(FileHeader)
|
||||
|
||||
//Get the file size
|
||||
FileStat, _ := openFile.Stat() //Get info from file
|
||||
FileSize := strconv.FormatInt(FileStat.Size(), 10) //Get file size as a string
|
||||
|
||||
//Send the headers
|
||||
//writer.Header().Set("Content-Disposition", "attachment; filename="+Filename)
|
||||
ctx.Writer.Header().Set("Content-Type", "audio/mpeg")
|
||||
ctx.Writer.Header().Set("Content-Length", FileSize)
|
||||
ctx.Writer.Header().Set("Expires", "Tue, 03 Jul 2001 06:00:00 GMT")
|
||||
ctx.Writer.Header().Set("Last-Modified", time.Now().String()+" GMT")
|
||||
ctx.Writer.Header().Set("Cache-Control", "no-cache, no-store, private, max-age=0")
|
||||
ctx.Writer.Header().Set("Pragma", "no-cache")
|
||||
ctx.Writer.Header().Set("X-Accel-Expires", "0")
|
||||
|
||||
var etagHeaders = []string{
|
||||
"ETag",
|
||||
"If-Modified-Since",
|
||||
"If-Match",
|
||||
"If-None-Match",
|
||||
"If-Range",
|
||||
"If-Unmodified-Since",
|
||||
}
|
||||
|
||||
for _, v := range etagHeaders {
|
||||
if ctx.Request.Header.Get(v) != "" {
|
||||
ctx.Request.Header.Del(v)
|
||||
}
|
||||
}
|
||||
|
||||
//Send the file
|
||||
//We read 512 bytes from the file already, so we reset the offset back to 0
|
||||
_, _ = openFile.Seek(0, 0)
|
||||
_, _ = io.Copy(ctx.Writer, openFile) //'Copy' the file to the client
|
||||
return
|
||||
}
|
||||
60
pkg/models/models.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Played struct {
|
||||
Song int
|
||||
}
|
||||
|
||||
type VersionData struct {
|
||||
Version string `json:"version" example:"1.0.0swagger.yaml"`
|
||||
Changelog string `json:"changelog" example:"account name"`
|
||||
History []VersionData `json:"history"`
|
||||
}
|
||||
|
||||
type SongInfo struct {
|
||||
Game string `json:"Game"`
|
||||
GamePlayed int `json:"GamePlayed"`
|
||||
Song string `json:"Song"`
|
||||
SongPlayed int `json:"SongPlayed"`
|
||||
CurrentlyPlaying bool `json:"CurrentlyPlaying"`
|
||||
SongNo int `json:"SongNo"`
|
||||
}
|
||||
|
||||
type GameData struct {
|
||||
Id int
|
||||
GameName string
|
||||
Added time.Time
|
||||
Deleted time.Time
|
||||
LastChanged time.Time
|
||||
Path string
|
||||
TimesPlayed int
|
||||
LastPlayed time.Time
|
||||
NumberOfSongs int32
|
||||
}
|
||||
|
||||
type SongData struct {
|
||||
GameId int
|
||||
SongName string
|
||||
Path string
|
||||
TimesPlayed int
|
||||
FileName string
|
||||
}
|
||||
|
||||
type SongListData struct {
|
||||
MatchDate time.Time
|
||||
MatchId int
|
||||
SongNo int
|
||||
GameName string
|
||||
SongName string
|
||||
}
|
||||
|
||||
type VgmqData struct {
|
||||
SongNo int
|
||||
Path string
|
||||
Clue string
|
||||
Answered bool
|
||||
Answer string
|
||||
}
|
||||
34
pkg/server/config.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
Dbname string
|
||||
Path string
|
||||
}
|
||||
|
||||
var Conf *Config
|
||||
|
||||
func ReadConf(filename string) error {
|
||||
buf, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c := &Config{}
|
||||
err = yaml.Unmarshal(buf, c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("in file %q: %w", filename, err)
|
||||
}
|
||||
Conf = c
|
||||
return err
|
||||
}
|
||||
@@ -1,40 +1,18 @@
|
||||
package backend
|
||||
package server
|
||||
|
||||
import (
|
||||
"music-server/internal/db"
|
||||
"music-server/pkg/db"
|
||||
"music-server/pkg/models"
|
||||
)
|
||||
|
||||
func TestDB() {
|
||||
db.Testf()
|
||||
}
|
||||
|
||||
type VersionData struct {
|
||||
Version string `json:"version" example:"1.0.0"`
|
||||
Changelog string `json:"changelog" example:"account name"`
|
||||
History []VersionData `json:"history"`
|
||||
}
|
||||
|
||||
func GetVersionHistory() VersionData {
|
||||
data := VersionData{Version: "4.5.0",
|
||||
Changelog: "#1 - Created request to check newest version of the app\n" +
|
||||
"#2 - Added request to download the newest version of the app\n" +
|
||||
"#3 - Added request to check progress during sync\n" +
|
||||
"#4 - Now blocking all request while sync is in progress\n" +
|
||||
"#5 - Implemented ants for thread pooling\n" +
|
||||
"#6 - Changed the sync request to now only start the sync",
|
||||
History: []VersionData{
|
||||
{
|
||||
Version: "4.0.0",
|
||||
Changelog: "Changed framework from gin to Echo\n" +
|
||||
"Reorganized the code\n" +
|
||||
"Implemented sqlc\n" +
|
||||
"Added support to send character images from the server\n" +
|
||||
"Added function to create a new database of no one exists",
|
||||
},
|
||||
{
|
||||
Version: "3.2",
|
||||
func GetVersionHistory() models.VersionData {
|
||||
data := models.VersionData{Version: "3.2",
|
||||
Changelog: "Upgraded Go version and the version of all dependencies. Fixed som more bugs.",
|
||||
},
|
||||
History: []models.VersionData{
|
||||
{
|
||||
Version: "3.1",
|
||||
Changelog: "Fixed some bugs with songs not found made the application crash. Now checking if song exists and if not, remove song from DB and find another one. Frontend is now decoupled from the backend.",
|
||||
292
pkg/server/music.go
Normal file
@@ -0,0 +1,292 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"music-server/pkg/db"
|
||||
"music-server/pkg/models"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var currentSong = -1
|
||||
var games []models.GameData
|
||||
var songQue []models.SongData
|
||||
var lastFetched models.SongData
|
||||
|
||||
func GetSoundCheckSong() string {
|
||||
files, err := os.ReadDir("songs")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fileInfo := files[rand.Intn(len(files))]
|
||||
return "songs/" + fileInfo.Name()
|
||||
}
|
||||
|
||||
func Reset() {
|
||||
songQue = nil
|
||||
currentSong = -1
|
||||
games = db.FindAllGames()
|
||||
}
|
||||
|
||||
func AddLatestToQue() {
|
||||
if lastFetched.Path != "" {
|
||||
currentSong = len(songQue)
|
||||
songQue = append(songQue, lastFetched)
|
||||
lastFetched = models.SongData{}
|
||||
}
|
||||
}
|
||||
|
||||
func AddLatestPlayed() {
|
||||
if len(songQue) == 0 {
|
||||
return
|
||||
}
|
||||
var currentSongData = songQue[currentSong]
|
||||
|
||||
db.AddGamePlayed(currentSongData.GameId)
|
||||
db.AddSongPlayed(currentSongData.GameId, currentSongData.SongName)
|
||||
}
|
||||
|
||||
func SetPlayed(songNumber int) {
|
||||
if len(songQue) == 0 || songNumber >= len(songQue) {
|
||||
return
|
||||
}
|
||||
var songData = songQue[songNumber]
|
||||
db.AddGamePlayed(songData.GameId)
|
||||
db.AddSongPlayed(songData.GameId, songData.SongName)
|
||||
}
|
||||
|
||||
func GetRandomSong() string {
|
||||
if len(games) == 0 {
|
||||
games = db.FindAllGames()
|
||||
}
|
||||
if len(games) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
song := getSongFromList(games)
|
||||
lastFetched = song
|
||||
return song.Path
|
||||
}
|
||||
|
||||
func GetRandomSongLowChance() string {
|
||||
if len(games) == 0 {
|
||||
games = db.FindAllGames()
|
||||
}
|
||||
|
||||
var listOfGames []models.GameData
|
||||
|
||||
var averagePlayed = getAveragePlayed(games)
|
||||
|
||||
for _, data := range games {
|
||||
var timesToAdd = averagePlayed - data.TimesPlayed
|
||||
if timesToAdd <= 0 {
|
||||
listOfGames = append(listOfGames, data)
|
||||
} else {
|
||||
for i := 0; i < timesToAdd; i++ {
|
||||
listOfGames = append(listOfGames, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
song := getSongFromList(listOfGames)
|
||||
|
||||
lastFetched = song
|
||||
return song.Path
|
||||
|
||||
}
|
||||
|
||||
func GetRandomSongClassic() string {
|
||||
if games == nil || len(games) == 0 {
|
||||
games = db.FindAllGames()
|
||||
}
|
||||
|
||||
var listOfAllSongs []models.SongData
|
||||
for _, game := range games {
|
||||
listOfAllSongs = append(listOfAllSongs, db.FindSongsFromGame(game.Id)...)
|
||||
}
|
||||
|
||||
songFound := false
|
||||
var song models.SongData
|
||||
for !songFound {
|
||||
song = listOfAllSongs[rand.Intn(len(listOfAllSongs))]
|
||||
gameData, err := db.GetGameById(song.GameId)
|
||||
|
||||
if err != nil {
|
||||
db.RemoveBrokenSong(song)
|
||||
log.Println("Song not found, song '" + song.SongName + "' deleted from game '" + gameData.GameName + "' FileName: " + song.FileName)
|
||||
continue
|
||||
}
|
||||
|
||||
//Check if file exists and open
|
||||
openFile, err := os.Open(song.Path)
|
||||
if err != nil || gameData.Path+song.FileName != song.Path {
|
||||
//File not found
|
||||
db.RemoveBrokenSong(song)
|
||||
log.Println("Song not found, song '" + song.SongName + "' deleted from game '" + gameData.GameName + "' FileName: " + song.FileName)
|
||||
} else {
|
||||
songFound = true
|
||||
}
|
||||
err = openFile.Close()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
lastFetched = song
|
||||
return song.Path
|
||||
}
|
||||
|
||||
func GetSongInfo() models.SongInfo {
|
||||
if songQue == nil {
|
||||
return models.SongInfo{}
|
||||
}
|
||||
var currentSongData = songQue[currentSong]
|
||||
|
||||
currentGameData := getCurrentGame(currentSongData)
|
||||
|
||||
return models.SongInfo{
|
||||
Game: currentGameData.GameName,
|
||||
GamePlayed: currentGameData.TimesPlayed,
|
||||
Song: currentSongData.SongName,
|
||||
SongPlayed: currentSongData.TimesPlayed,
|
||||
CurrentlyPlaying: true,
|
||||
SongNo: currentSong,
|
||||
}
|
||||
}
|
||||
|
||||
func GetPlayedSongs() []models.SongInfo {
|
||||
var songList []models.SongInfo
|
||||
|
||||
for i, song := range songQue {
|
||||
gameData := getCurrentGame(song)
|
||||
songList = append(songList, models.SongInfo{
|
||||
Game: gameData.GameName,
|
||||
GamePlayed: gameData.TimesPlayed,
|
||||
Song: song.SongName,
|
||||
SongPlayed: song.TimesPlayed,
|
||||
CurrentlyPlaying: i == currentSong,
|
||||
SongNo: i,
|
||||
})
|
||||
}
|
||||
return songList
|
||||
}
|
||||
|
||||
func GetSong(song string) string {
|
||||
currentSong, _ = strconv.Atoi(song)
|
||||
if currentSong >= len(songQue) {
|
||||
currentSong = len(songQue) - 1
|
||||
} else if currentSong < 0 {
|
||||
currentSong = 0
|
||||
}
|
||||
var songData = songQue[currentSong]
|
||||
return songData.Path
|
||||
}
|
||||
|
||||
func GetAllGames() []string {
|
||||
if games == nil || len(games) == 0 {
|
||||
games = db.FindAllGames()
|
||||
}
|
||||
|
||||
var jsonArray []string
|
||||
for _, game := range games {
|
||||
jsonArray = append(jsonArray, game.GameName)
|
||||
}
|
||||
return jsonArray
|
||||
}
|
||||
|
||||
func GetAllGamesRandom() []string {
|
||||
if games == nil || len(games) == 0 {
|
||||
games = db.FindAllGames()
|
||||
}
|
||||
|
||||
var jsonArray []string
|
||||
for _, game := range games {
|
||||
jsonArray = append(jsonArray, game.GameName)
|
||||
}
|
||||
rand.Shuffle(len(jsonArray), func(i, j int) { jsonArray[i], jsonArray[j] = jsonArray[j], jsonArray[i] })
|
||||
return jsonArray
|
||||
}
|
||||
|
||||
func GetNextSong() string {
|
||||
if songQue == nil {
|
||||
return ""
|
||||
}
|
||||
if currentSong == len(songQue)-1 || currentSong == -1 {
|
||||
var songData = songQue[currentSong]
|
||||
return songData.Path
|
||||
} else {
|
||||
currentSong = currentSong + 1
|
||||
var songData = songQue[currentSong]
|
||||
return songData.Path
|
||||
}
|
||||
}
|
||||
|
||||
func GetPreviousSong() string {
|
||||
if songQue == nil {
|
||||
return ""
|
||||
}
|
||||
if currentSong == -1 || currentSong == 0 {
|
||||
var songData = songQue[0]
|
||||
return songData.Path
|
||||
} else {
|
||||
currentSong = currentSong - 1
|
||||
var songData = songQue[currentSong]
|
||||
return songData.Path
|
||||
}
|
||||
}
|
||||
|
||||
func getSongFromList(games []models.GameData) models.SongData {
|
||||
songFound := false
|
||||
var song models.SongData
|
||||
for !songFound {
|
||||
game := getRandomGame(games)
|
||||
//log.Println("game = ", game)
|
||||
songs := db.FindSongsFromGame(game.Id)
|
||||
//log.Println("songs = ", songs)
|
||||
if len(songs) == 0 {
|
||||
continue
|
||||
}
|
||||
song = songs[rand.Intn(len(songs))]
|
||||
log.Println("song = ", song)
|
||||
|
||||
//Check if file exists and open
|
||||
openFile, err := os.Open(song.Path)
|
||||
//log.Println("game.Path+song.FileName: ", game.Path+song.FileName)
|
||||
//log.Println("song.Path: ", song.Path)
|
||||
//log.Println("game.Path+song.FileName != song.Path: ", game.Path+song.FileName != song.Path)
|
||||
if err != nil || game.Path+song.FileName != song.Path || strings.HasSuffix(song.FileName, ".wav") {
|
||||
//File not found
|
||||
db.RemoveBrokenSong(song)
|
||||
log.Println("Song not found, song '" + song.SongName + "' deleted from game '" + game.GameName + "' FileName: " + song.FileName)
|
||||
} else {
|
||||
songFound = true
|
||||
}
|
||||
|
||||
err = openFile.Close()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
return song
|
||||
}
|
||||
|
||||
func getCurrentGame(currentSongData models.SongData) models.GameData {
|
||||
for _, game := range games {
|
||||
if game.Id == currentSongData.GameId {
|
||||
return game
|
||||
}
|
||||
}
|
||||
return models.GameData{}
|
||||
}
|
||||
|
||||
func getAveragePlayed(gameList []models.GameData) int {
|
||||
var sum int
|
||||
for _, data := range gameList {
|
||||
sum += data.TimesPlayed
|
||||
}
|
||||
return sum / len(gameList)
|
||||
}
|
||||
|
||||
func getRandomGame(listOfGames []models.GameData) models.GameData {
|
||||
return listOfGames[rand.Intn(len(listOfGames))]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package backend
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
@@ -8,8 +8,9 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"music-server/internal/db"
|
||||
"music-server/internal/db/repository"
|
||||
"music-server/db/repository"
|
||||
"music-server/pkg/db"
|
||||
"music-server/pkg/models"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -17,19 +18,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
|
||||
"github.com/MShekow/directory-checksum/directory_checksum"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
var Syncing = false
|
||||
var foldersSynced float32
|
||||
var numberOfFoldersToSync float32
|
||||
var start time.Time
|
||||
var totalTime time.Duration
|
||||
var timeSpent time.Duration
|
||||
|
||||
var allGames []repository.Game
|
||||
var gamesBeforeSync []repository.Game
|
||||
var gamesAfterSync []repository.Game
|
||||
@@ -41,19 +33,13 @@ var gamesRemoved []string
|
||||
var catchedErrors []string
|
||||
var brokenSongs []string
|
||||
|
||||
type SyncResponse struct {
|
||||
type Response struct {
|
||||
GamesAdded []string `json:"games_added"`
|
||||
GamesReAdded []string `json:"games_re_added"`
|
||||
GamesChangedTitle map[string]string `json:"games_changed_title"`
|
||||
GamesChangedContent []string `json:"games_changed_content"`
|
||||
GamesRemoved []string `json:"games_removed"`
|
||||
CatchedErrors []string `json:"catched_errors"`
|
||||
TotalTime string `json:"total_time"`
|
||||
}
|
||||
|
||||
type ProgressResponse struct {
|
||||
Progress string `json:"progress"`
|
||||
TimeSpent string `json:"time_spent"`
|
||||
}
|
||||
|
||||
type GameStatus int
|
||||
@@ -76,26 +62,241 @@ func (gs GameStatus) String() string {
|
||||
return statusName[gs]
|
||||
}
|
||||
|
||||
func ResetDB() {
|
||||
repo.ClearSongs(db.Ctx)
|
||||
repo.ClearGames(db.Ctx)
|
||||
var syncWg sync.WaitGroup
|
||||
var repo *repository.Queries
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
func SyncGames() {
|
||||
start := time.Now()
|
||||
host := os.Getenv("DB_HOST")
|
||||
var dir string
|
||||
if host != "" {
|
||||
dir = "/sorted/"
|
||||
} else {
|
||||
dir = "/Users/sebastian/ResilioSync/Sorterat_test/"
|
||||
}
|
||||
fmt.Printf("dir: %s\n", dir)
|
||||
foldersToSkip := []string{".sync", "dist", "old"}
|
||||
fmt.Println(foldersToSkip)
|
||||
db.SetGameDeletionDate()
|
||||
checkBrokenSongs()
|
||||
|
||||
files, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
syncGame(file, foldersToSkip, dir)
|
||||
}
|
||||
finished := time.Now()
|
||||
totalTime := finished.Sub(start)
|
||||
out := time.Time{}.Add(totalTime)
|
||||
fmt.Printf("\nTotal time: %v\n", totalTime)
|
||||
fmt.Printf("Total time: %v\n", out.Format("15:04:05.00000"))
|
||||
}
|
||||
|
||||
func SyncProgress() ProgressResponse {
|
||||
progress := int((foldersSynced / numberOfFoldersToSync) * 100)
|
||||
currentTime := time.Now()
|
||||
timeSpent = currentTime.Sub(start)
|
||||
out := time.Time{}.Add(timeSpent)
|
||||
fmt.Printf("\nTime spent: %v\n", timeSpent)
|
||||
fmt.Printf("Time spent: %v\n", out.Format("15:04:05.00000"))
|
||||
log.Printf("Progress: %v/%v %v%%\n", int(foldersSynced), int(numberOfFoldersToSync), progress)
|
||||
return ProgressResponse{
|
||||
Progress: fmt.Sprintf("%v", progress),
|
||||
TimeSpent: out.Format("15:04:05"),
|
||||
func SyncGamesQuick() {
|
||||
start := time.Now()
|
||||
host := os.Getenv("DB_HOST")
|
||||
var dir string
|
||||
if host != "" {
|
||||
dir = "/sorted/"
|
||||
} else {
|
||||
dir = "/Users/sebastian/ResilioSync/Sorterat_test/"
|
||||
}
|
||||
fmt.Printf("dir: %s\n", dir)
|
||||
foldersToSkip := []string{".sync", "dist", "old"}
|
||||
fmt.Println(foldersToSkip)
|
||||
db.SetGameDeletionDate()
|
||||
checkBrokenSongs()
|
||||
|
||||
files, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
syncGame(file, foldersToSkip, dir)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
finished := time.Now()
|
||||
totalTime := finished.Sub(start)
|
||||
out := time.Time{}.Add(totalTime)
|
||||
fmt.Printf("\nTotal time: %v\n", totalTime)
|
||||
fmt.Printf("Total time: %v\n", out.Format("15:04:05.00000"))
|
||||
}
|
||||
|
||||
func syncGame(file os.DirEntry, foldersToSkip []string, dir string) {
|
||||
if file.IsDir() && !contains(foldersToSkip, file.Name()) {
|
||||
fmt.Println(file.Name())
|
||||
path := dir + file.Name() + "/"
|
||||
fmt.Println(path)
|
||||
|
||||
entries, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
id := -1
|
||||
for _, entry := range entries {
|
||||
fileInfo, err := entry.Info()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
id = getIdFromFile(fileInfo)
|
||||
if id != -1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if id == -1 {
|
||||
addNewGame(file.Name(), path)
|
||||
} else {
|
||||
checkIfChanged(id, file.Name(), path)
|
||||
checkSongs(path, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SyncResult() SyncResponse {
|
||||
func ResetDB() {
|
||||
db.ClearSongs(-1)
|
||||
db.ClearGames()
|
||||
}
|
||||
|
||||
func getIdFromFile(file os.FileInfo) int {
|
||||
name := file.Name()
|
||||
if !file.IsDir() && strings.HasSuffix(name, ".id") {
|
||||
name = strings.Replace(name, ".id", "", 1)
|
||||
name = strings.Replace(name, ".", "", 1)
|
||||
i, _ := strconv.Atoi(name)
|
||||
return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func checkIfChanged(id int, name string, path string) {
|
||||
fmt.Printf("Id from file: %v\n", id)
|
||||
nameFromDb := db.GetGameName(id)
|
||||
fmt.Printf("Name from file: %v\n", name)
|
||||
fmt.Printf("Name from DB: %v\n", nameFromDb)
|
||||
if nameFromDb == "" {
|
||||
fmt.Println("Not in db")
|
||||
db.InsertGameWithExistingId(id, name, path)
|
||||
fmt.Println("Added to db")
|
||||
} else if name != nameFromDb {
|
||||
fmt.Println("Diff name")
|
||||
db.UpdateGameName(id, name, path)
|
||||
checkBrokenSongs()
|
||||
}
|
||||
db.RemoveDeletionDate(id)
|
||||
}
|
||||
|
||||
func addNewGame(name string, path string) {
|
||||
newId := db.GetIdByGameName(name)
|
||||
if newId != -1 {
|
||||
checkBrokenSongs()
|
||||
db.RemoveDeletionDate(newId)
|
||||
} else {
|
||||
newId = db.InsertGame(name, path)
|
||||
}
|
||||
|
||||
fmt.Printf("newId = %v", newId)
|
||||
fileName := path + "/." + strconv.Itoa(newId) + ".id"
|
||||
fmt.Printf("fileName = %v", fileName)
|
||||
|
||||
err := os.WriteFile(fileName, nil, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
checkSongs(path, newId)
|
||||
}
|
||||
|
||||
func checkSongs(gameDir string, gameId int) {
|
||||
files, err := os.ReadDir(gameDir)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
for _, file := range files {
|
||||
entry, err := file.Info()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if isSong(entry) {
|
||||
path := gameDir + entry.Name()
|
||||
fileName := entry.Name()
|
||||
songName, _ := strings.CutSuffix(fileName, ".mp3")
|
||||
if db.CheckSong(path) {
|
||||
db.UpdateSong(songName, fileName, path)
|
||||
} else {
|
||||
db.AddSong(models.SongData{GameId: gameId, SongName: songName, Path: path, FileName: fileName})
|
||||
}
|
||||
} else if isCoverImage(entry) {
|
||||
//TODO: Later add cover art image here in db
|
||||
}
|
||||
}
|
||||
//TODO: Add number of songs here
|
||||
}
|
||||
|
||||
func checkBrokenSongs() {
|
||||
allSongs := db.FetchAllSongs()
|
||||
var brokenSongs []models.SongData
|
||||
for _, song := range allSongs {
|
||||
//Check if file exists and open
|
||||
openFile, err := os.Open(song.Path)
|
||||
if err != nil {
|
||||
//File not found
|
||||
brokenSongs = append(brokenSongs, song)
|
||||
fmt.Printf("song broken: %v", song.Path)
|
||||
} else {
|
||||
err = openFile.Close()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
db.RemoveBrokenSongs(brokenSongs)
|
||||
}
|
||||
|
||||
func SyncGamesNew() Response {
|
||||
repo = repository.New(db.Dbpool)
|
||||
start := time.Now()
|
||||
fmt.Printf("dir: %s\n", Conf.Path)
|
||||
foldersToSkip := []string{".sync", "dist", "old"}
|
||||
fmt.Println(foldersToSkip)
|
||||
|
||||
var err error
|
||||
|
||||
gamesBeforeSync, err = repo.FindAllGames(db.Ctx)
|
||||
handleError("FindAllGames Before", err, "")
|
||||
fmt.Printf("Games Before: %d\n", len(gamesBeforeSync))
|
||||
|
||||
allGames, err = repo.GetAllGamesIncludingDeleted(db.Ctx)
|
||||
handleError("GetAllGamesIncludingDeleted", err, "")
|
||||
err = repo.SetGameDeletionDate(db.Ctx)
|
||||
handleError("SetGameDeletionDate", err, "")
|
||||
|
||||
directories, err := os.ReadDir(Conf.Path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
syncWg.Add(len(directories))
|
||||
for _, dir := range directories {
|
||||
go func() {
|
||||
defer syncWg.Done()
|
||||
syncGameNew(dir, foldersToSkip, Conf.Path)
|
||||
}()
|
||||
}
|
||||
syncWg.Wait()
|
||||
checkBrokenSongsNew()
|
||||
|
||||
gamesAfterSync, err = repo.FindAllGames(db.Ctx)
|
||||
handleError("FindAllGames After", err, "")
|
||||
|
||||
fmt.Printf("\nGames Before: %d\n", len(gamesBeforeSync))
|
||||
fmt.Printf("Games After: %d\n", len(gamesAfterSync))
|
||||
|
||||
@@ -122,7 +323,7 @@ func SyncResult() SyncResponse {
|
||||
fmt.Printf("\n\n")
|
||||
var gamesRemovedTemp []string
|
||||
for _, beforeGame := range gamesBeforeSync {
|
||||
var found = false
|
||||
var found bool = false
|
||||
for _, afterGame := range gamesAfterSync {
|
||||
if beforeGame.GameName == afterGame.GameName {
|
||||
found = true
|
||||
@@ -138,7 +339,7 @@ func SyncResult() SyncResponse {
|
||||
|
||||
for _, game := range gamesRemovedTemp {
|
||||
var found bool = false
|
||||
for key := range gamesChangedTitle {
|
||||
for key, _ := range gamesChangedTitle {
|
||||
if game == key {
|
||||
found = true
|
||||
//fmt.Printf("Game: %s, Found: %v break2\n", game, found)
|
||||
@@ -160,110 +361,32 @@ func SyncResult() SyncResponse {
|
||||
fmt.Printf("%s\n", error)
|
||||
}
|
||||
|
||||
finished := time.Now()
|
||||
totalTime := finished.Sub(start)
|
||||
out := time.Time{}.Add(totalTime)
|
||||
fmt.Printf("\nTotal time: %v\n", totalTime)
|
||||
fmt.Printf("Total time: %v\n", out.Format("15:04:05.00000"))
|
||||
|
||||
return SyncResponse{
|
||||
return Response{
|
||||
GamesAdded: gamesAdded,
|
||||
GamesReAdded: gamesReAdded,
|
||||
GamesChangedTitle: gamesChangedTitle,
|
||||
GamesChangedContent: gamesChangedContent,
|
||||
GamesRemoved: gamesRemoved,
|
||||
CatchedErrors: catchedErrors,
|
||||
TotalTime: out.Format("15:04:05"),
|
||||
}
|
||||
}
|
||||
|
||||
func SyncGamesNewFull() {
|
||||
syncGamesNew(true)
|
||||
Reset()
|
||||
}
|
||||
|
||||
func SyncGamesNewOnlyChanges() {
|
||||
syncGamesNew(false)
|
||||
Reset()
|
||||
}
|
||||
|
||||
func syncGamesNew(full bool) {
|
||||
Syncing = true
|
||||
|
||||
musicPath := os.Getenv("MUSIC_PATH")
|
||||
fmt.Printf("dir: %s\n", musicPath)
|
||||
if !strings.HasSuffix(musicPath, "/") {
|
||||
musicPath += "/"
|
||||
}
|
||||
|
||||
var syncWg sync.WaitGroup
|
||||
|
||||
initRepo()
|
||||
start = time.Now()
|
||||
foldersToSkip := []string{".sync", "dist", "old", "characters"}
|
||||
fmt.Println(foldersToSkip)
|
||||
|
||||
var err error
|
||||
gamesAdded = nil
|
||||
gamesReAdded = nil
|
||||
gamesChangedTitle = nil
|
||||
gamesChangedContent = nil
|
||||
gamesRemoved = nil
|
||||
catchedErrors = nil
|
||||
brokenSongs = nil
|
||||
|
||||
gamesBeforeSync, err = repo.FindAllGames(db.Ctx)
|
||||
handleError("FindAllGames Before", err, "")
|
||||
fmt.Printf("Games Before: %d\n", len(gamesBeforeSync))
|
||||
|
||||
allGames, err = repo.GetAllGamesIncludingDeleted(db.Ctx)
|
||||
handleError("GetAllGamesIncludingDeleted", err, "")
|
||||
err = repo.SetGameDeletionDate(db.Ctx)
|
||||
handleError("SetGameDeletionDate", err, "")
|
||||
|
||||
directories, err := os.ReadDir(musicPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
pool, _ := ants.NewPool(50, ants.WithPreAlloc(true))
|
||||
defer pool.Release()
|
||||
|
||||
foldersSynced = 0
|
||||
numberOfFoldersToSync = float32(len(directories))
|
||||
syncWg.Add(int(numberOfFoldersToSync))
|
||||
for _, dir := range directories {
|
||||
pool.Submit(func() {
|
||||
defer syncWg.Done()
|
||||
syncGameNew(dir, foldersToSkip, musicPath, full)
|
||||
})
|
||||
}
|
||||
syncWg.Wait()
|
||||
checkBrokenSongsNew()
|
||||
|
||||
gamesAfterSync, err = repo.FindAllGames(db.Ctx)
|
||||
handleError("FindAllGames After", err, "")
|
||||
|
||||
finished := time.Now()
|
||||
totalTime = finished.Sub(start)
|
||||
out := time.Time{}.Add(totalTime)
|
||||
fmt.Printf("\nTotal time: %v\n", totalTime)
|
||||
fmt.Printf("Total time: %v\n", out.Format("15:04:05.00000"))
|
||||
|
||||
Syncing = false
|
||||
}
|
||||
|
||||
func checkBrokenSongsNew() {
|
||||
allSongs, err := repo.FetchAllSongs(db.Ctx)
|
||||
handleError("FetchAllSongs", err, "")
|
||||
var brokenWg sync.WaitGroup
|
||||
poolBroken, _ := ants.NewPool(50, ants.WithPreAlloc(true))
|
||||
defer poolBroken.Release()
|
||||
|
||||
brokenWg.Add(len(allSongs))
|
||||
for _, song := range allSongs {
|
||||
poolBroken.Submit(func() {
|
||||
go func() {
|
||||
defer brokenWg.Done()
|
||||
checkBrokenSongNew(song)
|
||||
})
|
||||
}()
|
||||
}
|
||||
brokenWg.Wait()
|
||||
err = repo.RemoveBrokenSongs(db.Ctx, brokenSongs)
|
||||
@@ -285,10 +408,10 @@ func checkBrokenSongNew(song repository.Song) {
|
||||
}
|
||||
}
|
||||
|
||||
func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string, full bool) {
|
||||
func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string) {
|
||||
if file.IsDir() && !contains(foldersToSkip, file.Name()) {
|
||||
fmt.Printf("Syncing: %s\n", file.Name())
|
||||
gameDir := baseDir + file.Name() + "/"
|
||||
|
||||
dirHash := getHashForDir(gameDir)
|
||||
|
||||
var status GameStatus = NewGame
|
||||
@@ -318,15 +441,13 @@ func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string, full
|
||||
}
|
||||
}
|
||||
|
||||
if full {
|
||||
status = TitleChanged
|
||||
}
|
||||
entries, err := os.ReadDir(gameDir)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
switch status {
|
||||
case NewGame:
|
||||
fmt.Printf("\n\nID: %d | GameName: %s | GameHash: %s | Status: %s\n", id, file.Name(), dirHash, status)
|
||||
if id != -1 {
|
||||
for _, entry := range entries {
|
||||
fileInfo, err := entry.Info()
|
||||
@@ -357,17 +478,16 @@ func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string, full
|
||||
} else {
|
||||
id = insertGameNew(file.Name(), gameDir, dirHash)
|
||||
}
|
||||
//fmt.Printf("\n\nID: %d | GameName: %s | GameHash: %s | Status: %s\n", id, file.Name(), dirHash, status)
|
||||
gamesAdded = append(gamesAdded, file.Name())
|
||||
newCheckSongs(entries, gameDir, id)
|
||||
case GameChanged:
|
||||
//fmt.Printf("\n\nID: %d | GameName: %s | GameHash: %s | Status: %s\n", id, file.Name(), dirHash, status)
|
||||
fmt.Printf("\n\nID: %d | GameName: %s | GameHash: %s | Status: %s\n", id, file.Name(), dirHash, status)
|
||||
err = repo.UpdateGameHash(db.Ctx, repository.UpdateGameHashParams{Hash: dirHash, ID: id})
|
||||
handleError("UpdateGameHash", err, "")
|
||||
gamesChangedContent = append(gamesChangedContent, file.Name())
|
||||
newCheckSongs(entries, gameDir, id)
|
||||
case TitleChanged:
|
||||
//fmt.Printf("\n\nID: %d | GameName: %s | GameHash: %s | Status: %s\n", id, file.Name(), dirHash, status)
|
||||
fmt.Printf("\n\nID: %d | GameName: %s | GameHash: %s | Status: %s\n", id, file.Name(), dirHash, status)
|
||||
//println("TitleChanged")
|
||||
err = repo.UpdateGameName(db.Ctx, repository.UpdateGameNameParams{Name: file.Name(), Path: gameDir, ID: id})
|
||||
handleError("UpdateGameName", err, "")
|
||||
@@ -391,12 +511,9 @@ func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string, full
|
||||
|
||||
}
|
||||
}
|
||||
fmt.Printf("\n\nID: %d | GameName: %s | GameHash: %s | Status: %s\n", id, file.Name(), dirHash, status)
|
||||
err = repo.RemoveDeletionDate(db.Ctx, id)
|
||||
handleError("RemoveDeletionDate", err, "")
|
||||
}
|
||||
foldersSynced++
|
||||
log.Printf("Progress: %v/%v %v%%\n", int(foldersSynced), int(numberOfFoldersToSync), int((foldersSynced/numberOfFoldersToSync)*100))
|
||||
}
|
||||
|
||||
func insertGameNew(name string, path string, hash string) int32 {
|
||||
@@ -419,26 +536,19 @@ func insertGameNew(name string, path string, hash string) int32 {
|
||||
func newCheckSongs(entries []os.DirEntry, gameDir string, id int32) int32 {
|
||||
//hasher := md5.New()
|
||||
var numberOfSongs int32
|
||||
numberOfFiles := len(entries)
|
||||
|
||||
var songWg sync.WaitGroup
|
||||
poolSong, _ := ants.NewPool(numberOfFiles, ants.WithPreAlloc(true))
|
||||
defer poolSong.Release()
|
||||
|
||||
songWg.Add(numberOfFiles)
|
||||
songWg.Add(len(entries))
|
||||
for _, entry := range entries {
|
||||
poolSong.Submit(func() {
|
||||
go func() {
|
||||
defer songWg.Done()
|
||||
if newCheckSong(entry, gameDir, id) {
|
||||
numberOfSongs++
|
||||
}
|
||||
})
|
||||
newCheckSong(entry, gameDir, id)
|
||||
}()
|
||||
}
|
||||
songWg.Wait()
|
||||
return numberOfSongs
|
||||
}
|
||||
|
||||
func newCheckSong(entry os.DirEntry, gameDir string, id int32) bool {
|
||||
func newCheckSong(entry os.DirEntry, gameDir string, id int32) {
|
||||
fileInfo, err := entry.Info()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
@@ -457,7 +567,7 @@ func newCheckSong(entry os.DirEntry, gameDir string, id int32) bool {
|
||||
handleError("GetSongWithHash", err, fmt.Sprintf("GameID: %d | Path: %s | SongName: %s | SongHash: %s\n", id, path, entry.Name(), songHash))
|
||||
if err == nil {
|
||||
if song.SongName == songName && song.Path == path {
|
||||
return false
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Printf("Song Changed\n")
|
||||
@@ -493,11 +603,9 @@ func newCheckSong(entry os.DirEntry, gameDir string, id int32) bool {
|
||||
|
||||
}
|
||||
}
|
||||
return true
|
||||
} else if isCoverImage(fileInfo) {
|
||||
//TODO: Later add cover art image here in db
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func handleError(funcName string, err error, msg string) {
|
||||
10
sqlc.yaml
@@ -1,13 +1,13 @@
|
||||
version: "2"
|
||||
sql:
|
||||
- engine: "postgresql"
|
||||
queries: "./internal/db/queries"
|
||||
schema: "./internal/db/migrations"
|
||||
queries: "./db/queries"
|
||||
schema: "./db/migrations"
|
||||
gen:
|
||||
go:
|
||||
emit_json_tags: true
|
||||
package: "repository"
|
||||
out: "internal/db/repository"
|
||||
out: "db/repository"
|
||||
sql_package: "pgx/v5"
|
||||
overrides:
|
||||
- db_type: "pg_catalog.timestamp"
|
||||
@@ -30,10 +30,6 @@ sql:
|
||||
go_type:
|
||||
pointer: true
|
||||
type: "int32"
|
||||
- db_type: "int4"
|
||||
nullable: false
|
||||
go_type:
|
||||
type: "int32"
|
||||
- db_type: "date"
|
||||
nullable: false
|
||||
go_type:
|
||||
|
||||