1 | // Aseprite |
2 | // Copyright (C) 2019-2020 Igara Studio S.A. |
3 | // |
4 | // This program is distributed under the terms of |
5 | // the End-User License Agreement for Aseprite. |
6 | |
7 | #ifdef HAVE_CONFIG_H |
8 | #include "config.h" |
9 | #endif |
10 | |
11 | #include "app/app.h" |
12 | #include "app/resource_finder.h" |
13 | #include "app/script/luacpp.h" |
14 | #include "app/script/security.h" |
15 | #include "base/fs.h" |
16 | |
17 | namespace app { |
18 | namespace script { |
19 | |
20 | namespace { |
21 | |
22 | struct AppFS { }; |
23 | |
24 | int AppFS_pathSeparator(lua_State* L) |
25 | { |
26 | const char sep[2] = { base::path_separator, 0 }; |
27 | lua_pushstring(L, sep); |
28 | return 1; |
29 | } |
30 | |
31 | template<std::string (*Func)(const std::string& path)> |
32 | int AppFS_pathElement(lua_State* L) |
33 | { |
34 | const char* fn = lua_tostring(L, 1); |
35 | if (fn) |
36 | lua_pushstring(L, Func(fn).c_str()); |
37 | else |
38 | lua_pushnil(L); |
39 | return 1; |
40 | } |
41 | |
42 | int AppFS_joinPath(lua_State* L) |
43 | { |
44 | const char* fn = lua_tostring(L, 1); |
45 | if (!fn) { |
46 | lua_pushnil(L); |
47 | return 1; |
48 | } |
49 | |
50 | std::string result(fn); |
51 | const int n = lua_gettop(L); |
52 | for (int i=2; i<=n; ++i) { |
53 | const char* part = lua_tostring(L, i); |
54 | if (part) |
55 | result = base::join_path(result, part); |
56 | } |
57 | lua_pushstring(L, result.c_str()); |
58 | return 1; |
59 | } |
60 | |
61 | template<std::string (*Func)()> |
62 | int AppFS_get_specialPath(lua_State* L) |
63 | { |
64 | lua_pushstring(L, Func().c_str()); |
65 | return 1; |
66 | } |
67 | |
68 | int AppFS_get_userConfigPath(lua_State* L) |
69 | { |
70 | ResourceFinder rf(false); |
71 | rf.includeUserDir("" ); |
72 | std::string dir = rf.defaultFilename(); |
73 | lua_pushstring(L, dir.c_str()); |
74 | return 1; |
75 | } |
76 | |
77 | int AppFS_isFile(lua_State* L) |
78 | { |
79 | const char* fn = lua_tostring(L, 1); |
80 | if (fn) |
81 | lua_pushboolean(L, base::is_file(fn)); |
82 | else |
83 | lua_pushboolean(L, false); |
84 | return 1; |
85 | } |
86 | |
87 | int AppFS_isDirectory(lua_State* L) |
88 | { |
89 | const char* fn = lua_tostring(L, 1); |
90 | if (fn) |
91 | lua_pushboolean(L, base::is_directory(fn)); |
92 | else |
93 | lua_pushboolean(L, false); |
94 | return 1; |
95 | } |
96 | |
97 | int AppFS_fileSize(lua_State* L) |
98 | { |
99 | const char* fn = lua_tostring(L, 1); |
100 | if (fn) |
101 | lua_pushinteger(L, base::file_size(fn)); |
102 | else |
103 | lua_pushnil(L); |
104 | return 1; |
105 | } |
106 | |
107 | int AppFS_listFiles(lua_State* L) |
108 | { |
109 | const char* path = lua_tostring(L, 1); |
110 | lua_newtable(L); |
111 | if (path) { |
112 | int i = 0; |
113 | for (auto fn : base::list_files(path)) { |
114 | lua_pushstring(L, fn.c_str()); |
115 | lua_seti(L, -2, ++i); |
116 | } |
117 | } |
118 | return 1; |
119 | } |
120 | |
121 | int AppFS_makeDirectory(lua_State* L) |
122 | { |
123 | const char* path = luaL_checkstring(L, 1); |
124 | if (base::is_directory(path)) { |
125 | lua_pushboolean(L, true); |
126 | return 1; |
127 | } |
128 | |
129 | if (!ask_access(L, path, FileAccessMode::Full, ResourceType::File)) |
130 | return luaL_error(L, "the script doesn't have access to create the directory '%s'" , path); |
131 | |
132 | try { |
133 | // TODO don't throw exception from base::make_directory() function |
134 | base::make_directory(path); |
135 | } |
136 | catch (const std::exception&) { |
137 | // Do nothing |
138 | } |
139 | lua_pushboolean(L, base::is_directory(path)); |
140 | return 1; |
141 | } |
142 | |
143 | int AppFS_makeAllDirectories(lua_State* L) |
144 | { |
145 | const char* path = luaL_checkstring(L, 1); |
146 | if (base::is_directory(path)) { |
147 | lua_pushboolean(L, true); |
148 | return 1; |
149 | } |
150 | |
151 | if (!ask_access(L, path, FileAccessMode::Write, ResourceType::File)) |
152 | return luaL_error(L, "the script doesn't have access to create all directories '%s'" , path); |
153 | |
154 | try { |
155 | base::make_all_directories(path); |
156 | } |
157 | catch (const std::exception&) { |
158 | // Do nothing |
159 | } |
160 | lua_pushboolean(L, base::is_directory(path)); |
161 | return 1; |
162 | } |
163 | |
164 | int AppFS_removeDirectory(lua_State* L) |
165 | { |
166 | const char* path = luaL_checkstring(L, 1); |
167 | if (!base::is_directory(path)) { |
168 | lua_pushboolean(L, (base::is_file(path) ? false: // Cannot remove files |
169 | true)); // The directory is already removed |
170 | return 1; |
171 | } |
172 | |
173 | if (!ask_access(L, path, FileAccessMode::Write, ResourceType::File)) |
174 | return luaL_error(L, "the script doesn't have access to remove the directory '%s'" , path); |
175 | |
176 | try { |
177 | base::remove_directory(path); |
178 | } |
179 | catch (const std::exception&) { |
180 | // do nothing... |
181 | } |
182 | lua_pushboolean(L, !base::is_directory(path)); |
183 | return 1; |
184 | } |
185 | |
186 | const Property AppFS_properties[] = { |
187 | { "pathSeparator" , AppFS_pathSeparator, nullptr }, |
188 | // Special folder names |
189 | { "currentPath" , AppFS_get_specialPath<base::get_current_path>, nullptr }, |
190 | { "appPath" , AppFS_get_specialPath<base::get_app_path>, nullptr }, |
191 | { "tempPath" , AppFS_get_specialPath<base::get_temp_path>, nullptr }, |
192 | { "userDocsPath" , AppFS_get_specialPath<base::get_user_docs_folder>, nullptr }, |
193 | { "userConfigPath" , AppFS_get_userConfigPath, nullptr }, |
194 | { nullptr, nullptr, nullptr } |
195 | }; |
196 | |
197 | const luaL_Reg AppFS_methods[] = { |
198 | // Path manipulation |
199 | { "filePath" , AppFS_pathElement<base::get_file_path> }, |
200 | { "fileName" , AppFS_pathElement<base::get_file_name> }, |
201 | { "fileExtension" , AppFS_pathElement<base::get_file_extension> }, |
202 | { "fileTitle" , AppFS_pathElement<base::get_file_title> }, |
203 | { "filePathAndTitle" , AppFS_pathElement<base::get_file_title_with_path> }, |
204 | { "normalizePath" , AppFS_pathElement<base::normalize_path> }, |
205 | { "joinPath" , AppFS_joinPath }, |
206 | // File system information |
207 | { "isFile" , AppFS_isFile }, |
208 | { "isDirectory" , AppFS_isDirectory }, |
209 | { "fileSize" , AppFS_fileSize }, |
210 | { "listFiles" , AppFS_listFiles }, |
211 | // Manipulate directories |
212 | { "makeDirectory" , AppFS_makeDirectory }, |
213 | { "makeAllDirectories" , AppFS_makeAllDirectories }, |
214 | { "removeDirectory" , AppFS_removeDirectory }, |
215 | { nullptr, nullptr } |
216 | }; |
217 | |
218 | } // anonymous namespace |
219 | |
220 | DEF_MTNAME(AppFS); |
221 | |
222 | void register_app_fs_object(lua_State* L) |
223 | { |
224 | REG_CLASS(L, AppFS); |
225 | REG_CLASS_PROPERTIES(L, AppFS); |
226 | |
227 | lua_getglobal(L, "app" ); |
228 | lua_pushstring(L, "fs" ); |
229 | push_new<AppFS>(L); |
230 | lua_rawset(L, -3); |
231 | lua_pop(L, 1); |
232 | } |
233 | |
234 | } // namespace script |
235 | } // namespace app |
236 | |