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)
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 		try synchronized(m_cmdInfo.mtx) { 
67 			m_cmdInfo.command = DNSCmd.RESOLVEHOST;
68 			m_cmdInfo.ipv6 = ipv6;
69 			m_cmdInfo.url = cast(shared) url;
70 		} catch {}
71 		
72 		return sendCommand();
73 	}
74 
75 	/// Returns an OS-specific NetworkAddress structure from the specified IP.
76 	NetworkAddress resolveIP(string url, bool ipv6)
77 	in {
78 		assert(!m_busy, "Resolver is busy or closed");
79 		assert(m_handler.ctxt !is null, "AsyncDNS must be running before being operated on.");
80 	}
81 	body {
82 		return (cast(EventLoop)m_evLoop).resolveIP(url, 0, ipv6?isIPv6.yes:isIPv6.no);
83 	}
84 
85 	// chooses a thread or starts it if necessary
86 	private bool sendCommand() 
87 	in { assert(!waiting, "File is busy or closed"); }
88 	body {
89 		waiting = true;
90 		m_error = false;
91 		status = StatusInfo.init;
92 		
93 		Waiter cmd_handler;
94 		try {
95 			cmd_handler = popWaiter();		
96 		} catch (Throwable e) {
97 			import std.stdio;
98 			try {
99 				status = StatusInfo(Status.ERROR, e.toString());
100 				m_error = true;
101 			} catch {}
102 			
103 			return false;
104 			
105 		}
106 		assert(cmd_handler.cond);
107 		m_cmdInfo.waiter = cast(shared)cmd_handler;
108 		try {
109 			synchronized(gs_wlock) 
110 				gs_jobs.insert(CommandInfo(CmdInfoType.DNS, cast(void*) this));
111 
112 			cmd_handler.cond.notifyAll(); 
113 		}
114 		catch (Exception e){
115 			import std.stdio;
116 			try writeln("Exception occured notifying foreign thread: ", e); catch {}
117 		}
118 		return true;
119 	}
120 
121 	/// Cleans up underlying resources. Used as a placeholder for possible future purposes.
122 	bool kill() {
123 		return true;
124 	}
125 
126 package:
127 	synchronized @property DNSCmdInfo cmdInfo() {
128 		return m_cmdInfo;
129 	}
130 
131 	
132 	shared(NetworkAddress*) addr() {
133 		try synchronized(m_cmdInfo.mtx)
134 			return cast(shared)&m_cmdInfo.addr;
135 		catch {}
136 		return null;
137 	}
138 
139 	synchronized @property void status(StatusInfo stat) {
140 		m_status = cast(shared) stat;
141 	}
142 
143 	synchronized @property bool waiting() const {
144 		return cast(bool) m_busy;
145 	}
146 
147 	synchronized @property void waiting(bool b) {
148 		m_busy = cast(shared) b;
149 	}
150 	
151 	void callback() {
152 
153 		try {
154 			m_handler(cast(NetworkAddress)m_cmdInfo.addr);
155 		}
156 		catch (Throwable e) {
157 			import std.stdio : writeln;
158 			try writeln("Failed to send command. ", e.toString()); catch {}
159 		}
160 	}
161 
162 }
163 
164 package shared struct DNSCmdInfo
165 {
166 	DNSCmd command;
167 	bool ipv6;
168 	string url;
169 	NetworkAddress addr;
170 	Waiter waiter;
171 	AsyncSignal ready;
172 	AsyncDNS dns;
173 	Mutex mtx; // for NetworkAddress writing
174 }
175 
176 package shared struct DNSReadyHandler {
177 	AsyncDNS ctxt;
178 	void delegate(NetworkAddress) del;
179 	
180 	void opCall(NetworkAddress addr) {
181 		assert(ctxt !is null);
182 		del(addr);
183 		return;
184 	}
185 }