Ember
Loading...
Searching...
No Matches
ProjectManager.cpp
Go to the documentation of this file.
2#include "Utils/Logger.h"
3#include <algorithm>
4#include <fstream>
5#include <iomanip>
6#include <nlohmann/json.hpp>
7#include <sys/stat.h>
8#include <sys/types.h>
9#include <unistd.h>
10
11namespace EmberCore {
12
14 static ProjectManager instance;
15 return instance;
16}
17
19
20 // Use resources/projects directory relative to the executable
21 char exe_path[1024];
22 ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
23
24 if (len != -1) {
25 exe_path[len] = '\0';
26 String exe_dir = String(exe_path);
27 size_t last_slash = exe_dir.find_last_of('/');
28 if (last_slash != String::npos) {
29 exe_dir = exe_dir.substr(0, last_slash);
30 }
31 projects_directory_ = exe_dir + "/resources/projects";
32 config_file_ = exe_dir + "/resources/projects/project_manager.json";
33 } else {
34 // Fallback to relative path
35 projects_directory_ = "resources/projects";
36 config_file_ = "resources/projects/project_manager.json";
37 }
38
39 LOG_INFO("ProjectManager", "Projects directory: " + projects_directory_);
40 LOG_INFO("ProjectManager", "Config file: " + config_file_);
41
42 // Ensure directory exists and load config
44 LoadConfig();
45 }
46}
47
49 // Create parent directories if needed (resources, then projects)
50 String resources_dir = projects_directory_.substr(0, projects_directory_.find_last_of('/'));
51
52 struct stat st;
53 if (stat(resources_dir.c_str(), &st) != 0) {
54 if (mkdir(resources_dir.c_str(), 0755) != 0) {
55 LOG_ERROR("ProjectManager", "Failed to create resources directory: " + resources_dir);
56 return false;
57 }
58 }
59
60 // Create projects directory
61 if (stat(projects_directory_.c_str(), &st) != 0) {
62 if (mkdir(projects_directory_.c_str(), 0755) != 0) {
63 LOG_ERROR("ProjectManager", "Failed to create projects directory: " + projects_directory_);
64 return false;
65 }
66 LOG_INFO("ProjectManager", "Created projects directory: " + projects_directory_);
67 }
68
69 return true;
70}
71
72std::shared_ptr<BehaviorTreeProject> ProjectManager::CreateProject(const String &name, const String &description) {
73 std::lock_guard<std::mutex> lock(mutex_);
74
75 auto project = std::make_shared<BehaviorTreeProject>(name, description);
76
77 LOG_INFO("ProjectManager", "Created new project: " + name);
78 return project;
79}
80
81bool ProjectManager::OpenProject(const String &filepath) {
82 std::lock_guard<std::mutex> lock(mutex_);
83
84 // Check if file exists
85 std::ifstream file(filepath);
86 if (!file.good()) {
87 LOG_ERROR("ProjectManager", "Project file not found: " + filepath);
88 return false;
89 }
90 file.close();
91
92 // Create new project and load from file
93 auto project = std::make_shared<BehaviorTreeProject>();
94 if (!project->LoadFromFile(filepath)) {
95 LOG_ERROR("ProjectManager", "Failed to load project from: " + filepath);
96 return false;
97 }
98
99 // Close any existing project
100 if (active_project_) {
101 // Note: In a real implementation, you might want to prompt for save
102 active_project_ = nullptr;
103 }
104
105 active_project_ = project;
106 active_project_path_ = filepath;
107 project_modified_ = false;
108
109 // Add to recent projects
111 SaveConfig();
112
113 LOG_INFO("ProjectManager", "Opened project: " + project->GetName() + " from " + filepath);
114 return true;
115}
116
118 std::lock_guard<std::mutex> lock(mutex_);
119
120 if (!active_project_) {
121 LOG_ERROR("ProjectManager", "No active project to save");
122 return false;
123 }
124
125 if (active_project_path_.empty()) {
126 LOG_ERROR("ProjectManager", "No project file path set");
127 return false;
128 }
129
130 if (!active_project_->SaveToFile(active_project_path_)) {
131 LOG_ERROR("ProjectManager", "Failed to save project to: " + active_project_path_);
132 return false;
133 }
134
135 project_modified_ = false;
136 LOG_INFO("ProjectManager", "Saved project: " + active_project_->GetName());
137 return true;
138}
139
141 std::lock_guard<std::mutex> lock(mutex_);
142
143 if (!active_project_) {
144 LOG_ERROR("ProjectManager", "No active project to save");
145 return false;
146 }
147
148 if (!active_project_->SaveToFile(filepath)) {
149 LOG_ERROR("ProjectManager", "Failed to save project to: " + filepath);
150 return false;
151 }
152
153 active_project_path_ = filepath;
154 project_modified_ = false;
155
156 // Add to recent projects
158 SaveConfig();
159
160 LOG_INFO("ProjectManager", "Saved project as: " + filepath);
161 return true;
162}
163
165 std::lock_guard<std::mutex> lock(mutex_);
166
167 if (!active_project_) {
168 LOG_WARNING("ProjectManager", "No active project to close");
169 return false;
170 }
171
172 String name = active_project_->GetName();
173 active_project_ = nullptr;
174 active_project_path_.clear();
175 project_modified_ = false;
176
177 LOG_INFO("ProjectManager", "Closed project: " + name);
178 return true;
179}
180
181std::shared_ptr<BehaviorTreeProject> ProjectManager::GetActiveProject() const {
182 std::lock_guard<std::mutex> lock(mutex_);
183 return active_project_;
184}
185
187 std::lock_guard<std::mutex> lock(mutex_);
188 return active_project_ != nullptr;
189}
190
192 std::lock_guard<std::mutex> lock(mutex_);
194}
195
197 std::lock_guard<std::mutex> lock(mutex_);
198 return project_modified_;
199}
200
202 std::lock_guard<std::mutex> lock(mutex_);
203 project_modified_ = modified;
204}
205
206const std::vector<String> &ProjectManager::GetRecentProjects() const {
207 // Note: Returning const reference, caller should hold lock for thread safety
208 // In practice, this is typically called from UI thread which handles this
209 return recent_projects_;
210}
211
213 std::lock_guard<std::mutex> lock(mutex_);
215 SaveConfig();
216}
217
219 // Remove if already exists (will be re-added at front)
221
222 // Add to front
223 recent_projects_.insert(recent_projects_.begin(), filepath);
224
225 // Trim to max size
228 }
229}
230
232 std::lock_guard<std::mutex> lock(mutex_);
234 SaveConfig();
235}
236
238 auto it = std::find(recent_projects_.begin(), recent_projects_.end(), filepath);
239 if (it != recent_projects_.end()) {
240 recent_projects_.erase(it);
241 }
242}
243
245 std::lock_guard<std::mutex> lock(mutex_);
246 recent_projects_.clear();
247 SaveConfig();
248}
249
250bool ProjectManager::RecentProjectExists(const String &filepath) const {
251 std::ifstream file(filepath);
252 return file.good();
253}
254
256 std::lock_guard<std::mutex> lock(mutex_);
257
258 LOG_INFO("ProjectManager", "Resetting to defaults");
259
260 active_project_ = nullptr;
261 active_project_path_.clear();
262 project_modified_ = false;
263 recent_projects_.clear();
264
265 SaveConfig();
266}
267
269 try {
270 std::ifstream file(config_file_);
271 if (!file.is_open()) {
272 LOG_INFO("ProjectManager", "Config file not found, creating with defaults: " + config_file_);
273 return SaveConfig();
274 }
275
276 nlohmann::json json;
277 file >> json;
278 file.close();
279
280 // Load recent projects
281 if (json.contains("recent_projects")) {
282 recent_projects_ = json["recent_projects"].get<std::vector<String>>();
283
284 // Validate that recent projects still exist
285 std::vector<String> valid_projects;
286 for (const auto &project : recent_projects_) {
287 if (RecentProjectExists(project)) {
288 valid_projects.push_back(project);
289 } else {
290 LOG_WARNING("ProjectManager", "Recent project no longer exists: " + project);
291 }
292 }
293 recent_projects_ = valid_projects;
294 }
295
296 LOG_INFO("ProjectManager",
297 "Loaded config with " + std::to_string(recent_projects_.size()) + " recent project(s)");
298 return true;
299 } catch (const std::exception &e) {
300 LOG_ERROR("ProjectManager", "Failed to load config file: " + String(e.what()));
301 return false;
302 }
303}
304
306 try {
307 nlohmann::json json;
308
309 // Save recent projects
310 json["recent_projects"] = recent_projects_;
311
312 // Add version for future compatibility
313 json["version"] = "1.0";
314
315 std::ofstream file(config_file_);
316 if (!file.is_open()) {
317 LOG_ERROR("ProjectManager", "Failed to open config file for writing: " + config_file_);
318 return false;
319 }
320
321 file << std::setw(4) << json << std::endl;
322 file.close();
323
324 LOG_INFO("ProjectManager", "Saved config file: " + config_file_);
325 return true;
326 } catch (const std::exception &e) {
327 LOG_ERROR("ProjectManager", "Failed to save config file: " + String(e.what()));
328 return false;
329 }
330}
331
332} // namespace EmberCore
#define LOG_ERROR(category, message)
Definition Logger.h:116
#define LOG_WARNING(category, message)
Definition Logger.h:115
#define LOG_INFO(category, message)
Definition Logger.h:114
void AddToRecentProjectsInternal(const String &filepath)
void AddToRecentProjects(const String &filepath)
void RemoveFromRecentProjectsInternal(const String &filepath)
static const size_t MAX_RECENT_PROJECTS
bool RecentProjectExists(const String &filepath) const
bool SaveProjectAs(const String &filepath)
bool OpenProject(const String &filepath)
void SetProjectModified(bool modified)
static ProjectManager & GetInstance()
void RemoveFromRecentProjects(const String &filepath)
const std::vector< String > & GetRecentProjects() const
std::vector< String > recent_projects_
std::shared_ptr< BehaviorTreeProject > active_project_
ProjectManager(const ProjectManager &)=delete
String GetActiveProjectPath() const
std::shared_ptr< BehaviorTreeProject > GetActiveProject() const
std::shared_ptr< BehaviorTreeProject > CreateProject(const String &name, const String &description="")
Main types header for EmberCore.
std::string String
Framework-agnostic string type.
Definition String.h:14