lua_dryos.c 18.4 KB
Newer Older
1
/***
alex@thinkpad's avatar
alex@thinkpad committed
2
3
4
5
6
7
8
9
10
11
 DryOS operating system functions.
 
 TODO: what about Penlight-like API for file I/O (see e.g.
 [pl.path](http://stevedonovan.github.io/Penlight/api/libraries/pl.path.html),
 [pl.dir](http://stevedonovan.github.io/Penlight/api/libraries/pl.dir.html),
 [pl.file](http://stevedonovan.github.io/Penlight/api/libraries/pl.file.html),
 [pl.utils](http://stevedonovan.github.io/Penlight/api/libraries/pl.utils.html))?
 
 Or maybe even porting a subset of [Penlight](http://stevedonovan.github.io/Penlight/api/manual/01-introduction.md.html)
 and/or [LFS](https://keplerproject.github.io/luafilesystem/manual.html)?
12
13
14
15
16
17
18
19
 
 @author Magic Lantern Team
 @copyright 2014
 @license GPL
 @module dryos
 */

#include <dryos.h>
20
#include <fio-ml.h>
21
22
#include <fileprefix.h>
#include <string.h>
David Milligan's avatar
David Milligan committed
23
#include <config.h>
24
#include <propvalues.h>
25
26
27

#include "lua_common.h"

28
29
static int luaCB_card_index(lua_State * L);
static int luaCB_card_newindex(lua_State * L);
30
31
static int luaCB_directory_index(lua_State * L);
static int luaCB_directory_newindex(lua_State * L);
32
static int luaCB_card_image_path(lua_State * L);
33

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
const char * lua_dryos_directory_fields[] =
{
    "exists",
    "create",
    "children",
    "files",
    "parent",
    NULL
};

const char * lua_dryos_card_fields[] =
{
    "cluster_size",
    "drive_letter",
    "file_number",
    "folder_number",
    "free_space",
    "type",
    NULL
};

55
56
57
58
59
/***
 Creates a @{directory} object that is used to get information about a directory
 
 This function does not actually create a directory on the file system, it just creates
 an object that represents a directory. To actually create the directory in the file system
alex@thinkpad's avatar
alex@thinkpad committed
60
 call `directory:create()`.
61
62
63
64
65
66
67
68
69
70
71
72
73
74
 @usage
 local mydir = dryos.directory("mydir")
 if mydir.exists == false then
    mydir:create()
 end
 for i,v in ipairs(mydir:files()) do 
    print("filename: "..v)
 end
 @tparam string path
 @treturn directory
 @function directory
 */
static int luaCB_dryos_directory(lua_State * L)
{
75
    LUA_PARAM_STRING(path, 1);
76
    lua_newtable(L);
77
78
79
80
81
82
83
84
85
86
87
88
89
90
    if (strlen(path) == 0 || !strcmp(path, "/"))
    {
        struct card_info * ml_card = get_ml_card();
        if (ml_card == NULL) return luaL_error(L, "Could not get ML card");
        lua_pushfstring(L, "%s:/", ml_card->drive_letter);
    }
    else if (path[strlen(path) - 1] != '/')
    {
        lua_pushfstring(L, "%s/", path);
    }
    else
    {
        lua_pushvalue(L, 1);
    }
91
    lua_setfield(L, -2, "path");
92
    lua_newtable(L);
93
94
95
96
    lua_pushcfunction(L, luaCB_directory_index);
    lua_setfield(L, -2, "__index");
    lua_pushcfunction(L, luaCB_directory_newindex);
    lua_setfield(L, -2, "__newindex");
97
98
99
100
    lua_pushcfunction(L, luaCB_pairs);
    lua_setfield(L, -2, "__pairs");
    lua_pushlightuserdata(L, lua_dryos_directory_fields);
    lua_setfield(L, -2, "fields");
101
102
103
104
    lua_setmetatable(L, -2);
    return 1;
}

105
106
107
108
109
110
111
112
113
114
115
116
117
/***
 Deletes a file from the card.
 @tparam string filename
 @treturn bool success
 @function remove
 */
static int luaCB_dryos_remove(lua_State * L)
{
    LUA_PARAM_STRING(filename, 1);
    lua_pushboolean(L, FIO_RemoveFile(filename) == 0);
    return 1;
}

