1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2020 Jon Evans <jon@craftyjon.com>
5  * Copyright (C) 2020-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation, either version 3 of the License, or (at your
10  * option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #ifndef _SETTINGS_MANAGER_H
22 #define _SETTINGS_MANAGER_H
23 
24 #include <algorithm>
25 #include <typeinfo>
26 #include <core/wx_stl_compat.h> // for wxString hash
27 #include <settings/color_settings.h>
28 
29 class COLOR_SETTINGS;
30 class COMMON_SETTINGS;
31 class KIWAY;
32 class PROJECT;
33 class PROJECT_FILE;
34 class REPORTER;
35 class wxSingleInstanceChecker;
36 
37 
38 /// Project settings path will be <projectname> + this
39 #define PROJECT_BACKUPS_DIR_SUFFIX wxT( "-backups" )
40 
41 
42 class SETTINGS_MANAGER
43 {
44 public:
45     SETTINGS_MANAGER( bool aHeadless = false );
46 
47     ~SETTINGS_MANAGER();
48 
49     /**
50      * @return true if settings load was successful
51      */
IsOK()52     bool IsOK() { return m_ok; }
53 
54     /**
55      * Associate this setting manager with the given Kiway.
56      *
57      * @param aKiway is the kiway this settings manager should use
58      */
SetKiway(KIWAY * aKiway)59     void SetKiway( KIWAY* aKiway ) { m_kiway = aKiway; }
60 
61     /**
62      * Takes ownership of the pointer passed in
63      * @param aSettings is a settings object to register
64      * @return a handle to the owned pointer
65      */
66     template<typename T>
67     T* RegisterSettings( T* aSettings, bool aLoadNow = true )
68     {
69         return static_cast<T*>( registerSettings( aSettings, aLoadNow ) );
70     }
71 
72     void Load();
73 
74     void Load( JSON_SETTINGS* aSettings );
75 
76     void Save();
77 
78     void Save( JSON_SETTINGS* aSettings );
79 
80     /**
81      * If the given settings object is registered, save it to disk and unregister it
82      * @param aSettings is the object to release
83      */
84     void FlushAndRelease( JSON_SETTINGS* aSettings, bool aSave = true );
85 
86     /**
87      * Returns a handle to the a given settings by type
88      * If the settings have already been loaded, returns the existing pointer.
89      * If the settings have not been loaded, creates a new object owned by the
90      * settings manager and returns a pointer to it.
91      *
92      * @tparam T is a type derived from APP_SETTINGS_BASE
93      * @param aLoadNow is true to load the registered file from disk immediately
94      * @return a pointer to a loaded settings object
95      */
96     template<typename T>
97     T* GetAppSettings( bool aLoadNow = true )
98     {
99         T*     ret      = nullptr;
100         size_t typeHash = typeid( T ).hash_code();
101 
102          if( m_app_settings_cache.count( typeHash ) )
103             ret = dynamic_cast<T*>( m_app_settings_cache.at( typeHash ) );
104 
105         if( ret )
106             return ret;
107 
108         auto it = std::find_if( m_settings.begin(), m_settings.end(),
109                                 []( const std::unique_ptr<JSON_SETTINGS>& aSettings )
110                                 {
111                                     return dynamic_cast<T*>( aSettings.get() );
112                                 } );
113 
114         if( it != m_settings.end() )
115         {
116             ret = dynamic_cast<T*>( it->get() );
117         }
118         else
119         {
120             try
121             {
122                 ret = static_cast<T*>( RegisterSettings( new T, aLoadNow ) );
123             }
catch(...)124             catch( ... )
125             {
126             }
127 
128         }
129 
130         m_app_settings_cache[typeHash] = ret;
131 
132         return ret;
133     }
134 
135     /**
136      * Retrieves a color settings object that applications can read colors from.
137      * If the given settings file cannot be found, returns the default settings.
138      *
139      * @param aName is the name of the color scheme to load
140      * @return a loaded COLOR_SETTINGS object
141      */
142     COLOR_SETTINGS* GetColorSettings( const wxString& aName = "user" );
143 
GetColorSettingsList()144     std::vector<COLOR_SETTINGS*> GetColorSettingsList()
145     {
146         std::vector<COLOR_SETTINGS*> ret;
147 
148         for( const std::pair<const wxString, COLOR_SETTINGS*>& entry : m_color_settings )
149             ret.push_back( entry.second );
150 
151         std::sort( ret.begin(), ret.end(), []( COLOR_SETTINGS* a, COLOR_SETTINGS* b )
152                                            {
153                                                return a->GetName() < b->GetName();
154                                            } );
155 
156         return ret;
157     }
158 
159     /**
160      * Safely saves a COLOR_SETTINGS to disk, preserving any changes outside the given namespace.
161      *
162      * A color settings namespace is one of the top-level JSON objects like "board", etc.
163      * This will perform a read-modify-write
164      *
165      * @param aSettings is a pointer to a valid COLOR_SETTINGS object managed by SETTINGS_MANAGER
166      * @param aNamespace is the namespace of settings to save
167      */
168     void SaveColorSettings( COLOR_SETTINGS* aSettings, const std::string& aNamespace = "" );
169 
170     /**
171      * Registers a new color settings object with the given filename
172      * @param aFilename is the location to store the new settings object
173      * @return a pointer to the new object
174      */
175     COLOR_SETTINGS* AddNewColorSettings( const wxString& aFilename );
176 
177     /**
178      * Returns a color theme for storing colors migrated from legacy (5.x and earlier) settings,
179      * creating the theme if necessary.  This theme will be called "user.json" / "User".
180      * @return the color settings to be used for migrating legacy settings
181      */
182     COLOR_SETTINGS* GetMigratedColorSettings();
183 
184     /**
185      * Retrieves the common settings shared by all applications
186      * @return a pointer to a loaded COMMON_SETTINGS
187      */
GetCommonSettings()188     COMMON_SETTINGS* GetCommonSettings() const { return m_common_settings; }
189 
190     /**
191      * Returns the path a given settings file should be loaded from / stored to.
192      * @param aSettings is the settings object
193      * @return a path based on aSettings->m_location
194      */
195     wxString GetPathForSettingsFile( JSON_SETTINGS* aSettings );
196 
197     /**
198      * Handles the initialization of the user settings directory and migration from previous
199      * KiCad versions as needed.
200      *
201      * This method will check for the existence of the user settings path for this KiCad version.
202      * If it exists, settings load will proceed normally using that path.
203      *
204      * If that directory is empty or does not exist, the migration wizard will be launched, which
205      * will give users the option to migrate settings from a previous KiCad version (if one is
206      * found), manually specify a directory to migrate fromm, or start with default settings.
207      *
208      * @return true if migration was successful or not necessary, false otherwise
209      */
210     bool MigrateIfNeeded();
211 
212     /**
213      * Helper for DIALOG_MIGRATE_SETTINGS to specify a source for migration
214      * @param aSource is a directory containing settings files to migrate from (can be empty)
215      */
SetMigrationSource(const wxString & aSource)216     void SetMigrationSource( const wxString& aSource ) { m_migration_source = aSource; }
217 
218     void SetMigrateLibraryTables( bool aMigrate = true ) { m_migrateLibraryTables = aMigrate; }
219 
220     /**
221      * Retrieves the name of the most recent previous KiCad version that can be found in the
222      * user settings directory.  For legacy versions (5.x, and 5.99 builds before this code was
223      * written), this will return "5.x"
224      *
225      * @param aName is filled with the name of the previous version, if one exists
226      * @return true if a previous version to migrate from exists
227      */
228     bool GetPreviousVersionPaths( std::vector<wxString>* aName = nullptr );
229 
230     /**
231      * Re-scans the color themes directory, reloading any changes it finds.
232      */
233     void ReloadColorSettings();
234 
235     /**
236      * Loads a project or sets up a new project with a specified path
237      * @param aFullPath is the full path to the project
238      * @param aSetActive if true will set the loaded project as the active project
239      * @return true if the PROJECT_FILE was successfully loaded from disk
240      */
241     bool LoadProject( const wxString& aFullPath, bool aSetActive = true );
242 
243     /**
244      * Saves, unloads and unregisters the given PROJECT
245      * @param aProject is the project object to unload
246      * @param aSave if true will save the project before unloading
247      * @return true if the PROJECT file was successfully saved
248      */
249     bool UnloadProject( PROJECT* aProject, bool aSave = true );
250 
251     /**
252      * Helper for checking if we have a project open
253      * TODO: This should be deprecated along with Prj() once we support multiple projects fully
254      * @return true if a call to Prj() will succeed
255      */
256     bool IsProjectOpen() const;
257 
258     /**
259      * A helper while we are not MDI-capable -- return the one and only project
260      * @return the loaded project
261      */
262     PROJECT& Prj() const;
263 
264     /**
265      * Retrieves a loaded project by name
266      * @param aFullPath is the full path including name and extension to the project file
267      * @return a pointer to the project if loaded, or nullptr
268      */
269     PROJECT* GetProject( const wxString& aFullPath ) const;
270 
271     /**
272      * @return a list of open projects
273      */
274     std::vector<wxString> GetOpenProjects() const;
275 
276     /**
277      * Saves a loaded project.
278      * @param aFullPath is the project name to save.  If empty, will save the first loaded project.
279      * @return true if save was successful
280      */
281     bool SaveProject( const wxString& aFullPath = wxEmptyString );
282 
283     /**
284      * Sets the currently loaded project path and saves it (pointers remain valid)
285      * Note that this will not modify the read-only state of the project, so it will have no effect
286      * if the project is marked as read-only!
287      * @param aFullPath is the full filename to set for the project
288      */
289     void SaveProjectAs( const wxString& aFullPath );
290 
291     /**
292      * Saves a copy of the current project under the given path.  Will save the copy even if the
293      * current project is marked as read-only.
294      */
295     void SaveProjectCopy( const wxString& aFullPath );
296 
297     /**
298      * @return the full path to where project backups should be stored
299      */
300     wxString GetProjectBackupsPath() const;
301 
302     /**
303      * Creates a backup archive of the current project
304      * @param aReporter is used for progress reporting
305      * @return true if everything succeeded
306      */
307     bool BackupProject( REPORTER& aReporter ) const;
308 
309     /**
310      * Calls BackupProject if a new backup is needed according to the current backup policy.
311      * @param aReporter is used for progress reporting
312      * @return if everything succeeded
313      */
314     bool TriggerBackupIfNeeded( REPORTER& aReporter ) const;
315 
316     /**
317      * Checks if a given path is probably a valid KiCad configuration directory.
318      * Actually it just checks if a file called "kicad_common" exists, because that's probably
319      * good enough for now.
320      *
321      * @param aPath is the path to check
322      * @return true if the path contains KiCad settings
323      */
324     static bool IsSettingsPathValid( const wxString& aPath );
325 
326     /**
327      * Returns the path where color scheme files are stored; creating it if missing
328      * (normally ./colors/ under the user settings path)
329      */
330     static wxString GetColorSettingsPath();
331 
332     /**
333      * Return the user configuration path used to store KiCad's configuration files.
334      *
335      * @see calculateUserSettingsPath
336      *
337      * NOTE: The path is cached at startup, it will never change during program lifetime!
338      *
339      * @return A string containing the config path for Kicad
340      */
341     static wxString GetUserSettingsPath();
342 
343     /**
344      * Parses the current KiCad build version and extracts the major and minor revision to use
345      * as the name of the settings directory for this KiCad version.
346      *
347      * @return a string such as "5.1"
348      */
349     static std::string GetSettingsVersion();
350 
351 private:
352 
353     JSON_SETTINGS* registerSettings( JSON_SETTINGS* aSettings, bool aLoadNow = true );
354 
355     /**
356      * Determines the base path for user settings files.
357      *
358      * The configuration path order of precedence is determined by the following criteria:
359      *
360      * - The value of the KICAD_CONFIG_HOME environment variable
361      * - The value of the XDG_CONFIG_HOME environment variable.
362      * - The result of the call to wxStandardPaths::GetUserConfigDir() with ".config" appended
363      *   as required on Linux builds.
364      *
365      * @param aIncludeVer will append the current KiCad version if true (default)
366      * @param aUseEnv will prefer the base path found in the KICAD_CONFIG_DIR if found (default)
367      * @return A string containing the config path for Kicad
368      */
369     static wxString calculateUserSettingsPath( bool aIncludeVer = true, bool aUseEnv = true );
370 
371     /**
372      * Compares two settings versions, like "5.99" and "6.0"
373      * @return -1 if aFirst is older than aSecond, 1 if aFirst is newer than aSecond, 0 otherwise
374      */
375     static int compareVersions( const std::string& aFirst, const std::string& aSecond );
376 
377     /**
378      * Extracts the numeric version from a given settings string
379      * @param aVersionString is the string to split at the "."
380      * @param aMajor will store the first part
381      * @param aMinor will store the second part
382      * @return true if extraction succeeded
383      */
384     static bool extractVersion( const std::string& aVersionString, int* aMajor, int* aMinor );
385 
386     /**
387      * Attempts to load a color theme by name (the color theme directory and .json ext are assumed)
388      * @param aName is the filename of the color theme (without the extension or path)
389      * @return the loaded settings, or nullptr if load failed
390      */
391     COLOR_SETTINGS* loadColorSettingsByName( const wxString& aName );
392 
393     COLOR_SETTINGS* registerColorSettings( const wxString& aFilename, bool aAbsolutePath = false );
394 
395     void loadAllColorSettings();
396 
397     /**
398      * Registers a PROJECT_FILE and attempts to load it from disk
399      * @param aProject is the project object to load the file for
400      * @return true if the PROJECT_FILE was successfully loaded
401      */
402     bool loadProjectFile( PROJECT& aProject );
403 
404     /**
405      * Optionally saves, and then unloads and unregisters the given PROJECT_FILE
406      * @param aProject is the project object to unload the file for
407      * @param aSave if true will save the project file before unloading
408      * @return true if the PROJECT file was successfully saved
409      */
410     bool unloadProjectFile( PROJECT* aProject, bool aSave );
411 
412 private:
413 
414     /// True if running outside a UI context
415     bool m_headless;
416 
417     /// The kiway this settings manager interacts with
418     KIWAY* m_kiway;
419 
420     std::vector<std::unique_ptr<JSON_SETTINGS>> m_settings;
421 
422     std::unordered_map<wxString, COLOR_SETTINGS*> m_color_settings;
423 
424     /// Cache for app settings
425     std::unordered_map<size_t, JSON_SETTINGS*> m_app_settings_cache;
426 
427     // Convenience shortcut
428     COMMON_SETTINGS* m_common_settings;
429 
430     wxString m_migration_source;
431 
432     /// If true, the symbol and footprint library tables will be migrated from the previous version
433     bool m_migrateLibraryTables;
434 
435     /// True if settings loaded successfully at construction
436     bool m_ok;
437 
438     /// Loaded projects (ownership here)
439     std::vector<std::unique_ptr<PROJECT>> m_projects_list;
440 
441     /// Loaded projects, mapped according to project full name
442     std::map<wxString, PROJECT*> m_projects;
443 
444     /// Loaded project files, mapped according to project full name
445     std::map<wxString, PROJECT_FILE*> m_project_files;
446 
447     /// Lock for loaded project (expand to multiple once we support MDI)
448     std::unique_ptr<wxSingleInstanceChecker> m_project_lock;
449 
450     static wxString backupDateTimeFormat;
451 };
452 
453 #endif
454