1 | /** |
2 | * Copyright (c) 2006-2023 LOVE Development Team |
3 | * |
4 | * This software is provided 'as-is', without any express or implied |
5 | * warranty. In no event will the authors be held liable for any damages |
6 | * arising from the use of this software. |
7 | * |
8 | * Permission is granted to anyone to use this software for any purpose, |
9 | * including commercial applications, and to alter it and redistribute it |
10 | * freely, subject to the following restrictions: |
11 | * |
12 | * 1. The origin of this software must not be misrepresented; you must not |
13 | * claim that you wrote the original software. If you use this software |
14 | * in a product, an acknowledgment in the product documentation would be |
15 | * appreciated but is not required. |
16 | * 2. Altered source versions must be plainly marked as such, and must not be |
17 | * misrepresented as being the original software. |
18 | * 3. This notice may not be removed or altered from any source distribution. |
19 | **/ |
20 | |
21 | #include "wrap_File.h" |
22 | |
23 | #include "common/Data.h" |
24 | #include "common/Exception.h" |
25 | #include "common/int.h" |
26 | |
27 | #include "data/wrap_DataModule.h" |
28 | |
29 | namespace love |
30 | { |
31 | namespace filesystem |
32 | { |
33 | |
34 | int luax_ioError(lua_State *L, const char *fmt, ...) |
35 | { |
36 | va_list args; |
37 | va_start(args, fmt); |
38 | |
39 | lua_pushnil(L); |
40 | lua_pushvfstring(L, fmt, args); |
41 | |
42 | va_end(args); |
43 | return 2; |
44 | } |
45 | |
46 | File *luax_checkfile(lua_State *L, int idx) |
47 | { |
48 | return luax_checktype<File>(L, idx); |
49 | } |
50 | |
51 | int w_File_getSize(lua_State *L) |
52 | { |
53 | File *t = luax_checkfile(L, 1); |
54 | |
55 | int64 size = -1; |
56 | try |
57 | { |
58 | size = t->getSize(); |
59 | } |
60 | catch (love::Exception &e) |
61 | { |
62 | return luax_ioError(L, "%s" , e.what()); |
63 | } |
64 | |
65 | // Push nil on failure or if size does not fit into a double precision floating-point number. |
66 | if (size == -1) |
67 | return luax_ioError(L, "Could not determine file size." ); |
68 | else if (size >= 0x20000000000000LL) |
69 | return luax_ioError(L, "Size is too large." ); |
70 | |
71 | lua_pushnumber(L, (lua_Number) size); |
72 | return 1; |
73 | } |
74 | |
75 | int w_File_open(lua_State *L) |
76 | { |
77 | File *file = luax_checkfile(L, 1); |
78 | const char *str = luaL_checkstring(L, 2); |
79 | File::Mode mode; |
80 | |
81 | if (!File::getConstant(str, mode)) |
82 | return luax_enumerror(L, "file open mode" , File::getConstants(mode), str); |
83 | |
84 | try |
85 | { |
86 | luax_pushboolean(L, file->open(mode)); |
87 | } |
88 | catch (love::Exception &e) |
89 | { |
90 | return luax_ioError(L, "%s" , e.what()); |
91 | } |
92 | |
93 | return 1; |
94 | } |
95 | |
96 | int w_File_close(lua_State *L) |
97 | { |
98 | File *file = luax_checkfile(L, 1); |
99 | luax_pushboolean(L, file->close()); |
100 | return 1; |
101 | } |
102 | |
103 | int w_File_isOpen(lua_State *L) |
104 | { |
105 | File *file = luax_checkfile(L, 1); |
106 | luax_pushboolean(L, file->isOpen()); |
107 | return 1; |
108 | } |
109 | |
110 | int w_File_read(lua_State *L) |
111 | { |
112 | File *file = luax_checkfile(L, 1); |
113 | StrongRef<FileData> d = nullptr; |
114 | |
115 | love::data::ContainerType ctype = love::data::CONTAINER_STRING; |
116 | |
117 | int startidx = 2; |
118 | if (lua_type(L, 2) == LUA_TSTRING) |
119 | { |
120 | ctype = love::data::luax_checkcontainertype(L, 2); |
121 | startidx = 3; |
122 | } |
123 | |
124 | int64 size = (int64) luaL_optnumber(L, startidx, (lua_Number) File::ALL); |
125 | |
126 | try |
127 | { |
128 | d.set(file->read(size), Acquire::NORETAIN); |
129 | } |
130 | catch (love::Exception &e) |
131 | { |
132 | return luax_ioError(L, "%s" , e.what()); |
133 | } |
134 | |
135 | if (ctype == love::data::CONTAINER_DATA) |
136 | luax_pushtype(L, d.get()); |
137 | else |
138 | lua_pushlstring(L, (const char *) d->getData(), d->getSize()); |
139 | |
140 | lua_pushinteger(L, d->getSize()); |
141 | |
142 | return 2; |
143 | } |
144 | |
145 | int w_File_write(lua_State *L) |
146 | { |
147 | File *file = luax_checkfile(L, 1); |
148 | bool result = false; |
149 | |
150 | if (lua_isstring(L, 2)) |
151 | { |
152 | try |
153 | { |
154 | size_t datasize = 0; |
155 | const char *data = lua_tolstring(L, 2, &datasize); |
156 | |
157 | if (!lua_isnoneornil(L, 3)) |
158 | datasize = luaL_checkinteger(L, 3); |
159 | |
160 | result = file->write(data, datasize); |
161 | } |
162 | catch (love::Exception &e) |
163 | { |
164 | return luax_ioError(L, "%s" , e.what()); |
165 | } |
166 | } |
167 | else if (luax_istype(L, 2, love::Data::type)) |
168 | { |
169 | try |
170 | { |
171 | love::Data *data = luax_totype<love::Data>(L, 2); |
172 | result = file->write(data, luaL_optinteger(L, 3, data->getSize())); |
173 | } |
174 | catch (love::Exception &e) |
175 | { |
176 | return luax_ioError(L, "%s" , e.what()); |
177 | } |
178 | } |
179 | else |
180 | { |
181 | return luaL_argerror(L, 2, "string or data expected" ); |
182 | } |
183 | |
184 | luax_pushboolean(L, result); |
185 | return 1; |
186 | } |
187 | |
188 | int w_File_flush(lua_State *L) |
189 | { |
190 | File *file = luax_checkfile(L, 1); |
191 | bool success = false; |
192 | try |
193 | { |
194 | success = file->flush(); |
195 | } |
196 | catch (love::Exception &e) |
197 | { |
198 | return luax_ioError(L, "%s" , e.what()); |
199 | } |
200 | luax_pushboolean(L, success); |
201 | return 1; |
202 | } |
203 | |
204 | int w_File_isEOF(lua_State *L) |
205 | { |
206 | File *file = luax_checkfile(L, 1); |
207 | luax_pushboolean(L, file->isEOF()); |
208 | return 1; |
209 | } |
210 | |
211 | int w_File_tell(lua_State *L) |
212 | { |
213 | File *file = luax_checkfile(L, 1); |
214 | int64 pos = file->tell(); |
215 | // Push nil on failure or if pos does not fit into a double precision floating-point number. |
216 | if (pos == -1) |
217 | return luax_ioError(L, "Invalid position." ); |
218 | else if (pos >= 0x20000000000000LL) |
219 | return luax_ioError(L, "Number is too large." ); |
220 | else |
221 | lua_pushnumber(L, (lua_Number)pos); |
222 | return 1; |
223 | } |
224 | |
225 | int w_File_seek(lua_State *L) |
226 | { |
227 | File *file = luax_checkfile(L, 1); |
228 | lua_Number pos = luaL_checknumber(L, 2); |
229 | |
230 | // Push false on negative and precision-problematic numbers. |
231 | // Better fail than seek to an unknown position. |
232 | if (pos < 0.0 || pos >= 9007199254740992.0) |
233 | luax_pushboolean(L, false); |
234 | else |
235 | luax_pushboolean(L, file->seek((uint64)pos)); |
236 | return 1; |
237 | } |
238 | |
239 | int w_File_lines_i(lua_State *L) |
240 | { |
241 | // The upvalues: |
242 | // - File |
243 | // - read buffer (string) |
244 | // - read buffer offset (number) |
245 | // - file position (number, optional) |
246 | // - restore userpos (bool, optional) |
247 | |
248 | File *file = luax_checktype<File>(L, lua_upvalueindex(1)); |
249 | |
250 | // Only accept read mode at this point. |
251 | if (file->getMode() != File::MODE_READ) |
252 | return luaL_error(L, "File needs to stay in read mode." ); |
253 | |
254 | // Get the current (lua-side) buffer info |
255 | size_t len; |
256 | const char *buffer = lua_tolstring(L, lua_upvalueindex(2), &len); |
257 | int offset = lua_tointeger(L, lua_upvalueindex(3)); |
258 | |
259 | // Find our next line |
260 | const char *start = buffer+offset; |
261 | const char *end = reinterpret_cast<const char*>(memchr(start, '\n', len-offset)); |
262 | |
263 | bool seekBack = luax_toboolean(L, lua_upvalueindex(5)); |
264 | |
265 | // If there are no more lines in the buffer, keep adding more data until we |
266 | // found another line or EOF |
267 | if (!end && !file->isEOF()) |
268 | { |
269 | const int readbufsize = 1024; |
270 | char readbuf[readbufsize]; |
271 | |
272 | // Build new buffer |
273 | luaL_Buffer storage; |
274 | luaL_buffinit(L, &storage); |
275 | luaL_addlstring(&storage, start, len-offset); |
276 | |
277 | // If the user has changed the position, we need to seek back first |
278 | int64 pos = file->tell(); |
279 | int64 userpos = -1; |
280 | if (seekBack) |
281 | { |
282 | userpos = pos; |
283 | pos = (int64) lua_tonumber(L, lua_upvalueindex(4)); |
284 | if (userpos != pos) |
285 | file->seek(pos); |
286 | } |
287 | |
288 | // Keep reading until newline or EOF |
289 | while (!file->isEOF()) |
290 | { |
291 | int read = (int) file->read(readbuf, readbufsize); |
292 | if (read < 0) |
293 | return luaL_error(L, "Could not read from file." ); |
294 | |
295 | luaL_addlstring(&storage, readbuf, read); |
296 | |
297 | // If we found a newline now, break |
298 | if (memchr(readbuf, '\n', read)) |
299 | break; |
300 | } |
301 | |
302 | // Possibly seek back to the user position |
303 | // But make sure to save our target position too |
304 | if (seekBack) |
305 | { |
306 | lua_pushnumber(L, file->tell()); |
307 | lua_replace(L, lua_upvalueindex(4)); |
308 | file->seek(userpos); |
309 | } |
310 | |
311 | // We've now got a new buffer, replace the old one |
312 | luaL_pushresult(&storage); |
313 | lua_replace(L, lua_upvalueindex(2)); |
314 | buffer = lua_tolstring(L, lua_upvalueindex(2), &len); |
315 | offset = 0; |
316 | start = buffer; |
317 | end = reinterpret_cast<const char*>(memchr(start, '\n', len)); |
318 | } |
319 | |
320 | if (!end) |
321 | end = buffer+len-1; |
322 | |
323 | // We've found the next line, update our offset upvalue and return |
324 | offset = end-buffer+1; |
325 | lua_pushinteger(L, offset); |
326 | lua_replace(L, lua_upvalueindex(3)); |
327 | |
328 | // If we're past the end, terminate (as we must be at EOF) |
329 | if (start == buffer+len) |
330 | { |
331 | file->close(); |
332 | return 0; |
333 | } |
334 | |
335 | // Unless we're at EOF, end points at '\n', so let's take that (and an |
336 | // optional '\r') off |
337 | if (end >= start && *end == '\n') |
338 | --end; |
339 | if (end >= start && *end == '\r') |
340 | --end; |
341 | |
342 | // Note: inclusive because *end contains the last character in the string |
343 | lua_pushlstring(L, start, end-start+1); |
344 | return 1; |
345 | } |
346 | |
347 | int w_File_lines(lua_State *L) |
348 | { |
349 | File *file = luax_checkfile(L, 1); |
350 | |
351 | lua_pushstring(L, "" ); // buffer |
352 | lua_pushnumber(L, 0); // buffer offset |
353 | lua_pushnumber(L, 0); // File position. |
354 | luax_pushboolean(L, file->getMode() != File::MODE_CLOSED); // Save current file position. |
355 | |
356 | if (file->getMode() != File::MODE_READ) |
357 | { |
358 | if (file->getMode() != File::MODE_CLOSED) |
359 | file->close(); |
360 | |
361 | bool success = false; |
362 | luax_catchexcept(L, [&](){ success = file->open(File::MODE_READ); }); |
363 | |
364 | if (!success) |
365 | return luaL_error(L, "Could not open file." ); |
366 | } |
367 | |
368 | lua_pushcclosure(L, w_File_lines_i, 5); |
369 | return 1; |
370 | } |
371 | |
372 | int w_File_setBuffer(lua_State *L) |
373 | { |
374 | File *file = luax_checkfile(L, 1); |
375 | const char *str = luaL_checkstring(L, 2); |
376 | int64 size = (int64) luaL_optnumber(L, 3, 0.0); |
377 | |
378 | File::BufferMode bufmode; |
379 | if (!File::getConstant(str, bufmode)) |
380 | return luax_enumerror(L, "file buffer mode" , File::getConstants(bufmode), str); |
381 | |
382 | bool success = false; |
383 | try |
384 | { |
385 | success = file->setBuffer(bufmode, size); |
386 | } |
387 | catch (love::Exception &e) |
388 | { |
389 | return luax_ioError(L, "%s" , e.what()); |
390 | } |
391 | |
392 | luax_pushboolean(L, success); |
393 | return 1; |
394 | } |
395 | |
396 | int w_File_getBuffer(lua_State *L) |
397 | { |
398 | File *file = luax_checkfile(L, 1); |
399 | int64 size = 0; |
400 | File::BufferMode bufmode = file->getBuffer(size); |
401 | const char *str = 0; |
402 | |
403 | if (!File::getConstant(bufmode, str)) |
404 | return luax_ioError(L, "Unknown file buffer mode." ); |
405 | |
406 | lua_pushstring(L, str); |
407 | lua_pushnumber(L, (lua_Number) size); |
408 | return 2; |
409 | } |
410 | |
411 | int w_File_getMode(lua_State *L) |
412 | { |
413 | File *file = luax_checkfile(L, 1); |
414 | |
415 | File::Mode mode = file->getMode(); |
416 | const char *str = 0; |
417 | |
418 | if (!File::getConstant(mode, str)) |
419 | return luax_ioError(L, "Unknown file mode." ); |
420 | |
421 | lua_pushstring(L, str); |
422 | return 1; |
423 | } |
424 | |
425 | int w_File_getFilename(lua_State *L) |
426 | { |
427 | File *file = luax_checkfile(L, 1); |
428 | luax_pushstring(L, file->getFilename()); |
429 | return 1; |
430 | } |
431 | |
432 | int w_File_getExtension(lua_State *L) |
433 | { |
434 | File *file = luax_checkfile(L, 1); |
435 | luax_pushstring(L, file->getExtension()); |
436 | return 1; |
437 | } |
438 | |
439 | const luaL_Reg w_File_functions[] = |
440 | { |
441 | { "getSize" , w_File_getSize }, |
442 | { "open" , w_File_open }, |
443 | { "close" , w_File_close }, |
444 | { "isOpen" , w_File_isOpen }, |
445 | { "read" , w_File_read }, |
446 | { "write" , w_File_write }, |
447 | { "flush" , w_File_flush }, |
448 | { "isEOF" , w_File_isEOF }, |
449 | { "tell" , w_File_tell }, |
450 | { "seek" , w_File_seek }, |
451 | { "lines" , w_File_lines }, |
452 | { "setBuffer" , w_File_setBuffer }, |
453 | { "getBuffer" , w_File_getBuffer }, |
454 | { "getMode" , w_File_getMode }, |
455 | { "getFilename" , w_File_getFilename }, |
456 | { "getExtension" , w_File_getExtension }, |
457 | { 0, 0 } |
458 | }; |
459 | |
460 | extern "C" int luaopen_file(lua_State *L) |
461 | { |
462 | return luax_register_type(L, &File::type, w_File_functions, nullptr); |
463 | } |
464 | |
465 | } // filesystem |
466 | } // love |
467 | |