118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/***
 Renames/moves a file on the card (or between cards).
 @tparam string filename
 @treturn bool success
 @function rename
 */
static int luaCB_dryos_rename(lua_State * L)
{
    LUA_PARAM_STRING(src, 1);
    LUA_PARAM_STRING(dst, 2);
    int err = FIO_RenameFile(src, dst);
    if (err)
    {
        err = FIO_MoveFile(src, dst);
    }
    lua_pushboolean(L, err == 0);
    return 1;
}

alex@thinkpad's avatar
alex@thinkpad committed
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/***
 Calls an eventproc (a function from the camera firmware which can be called by name).
 See [Eventprocs](http://magiclantern.wikia.com/wiki/Call_by_name).

 Dangerous - you need to compile Lua yourself in order to enable it.
 @tparam string function the name of the function to call
 @param[opt] arg argument to pass to the call
 @function call
 */
static int luaCB_dryos_call(lua_State * L)
{
#if 1
    return luaL_error(L,
        "dryos.call() is disabled for safety reasons.\n"
        "If you know what you are doing, just remove this message and recompile.\n"
    );
#endif

    LUA_PARAM_STRING(function_name, 1);
    int result = 0;
    int argc = lua_gettop(L);
    
    if(argc <= 1)
    {
        result = call(function_name);
    }
    else if(lua_isinteger(L, 2))
    {
        int arg = lua_tointeger(L, 2);
        result = call(function_name, arg);
    }
    else if(lua_isnumber(L, 2))
    {
        float arg = lua_tonumber(L, 2);
        result = call(function_name, arg);
    }
    else if(lua_isstring(L, 2))
    {
        const char * arg = lua_tostring(L, 2);
        result = call(function_name, arg);
    }
    
    lua_pushinteger(L, result);
    return 1;
}

183
184
185
186
187
188
189
190
191
192
193
194
static void setfield (lua_State *L, const char *key, int value) {
    lua_pushinteger(L, value);
    lua_setfield(L, -2, key);
}

static void setboolfield (lua_State *L, const char *key, int value) {
    if (value < 0)  /* undefined? */
        return;  /* does not set field */
    lua_pushboolean(L, value);
    lua_setfield(L, -2, key);
}

195
196
197
198
199
200
201
static const luaL_Reg card_funcs[] =
{
    { "image_path", luaCB_card_image_path },
    { NULL, NULL }
};


