1 
2 module mars.client;
3 
4 import std.datetime,
5        std.experimental.logger;
6 
7 import vibe.core.log;
8 import vibe.data.json;
9 import vibe.http.websockets;
10 
11 import mars.msg;
12 import mars.server;
13 import mars.websocket;
14 import mars.protomars : MarsProxyStoC; // XXX per instanziare la variabile per il socket... dovrei fare un interfaccia?
15                                    // nel momento in cui instanzio il client, non ho ancora il MarsProxy, che invece
16                                    // instanzio nel protoMars. Dovrei instanziare il client nel protoMars? Maybe yes
17                                    // visto che è li che, dopo aver stabilito che è un client mars, instanzio il socket.
18 
19 import mars.sync;
20 import mars.pgsql;
21 
22 struct MarsClient
23 {
24     void connected() in {
25         assert( connectionEvents.length ==0 || connectionEvents[$-1].type == ConnessionEvent.disconnected );
26     } body {
27         connectionEvents ~= ConnessionEvent(ConnessionEvent.connected, Clock.currTime);
28     }
29     void disconnected() in {
30         assert( connectionEvents[$-1].type == ConnessionEvent.connected );
31     } body {
32         connectionEvents ~= ConnessionEvent(ConnessionEvent.disconnected, Clock.currTime);
33     }
34 
35     bool isConnected(){ return connectionEvents.length >0 && connectionEvents[$-1].type == ConnessionEvent.connected; }
36     
37     SysTime[] reconnections() {
38         import std.algorithm : filter, map;
39         import std.array : array;
40 
41         return connectionEvents
42             .filter!( (e) => e.type == ConnessionEvent.connected )
43             .map!"a.when"
44             .array;
45     }
46     
47     private {
48         struct ConnessionEvent {
49             enum { connected, disconnected }
50             int type;
51             SysTime when;
52         }
53         ConnessionEvent[] connectionEvents;
54     }
55 
56     this(string id, const DatabaseService databaseService){ 
57         this.id_ = id; 
58         this.databaseService = databaseService;
59     }
60 
61     /**
62      * Push a forward-only message to the client, a reply is not expected. */
63     void sendBroadcast(M)(M msg) in { assert(isConnected); } body 
64     {
65         socket.sendRequest(0, msg); // ... this is really a proxy to the websocket
66     }
67 
68     /**
69      * Push a new message, from the server to the client. Used by the server to inform clients about events. */
70     void sendRequest(M)(M msg) in { assert(isConnected); } body 
71     {
72         bool sent = socket.sendRequest(nextId++, msg);
73         if( ! sent ){
74             disconnected();
75         }
76     }
77     private int nextId = 1;
78 
79     auto receiveReply(M)() in { assert(isConnected); } body
80     {
81         auto msg = socket.receiveMsg!M();
82         if( msg.status == msg.channelDropped ) disconnected();
83         return msg;
84     }
85 
86     /**
87      * The Helo protocol will wire the active socket here, and will set this to null when disconnecting. */
88     void wireSocket(MarsProxyStoC!WebSocket socket)
89     {
90         this.socket = socket;
91     }
92 
93     /**
94      * Returns true if the 'server to client' socket was opened and wired to us. */
95     bool socketWired() { return this.socket != this.socket.init; }
96 
97     /**
98      * Called by the authentication protocol.
99      * 
100      * Returns: false if PostgreSQL is offline or user in not authorised, or true. */
101     AuthoriseError authoriseUser(string username, string pgpassword) in {
102         assert(username && pgpassword);
103     } body {
104         this.username = username;
105 
106         AuthoriseError err;
107         if( databaseService.host == "" ){
108             logWarn("S --- C | the database host is not specified, we are operating in offline mode");
109             err = AuthoriseError.authorised;
110         }
111         else {
112             db = databaseService.connect(username, pgpassword, err );
113         }
114         return err;
115     }
116     void discardAuthorisation() { this.username = ""; }
117 
118     bool authorised() { return this.username != ""; }
119     string id() { return id_; }
120     
121     string callServerMethod(string method, Json parameters){
122         if( serverSideMethods !is null ){
123             return serverSideMethods(this, method, parameters);
124         }
125         assert(false); // catch on server side;
126     }
127 
128     immutable(ubyte)[][2] vueInsertRecord(int statementIndex, immutable(ubyte)[] record, ref InsertError err){
129         immutable(ubyte)[][2] inserted = marsServer.tables[statementIndex].insertRecord(db, record, err);
130         return inserted;
131     }
132 
133     immutable(ubyte)[] vueDeleteRecord(int tableIndex, immutable(ubyte)[] record, ref DeleteError err){
134         immutable(ubyte)[] deleted = marsServer.tables[tableIndex].deleteRecord(db, record, err);
135         return deleted;
136     }
137 
138     private {
139         string id_;
140 
141         string username = "";
142         //string password;
143         string seed;
144 
145         MarsProxyStoC!WebSocket socket;
146         
147         public typeof(MarsServer.serverSideMethods) serverSideMethods;
148         DatabaseService databaseService;
149         public Database db;
150     }
151 }