1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "os/exec" 9 "path" 10 "sync" 11 "time" 12 13 "vimagination.zapto.org/minewebgen/internal/data" 14 ) 15 16 type runner struct { 17 s *data.Server 18 shutdown chan struct{} 19 io.Writer 20 } 21 22 type Controller struct { 23 c *Config 24 25 mu sync.RWMutex 26 running map[int]*runner 27 28 w sync.WaitGroup 29 } 30 31 func NewController(c *Config) *Controller { 32 return &Controller{ 33 c: c, 34 running: make(map[int]*runner), 35 } 36 } 37 38 func (c *Controller) StartServer(id int, _ *struct{}) error { 39 s := c.c.Server(id) 40 if s == nil { 41 return ErrUnknownServer 42 } 43 s.Lock() 44 defer s.Unlock() 45 if s.State != data.StateStopped { 46 return ErrServerRunning 47 } 48 m := c.c.Map(s.Map) 49 if m == nil { 50 return ErrUnknownServer 51 } 52 m.RLock() 53 defer m.RUnlock() 54 mapPath := m.Path 55 if !path.IsAbs(mapPath) { 56 pwd, err := os.Getwd() 57 if err != nil { 58 return err 59 } 60 mapPath = path.Join(pwd, mapPath) 61 } 62 serverMapPath := path.Join(s.Path, "world") 63 if err := os.Remove(serverMapPath); err != nil && !os.IsNotExist(err) { 64 return err 65 } 66 if err := os.Symlink(mapPath, serverMapPath); err != nil { 67 return err 68 } 69 sp := make(ServerProperties) 70 f, err := os.Open(path.Join(s.Path, "properties.server")) 71 if err != nil { 72 return err 73 } 74 sp.ReadFrom(f) 75 f.Close() 76 if err != nil { 77 return err 78 } 79 f, err = os.Open(path.Join(m.Path, "properties.map")) 80 if err != nil { 81 return err 82 } 83 err = sp.ReadFrom(f) 84 f.Close() 85 if err != nil { 86 return err 87 } 88 sp["level-name"] = "world" 89 f, err = os.Create(path.Join(s.Path, "server.properties")) 90 if err != nil { 91 return err 92 } 93 sp.WriteTo(f) 94 f.Close() 95 s.State = data.StateStarting 96 r := &runner{ 97 s: s, 98 shutdown: make(chan struct{}, 1), 99 } 100 go c.run(r) 101 return nil 102 } 103 104 func (c *Controller) StopServer(id int, _ *struct{}) error { 105 c.mu.Lock() 106 defer c.mu.Unlock() 107 r, ok := c.running[id] 108 if !ok { 109 return errors.New("server not running") 110 } 111 close(r.shutdown) 112 delete(c.running, id) 113 return nil 114 } 115 116 func (c *Controller) stopAll() { 117 for _, r := range c.running { 118 close(r.shutdown) 119 } 120 c.w.Wait() 121 } 122 123 var stopCmd = []byte{'s', 't', 'o', 'p', '\r', '\n'} 124 125 // runs in its own goroutine 126 func (c *Controller) run(r *runner) { 127 c.w.Add(1) 128 defer c.w.Done() 129 cmd := exec.Command("java", append(r.s.Args, "-jar", "server.jar", "nogui")...) 130 cmd.Dir = r.s.Path 131 r.Writer, _ = cmd.StdinPipe() 132 err := cmd.Start() 133 if err != nil { 134 fmt.Fprintln(os.Stderr, err) 135 } else { 136 c.mu.Lock() 137 c.running[r.s.ID] = r 138 c.mu.Unlock() 139 r.s.Lock() 140 r.s.State = data.StateRunning 141 r.s.Unlock() 142 died := make(chan struct{}) 143 go func() { 144 select { 145 case <-r.shutdown: 146 r.s.Lock() 147 r.s.State = data.StateStopping 148 r.s.Unlock() 149 t := time.NewTimer(time.Second * 10) 150 defer t.Stop() 151 for i := 0; i < 6; i++ { 152 r.Write(stopCmd) 153 select { 154 case <-died: 155 return 156 case <-t.C: 157 } 158 } 159 cmd.Process.Kill() 160 case <-died: 161 c.mu.Lock() 162 delete(c.running, r.s.ID) 163 c.mu.Unlock() 164 } 165 }() 166 cmd.Wait() 167 r.shutdown = nil 168 close(died) 169 } 170 r.s.Lock() 171 r.s.State = data.StateStopped 172 r.s.Unlock() 173 } 174 175 func (c *Controller) WriteCmd(d data.WriteCmd, _ *struct{}) error { 176 c.mu.RLock() 177 defer c.mu.RUnlock() 178 r, ok := c.running[d.ID] 179 if !ok { 180 return ErrUnknownServer 181 } 182 toWrite := make([]byte, 0, len(d.Cmd)+2) 183 toWrite = append(toWrite, d.Cmd...) 184 toWrite = append(toWrite, '\r', '\n') 185 _, err := r.Write(toWrite) 186 return err 187 }