mirror of https://github.com/nealey/spongy
Merge branch 'master' of https://github.com/nealey/spongy
Conflicts: spongyd/network.go
This commit is contained in:
commit
cfe75df50f
|
@ -0,0 +1,171 @@
|
|||
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 {
|
||||
return fmt.Sprintf("ERROR: %s", err.Error())
|
||||
}
|
||||
|
||||
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)
|
||||
for bf.Scan() {
|
||||
lineno += 1
|
||||
if lineno <= nw.lineno {
|
||||
continue
|
||||
} else {
|
||||
nw.lineno = lineno
|
||||
}
|
||||
|
||||
t := bf.Text()
|
||||
|
||||
parts := strings.Split(t, " ")
|
||||
if (len(parts) >= 3) && (parts[1] == "NEXTLOG") {
|
||||
watcher.Remove(filepath)
|
||||
filename := parts[2]
|
||||
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
|
||||
}
|
||||
out <- t
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var playback int
|
||||
var running bool = true
|
||||
|
||||
func inputLoop(nw *Network) {
|
||||
bf := bufio.NewScanner(os.Stdin)
|
||||
for bf.Scan() {
|
||||
line := bf.Bytes()
|
||||
nw.Write(line)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] NETDIR\n", os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, "NETDIR is the path to your IRC directory (see README)\n")
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
fmt.Fprintf(os.Stderr, "OPTIONS:\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.IntVar(&playback, "playback", 0, "Number of lines to play back on startup")
|
||||
|
||||
flag.Parse()
|
||||
if flag.NArg() != 1 {
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
netDir, err := filepath.Abs(flag.Arg(0))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
nw := NewNetwork(netDir)
|
||||
defer nw.Close()
|
||||
go inputLoop(nw)
|
||||
|
||||
outq := make(chan string, 50) // to stdout
|
||||
go nw.Tail(outq)
|
||||
for line := range outq {
|
||||
fmt.Println(line)
|
||||
}
|
||||
}
|
|
@ -3,10 +3,10 @@ package main
|
|||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Unparsed string
|
||||
Command string
|
||||
FullSender string
|
||||
Sender string
|
||||
|
@ -20,6 +20,7 @@ func NewMessage(v string) (Message, error) {
|
|||
var parts []string
|
||||
var lhs string
|
||||
|
||||
m.Unparsed = v
|
||||
parts = strings.SplitN(v, " :", 2)
|
||||
if len(parts) == 2 {
|
||||
lhs = parts[0]
|
||||
|
@ -98,11 +99,9 @@ func NewMessage(v string) (Message, error) {
|
|||
}
|
||||
|
||||
func (m Message) String() string {
|
||||
args := strings.Join(m.Args, " ")
|
||||
return fmt.Sprintf("%s %s %s %s %s :%s", m.FullSender, m.Command, m.Sender, m.Forum, args, m.Text)
|
||||
return m.Unparsed
|
||||
}
|
||||
|
||||
|
||||
func SplitTarget(s string) (string, string, string) {
|
||||
var parts []string
|
||||
|
||||
|
@ -130,4 +129,3 @@ func IsChannel(s string) bool {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"log"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
@ -13,25 +14,54 @@ type Logfile struct {
|
|||
name string
|
||||
nlines int
|
||||
maxlines int
|
||||
outq chan string
|
||||
}
|
||||
|
||||
func timestamp(s string) string {
|
||||
ret := fmt.Sprintf("%d %s", time.Now().Unix(), s)
|
||||
return ret
|
||||
}
|
||||
|
||||
func NewLogfile(baseDir string, maxlines int) (*Logfile) {
|
||||
return &Logfile{baseDir, nil, "", 0, maxlines}
|
||||
lf := Logfile{baseDir, nil, "", 0, maxlines, make(chan string, 50)}
|
||||
go lf.processQueue();
|
||||
return &lf
|
||||
}
|
||||
|
||||
func (lf *Logfile) Close() {
|
||||
if lf.file != nil {
|
||||
lf.writeln("EXIT")
|
||||
lf.file.Close()
|
||||
lf.Log("EXIT")
|
||||
close(lf.outq)
|
||||
}
|
||||
}
|
||||
|
||||
func (lf *Logfile) writeln(s string) error {
|
||||
_, err := fmt.Fprintf(lf.file, "%d %s\n", time.Now().Unix(), s)
|
||||
if err == nil {
|
||||
func (lf *Logfile) Log(s string) error {
|
||||
lf.outq <- timestamp(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
|
||||
func (lf *Logfile) processQueue() {
|
||||
for line := range lf.outq {
|
||||
if (lf.file == nil) || (lf.nlines >= lf.maxlines) {
|
||||
if err := lf.rotate(); err != nil {
|
||||
// Just keep trying, I guess.
|
||||
log.Print(err)
|
||||
continue
|
||||
}
|
||||
lf.nlines = 0
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintln(lf.file, line); err != nil {
|
||||
log.Print(err)
|
||||
continue
|
||||
}
|
||||
lf.nlines += 1
|
||||
}
|
||||
return err
|
||||
|
||||
lf.file.Close()
|
||||
}
|
||||
|
||||
func (lf *Logfile) rotate() error {
|
||||
|
@ -45,15 +75,15 @@ func (lf *Logfile) rotate() error {
|
|||
currentPath := path.Join(lf.baseDir, "log", "current")
|
||||
|
||||
if lf.file == nil {
|
||||
// Set lf.file just so we can write out NEXTLOG.
|
||||
// If this fails, that's okay
|
||||
// Open "current" to append a NEXTLOG line.
|
||||
// If there's no "current", that's okay
|
||||
lf.file, _ = os.OpenFile(currentPath, os.O_WRONLY|os.O_APPEND, 0666)
|
||||
}
|
||||
|
||||
if lf.file != nil {
|
||||
// Note location of new log
|
||||
logmsg := fmt.Sprintf(". NEXTLOG %s", fn)
|
||||
lf.writeln(logmsg)
|
||||
logmsg := fmt.Sprintf("NEXTLOG %s", fn)
|
||||
fmt.Fprintln(lf.file, timestamp(logmsg))
|
||||
|
||||
// All done with the current log
|
||||
lf.file.Close()
|
||||
|
@ -66,27 +96,10 @@ func (lf *Logfile) rotate() error {
|
|||
os.Remove(currentPath)
|
||||
os.Symlink(fn, currentPath)
|
||||
|
||||
logmsg := fmt.Sprintf(". PREVLOG %s", lf.name)
|
||||
lf.writeln(logmsg)
|
||||
logmsg := fmt.Sprintf("PREVLOG %s", lf.name)
|
||||
fmt.Fprintln(lf.file, timestamp(logmsg))
|
||||
|
||||
lf.name = fn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lf *Logfile) Log(s string) error {
|
||||
if lf.file == nil {
|
||||
lf.rotate()
|
||||
}
|
||||
|
||||
err := lf.writeln(s)
|
||||
if err == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if lf.nlines >= lf.maxlines {
|
||||
return lf.rotate()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net"
|
||||
"os"
|
||||
"os/user"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -48,7 +49,9 @@ type Network struct {
|
|||
serverIndex int
|
||||
|
||||
conn io.ReadWriteCloser
|
||||
logq chan Message
|
||||
|
||||
logf *Logfile
|
||||
|
||||
inq chan string
|
||||
outq chan string
|
||||
}
|
||||
|
@ -57,23 +60,21 @@ func NewNetwork(basePath string) *Network {
|
|||
nw := Network{
|
||||
running: true,
|
||||
basePath: basePath,
|
||||
logq: make(chan Message, 20),
|
||||
}
|
||||
|
||||
go nw.LogLoop()
|
||||
nw.logf = NewLogfile(nw.basePath, int(maxlogsize))
|
||||
|
||||
return &nw
|
||||
}
|
||||
|
||||
func (nw *Network) Close() {
|
||||
nw.running = false
|
||||
close(nw.logq)
|
||||
if nw.conn != nil {
|
||||
nw.conn.Close()
|
||||
}
|
||||
nw.logf.Close()
|
||||
}
|
||||
|
||||
func (nw *Network) WatchOutqDirectory() {
|
||||
func (nw *Network) watchOutqDirectory() {
|
||||
outqDirname := path.Join(nw.basePath, "outq")
|
||||
|
||||
dir, err := os.Open(outqDirname)
|
||||
|
@ -113,20 +114,12 @@ func (nw *Network) HandleInfile(fn string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (nw *Network) LogLoop() {
|
||||
logf := NewLogfile(nw.basePath, int(maxlogsize))
|
||||
defer logf.Close()
|
||||
|
||||
for m := range nw.logq {
|
||||
logf.Log(m.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (nw *Network) ServerWriteLoop() {
|
||||
func (nw *Network) serverWriteLoop() {
|
||||
for v := range nw.outq {
|
||||
m, _ := NewMessage(v)
|
||||
nw.logq <- m
|
||||
debug("» %s", v)
|
||||
nw.logf.Log(v)
|
||||
fmt.Fprintln(nw.conn, v)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,21 +156,20 @@ func (nw *Network) JoinChannels() {
|
|||
}
|
||||
|
||||
for _, ch := range chans {
|
||||
debug("Joining %s", ch)
|
||||
nw.outq <- "JOIN " + ch
|
||||
}
|
||||
}
|
||||
|
||||
func (nw *Network) MessageDispatch() {
|
||||
func (nw *Network) messageDispatchLoop() {
|
||||
for line := range nw.inq {
|
||||
nw.logf.Log(line)
|
||||
|
||||
m, err := NewMessage(line)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
continue
|
||||
}
|
||||
|
||||
nw.logq <- m
|
||||
|
||||
// XXX: Add in a handler subprocess call
|
||||
|
||||
switch m.Command {
|
||||
case "PING":
|
||||
|
@ -196,6 +188,32 @@ func (nw *Network) MessageDispatch() {
|
|||
nw.outq <- "NOTICE " + m.Sender + " :\001 VERSION end\001"
|
||||
}
|
||||
}
|
||||
|
||||
handlerPath := path.Join(nw.basePath, "handler")
|
||||
cmd := exec.Command(handlerPath, m.Args...)
|
||||
cmd.Env = []string{
|
||||
"command=" + m.Command,
|
||||
"fullsender=" + m.FullSender,
|
||||
"sender=" + m.Sender,
|
||||
"forum=" + m.Forum,
|
||||
"text=" + m.Text,
|
||||
"raw=" + line,
|
||||
}
|
||||
cmd.Stderr = os.Stderr
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(out) > 0 {
|
||||
outlines := strings.Split(string(out), "\n")
|
||||
for _, line := range outlines {
|
||||
if len(line) > 0 {
|
||||
nw.outq <- line
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,6 +229,7 @@ func (nw *Network) ConnectToNextServer() bool {
|
|||
}
|
||||
server := servers[nw.serverIndex]
|
||||
|
||||
debug("Connecting to %s", server)
|
||||
switch (server[0]) {
|
||||
case '|':
|
||||
parts := strings.Split(server[1:], " ")
|
||||
|
@ -229,12 +248,21 @@ func (nw *Network) ConnectToNextServer() bool {
|
|||
log.Print(err)
|
||||
return false
|
||||
}
|
||||
debug("Connected")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (nw *Network) login() {
|
||||
var name string
|
||||
var username string
|
||||
|
||||
usernames, err := ReadLines(path.Join(nw.basePath, "username"))
|
||||
if err == nil {
|
||||
username = usernames[0]
|
||||
} else {
|
||||
username = "sponge"
|
||||
}
|
||||
|
||||
passwd, err := ReadLines(path.Join(nw.basePath, "passwd"))
|
||||
if err == nil {
|
||||
|
@ -255,15 +283,24 @@ func (nw *Network) login() {
|
|||
}
|
||||
|
||||
if name == "" {
|
||||
name = "Charlie"
|
||||
// Rogue used "Rodney" if you didn't give it a name.
|
||||
// This one works for the ladies, too.
|
||||
name = "Ronnie"
|
||||
}
|
||||
|
||||
nw.outq <- "USER g g g :" + name
|
||||
nw.outq <- "USER " + username + " g g :" + name
|
||||
nw.NextNick()
|
||||
}
|
||||
|
||||
func (nw *Network) keepaliveLoop() {
|
||||
for nw.running {
|
||||
time.Sleep(1 * time.Minute)
|
||||
nw.outq <- "PING :keepalive"
|
||||
}
|
||||
}
|
||||
|
||||
func (nw *Network) Connect(){
|
||||
|
||||
func (nw *Network) Connect() {
|
||||
for nw.running {
|
||||
if ! nw.ConnectToNextServer() {
|
||||
time.Sleep(8 * time.Second)
|
||||
|
@ -273,9 +310,10 @@ func (nw *Network) Connect(){
|
|||
nw.inq = make(chan string, 20)
|
||||
nw.outq = make(chan string, 20)
|
||||
|
||||
go nw.ServerWriteLoop()
|
||||
go nw.MessageDispatch()
|
||||
go nw.WatchOutqDirectory()
|
||||
go nw.serverWriteLoop()
|
||||
go nw.messageDispatchLoop()
|
||||
go nw.watchOutqDirectory()
|
||||
go nw.keepaliveLoop()
|
||||
|
||||
nw.login()
|
||||
|
|
@ -11,8 +11,15 @@ import (
|
|||
)
|
||||
|
||||
var running bool = true
|
||||
var verbose bool = false
|
||||
var maxlogsize uint
|
||||
|
||||
func debug(format string, a ...interface{}) {
|
||||
if verbose {
|
||||
log.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
func exists(filename string) bool {
|
||||
_, err := os.Stat(filename); if err != nil {
|
||||
return false
|
||||
|
@ -78,8 +85,9 @@ func usage() {
|
|||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.UintVar(&maxlogsize, "logsize", 1000, "Log entries before rotating")
|
||||
notime := flag.Bool("notime", false, "Don't timestamp log messages")
|
||||
flag.UintVar(&maxlogsize, "logsize", 6000, "Log entries before rotating")
|
||||
flag.BoolVar(&verbose, "verbose", false, "Verbose logging")
|
||||
notime := flag.Bool("notime", false, "Don't timestamp debugging messages")
|
||||
flag.Parse()
|
||||
if flag.NArg() != 1 {
|
||||
usage()
|
Loading…
Reference in New Issue