ref: 87d72e7e8614d96b4f61adae5fb05b0534231c4c
dir: /appl/demo/chat/chatsrv.b/
implement Chatsrv; # # simple text-based chat service # include "sys.m"; sys: Sys; Qid: import Sys; include "draw.m"; include "styx.m"; styx: Styx; Tmsg, Rmsg: import Styx; include "styxservers.m"; styxservers: Styxservers; Styxserver, Navigator: import styxservers; nametree: Nametree; Tree: import nametree; Chatsrv : module { init : fn (ctxt : ref Draw->Context, args : list of string); }; 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; } badmod(path: string) { sys->fprint(sys->fildes(1), "chatsrv: cannot load %s: %r\n", path); exit; } init(nil: ref Draw->Context, nil: list of string) { sys = load Sys Sys->PATH; styx = load Styx Styx->PATH; if(styx == nil) badmod(Styx->PATH); styxservers = load Styxservers Styxservers->PATH; if(styxservers == nil) badmod(Styxservers->PATH); nametree = load Nametree Nametree->PATH; if(nametree == nil) badmod(Nametree->PATH); 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)); nextmsg = ref Msg (0, nil, nil, nil); keptmsg = nextmsg; (tc, srv) = Styxserver.new(sys->fildes(0), Navigator.new(treeop), big Qdir); chatsrv(tree); } 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; }; NKEPT: con 6; keptcount := 0; nextmsg: ref Msg; keptmsg: 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, keptmsg, 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(nextmsg); srv.reply(styxservers->readstr(c.pending, s)); c.pending = nil; c.nextmsg = nm; } } nextmsg = nm; if (keptcount < NKEPT) keptcount++; else keptmsg = keptmsg.next; } 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(m); } return nil; } msgtext(m: ref Msg): string { prefix := ""; if (m.from != nil) prefix = m.from + ": "; return prefix + m.msg; }