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
29namespace love
30{
31namespace filesystem
32{
33
34int 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
46File *luax_checkfile(lua_State *L, int idx)
47{
48 return luax_checktype<File>(L, idx);
49}
50
51int 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
75int 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
96int 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
103int 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
110int 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
145int 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
188int 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
204int 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
211int 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
225int 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
239int 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
347int 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
372int 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
396int 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
411int 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
425int 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
432int 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
439const 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
460extern "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