diff --git a/internal/audiobookshelf/audiobookshelf_test.go b/internal/audiobookshelf/client_test.go similarity index 100% rename from internal/audiobookshelf/audiobookshelf_test.go rename to internal/audiobookshelf/client_test.go diff --git a/internal/controller/bootController.go b/internal/controller/boot_controller.go similarity index 87% rename from internal/controller/bootController.go rename to internal/controller/boot_controller.go index 3f9c7af..8815067 100644 --- a/internal/controller/bootController.go +++ b/internal/controller/boot_controller.go @@ -5,6 +5,7 @@ import ( "abb_ia/internal/config" "abb_ia/internal/dto" + "abb_ia/internal/github" "abb_ia/internal/logger" "abb_ia/internal/mq" "abb_ia/internal/utils" @@ -57,13 +58,13 @@ func (c *BootController) checkFFmpeg() bool { func (c *BootController) checkNewVersion() { - latestVersion, err := utils.GetLatestVersion(config.Instance().GetRepoOwner(), config.Instance().GetRepoName()) + latestVersion, err := github.GetLatestVersion(config.Instance().GetRepoOwner(), config.Instance().GetRepoName()) if err != nil { logger.Error("Can't check new version: " + err.Error()) return } - result, err := utils.CompareVersions(latestVersion, config.Instance().AppVersion()) + result, err := github.CompareVersions(latestVersion, config.Instance().AppVersion()) if err != nil { logger.Error("Can not compare versions: " + err.Error()) return diff --git a/internal/controller/buildController.go b/internal/controller/build_controller.go similarity index 100% rename from internal/controller/buildController.go rename to internal/controller/build_controller.go diff --git a/internal/controller/chaptersController.go b/internal/controller/chapters_controller.go similarity index 87% rename from internal/controller/chaptersController.go rename to internal/controller/chapters_controller.go index 7748823..0ab1a38 100644 --- a/internal/controller/chaptersController.go +++ b/internal/controller/chapters_controller.go @@ -17,13 +17,6 @@ type ChaptersController struct { stopFlag bool } -/** - * Creates a new ChaptersController instance. - * @param dispatcher - The message queue dispatcher. - * @returns The new ChaptersController instance. - * - * This code is useful for creating a new ChaptersController instance and registering it with the message queue dispatcher. This allows the ChaptersController to receive messages from the message queue and dispatch them to the appropriate handler. - **/ func NewChaptersController(dispatcher *mq.Dispatcher) *ChaptersController { c := &ChaptersController{} c.mq = dispatcher @@ -60,15 +53,6 @@ func (c *ChaptersController) stopChapters(cmd *dto.StopCommand) { logger.Debug(mq.ChaptersController + ": Received StopChapters command") } -/** - * @description Splits an audiobook into parts and chapters. - * @param {dto.ChaptersCreate} cmd - The command to create chapters. - * @returns {void} - * - * This function is useful for splitting an audiobook into parts and chapters. - * It takes in a command object containing the audiobook and then splits the audiobook into parts and chapters. - * It then sends messages to the ChaptersPage and Footer to update the status and busy indicator. - */ func (c *ChaptersController) createChapters(cmd *dto.ChaptersCreate) { logger.Debug(mq.ChaptersController + " received " + cmd.String()) @@ -92,6 +76,7 @@ func (c *ChaptersController) createChapters(cmd *dto.ChaptersCreate) { var fileNo int = 1 var chapterNo int = 1 var offset float64 = 0 + var abSize int64 = 0 var partSize int64 = 0 var partDuration float64 = 0 var partChapters []dto.Chapter = []dto.Chapter{} @@ -102,6 +87,7 @@ func (c *ChaptersController) createChapters(cmd *dto.ChaptersCreate) { mp3, _ := ffmpeg.NewFFProbe(filePath) chapterFiles = append(chapterFiles, dto.Mp3File{Number: fileNo, FileName: file.FileName, Size: mp3.Size(), Duration: mp3.Duration()}) fileNo++ + abSize += mp3.Size() partSize += mp3.Size() partDuration += mp3.Duration() chapter := dto.Chapter{Number: chapterNo, Name: mp3.Title(), Size: mp3.Size(), Duration: mp3.Duration(), Start: offset, End: offset + mp3.Duration(), Files: chapterFiles} diff --git a/internal/controller/cleanupController.go b/internal/controller/cleanup_controller.go similarity index 100% rename from internal/controller/cleanupController.go rename to internal/controller/cleanup_controller.go diff --git a/internal/controller/conductor.go b/internal/controller/conductor.go index 35d8190..72c747a 100644 --- a/internal/controller/conductor.go +++ b/internal/controller/conductor.go @@ -25,7 +25,7 @@ func NewConductor(dispatcher *mq.Dispatcher) *Conductor { c.controllers = append(c.controllers, NewChaptersController(c.dispatcher)) c.controllers = append(c.controllers, NewBuildController(c.dispatcher)) c.controllers = append(c.controllers, NewCopyController(c.dispatcher)) - c.controllers = append(c.controllers, NewAudiobookshelfController(c.dispatcher)) + c.controllers = append(c.controllers, NewUploadController(c.dispatcher)) c.controllers = append(c.controllers, NewCleanupController(c.dispatcher)) c.controllers = append(c.controllers, NewBootController(c.dispatcher)) return c diff --git a/internal/controller/configController.go b/internal/controller/config_controller.go similarity index 100% rename from internal/controller/configController.go rename to internal/controller/config_controller.go diff --git a/internal/controller/copyController.go b/internal/controller/copy_controller.go similarity index 97% rename from internal/controller/copyController.go rename to internal/controller/copy_controller.go index ac9b4e4..de894ff 100644 --- a/internal/controller/copyController.go +++ b/internal/controller/copy_controller.go @@ -86,7 +86,6 @@ func (c *CopyController) dispatchMessage(m *mq.Message) { func (c *CopyController) startCopy(cmd *dto.CopyCommand) { c.startTime = time.Now() logger.Info(mq.CopyController + " received " + cmd.String()) - c.ab = cmd.Audiobook // update part size and whole audiobook size after re-encoding @@ -193,7 +192,7 @@ func (c *CopyController) updateFileCopyProgress(fileId int, fileName string, siz } // sent a message only if progress changed - c.mq.SendMessage(mq.CopyController, mq.BuildPage, &dto.UploadFileProgress{FileId: fileId, FileName: fileName, Percent: percent}, false) + c.mq.SendMessage(mq.CopyController, mq.BuildPage, &dto.CopyFileProgress{FileId: fileId, FileName: fileName, Percent: percent}, false) } c.filesCopy[fileId].fileId = fileId c.filesCopy[fileId].fileSize = size diff --git a/internal/controller/downloadController.go b/internal/controller/download_controller.go similarity index 99% rename from internal/controller/downloadController.go rename to internal/controller/download_controller.go index 7ac74bb..26d6422 100644 --- a/internal/controller/downloadController.go +++ b/internal/controller/download_controller.go @@ -6,7 +6,7 @@ import ( "time" "abb_ia/internal/dto" - "abb_ia/internal/ia_client" + "abb_ia/internal/ia" "abb_ia/internal/logger" "abb_ia/internal/mq" "abb_ia/internal/utils" diff --git a/internal/controller/encodingController.go b/internal/controller/encoding_controller.go similarity index 100% rename from internal/controller/encodingController.go rename to internal/controller/encoding_controller.go diff --git a/internal/controller/searchController.go b/internal/controller/searchController.go deleted file mode 100644 index 225a682..0000000 --- a/internal/controller/searchController.go +++ /dev/null @@ -1,155 +0,0 @@ -package controller - -import ( - "net/url" - "sort" - "strconv" - "strings" - - "abb_ia/internal/config" - "abb_ia/internal/dto" - "abb_ia/internal/ia_client" - "abb_ia/internal/logger" - "abb_ia/internal/mq" - "abb_ia/internal/utils" - "github.com/rivo/tview" -) - -type SearchController struct { - mq *mq.Dispatcher -} - -func NewSearchController(dispatcher *mq.Dispatcher) *SearchController { - c := &SearchController{} - c.mq = dispatcher - c.mq.RegisterListener(mq.SearchController, c.dispatchMessage) - return c -} - -func (c *SearchController) checkMQ() { - m := c.mq.GetMessage(mq.SearchController) - if m != nil { - c.dispatchMessage(m) - } -} - -func (c *SearchController) dispatchMessage(m *mq.Message) { - switch dto := m.Dto.(type) { - case *dto.SearchCommand: - go c.performSearch(dto) - default: - m.UnsupportedTypeError(mq.SearchController) - } -} - -func (c *SearchController) performSearch(cmd *dto.SearchCommand) { - logger.Info(mq.SearchController + " received " + cmd.String()) - c.mq.SendMessage(mq.SearchController, mq.Footer, &dto.UpdateStatus{Message: "Fetching Internet Archive items..."}, false) - c.mq.SendMessage(mq.SearchController, mq.Footer, &dto.SetBusyIndicator{Busy: true}, false) - ia := ia_client.New(config.Instance().GetSearchRowsMax(), config.Instance().IsUseMock(), config.Instance().IsSaveMock()) - resp := ia.Search(cmd.SearchCondition, "audio") - if resp == nil { - logger.Error(mq.SearchController + ": Failed to perform IA search with condition: " + cmd.SearchCondition) - } - - itemsTotal := resp.Response.NumFound - itemsFetched := 0 - - docs := resp.Response.Docs - for _, doc := range docs { - item := &dto.IAItem{} - item.ID = doc.Identifier - item.Title = tview.Escape(doc.Title) - item.IaURL = ia_client.IA_BASE_URL + "/details/" + doc.Identifier - item.LicenseUrl = doc.Licenseurl - - item.AudioFiles = make([]dto.AudioFile, 0) - var totalSize int64 = 0 - var totalLength float64 = 0.0 - d := ia.GetItemDetails(doc.Identifier) - if d != nil { - item.Server = d.Server - item.Dir = d.Dir - if len(doc.Creator) > 0 && d.Metadata.Creator[0] != "" { - item.Creator = doc.Creator[0] - } else if len(d.Metadata.Creator) > 0 && d.Metadata.Creator[0] != "" { - item.Creator = d.Metadata.Creator[0] - } else if len(d.Metadata.Artist) > 0 && d.Metadata.Artist[0] != "" { - item.Creator = d.Metadata.Artist[0] - } else { - item.Creator = "Internet Archive" - } - - if len(d.Metadata.Description) > 0 { - item.Description = tview.Escape(ia.Html2Text(d.Metadata.Description[0])) - } - - for name, metadata := range d.Files { - format := metadata.Format - // collect mp3 files - // TODO: Implement filtering for mp3 files with multiple bitrates (see https://archive.org/details/voyage_moon_1512_librivox for ex.) - if utils.Contains(dto.Mp3Formats, format) { - size, sErr := strconv.ParseInt(metadata.Size, 10, 64) - length, lErr := utils.TimeToSeconds(metadata.Length) - if sErr == nil && lErr == nil { - file := dto.AudioFile{} - file.Name = strings.TrimPrefix(name, "/") - file.Size = size - file.Length = length - file.Format = metadata.Format - totalSize += size - totalLength += length - item.AudioFiles = append(item.AudioFiles, file) - } - } - - // collect image files - if utils.Contains(dto.CoverFormats, format) { - size, err := strconv.ParseInt(metadata.Size, 10, 64) - if err == nil { - file := dto.ImageFile{} - file.Name = strings.TrimPrefix(name, "/") - file.Size = size - file.Format = metadata.Format - item.ImageFiles = append(item.ImageFiles, file) - } - } - } - - // sort mp3 files by name TODO: Check if sort is needed - sort.Slice(item.AudioFiles, func(i, j int) bool { return item.AudioFiles[i].Name < item.AudioFiles[j].Name }) - item.TotalSize = totalSize - item.TotalLength = totalLength - - // if len(d.Misc.Image) > 0 { // _thumb images are too small. Have to collect and sort my size all item images below - // item.CoverUrl = d.Misc.Image - // } - // find biggest image by size (TODO: Need to find better solution. Maybe analyze if the image is colorful?) - if len(item.ImageFiles) > 0 { - biggestImage := item.ImageFiles[0] - for i := 1; i < len(item.ImageFiles); i++ { - if item.ImageFiles[i].Size > biggestImage.Size { - biggestImage = item.ImageFiles[i] - } - } - item.CoverUrl = (&url.URL{Scheme: "https", Host: item.Server, Path: item.Dir + "/" + biggestImage.Name}).String() - } else { - item.CoverUrl = "No cover available!" - } - - if len(item.AudioFiles) > 0 { - itemsFetched++ - sp := &dto.SearchProgress{ItemsTotal: itemsTotal, ItemsFetched: itemsFetched} - c.mq.SendMessage(mq.SearchController, mq.SearchPage, sp, false) - c.mq.SendMessage(mq.SearchController, mq.SearchPage, item, false) - } - } - logger.Debug(mq.SearchController + " fetched first " + strconv.Itoa(itemsFetched) + " items from " + strconv.Itoa(itemsTotal) + " total") - } - c.mq.SendMessage(mq.SearchController, mq.Footer, &dto.SetBusyIndicator{Busy: false}, false) - c.mq.SendMessage(mq.SearchController, mq.Footer, &dto.UpdateStatus{Message: ""}, false) - - if itemsFetched == 0 { - c.mq.SendMessage(mq.SearchController, mq.SearchPage, &dto.NothingFoundError{SearchCondition: cmd.SearchCondition}, false) - } -} diff --git a/internal/controller/search_controller.go b/internal/controller/search_controller.go index 412712b..0764481 100644 --- a/internal/controller/search_controller.go +++ b/internal/controller/search_controller.go @@ -87,7 +87,7 @@ func (c *SearchController) performSearch(cmd *dto.SearchCommand) { for name, metadata := range d.Files { format := metadata.Format // collect mp3 files - // TODO: Implement filtering for mp3 files with multiple bitrates (see https://archive.org/details/voyage_moon_1512_librivox or https://archive.org/details/OTRR_Blair_of_the_Mounties_Singles for ex.) + if utils.Contains(dto.Mp3Formats, format) { size, sErr := strconv.ParseInt(metadata.Size, 10, 64) length, lErr := utils.TimeToSeconds(metadata.Length) @@ -99,12 +99,13 @@ func (c *SearchController) performSearch(cmd *dto.SearchCommand) { if metadata.Title != "" { file.Title = metadata.Title } else { - file.Title = file.Name + file.Title = utils.SanitizeMp3FileName(file.Name) } file.Size = size file.Length = length file.Format = metadata.Format // check if there is a file with the same title but different bitrate. Keep highest bitrate only + // see https://archive.org/details/voyage_moon_1512_librivox or https://archive.org/details/OTRR_Blair_of_the_Mounties_Singles for ex. addNewFile := true for i, oldFile := range item.AudioFiles { if file.Title == oldFile.Title { diff --git a/internal/controller/audiobookShelfController.go b/internal/controller/upload_controller.go similarity index 74% rename from internal/controller/audiobookShelfController.go rename to internal/controller/upload_controller.go index 4dba73e..dda22a1 100644 --- a/internal/controller/audiobookShelfController.go +++ b/internal/controller/upload_controller.go @@ -11,7 +11,7 @@ import ( "abb_ia/internal/utils" ) -type AudiobookshelfController struct { +type UploadController struct { mq *mq.Dispatcher ab *dto.Audiobook startTime time.Time @@ -28,33 +28,33 @@ type fileUpload struct { progress int } -func NewAudiobookshelfController(dispatcher *mq.Dispatcher) *AudiobookshelfController { - c := &AudiobookshelfController{} +func NewUploadController(dispatcher *mq.Dispatcher) *UploadController { + c := &UploadController{} c.mq = dispatcher - c.mq.RegisterListener(mq.AudiobookshelfController, c.dispatchMessage) + c.mq.RegisterListener(mq.UploadController, c.dispatchMessage) return c } -func (c *AudiobookshelfController) checkMQ() { - m := c.mq.GetMessage(mq.AudiobookshelfController) +func (c *UploadController) checkMQ() { + m := c.mq.GetMessage(mq.UploadController) if m != nil { c.dispatchMessage(m) } } -func (c *AudiobookshelfController) dispatchMessage(m *mq.Message) { +func (c *UploadController) dispatchMessage(m *mq.Message) { switch dto := m.Dto.(type) { - case *dto.AudiobookshelfScanCommand: - go c.audiobookshelfScan(dto) - case *dto.AudiobookshelfUploadCommand: - go c.uploadAudiobook(dto) + case *dto.AbsScanCommand: + go c.absScan(dto) + case *dto.AbsUploadCommand: + go c.absUpload(dto) default: - m.UnsupportedTypeError(mq.AudiobookshelfController) + m.UnsupportedTypeError(mq.UploadController) } } -func (c *AudiobookshelfController) audiobookshelfScan(cmd *dto.AudiobookshelfScanCommand) { - logger.Info(mq.AudiobookshelfController + " received " + cmd.String()) +func (c *UploadController) absScan(cmd *dto.AbsScanCommand) { + logger.Info(mq.UploadController + " received " + cmd.String()) ab := cmd.Audiobook url := ab.Config.GetAudiobookshelfUrl() username := ab.Config.GetAudiobookshelfUser() @@ -89,11 +89,12 @@ func (c *AudiobookshelfController) audiobookshelfScan(cmd *dto.AudiobookshelfSca } logger.Info("A scan launched for library " + libraryName + " on audiobookshlf server") } - c.mq.SendMessage(mq.AudiobookshelfController, mq.BuildPage, &dto.ScanComplete{Audiobook: cmd.Audiobook}, true) + c.mq.SendMessage(mq.UploadController, mq.BuildPage, &dto.ScanComplete{Audiobook: cmd.Audiobook}, true) } -func (c *AudiobookshelfController) uploadAudiobook(cmd *dto.AudiobookshelfUploadCommand) { - logger.Info(mq.AudiobookshelfController + " received " + cmd.String()) +func (c *UploadController) absUpload(cmd *dto.AbsUploadCommand) { + c.startTime = time.Now() + logger.Info(mq.UploadController + " received " + cmd.String()) c.ab = cmd.Audiobook url := c.ab.Config.GetAudiobookshelfUrl() username := c.ab.Config.GetAudiobookshelfUser() @@ -129,16 +130,15 @@ func (c *AudiobookshelfController) uploadAudiobook(cmd *dto.AudiobookshelfUpload c.filesUpload = make([]fileUpload, len(c.ab.Parts)) go c.updateTotalUploadProgress() err = absClient.UploadBook(c.ab, libraryID, folderID, c.updateFileUplodProgress) - if err != nil { logger.Error("Can't upload the audiobook to audiobookshelf server: " + err.Error()) } c.stopFlag = true } - c.mq.SendMessage(mq.AudiobookshelfController, mq.BuildPage, &dto.UploadComplete{Audiobook: cmd.Audiobook}, true) + c.mq.SendMessage(mq.UploadController, mq.BuildPage, &dto.UploadComplete{Audiobook: cmd.Audiobook}, true) } -func (c *AudiobookshelfController) updateFileUplodProgress(fileId int, fileName string, size int64, pos int64, percent int) { +func (c *UploadController) updateFileUplodProgress(fileId int, fileName string, size int64, pos int64, percent int) { if c.filesUpload[fileId].progress != percent { // wrong calculation protection @@ -149,7 +149,7 @@ func (c *AudiobookshelfController) updateFileUplodProgress(fileId int, fileName } // sent a message only if progress changed - c.mq.SendMessage(mq.AudiobookshelfController, mq.BuildPage, &dto.UploadFileProgress{FileId: fileId, FileName: fileName, Percent: percent}, false) + c.mq.SendMessage(mq.UploadController, mq.BuildPage, &dto.UploadFileProgress{FileId: fileId, FileName: fileName, Percent: percent}, false) } c.filesUpload[fileId].fileId = fileId c.filesUpload[fileId].fileSize = size @@ -157,7 +157,7 @@ func (c *AudiobookshelfController) updateFileUplodProgress(fileId int, fileName c.filesUpload[fileId].progress = percent } -func (c *AudiobookshelfController) updateTotalUploadProgress() { +func (c *UploadController) updateTotalUploadProgress() { var percent int = -1 for !c.stopFlag && percent <= 100 { @@ -206,7 +206,7 @@ func (c *AudiobookshelfController) updateTotalUploadProgress() { speedH := utils.SpeedToHuman(speed) etaH := utils.SecondsToTime(eta) - c.mq.SendMessage(mq.AudiobookshelfController, mq.BuildPage, &dto.UploadProgress{Elapsed: elapsedH, Percent: percent, Files: filesH, Bytes: bytesH, Speed: speedH, ETA: etaH}, false) + c.mq.SendMessage(mq.UploadController, mq.BuildPage, &dto.UploadProgress{Elapsed: elapsedH, Percent: percent, Files: filesH, Bytes: bytesH, Speed: speedH, ETA: etaH}, false) } time.Sleep(mq.PullFrequency) } diff --git a/internal/dto/audiobookshelf.go b/internal/dto/audiobookshelf.go index ef7bb98..9ddad1d 100644 --- a/internal/dto/audiobookshelf.go +++ b/internal/dto/audiobookshelf.go @@ -2,12 +2,12 @@ package dto import "fmt" -type AudiobookshelfScanCommand struct { +type AbsScanCommand struct { Audiobook *Audiobook } -func (c *AudiobookshelfScanCommand) String() string { - return fmt.Sprintf("AudiobookshelfScanCommand: %s", c.Audiobook.String()) +func (c *AbsScanCommand) String() string { + return fmt.Sprintf("AbsScanCommand: %s", c.Audiobook.String()) } type ScanComplete struct { @@ -18,12 +18,12 @@ func (c *ScanComplete) String() string { return fmt.Sprintf("ScanComplete: %s", c.Audiobook.String()) } -type AudiobookshelfUploadCommand struct { +type AbsUploadCommand struct { Audiobook *Audiobook } -func (c *AudiobookshelfUploadCommand) String() string { - return fmt.Sprintf("AudiobookshelfUploadCommand: %s", c.Audiobook.String()) +func (c *AbsUploadCommand) String() string { + return fmt.Sprintf("AbsUploadCommand: %s", c.Audiobook.String()) } type UploadFileProgress struct { diff --git a/internal/dto/ia.go b/internal/dto/ia.go index bdba209..63f6eb4 100644 --- a/internal/dto/ia.go +++ b/internal/dto/ia.go @@ -24,6 +24,7 @@ func (i *IAItem) String() string { type AudioFile struct { Name string + Title string Format string Length float64 Size int64 diff --git a/internal/dto/search.go b/internal/dto/search.go index f215637..78799ad 100644 --- a/internal/dto/search.go +++ b/internal/dto/search.go @@ -6,7 +6,7 @@ import "fmt" var Mp3Formats = []string{"16Kbps MP3", "24Kbps MP3", "32Kbps MP3", "40Kbps MP3", "48Kbps MP3", "56Kbps MP3", "64Kbps MP3", "80Kbps MP3", "96Kbps MP3", "112Kbps MP3", "128Kbps MP3", "144Kbps MP3", "160Kbps MP3", "224Kbps MP3", "256Kbps MP3", "320Kbps MP3", "VBR MP3"} // -var CoverFormats = []string{"JPEG"} +var CoverFormats = []string{"JPEG", "JPEG Thumb"} type SearchCommand struct { SearchCondition string diff --git a/internal/utils/github.go b/internal/github/utils.go similarity index 98% rename from internal/utils/github.go rename to internal/github/utils.go index 9fc9a95..e35eb13 100644 --- a/internal/utils/github.go +++ b/internal/github/utils.go @@ -1,4 +1,4 @@ -package utils +package github import ( "encoding/json" diff --git a/internal/ia_client/ia_client.go b/internal/ia/client.go similarity index 100% rename from internal/ia_client/ia_client.go rename to internal/ia/client.go diff --git a/internal/ia_client/is_client_test.go b/internal/ia/client_test.go similarity index 98% rename from internal/ia_client/is_client_test.go rename to internal/ia/client_test.go index f45e9a4..6f059fa 100644 --- a/internal/ia_client/is_client_test.go +++ b/internal/ia/client_test.go @@ -7,7 +7,7 @@ import ( "testing" "abb_ia/internal/config" - "abb_ia/internal/ia_client" + "abb_ia/internal/ia" "abb_ia/internal/logger" "github.com/stretchr/testify/assert" ) diff --git a/internal/ia_client/ia_model.go b/internal/ia/model.go similarity index 100% rename from internal/ia_client/ia_model.go rename to internal/ia/model.go diff --git a/internal/ia_client/ia_client_utils.go b/internal/ia/utils.go similarity index 100% rename from internal/ia_client/ia_client_utils.go rename to internal/ia/utils.go diff --git a/internal/mq/recepients.go b/internal/mq/recepients.go index b5659a9..31f784c 100644 --- a/internal/mq/recepients.go +++ b/internal/mq/recepients.go @@ -2,25 +2,25 @@ package mq // MQ recipients const ( - TUI = "TUI" - Frame = "Frame" - Header = "Header" - Footer = "Footer" - DialogWindow = "DialogWindow" - SearchPage = "SearchPage" - ConfigPage = "ConfigPage" - DownloadPage = "DownloadPage" - EncodingPage = "EncodingPage" - ChaptersPage = "ChaptersPage" - BuildPage = "BuildPage" - BootController = "BootController" - SearchController = "SearchController" - ConfigController = "ConfigController" - DownloadController = "DownloadController" - EncodingController = "EncodingController" - ChaptersController = "ChaptersController" - BuildController = "BuildController" - CopyController = "CopyController" - CleanupController = "CleanupController" - AudiobookshelfController = "AudiobookshelfController" + TUI = "TUI" + Frame = "Frame" + Header = "Header" + Footer = "Footer" + DialogWindow = "DialogWindow" + SearchPage = "SearchPage" + ConfigPage = "ConfigPage" + DownloadPage = "DownloadPage" + EncodingPage = "EncodingPage" + ChaptersPage = "ChaptersPage" + BuildPage = "BuildPage" + BootController = "BootController" + SearchController = "SearchController" + ConfigController = "ConfigController" + DownloadController = "DownloadController" + EncodingController = "EncodingController" + ChaptersController = "ChaptersController" + BuildController = "BuildController" + CopyController = "CopyController" + CleanupController = "CleanupController" + UploadController = "UploadController" ) diff --git a/internal/ui/buildPage.go b/internal/ui/build_page.go similarity index 62% rename from internal/ui/buildPage.go rename to internal/ui/build_page.go index 41e7e31..a347798 100644 --- a/internal/ui/buildPage.go +++ b/internal/ui/build_page.go @@ -9,6 +9,7 @@ import ( "abb_ia/internal/dto" "abb_ia/internal/mq" "abb_ia/internal/utils" + "github.com/gdamore/tcell/v2" "github.com/rivo/tview" ) @@ -17,10 +18,13 @@ type BuildPage struct { mq *mq.Dispatcher grid *tview.Grid infoPanel *infoPanel + infoSection *tview.Grid buildSection *tview.Grid copySection *tview.Grid + uploadSection *tview.Grid buildTable *table copyTable *table + uploadTable *table progressTable *table progressSection *tview.Grid } @@ -31,7 +35,7 @@ func newBuildPage(dispatcher *mq.Dispatcher) *BuildPage { p.mq.RegisterListener(mq.BuildPage, p.dispatchMessage) p.grid = tview.NewGrid() - p.grid.SetRows(7, -1, -1, 4) + p.grid.SetRows(7, -1, -1, -1, 4) p.grid.SetColumns(0) // Ignore mouse events when the grid has no focus @@ -44,19 +48,19 @@ func newBuildPage(dispatcher *mq.Dispatcher) *BuildPage { }) // book info section - infoSection := tview.NewGrid() - infoSection.SetColumns(-2, -1) - infoSection.SetBorder(true) - infoSection.SetTitle(" Audiobook information: ") - infoSection.SetTitleAlign(tview.AlignLeft) + p.infoSection = tview.NewGrid() + p.infoSection.SetColumns(-2, -1) + p.infoSection.SetBorder(true) + p.infoSection.SetTitle(" Audiobook information: ") + p.infoSection.SetTitleAlign(tview.AlignLeft) p.infoPanel = newInfoPanel() - infoSection.AddItem(p.infoPanel.t, 0, 0, 1, 1, 0, 0, true) + p.infoSection.AddItem(p.infoPanel.t, 0, 0, 1, 1, 0, 0, true) f := newForm() f.SetHorizontal(false) f.f.SetButtonsAlign(tview.AlignRight) f.AddButton("Stop", p.stopConfirmation) - infoSection.AddItem(f.f, 0, 1, 1, 1, 0, 0, false) - p.grid.AddItem(infoSection, 0, 0, 1, 1, 0, 0, false) + p.infoSection.AddItem(f.f, 0, 1, 1, 1, 0, 0, false) + p.grid.AddItem(p.infoSection, 0, 0, 1, 1, 0, 0, false) // audiobook build section p.buildSection = tview.NewGrid() @@ -64,7 +68,6 @@ func newBuildPage(dispatcher *mq.Dispatcher) *BuildPage { p.buildSection.SetTitle(" Building audiobook... ") p.buildSection.SetTitleAlign(tview.AlignLeft) p.buildSection.SetBorder(true) - p.buildTable = newTable() p.buildTable.setHeaders(" # ", "File name", "Format", "Duration", "Total Size", "Build progress") p.buildTable.setWeights(1, 2, 1, 1, 1, 5) @@ -75,30 +78,41 @@ func newBuildPage(dispatcher *mq.Dispatcher) *BuildPage { // copy section p.copySection = tview.NewGrid() p.copySection.SetColumns(-1) - p.copySection.SetTitle(" Output directory copy progress: ") + p.copySection.SetTitle(" Copying the book to the output directory: ") p.copySection.SetTitleAlign(tview.AlignLeft) p.copySection.SetBorder(true) - p.copyTable = newTable() - p.copyTable.setHeaders(" # ", "File name", "Format", "Duration", "Total Size", "Copy progress") + p.copyTable.setHeaders(" # ", "File name", "Format", "Duration", "Total Size", "Copy Progress") p.copyTable.setWeights(1, 2, 1, 1, 1, 5) p.copyTable.setAlign(tview.AlignRight, tview.AlignLeft, tview.AlignLeft, tview.AlignRight, tview.AlignRight, tview.AlignLeft) p.copySection.AddItem(p.copyTable.t, 0, 0, 1, 1, 0, 0, true) p.grid.AddItem(p.copySection, 2, 0, 1, 1, 0, 0, true) - // build progress section - progressSection := tview.NewGrid() - progressSection.SetColumns(-1) - progressSection.SetBorder(true) - progressSection.SetTitle(" Build progress: ") - progressSection.SetTitleAlign(tview.AlignLeft) + // upload section + p.uploadSection = tview.NewGrid() + p.uploadSection.SetColumns(-1) + p.uploadSection.SetTitle(" Uploading the book to Audiobookshelf server: ") + p.uploadSection.SetTitleAlign(tview.AlignLeft) + p.uploadSection.SetBorder(true) + p.uploadTable = newTable() + p.uploadTable.setHeaders(" # ", "File name", "Format", "Duration", "Total Size", "Upload Progress") + p.uploadTable.setWeights(1, 2, 1, 1, 1, 5) + p.uploadTable.setAlign(tview.AlignRight, tview.AlignLeft, tview.AlignLeft, tview.AlignRight, tview.AlignRight, tview.AlignLeft) + p.uploadSection.AddItem(p.uploadTable.t, 0, 0, 1, 1, 0, 0, true) + p.grid.AddItem(p.uploadSection, 3, 0, 1, 1, 0, 0, true) + + // total progress section + p.progressSection = tview.NewGrid() + p.progressSection.SetColumns(-1) + p.progressSection.SetBorder(true) + p.progressSection.SetTitle(" Build progress: ") + p.progressSection.SetTitleAlign(tview.AlignLeft) p.progressTable = newTable() p.progressTable.setWeights(1) p.progressTable.setAlign(tview.AlignLeft) p.progressTable.t.SetSelectable(false, false) - progressSection.AddItem(p.progressTable.t, 0, 0, 1, 1, 0, 0, false) - p.grid.AddItem(progressSection, 3, 0, 1, 1, 0, 0, false) - p.progressSection = progressSection + p.progressSection.AddItem(p.progressTable.t, 0, 0, 1, 1, 0, 0, false) + p.grid.AddItem(p.progressSection, 4, 0, 1, 1, 0, 0, false) return p } @@ -120,10 +134,14 @@ func (p *BuildPage) dispatchMessage(m *mq.Message) { p.updateTotalBuildProgress(dto) case *dto.BuildComplete: p.buildComplete(dto) - case *dto.UploadFileProgress: + case *dto.CopyFileProgress: p.updateFileCopyProgress(dto) case *dto.CopyProgress: p.updateTotalCopyProgress(dto) + case *dto.UploadFileProgress: + p.updateFileUploadProgress(dto) + case *dto.UploadProgress: + p.updateTotalUploadProgress(dto) case *dto.CopyComplete: p.copyComplete(dto) case *dto.UploadComplete: @@ -138,6 +156,30 @@ func (p *BuildPage) dispatchMessage(m *mq.Message) { } func (p *BuildPage) displayBookInfo(ab *dto.Audiobook) { + + // dynamic grid layout generation + p.grid.Clear() + p.grid.SetColumns(0) + p.grid.SetRows(7, -1, 4) + p.grid.AddItem(p.infoSection, 0, 0, 1, 1, 0, 0, false) + p.grid.AddItem(p.buildSection, 1, 0, 1, 1, 0, 0, true) + if ab.Config.IsCopyToOutputDir() && ab.Config.IsUploadToAudiobookshef() { + p.grid.SetRows(7, -1, -1, -1, 4) + p.grid.AddItem(p.copySection, 2, 0, 1, 1, 0, 0, true) + p.grid.AddItem(p.uploadSection, 3, 0, 1, 1, 0, 0, true) + p.grid.AddItem(p.progressSection, 4, 0, 1, 1, 0, 0, false) + } else if ab.Config.IsCopyToOutputDir() { + p.grid.SetRows(7, -1, -1, 4) + p.grid.AddItem(p.copySection, 2, 0, 1, 1, 0, 0, true) + p.grid.AddItem(p.progressSection, 3, 0, 1, 1, 0, 0, false) + } else if ab.Config.IsUploadToAudiobookshef() { + p.grid.SetRows(7, -1, -1, 4) + p.grid.AddItem(p.uploadSection, 2, 0, 1, 1, 0, 0, true) + p.grid.AddItem(p.progressSection, 3, 0, 1, 1, 0, 0, false) + } else { + p.grid.AddItem(p.progressSection, 2, 0, 1, 1, 0, 0, false) + } + p.infoPanel.clear() // p.infoPanel.appendRow("", "") p.infoPanel.appendRow("Title:", ab.Title) @@ -153,14 +195,20 @@ func (p *BuildPage) displayBookInfo(ab *dto.Audiobook) { } p.buildTable.ScrollToBeginning() - if ab.Config.IsCopyToOutputDir() { - p.copyTable.clear() - p.copyTable.showHeader() - for i, part := range ab.Parts { - p.copyTable.appendRow(" "+strconv.Itoa(i+1)+" ", filepath.Base(part.M4BFile), part.Format, utils.SecondsToTime(part.Duration), utils.BytesToHuman(part.Size), "") - } - p.copyTable.ScrollToBeginning() + p.copyTable.clear() + p.copyTable.showHeader() + for i, part := range ab.Parts { + p.copyTable.appendRow(" "+strconv.Itoa(i+1)+" ", filepath.Base(part.M4BFile), part.Format, utils.SecondsToTime(part.Duration), utils.BytesToHuman(part.Size), "") } + p.copyTable.ScrollToBeginning() + + p.uploadTable.clear() + p.uploadTable.showHeader() + for i, part := range ab.Parts { + p.uploadTable.appendRow(" "+strconv.Itoa(i+1)+" ", filepath.Base(part.M4BFile), part.Format, utils.SecondsToTime(part.Duration), utils.BytesToHuman(part.Size), "") + } + p.uploadTable.ScrollToBeginning() + p.mq.SendMessage(mq.BuildPage, mq.TUI, &dto.SetFocusCommand{Primitive: p.buildTable.t}, true) p.mq.SendMessage(mq.BuildPage, mq.TUI, &dto.DrawCommand{Primitive: nil}, true) } @@ -172,7 +220,7 @@ func (p *BuildPage) stopConfirmation() { func (p *BuildPage) stopBuild() { // Stop the build here p.mq.SendMessage(mq.BuildPage, mq.BuildController, &dto.StopCommand{Process: "Build", Reason: "User request"}, false) - p.mq.SendMessage(mq.BuildPage, mq.Frame, &dto.SwitchToPageCommand{Name: "SearchPage"}, false) + p.switchToSearch() } func (p *BuildPage) updateFileBuildProgress(dp *dto.BuildFileProgress) { @@ -211,7 +259,7 @@ func (p *BuildPage) updateTotalBuildProgress(dp *dto.BuildProgress) { p.mq.SendMessage(mq.BuildPage, mq.TUI, &dto.DrawCommand{Primitive: nil}, false) } -func (p *BuildPage) updateFileCopyProgress(dp *dto.UploadFileProgress) { +func (p *BuildPage) updateFileCopyProgress(dp *dto.CopyFileProgress) { // update file progress col := 5 w := p.copyTable.getColumnWidth(col) - 3 @@ -248,8 +296,45 @@ func (p *BuildPage) updateTotalCopyProgress(dp *dto.CopyProgress) { p.mq.SendMessage(mq.BuildPage, mq.TUI, &dto.DrawCommand{Primitive: nil}, false) } +func (p *BuildPage) updateFileUploadProgress(dp *dto.UploadFileProgress) { + // update file progress + col := 5 + w := p.uploadTable.getColumnWidth(col) - 3 + progressText := fmt.Sprintf(" %3d%% ", dp.Percent) + barWidth := int((float32((w - len(progressText))) * float32(dp.Percent) / 100)) + progressBar := strings.Repeat("━", barWidth) + strings.Repeat(" ", w-len(progressText)-barWidth) + cell := p.uploadTable.t.GetCell(dp.FileId+1, col) + // cell.SetExpansion(0) + // cell.SetMaxWidth(50) + cell.Text = fmt.Sprintf("%s |%s|", progressText, progressBar) + // p.uploadTable.t.Select(dp.FileId+1, col) + p.mq.SendMessage(mq.BuildPage, mq.TUI, &dto.DrawCommand{Primitive: nil}, false) +} + +func (p *BuildPage) updateTotalUploadProgress(dp *dto.UploadProgress) { + if p.progressTable.GetRowCount() == 0 { + for i := 0; i < 2; i++ { + p.progressTable.appendRow("") + } + } + p.progressSection.SetTitle(" Upload progress: ") + infoCell := p.progressTable.t.GetCell(0, 0) + progressCell := p.progressTable.t.GetCell(1, 0) + infoCell.Text = fmt.Sprintf(" [yellow]Time elapsed: [white]%10s | [yellow]Files: [white]%10s | [yellow]Speed: [white]%12s | [yellow]ETA: [white]%10s", dp.Elapsed, dp.Files, dp.Speed, dp.ETA) + + col := 0 + w := p.progressTable.getColumnWidth(col) - 5 + progressText := fmt.Sprintf(" %3d%% ", dp.Percent) + barWidth := int((float32((w - len(progressText))) * float32(dp.Percent) / 100)) + progressBar := strings.Repeat("▒", barWidth) + strings.Repeat(" ", w-len(progressText)-barWidth) + // progressCell.SetExpansion(0) + // progressCell.SetMaxWidth(0) + progressCell.Text = fmt.Sprintf("%s |%s|", progressText, progressBar) + p.mq.SendMessage(mq.BuildPage, mq.TUI, &dto.DrawCommand{Primitive: nil}, false) +} + /* - * A chain of final operations -> ?Copy -> ?Upload -> ?Scan -> Cleanup - Done msg + * A chain of final operations: ?Copy -> ?Upload -> ?Scan -> Cleanup - Done msg */ func (p *BuildPage) buildComplete(c *dto.BuildComplete) { // copy the book to Output directory if needed @@ -265,9 +350,9 @@ func (p *BuildPage) copyComplete(c *dto.CopyComplete) { // upload the book to Audiobookshelf server if needed ab := c.Audiobook if ab.Config.IsUploadToAudiobookshef() { - p.mq.SendMessage(mq.BuildPage, mq.AudiobookshelfController, &dto.AudiobookshelfUploadCommand{Audiobook: ab}, true) + p.mq.SendMessage(mq.BuildPage, mq.UploadController, &dto.AbsUploadCommand{Audiobook: ab}, true) } else { - p.mq.SendMessage(mq.BuildPage, mq.BuildPage, &dto.AudiobookshelfScanCommand{Audiobook: ab}, true) + p.mq.SendMessage(mq.BuildPage, mq.BuildPage, &dto.UploadComplete{Audiobook: ab}, true) } } @@ -275,7 +360,7 @@ func (p *BuildPage) uploadComplete(c *dto.UploadComplete) { // launch Audiobookshelf library scan if needed ab := c.Audiobook if ab.Config.IsScanAudiobookshef() { - p.mq.SendMessage(mq.BuildPage, mq.AudiobookshelfController, &dto.AudiobookshelfScanCommand{Audiobook: c.Audiobook}, true) + p.mq.SendMessage(mq.BuildPage, mq.UploadController, &dto.AbsScanCommand{Audiobook: c.Audiobook}, true) } else { p.mq.SendMessage(mq.BuildPage, mq.BuildPage, &dto.ScanComplete{Audiobook: ab}, true) } @@ -291,6 +376,9 @@ func (p *BuildPage) cleanupComplete(c *dto.CleanupComplete) { } func (p *BuildPage) bookReadyMgs(ab *dto.Audiobook) { - newMessageDialog(p.mq, "Build Complete", "Audiobook has been created", p.buildSection) - //p.mq.SendMessage(mq.BuildPage, mq.Frame, &dto.SwitchToPageCommand{Name: "SearchPage"}, false) + newMessageDialog(p.mq, "Build Complete", "Audiobook has been created", p.buildSection, p.switchToSearch) +} + +func (p *BuildPage) switchToSearch() { + p.mq.SendMessage(mq.BuildPage, mq.Frame, &dto.SwitchToPageCommand{Name: "SearchPage"}, false) } diff --git a/internal/ui/chaptersPage.go b/internal/ui/chapters_page.go similarity index 100% rename from internal/ui/chaptersPage.go rename to internal/ui/chapters_page.go diff --git a/internal/ui/configPage.go b/internal/ui/config_page.go similarity index 99% rename from internal/ui/configPage.go rename to internal/ui/config_page.go index 02a6da8..1679411 100644 --- a/internal/ui/configPage.go +++ b/internal/ui/config_page.go @@ -182,7 +182,7 @@ func (p *ConfigPage) displayConfig(c *dto.DisplayConfigCommand) { p.maxFileSize.SetText(utils.ToString(p.configCopy.GetMaxFileSizeMb())) p.shortenTitles.SetChecked(p.configCopy.IsShortenTitle()) - p.uploadToAudiobookshelf.SetChecked(p.configCopy.IsCopyToOutputDir()) + p.uploadToAudiobookshelf.SetChecked(p.configCopy.IsUploadToAudiobookshef()) p.audiobookshelfUrl.SetText(p.configCopy.GetAudiobookshelfUrl()) p.audiobookshelfLibrary.SetText(p.configCopy.GetAudiobookshelfLibrary()) p.scanAudiobookshelf.SetChecked(p.configCopy.IsScanAudiobookshef()) diff --git a/internal/ui/dialog.go b/internal/ui/dialog.go index 14067f6..941b83b 100644 --- a/internal/ui/dialog.go +++ b/internal/ui/dialog.go @@ -90,7 +90,8 @@ func (d *dialogWindow) setFormAttributes() { d.form.SetButtonsAlign(tview.AlignCenter) } -func newMessageDialog(dispatcher *mq.Dispatcher, title string, message string, focus tview.Primitive) { +type OkFunc func() +func newMessageDialog(dispatcher *mq.Dispatcher, title string, message string, focus tview.Primitive, okFunc OkFunc) { d := newDialogWindow(dispatcher, 12, 80, focus) f := newForm() f.SetTitle(title) @@ -102,6 +103,7 @@ func newMessageDialog(dispatcher *mq.Dispatcher, title string, message string, f tv.SetTextAlign(tview.AlignCenter) f.AddFormItem(tv) f.AddButton("Ok", func() { + okFunc() d.Close() }) d.setForm(f.f) @@ -109,7 +111,6 @@ func newMessageDialog(dispatcher *mq.Dispatcher, title string, message string, f } type YesNoFunc func() - func newYesNoDialog(dispatcher *mq.Dispatcher, title string, message string, focus tview.Primitive, yesFunc YesNoFunc, noFunc YesNoFunc) { d := newDialogWindow(dispatcher, 11, 60, focus) f := newForm() diff --git a/internal/ui/downloadPage.go b/internal/ui/download_page.go similarity index 100% rename from internal/ui/downloadPage.go rename to internal/ui/download_page.go diff --git a/internal/ui/encodingPage.go b/internal/ui/encoding_page.go similarity index 100% rename from internal/ui/encodingPage.go rename to internal/ui/encoding_page.go diff --git a/internal/ui/footer.go b/internal/ui/footer.go index 9cfd6cd..f2a6ecc 100644 --- a/internal/ui/footer.go +++ b/internal/ui/footer.go @@ -79,7 +79,7 @@ func (f *footer) toggleBusyIndicator(c *dto.SetBusyIndicator) { f.busyFlag = true f.busyIndicator.SetTextColor(busyIndicatorFgColor) f.busyIndicator.SetBackgroundColor(busyIndicatorBgColor) - f.busyIndicator.SetText(" Busy > ") + f.busyIndicator.SetText(" Busy> ") go f.updateBusyIndicator() } else { f.busyFlag = false diff --git a/internal/ui/searchPage.go b/internal/ui/search_page.go similarity index 98% rename from internal/ui/searchPage.go rename to internal/ui/search_page.go index c87facb..601a2ed 100644 --- a/internal/ui/searchPage.go +++ b/internal/ui/search_page.go @@ -203,7 +203,7 @@ func (p *SearchPage) createBook() { // get selectet row from the results table row, _ := p.resultTable.t.GetSelection() if row <= 0 || len(p.searchResult) <= 0 || len(p.searchResult) < row { - newMessageDialog(p.mq, "Error", "Please perform a search first", p.searchSection) + newMessageDialog(p.mq, "Error", "Please perform a search first", p.searchSection, func() {}) } else { item := p.searchResult[row-1] // create new audiobook object @@ -248,7 +248,7 @@ func (p *SearchPage) showNothingFoundError(dto *dto.NothingFoundError) { newMessageDialog(p.mq, "Error", "No results were found for your search term: [darkblue]'"+dto.SearchCondition+"'[black].\n"+ "Please revise your search criteria.", - p.searchSection) + p.searchSection, func() {}) } func (p *SearchPage) showFFMPEGNotFoundError(dto *dto.FFMPEGNotFoundError) { @@ -256,7 +256,7 @@ func (p *SearchPage) showFFMPEGNotFoundError(dto *dto.FFMPEGNotFoundError) { "This application requires the utilities [darkblue]ffmpeg[black] and [darkblue]ffprobe[black].\n"+ "Please install both [darkblue]ffmpeg[black] and [darkblue]ffprobe[black] by following the instructions provided on FFMPEG website\n"+ "[darkblue]https://ffmpeg.org/download.html", - p.searchSection) + p.searchSection, func() {}) } func (p *SearchPage) showNewVersionMessage(dto *dto.NewAppVersionFound) { @@ -264,5 +264,5 @@ func (p *SearchPage) showNewVersionMessage(dto *dto.NewAppVersionFound) { "New version of the application has been released: [darkblue]"+dto.NewVersion+"[black]\n"+ "Your current version is [darkblue]"+dto.CurrentVersion+"[black]\n"+ "You can download the new version of the application from:\n[darkblue]https://abb_ia/releases", - p.searchSection) + p.searchSection, func() {}) } diff --git a/internal/utils/filepath.go b/internal/utils/filepath.go index b28e73b..0b5f8e7 100644 --- a/internal/utils/filepath.go +++ b/internal/utils/filepath.go @@ -11,7 +11,7 @@ func SanitizeFilePath(path string) string { {"!", "."}, {"?", "."}, {"…", ""}, - {"#","N"}, + {"#", "N"}, {"[", ""}, {"]", ""}, } @@ -32,3 +32,41 @@ func SanitizeFilePath(path string) string { } return path } + +// TODO: Refactor this (regex?) +func SanitizeMp3FileName(fileName string) string { + replacements := [][2]string{ + {"_64kb", ""}, + {"_24kb", ""}, + {"_32kb", ""}, + {"_40kb", ""}, + {"_48kb", ""}, + {"_56kb", ""}, + {"_64kb", ""}, + {"_80kb", ""}, + {"_96kb", ""}, + {"_112kb", ""}, + {"_128kb", ""}, + {"_144kb", ""}, + {"_160kb", ""}, + {"_224kb", ""}, + {"_256kb", ""}, + {"_320kb", ""}, + {"_vbr", ""}, + } + + for { + found := false + for _, row := range replacements { + old := row[0] + new := row[1] + if strings.Contains(fileName, old) { + fileName = strings.ReplaceAll(fileName, old, new) + } + } + if !found { + break + } + } + return fileName +} diff --git a/internal/utils/misc.go b/internal/utils/misc.go index d190b85..1207bce 100644 --- a/internal/utils/misc.go +++ b/internal/utils/misc.go @@ -28,6 +28,11 @@ func GetIndex(s []string, str string) int { return -1 } +// Remove an element from a slice +func RemoveElement(slice []interface{}, index int) []interface{} { + return append(slice[:index], slice[index+1:]...) +} + func AddSpaces(list []string) []string { var output []string for _, v := range list { diff --git a/tools/check_error.go b/tools/check_error.go deleted file mode 100644 index a848876..0000000 --- a/tools/check_error.go +++ /dev/null @@ -1,10 +0,0 @@ -package tools - -import "abb_ia/internal/logger" - -func CheckError(e error) { - if e != nil { - logger.Error(e.Error()) - panic(e) - } -}