This commit is contained in:
Neale Pickett 2015-02-23 02:45:05 +00:00
commit ec4b18892e
4 changed files with 269 additions and 121 deletions

37
spongy.cgi/config.go Normal file
View File

@ -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
}

177
spongy.cgi/network.go Normal file
View File

@ -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.

View File

@ -2,16 +2,10 @@ package main
import (
"fmt"
"github.com/go-fsnotify/fsnotify"
"io/ioutil"
"log"
"os"
"bufio"
"strconv"
"strings"
"net/http"
"net/http/cgi"
"time"
"path"
)
@ -19,144 +13,84 @@ type Handler struct {
cgi.Handler
}
var NetworkDir string
func ReadString(fn string) string {
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)
}
}
func (h Handler) handleCommand(cfg *Config, w http.ResponseWriter, r *http.Request) {
network := r.FormValue("network")
text := r.FormValue("text")
target := r.FormValue("target")
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()
nw := NewNetwork(path.Join(cfg.BaseDir, network))
var out string
switch {
case strings.HasPrefix(text, "/quote "):
fmt.Fprintln(f, text[7:])
out = text[7:]
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:
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")
}
func (h Handler) handleTail(cfg *Config, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
nws := Networks(cfg.BaseDir)
lastEventId := r.FormValue("HTTP_LAST_EVENT_ID")
updates := make(chan []string, 100)
for _, nw := range nws {
nw.ReadLastEventId(lastEventId)
go nw.Tail(updates)
defer nw.Close()
}
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) {
BaseDir := "networks"
DefaultDir := path.Join(BaseDir, "default")
NetworkDir = path.Join(BaseDir, r.FormValue("network"))
if path.Dir(DefaultDir) != path.Dir(NetworkDir) {
NetworkDir = DefaultDir
cfg, err := ReadConfig(h.Dir)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
authtok := ReadString(path.Join(NetworkDir, "authtok"))
// Validate authtok
authtok, err := cfg.Get("authtok")
if err != nil {
http.Error(w, err.Error(), 500)
return
}
if r.FormValue("auth") != authtok {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintln(w, "NO: Invalid authtok")
return
}
// Switch based on type
switch r.FormValue("type") {
case "command":
w.Header().Set("Content-Type", "text/plain")
handleCommand(w, r.Form.Get("text"), r.FormValue("target"))
h.handleCommand(cfg, w, r)
default:
w.Header().Set("Content-Type", "text/event-stream")
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)
}
h.handleTail(cfg, w, r)
}
}