1 module served.lsp.filereader;
2 
3 import core.thread;
4 import core.sync.mutex;
5 
6 import std.algorithm;
7 import std.stdio;
8 
9 /// A simple file reader continuously reading into a 1 byte buffer and appending
10 /// it to the data. Ensures data is never buffered on any platform at the cost
11 /// of being a lot more CPU intensive.
12 class StdFileReader : FileReader
13 {
14 	this(File file)
15 	{
16 		super();
17 		this.file = file;
18 	}
19 
20 	override void stop()
21 	{
22 		file.close();
23 	}
24 
25 	override void run()
26 	{
27 		ubyte[1] buffer;
28 		while (!file.eof)
29 		{
30 			auto chunk = file.rawRead(buffer[]);
31 			synchronized (mutex)
32 				data ~= chunk;
33 		}
34 	}
35 
36 	File file;
37 }
38 
39 /// A file reader implementation using the Win32 API using events. Reads as much
40 /// content as possible when new data is available at once, making the file
41 /// reading operation much more efficient when large chunks of data are being
42 /// transmitted.
43 version (Windows) class WindowsStdinReader : FileReader
44 {
45 	import core.sys.windows.windows;
46 
47 	bool running;
48 
49 	this()
50 	{
51 		super();
52 	}
53 
54 	override void stop()
55 	{
56 		running = false;
57 	}
58 
59 	override void run()
60 	{
61 		auto stdin = GetStdHandle(STD_INPUT_HANDLE);
62 		INPUT_RECORD inputRecord;
63 		DWORD nbRead;
64 		ubyte[4096] buffer;
65 		running = true;
66 		while (running)
67 		{
68 			switch (WaitForSingleObject(stdin, 1000))
69 			{
70 			case WAIT_TIMEOUT:
71 				break;
72 			case WAIT_OBJECT_0:
73 				DWORD len;
74 				if (!ReadFile(stdin, &buffer, buffer.length, &len, null))
75 				{
76 					stderr.writeln("ReadFile failed ", GetLastError());
77 					break;
78 				}
79 				if (len == 0)
80 				{
81 					stderr.writeln("WindowsStdinReader EOF");
82 					running = false;
83 					return;
84 				}
85 				synchronized (mutex)
86 					data ~= buffer[0 .. len];
87 				break;
88 			case WAIT_FAILED:
89 				stderr.writeln("stdin read failed ", GetLastError());
90 				break;
91 			case WAIT_ABANDONED:
92 				stderr.writeln("stdin read wait was abandoned ", GetLastError());
93 				break;
94 			default:
95 				stderr.writeln("Unexpected WaitForSingleObject response");
96 				break;
97 			}
98 		}
99 	}
100 }
101 
102 /// A file reader implementation using the POSIX select API using events. Reads
103 /// as much content as possible when new data is available at once, making the
104 /// file reading operation much more efficient when large chunks of data are
105 /// being transmitted.
106 version (Posix) class PosixStdinReader : FileReader
107 {
108 	import core.stdc.errno;
109 	import core.sys.posix.sys.select;
110 	import core.sys.posix.sys.time;
111 	import core.sys.posix.sys.types;
112 	import core.sys.posix.unistd;
113 
114 	override void stop()
115 	{
116 		stdin.close();
117 	}
118 
119 	override void run()
120 	{
121 
122 		ubyte[4096] buffer;
123 		while (!stdin.eof)
124 		{
125 			fd_set rfds;
126 			timeval tv;
127 
128 			FD_ZERO(&rfds);
129 			FD_SET(0, &rfds);
130 
131 			tv.tv_sec = 1;
132 
133 			auto ret = select(1, &rfds, null, null, &tv);
134 
135 			if (ret == -1)
136 			{
137 				int err = errno;
138 				if (err == EINTR)
139 					continue;
140 				stderr.writeln("PosixStdinReader error ", err, " in select()");
141 			}
142 			else if (ret)
143 			{
144 				auto len = read(0, buffer.ptr, buffer.length);
145 				if (len == -1)
146 					stderr.writeln("PosixStdinReader error ", errno, " in read()");
147 				else
148 					synchronized (mutex)
149 						data ~= buffer[0 .. len];
150 			}
151 		}
152 	}
153 }
154 
155 /// Base class for file readers which can read a file or standard handle line
156 /// by line in a Fiber context, yielding until a line is available.
157 abstract class FileReader : Thread
158 {
159 	this()
160 	{
161 		super(&run);
162 		mutex = new Mutex();
163 	}
164 
165 	string yieldLine()
166 	{
167 		ptrdiff_t index;
168 		string ret;
169 		while (true)
170 		{
171 			synchronized (mutex)
172 			{
173 				index = data.countUntil([cast(ubyte) '\r', cast(ubyte) '\n']);
174 				if (index != -1)
175 				{
176 					ret = cast(string) data[0 .. index].dup;
177 					data = data[index + 2 .. $];
178 					break;
179 				}
180 			}
181 			Fiber.yield();
182 		}
183 		return ret;
184 	}
185 
186 	ubyte[] yieldData(size_t length)
187 	{
188 		while (true)
189 		{
190 			synchronized (mutex)
191 			{
192 				if (data.length >= length)
193 				{
194 					auto ret = data[0 .. length].dup;
195 					data = data[length .. $];
196 					return ret;
197 				}
198 			}
199 			Fiber.yield();
200 		}
201 	}
202 
203 	abstract void stop();
204 
205 protected:
206 	abstract void run();
207 
208 	ubyte[] data;
209 	Mutex mutex;
210 	bool running;
211 }
212 
213 /// Creates a new FileReader using the GC reading from stdin using a platform
214 /// optimized implementation or StdFileReader if none is available.
215 ///
216 /// The created instance can then be started using the `start` method and
217 /// stopped at exit using the `stop` method.
218 ///
219 /// Examples:
220 /// ---
221 /// auto input = newStdinReader();
222 /// input.start();
223 /// scope (exit)
224 ///     input.stop();
225 /// ---
226 FileReader newStdinReader()
227 {
228 	version (Windows)
229 		return new WindowsStdinReader();
230 	else version (Posix)
231 		return new PosixStdinReader();
232 	else
233 		return new StdFileReader(stdin);
234 }
235 
236 /// Reads a file into a given buffer with a specified maximum length. If the
237 /// file is bigger than the buffer, the buffer will be resized using the GC and
238 /// updated through the ref argument.
239 /// Params:
240 ///   file = The filename of the file to read.
241 ///   buffer = A GC allocated buffer that may be enlarged if it is too small.
242 ///   maxLen = The maxmimum amount of bytes to read from the file.
243 /// Returns: The contents of the file up to maxLen or EOF. The data is a slice
244 /// of the buffer argument case to a `char[]`.
245 char[] readCodeWithBuffer(string file, ref ubyte[] buffer, size_t maxLen = 1024 * 50)
246 {
247 	auto f = File(file, "rb");
248 	size_t len = f.rawRead(buffer).length;
249 	if (f.eof)
250 		return cast(char[]) buffer[0 .. len];
251 	while (buffer.length * 2 < maxLen)
252 	{
253 		buffer.length *= 2;
254 		len += f.rawRead(buffer[len .. $]).length;
255 		if (f.eof)
256 			return cast(char[]) buffer[0 .. len];
257 	}
258 	if (buffer.length >= maxLen)
259 		return cast(char[]) buffer;
260 	buffer.length = maxLen;
261 	f.rawRead(buffer[len .. $]);
262 	return cast(char[]) buffer;
263 }