diff options
Diffstat (limited to 'appl/collab/servers/chatsrv.b')
| -rw-r--r-- | appl/collab/servers/chatsrv.b | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/appl/collab/servers/chatsrv.b b/appl/collab/servers/chatsrv.b new file mode 100644 index 00000000..072c0617 --- /dev/null +++ b/appl/collab/servers/chatsrv.b @@ -0,0 +1,263 @@ +implement Service; + +# +# simple text-based chat service +# + +include "sys.m"; + sys: Sys; + Qid: import Sys; + +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import Styx; + +include "styxservers.m"; + styxservers: Styxservers; + Styxserver, Navigator: import styxservers; + nametree: Nametree; + Tree: import nametree; + +include "../service.m"; + +Qdir, Qusers, Qmsgs: con iota; + +tc: chan of ref Tmsg; +srv: ref Styxserver; + +user := "inferno"; + +dir(name: string, perm: int, path: int): Sys->Dir +{ + d := sys->zerodir; + d.name = name; + d.uid = user; + d.gid = user; + d.qid.path = big path; + if(perm & Sys->DMDIR) + d.qid.qtype = Sys->QTDIR; + else + d.qid.qtype = Sys->QTFILE; + d.mode = perm; + return d; +} + +init(nil: list of string): (string, string, ref Sys->FD) +{ + sys = load Sys Sys->PATH; + styx = load Styx Styx->PATH; + if(styx == nil) + return (sys->sprint("can't load %s: %r", Styx->PATH), nil, nil); + styxservers = load Styxservers Styxservers->PATH; + if(styxservers == nil) + return (sys->sprint("can't load %s: %r", Styxservers->PATH), nil, nil); + nametree = load Nametree Nametree->PATH; + if(nametree == nil) + return (sys->sprint("can't load %s: %r", Nametree->PATH), nil, nil); + styx->init(); + styxservers->init(styx); + nametree->init(); + + (tree, treeop) := nametree->start(); + tree.create(big Qdir, dir(".", Sys->DMDIR|8r555, Qdir)); + tree.create(big Qdir, dir("users", 8r444, Qusers)); + tree.create(big Qdir, dir("msgs", 8r666, Qmsgs)); + + p := array [2] of ref Sys->FD; + if (sys->pipe(p) < 0){ + tree.quit(); + return (sys->sprint("cannot create pipe: %r"), nil, nil); + } + + nextmsg = ref Msg (0, nil, nil, nil); + + (tc, srv) = Styxserver.new(p[1], Navigator.new(treeop), big Qdir); + spawn chatsrv(tree); + + return (nil, "/", p[0]); +} + +chatsrv(tree: ref Tree) +{ + while((tmsg := <-tc) != nil){ + pick tm := tmsg { + Readerror => + break; + Flush => + cancelpending(tm.tag); + srv.reply(ref Rmsg.Flush(tm.tag)); + Open => + c := srv.open(tm); + if (c == nil) + break; + if (int c.path == Qmsgs){ + newmsgclient(tm.fid, c.uname); + #root[0].qid.vers++; # TO DO + } + Read => + c := srv.getfid(tm.fid); + if (c == nil || !c.isopen) { + srv.reply(ref Rmsg.Error(tm.tag, Styxservers->Ebadfid)); + break; + } + case int c.path { + Qdir => + srv.read(tm); + Qmsgs => + mc := getmsgclient(tm.fid); + if (mc == nil) { + srv.reply(ref Rmsg.Error(tm.tag, "internal error -- lost client")); + continue; + } + tm.offset = big 0; + msg := getnextmsg(mc); + if (msg == nil) { + if(mc.pending != nil) + srv.reply(ref Rmsg.Error(tm.tag, "read already pending")); + else + mc.pending = tm; + continue; + } + srv.reply(styxservers->readstr(tm, msg)); + Qusers => + srv.reply(styxservers->readstr(tm, usernames())); + * => + srv.reply(ref Rmsg.Error(tm.tag, "phase error -- bad path")); + } + Write => + c := srv.getfid(tm.fid); + if (c == nil || !c.isopen) { + srv.reply(ref Rmsg.Error(tm.tag, Styxservers->Ebadfid)); + continue; + } + if (int c.path != Qmsgs) { + srv.reply(ref Rmsg.Error(tm.tag, Styxservers->Eperm)); + continue; + } + writemsgclients(tm.fid, c.uname, string tm.data); + srv.reply(ref Rmsg.Write(tm.tag, len tm.data)); + Clunk => + c := srv.clunk(tm); + if (c != nil && int c.path == Qmsgs){ + closemsgclient(tm.fid); + # root[0].qid.vers++; # TO DO + } + * => + srv.default(tmsg); + } + } + tree.quit(); + sys->print("chatsrv exit\n"); +} + +Msg: adt { + fromfid: int; + from: string; + msg: string; + next: cyclic ref Msg; +}; + +Msgclient: adt { + fid: int; + name: string; + nextmsg: ref Msg; + pending: ref Tmsg.Read; + next: cyclic ref Msgclient; +}; + +nextmsg: ref Msg; +msgclients: ref Msgclient; + +usernames(): string +{ + s := ""; + for (c := msgclients; c != nil; c = c.next) + s += c.name+"\n"; + return s; +} + +newmsgclient(fid: int, name: string) +{ + writemsgclients(fid, nil, "+++ " + name + " has arrived"); + msgclients = ref Msgclient(fid, name, nextmsg, nil, msgclients); +} + +getmsgclient(fid: int): ref Msgclient +{ + for (c := msgclients; c != nil; c = c.next) + if (c.fid == fid) + return c; + return nil; +} + +cancelpending(tag: int) +{ + for (c := msgclients; c != nil; c = c.next) + if((tm := c.pending) != nil && tm.tag == tag){ + c.pending = nil; + break; + } +} + +closemsgclient(fid: int) +{ + prev: ref Msgclient; + s := ""; + for (c := msgclients; c != nil; c = c.next) { + if (c.fid == fid) { + if (prev == nil) + msgclients = c.next; + else + prev.next = c.next; + s = "--- " + c.name + " has left"; + break; + } + prev = c; + } + if (s != nil) + writemsgclients(fid, nil, s); +} + +writemsgclients(fromfid: int, from: string, msg: string) +{ + nm := ref Msg(0, nil, nil, nil); + nextmsg.fromfid = fromfid; + nextmsg.from = from; + nextmsg.msg = msg; + nextmsg.next = nm; + + for (c := msgclients; c != nil; c = c.next) { + if (c.pending != nil) { + s := msgtext(c, nextmsg); + srv.reply(styxservers->readstr(c.pending, s)); + c.pending = nil; + c.nextmsg = nm; + } + } + nextmsg = nm; +} + +getnextmsg(mc: ref Msgclient): string +{ +# uncomment next two lines to eliminate queued messages to self +# while(mc.nextmsg.next != nil && mc.nextmsg.fromfid == mc.fid) +# mc.nextmsg = mc.nextmsg.next; + if ((m := mc.nextmsg).next != nil){ + mc.nextmsg = m.next; + return msgtext(mc, m); + } + return nil; +} + +msgtext(mc: ref Msgclient, m: ref Msg): string +{ + prefix := ""; + if (m.from != nil) { + # not a system message + if (mc.fid == m.fromfid) + prefix = "<you>: "; + else + prefix = m.from + ": "; + } + return prefix + m.msg; +} |
