551536989ed0b6e51538e0be08e2056b51c2bc74
[geeqie.git] / src / lua.cc
1 /*
2  * Copyright (C) 2008 - 2016 The Geeqie Team
3  *
4  * Author: Klaus Ethgen
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include <config.h>
22
23 #ifdef HAVE_LUA
24
25 #define _XOPEN_SOURCE
26
27 #include <cstdio>
28 #include <cstring>
29 #include <ctime>
30 #include <memory>
31
32 #include <glib.h>
33 #include <lua.hpp>
34
35 #include "glua.h"
36 #include "debug.h"
37 #include "exif.h"
38 #include "filedata.h"
39 #include "main.h"
40 #include "main-defines.h"
41 #include "ui-fileops.h"
42
43 /**
44  * @file
45  * User API consists of the following namespaces:
46  *
47  * @link image_methods Image:@endlink basic image information
48  *
49  * <b>Collection</b>: not implemented
50  *
51  * @link exif_methods <exif-structure>:get_datum() @endlink get single exif parameter
52  *
53  */
54
55 static lua_State *L; /** The LUA object needed for all operations (NOTE: That is
56                        * a upper-case variable to match the documentation!) */
57
58 /* Taking that definition from lua 5.1 source */
59 #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 502
60 int luaL_typerror(lua_State *L, int narg, const char *tname)
61 {
62         const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, luaL_typename(L, narg));
63         return luaL_argerror(L, narg, msg);
64 }
65
66 # define LUA_register_meta(L, meta) luaL_setfuncs(L, meta, 0);
67 # define LUA_register_global(L, string, func) \
68         lua_newtable(L); \
69         luaL_setfuncs(L, func, 0); \
70         lua_pushvalue(L, -1); \
71         lua_setglobal(L, string)
72 #else
73 # define LUA_register_meta(L, meta) luaL_register(L, NULL, meta)
74 # define LUA_register_global(L, string, func) luaL_register(L, string, func)
75 #endif
76
77 static FileData *lua_check_image(lua_State *L, int index)
78 {
79         FileData **fd;
80         luaL_checktype(L, index, LUA_TUSERDATA);
81         fd = static_cast<FileData **>(luaL_checkudata(L, index, "Image"));
82         if (fd == nullptr) luaL_typerror(L, index, "Image");
83         return *fd;
84 }
85
86 /**
87  * @brief Get exif structure of selected image
88  * @param L
89  * @returns An #ExifData data structure containing the entire exif data
90  *
91  * To be used in conjunction with @link lua_exif_get_datum <exif-structure>:get_datum() @endlink
92  */
93 static int lua_image_get_exif(lua_State *L)
94 {
95         FileData *fd;
96         ExifData *exif;
97         ExifData **exif_data;
98
99         fd = lua_check_image(L, 1);
100         exif = exif_read_fd(fd);
101
102         exif_data = static_cast<ExifData **>(lua_newuserdata(L, sizeof(ExifData *)));
103         luaL_getmetatable(L, "Exif");
104         lua_setmetatable(L, -2);
105
106         *exif_data = exif;
107
108         return 1;
109 }
110
111 /**
112  * @brief Get full path of selected image
113  * @param L
114  * @returns char The full path of the file, including filename and extension
115  *
116  *
117  */
118 static int lua_image_get_path(lua_State *L)
119 {
120         FileData *fd;
121
122         fd = lua_check_image(L, 1);
123         lua_pushstring(L, fd->path);
124         return 1;
125 }
126
127 /**
128  * @brief Get full filename of selected image
129  * @param L
130  * @returns char The full filename including extension
131  *
132  *
133  */
134 static int lua_image_get_name(lua_State *L)
135 {
136         FileData *fd;
137
138         fd = lua_check_image(L, 1);
139         lua_pushstring(L, fd->name);
140         return 1;
141 }
142
143 /**
144  * @brief Get file extension of selected image
145  * @param L
146  * @returns char The file extension including preceding dot
147  *
148  *
149  */
150 static int lua_image_get_extension(lua_State *L)
151 {
152         FileData *fd;
153
154         fd = lua_check_image(L, 1);
155         lua_pushstring(L, fd->extension);
156         return 1;
157 }
158
159 /**
160  * @brief Get file date of selected image
161  * @param L
162  * @returns time_t The file date in Unix timestamp format.
163  *
164  * time_t - signed integer which represents the number of seconds since
165  * the start of the Unix epoch: midnight UTC of January 1, 1970
166  */
167 static int lua_image_get_date(lua_State *L)
168 {
169         FileData *fd;
170
171         fd = lua_check_image(L, 1);
172         lua_pushnumber(L, fd->date);
173         return 1;
174 }
175
176 /**
177  * @brief Get file size of selected image
178  * @param L
179  * @returns integer The file size in bytes
180  *
181  *
182  */
183 static int lua_image_get_size(lua_State *L)
184 {
185         FileData *fd;
186
187         fd = lua_check_image(L, 1);
188         lua_pushnumber(L, fd->size);
189         return 1;
190 }
191
192 /**
193  * @brief Get marks of selected image
194  * @param L
195  * @returns unsigned integer Bit map of marks set
196  *
197  * Bit 0 == Mark 1 etc.
198  *
199  *
200  */
201 static int lua_image_get_marks(lua_State *L)
202 {
203         FileData *fd;
204
205         fd = lua_check_image(L, 1);
206         lua_pushnumber(L, fd->marks);
207         return 1;
208 }
209
210 static ExifData *lua_check_exif(lua_State *L, int index)
211 {
212         ExifData **exif;
213         luaL_checktype(L, index, LUA_TUSERDATA);
214         exif = static_cast<ExifData **>(luaL_checkudata(L, index, "Exif"));
215         if (exif == nullptr) luaL_typerror(L, index, "Exif");
216         return *exif;
217 }
218
219 /**
220  * @brief Interface for EXIF data
221  * @param L
222  * @returns <i>return</i> A single exif tag extracted from a structure output by the @link lua_image_get_exif Image:get_exif() @endlink command
223  *
224  * e.g. \n
225  * exif_structure = Image:get_exif(); \n
226  * DateTimeDigitized = exif_structure:get_datum("Exif.Photo.DateTimeDigitized");
227  *
228  * Where <i>return</i> is: \n
229  * Exif.Photo.DateTimeOriginal = signed integer time_t \n
230  * Exif.Photo.DateTimeDigitized = signed integer time_t \n
231  * otherwise char
232  *
233  */
234 static int lua_exif_get_datum(lua_State *L)
235 {
236         const gchar *key;
237         gchar *value = nullptr;
238         ExifData *exif;
239         struct tm tm;
240         time_t datetime;
241
242         exif = lua_check_exif(L, 1);
243         key = luaL_checkstring(L, 2);
244         if (key == nullptr || key[0] == '\0')
245                 {
246                 lua_pushnil(L);
247                 return 1;
248                 }
249         if (!exif)
250                 {
251                 lua_pushnil(L);
252                 return 1;
253                 }
254         value = exif_get_data_as_text(exif, key);
255         if (strcmp(key, "Exif.Photo.DateTimeOriginal") == 0)
256                 {
257                 memset(&tm, 0, sizeof(tm));
258                 if (value && strptime(value, "%Y:%m:%d %H:%M:%S", &tm))
259                         {
260                         datetime = mktime(&tm);
261                         lua_pushnumber(L, datetime);
262                         return 1;
263                         }
264
265                 lua_pushnil(L);
266                 return 1;
267                 }
268
269         if (strcmp(key, "Exif.Photo.DateTimeDigitized") == 0)
270                 {
271                 memset(&tm, 0, sizeof(tm));
272                 if (value && strptime(value, "%Y:%m:%d %H:%M:%S", &tm))
273                         {
274                         datetime = mktime(&tm);
275                         lua_pushnumber(L, datetime);
276                         return 1;
277                         }
278
279                 lua_pushnil(L);
280                 return 1;
281                 }
282         lua_pushstring(L, value);
283         return 1;
284 }
285
286 /**
287  * @brief  <b>Image:</b> metatable and methods \n
288  * Call by e.g. \n
289  * path_name = @link lua_image_get_path Image:getpath() @endlink \n
290  * where the keyword <b>Image</b> represents the currently selected image
291  */
292 static const luaL_Reg image_methods[] = {
293                 {"get_path", lua_image_get_path},
294                 {"get_name", lua_image_get_name},
295                 {"get_extension", lua_image_get_extension},
296                 {"get_date", lua_image_get_date},
297                 {"get_size", lua_image_get_size},
298                 {"get_exif", lua_image_get_exif},
299                 {"get_marks", lua_image_get_marks},
300                 {nullptr, nullptr}
301 };
302
303 /**
304  * @brief  <b>exif:</b> table and methods \n
305  * Call by e.g. \n
306  * @link lua_exif_get_datum <exif-structure>:get_datum() @endlink \n
307  * where <exif-structure> is the output of @link lua_image_get_exif Image:get_exif() @endlink
308  *
309  * exif_structure = Image:get_exif(); \n
310  * DateTimeDigitized = exif_structure:get_datum("Exif.Photo.DateTimeDigitized");
311  */
312 static const luaL_Reg exif_methods[] = {
313                 {"get_datum", lua_exif_get_datum},
314                 {nullptr, nullptr}
315 };
316
317 /**
318  * @brief Initialize the lua interpreter.
319  */
320 void lua_init()
321 {
322         L = luaL_newstate();
323         luaL_openlibs(L); /* Open all libraries for lua programs */
324
325         /* Now create custom methodes to do something */
326         static const luaL_Reg meta_methods[] = {
327                         {nullptr, nullptr}
328         };
329
330         LUA_register_global(L, "Image", image_methods);
331         luaL_newmetatable(L, "Image");
332         LUA_register_meta(L, meta_methods);
333         lua_pushliteral(L, "__index");
334         lua_pushvalue(L, -3);
335         lua_settable(L, -3);
336         lua_pushliteral(L, "__metatable");
337         lua_pushvalue(L, -3);
338         lua_settable(L, -3);
339         lua_pop(L, 1);
340         lua_pop(L, 1);
341
342         LUA_register_global(L, "Exif", exif_methods);
343         luaL_newmetatable(L, "Exif");
344         LUA_register_meta(L, meta_methods);
345         lua_pushliteral(L, "__index");
346         lua_pushvalue(L, -3);
347         lua_settable(L, -3);
348         lua_pushliteral(L, "__metatable");
349         lua_pushvalue(L, -3);
350         lua_settable(L, -3);
351         lua_pop(L, 1);
352         lua_pop(L, 1);
353 }
354
355 /**
356  * @brief Call a lua function to get a single value.
357  */
358 gchar *lua_callvalue(FileData *fd, const gchar *file, const gchar *function)
359 {
360         std::unique_ptr<gchar, decltype(&g_free)> path{g_build_filename(get_rc_dir(), "lua", file, NULL), g_free};
361         if (access(path.get(), R_OK) == -1)
362                 {
363                 path.reset(g_build_filename(gq_bindir, file, NULL));
364                 if (access(path.get(), R_OK) == -1)
365                         {
366                         return g_strdup("");
367                         }
368                 }
369
370         /* Collection Table (Dummy at the moment) */
371         lua_newtable(L);
372         lua_setglobal(L, "Collection");
373
374         /* Current Image */
375         auto image_data = static_cast<FileData **>(lua_newuserdata(L, sizeof(FileData *)));
376         luaL_getmetatable(L, "Image");
377         lua_setmetatable(L, -2);
378         lua_setglobal(L, "Image");
379
380         *image_data = fd;
381
382         gint result;
383         if (file[0] == '\0')
384                 {
385                 result = luaL_dostring(L, function);
386                 }
387         else
388                 {
389                 result = luaL_dofile(L, path.get());
390                 }
391
392         if (result)
393                 {
394                 return g_strdup_printf("Error running lua script: %s", lua_tostring(L, -1));
395                 }
396
397         gchar *data = g_strdup(lua_tostring(L, -1));
398         GError *error = nullptr;
399         gchar *tmp = g_locale_to_utf8(data, strlen(data), nullptr, nullptr, &error);
400         if (error)
401                 {
402                 log_printf("Error converting lua output from locale to UTF-8: %s\n", error->message);
403                 g_error_free(error);
404                 }
405         else
406                 {
407                 std::swap(data, tmp);
408                 } // if (error) { ... } else
409         g_free(tmp);
410         return data;
411 }
412 #else
413 using dummy_variable = int;
414 #endif
415 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */