diff --git a/app.go b/app.go index 51e0d14..d0e1c77 100644 --- a/app.go +++ b/app.go @@ -2,20 +2,23 @@ package main import ( "context" + slog "log/slog" "github.com/marcus-crane/october/backend" - "github.com/sirupsen/logrus" ) // App struct type App struct { ctx context.Context + logger *slog.Logger portable bool } // NewApp creates a new App application struct -func NewApp(portable bool) *App { +func NewApp(portable bool, logger *slog.Logger) *App { + logger.Debug("Initialising app struct") return &App{ + logger: logger, portable: portable, } } @@ -23,17 +26,16 @@ func NewApp(portable bool) *App { // startup is called when the app starts. The context is saved // so we can call the runtime methods func (a *App) startup(ctx context.Context) { + a.logger.Debug("Calling app startup method") a.ctx = ctx } func (a *App) domReady(ctx context.Context) { + a.logger.Debug("Calling app domReady method") a.ctx = ctx - backend.StartLogger(a.portable) - logrus.WithContext(ctx).Info("Logger should be initialised now") - logrus.WithContext(ctx).Info("Backend is about to start up") } func (a *App) shutdown(ctx context.Context) { - logrus.WithContext(ctx).Info("Shutting down. Goodbye!") + a.logger.Debug("Calling app shutdown method") backend.CloseLogFile() } diff --git a/backend/db.go b/backend/db.go index 5b072f5..832468f 100644 --- a/backend/db.go +++ b/backend/db.go @@ -2,7 +2,6 @@ package backend import ( "github.com/glebarez/sqlite" - "github.com/sirupsen/logrus" "gorm.io/gorm" ) @@ -11,7 +10,6 @@ var Conn *gorm.DB func OpenConnection(filepath string) error { conn, err := gorm.Open(sqlite.Open(filepath), &gorm.Config{}) if err != nil { - logrus.WithError(err).WithField("filepath", filepath).Error("Failed to open DB connection") return err } Conn = conn diff --git a/backend/device.go b/backend/device.go index 71de1fa..5c755a2 100644 --- a/backend/device.go +++ b/backend/device.go @@ -2,10 +2,9 @@ package backend import ( "fmt" + "log/slog" "strings" - "github.com/sirupsen/logrus" - "github.com/pgaskin/koboutils/v2/kobo" ) @@ -155,14 +154,20 @@ func (Bookmark) TableName() string { return "Bookmark" } -func GetKoboMetadata(detectedPaths []string) []Kobo { +func GetKoboMetadata(detectedPaths []string, logger *slog.Logger) []Kobo { var kobos []Kobo for _, path := range detectedPaths { _, _, deviceId, err := kobo.ParseKoboVersion(path) if err != nil { - logrus.WithField("kobo_path", path).WithError(err).Error("Failed to parse Kobo version") + logger.Error("Failed to parse Kobo version", + slog.String("error", err.Error()), + slog.String("kobo_path", path), + ) } - logrus.WithField("device_id", deviceId).Info("Found an attached device") + logger.Info("Found attached device", + slog.String("device_id", deviceId), + slog.String("kobo_path", path), + ) device, found := kobo.DeviceByID(deviceId) if found { kobos = append(kobos, Kobo{ @@ -187,7 +192,9 @@ func GetKoboMetadata(detectedPaths []string) []Kobo { }) continue } - logrus.WithField("device_id", deviceId).Warn("Found a device that isn't officially supported but will likely still operate just fine") + logger.Warn("Found a device that isn't officially supported but will likely still operate just fine", + slog.String("device_id", deviceId), + ) // We can handle unsupported Kobos in future but at present, there are none kobos = append(kobos, Kobo{ MntPath: path, @@ -197,55 +204,67 @@ func GetKoboMetadata(detectedPaths []string) []Kobo { return kobos } -func (k *Kobo) ListDeviceContent(includeStoreBought bool) ([]Content, error) { +func (k *Kobo) ListDeviceContent(includeStoreBought bool, logger *slog.Logger) ([]Content, error) { var content []Content - logrus.Debug("Retrieving content list from device") + logger.Debug("Retrieving content list from device") result := Conn.Where(&Content{ContentType: "6", VolumeIndex: -1}) if !includeStoreBought { result = result.Where("ContentID LIKE '%file:///%'") } result = result.Order("___PercentRead desc, title asc").Find(&content) if result.Error != nil { - logrus.WithError(result.Error).Error("Failed to retrieve content from device") + logger.Error("Failed to retrieve content from device", + slog.String("error", result.Error.Error()), + ) return nil, result.Error } - logrus.WithField("content_count", len(content)).Debug("Successfully retrieved device content") + logger.Debug("Successfully retrieved device content", + slog.Int("content_count", len(content)), + ) return content, nil } -func (k *Kobo) ListDeviceBookmarks(includeStoreBought bool) ([]Bookmark, error) { +func (k *Kobo) ListDeviceBookmarks(includeStoreBought bool, logger *slog.Logger) ([]Bookmark, error) { var bookmarks []Bookmark - logrus.Debug("Retrieving bookmarks from device") + logger.Debug("Retrieving bookmarks from device") result := Conn if !includeStoreBought { result = result.Where("VolumeID LIKE '%file:///%'") } result = result.Order("VolumeID ASC, ChapterProgress ASC").Find(&bookmarks).Limit(1) if result.Error != nil { - logrus.WithError(result.Error).Error("Failed to retrieve bookmarks from device") + logger.Error("Failed to retrieve bookmarks from device", + slog.String("error", result.Error.Error()), + ) return nil, result.Error } - logrus.WithField("bookmark_count", len(bookmarks)).Debug("Successfully retrieved device bookmarks") + logger.Debug("Successfully retrieved device bookmarks", + slog.Int("bookmark_count", len(bookmarks)), + ) return bookmarks, nil } -func (k *Kobo) BuildContentIndex(content []Content) map[string]Content { - logrus.Debug("Building an index out of device content") +func (k *Kobo) BuildContentIndex(content []Content, logger *slog.Logger) map[string]Content { + logger.Debug("Building an index out of device content") contentIndex := make(map[string]Content) for _, item := range content { contentIndex[item.ContentID] = item } - logrus.WithField("index_count", len(contentIndex)).Debug("Built content index") + logger.Debug("Built content index", + slog.Int("index_count", len(contentIndex)), + ) return contentIndex } -func (k *Kobo) CountDeviceBookmarks() HighlightCounts { +func (k *Kobo) CountDeviceBookmarks(logger *slog.Logger) HighlightCounts { var totalCount int64 var officialCount int64 var sideloadedCount int64 result := Conn.Model(&Bookmark{}).Count(&totalCount) if result.Error != nil { - logrus.WithError(result.Error).Error("Failed to count bookmarks on device") + logger.Error("Failed to count bookmarks on device", + slog.String("error", result.Error.Error()), + ) } Conn.Model(&Bookmark{}).Where("VolumeID LIKE '%file:///%'").Count(&sideloadedCount) Conn.Model(&Bookmark{}).Where("VolumeID NOT LIKE '%file:///%'").Count(&officialCount) diff --git a/backend/device_test.go b/backend/device_test.go index da4f77b..178a4f5 100644 --- a/backend/device_test.go +++ b/backend/device_test.go @@ -1,11 +1,11 @@ package backend import ( + "log/slog" "os" "path/filepath" "testing" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) @@ -22,20 +22,18 @@ func setupTmpKobo(dir string, deviceId string) string { content := []byte(deviceId) err := os.Mkdir(filepath.Join(dir, ".kobo"), 0777) if err != nil { - logrus.Fatal(err) - return "" + panic(err) } tmpfn := filepath.Join(dir, ".kobo", "version") if err := os.WriteFile(tmpfn, content, 0666); err != nil { - logrus.Fatal(err) - return "" + panic(err) } return dir } func TestGetKoboMetadata_HandleNoDevices(t *testing.T) { var expected []Kobo - actual := GetKoboMetadata([]string{}) + actual := GetKoboMetadata([]string{}, slog.New(&discardHandler{})) assert.Equal(t, expected, actual) } @@ -85,6 +83,6 @@ func TestGetKoboMetadata_HandleConnectedDevices(t *testing.T) { }, } detectedPaths := []string{fakeLibraVolume, fakeMiniVolume, fakeElipsaVolume, fakeClara2EVolume, fakeUnknownVolume} - actual := GetKoboMetadata(detectedPaths) + actual := GetKoboMetadata(detectedPaths, slog.New(&discardHandler{})) assert.Equal(t, expected, actual) } diff --git a/backend/init.go b/backend/init.go index 60b2d3b..7457df9 100644 --- a/backend/init.go +++ b/backend/init.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "errors" "fmt" + "log/slog" "net/http" "os" "os/exec" @@ -12,8 +13,6 @@ import ( "runtime" "strings" - "github.com/sirupsen/logrus" - "github.com/pgaskin/koboutils/v2/kobo" wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime" @@ -28,14 +27,23 @@ type Backend struct { Kobo *Kobo Content *Content Bookmark *Bookmark + logger *slog.Logger version string portable bool } -func StartBackend(ctx *context.Context, version string, portable bool) *Backend { - settings, err := LoadSettings(portable) +func StartBackend(ctx *context.Context, version string, portable bool, logger *slog.Logger) (*Backend, error) { + settings, err := LoadSettings(portable, logger) + logger.Info("Successfully parsed settings file", + slog.String("path", settings.path), + slog.Bool("upload_store_highlights", settings.UploadStoreHighlights), + slog.Bool("upload_covers", settings.UploadCovers), + ) if err != nil { - logrus.WithContext(*ctx).WithError(err).Error("Failed to load settings") + logger.Error("Failed to load settings", + slog.String("error", err.Error()), + ) + return &Backend{}, err } return &Backend{ SelectedKobo: Kobo{}, @@ -43,14 +51,16 @@ func StartBackend(ctx *context.Context, version string, portable bool) *Backend RuntimeContext: ctx, Settings: settings, Readwise: &Readwise{ + logger: logger, UserAgent: fmt.Sprintf(UserAgentFmt, version), }, Kobo: &Kobo{}, Content: &Content{}, Bookmark: &Bookmark{}, + logger: logger, version: version, portable: portable, - } + }, nil } func (b *Backend) GetSettings() *Settings { @@ -88,10 +98,20 @@ func (b *Backend) NavigateExplorerToLogLocation() { if runtime.GOOS == "linux" { explorerCommand = "xdg-open" } + b.logger.Info("Opening logs in system file explorer", + slog.String("command", explorerCommand), + slog.String("os", runtime.GOOS), + ) logLocation, err := LocateDataFile("october/logs", b.portable) if err != nil { - logrus.WithError(err).Error("Failed to determine XDG data location for opening log location in explorer") + b.logger.Error("Failed to determine XDG data location for opening log location in explorer", + slog.String("error", err.Error()), + ) } + b.logger.Debug("Executing command to open system file explorer", + slog.String("command", explorerCommand), + slog.String("os", runtime.GOOS), + ) // We will always get an error because the file explorer doesn't exit so it is unable to // return a 0 successful exit code until y'know, the user exits the window _ = exec.Command(explorerCommand, logLocation).Run() @@ -100,10 +120,21 @@ func (b *Backend) NavigateExplorerToLogLocation() { func (b *Backend) DetectKobos() []Kobo { connectedKobos, err := kobo.Find() if err != nil { + b.logger.Error("Failed to detect any connected Kobos") panic(err) } - kobos := GetKoboMetadata(connectedKobos) + kobos := GetKoboMetadata(connectedKobos, b.logger) + b.logger.Info("Found one or more kobos", + "count", len(kobos), + ) for _, kb := range kobos { + b.logger.Info("Found connected device", + slog.String("mount_path", kb.MntPath), + slog.String("database_path", kb.DbPath), + slog.String("name", kb.Name), + slog.Int("display_ppi", kb.DisplayPPI), + slog.Int("storage", kb.Storage), + ) b.ConnectedKobos[kb.MntPath] = kb } return kobos @@ -117,6 +148,9 @@ func (b *Backend) SelectKobo(devicePath string) error { if val, ok := b.ConnectedKobos[devicePath]; ok { b.SelectedKobo = val } else { + b.logger.Info("No device found at path. Selecting local database", + slog.String("device_path", devicePath), + ) b.SelectedKobo = Kobo{ Name: "Local Database", Storage: 0, @@ -126,6 +160,10 @@ func (b *Backend) SelectKobo(devicePath string) error { } } if err := OpenConnection(b.SelectedKobo.DbPath); err != nil { + b.logger.Error("Failed to open DB connection", + slog.String("error", err.Error()), + slog.String("db_path", b.SelectedKobo.DbPath), + ) return err } return nil @@ -152,39 +190,69 @@ func (b *Backend) PromptForLocalDBPath() error { } func (b *Backend) ForwardToReadwise() (int, error) { - highlightBreakdown := b.Kobo.CountDeviceBookmarks() + highlightBreakdown := b.Kobo.CountDeviceBookmarks(b.logger) + slog.Info("Got highlight counts from device", + slog.Int("highlight_count_sideload", int(highlightBreakdown.Sideloaded)), + slog.Int("highlight_count_official", int(highlightBreakdown.Official)), + slog.Int("highlight_count_total", int(highlightBreakdown.Total)), + ) if highlightBreakdown.Total == 0 { - logrus.Error("Tried to submit highlights when there are none on device.") + slog.Error("Tried to submit highlights when there are none on device.") return 0, fmt.Errorf("Your device doesn't seem to have any highlights so there is nothing left to sync.") } includeStoreBought := b.Settings.UploadStoreHighlights if !includeStoreBought && highlightBreakdown.Sideloaded == 0 { - logrus.Error("Tried to submit highlights with no sideloaded highlights + store-bought syncing disabled. Result is that no highlights would be fetched.") + slog.Error("Tried to submit highlights with no sideloaded highlights + store-bought syncing disabled. Result is that no highlights would be fetched.") return 0, fmt.Errorf("You have disabled store-bought syncing but you don't have any sideloaded highlights either. This combination means there are no highlights left to be synced.") } - content, err := b.Kobo.ListDeviceContent(includeStoreBought) + content, err := b.Kobo.ListDeviceContent(includeStoreBought, b.logger) if err != nil { + slog.Error("Received an error trying to list content from device", + slog.String("error", err.Error()), + ) return 0, err } - contentIndex := b.Kobo.BuildContentIndex(content) - bookmarks, err := b.Kobo.ListDeviceBookmarks(includeStoreBought) + contentIndex := b.Kobo.BuildContentIndex(content, b.logger) + bookmarks, err := b.Kobo.ListDeviceBookmarks(includeStoreBought, b.logger) if err != nil { + slog.Error("Received an error trying to list bookmarks from device", + slog.String("error", err.Error()), + ) return 0, err } - payload, err := BuildPayload(bookmarks, contentIndex) + payload, err := BuildPayload(bookmarks, contentIndex, b.logger) if err != nil { + slog.Error("Received an error trying to build Readwise payload", + slog.String("error", err.Error()), + ) return 0, err } numUploads, err := b.Readwise.SendBookmarks(payload, b.Settings.ReadwiseToken) if err != nil { + slog.Error("Received an error trying to send bookmarks to Readwise", + slog.String("error", err.Error()), + ) return 0, err } + slog.Info("Successfully uploaded bookmarks to Readwise", + slog.Int("payload_count", numUploads), + ) if b.Settings.UploadCovers { uploadedBooks, err := b.Readwise.RetrieveUploadedBooks(b.Settings.ReadwiseToken) if err != nil { + slog.Error("Failed to retrieve uploaded titles from Readwise", + slog.String("error", err.Error()), + ) return numUploads, fmt.Errorf("Successfully uploaded %d bookmarks", numUploads) } + slog.Info("Retrieved uploaded books from Readwise for cover insertion", + slog.Int("book_count", uploadedBooks.Count), + ) for _, book := range uploadedBooks.Results { + slog.Info("Checking cover status for book", + slog.Int("book_id", book.ID), + slog.String("book_title", book.Title), + ) // We don't want to overwrite user uploaded covers or covers already present if !strings.Contains(book.CoverURL, "uploaded_book_covers") { coverID := kobo.ContentIDToImageID(book.SourceURL) @@ -192,7 +260,12 @@ func (b *Backend) ForwardToReadwise() (int, error) { absCoverPath := path.Join(b.SelectedKobo.MntPath, "/", coverPath) coverBytes, err := os.ReadFile(absCoverPath) if err != nil { - logrus.WithError(err).WithFields(logrus.Fields{"cover": book.SourceURL, "location": absCoverPath}).Warn("Failed to load cover. Carrying on") + slog.Warn("Failed to load cover from disc. Skipping to next book.", + slog.String("error", err.Error()), + slog.String("cover_path", absCoverPath), + slog.String("cover_id", book.SourceURL), + ) + continue } var base64Encoding string mimeType := http.DetectContentType(coverBytes) @@ -205,10 +278,17 @@ func (b *Backend) ForwardToReadwise() (int, error) { base64Encoding += base64.StdEncoding.EncodeToString(coverBytes) err = b.Readwise.UploadCover(base64Encoding, book.ID, b.Settings.ReadwiseToken) if err != nil { - logrus.WithError(err).WithField("cover", book.SourceURL).Error("Failed to upload cover to Readwise") + slog.Error("Failed to upload cover to Readwise. Skipping to next book.", + slog.String("error", err.Error()), + slog.String("cover_url", book.SourceURL), + ) + continue } - logrus.WithField("cover", book.SourceURL).Debug("Successfully uploaded cover to Readwise") + slog.Debug("Successfully uploaded cover to Readwise", + slog.String("cover_url", book.SourceURL), + ) } + slog.Info("Cover already exists for book. Skipping as we don't know if this was us prior or a user upload.") } } return numUploads, nil diff --git a/backend/log.go b/backend/log.go index 0355792..c55cd73 100644 --- a/backend/log.go +++ b/backend/log.go @@ -1,33 +1,41 @@ package backend import ( + "context" "fmt" + "log/slog" "os" "time" - - "github.com/sirupsen/logrus" ) var logFileHandle *os.File -var logFile = fmt.Sprintf("october/logs/%s.json", time.Now().Format("20060102150405")) +var logFile = fmt.Sprintf("october/logs/%s.txt", time.Now().Format("20060102150405")) -func StartLogger(portable bool) { +func StartLogger(portable bool, level slog.Leveler) (*slog.Logger, error) { logPath, err := LocateDataFile(logFile, portable) if err != nil { panic("Failed to create location to store logfiles") } - logrus.SetFormatter(&logrus.JSONFormatter{}) file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) - if err == nil { - logFileHandle = file - logrus.SetOutput(file) - } else { - logrus.WithError(err).Error(err) - logrus.Error("Failed to create log file, using stdout") + if err != nil { + return &slog.Logger{}, err + } + handlerOpts := slog.HandlerOptions{ + AddSource: true, + Level: level, } + handler := slog.NewTextHandler(file, &handlerOpts) + return slog.New(handler), nil } func CloseLogFile() { logFileHandle.Close() } + +// Dummy until real handler ships in Go 1.24 +type discardHandler struct { + slog.TextHandler +} + +func (d *discardHandler) Enabled(context.Context, slog.Level) bool { return false } diff --git a/backend/readwise.go b/backend/readwise.go index 63d2c75..34b05f3 100644 --- a/backend/readwise.go +++ b/backend/readwise.go @@ -6,14 +6,13 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/http/httputil" "net/url" "path" "strings" "time" - - "github.com/sirupsen/logrus" ) const ( @@ -52,6 +51,7 @@ type BookListEntry struct { } type Readwise struct { + logger *slog.Logger UserAgent string } @@ -70,7 +70,7 @@ func (r *Readwise) CheckTokenValidity(token string) error { if resp.StatusCode != 204 { return errors.New(resp.Status) } - logrus.Info("Successfully validated token against the Readwise API") + r.logger.Info("Successfully validated token against the Readwise API") return nil } @@ -98,13 +98,18 @@ func (r *Readwise) SendBookmarks(payloads []Response, token string) (int, error) defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err == nil { - logrus.WithFields(logrus.Fields{"status_code": resp.StatusCode, "response": string(body)}).Error("Received a non-200 response from Readwise") + r.logger.Error("Received a non-200 response from Readwise", + slog.Int("status_code", resp.StatusCode), + slog.String("response", string(body)), + ) } return 0, fmt.Errorf("received a non-200 status code from Readwise: code %d", resp.StatusCode) } submittedHighlights += len(payload.Highlights) } - logrus.WithField("batch_count", len(payloads)).Info("Successfully sent bookmarks to Readwise") + r.logger.Info("Successfully sent bookmarks to Readwise", + slog.Int("batch_count", len(payloads)), + ) return submittedHighlights, nil } @@ -117,7 +122,9 @@ func (r *Readwise) RetrieveUploadedBooks(token string) (BookListResponse, error) client := http.Client{} remoteURL, err := url.Parse(BooksEndpoint) if err != nil { - logrus.WithError(err).Error("Failed to parse URL for Readwise book upload endpoint") + r.logger.Error("Failed to parse URL for Readwise book upload endpoint", + slog.String("error", err.Error()), + ) } request := http.Request{ Method: "GET", @@ -126,7 +133,10 @@ func (r *Readwise) RetrieveUploadedBooks(token string) (BookListResponse, error) } res, err := client.Do(&request) if err != nil { - logrus.WithError(err).WithField("status_code", res.StatusCode).Error("An unexpected error occurred while retrieving uploads from Readwise") + r.logger.Error("An unexpected error occurred while retrieving uploads from Readwise", + slog.String("error", err.Error()), + slog.Int("status_code", res.StatusCode), + ) return bookList, err } defer func(Body io.ReadCloser) { @@ -136,24 +146,37 @@ func (r *Readwise) RetrieveUploadedBooks(token string) (BookListResponse, error) }(res.Body) b, err := httputil.DumpResponse(res, true) if err != nil { - logrus.WithError(err).Error("Encountered an error while dumping response from Readwise") + r.logger.Error("Encountered an error while dumping response from Readwise", + slog.String("error", err.Error()), + ) return bookList, err } if res.StatusCode != 200 { - logrus.WithFields(logrus.Fields{"status": res.StatusCode, "body": string(b)}).Error("Received a non-200 response from Readwise") + r.logger.Error("Received a non-200 response from Readwise", + slog.Int("status", res.StatusCode), + slog.String("body", string(b)), + ) return bookList, err } body, err := io.ReadAll(res.Body) if err != nil { - logrus.WithError(err).Error("Failed to parse response from Readwise") + r.logger.Error("Failed to parse response from Readwise", + slog.String("error", err.Error()), + ) return bookList, err } err = json.Unmarshal(body, &bookList) if err != nil { - logrus.WithError(err).WithFields(logrus.Fields{"status": res.StatusCode, "body": string(b)}).Error("Failed to unmarshal response from Readwise") + r.logger.Error("Failed to unmarshal response from Readwise", + slog.String("error", err.Error()), + slog.Int("status", res.StatusCode), + slog.String("body", string(b)), + ) return bookList, err } - logrus.WithField("book_count", bookList.Count).Info("Successfully retrieved books from Readwise API") + r.logger.Info("Successfully retrieved books from Readwise API", + slog.Int("book_count", bookList.Count), + ) return bookList, nil } @@ -181,14 +204,17 @@ func (r *Readwise) UploadCover(encodedCover string, bookId int, token string) er defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err == nil { - logrus.WithFields(logrus.Fields{"status_code": resp.StatusCode, "response": string(body)}).Error("Received a non-200 response from Readwise") + r.logger.Error("Received a non-200 response from Readwise", + slog.Int("status", resp.StatusCode), + slog.String("body", string(body)), + ) } return fmt.Errorf("failed to upload cover for book with id %d", bookId) } return nil } -func BuildPayload(bookmarks []Bookmark, contentIndex map[string]Content) ([]Response, error) { +func BuildPayload(bookmarks []Bookmark, contentIndex map[string]Content, logger *slog.Logger) ([]Response, error) { var payloads []Response var currentBatch Response for count, entry := range bookmarks { @@ -199,17 +225,30 @@ func BuildPayload(bookmarks []Bookmark, contentIndex map[string]Content) ([]Resp currentBatch = Response{} } source := contentIndex[entry.VolumeID] - logrus.WithField("title", source.Title).Debug("Parsing highlight") + logger.Debug("Parsing highlight", + slog.String("title", source.Title), + ) var createdAt string if entry.DateCreated == "" { - logrus.WithFields(logrus.Fields{"title": source.Title, "volume_id": entry.VolumeID}).Warn("No date created for bookmark. Defaulting to date last modified.") + logger.Warn("No date created for bookmark. Defaulting to date last modified.", + slog.String("title", source.Title), + slog.String("volume_id", entry.VolumeID), + ) if entry.DateModified == "" { - logrus.WithFields(logrus.Fields{"title": source.Title, "volume_id": entry.VolumeID}).Warn("No date modified for bookmark. Default to current date.") + logger.Warn("No date modified for bookmark. Default to current date.", + slog.String("title", source.Title), + slog.String("volume_id", entry.VolumeID), + ) createdAt = time.Now().Format("2006-01-02T15:04:05-07:00") } else { t, err := time.Parse("2006-01-02T15:04:05Z", entry.DateModified) if err != nil { - logrus.WithError(err).WithFields(logrus.Fields{"title": source.Title, "volume_id": entry.VolumeID, "date_modified": entry.DateModified}).Error("Failed to parse a valid timestamp from date modified field") + logger.Error("Failed to parse a valid timestamp from date modified field", + slog.String("error", err.Error()), + slog.String("title", source.Title), + slog.String("volume_id", entry.VolumeID), + slog.String("date_modified", entry.DateModified), + ) return []Response{}, err } createdAt = t.Format("2006-01-02T15:04:05-07:00") @@ -217,7 +256,12 @@ func BuildPayload(bookmarks []Bookmark, contentIndex map[string]Content) ([]Resp } else { t, err := time.Parse("2006-01-02T15:04:05.000", entry.DateCreated) if err != nil { - logrus.WithError(err).WithFields(logrus.Fields{"title": source.Title, "volume_id": entry.VolumeID, "date_modified": entry.DateModified}).Error("Failed to parse a valid timestamp from date created field") + logger.Error("Failed to parse a valid timestamp from date created field", + slog.String("error", err.Error()), + slog.String("title", source.Title), + slog.String("volume_id", entry.VolumeID), + slog.String("date_modified", entry.DateModified), + ) return []Response{}, err } createdAt = t.Format("2006-01-02T15:04:05-07:00") @@ -231,7 +275,10 @@ func BuildPayload(bookmarks []Bookmark, contentIndex map[string]Content) ([]Resp } if entry.Annotation == "" && text == "" { // This state should be impossible but stranger things have happened so worth a sanity check - logrus.WithFields(logrus.Fields{"title": source.Title, "volume_id": entry.VolumeID}).Warn("Found an entry with neither highlighted text nor an annotation so skipping entry") + logger.Warn("Found an entry with neither highlighted text nor an annotation so skipping entry", + slog.String("title", source.Title), + slog.String("volume_id", entry.VolumeID), + ) continue } if source.Title == "" { @@ -244,11 +291,17 @@ func BuildPayload(bookmarks []Bookmark, contentIndex map[string]Content) ([]Resp // or even just arbitrary strings. Given we don't set a title here, we will use the Readwise fallback which is to add // these highlights to a book called "Quotes" and let the user figure out their metadata situation. That reminds me though: // TODO: Test exports with non-epub files - logrus.WithError(err).WithFields(logrus.Fields{"title": source.Title, "volume_id": entry.VolumeID}).Warn("Failed to retrieve epub title. This is not a hard requirement so sending with a dummy title.") + logger.Warn("Failed to retrieve epub title. This is not a hard requirement so sending with a dummy title.", + slog.String("error", err.Error()), + slog.String("title", source.Title), + slog.String("volume_id", entry.VolumeID), + ) goto sendhighlight } filename := path.Base(sourceFile.Path) - logrus.WithField("filename", filename).Debug("No source title. Constructing title from filename") + logger.Debug("No source title. Constructing title from filename", + slog.String("filename", filename), + ) source.Title = strings.TrimSuffix(filename, ".epub") } sendhighlight: @@ -266,10 +319,17 @@ func BuildPayload(bookmarks []Bookmark, contentIndex map[string]Content) ([]Resp } currentBatch.Highlights = append(currentBatch.Highlights, highlight) } - logrus.WithFields(logrus.Fields{"title": source.Title, "volume_id": entry.VolumeID, "chunks": len(highlightChunks)}).Debug("Successfully compiled highlights for book") + logger.Debug("Successfully compiled highlights for book", + slog.String("title", source.Title), + slog.String("volume_id", entry.VolumeID), + slog.Int("chunks", len(highlightChunks)), + ) } payloads = append(payloads, currentBatch) - logrus.WithFields(logrus.Fields{"highlight_count": len(currentBatch.Highlights), "batch_count": len(payloads)}).Info("Successfully parsed highlights") + logger.Info("Succcessfully parsed highlights", + slog.Int("highlight_count", len(currentBatch.Highlights)), + slog.Int("batch_count", len(payloads)), + ) return payloads, nil } diff --git a/backend/readwise_test.go b/backend/readwise_test.go index 689e920..2806f4c 100644 --- a/backend/readwise_test.go +++ b/backend/readwise_test.go @@ -2,6 +2,7 @@ package backend import ( "fmt" + "log/slog" "strings" "testing" @@ -12,7 +13,7 @@ func TestBuildPayload_NoBookmarks(t *testing.T) { var expected Response var contentIndex map[string]Content var bookmarks []Bookmark - var actual, _ = BuildPayload(bookmarks, contentIndex) + var actual, _ = BuildPayload(bookmarks, contentIndex, slog.New(&discardHandler{})) assert.Equal(t, expected, actual[0]) } @@ -32,7 +33,7 @@ func TestBuildPayload_BookmarksPresent(t *testing.T) { bookmarks := []Bookmark{ {VolumeID: "mnt://kobo/blah/Good Book - An Author.epub", Text: "Hello World", DateCreated: "2006-01-02T15:04:05.000", Annotation: "Making a note here"}, } - var actual, _ = BuildPayload(bookmarks, contentIndex) + var actual, _ = BuildPayload(bookmarks, contentIndex, slog.New(&discardHandler{})) assert.Equal(t, expected, actual[0]) } @@ -52,7 +53,7 @@ func TestBuildPayload_HandleAnnotationOnly(t *testing.T) { bookmarks := []Bookmark{ {VolumeID: "mnt://kobo/blah/Good Book - An Author.epub", DateCreated: "2006-01-02T15:04:05.000", Annotation: "Making a note here"}, } - var actual, _ = BuildPayload(bookmarks, contentIndex) + var actual, _ = BuildPayload(bookmarks, contentIndex, slog.New(&discardHandler{})) assert.Equal(t, expected, actual[0]) } @@ -72,7 +73,7 @@ func TestBuildPayload_TitleFallback(t *testing.T) { bookmarks := []Bookmark{ {VolumeID: "mnt://kobo/blah/Good Book - An Author.epub", Text: "Hello World", DateCreated: "2006-01-02T15:04:05.000", Annotation: "Making a note here"}, } - var actual, _ = BuildPayload(bookmarks, contentIndex) + var actual, _ = BuildPayload(bookmarks, contentIndex, slog.New(&discardHandler{})) assert.Equal(t, expected, actual[0]) } @@ -90,7 +91,7 @@ func TestBuildPayload_TitleFallbackFailure(t *testing.T) { bookmarks := []Bookmark{ {VolumeID: "\t", Text: "Hello World", DateCreated: "2006-01-02T15:04:05.000", Annotation: "Making a note here"}, } - var actual, _ = BuildPayload(bookmarks, contentIndex) + var actual, _ = BuildPayload(bookmarks, contentIndex, slog.New(&discardHandler{})) assert.Equal(t, expected, actual[0]) } @@ -108,7 +109,7 @@ func TestBuildPayload_NoHighlightDateCreated(t *testing.T) { bookmarks := []Bookmark{ {VolumeID: "\t", Text: "Hello World", DateCreated: "", Annotation: "Making a note here", DateModified: "2006-01-02T15:04:05Z"}, } - var actual, _ = BuildPayload(bookmarks, contentIndex) + var actual, _ = BuildPayload(bookmarks, contentIndex, slog.New(&discardHandler{})) assert.Equal(t, expected, actual[0]) } @@ -117,7 +118,7 @@ func TestBuildPayload_NoHighlightDateAtAll(t *testing.T) { bookmarks := []Bookmark{ {VolumeID: "abc123", Text: "Hello World", Annotation: "Making a note here"}, } - var actual, _ = BuildPayload(bookmarks, contentIndex) + var actual, _ = BuildPayload(bookmarks, contentIndex, slog.New(&discardHandler{})) assert.Equal(t, actual[0].Highlights[0].SourceURL, bookmarks[0].VolumeID) assert.Equal(t, actual[0].Highlights[0].Text, bookmarks[0].Text) assert.Equal(t, actual[0].Highlights[0].Note, bookmarks[0].Annotation) @@ -130,7 +131,7 @@ func TestBuildPayload_SkipMalformedBookmarks(t *testing.T) { bookmarks := []Bookmark{ {VolumeID: "mnt://kobo/blah/Good Book - An Author.epub", DateCreated: "2006-01-02T15:04:05.000"}, } - var actual, _ = BuildPayload(bookmarks, contentIndex) + var actual, _ = BuildPayload(bookmarks, contentIndex, slog.New(&discardHandler{})) assert.Equal(t, expected, actual[0]) } @@ -176,7 +177,7 @@ func TestBuildPayload_LongHighlightChunks(t *testing.T) { bookmarks := []Bookmark{ {VolumeID: "mnt://kobo/blah/Good Book - An Author.epub", Text: text, DateCreated: "2006-01-02T15:04:05.000", Annotation: "Making a note here"}, } - actual, err := BuildPayload(bookmarks, contentIndex) + actual, err := BuildPayload(bookmarks, contentIndex, slog.New(&discardHandler{})) assert.NoError(t, err) assert.Equal(t, expected, actual[0]) } @@ -188,7 +189,7 @@ func TestExcessiveHighlightAmounts(t *testing.T) { for i := 0; i < 4002; i++ { bookmarks = append(bookmarks, Bookmark{VolumeID: "mnt://kobo/blah/Good Book - An Author.epub", Text: fmt.Sprintf("hi%d", i), DateCreated: "2006-01-02T15:04:05.000", Annotation: "Making a note here"}) } - actual, err := BuildPayload(bookmarks, contentIndex) + actual, err := BuildPayload(bookmarks, contentIndex, slog.New(&discardHandler{})) assert.NoError(t, err) assert.Equal(t, expected, len(actual)) } diff --git a/backend/settings.go b/backend/settings.go index 9e6adaa..e1c72c7 100644 --- a/backend/settings.go +++ b/backend/settings.go @@ -2,6 +2,7 @@ package backend import ( "encoding/json" + "log/slog" "os" "path/filepath" "strings" @@ -16,11 +17,17 @@ type Settings struct { UploadStoreHighlights bool `json:"upload_store_highlights"` } -func LoadSettings(portable bool) (*Settings, error) { +func LoadSettings(portable bool, logger *slog.Logger) (*Settings, error) { settingsPath, err := LocateConfigFile(configFilename, portable) if err != nil { - return nil, errors.Wrap(err, "Failed to create settings directory. Do you have proper permissions?") + logger.Error("Failed to create settings directory. Do you have proper permissions?", + slog.String("error", err.Error()), + ) + return nil, err } + logger.Debug("Located settings path", + slog.String("path", settingsPath), + ) s := &Settings{ path: settingsPath, UploadStoreHighlights: true, // default on as users with only store purchased books are blocked from usage otherwise but give ample warning during setup @@ -29,31 +36,56 @@ func LoadSettings(portable bool) (*Settings, error) { b, err := os.ReadFile(settingsPath) if err != nil { if os.IsNotExist(err) { + logger.Info("Settings file at path does not exist. Reinitialising with default values.", + slog.String("path", settingsPath), + slog.Bool("upload_store_highlights", s.UploadStoreHighlights), + slog.Bool("upload_covers", s.UploadCovers), + ) // We should always have settings but if they have been deleted, just use the defaults return s, nil } - return nil, errors.Wrap(err, "Failed to read settings file. Is it corrupted?") + logger.Error("Failed to read settings file. Perhaps it was corrupted?", + slog.String("path", settingsPath), + ) + return nil, err } err = json.Unmarshal(b, s) if err != nil { + logger.Warn("Failed to unmarshal settings file. Attempting to repair as v1.6.0 and earlier had known issues.") // v1.6.0 and prior may have caused settings files to have an extra `}` so we check for common issues // We're not gonna go overboard here though, just basic text checking plainSettings := strings.TrimSpace(string(b)) if strings.HasPrefix(plainSettings, "{{") { + logger.Debug("Stripped duplicate opening braces from corrupted settings file") plainSettings = strings.Replace(plainSettings, "{{", "{", 1) } if strings.HasSuffix(plainSettings, "}}") { + logger.Debug("Stripped duplicate closing braces from corrupted settings file") plainSettings = strings.Replace(plainSettings, "}}", "}", 1) } err := json.Unmarshal([]byte(plainSettings), s) if err != nil { - return nil, errors.Wrap(err, "Failed to parse settings file. Is it valid?") + logger.Error("Failed to parse settings file after trying brace stripping hack. Can't continue.", + slog.String("path", settingsPath), + ) + return nil, err } + logger.Info("Successfully fixed up corrupted settings file", + slog.String("path", settingsPath), + slog.Bool("upload_store_highlights", s.UploadStoreHighlights), + slog.Bool("upload_covers", s.UploadCovers), + ) // We managed to fix the settings file so we'll persist it to disc err = s.Save() if err != nil { - return nil, errors.Wrap(err, "Failed to persist fixed up settings file!") + logger.Error("Failed to persist uncorrupted settings file to disc", + slog.String("path", settingsPath), + ) + return nil, err } + logger.Info("Successfully persisted uncorrupted settings file to disc", + slog.String("path", settingsPath), + ) return s, nil } return s, nil diff --git a/cli/cli.go b/cli/cli.go index c08ebd5..f3c95f1 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -3,11 +3,10 @@ package cli import ( "context" "fmt" - "log" + "log/slog" "os" "github.com/marcus-crane/october/backend" - "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" ) @@ -20,7 +19,7 @@ func IsCLIInvokedExplicitly(args []string) bool { return false } -func Invoke(isPortable bool, version string) { +func Invoke(isPortable bool, version string, logger *slog.Logger) { app := &cli.App{ Name: "october cli", HelpName: "october cli", @@ -39,7 +38,10 @@ func Invoke(isPortable bool, version string) { Usage: "sync kobo highlights to readwise", Action: func(c *cli.Context) error { ctx := context.Background() - b := backend.StartBackend(&ctx, version, isPortable) + b, err := backend.StartBackend(&ctx, version, isPortable, logger) + if err != nil { + return err + } if b.Settings.ReadwiseToken == "" { return fmt.Errorf("no readwise token was configured. please set this up using the gui as the cli does not support this yet") } @@ -50,15 +52,16 @@ func Invoke(isPortable bool, version string) { if len(kobos) > 1 { return fmt.Errorf("cli only supports one connected kobo at a time") } - err := b.SelectKobo(kobos[0].MntPath) - if err != nil { + if err := b.SelectKobo(kobos[0].MntPath); err != nil { return fmt.Errorf("an error occurred trying to connect to the kobo at %s", kobos[0].MntPath) } num, err := b.ForwardToReadwise() if err != nil { return err } - logrus.Infof("Successfully synced %d highlights to Readwise", num) + logger.Info("Successfully synced highlights to Readwise", + slog.Int("count", num), + ) return nil }, }, @@ -79,6 +82,7 @@ func Invoke(isPortable bool, version string) { err := app.Run(args) if err != nil { - log.Fatal(err) + logger.Error(err.Error()) + os.Exit(1) } } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index e6eae8d..76ac696 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -54,10 +54,28 @@ export namespace backend { bookmark_id: string; volume_id: string; content_id: string; + StartContainerPath: string; + StartContainerChild: string; + StartContainerChildIndex: string; + StartOffset: string; + EndContainerPath: string; + EndContainerChildIndex: string; + EndOffset: string; text: string; annotation: string; extra_annotation_data: string; date_created: string; + ChapterProgress: number; + Hidden: string; + Version: string; + DateModified: string; + Creator: string; + UUID: string; + UserID: string; + SyncTime: string; + Published: string; + ContextString: string; + Type: string; static createFrom(source: any = {}) { return new Bookmark(source); @@ -68,10 +86,28 @@ export namespace backend { this.bookmark_id = source["bookmark_id"]; this.volume_id = source["volume_id"]; this.content_id = source["content_id"]; + this.StartContainerPath = source["StartContainerPath"]; + this.StartContainerChild = source["StartContainerChild"]; + this.StartContainerChildIndex = source["StartContainerChildIndex"]; + this.StartOffset = source["StartOffset"]; + this.EndContainerPath = source["EndContainerPath"]; + this.EndContainerChildIndex = source["EndContainerChildIndex"]; + this.EndOffset = source["EndOffset"]; this.text = source["text"]; this.annotation = source["annotation"]; this.extra_annotation_data = source["extra_annotation_data"]; this.date_created = source["date_created"]; + this.ChapterProgress = source["ChapterProgress"]; + this.Hidden = source["Hidden"]; + this.Version = source["Version"]; + this.DateModified = source["DateModified"]; + this.Creator = source["Creator"]; + this.UUID = source["UUID"]; + this.UserID = source["UserID"]; + this.SyncTime = source["SyncTime"]; + this.Published = source["Published"]; + this.ContextString = source["ContextString"]; + this.Type = source["Type"]; } } export class Content { @@ -85,9 +121,89 @@ export namespace backend { attribution: string; description: string; date_created: string; + ShortCoverKey: string; + AdobeLocation: string; + Publisher: string; + IsEncrypted: boolean; date_last_read: string; + FirstTimeReading: boolean; + ChapterIDBookmarked: string; + ParagraphBookmarked: number; + BookmarkWordOffset: number; + NumShortcovers: number; + VolumeIndex: number; num_pages: number; + ReadStatus: number; + SyncTime: string; + UserID: string; + PublicationId: string; + FileOffset: number; + FileSize: number; percent_read: string; + ExpirationStatus: number; + FavouritesIndex: number; + Accessibility: number; + ContentURL: string; + Language: string; + BookshelfTags: string; + IsDownloaded: boolean; + FeedbackType: number; + AverageRating: number; + Depth: number; + PageProgressDirection: string; + InWishlist: string; + ISBN: string; + WishlistedDate: string; + FeedbackTypeSynced: boolean; + IsSocialEnabled: boolean; + EpubType: string; + Monetization: string; + ExternalId: string; + Series: string; + SeriesNumber: string; + Subtitle: string; + WordCount: string; + Fallback: string; + RestOfBookEstimate: string; + CurrentChapterEstimate: string; + CurrentChapterProgress: number; + PocketStatus: string; + UnsyncedPocketChanges: string; + ImageUrl: string; + DateAdded: string; + WorkId: string; + Properties: string; + RenditionSpread: string; + RatingCount: string; + ReviewsSyncDate: string; + MediaOverlay: string; + RedirectPreviewUrl: boolean; + PreviewFileSize: number; + EntitlementId: string; + CrossRevisionId: string; + DownloadUrl: boolean; + ReadStateSynced: boolean; + TimesStartedReading: number; + TimeSpentReading: number; + LastTimeStartedReading: string; + LastTimeFinishedReading: string; + ApplicableSubscriptions: string; + ExternalIds: string; + PurchaseRevisionId: string; + SeriesID: string; + SeriesNumberFloat: number; + AdobeLoanExpiration: string; + HideFromHomePage: boolean; + IsInternetArchive: boolean; + TitleKana: string; + SubtitleKana: string; + SeriesKana: string; + AttributionKana: string; + PublisherKana: string; + IsPurchaseable: boolean; + IsSupported: boolean; + AnnotationsSyncToken: string; + DateModified: string; static createFrom(source: any = {}) { return new Content(source); @@ -105,9 +221,89 @@ export namespace backend { this.attribution = source["attribution"]; this.description = source["description"]; this.date_created = source["date_created"]; + this.ShortCoverKey = source["ShortCoverKey"]; + this.AdobeLocation = source["AdobeLocation"]; + this.Publisher = source["Publisher"]; + this.IsEncrypted = source["IsEncrypted"]; this.date_last_read = source["date_last_read"]; + this.FirstTimeReading = source["FirstTimeReading"]; + this.ChapterIDBookmarked = source["ChapterIDBookmarked"]; + this.ParagraphBookmarked = source["ParagraphBookmarked"]; + this.BookmarkWordOffset = source["BookmarkWordOffset"]; + this.NumShortcovers = source["NumShortcovers"]; + this.VolumeIndex = source["VolumeIndex"]; this.num_pages = source["num_pages"]; + this.ReadStatus = source["ReadStatus"]; + this.SyncTime = source["SyncTime"]; + this.UserID = source["UserID"]; + this.PublicationId = source["PublicationId"]; + this.FileOffset = source["FileOffset"]; + this.FileSize = source["FileSize"]; this.percent_read = source["percent_read"]; + this.ExpirationStatus = source["ExpirationStatus"]; + this.FavouritesIndex = source["FavouritesIndex"]; + this.Accessibility = source["Accessibility"]; + this.ContentURL = source["ContentURL"]; + this.Language = source["Language"]; + this.BookshelfTags = source["BookshelfTags"]; + this.IsDownloaded = source["IsDownloaded"]; + this.FeedbackType = source["FeedbackType"]; + this.AverageRating = source["AverageRating"]; + this.Depth = source["Depth"]; + this.PageProgressDirection = source["PageProgressDirection"]; + this.InWishlist = source["InWishlist"]; + this.ISBN = source["ISBN"]; + this.WishlistedDate = source["WishlistedDate"]; + this.FeedbackTypeSynced = source["FeedbackTypeSynced"]; + this.IsSocialEnabled = source["IsSocialEnabled"]; + this.EpubType = source["EpubType"]; + this.Monetization = source["Monetization"]; + this.ExternalId = source["ExternalId"]; + this.Series = source["Series"]; + this.SeriesNumber = source["SeriesNumber"]; + this.Subtitle = source["Subtitle"]; + this.WordCount = source["WordCount"]; + this.Fallback = source["Fallback"]; + this.RestOfBookEstimate = source["RestOfBookEstimate"]; + this.CurrentChapterEstimate = source["CurrentChapterEstimate"]; + this.CurrentChapterProgress = source["CurrentChapterProgress"]; + this.PocketStatus = source["PocketStatus"]; + this.UnsyncedPocketChanges = source["UnsyncedPocketChanges"]; + this.ImageUrl = source["ImageUrl"]; + this.DateAdded = source["DateAdded"]; + this.WorkId = source["WorkId"]; + this.Properties = source["Properties"]; + this.RenditionSpread = source["RenditionSpread"]; + this.RatingCount = source["RatingCount"]; + this.ReviewsSyncDate = source["ReviewsSyncDate"]; + this.MediaOverlay = source["MediaOverlay"]; + this.RedirectPreviewUrl = source["RedirectPreviewUrl"]; + this.PreviewFileSize = source["PreviewFileSize"]; + this.EntitlementId = source["EntitlementId"]; + this.CrossRevisionId = source["CrossRevisionId"]; + this.DownloadUrl = source["DownloadUrl"]; + this.ReadStateSynced = source["ReadStateSynced"]; + this.TimesStartedReading = source["TimesStartedReading"]; + this.TimeSpentReading = source["TimeSpentReading"]; + this.LastTimeStartedReading = source["LastTimeStartedReading"]; + this.LastTimeFinishedReading = source["LastTimeFinishedReading"]; + this.ApplicableSubscriptions = source["ApplicableSubscriptions"]; + this.ExternalIds = source["ExternalIds"]; + this.PurchaseRevisionId = source["PurchaseRevisionId"]; + this.SeriesID = source["SeriesID"]; + this.SeriesNumberFloat = source["SeriesNumberFloat"]; + this.AdobeLoanExpiration = source["AdobeLoanExpiration"]; + this.HideFromHomePage = source["HideFromHomePage"]; + this.IsInternetArchive = source["IsInternetArchive"]; + this.TitleKana = source["TitleKana"]; + this.SubtitleKana = source["SubtitleKana"]; + this.SeriesKana = source["SeriesKana"]; + this.AttributionKana = source["AttributionKana"]; + this.PublisherKana = source["PublisherKana"]; + this.IsPurchaseable = source["IsPurchaseable"]; + this.IsSupported = source["IsSupported"]; + this.AnnotationsSyncToken = source["AnnotationsSyncToken"]; + this.DateModified = source["DateModified"]; } } export class Highlight { diff --git a/frontend/wailsjs/runtime/runtime.d.ts b/frontend/wailsjs/runtime/runtime.d.ts index a3723f9..94778df 100644 --- a/frontend/wailsjs/runtime/runtime.d.ts +++ b/frontend/wailsjs/runtime/runtime.d.ts @@ -233,3 +233,17 @@ export function ClipboardGetText(): Promise; // [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext) // Sets a text on the clipboard export function ClipboardSetText(text: string): Promise; + +// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop) +// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. +export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void + +// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff) +// OnFileDropOff removes the drag and drop listeners and handlers. +export function OnFileDropOff() :void + +// Check if the file path resolver is available +export function CanResolveFilePaths(): boolean; + +// Resolves file paths for an array of files +export function ResolveFilePaths(files: File[]): void \ No newline at end of file diff --git a/frontend/wailsjs/runtime/runtime.js b/frontend/wailsjs/runtime/runtime.js index bd4f371..623397b 100644 --- a/frontend/wailsjs/runtime/runtime.js +++ b/frontend/wailsjs/runtime/runtime.js @@ -199,4 +199,40 @@ export function ClipboardGetText() { export function ClipboardSetText(text) { return window.runtime.ClipboardSetText(text); +} + +/** + * Callback for OnFileDrop returns a slice of file path strings when a drop is finished. + * + * @export + * @callback OnFileDropCallback + * @param {number} x - x coordinate of the drop + * @param {number} y - y coordinate of the drop + * @param {string[]} paths - A list of file paths. + */ + +/** + * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. + * + * @export + * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished. + * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target) + */ +export function OnFileDrop(callback, useDropTarget) { + return window.runtime.OnFileDrop(callback, useDropTarget); +} + +/** + * OnFileDropOff removes the drag and drop listeners and handlers. + */ +export function OnFileDropOff() { + return window.runtime.OnFileDropOff(); +} + +export function CanResolveFilePaths() { + return window.runtime.CanResolveFilePaths(); +} + +export function ResolveFilePaths(files) { + return window.runtime.ResolveFilePaths(files); } \ No newline at end of file diff --git a/go.mod b/go.mod index 08dd004..be9d15e 100644 --- a/go.mod +++ b/go.mod @@ -5,30 +5,29 @@ go 1.22.0 toolchain go1.23.2 require ( - github.com/adrg/xdg v0.4.0 - github.com/glebarez/sqlite v1.10.0 + github.com/adrg/xdg v0.5.3 + github.com/glebarez/sqlite v1.11.0 github.com/pgaskin/koboutils/v2 v2.2.0 github.com/pkg/errors v0.9.1 - github.com/sirupsen/logrus v1.9.0 - github.com/stretchr/testify v1.9.0 - github.com/urfave/cli/v2 v2.27.0 + github.com/stretchr/testify v1.10.0 + github.com/urfave/cli/v2 v2.27.5 github.com/wailsapp/wails/v2 v2.9.2 - gorm.io/gorm v1.25.5 + gorm.io/gorm v1.25.12 ) require ( github.com/bep/debounce v1.2.1 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/glebarez/go-sqlite v1.22.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/labstack/echo/v4 v4.13.0 // indirect + github.com/labstack/echo/v4 v4.13.2 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/leaanthony/go-ansi-parser v1.6.1 // indirect github.com/leaanthony/gosod v1.0.4 // indirect @@ -36,6 +35,7 @@ require ( github.com/leaanthony/u v1.1.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect @@ -47,15 +47,16 @@ require ( github.com/valyala/fasttemplate v1.2.2 // indirect github.com/wailsapp/go-webview2 v1.0.18 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/crypto v0.30.0 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect golang.org/x/net v0.32.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/libc v1.22.5 // indirect - modernc.org/mathutil v1.5.0 // indirect - modernc.org/memory v1.5.0 // indirect - modernc.org/sqlite v1.23.1 // indirect + modernc.org/libc v1.61.4 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/sqlite v1.34.2 // indirect ) diff --git a/go.sum b/go.sum index 0884227..2c3c2dc 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,23 @@ -github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= -github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= -github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= -github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc= -github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA= +github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= @@ -32,8 +31,8 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/labstack/echo/v4 v4.13.0 h1:8DjSi4H/k+RqoOmwXkxW14A2H1pdPdS95+qmdJ4q1Tg= -github.com/labstack/echo/v4 v4.13.0/go.mod h1:61j7WN2+bp8V21qerqRs4yVlVTGyOagMBpF0vE7VcmM= +github.com/labstack/echo/v4 v4.13.2 h1:9aAt4hstpH54qIcqkuUXRLTf+v7yOTfMPWzDtuqLmtA= +github.com/labstack/echo/v4 v4.13.2/go.mod h1:uc9gDtHB8UWt3FfbYx0HyxcCuvR4YuPYOxF/1QjoV/c= 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/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= @@ -54,6 +53,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pgaskin/koboutils/v2 v2.2.0 h1:QPZdJqypR70FcI7MwFmo0ytc5aIoNYghYaZcPqiC5yY= github.com/pgaskin/koboutils/v2 v2.2.0/go.mod h1:VZgKQWcGI6jHpGKN+RJ34Xm6IZjuY8nauqYLrSfruo4= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -62,7 +63,6 @@ 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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -72,16 +72,12 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ= github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= -github.com/urfave/cli/v2 v2.27.0 h1:uNs1K8JwTFL84X68j5Fjny6hfANh9nTlJ6dRtZAFAHY= -github.com/urfave/cli/v2 v2.27.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= 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= @@ -92,18 +88,22 @@ github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhw github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= github.com/wailsapp/wails/v2 v2.9.2 h1:Xb5YRTos1w5N7DTMyYegWaGukCP2fIaX9WF21kPPF2k= github.com/wailsapp/wails/v2 v2.9.2/go.mod h1:uehvlCwJSFcBq7rMCGfk4rxca67QQGsbg5Nm4m9UnBs= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w= +golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +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-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -114,19 +114,36 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= -gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= -gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= -modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= -modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= -modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= -modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= -modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +modernc.org/cc/v4 v4.23.1 h1:WqJoPL3x4cUufQVHkXpXX7ThFJ1C4ik80i2eXEXbhD8= +modernc.org/cc/v4 v4.23.1/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.23.1 h1:N49a7JiWGWV7lkPE4yYcvjkBGZQi93/JabRYjdWmJXc= +modernc.org/ccgo/v4 v4.23.1/go.mod h1:JoIUegEIfutvoWV/BBfDFpPpfR2nc3U0jKucGcbmwDU= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= +modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/libc v1.61.4 h1:wVyqEx6tlltte9lPTjq0kDAdtdM9c4JH8rU6M1ZVawA= +modernc.org/libc v1.61.4/go.mod h1:VfXVuM/Shh5XsMNrh3C6OkfL78G3loa4ZC/Ljv9k7xc= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.34.2 h1:J9n76TPsfYYkFkZ9Uy1QphILYifiVEwwOT7yP5b++2Y= +modernc.org/sqlite v1.34.2/go.mod h1:dnR723UrTtjKpoHCAMN0Q/gZ9MT4r+iRvIBb9umWFkU= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/main.go b/main.go index 63f94ac..10d9436 100644 --- a/main.go +++ b/main.go @@ -3,13 +3,16 @@ package main import ( "embed" "fmt" + "log/slog" "os" + "runtime" "strconv" + "strings" "github.com/marcus-crane/october/backend" "github.com/marcus-crane/october/cli" "github.com/wailsapp/wails/v2" - "github.com/wailsapp/wails/v2/pkg/logger" + wlogger "github.com/wailsapp/wails/v2/pkg/logger" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/linux" @@ -25,6 +28,8 @@ var icon []byte var version = "DEV" +var loglevel = slog.LevelDebug + // Builds with this set to true cause files to be created // in the same directory as the running executable var portablebuild = "false" @@ -33,26 +38,54 @@ func main() { isPortable := false isPortable, _ = strconv.ParseBool(portablebuild) + logger, err := backend.StartLogger(isPortable, loglevel) + if err != nil { + panic("Failed to set up logger") + } + + usr_cache_dir, _ := os.UserCacheDir() + usr_config_dir, _ := os.UserConfigDir() + + logger.Info("Initialising October", + slog.String("version", version), + slog.String("loglevel", loglevel.String()), + slog.Bool("portable", isPortable), + slog.String("user_cache_dir", usr_cache_dir), + slog.String("user_config_dir", usr_config_dir), + slog.String("goarch", runtime.GOARCH), + slog.String("goos", runtime.GOOS), + slog.String("goversion", runtime.Version()), + ) + if cli.IsCLIInvokedExplicitly(os.Args) { - cli.Invoke(isPortable, version) + logger.Info("CLI command invoked", + slog.String("args", strings.Join(os.Args, " ")), + ) + cli.Invoke(isPortable, version, logger) return } // Create an instance of the app structure - app := NewApp(isPortable) + app := NewApp(isPortable, logger) - backend := backend.StartBackend(&app.ctx, version, isPortable) + backend, err := backend.StartBackend(&app.ctx, version, isPortable, logger) + if err != nil { + logger.Error("Backend failed to start up", + slog.String("error", err.Error()), + ) + panic("Failed to start backend") + } // Create application with options - err := wails.Run(&options.App{ + err = wails.Run(&options.App{ Title: "October", Width: 1366, Height: 768, AssetServer: &assetserver.Options{ Assets: assets, }, - LogLevel: logger.DEBUG, - LogLevelProduction: logger.DEBUG, + LogLevel: wlogger.DEBUG, + LogLevelProduction: wlogger.DEBUG, OnStartup: app.startup, OnDomReady: app.domReady, OnShutdown: app.shutdown, @@ -88,6 +121,10 @@ func main() { }) if err != nil { - println("Error:", err.Error()) + logger.Error("Wails runtime exited", + slog.String("error", err.Error()), + ) } + + // TODO: Close file }