static void StartGui(Connector conn, Settings settings) { Gui.Start(x => { //if changes UI: have to use this Action<string, Action<CmdArg>> xRegister = (cmd, a) => conn.Register(cmd, x.InSTAThread(a)); var help = @"Help: Type (messages) to line at bottom of window. Commands start with ':'. All messages are trimmed by default, so to write ':-)' just write ' :-)'. Use tab for command completion. :chat # show message board :server.start <port> # starts server and DOES NOT connect to it :server.stop :ip # get local IP address(es) :wsdl # open server with wsdl description of service to access chans :connect <host:port> # connect to a server; default port is " + settings.DefaultServerPort + @" :disconnect # closes client :host <port> # :server.start, :connect :join <host> <name> # :connect <host>, :name <name> :name <new name> # get or set; 'anon' by default :down, :d # scroll to end of message board :exit, :quit :help, :h # show this help :send <msg> # send msg as it is; even just whitespace / nothing... :text <theText> # ~= :send <trim($theText)> # wouldn't send nothing if line doesn't start with ':': gets translated to: ':text <$line>' "; xRegister(Cmd.Help, _ => x.SwitchToHelp(help)); xRegister(Cmd.Chat, _ => x.SwitchToChat()); xRegister(Cmd.Exit, _ => x.Exit()); xRegister(Cmd.ScrollDown, _ => x.ScrollToBottom()); xRegister(Cmd.NotifyError, a => x.ShowError(a.Source, a.Text)); xRegister(Cmd.NotifySystem, a => x.ShowNotifySystem(a.Source, a.Text)); xRegister(Cmd.ReceivedMsg, a => x.ShowMessage(a.Source, a.Text)); xRegister(Cmd.CmdAccepted, a => x.ClearCmdLineIfSame(a)); xRegister(Cmd.CompletionResponse, a => x.PushCompletion(a, a.Source)); conn.Alias("d", Cmd.ScrollDown); conn.Alias("h", Cmd.Help); conn.Alias("quit", Cmd.Exit); conn.Run(Cmd.Help); x.Command += line => conn.RunOrDefault(Cmd.CmdParseRun, line); x.CompletionRequest += line => conn.RunOrDefault(Cmd.CompletionRequest, line); x.BoardChanged += () => conn.Run(Cmd.Chat); conn.RunOrDefault(Cmd.AfterGuiLoaded); }); }
static void Init(Settings settings, Connector conn, Action<Connector, ChanStore> initExtra) { var store = new ChanStore(); var client = new ChatClient(settings, store, conn); //simple default config for now var dfltCfg = NetChanConfig.MakeDefault<Message>(); //client var rFailT = store.PrepareClientReceiverForType(dfltCfg); var sFailT = store.PrepareClientSenderForType(dfltCfg); rFailT.PipeEx(conn, "store: receiver cache"); sFailT.PipeEx(conn, "store: sender cache"); conn.Register(Cmd.Send, client.BroadcastMessage); conn.Register(Cmd.Connect, client.Connect); conn.Register(Cmd.Disconnect, _ => client.Disconnect()); conn.Register(Cmd.Join, a => { var args = (a.Text ?? "").Split(new []{ ' ' }, StringSplitOptions.RemoveEmptyEntries); if (args.Length > 2) conn.RunError("requires 1 or 2 arguments <?host:port> <name>".ArgSrc("join")); else ((Func<Func<string, string, bool>, bool>) (cont => { //I could use ifs; this is more obvious to me... switch (args.Length) { //host, name case 1: return cont(null, args[0]); case 2: return cont(args[0], args[1]); default: return cont(null, null); } // apply args^ to connect and (name if != null) }))((host, name) => conn.RunOrDefault(Cmd.Connect, host) && (name != null) && conn.RunOrDefault(Cmd.Name, name)); }); //server //it reregisters it's commands if started (to ones created in start fn) Action<CmdArg> serverStartOff = null; //~ referenced from itself Action<CmdArg> serverStopOff = _ => conn.RunError("not running".ArgSrc("server")); serverStartOff = a => { var portS = a.Text; int port; if (!int.TryParse(portS, out port)) { //couldn't parse: try default if (settings.DefaultServerPort == -1) { conn.RunError("requires port (default not allowed)".ArgSrc(Cmd.ServerStart + " " + portS)); return; } port = settings.DefaultServerPort; } //port OK: try creating and starting server: try { var serverStore = new ChanStore(); InitServer(settings, serverStore, conn); serverStore.StartServer(port); //created fine: reregister conn.Reregister(Cmd.ServerStart, _ => conn.RunError(("already running (port: " + port + ")").ArgSrc("server"))); conn.Reregister(Cmd.ServerStop, _ => { try { serverStore.StopServer();//stop listening for new //kill stuff in ChanStore: this will force shut the open channels and close clients serverStore.CloseAll().PipeEx(conn, "server.stop (in clear)"); conn.RunNotifySystem("stopped".ArgSrc("server")); } catch (Exception ex) { ex.PipeEx(conn, "server.stop"); } finally { conn.Reregister(Cmd.ServerStop, serverStopOff); //use original handlers again conn.Reregister(Cmd.ServerStart, serverStartOff); } }); conn.RunNotifySystem("started".ArgSrc("server")); } catch (Exception ex) { ex.PipeEx(conn, Cmd.ServerStart + " " + port); } }; conn.Register(Cmd.ServerStart, serverStartOff); conn.Register(Cmd.ServerStop, serverStopOff); conn.Register(Cmd.Host, async port => { try { if (string.IsNullOrWhiteSpace(port)) { if (settings.DefaultServerPort == -1) throw new ArgumentException("requires port (default not allowed)"); port = settings.DefaultServerPort.ToString(); } if (await conn.RunOrDefaultAsync(Cmd.ServerStart, port)) { //~ok: problem: this is run after known, whether Cmd.Server exists, not after server started //: connect waits until started; ok // - no it doesn't: it cannot: it's elsewhere... await Task.Delay(50); //wait a little instead await conn.RunOrDefaultAsync(Cmd.Connect, "localhost:" + port); } } catch (Exception ex) { ex.PipeEx(conn, Cmd.Host + " " + port); } }); //translate text to send conn.Register(Cmd.Text, a => { if (!string.IsNullOrWhiteSpace(a)) conn.RunOrDefault(Cmd.Send, a.Text.Trim()); }); //get/change name conn.Register(Cmd.Name, a => { var arg = a.Text; if (string.IsNullOrWhiteSpace(arg)) { //get name conn.RunNotifySystem(client.ClientName.ArgSrc("clinet name")); } else { //set name client.ClientName = arg; } }); //parse line into command and run it conn.Register(Cmd.CmdParseRun, a => { var ok = Cmd.ParseCommandInto(a, (cmd, arg) => conn.RunOrDefault(cmd ?? Cmd.NoCommand, arg)); if (ok) conn.RunOrDefault(Cmd.CmdAccepted, a); }); //command completion conn.Register(Cmd.CompletionRequest, a => { string arg = a; if (string.IsNullOrWhiteSpace(arg) || arg[0] != Settings.UserCommandStart || arg.Contains(' ')) //for now: only completion of main command is suppoerted return; arg = arg.Substring(1); //remove commandStart char (:) var possibles = conn.Keys.Where(k => k.StartsWith(arg)).ToArray(); if (possibles.Length == 0) //no option to help with return; //if more then 1: find longest common prefix (starting at already known: len of arg //if 1: append ' ' as it is both useful and shows the cmd is complete string completedCmd = possibles.Length == 1 ? possibles[0] + ' ' : longestCommonPrefix(possibles, arg.Length); conn.RunOrDefault(Cmd.CompletionResponse, (Settings.UserCommandStart + completedCmd).ArgSrc(a)); }); conn.Reregister(Cmd.AfterGuiLoaded, _ => { initExtra(conn, store); //in AfterGuiLoaded because Gui registers Exit through Add, not Merge conn.Coregister(Cmd.Exit, __ => { //dirty way of making sure every WCF server is cosed, so the app doesn't hang after GUI thread finished conn.Reregister(Cmd.NotifyError, err => Console.Error.WriteLine(string.Format("{0}:! {1}", err.Source, err.Text))); conn.Reregister(Cmd.NotifySystem, err => Console.WriteLine(string.Format("{0}:: {1}", err.Source, err.Text))); conn.RunOrDefault(Cmd.Disconnect); //this is probaly not necesary, but why not... conn.RunOrDefault(Cmd.ServerStop); conn.RunOrDefault(Cmd.WsdlStop); }); }); conn.Register(Cmd.Wsdl, a => { int port; if (!int.TryParse(a, out port)/*try port from argument*/ && (port = settings.DefaultWsdlPort) < 0/*try default; if (<0):*/ && conn.RunError("default port not allowed".ArgSrc(Cmd.Wsdl))) return; try { var s = new ChanStore(port); s.StartServer(port); conn.RunNotifySystem(string.Format("running :{0}/ChanStore", port).ArgSrc(Cmd.Wsdl)); conn.Register(Cmd.WsdlStop, _ => { s.StopServer(); conn.Reregister(Cmd.WsdlStop, null); conn.RunNotifySystem("stopped".ArgSrc(Cmd.Wsdl)); }); } catch (Exception ex) { ex.PipeEx(conn, Cmd.Wsdl); } }); conn.Register(Cmd.Ip, _ => conn.RunOrDefault(Cmd.NotifySystem, string.Join<IPAddress>(", ", Dns.GetHostEntry("").AddressList).ArgSrc(Cmd.Ip))); #if DEBUG conn.Register("test", a => { conn.Run(Cmd.NotifyError, "test error".ArgSrc("err source")); conn.Run(Cmd.NotifyError, "test error without source"); conn.Run(Cmd.NotifySystem, "test system".ArgSrc("system source")); conn.Run(Cmd.NotifySystem, "test system without source"); conn.Run(Cmd.ReceivedMsg, "test msg".ArgSrc("msg source")); conn.Run(Cmd.ReceivedMsg, "test msg without source"); conn.Run(Cmd.Help); conn.Run(Cmd.Chat); }); #endif }