You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

545 lines
14 KiB

  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 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. #include <regex>
  21. #include <wx/debug.h>
  22. #include <wx/filename.h>
  23. #include <wx/stdpaths.h>
  24. #include <wx/utils.h>
  25. #include <build_version.h>
  26. #include <confirm.h>
  27. #include <dialogs/dialog_migrate_settings.h>
  28. #include <gestfich.h>
  29. #include <macros.h>
  30. #include <settings/app_settings.h>
  31. #include <settings/common_settings.h>
  32. #include <settings/settings_manager.h>
  33. #include <settings/color_settings.h>
  34. /**
  35. * Flag to enable settings tracing
  36. * @ingroup trace_env_vars
  37. */
  38. const char* traceSettings = "SETTINGS";
  39. SETTINGS_MANAGER::SETTINGS_MANAGER() :
  40. m_common_settings( nullptr ), m_migration_source()
  41. {
  42. // Check if the settings directory already exists, and if not, perform a migration if possible
  43. if( !MigrateIfNeeded() )
  44. {
  45. m_ok = false;
  46. return;
  47. }
  48. m_ok = true;
  49. // create the common settings shared by all applications. Not loaded immediately
  50. m_common_settings =
  51. static_cast<COMMON_SETTINGS*>( RegisterSettings( new COMMON_SETTINGS, false ) );
  52. // create the default color settings
  53. m_color_settings["default"] =
  54. static_cast<COLOR_SETTINGS*>( RegisterSettings( new COLOR_SETTINGS ) );
  55. }
  56. SETTINGS_MANAGER::~SETTINGS_MANAGER()
  57. {
  58. m_settings.clear();
  59. m_color_settings.clear();
  60. }
  61. JSON_SETTINGS* SETTINGS_MANAGER::RegisterSettings( JSON_SETTINGS* aSettings, bool aLoadNow )
  62. {
  63. std::unique_ptr<JSON_SETTINGS> ptr( aSettings );
  64. wxLogTrace( traceSettings, "Registered new settings object %s", ptr->GetFilename() );
  65. if( aLoadNow )
  66. ptr->LoadFromFile( GetPathForSettingsFile( ptr.get() ) );
  67. m_settings.push_back( std::move( ptr ) );
  68. return m_settings.back().get();
  69. }
  70. void SETTINGS_MANAGER::Load()
  71. {
  72. // TODO(JE) We should check for dirty settings here and write them if so, because
  73. // Load() could be called late in the application lifecycle
  74. for( auto&& settings : m_settings )
  75. settings->LoadFromFile( GetPathForSettingsFile( settings.get() ) );
  76. }
  77. void SETTINGS_MANAGER::Load( JSON_SETTINGS* aSettings )
  78. {
  79. auto it = std::find_if( m_settings.begin(), m_settings.end(),
  80. [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr ) {
  81. return aPtr.get() == aSettings;
  82. } );
  83. if( it != m_settings.end() )
  84. ( *it )->LoadFromFile( GetPathForSettingsFile( it->get() ) );
  85. }
  86. void SETTINGS_MANAGER::Save()
  87. {
  88. for( auto&& settings : m_settings )
  89. {
  90. // Never automatically save color settings, caller should use SaveColorSettings
  91. if( dynamic_cast<COLOR_SETTINGS*>( settings.get() ) )
  92. continue;
  93. settings->SaveToFile( GetPathForSettingsFile( settings.get() ) );
  94. }
  95. }
  96. void SETTINGS_MANAGER::Save( JSON_SETTINGS* aSettings )
  97. {
  98. auto it = std::find_if( m_settings.begin(), m_settings.end(),
  99. [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr ) {
  100. return aPtr.get() == aSettings;
  101. } );
  102. if( it != m_settings.end() )
  103. {
  104. wxLogTrace( traceSettings, "Saving %s", ( *it )->GetFilename() );
  105. ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
  106. }
  107. }
  108. void SETTINGS_MANAGER::FlushAndRelease( JSON_SETTINGS* aSettings )
  109. {
  110. auto it = std::find_if( m_settings.begin(), m_settings.end(),
  111. [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr ) {
  112. return aPtr.get() == aSettings;
  113. } );
  114. if( it != m_settings.end() )
  115. {
  116. wxLogTrace( traceSettings, "Flush and release %s", ( *it )->GetFilename() );
  117. ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
  118. m_settings.erase( it );
  119. }
  120. }
  121. COLOR_SETTINGS* SETTINGS_MANAGER::GetColorSettings( std::string aName )
  122. {
  123. COLOR_SETTINGS* ret = nullptr;
  124. try
  125. {
  126. ret = m_color_settings.at( aName );
  127. }
  128. catch( std::out_of_range& )
  129. {
  130. // This had better work
  131. ret = m_color_settings.at( "default" );
  132. }
  133. return ret;
  134. }
  135. void SETTINGS_MANAGER::SaveColorSettings( COLOR_SETTINGS* aSettings, const std::string& aNamespace )
  136. {
  137. // The passed settings should already be managed
  138. wxASSERT( std::find_if( m_color_settings.begin(), m_color_settings.end(),
  139. [aSettings] ( const auto& el ) {
  140. return el.second == aSettings;
  141. } ) != m_color_settings.end() );
  142. nlohmann::json::json_pointer ptr = JSON_SETTINGS::PointerFromString( aNamespace );
  143. aSettings->Store();
  144. wxASSERT( aSettings->contains( ptr ) );
  145. wxLogTrace( traceSettings, "Saving color scheme %s, preserving &s",
  146. aSettings->GetFilename(), aNamespace );
  147. nlohmann::json backup = aSettings->at( ptr );
  148. std::string path = GetColorSettingsPath();
  149. aSettings->LoadFromFile( path );
  150. ( *aSettings )[ptr].update( backup );
  151. aSettings->Load();
  152. aSettings->SaveToFile( path );
  153. }
  154. std::string SETTINGS_MANAGER::GetPathForSettingsFile( JSON_SETTINGS* aSettings )
  155. {
  156. wxASSERT( aSettings );
  157. switch( aSettings->GetLocation() )
  158. {
  159. case SETTINGS_LOC::USER:
  160. return GetUserSettingsPath();
  161. case SETTINGS_LOC::PROJECT:
  162. // TODO(JE)
  163. return "";
  164. case SETTINGS_LOC::COLORS:
  165. return GetColorSettingsPath();
  166. default:
  167. wxASSERT_MSG( false, "Unknown settings location!" );
  168. }
  169. return "";
  170. }
  171. class MIGRATION_TRAVERSER : public wxDirTraverser
  172. {
  173. private:
  174. wxString m_src;
  175. wxString m_dest;
  176. wxString m_errors;
  177. public:
  178. MIGRATION_TRAVERSER( const wxString& aSrcDir, const wxString& aDestDir ) :
  179. m_src( aSrcDir ), m_dest( aDestDir )
  180. {
  181. }
  182. wxString GetErrors() { return m_errors; }
  183. virtual wxDirTraverseResult OnFile( const wxString& aSrcFilePath ) override
  184. {
  185. wxFileName file( aSrcFilePath );
  186. wxString path = file.GetPath();
  187. path.Replace( m_src, m_dest, false );
  188. file.SetPath( path );
  189. wxLogTrace( traceSettings, "Copying %s to %s", aSrcFilePath, file.GetFullPath() );
  190. // For now, just copy everything
  191. CopyFile( aSrcFilePath, file.GetFullPath(), m_errors );
  192. return wxDIR_CONTINUE;
  193. }
  194. virtual wxDirTraverseResult OnDir( const wxString& dirPath ) override
  195. {
  196. wxFileName dir( dirPath );
  197. // Whitelist of directories to migrate
  198. if( dir.GetName() == "colors" ||
  199. dir.GetName() == "3d" )
  200. {
  201. wxString path = dir.GetPath();
  202. path.Replace( m_src, m_dest, false );
  203. dir.SetPath( path );
  204. wxMkdir( dir.GetFullPath() );
  205. return wxDIR_CONTINUE;
  206. }
  207. else
  208. {
  209. return wxDIR_IGNORE;
  210. }
  211. }
  212. };
  213. bool SETTINGS_MANAGER::MigrateIfNeeded()
  214. {
  215. wxFileName path( GetUserSettingsPath(), "" );
  216. wxLogTrace( traceSettings, "Using settings path %s", path.GetFullPath() );
  217. if( path.DirExists() )
  218. {
  219. wxFileName common = path;
  220. common.SetName( "kicad_common" );
  221. common.SetExt( "json" );
  222. if( common.Exists() )
  223. {
  224. wxLogTrace( traceSettings, "Path exists and has a kicad_common, continuing!" );
  225. return true;
  226. }
  227. }
  228. // Now we have an empty path, let's figure out what to put in it
  229. DIALOG_MIGRATE_SETTINGS dlg( this );
  230. if( dlg.ShowModal() != wxID_OK )
  231. {
  232. wxLogTrace( traceSettings, "Migration dialog canceled; exiting" );
  233. return false;
  234. }
  235. if( !path.DirExists() )
  236. {
  237. wxLogTrace( traceSettings, "Path didn't exist; creating it" );
  238. path.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
  239. }
  240. if( m_migration_source.IsEmpty() )
  241. {
  242. wxLogTrace( traceSettings, "No migration source given; starting with defaults" );
  243. return true;
  244. }
  245. MIGRATION_TRAVERSER traverser( m_migration_source, path.GetFullPath() );
  246. wxDir source_dir( m_migration_source );
  247. source_dir.Traverse( traverser );
  248. if( !traverser.GetErrors().empty() )
  249. DisplayErrorMessage( nullptr, traverser.GetErrors() );
  250. return true;
  251. }
  252. bool SETTINGS_MANAGER::GetPreviousVersionPaths( std::vector<wxString>* aPaths )
  253. {
  254. wxASSERT( aPaths );
  255. aPaths->clear();
  256. wxDir dir;
  257. std::vector<wxFileName> base_paths;
  258. base_paths.emplace_back( wxFileName( calculateUserSettingsPath( false ), "" ) );
  259. // If the env override is set, also check the default paths
  260. if( wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), nullptr ) )
  261. base_paths.emplace_back( wxFileName( calculateUserSettingsPath( false, false ), "" ) );
  262. wxString subdir;
  263. std::string mine = GetSettingsVersion();
  264. auto check_dir = [&] ( const wxString& aSubDir )
  265. {
  266. // Only older versions are valid for migration
  267. if( compareVersions( aSubDir.ToStdString(), mine ) <= 0 )
  268. {
  269. wxString sub_path = dir.GetNameWithSep() + aSubDir;
  270. if( IsSettingsPathValid( sub_path ) )
  271. {
  272. aPaths->push_back( sub_path );
  273. wxLogTrace( traceSettings, "GetPreviousVersionName: %s is valid", sub_path );
  274. }
  275. }
  276. };
  277. for( auto base_path : base_paths )
  278. {
  279. if( !dir.Open( base_path.GetFullPath() ) )
  280. {
  281. wxLogTrace( traceSettings, "GetPreviousVersionName: could not open base path %s",
  282. base_path.GetFullPath() );
  283. continue;
  284. }
  285. wxLogTrace( traceSettings, "GetPreviousVersionName: checking base path %s",
  286. base_path.GetFullPath() );
  287. if( dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS ) )
  288. {
  289. if( subdir != mine )
  290. check_dir( subdir );
  291. while( dir.GetNext( &subdir ) )
  292. {
  293. if( subdir != mine )
  294. check_dir( subdir );
  295. }
  296. }
  297. // If we didn't find one yet, check for legacy settings without a version directory
  298. if( IsSettingsPathValid( dir.GetNameWithSep() ) )
  299. {
  300. wxLogTrace( traceSettings,
  301. "GetPreviousVersionName: root path %s is valid", dir.GetName() );
  302. aPaths->push_back( dir.GetName() );
  303. }
  304. }
  305. return aPaths->size() > 0;
  306. }
  307. bool SETTINGS_MANAGER::IsSettingsPathValid( const wxString& aPath )
  308. {
  309. wxFileName test( aPath, "kicad_common" );
  310. return test.Exists();
  311. }
  312. std::string SETTINGS_MANAGER::GetColorSettingsPath()
  313. {
  314. wxFileName path;
  315. path.AssignDir( GetUserSettingsPath() );
  316. path.AppendDir( "colors" );
  317. return path.GetPath().ToStdString();
  318. }
  319. std::string SETTINGS_MANAGER::GetUserSettingsPath()
  320. {
  321. static std::string user_settings_path;
  322. if( user_settings_path.empty() )
  323. user_settings_path = calculateUserSettingsPath();
  324. return user_settings_path;
  325. }
  326. std::string SETTINGS_MANAGER::calculateUserSettingsPath( bool aIncludeVer, bool aUseEnv )
  327. {
  328. wxFileName cfgpath;
  329. // http://docs.wxwidgets.org/3.0/classwx_standard_paths.html#a7c7cf595d94d29147360d031647476b0
  330. cfgpath.AssignDir( wxStandardPaths::Get().GetUserConfigDir() );
  331. // GetUserConfigDir() does not default to ~/.config which is the current standard
  332. // configuration file location on Linux. This has been fixed in later versions of wxWidgets.
  333. #if !defined( __WXMSW__ ) && !defined( __WXMAC__ )
  334. wxArrayString dirs = cfgpath.GetDirs();
  335. if( dirs.Last() != ".config" )
  336. cfgpath.AppendDir( ".config" );
  337. #endif
  338. wxString envstr;
  339. // This shouldn't cause any issues on Windows or MacOS.
  340. if( wxGetEnv( wxT( "XDG_CONFIG_HOME" ), &envstr ) && !envstr.IsEmpty() )
  341. {
  342. // Override the assignment above with XDG_CONFIG_HOME
  343. cfgpath.AssignDir( envstr );
  344. }
  345. cfgpath.AppendDir( TO_STR( KICAD_CONFIG_DIR ) );
  346. // Use KICAD_CONFIG_HOME to allow the user to force a specific configuration path.
  347. if( aUseEnv && wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), &envstr ) && !envstr.IsEmpty() )
  348. {
  349. // Override the assignment above with KICAD_CONFIG_HOME
  350. cfgpath.AssignDir( envstr );
  351. }
  352. if( aIncludeVer )
  353. cfgpath.AppendDir( GetSettingsVersion() );
  354. return cfgpath.GetPath().ToStdString();
  355. }
  356. std::string SETTINGS_MANAGER::GetSettingsVersion()
  357. {
  358. wxString version = GetBuildVersion().BeforeLast( '.' );
  359. // A full build version looks like (x.y.z-nnn-g1234567) or x.y.z-xxx
  360. // We want to extract the x.y portion here
  361. if( version.StartsWith( '(' ) )
  362. version = version.Mid( 1 );
  363. return version.ToStdString();
  364. }
  365. int SETTINGS_MANAGER::compareVersions( const std::string& aFirst, const std::string& aSecond )
  366. {
  367. int a_maj = 0;
  368. int a_min = 0;
  369. int b_maj = 0;
  370. int b_min = 0;
  371. if( !extractVersion( aFirst, &a_maj, &a_min ) || !extractVersion( aSecond, &b_maj, &b_min ) )
  372. {
  373. wxLogTrace( traceSettings, "compareSettingsVersions: bad input (%s, %s)", aFirst, aSecond );
  374. return -1;
  375. }
  376. if( a_maj < b_maj )
  377. {
  378. return -1;
  379. }
  380. else if( a_maj > b_maj )
  381. {
  382. return 1;
  383. }
  384. else
  385. {
  386. if( a_min < b_min )
  387. {
  388. return -1;
  389. }
  390. else if( a_min > b_min )
  391. {
  392. return 1;
  393. }
  394. else
  395. {
  396. return 0;
  397. }
  398. }
  399. }
  400. bool SETTINGS_MANAGER::extractVersion( const std::string& aVersionString, int* aMajor, int* aMinor )
  401. {
  402. std::regex re_version( "(\\d+)\\.(\\d+)" );
  403. std::smatch match;
  404. if( std::regex_match( aVersionString, match, re_version ) )
  405. {
  406. try
  407. {
  408. *aMajor = std::stoi( match[1].str() );
  409. *aMinor = std::stoi( match[2].str() );
  410. }
  411. catch( ... )
  412. {
  413. return false;
  414. }
  415. return true;
  416. }
  417. return false;
  418. }