feat: add local DB to save downloads

This commit is contained in:
Mathis Maquenne
2025-04-26 13:09:59 +02:00
parent 6675834842
commit f931cad746
8 changed files with 215 additions and 64 deletions
+23 -4
View File
@@ -5,9 +5,11 @@ import (
"os" "os"
"path" "path"
"strings" "strings"
"time"
"github.com/flytam/filenamify" "github.com/flytam/filenamify"
"github.com/mathismqn/godeez/internal/config" "github.com/mathismqn/godeez/internal/config"
"github.com/mathismqn/godeez/internal/db"
"github.com/mathismqn/godeez/internal/deezer" "github.com/mathismqn/godeez/internal/deezer"
"github.com/mathismqn/godeez/internal/tags" "github.com/mathismqn/godeez/internal/tags"
"github.com/mathismqn/godeez/internal/utils" "github.com/mathismqn/godeez/internal/utils"
@@ -126,11 +128,9 @@ func downloadContent(contentType string, args []string) {
songTitle = fmt.Sprintf("%s %s", song.Title, song.Version) songTitle = fmt.Sprintf("%s %s", song.Title, song.Version)
} }
fmt.Printf(" Downloading %s...", songTitle)
media, err := song.GetMediaData(session.LicenseToken, quality) media, err := song.GetMediaData(session.LicenseToken, quality)
if err != nil { if err != nil {
fmt.Printf("\r Downloading %s... FAILED\n", songTitle) fmt.Printf(" Downloading %s... FAILED\n", songTitle)
fmt.Fprintf(os.Stderr, "Error: could not get media data: %v\n", err) fmt.Fprintf(os.Stderr, "Error: could not get media data: %v\n", err)
if err.Error() == "invalid license token" { if err.Error() == "invalid license token" {
os.Exit(1) os.Exit(1)
@@ -139,7 +139,7 @@ func downloadContent(contentType string, args []string) {
} }
if len(media.Data) == 0 || len(media.Data[0].Media) == 0 || len(media.Data[0].Media[0].Sources) == 0 { if len(media.Data) == 0 || len(media.Data[0].Media) == 0 || len(media.Data[0].Media[0].Sources) == 0 {
fmt.Printf("\r Downloading %s... FAILED\n", songTitle) fmt.Printf(" Downloading %s... FAILED\n", songTitle)
fmt.Fprintf(os.Stderr, "Error: could not get media sources\n") fmt.Fprintf(os.Stderr, "Error: could not get media sources\n")
continue continue
} }
@@ -165,6 +165,15 @@ func downloadContent(contentType string, args []string) {
fileName, _ = filenamify.Filenamify(fileName, filenamify.Options{}) fileName, _ = filenamify.Filenamify(fileName, filenamify.Options{})
filePath := path.Join(output, fileName) filePath := path.Join(output, fileName)
if existing, err := db.Get(song.ID); err == nil {
if existing.Quality == media.Data[0].Media[0].Format && utils.FileExists(existing.Path) {
fmt.Printf(" Skipping %s (already downloaded at %s)\n", songTitle, existing.Path)
continue
}
}
fmt.Printf(" Downloading %s...", songTitle)
err = media.Download(url, filePath, song.ID) err = media.Download(url, filePath, song.ID)
if err != nil { if err != nil {
fmt.Printf("\r Downloading %s... FAILED\n", songTitle) fmt.Printf("\r Downloading %s... FAILED\n", songTitle)
@@ -184,6 +193,16 @@ func downloadContent(contentType string, args []string) {
if err := tags.AddTags(resource, song, filePath, tempo, key); err != nil { if err := tags.AddTags(resource, song, filePath, tempo, key); err != nil {
fmt.Fprintf(os.Stderr, "Warning: could not add tags to song: %v\n", err) fmt.Fprintf(os.Stderr, "Warning: could not add tags to song: %v\n", err)
} }
info := &db.DownloadInfo{
SongID: song.ID,
Quality: media.Data[0].Media[0].Format,
Path: filePath,
Downloaded: time.Now(),
}
if err := info.Save(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: could not save download info: %v\n", err)
}
} }
} }
+5 -54
View File
@@ -1,13 +1,9 @@
package cmd package cmd
import ( import (
"fmt"
"os"
"path"
"github.com/mathismqn/godeez/internal/config" "github.com/mathismqn/godeez/internal/config"
"github.com/mathismqn/godeez/internal/db"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
) )
var cfgFile string var cfgFile string
@@ -22,53 +18,8 @@ var RootCmd = &cobra.Command{
func init() { func init() {
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.godeez)") RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.godeez)")
cobra.OnInitialize(initConfig) cobra.OnInitialize(func() {
} config.Init(cfgFile)
db.Init()
func initConfig() { })
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
homedir, err := os.UserHomeDir()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: could not get home directory: %v\n", err)
os.Exit(1)
}
path := path.Join(homedir, ".godeez")
if _, err := os.Stat(path); os.IsNotExist(err) {
fmt.Printf("Config file not found, creating one at %s\n", path)
content := []byte("arl_cookie = ''\nsecret_key = ''\niv = '0001020304050607'\n")
if err := os.WriteFile(path, content, 0644); err != nil {
fmt.Fprintf(os.Stderr, "Error: could not create config file: %v\n", err)
os.Exit(1)
}
}
viper.AddConfigPath(homedir)
viper.SetConfigName(".godeez")
}
viper.SetConfigType("toml")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err != nil {
fmt.Fprintf(os.Stderr, "Error: could not read config file: %v\n", err)
os.Exit(1)
}
cfg := &config.Cfg
if err := viper.Unmarshal(&cfg); err != nil {
fmt.Fprintf(os.Stderr, "Error: could not unmarshal config file: %v\n", err)
os.Exit(1)
}
if cfg.SecretKey == "" {
fmt.Fprintln(os.Stderr, "Error: secret_key is not set in config file")
os.Exit(1)
}
if cfg.IV == "" {
fmt.Fprintln(os.Stderr, "Error: iv is not set in config file")
os.Exit(1)
}
} }
+3 -2
View File
@@ -24,7 +24,7 @@ require (
go.uber.org/multierr v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.29.0 // indirect golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/text v0.19.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
@@ -38,6 +38,7 @@ require (
github.com/go-flac/flacvorbis/v2 v2.0.2 github.com/go-flac/flacvorbis/v2 v2.0.2
github.com/go-flac/go-flac/v2 v2.0.1 github.com/go-flac/go-flac/v2 v2.0.1
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.6 // indirect
go.etcd.io/bbolt v1.4.0
golang.org/x/crypto v0.28.0 golang.org/x/crypto v0.28.0
) )
+10 -4
View File
@@ -55,8 +55,9 @@ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -67,11 +68,14 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk=
go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
@@ -94,6 +98,8 @@ golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -101,8 +107,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+63
View File
@@ -1,5 +1,14 @@
package config package config
import (
"fmt"
"os"
"path"
"github.com/mathismqn/godeez/internal/utils"
"github.com/spf13/viper"
)
type Config struct { type Config struct {
ArlCookie string `mapstructure:"arl_cookie"` ArlCookie string `mapstructure:"arl_cookie"`
SecretKey string `mapstructure:"secret_key"` SecretKey string `mapstructure:"secret_key"`
@@ -7,3 +16,57 @@ type Config struct {
} }
var Cfg Config var Cfg Config
func Init(cfgFile string) {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
homeDir, err := os.UserHomeDir()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: could not get home directory: %v\n", err)
os.Exit(1)
}
appDir := path.Join(homeDir, ".godeez")
if err := utils.EnsureDir(appDir); err != nil {
fmt.Fprintf(os.Stderr, "Error: could not create app directory: %v\n", err)
os.Exit(1)
}
cfgPath := path.Join(appDir, "config.toml")
if _, err := os.Stat(cfgPath); os.IsNotExist(err) {
fmt.Printf("Config file not found, creating one at %s\n", cfgPath)
content := []byte("arl_cookie = ''\nsecret_key = ''\niv = '0001020304050607'\n")
if err := os.WriteFile(cfgPath, content, 0644); err != nil {
fmt.Fprintf(os.Stderr, "Error: could not create config file: %v\n", err)
os.Exit(1)
}
}
viper.AddConfigPath(appDir)
viper.SetConfigName("config.toml")
}
viper.SetConfigType("toml")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err != nil {
fmt.Fprintf(os.Stderr, "Error: could not read config file: %v\n", err)
os.Exit(1)
}
cfg := &Cfg
if err := viper.Unmarshal(&cfg); err != nil {
fmt.Fprintf(os.Stderr, "Error: could not unmarshal config file: %v\n", err)
os.Exit(1)
}
if cfg.SecretKey == "" {
fmt.Fprintln(os.Stderr, "Error: secret_key is not set in config file")
os.Exit(1)
}
if cfg.IV == "" {
fmt.Fprintln(os.Stderr, "Error: iv is not set in config file")
os.Exit(1)
}
}
+58
View File
@@ -0,0 +1,58 @@
package db
import (
"encoding/json"
"fmt"
"time"
"go.etcd.io/bbolt"
)
type DownloadInfo struct {
SongID string `json:"song_id"`
Quality string `json:"quality"`
Path string `json:"path"`
Downloaded time.Time `json:"downloaded_at"`
}
func Get(songID string) (*DownloadInfo, error) {
var info DownloadInfo
if err := db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket(trackBucket)
if b == nil {
return fmt.Errorf("bucket not found")
}
data := b.Get([]byte(songID))
if data == nil {
return fmt.Errorf("not found")
}
return json.Unmarshal(data, &info)
}); err != nil {
return nil, err
}
return &info, nil
}
func (d *DownloadInfo) Save() error {
return db.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket(trackBucket)
if b == nil {
return fmt.Errorf("bucket not found")
}
data, err := json.Marshal(d)
if err != nil {
return err
}
err = b.Put([]byte(d.SongID), data)
if err != nil {
return err
}
return nil
})
}
+44
View File
@@ -0,0 +1,44 @@
package db
import (
"fmt"
"os"
"path"
"github.com/mathismqn/godeez/internal/utils"
bolt "go.etcd.io/bbolt"
)
var (
db *bolt.DB
trackBucket = []byte("tracks")
)
func Init() {
homeDir, err := os.UserHomeDir()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: could not get home directory: %v\n", err)
os.Exit(1)
}
appDir := path.Join(homeDir, ".godeez")
if err := utils.EnsureDir(appDir); err != nil {
fmt.Fprintf(os.Stderr, "Error: could not create app directory: %v\n", err)
os.Exit(1)
}
dbPath := path.Join(appDir, "tracks.db")
db, err = bolt.Open(dbPath, 0600, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: could not open database: %v\n", err)
os.Exit(1)
}
if err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists(trackBucket)
return err
}); err != nil {
fmt.Fprintf(os.Stderr, "Error: could not create bucket: %v\n", err)
os.Exit(1)
}
}
+9
View File
@@ -0,0 +1,9 @@
package utils
import "os"
func FileExists(path string) bool {
info, err := os.Stat(path)
return err == nil && !info.IsDir()
}