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 }