202
203
204
static int luaCB_dryos_index(lua_State * L)
{
    LUA_PARAM_STRING_OPTIONAL(key, 2, "");
alex@thinkpad's avatar
alex@thinkpad committed
205
    /// Get the number of the seconds since camera startup.
David Milligan's avatar
David Milligan committed
206
    // @tfield int clock
207
    if(!strcmp(key, "clock")) lua_pushinteger(L, get_seconds_clock());
alex@thinkpad's avatar
alex@thinkpad committed
208
    /// Get the number of milliseconds since camera startup.
David Milligan's avatar
David Milligan committed
209
    // @tfield int ms_clock
210
    else if(!strcmp(key, "ms_clock")) lua_pushinteger(L, get_ms_clock());
alex@thinkpad's avatar
alex@thinkpad committed
211
    /// Get/Set the image filename prefix (e.g.&nbsp;"IMG_").
212
213
214
215
    ///
    /// Set to empty string to restore default value.
    // @tfield string image_prefix
    else if(!strcmp(key, "image_prefix")) lua_pushstring(L, get_file_prefix());
alex@thinkpad's avatar
alex@thinkpad committed
216
    /// Get the DCIM directory.
David Milligan's avatar
David Milligan committed
217
218
219
    // @tfield directory dcim_dir
    else if(!strcmp(key, "dcim_dir"))
    {
220
        lua_pushcfunction(L, luaCB_dryos_directory);
David Milligan's avatar
David Milligan committed
221
222
223
        lua_pushstring(L, get_dcim_dir());
        lua_call(L, 1, 1);
    }
alex@thinkpad's avatar
alex@thinkpad committed
224
    /// Get the ML config directory.
David Milligan's avatar
David Milligan committed
225
226
227
228
229
230
231
    // @tfield directory config_dir
    else if(!strcmp(key, "config_dir"))
    {
        lua_pushcfunction(L, luaCB_dryos_directory);
        lua_pushstring(L, get_config_dir());
        lua_call(L, 1, 1);
    }
alex@thinkpad's avatar
alex@thinkpad committed
232
    /// Get the card ML was started from.
233
234
235
236
    // @tfield card ml_card
    else if(!strcmp(key, "ml_card"))
    {
        lua_newtable(L);
237
238
239
        struct card_info * card = get_ml_card();
        if(!card) return luaL_error(L, "Error getting ml_card");
        lua_pushlightuserdata(L, card);
240
        lua_setfield(L, -2, "_card_ptr");
241
        lua_pushfstring(L, "%s:/", card->drive_letter);
242
        lua_setfield(L, -2, "path");
243
        lua_newtable(L);
244
245
246
247
        lua_pushcfunction(L, luaCB_card_index);
        lua_setfield(L, -2, "__index");
        lua_pushcfunction(L, luaCB_card_newindex);
        lua_setfield(L, -2, "__newindex");
248
249
250
251
        lua_pushcfunction(L, luaCB_pairs);
        lua_setfield(L, -2, "__pairs");
        lua_pushlightuserdata(L, lua_dryos_card_fields);
        lua_setfield(L, -2, "fields");
252
253
        lua_setmetatable(L, -2);
    }
alex@thinkpad's avatar
alex@thinkpad committed
254
    /// Get the shooting card (the one selected in Canon menu for taking pictures / recording videos).
255
256
257
258
    // @tfield card shooting_card
    else if(!strcmp(key, "shooting_card"))
    {
        lua_newtable(L);
259
260
261
        struct card_info * card = get_shooting_card();
        if(!card) return luaL_error(L, "Error getting shooting_card");
        lua_pushlightuserdata(L, card);
262
        lua_setfield(L, -2, "_card_ptr");
263
        lua_pushfstring(L, "%s:/", card->drive_letter);
264
        lua_setfield(L, -2, "path");
265
        luaL_setfuncs(L, card_funcs, 0);
266
        lua_newtable(L);
267
268
269
270
        lua_pushcfunction(L, luaCB_card_index);
        lua_setfield(L, -2, "__index");
        lua_pushcfunction(L, luaCB_card_newindex);
        lua_setfield(L, -2, "__newindex");
271
272
273
274
        lua_pushcfunction(L, luaCB_pairs);
        lua_setfield(L, -2, "__pairs");
        lua_pushlightuserdata(L, lua_dryos_card_fields);
        lua_setfield(L, -2, "fields");
275
276
        lua_setmetatable(L, -2);
    }
alex@thinkpad's avatar
alex@thinkpad committed
277
    /// Gets a table representing the current date/time.
David Milligan's avatar
David Milligan committed
278
    // @tfield date date
279
280
    else if(!strcmp(key, "date"))
    {
David Milligan's avatar
David Milligan committed
281
282
        /// Represents a date/time
        // @type date
283
284
285
        struct tm tm;
        LoadCalendarFromRTC(&tm);
        lua_newtable(L);
David Milligan's avatar
David Milligan committed
286
        /// Second
David Milligan's avatar
David Milligan committed
287
        // @tfield int sec
288
        setfield(L, "sec", tm.tm_sec);
David Milligan's avatar
David Milligan committed
289
        /// Minute
David Milligan's avatar
David Milligan committed
290
        // @tfield int min
291
        setfield(L, "min", tm.tm_min);
David Milligan's avatar
David Milligan committed
292
        /// Hour
David Milligan's avatar
David Milligan committed
293
        // @tfield int hour
294
        setfield(L, "hour", tm.tm_hour);
David Milligan's avatar
David Milligan committed
295
        /// Day
David Milligan's avatar
David Milligan committed
296
        // @tfield int day
297
        setfield(L, "day", tm.tm_mday);
David Milligan's avatar
David Milligan committed
298
        /// Month
David Milligan's avatar
David Milligan committed
299
        // @tfield int month
300
        setfield(L, "month", tm.tm_mon+1);
David Milligan's avatar
David Milligan committed
301
        /// Year
David Milligan's avatar
David Milligan committed
302
        // @tfield int year
303
        setfield(L, "year", tm.tm_year+1900);
David Milligan's avatar
David Milligan committed
304
        /// Day of week
David Milligan's avatar
David Milligan committed
305
        // @tfield int wday
306
        setfield(L, "wday", tm.tm_wday+1);
David Milligan's avatar
David Milligan committed
307
        /// Day of year
David Milligan's avatar
David Milligan committed
308
        // @tfield int yday
309
        setfield(L, "yday", tm.tm_yday+1);
David Milligan's avatar
David Milligan committed
310
        /// Daylight Savings
David Milligan's avatar
David Milligan committed
311
        // @tfield bool isdst
312
313
314
315
316
317
318
319
320
321
322
323
324
        setboolfield(L, "isdst", tm.tm_isdst);
    }
    else lua_rawget(L, 1);
    return 1;
}

