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