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 
20 import ae.sys.timing;
21 import ae.sys.log;
22 
23 const POLL_PERIOD = 2.seconds;
24 
25 class NntpListener
26 {
27 private:
28 	NntpClient client;
29 	string server;
30 	string lastDate;
31 	bool[string] oldMessages;
32 	TimerTask pollTimer;
33 	bool connected, polling;
34 	int queued;
35 
36 	void reconnect()
37 	{
38 		assert(!connected);
39 		client.connect(server, &onConnect);
40 	}
41 
42 	void schedulePoll()
43 	{
44 		pollTimer = setTimeout(&poll, POLL_PERIOD);
45 	}
46 
47 	void poll()
48 	{
49 		pollTimer = null;
50 		client.getDate(&onDate);
51 		client.getNewNews("*", lastDate[0..8] ~ " " ~ lastDate[8..14] ~ " GMT", &onNewNews);
52 	}
53 
54 	void onConnect()
55 	{
56 		connected = true;
57 		queued = 0;
58 
59 		if (polling)
60 		{
61 			if (lastDate)
62 				poll();
63 			else
64 				client.getDate(&onDate);
65 		}
66 	}
67 
68 	void onDisconnect(string reason, DisconnectType type)
69 	{
70 		connected = false;
71 		if (polling)
72 		{
73 			if (pollTimer && pollTimer.isWaiting())
74 				pollTimer.cancel();
75 			if (type != DisconnectType.requested)
76 				setTimeout(&reconnect, 10.seconds);
77 		}
78 	}
79 
80 	void onDate(string date)
81 	{
82 		if (polling)
83 		{
84 			if (lastDate is null)
85 				schedulePoll();
86 			lastDate = date;
87 		}
88 	}
89 
90 	void onNewNews(string[] reply)
91 	{
92 		bool[string] messages;
93 		foreach (message; reply[1..$])
94 			messages[message] = true;
95 
96 		assert(queued == 0);
97 		foreach (message, b; messages)
98 			if (!(message in oldMessages))
99 			{
100 				client.getMessage(message, &onMessage);
101 				queued++;
102 			}
103 		oldMessages = messages;
104 		if (queued==0)
105 			schedulePoll();
106 	}
107 
108 	void onMessage(string[] lines, string num, string id)
109 	{
110 		if (handleMessage)
111 			handleMessage(lines, num, id);
112 
113 		if (polling)
114 		{
115 			queued--;
116 			if (queued==0)
117 				schedulePoll();
118 		}
119 	}
120 
121 public:
122 	this(Logger log)
123 	{
124 		client = new NntpClient(log);
125 		client.handleDisconnect = &onDisconnect;
126 	}
127 
128 	void connect(string server)
129 	{
130 		this.server = server;
131 		reconnect();
132 	}
133 
134 	void disconnect()
135 	{
136 		client.disconnect();
137 	}
138 
139 	void startPolling(string lastDate = null)
140 	{
141 		assert(!polling, "Already polling");
142 		polling = true;
143 		this.lastDate = lastDate;
144 		if (connected)
145 			poll();
146 	}
147 
148 	void delegate(string[] lines, string num, string id) handleMessage;
149 }