static int luaCB_dryos_newindex(lua_State * L)
{
    LUA_PARAM_STRING_OPTIONAL(key, 2, "");
    if(!strcmp(key, "clock") || !strcmp(key, "ms_clock") || !strcmp(key, "date") || !strcmp(key, "ml_card") || !strcmp(key, "dcim_dir"))
    {
        return luaL_error(L, "'%s' is readonly!", key);
    }
325
    else if(!strcmp(key, "image_prefix"))
326
    {
327
        static char prefix[8];
328
        static int prefix_key = 0;
329
        LUA_PARAM_STRING(new_prefix, 3);
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347

        int len = strlen(new_prefix);
        if (len != 0 && len != 4)
        {
            return luaL_error(L, "invalid prefix length (4 chars, 0 to reset)");
        }

        if (prefix_key)
        {
            file_prefix_reset(prefix_key);
            prefix_key = 0;
        }

        if (len == 4)
        {
            strncpy(prefix, new_prefix, 7);
            prefix_key = file_prefix_set(prefix);
        }
348
349
350
351
352
353
354
355
    }
    else
    {
        lua_rawset(L, 1);
    }
    return 0;
}

356
357
358
359
/// Represents a directory
// @type directory

/***
alex@thinkpad's avatar
alex@thinkpad committed
360
 Creates a directory.
David Milligan's avatar
David Milligan committed
361
 @treturn bool whether or not the directory was sucessfully created
362
363
364
365
366
 @function create
 */
static int luaCB_directory_create(lua_State * L)
{
    if(!lua_istable(L, 1)) return luaL_argerror(L, 1, "expected table");
367
    lua_getfield(L, 1, "path");
368
369
370
371
372
373
    const char * path = lua_tostring(L, -1);
    lua_pushinteger(L, FIO_CreateDirectory(path));
    return 1;
}

/***
alex@thinkpad's avatar
alex@thinkpad committed
374
 Get a table (of @{directory} objects) containing this directory's child directories.
375
376
377
378
379
380
 @treturn {directory,...}
 @function children
 */
static int luaCB_directory_children(lua_State * L)
{
    if(!lua_istable(L, 1)) return luaL_argerror(L, 1, "expected table");
381
    if(lua_getfield(L, 1, "path") != LUA_TSTRING) return luaL_error(L, "invalid directory path");
382
383
384
385
386
387
388
389
390
391
392
393
394
    const char * path = lua_tostring(L, -1);
    struct fio_file file;
    struct fio_dirent * dirent = FIO_FindFirstEx(path, &file);
    int index = 1;
    if(!IS_ERROR(dirent))
    {
        lua_newtable(L);
        do
        {
            if (file.mode & ATTR_DIRECTORY)
            {
                //call the directory constructor
                lua_pushcfunction(L, luaCB_dryos_directory);
395
                lua_pushfstring(L, "%s%s/", path, file.name);
396
397
398
399
400
                lua_call(L, 1, 1);
                lua_seti(L, -2, index++);
            }
        }
        while(FIO_FindNextEx(dirent, &file) == 0);
401
        FIO_FindClose(dirent);
402
403
404
    }
    else
    {
405
        return luaL_error(L, "error reading directory '%s'", path);
406
407
408
409
410
411
    }
    
    return 1;
}

/***
alex@thinkpad's avatar
alex@thinkpad committed
412
 Get a table (of @{string}s) that are the file names of this directory's files.
413
414
415
416
417
418
 @treturn {string,...}
 @function files
 */
