1package telegram
2
3import (
4 "errors"
5 "fmt"
6 "os"
7 "path/filepath"
8 "strconv"
9 "strings"
10 "time"
11)
12
13// PIDPath returns the location of the bot's pid file.
14func PIDPath(zotHome string) string {
15 return filepath.Join(zotHome, "bot.pid")
16}
17
18// LogPath returns the location of the bot's log file (stdout+stderr
19// from a detached `zot bot start`).
20func LogPath(zotHome string) string {
21 return filepath.Join(zotHome, "logs", "bot.log")
22}
23
24// WritePID persists pid to bot.pid. Overwrites any existing file.
25func WritePID(zotHome string, pid int) error {
26 p := PIDPath(zotHome)
27 if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
28 return err
29 }
30 return os.WriteFile(p, []byte(strconv.Itoa(pid)+"\n"), 0o644)
31}
32
33// ReadPID returns the pid stored in bot.pid, or 0 if the file doesn't
34// exist. Returns an error for any other read/parse failure.
35func ReadPID(zotHome string) (int, error) {
36 b, err := os.ReadFile(PIDPath(zotHome))
37 if errors.Is(err, os.ErrNotExist) {
38 return 0, nil
39 }
40 if err != nil {
41 return 0, err
42 }
43 pid, err := strconv.Atoi(strings.TrimSpace(string(b)))
44 if err != nil {
45 return 0, fmt.Errorf("parse pid: %w", err)
46 }
47 return pid, nil
48}
49
50// RemovePID deletes the pid file if it exists.
51func RemovePID(zotHome string) error {
52 err := os.Remove(PIDPath(zotHome))
53 if errors.Is(err, os.ErrNotExist) {
54 return nil
55 }
56 return err
57}
58
59// IsRunning returns (pid, true) if a live process with the recorded
60// pid exists, or (pid, false) if the pid file points to a dead process.
61// Stale pid files are left in place; the caller may remove them.
62func IsRunning(zotHome string) (int, bool, error) {
63 pid, err := ReadPID(zotHome)
64 if err != nil {
65 return 0, false, err
66 }
67 if pid <= 0 {
68 return 0, false, nil
69 }
70 alive, err := processAlive(pid)
71 if err != nil {
72 return pid, false, nil
73 }
74 return pid, alive, nil
75}
76
77// StopProcess asks pid to exit and waits up to graceful for it to stop,
78// then escalates to a forced kill. Returns nil if the process is gone.
79func StopProcess(pid int, graceful time.Duration) error {
80 return stopProcess(pid, graceful)
81}