KiCad PCB EDA Suite
Loading...
Searching...
No Matches
design_block_io.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2024 Mike Williams <[email protected]>
5 * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include <common.h>
26#include <i18n_utility.h>
27#include <wx/dir.h>
28#include <wx/ffile.h>
29#include <wx/filename.h>
30#include <wx/log.h>
31#include <wx/translation.h>
32#include <wx/string.h>
33#include <wx/arrstr.h>
34#include <wx/datetime.h>
36#include <kiway_player.h>
37#include <design_block_io.h>
38#include <design_block.h>
39#include <ki_exception.h>
40#include <trace_helpers.h>
41#include <fstream>
42
44{
45 switch( aFileType )
46 {
47 case KICAD_SEXP: return _( "KiCad" );
48 default: return wxString::Format( _( "UNKNOWN (%d)" ), aFileType );
49 }
50}
51
52
54DESIGN_BLOCK_IO_MGR::EnumFromStr( const wxString& aFileType )
55{
56 if( aFileType == _( "KiCad" ) )
58
60}
61
62
64{
65 switch( aFileType )
66 {
67 case KICAD_SEXP: return new DESIGN_BLOCK_IO();
68 default: return nullptr;
69 }
70}
71
72
74DESIGN_BLOCK_IO_MGR::GuessPluginTypeFromLibPath( const wxString& aLibPath, int aCtl )
75{
76 if( IO_RELEASER<DESIGN_BLOCK_IO>( FindPlugin( KICAD_SEXP ) )->CanReadLibrary( aLibPath ) && aCtl != KICTL_NONKICAD_ONLY )
77 return KICAD_SEXP;
78
80}
81
82
83bool DESIGN_BLOCK_IO_MGR::ConvertLibrary( std::map<std::string, UTF8>* aOldFileProps,
84 const wxString& aOldFilePath,
85 const wxString& aNewFilePath )
86{
89
90 if( oldFileType == DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE )
91 return false;
92
93
97 wxArrayString dbNames;
98 wxFileName newFileName( aNewFilePath );
99
100 if( newFileName.HasExt() )
101 {
102 wxString extraDir = newFileName.GetFullName();
103 newFileName.ClearExt();
104 newFileName.SetName( "" );
105 newFileName.AppendDir( extraDir );
106 }
107
108 if( !newFileName.DirExists() && !wxFileName::Mkdir( aNewFilePath, wxS_DIR_DEFAULT ) )
109 return false;
110
111 try
112 {
113 bool bestEfforts = false; // throw on first error
114 oldFilePI->DesignBlockEnumerate( dbNames, aOldFilePath, bestEfforts, aOldFileProps );
115
116 for( const wxString& dbName : dbNames )
117 {
118 std::unique_ptr<const DESIGN_BLOCK> db(
119 oldFilePI->GetEnumeratedDesignBlock( aOldFilePath, dbName, aOldFileProps ) );
120 kicadPI->DesignBlockSave( aNewFilePath, db.get() );
121 }
122 }
123 catch( ... )
124 {
125 return false;
126 }
127
128 return true;
129}
130
131
133{
134 return IO_BASE::IO_FILE_DESC( _HKI( "KiCad Design Block folders" ), {},
136}
137
138
139long long DESIGN_BLOCK_IO::GetLibraryTimestamp( const wxString& aLibraryPath ) const
140{
141 wxDir libDir( aLibraryPath );
142
143 if( !libDir.IsOpened() )
144 return 0;
145
146 long long ts = 0;
147
148 wxString filename;
149 bool hasMoreFiles = libDir.GetFirst( &filename, wxEmptyString, wxDIR_DIRS );
150
151 while( hasMoreFiles )
152 {
153 wxFileName blockDir( aLibraryPath, filename );
154
155 // Check if the directory ends with ".block", if so hash all the files in it
156 if( blockDir.GetFullName().EndsWith( FILEEXT::KiCadDesignBlockPathExtension ) )
157 ts += TimestampDir( blockDir.GetFullPath(), wxT( "*" ) );
158
159 hasMoreFiles = libDir.GetNext( &filename );
160 }
161
162 return ts;
163}
164
165
166void DESIGN_BLOCK_IO::CreateLibrary( const wxString& aLibraryPath,
167 const std::map<std::string, UTF8>* aProperties )
168{
169 if( wxDir::Exists( aLibraryPath ) )
170 {
171 THROW_IO_ERROR( wxString::Format( _( "Cannot overwrite library path '%s'." ),
172 aLibraryPath.GetData() ) );
173 }
174
175 wxFileName dir;
176 dir.SetPath( aLibraryPath );
177
178 if( !dir.Mkdir() )
179 {
181 wxString::Format( _( "Library path '%s' could not be created.\n\n"
182 "Make sure you have write permissions and try again." ),
183 dir.GetPath() ) );
184 }
185}
186
187
188bool DESIGN_BLOCK_IO::DeleteLibrary( const wxString& aLibraryPath,
189 const std::map<std::string, UTF8>* aProperties )
190{
191 wxFileName fn;
192 fn.SetPath( aLibraryPath );
193
194 // Return if there is no library path to delete.
195 if( !fn.DirExists() )
196 return false;
197
198 if( !fn.IsDirWritable() )
199 {
200 THROW_IO_ERROR( wxString::Format( _( "Insufficient permissions to delete folder '%s'." ),
201 aLibraryPath.GetData() ) );
202 }
203
204 wxDir dir( aLibraryPath );
205
206 // Design block folders should only contain sub-folders for each design block
207 if( dir.HasFiles() )
208 {
209 THROW_IO_ERROR( wxString::Format( _( "Library folder '%s' has unexpected files." ),
210 aLibraryPath.GetData() ) );
211 }
212
213 // Must delete all sub-directories before deleting the library directory
214 if( dir.HasSubDirs() )
215 {
216 wxArrayString dirs;
217
218 // Get all sub-directories in the library path
219 dir.GetAllFiles( aLibraryPath, &dirs, wxEmptyString, wxDIR_DIRS );
220
221 for( size_t i = 0; i < dirs.GetCount(); i++ )
222 {
223 wxFileName tmp = dirs[i];
224
226 {
227 THROW_IO_ERROR( wxString::Format( _( "Unexpected folder '%s' found in library "
228 "path '%s'." ),
229 dirs[i].GetData(), aLibraryPath.GetData() ) );
230 }
231 }
232
233 for( size_t i = 0; i < dirs.GetCount(); i++ )
234 wxRemoveFile( dirs[i] );
235 }
236
237 wxLogTrace( traceDesignBlocks, wxT( "Removing design block library '%s'." ),
238 aLibraryPath.GetData() );
239
240 // Some of the more elaborate wxRemoveFile() crap puts up its own wxLog dialog
241 // we don't want that. we want bare metal portability with no UI here.
242 if( !wxFileName::Rmdir( aLibraryPath, wxPATH_RMDIR_RECURSIVE ) )
243 {
244 THROW_IO_ERROR( wxString::Format( _( "Design block library '%s' cannot be deleted." ),
245 aLibraryPath.GetData() ) );
246 }
247
248 // For some reason removing a directory in Windows is not immediately updated. This delay
249 // prevents an error when attempting to immediately recreate the same directory when over
250 // writing an existing library.
251#ifdef __WINDOWS__
252 wxMilliSleep( 250L );
253#endif
254
255 return true;
256}
257
258
259void DESIGN_BLOCK_IO::DesignBlockEnumerate( wxArrayString& aDesignBlockNames,
260 const wxString& aLibraryPath, bool aBestEfforts,
261 const std::map<std::string, UTF8>* aProperties )
262{
263 // From the starting directory, look for all directories ending in the .block extension
264 wxDir dir( aLibraryPath );
265
266 if( !dir.IsOpened() )
267 return;
268
269 wxString dirname;
270 wxString fileSpec = wxT( "*." ) + wxString( FILEEXT::KiCadDesignBlockPathExtension );
271 bool cont = dir.GetFirst( &dirname, fileSpec, wxDIR_DIRS );
272
273 while( cont )
274 {
275 aDesignBlockNames.Add( dirname.Before( wxT( '.' ) ) );
276 cont = dir.GetNext( &dirname );
277 }
278}
279
280
281DESIGN_BLOCK* DESIGN_BLOCK_IO::DesignBlockLoad( const wxString& aLibraryPath,
282 const wxString& aDesignBlockName, bool aKeepUUID,
283 const std::map<std::string, UTF8>* aProperties )
284{
285 wxString dbPath = aLibraryPath + wxFileName::GetPathSeparator() +
286 aDesignBlockName + wxT( "." ) + FILEEXT::KiCadDesignBlockPathExtension + wxFileName::GetPathSeparator();
287 wxString dbSchPath = dbPath + aDesignBlockName + wxT( "." ) + FILEEXT::KiCadSchematicFileExtension;
288 wxString dbMetadataPath = dbPath + aDesignBlockName + wxT( "." ) + FILEEXT::JsonFileExtension;
289
290 if( !wxFileExists( dbSchPath ) )
291 return nullptr;
292
293 DESIGN_BLOCK* newDB = new DESIGN_BLOCK();
294
295 // Library name needs to be empty for when we fill it in with the correct library nickname
296 // one layer above
297 newDB->SetLibId( LIB_ID( wxEmptyString, aDesignBlockName ) );
298 newDB->SetSchematicFile( dbSchPath );
299
300 // Parse the JSON file if it exists
301 if( wxFileExists( dbMetadataPath ) )
302 {
303 try
304 {
305 nlohmann::ordered_json dbMetadata;
306 std::ifstream dbMetadataFile( dbMetadataPath.fn_str() );
307
308 dbMetadataFile >> dbMetadata;
309
310 if( dbMetadata.contains( "description" ) )
311 newDB->SetLibDescription( dbMetadata["description"] );
312
313 if( dbMetadata.contains( "keywords" ) )
314 newDB->SetKeywords( dbMetadata["keywords"] );
315
316 nlohmann::ordered_map<wxString, wxString> fields;
317
318 // Read the "fields" object from the JSON
319 if( dbMetadata.contains( "fields" ) )
320 {
321 for( auto& item : dbMetadata["fields"].items() )
322 {
323 wxString name = wxString::FromUTF8( item.key() );
324 wxString value = wxString::FromUTF8( item.value().get<std::string>() );
325
326 fields[name] = value;
327 }
328
329 newDB->SetFields( fields );
330 }
331 }
332 catch( ... )
333 {
334 THROW_IO_ERROR( wxString::Format(
335 _( "Design block metadata file '%s' could not be read." ), dbMetadataPath ) );
336 }
337 }
338
339
340 return newDB;
341}
342
343
344void DESIGN_BLOCK_IO::DesignBlockSave( const wxString& aLibraryPath,
345 const DESIGN_BLOCK* aDesignBlock,
346 const std::map<std::string, UTF8>* aProperties )
347{
348 // Make sure we have a valid LIB_ID or we can't save the design block
349 if( !aDesignBlock->GetLibId().IsValid() )
350 {
351 THROW_IO_ERROR( _( "Design block does not have a valid library ID." ) );
352 }
353
354 if( !wxFileExists( aDesignBlock->GetSchematicFile() ) )
355 {
356 THROW_IO_ERROR( wxString::Format( _( "Schematic source file '%s' does not exist." ),
357 aDesignBlock->GetSchematicFile() ) );
358 }
359
360 // Create the design block folder
361 wxFileName dbFolder( aLibraryPath + wxFileName::GetPathSeparator()
362 + aDesignBlock->GetLibId().GetLibItemName() + wxT( "." )
364 + wxFileName::GetPathSeparator() );
365
366 if( !dbFolder.DirExists() )
367 {
368 if( !dbFolder.Mkdir() )
369 {
370 THROW_IO_ERROR( wxString::Format( _( "Design block folder '%s' could not be created." ),
371 dbFolder.GetFullPath().GetData() ) );
372 }
373 }
374
375 // The new schematic file name is based on the design block name, not the source sheet name
376 wxString dbSchematicFile = dbFolder.GetFullPath() + aDesignBlock->GetLibId().GetLibItemName()
378
379 // If the source and destination files are the same, then we don't need to copy the file
380 // as we are just updating the metadata
381 if( aDesignBlock->GetSchematicFile() != dbSchematicFile )
382 {
383 // Copy the source sheet file to the design block folder, under the design block name
384 if( !wxCopyFile( aDesignBlock->GetSchematicFile(), dbSchematicFile ) )
385 {
386 THROW_IO_ERROR( wxString::Format(
387 _( "Schematic file '%s' could not be saved as design block at '%s'." ),
388 aDesignBlock->GetSchematicFile().GetData(), dbSchematicFile ) );
389 }
390 }
391
392
393 wxString dbMetadataFile = dbFolder.GetFullPath() + aDesignBlock->GetLibId().GetLibItemName()
394 + wxT( "." ) + FILEEXT::JsonFileExtension;
395
396 // Write the metadata file
397 nlohmann::ordered_json dbMetadata;
398 dbMetadata["description"] = aDesignBlock->GetLibDescription();
399 dbMetadata["keywords"] = aDesignBlock->GetKeywords();
400 dbMetadata["fields"] = aDesignBlock->GetFields();
401
402 bool success = false;
403
404 try
405 {
406 wxFFile mdFile( dbMetadataFile, wxT( "wb" ) );
407
408 if( mdFile.IsOpened() )
409 success = mdFile.Write( dbMetadata.dump( 0 ) );
410
411 // wxFFile dtor will close the file
412 }
413 catch( ... )
414 {
415 success = false;
416 }
417
418 if( !success )
419 {
420 THROW_IO_ERROR( wxString::Format(
421 _( "Design block metadata file '%s' could not be saved." ), dbMetadataFile ) );
422 }
423}
424
425
426void DESIGN_BLOCK_IO::DesignBlockDelete( const wxString& aLibPath, const wxString& aDesignBlockName,
427 const std::map<std::string, UTF8>* aProperties )
428{
429 wxFileName dbDir = wxFileName( aLibPath + wxFileName::GetPathSeparator() + aDesignBlockName
431
432 if( !dbDir.DirExists() )
433 {
435 wxString::Format( _( "Design block '%s' does not exist." ), dbDir.GetFullName() ) );
436 }
437
438 // Delete the whole design block folder
439 if( !wxFileName::Rmdir( dbDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE ) )
440 {
441 THROW_IO_ERROR( wxString::Format( _( "Design block folder '%s' could not be deleted." ),
442 dbDir.GetFullPath().GetData() ) );
443 }
444}
445
446
447bool DESIGN_BLOCK_IO::IsLibraryWritable( const wxString& aLibraryPath )
448{
449 wxFileName path( aLibraryPath );
450 return path.IsOk() && path.IsDirWritable();
451}
const char * name
Definition: DXF_plotter.cpp:57
@ KICAD_SEXP
S-expression KiCad file format.
@ DESIGN_BLOCK_FILE_UNKNOWN
0 is not a legal menu id on Mac
static const wxString ShowType(DESIGN_BLOCK_FILE_T aFileType)
static DESIGN_BLOCK_FILE_T GuessPluginTypeFromLibPath(const wxString &aLibPath, int aCtl=0)
static DESIGN_BLOCK_FILE_T EnumFromStr(const wxString &aFileType)
static bool ConvertLibrary(std::map< std::string, UTF8 > *aOldFileProps, const wxString &aOldFilePath, const wxString &aNewFilePath)
Convert a design block library to the latest KiCad format.
static DESIGN_BLOCK_IO * FindPlugin(DESIGN_BLOCK_FILE_T aFileType)
long long GetLibraryTimestamp(const wxString &aLibraryPath) const
void DesignBlockDelete(const wxString &aLibraryPath, const wxString &aDesignBlockName, const std::map< std::string, UTF8 > *aProperties=nullptr)
DESIGN_BLOCK * DesignBlockLoad(const wxString &aLibraryPath, const wxString &aDesignBlockName, bool aKeepUUID=false, const std::map< std::string, UTF8 > *aProperties=nullptr)
void DesignBlockSave(const wxString &aLibraryPath, const DESIGN_BLOCK *aDesignBlock, const std::map< std::string, UTF8 > *aProperties=nullptr)
void DesignBlockEnumerate(wxArrayString &aDesignBlockNames, const wxString &aLibraryPath, bool aBestEfforts, const std::map< std::string, UTF8 > *aProperties=nullptr)
virtual bool DeleteLibrary(const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Delete an existing library and returns true, or if library does not exist returns false,...
void CreateLibrary(const wxString &aLibraryPath, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Create a new empty library at aLibraryPath empty.
bool IsLibraryWritable(const wxString &aLibraryPath) override
Return true if the library at aLibraryPath is writable.
const IO_BASE::IO_FILE_DESC GetLibraryDesc() const override
Get the descriptor for the library container that this IO plugin operates on.
void SetLibDescription(const wxString &aDesc)
Definition: design_block.h:36
void SetKeywords(const wxString &aKeywords)
Definition: design_block.h:39
void SetSchematicFile(const wxString &aFile)
Definition: design_block.h:42
const wxString & GetKeywords() const
Definition: design_block.h:38
const wxString & GetLibDescription() const
Definition: design_block.h:35
void SetLibId(const LIB_ID &aName)
Definition: design_block.h:32
const wxString & GetSchematicFile() const
Definition: design_block.h:41
void SetFields(nlohmann::ordered_map< wxString, wxString > &aFields)
Definition: design_block.h:44
const LIB_ID & GetLibId() const
Definition: design_block.h:33
const nlohmann::ordered_map< wxString, wxString > & GetFields() const
Definition: design_block.h:48
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:49
bool IsValid() const
Check if this LID_ID is valid.
Definition: lib_id.h:172
const UTF8 & GetLibItemName() const
Definition: lib_id.h:102
long long TimestampDir(const wxString &aDirPath, const wxString &aFilespec)
A copy of ConvertFileTimeToWx() because wxWidgets left it as a static function private to src/common/...
Definition: common.cpp:597
The common library.
#define _HKI(x)
#define _(s)
static const std::string KiCadDesignBlockLibPathExtension
static const std::string KiCadDesignBlockPathExtension
static const std::string JsonFileExtension
static const std::string KiCadSchematicFileExtension
const wxChar *const traceDesignBlocks
Some functions to handle hotkeys in KiCad.
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition: io_mgr.h:33
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:39
#define KICTL_NONKICAD_ONLY
chosen file is non-KiCad according to user
Definition: kiway_player.h:74
Container that describes file type info.
Definition: io_base.h:43
wxLogTrace helper definitions.
Definition of file extensions used in Kicad.