From f718e0323f669705e8334e4a2aae247c38f1c555 Mon Sep 17 00:00:00 2001 From: jo_kil Date: Mon, 25 Dec 2023 15:17:24 +0100 Subject: [PATCH] delete & list command, always ensure response from bot, remove cache deadlock, remove db panic on error --- EsefexApi/bot/commands/commands.go | 43 ++++++++++++++- EsefexApi/bot/commands/delete.go | 61 +++++++++++++++++++++ EsefexApi/bot/commands/list.go | 68 ++++++++++++++++++++++++ EsefexApi/bot/commands/session.go | 20 ++++--- EsefexApi/bot/commands/upload.go | 25 ++++----- EsefexApi/sounddb/apimockdb/apimockdb.go | 6 +++ EsefexApi/sounddb/db.go | 1 + EsefexApi/sounddb/dbcache/dbcache.go | 24 ++++++--- EsefexApi/sounddb/filedb/addsound.go | 8 +-- EsefexApi/sounddb/filedb/deletesound.go | 4 +- EsefexApi/sounddb/filedb/filedb_test.go | 4 +- EsefexApi/sounddb/filedb/getserverids.go | 2 +- EsefexApi/sounddb/filedb/getsounduids.go | 3 +- EsefexApi/sounddb/filedb/internal.go | 12 +---- EsefexApi/sounddb/filedb/soundexists.go | 15 ++++++ 15 files changed, 238 insertions(+), 58 deletions(-) create mode 100644 EsefexApi/bot/commands/delete.go create mode 100644 EsefexApi/bot/commands/list.go create mode 100644 EsefexApi/sounddb/filedb/soundexists.go diff --git a/EsefexApi/bot/commands/commands.go b/EsefexApi/bot/commands/commands.go index c8606e7..54c32a7 100644 --- a/EsefexApi/bot/commands/commands.go +++ b/EsefexApi/bot/commands/commands.go @@ -2,6 +2,8 @@ package commands import ( "esefexapi/sounddb" + "fmt" + "log" "github.com/bwmarrin/discordgo" ) @@ -22,10 +24,47 @@ func NewCommandHandlers(db sounddb.ISoundDB, domain string) *CommandHandlers { } ch.Commands["upload"] = UploadCommand - ch.Handlers["upload"] = ch.Upload + ch.Handlers["upload"] = WithErrorHandling(ch.Upload) ch.Commands["session"] = SessionCommand - ch.Handlers["session"] = ch.Session + ch.Handlers["session"] = WithErrorHandling(ch.Session) + + ch.Commands["list"] = ListCommand + ch.Handlers["list"] = WithErrorHandling(ch.List) + + ch.Commands["delete"] = DeleteCommand + ch.Handlers["delete"] = WithErrorHandling(ch.Delete) return ch } + +func WithErrorHandling(h func(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error)) func(s *discordgo.Session, i *discordgo.InteractionCreate) { + return func(s *discordgo.Session, i *discordgo.InteractionCreate) { + r, err := h(s, i) + if err != nil { + log.Printf("Cannot execute command: %v", err) + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("An error has occurred while executing the command: %v", err), + }, + }) + } + + if r != nil { + s.InteractionRespond(i.Interaction, r) + } + } +} + +func OptionsMap(i *discordgo.InteractionCreate) map[string]*discordgo.ApplicationCommandInteractionDataOption { + options := i.ApplicationCommandData().Options + + optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options)) + for _, opt := range options { + optionMap[opt.Name] = opt + } + + return optionMap +} diff --git a/EsefexApi/bot/commands/delete.go b/EsefexApi/bot/commands/delete.go new file mode 100644 index 0000000..8d1ce8f --- /dev/null +++ b/EsefexApi/bot/commands/delete.go @@ -0,0 +1,61 @@ +package commands + +import ( + "esefexapi/sounddb" + "fmt" + "log" + + "github.com/bwmarrin/discordgo" +) + +var ( + DeleteCommand = &discordgo.ApplicationCommand{ + Name: "delete", + Description: "Delete a sound effect", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "sound-id", + Description: "The sound effect to delete", + Required: true, + }, + }, + } +) + +func (c *CommandHandlers) Delete(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + options := OptionsMap(i) + soundID := options["sound-id"] + + uid := sounddb.SuidFromStrings(i.GuildID, fmt.Sprint(soundID.Value)) + + exists, err := c.db.SoundExists(uid) + if err != nil { + return nil, err + } + if !exists { + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("Sound effect `%s` does not exist", soundID.Value), + }, + }, nil + } + + log.Print("a") + + err = c.db.DeleteSound(uid) + if err != nil { + log.Println(err) + return nil, err + } + + log.Printf("Deleted sound effect %v from server %v", soundID.Value, i.GuildID) + + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("Deleted sound effect `%s`", soundID.Value), + }, + }, nil +} diff --git a/EsefexApi/bot/commands/list.go b/EsefexApi/bot/commands/list.go new file mode 100644 index 0000000..51d700f --- /dev/null +++ b/EsefexApi/bot/commands/list.go @@ -0,0 +1,68 @@ +package commands + +import ( + "esefexapi/sounddb" + "fmt" + + "github.com/bwmarrin/discordgo" +) + +var ( + ListCommand = &discordgo.ApplicationCommand{ + Name: "list", + Description: "List all sound effects in the server", + } +) + +func (c *CommandHandlers) List(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + uids, err := c.db.GetSoundUIDs(i.GuildID) + if err != nil { + return nil, err + } + // log.Printf("List: %v", uids) + + var metas = make([]sounddb.SoundMeta, 0, len(uids)) + for _, uid := range uids { + meta, err := c.db.GetSoundMeta(uid) + if err != nil { + return nil, err + } + metas = append(metas, meta) + } + + if len(metas) == 0 { + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "There are no sounds in this server", + }, + }, nil + } + + if len(metas) == 1 { + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("There is 1 sound in this server: \n%s", fmtMetaList(metas)), + }, + }, nil + } + + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("There are %d sounds in this server: \n%s", len(metas), fmtMetaList(metas)), + }, + }, nil +} + +func fmtMetaList(metas []sounddb.SoundMeta) string { + // log.Printf("fmtMetaList: %v", metas) + var str string + for _, meta := range metas { + str += fmt.Sprintf("- %s `%s`\n", meta.Name, meta.SoundID) + } + + // log.Println(str) + return str +} diff --git a/EsefexApi/bot/commands/session.go b/EsefexApi/bot/commands/session.go index 0ab0659..4028de3 100644 --- a/EsefexApi/bot/commands/session.go +++ b/EsefexApi/bot/commands/session.go @@ -3,7 +3,6 @@ package commands import ( "esefexapi/bot/actions" "fmt" - "log" "github.com/bwmarrin/discordgo" ) @@ -15,10 +14,10 @@ var ( } ) -func (c *CommandHandlers) Session(s *discordgo.Session, i *discordgo.InteractionCreate) { +func (c *CommandHandlers) Session(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { g, err := s.State.Guild(i.GuildID) if err != nil { - log.Printf("Cannot get guild: %v", err) + return nil, err } var userChannel string @@ -29,14 +28,13 @@ func (c *CommandHandlers) Session(s *discordgo.Session, i *discordgo.Interaction userChannel = vs.ChannelID } } + if !userConnected { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + return &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "You must be connected to a voice channel to get the session link.", - }, - }) - return + }}, nil } route := "joinsession" @@ -44,12 +42,12 @@ func (c *CommandHandlers) Session(s *discordgo.Session, i *discordgo.Interaction // https://esefex.com/joinsession/1234567890 url := fmt.Sprintf("https://%s/%s/%s", c.domain, route, i.GuildID) - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + actions.JoinChannelVoice(s, i.GuildID, userChannel) + + return &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: url, }, - }) - - actions.JoinChannelVoice(s, i.GuildID, userChannel) + }, nil } diff --git a/EsefexApi/bot/commands/upload.go b/EsefexApi/bot/commands/upload.go index e81b62c..654dcc1 100644 --- a/EsefexApi/bot/commands/upload.go +++ b/EsefexApi/bot/commands/upload.go @@ -35,34 +35,27 @@ var ( } ) -func (c *CommandHandlers) Upload(s *discordgo.Session, i *discordgo.InteractionCreate) { - options := i.ApplicationCommandData().Options +func (c *CommandHandlers) Upload(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + options := OptionsMap(i) - optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options)) - for _, opt := range options { - optionMap[opt.Name] = opt - } - - icon := optionMap["icon"] + icon := options["icon"] iconURL := util.ExtractIconUrl(icon) - soundFile := optionMap["sound-file"] + soundFile := options["sound-file"] soundFileUrl := i.ApplicationCommandData().Resolved.Attachments[fmt.Sprint(soundFile.Value)].URL pcm, err := util.Download2PCM(soundFileUrl) if err != nil { - log.Printf("Cannot download sound file: %v", err) - return + return nil, err } - c.db.AddSound(i.GuildID, fmt.Sprint(optionMap["name"].Value), iconURL, pcm) + c.db.AddSound(i.GuildID, fmt.Sprint(options["name"].Value), iconURL, pcm) - log.Printf("Uploaded sound effect %v to server %v", optionMap["name"].Value, i.GuildID) - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + log.Printf("Uploaded sound effect %v to server %v", options["name"].Value, i.GuildID) + return &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "Uploaded sound effect", }, - }) - + }, nil } diff --git a/EsefexApi/sounddb/apimockdb/apimockdb.go b/EsefexApi/sounddb/apimockdb/apimockdb.go index 68849d4..3ec7861 100644 --- a/EsefexApi/sounddb/apimockdb/apimockdb.go +++ b/EsefexApi/sounddb/apimockdb/apimockdb.go @@ -82,3 +82,9 @@ func (*ApiMockDB) GetSoundUIDs(serverID string) ([]sounddb.SoundUID, error) { } return uids, nil } + +// SoundExists implements sounddb.ISoundDB. +func (*ApiMockDB) SoundExists(uid sounddb.SoundUID) (bool, error) { + _, ok := mockData[uid.ServerID][uid.SoundID] + return ok, nil +} diff --git a/EsefexApi/sounddb/db.go b/EsefexApi/sounddb/db.go index 18fc552..72138d6 100644 --- a/EsefexApi/sounddb/db.go +++ b/EsefexApi/sounddb/db.go @@ -7,6 +7,7 @@ type ISoundDB interface { GetSoundPcm(uid SoundUID) ([]int16, error) GetSoundUIDs(serverID string) ([]SoundUID, error) GetServerIDs() ([]string, error) + SoundExists(uid SoundUID) (bool, error) } type SoundUID struct { diff --git a/EsefexApi/sounddb/dbcache/dbcache.go b/EsefexApi/sounddb/dbcache/dbcache.go index 8831b88..71884dc 100644 --- a/EsefexApi/sounddb/dbcache/dbcache.go +++ b/EsefexApi/sounddb/dbcache/dbcache.go @@ -32,14 +32,14 @@ func NewDBCache(db sounddb.ISoundDB) *DBCache { // AddSound implements db.SoundDB. func (c *DBCache) AddSound(serverID string, name string, icon string, pcm []int16) (sounddb.SoundUID, error) { + c.rw.Lock() + defer c.rw.Unlock() + uid, err := c.db.AddSound(serverID, name, icon, pcm) if err != nil { return sounddb.SoundUID{}, err } - c.rw.Lock() - defer c.rw.Unlock() - c.sounds[uid] = &CachedSound{ Data: pcm, Meta: sounddb.SoundMeta{ @@ -55,17 +55,14 @@ func (c *DBCache) AddSound(serverID string, name string, icon string, pcm []int1 // DeleteSound implements db.SoundDB. func (c *DBCache) DeleteSound(uid sounddb.SoundUID) error { - c.rw.RLock() - defer c.rw.RUnlock() + c.rw.Lock() + defer c.rw.Unlock() err := c.db.DeleteSound(uid) if err != nil { return err } - c.rw.Lock() - defer c.rw.Unlock() - delete(c.sounds, uid) return nil @@ -193,3 +190,14 @@ func (c *DBCache) LoadSound(uid sounddb.SoundUID) (*CachedSound, error) { return &s, nil } + +func (c *DBCache) SoundExists(uid sounddb.SoundUID) (bool, error) { + c.rw.RLock() + defer c.rw.RUnlock() + + if _, ok := c.sounds[uid]; ok { + return true, nil + } + + return c.db.SoundExists(uid) +} diff --git a/EsefexApi/sounddb/filedb/addsound.go b/EsefexApi/sounddb/filedb/addsound.go index 382f2f8..966d3e2 100644 --- a/EsefexApi/sounddb/filedb/addsound.go +++ b/EsefexApi/sounddb/filedb/addsound.go @@ -31,13 +31,13 @@ func (f *FileDB) AddSound(serverID string, name string, iconUrl string, pcm []in path = fmt.Sprintf("%s/%s/%s_meta.json", f.location, serverID, sound.SoundID) metaFile, err := os.Create(path) if err != nil { - log.Fatal(err) + log.Print(err) return sounddb.SoundUID{}, err } metaJson, err := json.Marshal(sound) if err != nil { - log.Fatal(err) + log.Print(err) return sounddb.SoundUID{}, err } @@ -50,13 +50,13 @@ func (f *FileDB) AddSound(serverID string, name string, iconUrl string, pcm []in soundFile, err := os.Create(path) if err != nil { - log.Fatal(err) + log.Print(err) return sounddb.SoundUID{}, err } err = binary.Write(soundFile, binary.LittleEndian, pcm) if err != nil { - log.Fatal(err) + log.Print(err) return sounddb.SoundUID{}, err } diff --git a/EsefexApi/sounddb/filedb/deletesound.go b/EsefexApi/sounddb/filedb/deletesound.go index 3094694..c094746 100644 --- a/EsefexApi/sounddb/filedb/deletesound.go +++ b/EsefexApi/sounddb/filedb/deletesound.go @@ -12,14 +12,14 @@ func (f *FileDB) DeleteSound(uid sounddb.SoundUID) error { path := fmt.Sprintf("%s/%s/%s_meta.json", f.location, uid.ServerID, uid.SoundID) err := os.Remove(path) if err != nil { - log.Fatal(err) + log.Println(err) return err } path = fmt.Sprintf("%s/%s/%s_sound.s16le", f.location, uid.ServerID, uid.SoundID) err = os.Remove(path) if err != nil { - log.Fatal(err) + log.Println(err) return err } diff --git a/EsefexApi/sounddb/filedb/filedb_test.go b/EsefexApi/sounddb/filedb/filedb_test.go index a4f6876..b93866c 100644 --- a/EsefexApi/sounddb/filedb/filedb_test.go +++ b/EsefexApi/sounddb/filedb/filedb_test.go @@ -33,7 +33,7 @@ func TestFileDB(t *testing.T) { assert.Nil(t, err) // Test that the sound exists - exists, err := db.soundExists(uid) + exists, err := db.SoundExists(uid) assert.Nil(t, err) assert.True(t, exists) @@ -67,7 +67,7 @@ func TestFileDB(t *testing.T) { assert.Nil(t, err) // Test that the sound doesn't exist - exists, err = db.soundExists(uid) + exists, err = db.SoundExists(uid) assert.Nil(t, err) assert.False(t, exists) diff --git a/EsefexApi/sounddb/filedb/getserverids.go b/EsefexApi/sounddb/filedb/getserverids.go index 590cd52..737081f 100644 --- a/EsefexApi/sounddb/filedb/getserverids.go +++ b/EsefexApi/sounddb/filedb/getserverids.go @@ -9,7 +9,7 @@ import ( func (f *FileDB) GetServerIDs() ([]string, error) { files, err := os.ReadDir(f.location) if err != nil { - log.Fatal(err) + log.Print(err) return nil, err } diff --git a/EsefexApi/sounddb/filedb/getsounduids.go b/EsefexApi/sounddb/filedb/getsounduids.go index 27eeb63..d675737 100644 --- a/EsefexApi/sounddb/filedb/getsounduids.go +++ b/EsefexApi/sounddb/filedb/getsounduids.go @@ -18,7 +18,8 @@ func (f *FileDB) GetSoundUIDs(serverID string) ([]sounddb.SoundUID, error) { files, err := os.ReadDir(path) if err != nil { - log.Fatal(err) + log.Print(err) + return nil, err } uids := make([]sounddb.SoundUID, 0) diff --git a/EsefexApi/sounddb/filedb/internal.go b/EsefexApi/sounddb/filedb/internal.go index bbb10f8..bfb845f 100644 --- a/EsefexApi/sounddb/filedb/internal.go +++ b/EsefexApi/sounddb/filedb/internal.go @@ -3,7 +3,6 @@ package filedb import ( "esefexapi/sounddb" "math/rand" - "slices" "strconv" ) @@ -15,7 +14,7 @@ func (f *FileDB) generateSoundID(serverId string) (string, error) { for { id := strconv.FormatInt(int64(rand.Intn(max-min)+min), 10) - exists, err := f.soundExists(sounddb.SuidFromStrings(serverId, id)) + exists, err := f.SoundExists(sounddb.SuidFromStrings(serverId, id)) if err != nil { return "", err } @@ -25,12 +24,3 @@ func (f *FileDB) generateSoundID(serverId string) (string, error) { } } } - -func (f *FileDB) soundExists(uid sounddb.SoundUID) (bool, error) { - uids, err := f.GetSoundUIDs(uid.ServerID) - if err != nil { - return false, err - } - - return slices.Contains(uids, uid), nil -} diff --git a/EsefexApi/sounddb/filedb/soundexists.go b/EsefexApi/sounddb/filedb/soundexists.go new file mode 100644 index 0000000..04207a3 --- /dev/null +++ b/EsefexApi/sounddb/filedb/soundexists.go @@ -0,0 +1,15 @@ +package filedb + +import ( + "esefexapi/sounddb" + "slices" +) + +func (f *FileDB) SoundExists(uid sounddb.SoundUID) (bool, error) { + uids, err := f.GetSoundUIDs(uid.ServerID) + if err != nil { + return false, err + } + + return slices.Contains(uids, uid), nil +}