1 module beangle.task.channel; 2 import vibe.http.websockets : WebSocket; 3 import beangle.task; 4 import beangle.util; 5 6 //name -> channel 7 private AgentChannel[string] agents; 8 //id -> channel 9 private AdminChannel[string] admins; 10 11 abstract class Channel{ 12 13 immutable string id; 14 15 immutable string name; 16 17 //socket to client 18 WebSocket socket; 19 20 this(string id, string name, WebSocket socket){ 21 this.id = id; 22 this.name = name; 23 this.socket = socket; 24 } 25 26 void send(string text){ 27 if (this.socket.connected) { 28 this.socket.send(text); 29 } 30 } 31 32 override string toString(){ 33 return name ~ "@" ~ id; 34 } 35 } 36 37 class AdminChannel : Channel{ 38 39 AgentChannel agent; 40 41 string workdir = "~"; 42 43 static AdminChannel create(string name, WebSocket socket){ 44 return new AdminChannel(nextChannelId(name), name, socket); 45 } 46 47 this(string id, string name, WebSocket socket){ 48 super(id, name, socket); 49 } 50 51 void changeWorkdir(string path){ 52 import std.algorithm; 53 if (path.startsWith("/") || path == "~"){ 54 this.workdir = path; 55 }else { 56 if (path.endsWith("/")){ 57 this.workdir = this.workdir ~ "/" ~ path[0..$-1]; 58 }else { 59 this.workdir = this.workdir ~ "/" ~ path; 60 } 61 } 62 } 63 void setAgent(AgentChannel agent){ 64 this.agent = agent; 65 } 66 67 void purge(){ 68 if (this.agent !is null && !this.agent.socket.connected){ 69 this.agent = null; 70 } 71 } 72 73 } 74 75 class AgentChannel : Channel { 76 //caller name -> caller 77 Caller[string] callers; 78 79 void invoke(Caller caller, string cmds){ 80 callers[caller.getId()] = caller; 81 import std.json; 82 JSONValue jj = ["id":caller.getId()]; 83 jj.object["commands"]=cmds; 84 send(jj.toString()); 85 } 86 87 void reply(string text){ 88 import std.json; 89 JSONValue js = parseJSON(text); 90 auto callId = js["id"].str; 91 auto data = js["data"].str; 92 auto caller = callId in callers; 93 if (caller !is null){ 94 callers.remove(callId); 95 caller.callback(data); 96 } 97 } 98 99 this(string id, string name, WebSocket socket){ 100 super(id, name, socket); 101 } 102 103 static AgentChannel create(string name, WebSocket socket){ 104 return new AgentChannel(nextChannelId(name), name, socket); 105 } 106 } 107 108 interface Caller{ 109 void callback(string result); 110 string getId(); 111 } 112 113 class SocketCaller : Caller{ 114 WebSocket socket; 115 const string id; 116 117 this(string id, WebSocket socket){ 118 this.id = id; 119 this.socket = socket; 120 } 121 122 string getId(){ 123 return id; 124 } 125 static SocketCaller create(AdminChannel channel){ 126 import std.datetime; 127 auto id = Clock.currTime().toISOString() ~ "@" ~ channel.name; 128 return new SocketCaller(id, channel.socket); 129 } 130 131 override void callback(string result){ 132 if (this.socket.connected) { 133 this.socket.send(result); 134 } 135 } 136 } 137 138 class WebCaller : Caller{ 139 import vibe.core.sync; 140 string id; 141 InterruptibleTaskMutex m_readMutex; 142 InterruptibleTaskCondition m_readCondition; 143 string result; 144 145 this(){ 146 import std.datetime; 147 this.id = Clock.currTime().toISOString() ~ "@web"; 148 } 149 150 string getId(){ 151 return id; 152 } 153 154 import core.time; 155 156 string invoke(AgentChannel agent, string commands, Duration timeout = 20.seconds){ 157 m_readMutex = new InterruptibleTaskMutex; 158 m_readCondition = new InterruptibleTaskCondition(m_readMutex); 159 160 agent.invoke(this, commands); 161 m_readMutex.performLocked!({ 162 m_readCondition.wait(timeout); 163 }); 164 return result; 165 } 166 167 override void callback(string result){ 168 import std.stdio; 169 if (result is null){ 170 this.result = "null"; 171 }else { 172 this.result = result; 173 } 174 m_readCondition.notify(); 175 } 176 } 177