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
17namespace app {
18namespace script {
19
20namespace {
21
22struct AppFS { };
23
24int 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
31template<std::string (*Func)(const std::string& path)>
32int 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
42int 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
61template<std::string (*Func)()>
62int AppFS_get_specialPath(lua_State* L)
63{
64 lua_pushstring(L, Func().c_str());
65 return 1;
66}
67
68int 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
77int 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
87int 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
97int 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
107int 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
121int 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
143int 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
164int 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
186const 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
197const 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
220DEF_MTNAME(AppFS);
221
222void 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