static int luaCB_directory_files(lua_State * L)
{
    if(!lua_istable(L, 1)) return luaL_argerror(L, 1, "expected table");
419
    if(lua_getfield(L, 1, "path") != LUA_TSTRING) return luaL_error(L, "invalid directory path");
420
421
422
423
424
425
426
427
428
429
430
    const char * path = lua_tostring(L, -1);
    struct fio_file file;
    struct fio_dirent * dirent = FIO_FindFirstEx(path, &file);
    int index = 1;
    if(!IS_ERROR(dirent))
    {
        lua_newtable(L);
        do
        {
            if (!(file.mode & ATTR_DIRECTORY))
            {
431
                lua_pushfstring(L, "%s%s", path, file.name);
432
433
434
435
                lua_seti(L, -2, index++);
            }
        }
        while(FIO_FindNextEx(dirent, &file) == 0);
436
        FIO_FindClose(dirent);
437
438
439
    }
    else
    {
440
        return luaL_error(L, "error reading directory: '%s'", path);
441
442
443
444
445
446
447
448
449
    }
    
    return 1;
}

static int luaCB_directory_index(lua_State * L)
{
    if(!lua_istable(L, 1)) return luaL_argerror(L, 1, "expected table");
    LUA_PARAM_STRING_OPTIONAL(key, 2, "");
alex@thinkpad's avatar
alex@thinkpad committed
450
    /// Get the full path of the directory.
451
    // @tfield string path
452
453
454
455
456
    if(!strcmp(key, "path")) return lua_rawget(L, 1);
    
    if(lua_getfield(L, 1, "path") != LUA_TSTRING) return luaL_error(L, "invalid directory path");
    const char * path = lua_tostring(L, -1);
    lua_pop(L, 1);
alex@thinkpad's avatar
alex@thinkpad committed
457
    /// Get whether or not the directory exists.
David Milligan's avatar
David Milligan committed
458
    // @tfield bool exists
459
    if(!strcmp(key, "exists")) lua_pushboolean(L, is_dir(path));
460
461
462
    else if(!strcmp(key, "create")) lua_pushcfunction(L, luaCB_directory_create);
    else if(!strcmp(key, "children")) lua_pushcfunction(L, luaCB_directory_children);
    else if(!strcmp(key, "files")) lua_pushcfunction(L, luaCB_directory_files);
463
464
465
466
467
    /// Get a @{directory} object that represents the current directory's parent
    // @tfield directory parent
    else if(!strcmp(key, "parent"))
    {
        size_t len = strlen(path);
468
        if ((len > 3 || ((len == 2 || len == 3) && path[1] != ':')) && path[len - 1] == '/')
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
        {
            char * parent_path = copy_string(path);
            parent_path[len - 1] = 0x0;
            char * last = strrchr(parent_path, '/');
            if (last) *(last + 1) = 0x0;
            else parent_path[0] = 0x0;
            
            //call the directory constructor
            lua_pushcfunction(L, luaCB_dryos_directory);
            lua_pushstring(L, parent_path);
            lua_call(L, 1, 1);
            free(parent_path);
            return 1;
        }
        else
        {
            return 0;
        }
        
    }
489
490
491
492
493
494
495
496
497
    else lua_rawget(L, 1);
    return 1;
}

static int luaCB_directory_newindex(lua_State * L)
{
    return luaL_error(L, "'directory' type is readonly");
}

alex@thinkpad's avatar
alex@thinkpad committed
498
/// Represents a card (storage media).
499
// Inherits from @{directory}
500
501
// @type card

502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
/***
 Returns current/previous/future still image path. Examples:

 - `B:/DCIM/100CANON/IMG1234.CR2` (with extension)
 - `B:/DCIM/100CANON/IMG1234` (without extension)
 
 @tparam[opt=0] int file_offset 0 = last saved image, positive = future images, negative = previous images.
 @tparam[opt=nil] ?string|nil = preferred extension, nil = automatic.
 @function image_path
 @treturn string
 */
