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
27namespace app {
28
29ResourceFinder::ResourceFinder(bool log)
30 : m_log(log)
31 , m_current(-1)
32{
33}
34
35const 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
41const 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
51bool ResourceFinder::next()
52{
53 ++m_current;
54 return (m_current < (int)m_paths.size());
55}
56
57bool 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
76void ResourceFinder::addPath(const std::string& path)
77{
78 m_paths.push_back(path);
79}
80
81void ResourceFinder::includeBinDir(const char* filename)
82{
83 addPath(base::join_path(base::get_file_path(base::get_app_path()), filename));
84}
85
86void 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
122void 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
156void 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
171void 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
214void 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
249std::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