1 module client.sender;
2 
3 import core.thread;
4 import std.json : JSONValue, JSONException, parseJSON, toJSON;
5 import bmessage;
6 import std.socket;
7 import gogga;
8 import std.string;
9 import std.conv : to;
10 import client.client;
11 
12 /**
13 * The MailSender class is used to instantiate an object
14 * which is used to start its own thread which will deliver
15 * mail (remote-only) to the respective recipients of the mail
16 * message specified. This is done such that the user need not
17 * wait and hang whilst mail is being delivered
18 */
19 public final class MailSender : Thread
20 {
21     /**
22     * Delivery information
23     */
24     private string[] remoteRecipients;
25     private JSONValue mailBlock;
26 
27     /* Failed recipients (at the beginning it will be only local) */
28     private string[] failedRecipients;
29 
30     private ButterflyClient client;
31 
32     /**
33     * Constructs a new MailSender with the given
34     * email to be delivered (remotely)
35     */
36     this(string[] remoteRecipients, JSONValue mailBlock, string[] failedRecipients, ButterflyClient client)
37     {
38         /* Set the worker function */
39         super(&run);
40 
41         /* Save delivery information */
42         this.remoteRecipients = remoteRecipients;
43         this.mailBlock = mailBlock;
44 
45         /* Save the failed local recipients */
46         this.failedRecipients = failedRecipients;
47 
48         this.client = client;
49 
50         /* Start the delivery */
51         start();
52     }
53 
54     /**
55     * Does the remote mail delivery
56     */
57     private void remoteDeliver()
58     {
59         /* Deliver mail to each recipient */
60         foreach (string remoteRecipient; remoteRecipients)
61         {
62             /* TODO: Do remote mail delivery */
63             gprintln("Remote delivery occurring...");
64 
65             /* Get the mail address */
66             string[] mailAddress = split(remoteRecipient, "@");
67 
68             /* Get the username */
69             string username = mailAddress[0];
70 
71             /* Get the domain */
72             string domain = mailAddress[1];
73 
74             try
75             {
76                 /**
77                 * Construct the server message to send to the
78                 * remote server.
79                 */
80                 JSONValue messageBlock;
81                 messageBlock["command"] = "deliverMail";
82 
83                 JSONValue requestBlock;
84                 requestBlock["mail"] = mailBlock;
85                 messageBlock["request"] = requestBlock;
86 
87                 /* Deliver the mail to the remote server */
88                 Socket remoteServer = new Socket(AddressFamily.INET,
89                         SocketType.STREAM, ProtocolType.TCP);
90 
91                 /* TODO: Add check over here to make sure these are met */
92                 string remoteHost = split(domain, ":")[0];
93                 ushort remotePort = to!(ushort)(split(domain, ":")[1]);
94 
95                 remoteServer.connect(parseAddress(remoteHost, remotePort));
96                 bool sendStatus = sendMessage(remoteServer, cast(byte[]) toJSON(messageBlock));
97 
98                 if (!sendStatus)
99                 {
100                     goto deliveryFailed;
101                 }
102 
103                 byte[] receivedBytes;
104                 bool recvStatus = receiveMessage(remoteServer, receivedBytes);
105 
106                 if (!recvStatus)
107                 {
108                     goto deliveryFailed;
109                 }
110 
111                 /* Close the connection with the remote host */
112                 remoteServer.close();
113 
114                 JSONValue responseBlock = parseJSON(cast(string) receivedBytes);
115 
116                 /* TODO: Get ["status"]["code"] code here an act on it */
117                 if (responseBlock["status"]["code"].integer() == 0)
118                 {
119                     gprintln("Message delivered to user " ~ remoteRecipient);
120                 }
121                 else
122                 {
123                     goto deliveryFailed;
124                 }
125             }
126             catch (SocketOSException)
127             {
128                 goto deliveryFailed;
129             }
130             catch (JSONException)
131             {
132                 /* When delivery fails */
133             deliveryFailed:
134                 gprintln("Error delivering to " ~ remoteRecipient);
135 
136                 /* Append failed recipient to array of failed recipients */
137                 failedRecipients ~= remoteRecipient;
138 
139                 continue;
140             }
141         }
142 
143         gprintln("Sent mail message to " ~ remoteRecipients);
144     }
145 
146     /**
147     * Sends a mail message to the sender's INbox specifying that there
148     * was a mail delivery failure to one or more of the provided addresses
149     */
150     private void mailReport()
151     {
152         /* Create the error message */
153         JSONValue deliveryReport;
154         JSONValue[] errorRecipients = [
155             JSONValue(client.mailbox.username ~ "@" ~ client.listener.getDomain())
156         ];
157         deliveryReport["recipients"] = errorRecipients;
158 
159         /* TODO: Make more indepth, and have copy of the mail that was tried to be sent */
160         /* Get a list of the recipients of the mail message */
161         string[] recipients;
162         foreach(JSONValue recipient; mailBlock["recipients"].array())
163         {
164             recipients ~= recipient.str();
165         }
166         string errorMessage = "There was an error delivery the mail to: " ~ to!(
167                 string)(recipients) ~ "\n";
168         errorMessage ~= "\nThe message was:\n\n" ~ mailBlock.toPrettyString();
169         deliveryReport["message"] = errorMessage;
170 
171         gprintln(deliveryReport);
172 
173         /* Deliver the error message (and don't put the report in the sent box) */
174         client.sendMail(deliveryReport, false);
175 
176         gprintln("Mail delivery report sent: " ~ deliveryReport.toPrettyString());
177 
178     }
179 
180     private void run()
181     {
182         /* Do the remote mail delivery */
183         remoteDeliver();
184 
185         /* If there were failed recipients send a report to the sender */
186         if (failedRecipients.length)
187         {
188             /* Send the mail report */
189             mailReport();
190         }
191     }
192 }