203 lines
4.6 KiB
Go
203 lines
4.6 KiB
Go
|
package KFLog
|
||
|
|
||
|
import (
|
||
|
"io"
|
||
|
"log"
|
||
|
"os"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"fyne.io/fyne/v2"
|
||
|
"fyne.io/fyne/v2/container"
|
||
|
"fyne.io/fyne/v2/dialog"
|
||
|
"fyne.io/fyne/v2/theme"
|
||
|
"fyne.io/fyne/v2/widget"
|
||
|
)
|
||
|
|
||
|
var Directory = ""
|
||
|
var LogList *widget.List
|
||
|
var LogDialog *dialog.CustomDialog
|
||
|
|
||
|
type entry struct {
|
||
|
Date string
|
||
|
Time string
|
||
|
File string
|
||
|
Line string
|
||
|
Message string
|
||
|
}
|
||
|
|
||
|
var entries []entry
|
||
|
|
||
|
type writer chan<- string
|
||
|
|
||
|
func (c writer) Write(p []byte) (n int, err error) {
|
||
|
c <- string(p)
|
||
|
return len(p), nil
|
||
|
}
|
||
|
|
||
|
// ShowDialog shows the log dialog
|
||
|
func ShowDialog(window fyne.Window) {
|
||
|
// if the log dialog is nil, create it
|
||
|
if LogDialog == nil {
|
||
|
err := Setup(window)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LogDialog.Show()
|
||
|
}
|
||
|
|
||
|
// GetDirectory returns the directory that the log files are stored in
|
||
|
func GetDirectory() string {
|
||
|
return Directory
|
||
|
}
|
||
|
|
||
|
// SetDirectory sets the directory that the log files are stored in and creates it if it doesn't exist
|
||
|
func SetDirectory(dir string) {
|
||
|
// make sure it ends in a slash and only one slash
|
||
|
if !strings.HasSuffix(dir, "/") {
|
||
|
dir += "/"
|
||
|
}
|
||
|
|
||
|
// check if the directory exists
|
||
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||
|
// create the directory
|
||
|
err = os.MkdirAll(dir, os.ModePerm)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Directory = dir
|
||
|
}
|
||
|
|
||
|
func Setup(window fyne.Window, dir ...string) error {
|
||
|
if len(dir) > 0 {
|
||
|
SetDirectory(dir[0])
|
||
|
} else {
|
||
|
SetDirectory("logs/")
|
||
|
}
|
||
|
|
||
|
// check if a log.txt file already exists
|
||
|
fi, err := os.Stat(Directory + "log.txt")
|
||
|
if err != nil {
|
||
|
// create a new log.txt file
|
||
|
_, err = os.Create(Directory + "log.txt")
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// copy the new log file to one named with its creation date
|
||
|
if fi != nil {
|
||
|
err = os.Rename(Directory+"log.txt", Directory+fi.ModTime().Format("2006-01-02-1504")+".txt")
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
// create a new log.txt file
|
||
|
_, err = os.Create(Directory + "log.txt")
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// open the log file for writing
|
||
|
logFile, err := os.OpenFile(Directory+"log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
logChannel := make(chan string)
|
||
|
channelWriter := writer(logChannel)
|
||
|
multi := io.MultiWriter(os.Stdout, logFile, channelWriter)
|
||
|
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
||
|
|
||
|
log.SetOutput(multi)
|
||
|
|
||
|
LogList = widget.NewList(
|
||
|
func() int {
|
||
|
return len(entries)
|
||
|
},
|
||
|
func() fyne.CanvasObject {
|
||
|
return container.NewHBox(
|
||
|
widget.NewButtonWithIcon("", theme.ContentCopyIcon(), func() {}),
|
||
|
widget.NewLabel("Template List Item"),
|
||
|
)
|
||
|
},
|
||
|
func(i widget.ListItemID, o fyne.CanvasObject) {
|
||
|
// create a message with everything but the date and time
|
||
|
message := entries[i].File + ":" + entries[i].Line + " | " + entries[i].Message
|
||
|
dateTime := entries[i].Date + "-" + entries[i].Time
|
||
|
// set the button to copy the message to the clipboard
|
||
|
o.(*fyne.Container).Objects[0].(*widget.Button).OnTapped = func() {
|
||
|
window.Clipboard().SetContent(dateTime + " | " + entries[i].File + ":" + entries[i].Line + " | " + entries[i].Message)
|
||
|
}
|
||
|
// set the label to the message
|
||
|
o.(*fyne.Container).Objects[1].(*widget.Label).SetText(message)
|
||
|
// set the button text to the date and time
|
||
|
o.(*fyne.Container).Objects[0].(*widget.Button).SetText(dateTime)
|
||
|
},
|
||
|
)
|
||
|
|
||
|
LogDialog = dialog.NewCustom("Log", "Close", LogList, window)
|
||
|
|
||
|
// listen for a Escape keypress, hide dialog on fired event
|
||
|
window.Canvas().SetOnTypedKey(func(key *fyne.KeyEvent) {
|
||
|
if key.Name == fyne.KeyEscape {
|
||
|
LogDialog.Hide()
|
||
|
}
|
||
|
})
|
||
|
|
||
|
go func() {
|
||
|
for {
|
||
|
if LogDialog != nil {
|
||
|
// get the size of the screen
|
||
|
size := window.Canvas().Size()
|
||
|
// set the size of the dialog to 80% of the screen
|
||
|
LogDialog.Resize(fyne.NewSize(size.Width*0.8, size.Height*0.8))
|
||
|
}
|
||
|
time.Sleep(time.Millisecond)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
go func() {
|
||
|
for logEntry := range logChannel {
|
||
|
e := parseLogEntry(logEntry)
|
||
|
entries = append(entries, e)
|
||
|
if LogDialog != nil {
|
||
|
LogDialog.Refresh()
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func parseLogEntry(logEntry string) entry {
|
||
|
// split the log entry into its parts based on spaces
|
||
|
parts := strings.SplitN(logEntry, " ", 4)
|
||
|
|
||
|
// create a new entry
|
||
|
e := entry{}
|
||
|
|
||
|
// set the date and time
|
||
|
e.Date = parts[0]
|
||
|
e.Time = parts[1]
|
||
|
|
||
|
// further split the "file:line" into file and line
|
||
|
fileAndLine := strings.Split(parts[2], ":")
|
||
|
if len(fileAndLine) >= 2 {
|
||
|
e.File = fileAndLine[0]
|
||
|
e.Line = fileAndLine[1]
|
||
|
} else {
|
||
|
e.File = parts[2] // if it doesn't split, use it as is
|
||
|
e.Line = "unknown" // we couldn't determine the line number
|
||
|
}
|
||
|
|
||
|
// set the message
|
||
|
e.Message = parts[3]
|
||
|
|
||
|
return e
|
||
|
}
|