Line data Source code
1 : #include <sys/stat.h>
2 :
3 : #include <cstdlib>
4 : #include <filesystem>
5 : #include <iostream>
6 :
7 : #include "preset_manager.h"
8 : #include "preset_manager_impl.h"
9 :
10 : #ifdef _WIN32
11 : #include <direct.h>
12 : #include <io.h>
13 : #include <windows.h>
14 : #define MKDIR(path) _mkdir(path)
15 : #elif defined(__APPLE__)
16 : #include <dirent.h>
17 : #include <mach-o/dyld.h>
18 : #define MKDIR(path) mkdir(path, 0755)
19 : #else
20 : #include <dirent.h>
21 : #define MKDIR(path) mkdir(path, 0755)
22 : #endif
23 :
24 : #ifdef _EMSCRIPTEN_
25 : #include <emscripten.h>
26 :
27 : namespace Amplitron {
28 : std::string PresetManager::get_user_presets_dir() {
29 : char* result =
30 : (char*)EM_ASM_PTR({ return stringToNewUTF8(window._amplitronPresetDir || 'preset'); });
31 : std::string dir(result);
32 : free(result);
33 : return dir;
34 : }
35 : } // namespace Amplitron
36 : #endif
37 :
38 : namespace Amplitron {
39 :
40 463 : void append_json_files(const std::string& dir, std::vector<std::string>& result) {
41 159 : try {
42 3549 : for (const auto& entry : std::filesystem::directory_iterator(dir)) {
43 2620 : if (!entry.is_regular_file()) continue;
44 2605 : const auto& path = entry.path();
45 5296 : if (path.extension() == ".json") {
46 3498 : result.push_back(path.string());
47 853 : }
48 453 : }
49 158 : } catch (...) {
50 : // Ignore invalid or non-existent directories
51 9 : }
52 466 : }
53 :
54 21 : std::string get_bundled_presets_dir() {
55 : #ifdef _WIN32
56 14 : auto has_json_presets = [](const std::string& dir) -> bool {
57 7 : std::vector<std::string> files;
58 7 : append_json_files(dir, files);
59 7 : return !files.empty();
60 7 : };
61 :
62 7 : char path[MAX_PATH];
63 7 : if (GetModuleFileNameA(nullptr, path, sizeof(path))) {
64 7 : std::filesystem::path exe_path(path);
65 7 : std::filesystem::path exe_dir = exe_path.parent_path();
66 :
67 : // Installed/bundled layout: presets next to the executable.
68 14 : std::string bundled = (exe_dir / "presets").string();
69 7 : if (dir_exists(bundled) && has_json_presets(bundled)) return bundled;
70 :
71 : // Dev/CMake layout: executable under build dir, presets at repo root.
72 0 : std::string repo_root_presets = (exe_dir / ".." / "presets").string();
73 0 : if (dir_exists(repo_root_presets) && has_json_presets(repo_root_presets))
74 0 : return repo_root_presets;
75 21 : }
76 :
77 : // Fallback: relative to current working directory (useful for local runs).
78 0 : if (dir_exists("presets") && has_json_presets("presets")) return "presets";
79 0 : return "presets";
80 : #elif defined(__APPLE__)
81 : char exe_path[4096];
82 7 : uint32_t size = sizeof(exe_path);
83 7 : if (_NSGetExecutablePath(exe_path, &size) == 0) {
84 7 : std::string exe_str = exe_path;
85 7 : size_t last_slash = exe_str.find_last_of("/");
86 7 : if (last_slash != std::string::npos) {
87 7 : std::string bundle_presets = exe_str.substr(0, last_slash) + "/../Resources/presets";
88 7 : if (dir_exists(bundle_presets)) {
89 0 : return bundle_presets;
90 : }
91 14 : }
92 7 : }
93 7 : return "presets";
94 : #else
95 14 : if (dir_exists("presets")) {
96 14 : return "presets";
97 : }
98 0 : return "/usr/share/amplitron/presets";
99 : #endif
100 7 : }
101 411 : void PresetManager::set_presets_dir(const std::string& dir) {
102 411 : if (dir.empty()) {
103 30 : custom_presets_dir_ = "";
104 40 : return;
105 : }
106 381 : std::string normalized = dir;
107 : #ifdef _WIN32
108 : // Allow callers/tests to pass forward slashes; normalize for _findfirst/_mkdir.
109 6509 : for (char& c : normalized) {
110 6382 : if (c == '/') c = '\\';
111 : }
112 : #endif
113 127 : try {
114 381 : std::filesystem::create_directories(normalized);
115 127 : } catch (...) {
116 0 : return;
117 0 : }
118 381 : if (dir_exists(normalized)) {
119 381 : custom_presets_dir_ = normalized;
120 381 : std::vector<std::string> dir_presets;
121 381 : append_json_files(normalized, dir_presets);
122 381 : if (dir_presets.empty()) {
123 21 : save_factory_presets(normalized);
124 7 : }
125 381 : }
126 391 : }
127 :
128 15 : void PresetManager::save_config() {
129 15 : std::string path = get_config_path();
130 15 : std::ofstream f(path);
131 15 : if (!f.is_open()) return;
132 9 : f << "{\n";
133 9 : f << " \"presets_dir\": \"";
134 325 : for (char c : custom_presets_dir_) {
135 316 : if (c == '\\')
136 7 : f << "\\\\";
137 309 : else if (c == '"')
138 0 : f << "\\\"";
139 : else
140 309 : f << c;
141 : }
142 9 : f << "\"\n}\n";
143 17 : }
144 :
145 45 : void PresetManager::load_config() {
146 45 : std::string path = get_config_path();
147 45 : std::ifstream f(path);
148 45 : if (!f.is_open()) return;
149 :
150 40 : std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
151 :
152 32 : const std::string key = "\"presets_dir\"";
153 32 : size_t key_pos = content.find(key);
154 32 : if (key_pos == std::string::npos) return;
155 :
156 29 : size_t colon = content.find(':', key_pos + key.size());
157 29 : if (colon == std::string::npos) return;
158 :
159 24 : size_t quote_open = content.find('"', colon + 1);
160 24 : if (quote_open == std::string::npos) return;
161 :
162 19 : std::string value;
163 19 : size_t i = quote_open + 1;
164 687 : while (i < content.size() && content[i] != '"') {
165 668 : if (content[i] == '\\' && i + 1 < content.size()) {
166 17 : ++i;
167 17 : if (content[i] == '\\')
168 9 : value += '\\';
169 8 : else if (content[i] == '"')
170 2 : value += '"';
171 6 : else if (content[i] == 'n')
172 2 : value += '\n';
173 : else {
174 4 : value += '\\';
175 4 : value += content[i];
176 : }
177 4 : } else {
178 651 : value += content[i];
179 : }
180 668 : ++i;
181 : }
182 :
183 19 : if (!value.empty() && dir_exists(value)) {
184 17 : custom_presets_dir_ = value;
185 4 : }
186 63 : }
187 :
188 453 : std::string PresetManager::get_presets_dir() {
189 453 : if (!custom_presets_dir_.empty()) {
190 440 : MKDIR(custom_presets_dir_.c_str());
191 440 : if (dir_exists(custom_presets_dir_)) {
192 591 : return custom_presets_dir_;
193 : }
194 0 : }
195 :
196 13 : std::string user_dir = get_user_presets_dir();
197 13 : if (!user_dir.empty()) {
198 2 : try {
199 12 : std::filesystem::create_directories(user_dir);
200 5 : } catch (...) {
201 : // Fall through to local fallback if creation fails
202 1 : }
203 10 : if (dir_exists(user_dir)) {
204 9 : return user_dir;
205 : }
206 0 : }
207 :
208 4 : std::string dir = "presets";
209 4 : MKDIR(dir.c_str());
210 4 : return dir;
211 162 : }
212 :
213 21 : void PresetManager::save_factory_presets(const std::string& dir) {
214 21 : std::string src_dir = get_bundled_presets_dir();
215 21 : if (!dir_exists(src_dir)) {
216 0 : std::cerr << "Bundled presets directory not found: " << src_dir << std::endl;
217 0 : return;
218 : }
219 :
220 21 : std::vector<std::string> preset_files;
221 21 : append_json_files(src_dir, preset_files);
222 :
223 150 : for (const auto& src_path : preset_files) {
224 129 : size_t last_slash = src_path.find_last_of("/\\");
225 43 : std::string filename =
226 129 : (last_slash != std::string::npos) ? src_path.substr(last_slash + 1) : src_path;
227 :
228 : #ifdef _WIN32
229 43 : std::string dest_path = dir + "\\" + filename;
230 : #else
231 86 : std::string dest_path = dir + "/" + filename;
232 : #endif
233 :
234 129 : std::ifstream src_file(src_path);
235 129 : if (!src_file.is_open()) {
236 0 : std::cerr << "Could not open source preset: " << src_path << std::endl;
237 0 : continue;
238 : }
239 :
240 129 : std::string content((std::istreambuf_iterator<char>(src_file)),
241 172 : std::istreambuf_iterator<char>());
242 129 : src_file.close();
243 :
244 129 : std::ofstream dest_file(dest_path);
245 129 : if (!dest_file.is_open()) {
246 14 : std::cerr << "Could not write preset: " << dest_path << std::endl;
247 14 : continue;
248 : }
249 :
250 115 : dest_file << content;
251 115 : dest_file.close();
252 157 : }
253 21 : }
254 :
255 : } // namespace Amplitron
|