mirror of https://github.com/nealey/spongy
Merge branch 'master' of https://github.com/nealey/spongy
This commit is contained in:
commit
ec4b18892e
|
@ -0,0 +1,37 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
BaseDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func readString(filename string) (string, error) {
|
||||||
|
octets, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(octets)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func ReadConfig(cgiDir string) (*Config, error) {
|
||||||
|
cfgfn := path.Join(cgiDir, "spongy.cfg")
|
||||||
|
basePath, err := readString(cfgfn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Config{basePath}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Get(name string) (string, error) {
|
||||||
|
path := path.Join(c.BaseDir, name)
|
||||||
|
val, err := readString(path)
|
||||||
|
return val, err
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-fsnotify/fsnotify"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const eventIdSep = "/"
|
||||||
|
|
||||||
|
type Network struct {
|
||||||
|
running bool
|
||||||
|
|
||||||
|
Name string
|
||||||
|
currentLog string
|
||||||
|
lineno int64
|
||||||
|
|
||||||
|
basePath string
|
||||||
|
seq int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNetwork(basePath string) (*Network) {
|
||||||
|
return &Network{
|
||||||
|
running: true,
|
||||||
|
Name: path.Base(basePath),
|
||||||
|
basePath: basePath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nw *Network) Close() {
|
||||||
|
nw.running = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nw *Network) ReadLastEventId(lastEventId string) {
|
||||||
|
for _, eventId := range strings.Split(lastEventId, " ") {
|
||||||
|
parts := strings.Split(eventId, eventIdSep)
|
||||||
|
if len(parts) != 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if parts[0] != nw.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nw.currentLog = parts[1]
|
||||||
|
nw.lineno, _ = strconv.ParseInt(parts[2], 10, 64)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nw *Network) LastEventId() string {
|
||||||
|
parts := []string{nw.Name, nw.currentLog, strconv.FormatInt(nw.lineno, 10)}
|
||||||
|
return strings.Join(parts, eventIdSep)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nw *Network) errmsg(err error) []string {
|
||||||
|
s := fmt.Sprintf("ERROR: %s", err.Error())
|
||||||
|
return []string{s}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nw *Network) Tail(out chan<- []string) {
|
||||||
|
if nw.currentLog == "" {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
currentfn := path.Join(nw.basePath, "log", "current")
|
||||||
|
nw.currentLog, err = os.Readlink(currentfn)
|
||||||
|
if err != nil {
|
||||||
|
out <- nw.errmsg(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filepath := path.Join(nw.basePath, "log", nw.currentLog)
|
||||||
|
f, err := os.Open(filepath)
|
||||||
|
if err != nil {
|
||||||
|
out <- nw.errmsg(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
out <- nw.errmsg(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
watcher.Add(filepath)
|
||||||
|
lineno := int64(0)
|
||||||
|
|
||||||
|
// XXX: some way to stop this?
|
||||||
|
for nw.running {
|
||||||
|
bf := bufio.NewScanner(f)
|
||||||
|
lines := make([]string, 0)
|
||||||
|
for bf.Scan() {
|
||||||
|
lineno += 1
|
||||||
|
if lineno <= nw.lineno {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
nw.lineno = lineno
|
||||||
|
}
|
||||||
|
|
||||||
|
t := bf.Text()
|
||||||
|
|
||||||
|
// XXX: Consider omitting PING and PONG
|
||||||
|
parts := strings.Split(t, " ")
|
||||||
|
if (len(parts) >= 4) && (parts[2] == "NEXTLOG") {
|
||||||
|
watcher.Remove(filepath)
|
||||||
|
filename := parts[3]
|
||||||
|
filepath = path.Join(nw.basePath, "log", filename)
|
||||||
|
f.Close()
|
||||||
|
f, err = os.Open(filepath)
|
||||||
|
if err != nil {
|
||||||
|
out <- nw.errmsg(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
watcher.Add(filepath)
|
||||||
|
lineno = 0
|
||||||
|
nw.lineno = 0
|
||||||
|
}
|
||||||
|
lines = append(lines, t)
|
||||||
|
}
|
||||||
|
if len(lines) > 0 {
|
||||||
|
out <- lines
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case _ = <-watcher.Events:
|
||||||
|
// Somethin' happened!
|
||||||
|
case err := <-watcher.Errors:
|
||||||
|
out <- nw.errmsg(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nw *Network) Write(data []byte) {
|
||||||
|
epoch := time.Now().Unix()
|
||||||
|
pid := os.Getpid()
|
||||||
|
filename := fmt.Sprintf("%d-%d-%d.txt", epoch, pid, nw.seq)
|
||||||
|
|
||||||
|
filepath := path.Join(nw.basePath, "outq", filename)
|
||||||
|
ioutil.WriteFile(filepath, data, 0750)
|
||||||
|
nw.seq += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func Networks(basePath string) (found []*Network) {
|
||||||
|
|
||||||
|
dir, err := os.Open(basePath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer dir.Close()
|
||||||
|
|
||||||
|
|
||||||
|
entities, _ := dir.Readdirnames(0)
|
||||||
|
for _, fn := range entities {
|
||||||
|
netdir := path.Join(basePath, fn)
|
||||||
|
|
||||||
|
_, err = os.Stat(path.Join(netdir, "nick"))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nw := NewNetwork(netdir)
|
||||||
|
found = append(found, nw)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
Binary file not shown.
|
@ -2,16 +2,10 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-fsnotify/fsnotify"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"bufio"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cgi"
|
"net/http/cgi"
|
||||||
"time"
|
|
||||||
"path"
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,144 +13,84 @@ type Handler struct {
|
||||||
cgi.Handler
|
cgi.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
var NetworkDir string
|
func (h Handler) handleCommand(cfg *Config, w http.ResponseWriter, r *http.Request) {
|
||||||
|
network := r.FormValue("network")
|
||||||
|
text := r.FormValue("text")
|
||||||
|
target := r.FormValue("target")
|
||||||
|
|
||||||
func ReadString(fn string) string {
|
nw := NewNetwork(path.Join(cfg.BaseDir, network))
|
||||||
octets, err := ioutil.ReadFile(fn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(string(octets))
|
|
||||||
}
|
|
||||||
|
|
||||||
func tail(w http.ResponseWriter, filename string, pos int64) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
currentfn := path.Join(NetworkDir, "current")
|
|
||||||
if filename == "" {
|
|
||||||
filename, err = os.Readlink(currentfn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filepath := path.Join(NetworkDir, filename)
|
|
||||||
|
|
||||||
f, err := os.Open(filepath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
watcher, err := fsnotify.NewWatcher()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer watcher.Close()
|
|
||||||
watcher.Add(filepath)
|
|
||||||
|
|
||||||
for {
|
|
||||||
printid := false
|
|
||||||
|
|
||||||
newpos, err := f.Seek(pos, 0)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if newpos != pos {
|
|
||||||
log.Fatal("Lost my position in the log, somehow (log truncated?)")
|
|
||||||
}
|
|
||||||
|
|
||||||
bf := bufio.NewScanner(f)
|
|
||||||
for bf.Scan() {
|
|
||||||
t := bf.Text()
|
|
||||||
pos += int64(len(t)) + 1 // XXX: this breaks if we ever see \r\n
|
|
||||||
|
|
||||||
parts := strings.Split(t, " ")
|
|
||||||
if (len(parts) >= 4) && (parts[2] == "NEXTLOG") {
|
|
||||||
watcher.Remove(filepath)
|
|
||||||
filename = parts[3]
|
|
||||||
filepath = path.Join(NetworkDir, filename)
|
|
||||||
f.Close()
|
|
||||||
f, err = os.Open(filepath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
watcher.Add(filepath)
|
|
||||||
pos = 0
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "data: %s\n", t)
|
|
||||||
printid = true
|
|
||||||
}
|
|
||||||
if printid {
|
|
||||||
_, err = fmt.Fprintf(w, "id: %s/%d\n\n", filename, pos)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
w.(http.Flusher).Flush()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case _ = <-watcher.Events:
|
|
||||||
// Somethin' happened!
|
|
||||||
case err := <-watcher.Errors:
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleCommand(w http.ResponseWriter, text string, target string) {
|
|
||||||
fn := path.Join(NetworkDir, fmt.Sprintf("outq/cgi.%d", time.Now().Unix()))
|
|
||||||
f, err := os.Create(fn)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(w, "NO: Cannot create outq file")
|
|
||||||
fmt.Fprintln(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
|
var out string
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(text, "/quote "):
|
case strings.HasPrefix(text, "/quote "):
|
||||||
fmt.Fprintln(f, text[7:])
|
out = text[7:]
|
||||||
case strings.HasPrefix(text, "/me "):
|
case strings.HasPrefix(text, "/me "):
|
||||||
fmt.Fprintf(f, "PRIVMSG %s :\001ACTION %s\001\n", target, text[4:])
|
out = fmt.Sprintf("PRIVMSG %s :\001ACTION %s\001", target, text[4:])
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(f, "PRIVMSG %s :%s\n", target, text)
|
out = fmt.Sprintf("PRIVMSG %s :%s", target, text)
|
||||||
}
|
}
|
||||||
|
nw.Write([]byte(out))
|
||||||
|
|
||||||
fmt.Fprintln(w, "OK")
|
fmt.Fprintln(w, "OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h Handler) handleTail(cfg *Config, w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/event-stream")
|
||||||
|
nws := Networks(cfg.BaseDir)
|
||||||
|
|
||||||
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
lastEventId := r.FormValue("HTTP_LAST_EVENT_ID")
|
||||||
BaseDir := "networks"
|
updates := make(chan []string, 100)
|
||||||
DefaultDir := path.Join(BaseDir, "default")
|
|
||||||
NetworkDir = path.Join(BaseDir, r.FormValue("network"))
|
|
||||||
|
|
||||||
if path.Dir(DefaultDir) != path.Dir(NetworkDir) {
|
for _, nw := range nws {
|
||||||
NetworkDir = DefaultDir
|
nw.ReadLastEventId(lastEventId)
|
||||||
|
go nw.Tail(updates)
|
||||||
|
defer nw.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
authtok := ReadString(path.Join(NetworkDir, "authtok"))
|
for lines := range updates {
|
||||||
|
for _, line := range lines {
|
||||||
|
fmt.Fprintf(w, "data: %s\n", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
ids := make([]string, 0)
|
||||||
|
for _, nw := range nws {
|
||||||
|
ids = append(ids, nw.LastEventId())
|
||||||
|
}
|
||||||
|
idstring := strings.Join(ids, " ")
|
||||||
|
_, err := fmt.Fprintf(w, "id: %s\n\n", idstring)
|
||||||
|
if err != nil {
|
||||||
|
// Can't write anymore, guess they hung up.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cfg, err := ReadConfig(h.Dir)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate authtok
|
||||||
|
authtok, err := cfg.Get("authtok")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
if r.FormValue("auth") != authtok {
|
if r.FormValue("auth") != authtok {
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
fmt.Fprintln(w, "NO: Invalid authtok")
|
fmt.Fprintln(w, "NO: Invalid authtok")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Switch based on type
|
||||||
switch r.FormValue("type") {
|
switch r.FormValue("type") {
|
||||||
case "command":
|
case "command":
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
h.handleCommand(cfg, w, r)
|
||||||
handleCommand(w, r.Form.Get("text"), r.FormValue("target"))
|
|
||||||
default:
|
default:
|
||||||
w.Header().Set("Content-Type", "text/event-stream")
|
h.handleTail(cfg, w, r)
|
||||||
parts := strings.Split(os.Getenv("HTTP_LAST_EVENT_ID"), "/")
|
|
||||||
if len(parts) == 2 {
|
|
||||||
filename := path.Base(parts[0])
|
|
||||||
pos, _ := strconv.ParseInt(parts[1], 0, 64)
|
|
||||||
tail(w, filename, pos)
|
|
||||||
} else {
|
|
||||||
tail(w, "", 0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue