From e269ff8aead74d9be6c51c48962b0aa3e02029bb Mon Sep 17 00:00:00 2001 From: Jan Delta Date: Sat, 26 Nov 2022 17:03:33 +0900 Subject: [PATCH] initial commit --- .gitignore | 1 + go.mod | 8 ++ go.sum | 4 + main.go | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 240 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12585c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +icon.png \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a2f8ac7 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module tildegit.org/jande/gohrl + +go 1.18 + +require ( + github.com/mattn/go-gtk v0.0.0-20191030024613-af2e013261f5 // indirect + github.com/mqu/go-notify v0.0.0-20130719194048-ef6f6f49d093 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7a80eb0 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/mattn/go-gtk v0.0.0-20191030024613-af2e013261f5 h1:GMB3MVJnxysGrSvjWGsgK8L3XGI3F4etQQq37Py6W5A= +github.com/mattn/go-gtk v0.0.0-20191030024613-af2e013261f5/go.mod h1:PwzwfeB5syFHXORC3MtPylVcjIoTDT/9cvkKpEndGVI= +github.com/mqu/go-notify v0.0.0-20130719194048-ef6f6f49d093 h1:OvySnanP8CQIKS+MTq9AXBwEXzm0YaKeu331bWql3ug= +github.com/mqu/go-notify v0.0.0-20130719194048-ef6f6f49d093/go.mod h1:AthsKyBZ9hqwU7DBWFiOxYObyF8nVyYVubXv/pQNC5E= diff --git a/main.go b/main.go new file mode 100644 index 0000000..b8413c6 --- /dev/null +++ b/main.go @@ -0,0 +1,227 @@ +package main + +import ( + "bytes" + "fmt" + "hash/fnv" + "io" + "io/fs" + "log" + "os" + "os/exec" + "os/signal" + "path/filepath" + "strconv" + "strings" + "syscall" + "time" + + "github.com/mqu/go-notify" +) + +func logg(v ...any) { + spr := fmt.Sprintln(v...) + fmt.Print("\x1B[32m", time.Now().Format("15:04:05.000"), " \x1B[93m", spr[:len(spr)-1], "\x1B[0m\n") +} + +func getChildPID(pid int) int { + get, _ := filepath.Glob("/proc/" + strconv.Itoa(pid) + "/task/*/children") + for _, x := range get { + dat, err := os.ReadFile(x) + if err != nil { + logg(err) + continue + } + if len(dat) <= 1 { + continue + } + npid, err := strconv.Atoi(string(dat)[:len(dat)-1]) + if err != nil { + logg(err) + continue + } + cmdl, err := os.ReadFile("/proc/" + strconv.Itoa(npid) + "/cmdline") + if err != nil { + logg(err) + continue + } + if !strings.HasPrefix(string(cmdl), "/tmp/go-build") { + continue + } + return npid + } + return pid +} + +func main() { + notify.Init("gohrl") + + name := os.Args[1] + args := os.Args[2:] + + cont := true + + sc := make(chan os.Signal, 1) + exit := make(chan struct{}, 1) + change := make(chan struct{}, 1) + + signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) + + var cmd *exec.Cmd + + var changes []string + go func() { + hash() + var ok bool + + for { + time.Sleep(time.Second) + + changes, ok = hash() + if !ok { + continue + } + + change <- struct{}{} + } + }() + + var not *notify.NotifyNotification + + for cont { + logg("starting:", name, args) + + cmd = exec.Command(name, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + + pid := 0 + + wait := make(chan struct{}) + go func() { + cmd.Start() + pid = cmd.Process.Pid + for i := 0; pid == cmd.Process.Pid && i < 1200; i++ { + select { + case <-exit: + exit<-struct{}{} + break + case <-change: + default: + } + time.Sleep(time.Millisecond * 100) + pid = getChildPID(cmd.Process.Pid) + } + logg("PID:", pid) + + wait <- struct{}{} + cmd.Wait() + + logg("program exited") + exit <- struct{}{} + }() + logg("waiting for start") + <-wait + + if not != nil { + not.Update("updated applied", "now running", "/data/drive/jan/hotreload/icon.png") + not.Show() + } + + kill := func() { + syscall.Kill(pid, syscall.SIGINT) + + t := time.NewTimer(time.Second * 10) + + select { + case <-exit: + return + case <-t.C: + logg("program didn't exit: killing") + syscall.Kill(cmd.Process.Pid, syscall.SIGTERM) + } + } + + select { + case <-sc: + logg("inturrupting") + cont = false + kill() + case <-change: + logg("change to code detected") + not = notify.NotificationNew("updating", strings.Join(changes, "\n"), "/data/drive/jan/hotreload/icon.png") + not.SetTimeout(2500) + not.Show() + + logg("waiting for exit") + kill() + logg("restarting to apply update") + + case <-exit: + logg("exit receieved, restarting") + if !cmd.ProcessState.Success() { + logg("possible error, waiting for next update") + <-change + } + } + + time.Sleep(time.Second) + logg("releasing") + cmd.Process.Release() + } +} + +var hashes = make(map[string][]byte) + +var pt = false + +func hash() ([]string, bool) { + dif := false + var changes = []string{} + + err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error { + if !strings.HasSuffix(d.Name(), ".go") { + return nil + } + + dat, err := os.Open(filepath.Join(path)) + defer dat.Close() + if err != nil { + return err + } + + var pfx = make([]byte, len("package")) + dat.Read(pfx) + if string(pfx) != "package" { + return nil + } + dat.Seek(0, 0) + + h := fnv.New128() + _, err = io.Copy(h, dat) + if err != nil { + return err + } + + var b []byte + b = h.Sum(b) + + if bytes.Compare(hashes[dat.Name()], b) != 0 { + if pt { + logg(dat.Name(), "changed, recompiling and reloading") + changes = append(changes, dat.Name()) + } + dif = true + } + hashes[dat.Name()] = b + + return nil + }) + if err != nil { + log.Fatalln(err) + } + pt = true + + return changes, dif +}