static int luaCB_card_image_path(lua_State * L)
{
    if(!lua_istable(L, 1)) return luaL_argerror(L, 1, "expected table");
    if(lua_getfield(L, 1, "_card_ptr") == LUA_TLIGHTUSERDATA)
    {
        struct card_info * card = lua_touserdata(L, -1);
        LUA_PARAM_INT_OPTIONAL(file_offset, 2, 0);
        LUA_PARAM_STRING_OPTIONAL(extension, 3, 0);

        char image_path[32];    /* B:/DCIM/100CANON/IMG_1234.CR2 */

        int folder_num = card->folder_number;
        int file_num = card->file_number;
        for (int i = 0; i != file_offset; i += SGN(file_offset))
        {
            file_num += SGN(file_offset);
            if (file_num > 9999)
            {
                file_num = 1;
                folder_num++;
                if (folder_num > 999)
                {
                    folder_num = 100;
                }
            }
            else if (file_num < 1)
            {
                file_num = 9999;
                folder_num--;
                if (folder_num < 100)
                {
                    folder_num = 999;
                }
            }
        }

        int raw = pic_quality & 0x60000;

        snprintf(image_path, sizeof(image_path),
            "%s:/DCIM/%03d%s/%s%04d%s",
            card->drive_letter,     /* B */
            folder_num,             /* 100 */
            get_dcim_dir_suffix(),  /* CANON */
            get_file_prefix(),      /* IMG_ */
            file_num,               /* 1234 */
            extension ? extension : raw ? ".CR2" : ".JPG"
        );

        lua_pushstring(L, image_path);
        return 1;
    }
    else
    {
        return luaL_error(L, "could not get lightuserdata for card");
    }
}

570
571
572
573
574
575
576
static int luaCB_card_index(lua_State * L)
{
    if(!lua_istable(L, 1)) return luaL_argerror(L, 1, "expected table");
    LUA_PARAM_STRING_OPTIONAL(key, 2, "");
    if(lua_getfield(L, 1, "_card_ptr") == LUA_TLIGHTUSERDATA)
    {
        struct card_info * card = lua_touserdata(L, -1);
alex@thinkpad's avatar
alex@thinkpad committed
577
        /// Get the cluster size of the filesystem.
David Milligan's avatar
David Milligan committed
578
        // @tfield int cluster_size
579
        if(!strcmp(key, "cluster_size")) lua_pushinteger(L, card->cluster_size);
alex@thinkpad's avatar
alex@thinkpad committed
580
        /// Get the drive letter (A or B).
581
582
        // @tfield string drive_letter
        else if(!strcmp(key, "drive_letter")) lua_pushstring(L, card->drive_letter);
alex@thinkpad's avatar
alex@thinkpad committed
583
        /// Get the current Canon file number (e.g. IMG_1234.CR2 -> 1234).
David Milligan's avatar
David Milligan committed
584
        // @tfield int file_number
585
        else if(!strcmp(key, "file_number")) lua_pushinteger(L, card->file_number);
alex@thinkpad's avatar
alex@thinkpad committed
586
        /// Get the current Canon folder number (e.g. DCIM/101CANON => 101).
David Milligan's avatar
David Milligan committed
587
        // @tfield int folder_number
588
        else if(!strcmp(key, "folder_number")) lua_pushinteger(L, card->folder_number);
alex@thinkpad's avatar
alex@thinkpad committed
589
590
591
        /// Get the current free space (in MiB).
        ///
        /// FIXME: does not update after writing files from ML code.
David Milligan's avatar
David Milligan committed
592
        // @tfield int free_space
593
        else if(!strcmp(key, "free_space")) lua_pushinteger(L, get_free_space_32k(card) * 1024 / 32);
alex@thinkpad's avatar
alex@thinkpad committed
594
        /// Get the type of card (SD or CF).
595
596
        // @tfield string type
        else if(!strcmp(key, "type")) lua_pushstring(L, card->type);
597
        else return luaCB_directory_index(L);
598
599
600
601
602
603
604
605
606
607
608
609
    }
    else
    {
        return luaL_error(L, "could not get lightuserdata for card");
    }
    return 1;
}
static int luaCB_card_newindex(lua_State * L)
{
    return luaL_error(L, "'card' type is readonly");
}

610
611
612
613
static const char * lua_dryos_fields[] =
{
    "clock",
    "ms_clock",
614
    "image_prefix",
615
616
617
618
619
620
621
622
    "dcim_dir",
    "config_dir",
    "ml_card",
    "shooting_card",
    "date",
    NULL
};

623
624
625
const luaL_Reg dryoslib[] =
{
    {"call", luaCB_dryos_call},
626
    {"directory", luaCB_dryos_directory},
627
    {"remove", luaCB_dryos_remove},
628
    {"rename", luaCB_dryos_rename},
629
630
631
632
    {NULL, NULL}
};

LUA_LIB(dryos)