1 /**
2  * NNTP listener (periodically poll server for new messages).
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.net.nntp.listener;
15 
16 import ae.net.nntp.client;
17 
18 import std.datetime;
19 import std.typecons;
20 
21 import ae.sys.timing;
22 import ae.sys.log;
23 
24 const POLL_PERIOD = 2.seconds;
25 
26 class NntpListener
27 {
28 private:
29 	typeof(scoped!NntpClient(Logger.init)) client;
30 	string server;
31 	string lastDate;
32 	bool[string] oldMessages;
33 	TimerTask pollTimer;
34 	bool connected, polling;
35 	int queued;
36 
37 	void reconnect()
38 	{
39 		assert(!connected);
40 		client.connect(server, &onConnect);
41 	}
42 
43 	void schedulePoll()
44 	{
45 		pollTimer = setTimeout(&poll, POLL_PERIOD);
46 	}
47 
48 	void poll()
49 	{
50 		pollTimer = null;
51 		client.getDate(&onDate);
52 		client.getNewNews("*", lastDate[0..8] ~ " " ~ lastDate[8..14] ~ " GMT", &onNewNews);
53 	}
54 
55 	void onConnect()
56 	{
57 		connected = true;
58 		queued = 0;
59 
60 		if (polling)
61 		{
62 			if (lastDate)
63 				poll();
64 			else
65 				client.getDate(&onDate);
66 		}
67 	}
68 
69 	void onDisconnect(string reason, DisconnectType type)
70 	{
71 		connected = false;
72 		if (polling)
73 		{
74 			if (pollTimer && pollTimer.isWaiting())
75 				pollTimer.cancel();
76 			if (type != DisconnectType.requested)
77 				setTimeout(&reconnect, 10.seconds);
78 		}
79 	}
80 
81 	void onDate(string date)
82 	{
83 		if (polling)
84 		{
85 			if (lastDate is null)
86 				schedulePoll();
87 			lastDate = date;
88 		}
89 	}
90 
91 	void onNewNews(string[] reply)
92 	{
93 		auto messages = reply[1..$];
94 
95 		assert(queued == 0);
96 		foreach (message; messages)
97 			if (message !in oldMessages)
98 			{
99 				oldMessages[message] = true;
100 				client.getMessage(message, &onMessage);
101 				queued++;
102 			}
103 		if (queued==0)
104 			schedulePoll();
105 	}
106 
107 	void onMessage(string[] lines, string num, string id)
108 	{
109 		if (handleMessage)
110 			handleMessage(lines, num, id);
111 
112 		if (polling)
113 		{
114 			queued--;
115 			if (queued==0)
116 				schedulePoll();
117 		}
118 	}
119 
120 public:
121 	this(Logger log)
122 	{
123 		client = scoped!NntpClient(log);
124 		client.handleDisconnect = &onDisconnect;
125 	}
126 
127 	void connect(string server)
128 	{
129 		this.server = server;
130 		reconnect();
131 	}
132 
133 	void disconnect()
134 	{
135 		client.disconnect();
136 	}
137 
138 	void startPolling(string lastDate = null)
139 	{
140 		assert(!polling, "Already polling");
141 		polling = true;
142 		this.lastDate = lastDate;
143 		if (connected)
144 			poll();
145 	}
146 
147 	void delegate(string[] lines, string num, string id) handleMessage;
148 }