diff --git a/about.go b/about.go index d778987..ef5d00f 100644 --- a/about.go +++ b/about.go @@ -5,7 +5,6 @@ import ( "bytes" _ "embed" "fmt" - "net/url" "strings" "fyne.io/fyne/v2" @@ -14,11 +13,6 @@ import ( "fyne.io/fyne/v2/widget" ) -func parseURL(s string) *url.URL { - link, _ := url.Parse(s) - return link -} - //go:embed metadata/en-US/full_description.txt var longdesc string diff --git a/go.mod b/go.mod index 6cbd2ff..9c9b1e5 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( fyne.io/fyne/v2 v2.0.0 github.com/schollz/croc/v8 v8.6.7 + github.com/schollz/logger v1.2.0 // indirect ) replace fyne.io/fyne/v2 => ./fyne-uri-name diff --git a/main.go b/main.go index 88b1e7a..0b68e82 100644 --- a/main.go +++ b/main.go @@ -1,22 +1,57 @@ package main import ( + "bytes" _ "embed" + "strings" + "time" + + log "github.com/schollz/logger" "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/layout" ) //go:embed metadata/en-US/images/featureGraphic.png var textlogobytes []byte +type logwriter struct { + buf bytes.Buffer + lastlines []string + lastupdate time.Time +} + +const LOG_LINES = 20 + +func (lw *logwriter) Write(p []byte) (n int, err error) { + n, err = lw.buf.Write(p) + + lw.lastlines = append([]string{string(p)}, lw.lastlines...) + if len(lw.lastlines) > LOG_LINES { + lw.lastlines = lw.lastlines[:LOG_LINES] + } + + if time.Since(lw.lastupdate) > time.Second { + logbinding.Set(strings.Join(lw.lastlines, "")) + lw.lastupdate = time.Now() + } + return +} + +var logoutput logwriter +var logbinding binding.String + func main() { a := app.NewWithID("com.github.howeyc.crocgui") w := a.NewWindow("croc") + logbinding = binding.NewString() + log.SetOutput(&logoutput) + // Defaults a.Preferences().SetString("relay-address", a.Preferences().StringWithFallback("relay-address", "croc.schollz.com:9009")) a.Preferences().SetString("relay-password", a.Preferences().StringWithFallback("relay-password", "pass123")) @@ -26,8 +61,10 @@ func main() { a.Preferences().SetBool("disable-multiplexing", a.Preferences().BoolWithFallback("disable-multiplexing", false)) a.Preferences().SetBool("disable-compression", a.Preferences().BoolWithFallback("disable-compression", false)) a.Preferences().SetString("theme", a.Preferences().StringWithFallback("theme", "light")) + a.Preferences().SetString("debug-level", a.Preferences().StringWithFallback("debug-level", "error")) setTheme(a.Preferences().String("theme")) + log.SetLevel(a.Preferences().String("debug-level")) textlogores := fyne.NewStaticResource("text-logo", textlogobytes) textlogo := canvas.NewImageFromResource(textlogores) @@ -41,5 +78,8 @@ func main() { aboutTabItem(), ))) w.Resize(fyne.NewSize(800, 600)) + + setDebugObjects() + w.ShowAndRun() } diff --git a/metadata/en-US/changelogs/10.txt b/metadata/en-US/changelogs/10.txt new file mode 100644 index 0000000..587ecba --- /dev/null +++ b/metadata/en-US/changelogs/10.txt @@ -0,0 +1,2 @@ +- Settings are scrollable +- Add debug logging diff --git a/recv.go b/recv.go index 6d1999f..94bacf4 100644 --- a/recv.go +++ b/recv.go @@ -4,7 +4,6 @@ import ( "fmt" "io" "io/fs" - "log" "os" "path/filepath" "sort" @@ -12,24 +11,42 @@ import ( "sync" "time" + log "github.com/schollz/logger" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/schollz/croc/v8/src/croc" ) func recvTabItem(a fyne.App, w fyne.Window) *container.TabItem { + logInfo := widget.NewLabelWithData(logbinding) + logInfo.Wrapping = fyne.TextWrapWord + status := widget.NewLabel("") defer func() { if r := recover(); r != nil { - status.SetText(fmt.Sprint(r)) + logInfo.SetText(fmt.Sprint(r)) } }() recvDir, _ := os.MkdirTemp("", "crocgui-recv") + debugBox := container.NewHBox(widget.NewLabel("Debug log:"), layout.NewSpacer(), widget.NewButton("Export full log", func() { + savedialog := dialog.NewFileSave(func(f fyne.URIWriteCloser, e error) { + if f != nil { + logoutput.buf.WriteTo(f) + f.Close() + } + }, w) + savedialog.SetFileName("crocdebuglog.txt") + savedialog.Show() + })) + debugObjects = append(debugObjects, debugBox) + prog := widget.NewProgressBar() prog.Hide() recvEntry := widget.NewEntry() @@ -42,7 +59,7 @@ func recvTabItem(a fyne.App, w fyne.Window) *container.TabItem { receiver, err := croc.New(croc.Options{ IsSender: false, SharedSecret: recvEntry.Text, - Debug: false, + Debug: crocDebugMode(), RelayAddress: a.Preferences().String("relay-address"), RelayPassword: a.Preferences().String("relay-password"), Stdout: false, @@ -53,8 +70,12 @@ func recvTabItem(a fyne.App, w fyne.Window) *container.TabItem { NoCompress: a.Preferences().Bool("disable-compression"), }) if err != nil { - log.Println("Receive setup error:", err) + log.Error("Receive setup error:", err) + return } + log.SetLevel(crocDebugLevel()) + log.Trace("croc receiver created") + prog.Show() donechan := make(chan bool) var filename string @@ -81,7 +102,7 @@ func recvTabItem(a fyne.App, w fyne.Window) *container.TabItem { }() cderr := os.Chdir(recvDir) if cderr != nil { - log.Println("Unable to change to dir:", recvDir, cderr) + log.Error("Unable to change to dir:", recvDir, cderr) } status.SetText("") rerr := receiver.Receive() @@ -90,7 +111,7 @@ func recvTabItem(a fyne.App, w fyne.Window) *container.TabItem { prog.SetValue(0) topline.SetText("Enter code to download") if rerr != nil { - status.Text = "Receive failed: " + rerr.Error() + log.Error("Receive failed: " + rerr.Error()) } else { filesReceived := make([]string, len(receivednames)) var i int @@ -120,18 +141,20 @@ func recvTabItem(a fyne.App, w fyne.Window) *container.TabItem { ofile = f oerr = e if oerr != nil { - status.SetText(oerr.Error()) + log.Error(oerr.Error()) return } ifile, ierr := os.Open(path) if ierr != nil { - status.SetText(ierr.Error()) + log.Error(ierr.Error()) return } io.Copy(ofile, ifile) ifile.Close() ofile.Close() os.Remove(path) + log.Tracef("saved (%s) to user path %s", path, f.URI().String()) + log.Tracef("remove internal cache file %s", path) diagwg.Done() }, w) savedialog.SetFileName(filepath.Base(path)) @@ -144,6 +167,8 @@ func recvTabItem(a fyne.App, w fyne.Window) *container.TabItem { }), prog, status, + debugBox, + logInfo, )) } diff --git a/send.go b/send.go index d5780e7..c934733 100644 --- a/send.go +++ b/send.go @@ -3,12 +3,13 @@ package main import ( "fmt" "io" - "log" "os" "path/filepath" "strings" "time" + log "github.com/schollz/logger" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" @@ -20,10 +21,13 @@ import ( ) func sendTabItem(a fyne.App, w fyne.Window) *container.TabItem { + logInfo := widget.NewLabelWithData(logbinding) + logInfo.Wrapping = fyne.TextWrapWord + status := widget.NewLabel("") defer func() { if r := recover(); r != nil { - status.SetText(fmt.Sprint(r)) + log.Error(fmt.Sprint(r)) } }() prog := widget.NewProgressBar() @@ -45,21 +49,23 @@ func sendTabItem(a fyne.App, w fyne.Window) *container.TabItem { addFileButton := widget.NewButtonWithIcon("", theme.FileIcon(), func() { dialog.ShowFileOpen(func(f fyne.URIReadCloser, e error) { if e != nil { + log.Errorf("Open dialog error: %s", e.Error()) return } if f != nil { nfile, oerr := os.Create(filepath.Join(sendDir, f.URI().Name())) if oerr != nil { - status.SetText(fmt.Sprintf("Unable to copy file, error: %s - %s", sendDir, oerr.Error())) + log.Errorf("Unable to copy file, error: %s - %s\n", sendDir, oerr.Error()) return } io.Copy(nfile, f) nfile.Close() fpath := nfile.Name() + log.Tracef("Android URI (%s), copied to internal cache %s", f.URI().String(), nfile.Name()) _, sterr := os.Stat(fpath) if sterr != nil { - status.SetText(fmt.Sprintf("Stat error: %s - %s", fpath, sterr.Error())) + log.Errorf("Stat error: %s - %s\n", fpath, sterr.Error()) return } labelFile := widget.NewLabel(filepath.Base(fpath)) @@ -67,6 +73,7 @@ func sendTabItem(a fyne.App, w fyne.Window) *container.TabItem { if fe, ok := fileentries[fpath]; ok { boxholder.Remove(fe) os.Remove(fpath) + log.Tracef("Removed file from internal cache: %s", fpath) delete(fileentries, fpath) } })) @@ -76,6 +83,18 @@ func sendTabItem(a fyne.App, w fyne.Window) *container.TabItem { }, w) }) + debugBox := container.NewHBox(widget.NewLabel("Debug log:"), layout.NewSpacer(), widget.NewButton("Export full log", func() { + savedialog := dialog.NewFileSave(func(f fyne.URIWriteCloser, e error) { + if f != nil { + logoutput.buf.WriteTo(f) + f.Close() + } + }, w) + savedialog.SetFileName("crocdebuglog.txt") + savedialog.Show() + })) + debugObjects = append(debugObjects, debugBox) + return container.NewTabItemWithIcon("Send", theme.MailSendIcon(), container.NewVBox( container.NewHBox(topline, layout.NewSpacer(), addFileButton), @@ -91,7 +110,7 @@ func sendTabItem(a fyne.App, w fyne.Window) *container.TabItem { sender, err := croc.New(croc.Options{ IsSender: true, SharedSecret: randomName, - Debug: false, + Debug: crocDebugMode(), RelayAddress: a.Preferences().String("relay-address"), RelayPorts: strings.Split(a.Preferences().String("relay-ports"), ","), RelayPassword: a.Preferences().String("relay-password"), @@ -103,9 +122,12 @@ func sendTabItem(a fyne.App, w fyne.Window) *container.TabItem { NoCompress: a.Preferences().Bool("disable-compression"), }) if err != nil { - status.SetText("croc error: " + err.Error()) + log.Errorf("croc error: %s\n", err.Error()) return } + log.SetLevel(crocDebugLevel()) + log.Trace("croc sender created") + var filename string status.SetText("Receive Code: " + randomName) currentCode = randomName @@ -148,6 +170,7 @@ func sendTabItem(a fyne.App, w fyne.Window) *container.TabItem { if fe, ok := fileentries[fpath]; ok { boxholder.Remove(fe) os.Remove(fpath) + log.Tracef("Removed file from internal cache: %s", fpath) delete(fileentries, fpath) } } @@ -155,7 +178,7 @@ func sendTabItem(a fyne.App, w fyne.Window) *container.TabItem { topline.SetText("Pick a file to send") addFileButton.Show() if serr != nil { - log.Println("Send failed:", serr) + log.Errorf("Send failed: %s\n", serr) } else { status.SetText(fmt.Sprintf("Sent file %s", filename)) } @@ -165,5 +188,7 @@ func sendTabItem(a fyne.App, w fyne.Window) *container.TabItem { }), prog, container.NewHBox(status, copyCodeButton), + debugBox, + logInfo, )) } diff --git a/settings.go b/settings.go index 9ba5270..e28734d 100644 --- a/settings.go +++ b/settings.go @@ -3,6 +3,8 @@ package main import ( "crocgui/internal/croctheme" + log "github.com/schollz/logger" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" @@ -25,6 +27,32 @@ func setTheme(themeName string) { } } +func crocDebugMode() bool { + switch fyne.CurrentApp().Preferences().String("debug-level") { + case "trace", "debug": + return true + default: + return false + } +} + +func crocDebugLevel() string { + return fyne.CurrentApp().Preferences().String("debug-level") +} + +var debugObjects []fyne.CanvasObject + +func setDebugObjects() { + debugging := crocDebugMode() + for _, obj := range debugObjects { + if debugging { + obj.Show() + } else { + obj.Hide() + } + } +} + func settingsTabItem(a fyne.App) *container.TabItem { themeBinding := binding.BindPreferenceString("theme", a.Preferences()) themeSelect := widget.NewSelect([]string{"light", "dark", "black"}, func(selection string) { @@ -33,7 +61,26 @@ func settingsTabItem(a fyne.App) *container.TabItem { }) currentTheme, _ := themeBinding.Get() themeSelect.SetSelected(currentTheme) - return container.NewTabItemWithIcon("Settings", theme.SettingsIcon(), container.NewVBox( + + debugLevelBinding := binding.BindPreferenceString("debug-level", a.Preferences()) + debugCheck := widget.NewCheck("Enable Debug Log", func(debug bool) { + if debug { + log.SetLevel("trace") + debugLevelBinding.Set("trace") + } else { + log.SetLevel("error") + debugLevelBinding.Set("error") + } + setDebugObjects() + }) + debugCheck.SetChecked(crocDebugMode()) + + return container.NewTabItemWithIcon("Settings", theme.SettingsIcon(), container.NewVScroll(container.NewVBox( + widget.NewLabelWithStyle("Appearance", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + widget.NewForm( + widget.NewFormItem("Theme", themeSelect), + ), + widget.NewSeparator(), widget.NewLabelWithStyle("Relay", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), widget.NewForm( widget.NewFormItem("Address", widget.NewEntryWithData(binding.BindPreferenceString("relay-address", a.Preferences()))), @@ -53,9 +100,9 @@ func settingsTabItem(a fyne.App) *container.TabItem { widget.NewFormItem("", widget.NewCheckWithData("Disable Compression", binding.BindPreferenceBool("disable-compression", a.Preferences()))), ), widget.NewSeparator(), - widget.NewLabelWithStyle("Appearance", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + widget.NewLabelWithStyle("Debug", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), widget.NewForm( - widget.NewFormItem("Theme", themeSelect), + widget.NewFormItem("", debugCheck), ), - )) + ))) }