1 module libasync.dns;
2 
3 import libasync.types;
4 import libasync.events;
5 import core.thread : Thread, ThreadGroup;
6 import core.sync.mutex;
7 import core.sync.condition;
8 import core.atomic;
9 import libasync.threads;
10 
11 enum DNSCmd {
12 	RESOLVEHOST,
13 	RESOLVEIP
14 }
15 
16 /// Resolves internet addresses and returns the results in a specified callback.
17 shared final class AsyncDNS
18 {
19 nothrow:
20 private:
21 	EventLoop m_evLoop;
22 	bool m_busy;
23 	bool m_error;
24 	DNSReadyHandler m_handler;
25 	DNSCmdInfo m_cmdInfo;
26 	StatusInfo m_status;
27 	Thread m_owner;
28 	
29 public:
30 	this(EventLoop evl) {
31 		m_evLoop = cast(shared) evl;
32 		try m_cmdInfo.ready = new shared AsyncSignal(cast(EventLoop)m_evLoop); catch { assert(false, "Failed to start DNS Signaling"); }
33 		m_cmdInfo.ready.run(cast(void delegate())&callback);
34 		m_owner = cast(shared)Thread.getThis();
35 		try m_cmdInfo.mtx = cast(shared) new Mutex; catch {}
36 	}
37 	
38 	synchronized @property StatusInfo status() const
39 	{
40 		return cast(StatusInfo) m_status;
41 	}
42 	
43 	@property string error() const
44 	{
45 		return status.text;
46 	}
47 
48 	/// Uses the callback for all resolved addresses.
49 	shared(typeof(this)) handler(void delegate(NetworkAddress) del) {
50 		shared DNSReadyHandler handler;
51 		handler.del = cast(shared) del;
52 		handler.ctxt = this;
53 		try synchronized(this) m_handler = handler; catch { assert(false, "Failed to set handler in AsyncDNS"); }
54 		return this;
55 	}
56 
57 	/// Sends a request through a thread pool for the specified host to be resolved. The
58 	/// callback specified in run() will be signaled with the OS-specific NetworkAddress
59 	/// structure.
60 	bool resolveHost(string url, bool ipv6 = false, bool force_async = false)
61 	in {
62 		assert(!m_busy, "Resolver is busy or closed");
63 		assert(m_handler.ctxt !is null, "AsyncDNS must be running before being operated on.");
64 	}
65 	body {
66 		if (force_async == true) {
67 			try synchronized(m_cmdInfo.mtx) { 
68 				m_cmdInfo.command = DNSCmd.RESOLVEHOST;
69 				m_cmdInfo.ipv6 = ipv6;
70 				m_cmdInfo.url = cast(shared) url;
71 			} catch {}
72 		} else {
73 			m_cmdInfo.command = DNSCmd.RESOLVEHOST;
74 			m_cmdInfo.ipv6 = ipv6;
75 			m_cmdInfo.url = cast(shared) url;
76 			m_cmdInfo.addr = cast(shared)( (cast(EventLoop)m_evLoop).resolveHost(cmdInfo.url, 0, cmdInfo.ipv6?isIPv6.yes:isIPv6.no) );
77 			callback();
78 			return true;
79 		}
80 
81 		return sendCommand();
82 	}
83 
84 	/// Returns an OS-specific NetworkAddress structure from the specified IP.
85 	NetworkAddress resolveIP(string url, bool ipv6)
86 	in {
87 		assert(!m_busy, "Resolver is busy or closed");
88 		assert(m_handler.ctxt !is null, "AsyncDNS must be running before being operated on.");
89 	}
90 	body {
91 		return (cast(EventLoop)m_evLoop).resolveIP(url, 0, ipv6?isIPv6.yes:isIPv6.no);
92 	}
93 
94 	// chooses a thread or starts it if necessary
95 	private bool sendCommand() 
96 	in { assert(!waiting, "File is busy or closed"); }
97 	body {
98 		waiting = true;
99 		m_error = false;
100 		status = StatusInfo.init;
101 		
102 		Waiter cmd_handler;
103 		try {
104 			cmd_handler = popWaiter();		
105 		} catch (Throwable e) {
106 			import std.stdio;
107 			try {
108 				status = StatusInfo(Status.ERROR, e.toString());
109 				m_error = true;
110 			} catch {}
111 			
112 			return false;			
113 		}
114 
115 		if (cmd_handler is Waiter.init) {
116 			m_cmdInfo.addr = cast(shared)((cast()m_evLoop).resolveHost(cmdInfo.url, 0, cmdInfo.ipv6?isIPv6.yes:isIPv6.no));
117 			callback();
118 			return true;
119 		}
120 		assert(cmd_handler.cond);
121 		m_cmdInfo.waiter = cast(shared)cmd_handler;
122 		try {
123 			synchronized(gs_wlock) 
124 				gs_jobs.insert(CommandInfo(CmdInfoType.DNS, cast(void*) this));
125 
126 			cmd_handler.cond.notifyAll(); 
127 		}
128 		catch (Exception e){
129 			import std.stdio;
130 			try writeln("Exception occured notifying foreign thread: ", e); catch {}
131 		}
132 		return true;
133 	}
134 
135 	/// Cleans up underlying resources. Used as a placeholder for possible future purposes.
136 	bool kill() {
137 		return true;
138 	}
139 
140 package:
141 	synchronized @property DNSCmdInfo cmdInfo() {
142 		return m_cmdInfo;
143 	}
144 
145 	
146 	shared(NetworkAddress*) addr() {
147 		try synchronized(m_cmdInfo.mtx)
148 			return cast(shared)&m_cmdInfo.addr;
149 		catch {}
150 		return null;
151 	}
152 
153 	synchronized @property void status(StatusInfo stat) {
154 		m_status = cast(shared) stat;
155 	}
156 
157 	synchronized @property bool waiting() const {
158 		return cast(bool) m_busy;
159 	}
160 
161 	synchronized @property void waiting(bool b) {
162 		m_busy = cast(shared) b;
163 	}
164 	
165 	void callback() {
166 
167 		try {
168 			m_handler(cast(NetworkAddress)m_cmdInfo.addr);
169 		}
170 		catch (Throwable e) {
171 			static if (DEBUG) {
172 				import std.stdio : writeln;
173 				try writeln("Failed to send command. ", e.toString()); catch {}
174 			}
175 		}
176 	}
177 
178 }
179 
180 package shared struct DNSCmdInfo
181 {
182 	DNSCmd command;
183 	bool ipv6;
184 	string url;
185 	NetworkAddress addr;
186 	Waiter waiter;
187 	AsyncSignal ready;
188 	AsyncDNS dns;
189 	Mutex mtx; // for NetworkAddress writing
190 }
191 
192 package shared struct DNSReadyHandler {
193 	AsyncDNS ctxt;
194 	void delegate(NetworkAddress) del;
195 	
196 	void opCall(NetworkAddress addr) {
197 		assert(ctxt !is null);
198 		del(addr);
199 		return;
200 	}
201 }