1 | // Aseprite |
2 | // Copyright (C) 2019-2021 Igara Studio S.A. |
3 | // Copyright (C) 2001-2016 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "app/resource_finder.h" |
13 | |
14 | #include "app/app.h" |
15 | #include "base/fs.h" |
16 | #include "base/string.h" |
17 | #include "ver/info.h" |
18 | |
19 | #include <cstdio> |
20 | #include <cstdlib> |
21 | |
22 | #ifdef _WIN32 |
23 | #include <windows.h> |
24 | #include <shlobj.h> |
25 | #endif |
26 | |
27 | namespace app { |
28 | |
29 | ResourceFinder::ResourceFinder(bool log) |
30 | : m_log(log) |
31 | , m_current(-1) |
32 | { |
33 | } |
34 | |
35 | const std::string& ResourceFinder::filename() const |
36 | { |
37 | // Throw an exception if we are out of bounds |
38 | return m_paths.at(m_current); |
39 | } |
40 | |
41 | const std::string& ResourceFinder::defaultFilename() const |
42 | { |
43 | if (m_default.empty()) { |
44 | // The first path is the default one if nobody specified it. |
45 | if (!m_paths.empty()) |
46 | return m_paths[0]; |
47 | } |
48 | return m_default; |
49 | } |
50 | |
51 | bool ResourceFinder::next() |
52 | { |
53 | ++m_current; |
54 | return (m_current < (int)m_paths.size()); |
55 | } |
56 | |
57 | bool ResourceFinder::findFirst() |
58 | { |
59 | while (next()) { |
60 | if (m_log) |
61 | LOG("FIND: \"%s\"" , filename().c_str()); |
62 | |
63 | if (base::is_file(filename())) { |
64 | if (m_log) |
65 | LOG(" (found)\n" ); |
66 | |
67 | return true; |
68 | } |
69 | else if (m_log) |
70 | LOG(" (not found)\n" ); |
71 | } |
72 | |
73 | return false; |
74 | } |
75 | |
76 | void ResourceFinder::addPath(const std::string& path) |
77 | { |
78 | m_paths.push_back(path); |
79 | } |
80 | |
81 | void ResourceFinder::includeBinDir(const char* filename) |
82 | { |
83 | addPath(base::join_path(base::get_file_path(base::get_app_path()), filename)); |
84 | } |
85 | |
86 | void ResourceFinder::includeDataDir(const char* filename) |
87 | { |
88 | char buf[4096]; |
89 | |
90 | #ifdef _WIN32 |
91 | |
92 | sprintf(buf, "data/%s" , filename); |
93 | includeHomeDir(buf); // %AppData%/Aseprite/data/filename |
94 | includeBinDir(buf); // $BINDIR/data/filename |
95 | |
96 | #elif __APPLE__ |
97 | |
98 | sprintf(buf, "data/%s" , filename); |
99 | includeUserDir(buf); // $HOME/Library/Application Support/Aseprite/data/filename |
100 | includeBinDir(buf); // $BINDIR/data/filename (outside the bundle) |
101 | |
102 | sprintf(buf, "../Resources/data/%s" , filename); |
103 | includeBinDir(buf); // $BINDIR/../Resources/data/filename (inside a bundle) |
104 | |
105 | #else |
106 | |
107 | // $HOME/.config/aseprite/filename |
108 | sprintf(buf, "aseprite/data/%s" , filename); |
109 | includeHomeConfigDir(buf); |
110 | |
111 | // $BINDIR/data/filename |
112 | sprintf(buf, "data/%s" , filename); |
113 | includeBinDir(buf); |
114 | |
115 | // $BINDIR/../share/aseprite/data/filename (installed in /usr/ or /usr/local/) |
116 | sprintf(buf, "../share/aseprite/data/%s" , filename); |
117 | includeBinDir(buf); |
118 | |
119 | #endif |
120 | } |
121 | |
122 | void ResourceFinder::includeHomeDir(const char* filename) |
123 | { |
124 | #ifdef _WIN32 |
125 | |
126 | // %AppData%/Aseprite/filename |
127 | wchar_t* env = _wgetenv(L"AppData" ); |
128 | if (env) { |
129 | std::string path = base::join_path(base::to_utf8(env), "Aseprite" ); |
130 | path = base::join_path(path, filename); |
131 | addPath(path); |
132 | m_default = path; |
133 | } |
134 | |
135 | #else |
136 | |
137 | char* env = std::getenv("HOME" ); |
138 | char buf[4096]; |
139 | |
140 | if ((env) && (*env)) { |
141 | // $HOME/filename |
142 | sprintf(buf, "%s/%s" , env, filename); |
143 | addPath(buf); |
144 | } |
145 | else { |
146 | LOG("FIND: You don't have set $HOME variable\n" ); |
147 | addPath(filename); |
148 | } |
149 | |
150 | #endif |
151 | } |
152 | |
153 | #if !defined(_WIN32) && !defined(__APPLE__) |
154 | |
155 | // For Linux: It's $XDG_CONFIG_HOME or $HOME/.config |
156 | void ResourceFinder::includeHomeConfigDir(const char* filename) |
157 | { |
158 | char* configHome = std::getenv("XDG_CONFIG_HOME" ); |
159 | if (configHome && *configHome) { |
160 | // $XDG_CONFIG_HOME/filename |
161 | addPath(base::join_path(configHome, filename)); |
162 | } |
163 | else { |
164 | // $HOME/.config/filename |
165 | includeHomeDir(base::join_path(std::string(".config" ), filename).c_str()); |
166 | } |
167 | } |
168 | |
169 | #endif // !defined(_WIN32) && !defined(__APPLE__) |
170 | |
171 | void ResourceFinder::includeUserDir(const char* filename) |
172 | { |
173 | #ifdef _WIN32 |
174 | |
175 | // $ASEPRITE_USER_FOLDER/filename |
176 | if (const wchar_t* env = _wgetenv(L"ASEPRITE_USER_FOLDER" )) { |
177 | addPath(base::join_path(base::to_utf8(env), filename)); |
178 | } |
179 | else if (App::instance()->isPortable()) { |
180 | // $BINDIR/filename |
181 | includeBinDir(filename); |
182 | } |
183 | else { |
184 | // %AppData%/Aseprite/filename |
185 | includeHomeDir(filename); |
186 | } |
187 | |
188 | #else // Unix-like |
189 | |
190 | // $ASEPRITE_USER_FOLDER/filename |
191 | if (const char* env = std::getenv("ASEPRITE_USER_FOLDER" )) { |
192 | addPath(base::join_path(env, filename)); |
193 | } |
194 | else { |
195 | #ifdef __APPLE__ |
196 | |
197 | // $HOME/Library/Application Support/Aseprite/filename |
198 | addPath( |
199 | base::join_path( |
200 | base::join_path(base::get_lib_app_support_path(), get_app_name()), |
201 | filename).c_str()); |
202 | |
203 | #else // !__APPLE__ |
204 | |
205 | // $HOME/.config/aseprite/filename |
206 | includeHomeConfigDir((std::string("aseprite/" ) + filename).c_str()); |
207 | |
208 | #endif |
209 | } |
210 | |
211 | #endif // end Unix-like |
212 | } |
213 | |
214 | void ResourceFinder::includeDesktopDir(const char* filename) |
215 | { |
216 | #ifdef _WIN32 |
217 | |
218 | std::vector<wchar_t> buf(MAX_PATH); |
219 | HRESULT hr = SHGetFolderPath(NULL, CSIDL_DESKTOPDIRECTORY, NULL, |
220 | SHGFP_TYPE_DEFAULT, &buf[0]); |
221 | if (hr == S_OK) { |
222 | addPath(base::join_path(base::to_utf8(&buf[0]), filename)); |
223 | } |
224 | else { |
225 | includeHomeDir(filename); |
226 | } |
227 | |
228 | #elif defined(__APPLE__) |
229 | |
230 | // TODO get the desktop folder |
231 | // $HOME/Desktop/filename |
232 | includeHomeDir(base::join_path(std::string("Desktop" ), filename).c_str()); |
233 | |
234 | #else |
235 | |
236 | char* desktopDir = std::getenv("XDG_DESKTOP_DIR" ); |
237 | if (desktopDir) { |
238 | // $XDG_DESKTOP_DIR/filename |
239 | addPath(base::join_path(desktopDir, filename)); |
240 | } |
241 | else { |
242 | // $HOME/Desktop/filename |
243 | includeHomeDir(base::join_path(std::string("Desktop" ), filename).c_str()); |
244 | } |
245 | |
246 | #endif |
247 | } |
248 | |
249 | std::string ResourceFinder::getFirstOrCreateDefault() |
250 | { |
251 | std::string fn; |
252 | |
253 | // Search from first to last path |
254 | if (findFirst()) |
255 | fn = filename(); |
256 | |
257 | // If the file wasn't found, we will create the directories for the |
258 | // default file name. |
259 | if (fn.empty()) { |
260 | fn = defaultFilename(); |
261 | |
262 | std::string dir = base::get_file_path(fn); |
263 | if (!base::is_directory(dir)) |
264 | base::make_all_directories(dir); |
265 | } |
266 | |
267 | return fn; |
268 | } |
269 | |
270 | } // namespace app |
271 | |