- commit
- cfe75df
- parent
- 9b44355
- author
- Neale Pickett
- date
- 2016-01-28 09:11:07 -0700 MST
Merge branch 'master' of https://github.com/nealey/spongy Conflicts: spongyd/network.go
10 files changed,
+349,
-66
+171,
-0
1@@ -0,0 +1,171 @@
2+package main
3+
4+import (
5+ "bufio"
6+ "fmt"
7+ "github.com/go-fsnotify/fsnotify"
8+ "io/ioutil"
9+ "os"
10+ "path"
11+ "strconv"
12+ "strings"
13+ "time"
14+)
15+
16+const eventIdSep = "/"
17+
18+type Network struct {
19+ running bool
20+
21+ Name string
22+ currentLog string
23+ lineno int64
24+
25+ basePath string
26+ seq int
27+}
28+
29+func NewNetwork(basePath string) (*Network) {
30+ return &Network{
31+ running: true,
32+ Name: path.Base(basePath),
33+ basePath: basePath,
34+ }
35+}
36+
37+func (nw *Network) Close() {
38+ nw.running = false
39+}
40+
41+func (nw *Network) ReadLastEventId(lastEventId string) {
42+ for _, eventId := range strings.Split(lastEventId, " ") {
43+ parts := strings.Split(eventId, eventIdSep)
44+ if len(parts) != 3 {
45+ continue
46+ }
47+
48+ if parts[0] != nw.Name {
49+ continue
50+ }
51+ nw.currentLog = parts[1]
52+ nw.lineno, _ = strconv.ParseInt(parts[2], 10, 64)
53+ return
54+ }
55+}
56+
57+func (nw *Network) LastEventId() string {
58+ parts := []string{nw.Name, nw.currentLog, strconv.FormatInt(nw.lineno, 10)}
59+ return strings.Join(parts, eventIdSep)
60+}
61+
62+func (nw *Network) errmsg(err error) string {
63+ return fmt.Sprintf("ERROR: %s", err.Error())
64+}
65+
66+func (nw *Network) Tail(out chan<- string) {
67+ if nw.currentLog == "" {
68+ var err error
69+
70+ currentfn := path.Join(nw.basePath, "log", "current")
71+ nw.currentLog, err = os.Readlink(currentfn)
72+ if err != nil {
73+ out <- nw.errmsg(err)
74+ return
75+ }
76+ }
77+
78+ filepath := path.Join(nw.basePath, "log", nw.currentLog)
79+ f, err := os.Open(filepath)
80+ if err != nil {
81+ out <- nw.errmsg(err)
82+ return
83+ }
84+ defer f.Close()
85+
86+ watcher, err := fsnotify.NewWatcher()
87+ if err != nil {
88+ out <- nw.errmsg(err)
89+ return
90+ }
91+ defer watcher.Close()
92+
93+ watcher.Add(filepath)
94+ lineno := int64(0)
95+
96+ // XXX: some way to stop this?
97+ for nw.running {
98+ bf := bufio.NewScanner(f)
99+ for bf.Scan() {
100+ lineno += 1
101+ if lineno <= nw.lineno {
102+ continue
103+ } else {
104+ nw.lineno = lineno
105+ }
106+
107+ t := bf.Text()
108+
109+ parts := strings.Split(t, " ")
110+ if (len(parts) >= 3) && (parts[1] == "NEXTLOG") {
111+ watcher.Remove(filepath)
112+ filename := parts[2]
113+ filepath = path.Join(nw.basePath, "log", filename)
114+ f.Close()
115+ f, err = os.Open(filepath)
116+ if err != nil {
117+ out <- nw.errmsg(err)
118+ return
119+ }
120+ watcher.Add(filepath)
121+ lineno = 0
122+ nw.lineno = 0
123+ }
124+ out <- t
125+ }
126+
127+ select {
128+ case _ = <-watcher.Events:
129+ // Somethin' happened!
130+ case err := <-watcher.Errors:
131+ out <- nw.errmsg(err)
132+ return
133+ }
134+ }
135+}
136+
137+func (nw *Network) Write(data []byte) {
138+ epoch := time.Now().Unix()
139+ pid := os.Getpid()
140+ filename := fmt.Sprintf("%d-%d-%d.txt", epoch, pid, nw.seq)
141+
142+ filepath := path.Join(nw.basePath, "outq", filename)
143+ ioutil.WriteFile(filepath, data, 0750)
144+ nw.seq += 1
145+}
146+
147+
148+func Networks(basePath string) (found []*Network) {
149+
150+ dir, err := os.Open(basePath)
151+ if err != nil {
152+ return
153+ }
154+ defer dir.Close()
155+
156+
157+ entities, _ := dir.Readdirnames(0)
158+ for _, fn := range entities {
159+ netdir := path.Join(basePath, fn)
160+
161+ _, err = os.Stat(path.Join(netdir, "nick"))
162+ if err != nil {
163+ continue
164+ }
165+
166+ nw := NewNetwork(netdir)
167+ found = append(found, nw)
168+ }
169+
170+ return
171+}
172+
+55,
-0
1@@ -0,0 +1,55 @@
2+package main
3+
4+import (
5+ "bufio"
6+ "fmt"
7+ "flag"
8+ "log"
9+ "os"
10+ "path/filepath"
11+)
12+
13+var playback int
14+var running bool = true
15+
16+func inputLoop(nw *Network) {
17+ bf := bufio.NewScanner(os.Stdin)
18+ for bf.Scan() {
19+ line := bf.Bytes()
20+ nw.Write(line)
21+ }
22+}
23+
24+func usage() {
25+ fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] NETDIR\n", os.Args[0])
26+ fmt.Fprintf(os.Stderr, "\n")
27+ fmt.Fprintf(os.Stderr, "NETDIR is the path to your IRC directory (see README)\n")
28+ fmt.Fprintf(os.Stderr, "\n")
29+ fmt.Fprintf(os.Stderr, "OPTIONS:\n")
30+ flag.PrintDefaults()
31+}
32+
33+func main() {
34+ flag.Usage = usage
35+ flag.IntVar(&playback, "playback", 0, "Number of lines to play back on startup")
36+
37+ flag.Parse()
38+ if flag.NArg() != 1 {
39+ usage()
40+ os.Exit(2)
41+ }
42+ netDir, err := filepath.Abs(flag.Arg(0))
43+ if err != nil {
44+ log.Fatal(err)
45+ }
46+
47+ nw := NewNetwork(netDir)
48+ defer nw.Close()
49+ go inputLoop(nw)
50+
51+ outq := make(chan string, 50) // to stdout
52+ go nw.Tail(outq)
53+ for line := range outq {
54+ fmt.Println(line)
55+ }
56+}
R spongy/irc.go =>
spongyd/irc.go
+3,
-5
1@@ -3,10 +3,10 @@ package main
2 import (
3 "strconv"
4 "strings"
5- "fmt"
6 )
7
8 type Message struct {
9+ Unparsed string
10 Command string
11 FullSender string
12 Sender string
13@@ -20,6 +20,7 @@ func NewMessage(v string) (Message, error) {
14 var parts []string
15 var lhs string
16
17+ m.Unparsed = v
18 parts = strings.SplitN(v, " :", 2)
19 if len(parts) == 2 {
20 lhs = parts[0]
21@@ -98,11 +99,9 @@ func NewMessage(v string) (Message, error) {
22 }
23
24 func (m Message) String() string {
25- args := strings.Join(m.Args, " ")
26- return fmt.Sprintf("%s %s %s %s %s :%s", m.FullSender, m.Command, m.Sender, m.Forum, args, m.Text)
27+ return m.Unparsed
28 }
29
30-
31 func SplitTarget(s string) (string, string, string) {
32 var parts []string
33
34@@ -130,4 +129,3 @@ func IsChannel(s string) bool {
35 return false
36 }
37 }
38-
R spongy/logfile.go =>
spongyd/logfile.go
+43,
-30
1@@ -3,6 +3,7 @@ package main
2 import (
3 "fmt"
4 "os"
5+ "log"
6 "path"
7 "time"
8 )
9@@ -13,25 +14,54 @@ type Logfile struct {
10 name string
11 nlines int
12 maxlines int
13+ outq chan string
14+}
15+
16+func timestamp(s string) string {
17+ ret := fmt.Sprintf("%d %s", time.Now().Unix(), s)
18+ return ret
19 }
20
21 func NewLogfile(baseDir string, maxlines int) (*Logfile) {
22- return &Logfile{baseDir, nil, "", 0, maxlines}
23+ lf := Logfile{baseDir, nil, "", 0, maxlines, make(chan string, 50)}
24+ go lf.processQueue();
25+ return &lf
26 }
27
28 func (lf *Logfile) Close() {
29 if lf.file != nil {
30- lf.writeln("EXIT")
31- lf.file.Close()
32+ lf.Log("EXIT")
33+ close(lf.outq)
34 }
35 }
36
37-func (lf *Logfile) writeln(s string) error {
38- _, err := fmt.Fprintf(lf.file, "%d %s\n", time.Now().Unix(), s)
39- if err == nil {
40+func (lf *Logfile) Log(s string) error {
41+ lf.outq <- timestamp(s)
42+ return nil
43+}
44+
45+//
46+//
47+
48+func (lf *Logfile) processQueue() {
49+ for line := range lf.outq {
50+ if (lf.file == nil) || (lf.nlines >= lf.maxlines) {
51+ if err := lf.rotate(); err != nil {
52+ // Just keep trying, I guess.
53+ log.Print(err)
54+ continue
55+ }
56+ lf.nlines = 0
57+ }
58+
59+ if _, err := fmt.Fprintln(lf.file, line); err != nil {
60+ log.Print(err)
61+ continue
62+ }
63 lf.nlines += 1
64 }
65- return err
66+
67+ lf.file.Close()
68 }
69
70 func (lf *Logfile) rotate() error {
71@@ -45,15 +75,15 @@ func (lf *Logfile) rotate() error {
72 currentPath := path.Join(lf.baseDir, "log", "current")
73
74 if lf.file == nil {
75- // Set lf.file just so we can write out NEXTLOG.
76- // If this fails, that's okay
77+ // Open "current" to append a NEXTLOG line.
78+ // If there's no "current", that's okay
79 lf.file, _ = os.OpenFile(currentPath, os.O_WRONLY|os.O_APPEND, 0666)
80 }
81
82 if lf.file != nil {
83 // Note location of new log
84- logmsg := fmt.Sprintf(". NEXTLOG %s", fn)
85- lf.writeln(logmsg)
86+ logmsg := fmt.Sprintf("NEXTLOG %s", fn)
87+ fmt.Fprintln(lf.file, timestamp(logmsg))
88
89 // All done with the current log
90 lf.file.Close()
91@@ -66,27 +96,10 @@ func (lf *Logfile) rotate() error {
92 os.Remove(currentPath)
93 os.Symlink(fn, currentPath)
94
95- logmsg := fmt.Sprintf(". PREVLOG %s", lf.name)
96- lf.writeln(logmsg)
97+ logmsg := fmt.Sprintf("PREVLOG %s", lf.name)
98+ fmt.Fprintln(lf.file, timestamp(logmsg))
99
100 lf.name = fn
101
102 return nil
103 }
104-
105-func (lf *Logfile) Log(s string) error {
106- if lf.file == nil {
107- lf.rotate()
108- }
109-
110- err := lf.writeln(s)
111- if err == nil {
112- return err
113- }
114-
115- if lf.nlines >= lf.maxlines {
116- return lf.rotate()
117- }
118-
119- return nil
120-}
R spongy/network.go =>
spongyd/network.go
+67,
-29
1@@ -9,6 +9,7 @@ import (
2 "net"
3 "os"
4 "os/user"
5+ "os/exec"
6 "path"
7 "strings"
8 "time"
9@@ -48,7 +49,9 @@ type Network struct {
10 serverIndex int
11
12 conn io.ReadWriteCloser
13- logq chan Message
14+
15+ logf *Logfile
16+
17 inq chan string
18 outq chan string
19 }
20@@ -57,23 +60,21 @@ func NewNetwork(basePath string) *Network {
21 nw := Network{
22 running: true,
23 basePath: basePath,
24- logq: make(chan Message, 20),
25 }
26-
27- go nw.LogLoop()
28+ nw.logf = NewLogfile(nw.basePath, int(maxlogsize))
29
30 return &nw
31 }
32
33 func (nw *Network) Close() {
34 nw.running = false
35- close(nw.logq)
36 if nw.conn != nil {
37 nw.conn.Close()
38 }
39+ nw.logf.Close()
40 }
41
42-func (nw *Network) WatchOutqDirectory() {
43+func (nw *Network) watchOutqDirectory() {
44 outqDirname := path.Join(nw.basePath, "outq")
45
46 dir, err := os.Open(outqDirname)
47@@ -113,20 +114,12 @@ func (nw *Network) HandleInfile(fn string) {
48 }
49 }
50
51-func (nw *Network) LogLoop() {
52- logf := NewLogfile(nw.basePath, int(maxlogsize))
53- defer logf.Close()
54-
55- for m := range nw.logq {
56- logf.Log(m.String())
57- }
58-}
59-
60-func (nw *Network) ServerWriteLoop() {
61+func (nw *Network) serverWriteLoop() {
62 for v := range nw.outq {
63- m, _ := NewMessage(v)
64- nw.logq <- m
65+ debug("ยป %s", v)
66+ nw.logf.Log(v)
67 fmt.Fprintln(nw.conn, v)
68+ time.Sleep(500 * time.Millisecond)
69 }
70 }
71
72@@ -163,21 +156,20 @@ func (nw *Network) JoinChannels() {
73 }
74
75 for _, ch := range chans {
76+ debug("Joining %s", ch)
77 nw.outq <- "JOIN " + ch
78 }
79 }
80
81-func (nw *Network) MessageDispatch() {
82+func (nw *Network) messageDispatchLoop() {
83 for line := range nw.inq {
84+ nw.logf.Log(line)
85+
86 m, err := NewMessage(line)
87 if err != nil {
88 log.Print(err)
89 continue
90 }
91-
92- nw.logq <- m
93-
94- // XXX: Add in a handler subprocess call
95
96 switch m.Command {
97 case "PING":
98@@ -196,6 +188,32 @@ func (nw *Network) MessageDispatch() {
99 nw.outq <- "NOTICE " + m.Sender + " :\001 VERSION end\001"
100 }
101 }
102+
103+ handlerPath := path.Join(nw.basePath, "handler")
104+ cmd := exec.Command(handlerPath, m.Args...)
105+ cmd.Env = []string{
106+ "command=" + m.Command,
107+ "fullsender=" + m.FullSender,
108+ "sender=" + m.Sender,
109+ "forum=" + m.Forum,
110+ "text=" + m.Text,
111+ "raw=" + line,
112+ }
113+ cmd.Stderr = os.Stderr
114+ out, err := cmd.Output()
115+ if err != nil {
116+ log.Print(err)
117+ continue
118+ }
119+
120+ if len(out) > 0 {
121+ outlines := strings.Split(string(out), "\n")
122+ for _, line := range outlines {
123+ if len(line) > 0 {
124+ nw.outq <- line
125+ }
126+ }
127+ }
128 }
129 }
130
131@@ -211,6 +229,7 @@ func (nw *Network) ConnectToNextServer() bool {
132 }
133 server := servers[nw.serverIndex]
134
135+ debug("Connecting to %s", server)
136 switch (server[0]) {
137 case '|':
138 parts := strings.Split(server[1:], " ")
139@@ -229,12 +248,21 @@ func (nw *Network) ConnectToNextServer() bool {
140 log.Print(err)
141 return false
142 }
143+ debug("Connected")
144
145 return true
146 }
147
148 func (nw *Network) login() {
149 var name string
150+ var username string
151+
152+ usernames, err := ReadLines(path.Join(nw.basePath, "username"))
153+ if err == nil {
154+ username = usernames[0]
155+ } else {
156+ username = "sponge"
157+ }
158
159 passwd, err := ReadLines(path.Join(nw.basePath, "passwd"))
160 if err == nil {
161@@ -255,15 +283,24 @@ func (nw *Network) login() {
162 }
163
164 if name == "" {
165- name = "Charlie"
166+ // Rogue used "Rodney" if you didn't give it a name.
167+ // This one works for the ladies, too.
168+ name = "Ronnie"
169 }
170
171- nw.outq <- "USER g g g :" + name
172+ nw.outq <- "USER " + username + " g g :" + name
173 nw.NextNick()
174 }
175
176+func (nw *Network) keepaliveLoop() {
177+ for nw.running {
178+ time.Sleep(1 * time.Minute)
179+ nw.outq <- "PING :keepalive"
180+ }
181+}
182+
183
184-func (nw *Network) Connect(){
185+func (nw *Network) Connect() {
186 for nw.running {
187 if ! nw.ConnectToNextServer() {
188 time.Sleep(8 * time.Second)
189@@ -273,9 +310,10 @@ func (nw *Network) Connect(){
190 nw.inq = make(chan string, 20)
191 nw.outq = make(chan string, 20)
192
193- go nw.ServerWriteLoop()
194- go nw.MessageDispatch()
195- go nw.WatchOutqDirectory()
196+ go nw.serverWriteLoop()
197+ go nw.messageDispatchLoop()
198+ go nw.watchOutqDirectory()
199+ go nw.keepaliveLoop()
200
201 nw.login()
202
R spongy/network_test.go =>
spongyd/network_test.go
+0,
-0
R spongy/readwritecloserwrapper.go =>
spongyd/readwritecloserwrapper.go
+0,
-0
R spongy/readwritecloserwrapper_test.go =>
spongyd/readwritecloserwrapper_test.go
+0,
-0
R spongy/spongy_test.go =>
spongyd/spongy_test.go
+0,
-0
R spongy/spongy.go =>
spongyd/spongyd.go
+10,
-2
1@@ -11,8 +11,15 @@ import (
2 )
3
4 var running bool = true
5+var verbose bool = false
6 var maxlogsize uint
7
8+func debug(format string, a ...interface{}) {
9+ if verbose {
10+ log.Printf(format, a...)
11+ }
12+}
13+
14 func exists(filename string) bool {
15 _, err := os.Stat(filename); if err != nil {
16 return false
17@@ -78,8 +85,9 @@ func usage() {
18
19 func main() {
20 flag.Usage = usage
21- flag.UintVar(&maxlogsize, "logsize", 1000, "Log entries before rotating")
22- notime := flag.Bool("notime", false, "Don't timestamp log messages")
23+ flag.UintVar(&maxlogsize, "logsize", 6000, "Log entries before rotating")
24+ flag.BoolVar(&verbose, "verbose", false, "Verbose logging")
25+ notime := flag.Bool("notime", false, "Don't timestamp debugging messages")
26 flag.Parse()
27 if flag.NArg() != 1 {
28 usage()