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