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 The 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 <json_common.h>
27#include <i18n_utility.h>
28#include <kiplatform/io.h>
29#include <wx/dir.h>
30#include <wx/ffile.h>
31#include <wx/filename.h>
32#include <wx/log.h>
33#include <wx/translation.h>
34#include <wx/string.h>
35#include <wx/arrstr.h>
36#include <wx/datetime.h>
38#include <kiway_player.h>
39#include <design_block_io.h>
40#include <design_block.h>
41#include <ki_exception.h>
42#include <trace_helpers.h>
43#include <fstream>
46
48{
49 switch( aFileType )
50 {
51 case KICAD_SEXP: return _( "KiCad" );
53 default: return wxString::Format( _( "UNKNOWN (%d)" ), aFileType );
54 }
55}
56
57
59DESIGN_BLOCK_IO_MGR::EnumFromStr( const wxString& aFileType )
60{
61 if( aFileType == _( "KiCad" ) )
63 else if( aFileType == LIBRARY_TABLE_ROW::TABLE_TYPE_NAME )
65
67}
68
69
71{
72 switch( aFileType )
73 {
74 case KICAD_SEXP: return new DESIGN_BLOCK_IO();
75 default: return nullptr;
76 }
77}
78
79
81DESIGN_BLOCK_IO_MGR::GuessPluginTypeFromLibPath( const wxString& aLibPath, int aCtl )
82{
84
85 if( parser.Parse( aLibPath.ToStdString() ).has_value() )
86 return NESTED_TABLE;
87
88 if( IO_RELEASER<DESIGN_BLOCK_IO>( FindPlugin( KICAD_SEXP ) )->CanReadLibrary( aLibPath )
89 && aCtl != KICTL_NONKICAD_ONLY )
90 {
91 return KICAD_SEXP;
92 }
93
95}
96
97
98bool DESIGN_BLOCK_IO_MGR::ConvertLibrary( std::map<std::string, UTF8>* aOldFileProps,
99 const wxString& aOldFilePath,
100 const wxString& aNewFilePath )
101{
104
105 if( oldFileType == DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE )
106 return false;
107
108
111 wxArrayString dbNames;
112 wxFileName newFileName( aNewFilePath );
113
114 if( newFileName.HasExt() )
115 {
116 wxString extraDir = newFileName.GetFullName();
117 newFileName.ClearExt();
118 newFileName.SetName( "" );
119 newFileName.AppendDir( extraDir );
120 }
121
122 if( !newFileName.DirExists() && !wxFileName::Mkdir( aNewFilePath, wxS_DIR_DEFAULT ) )
123 return false;
124
125 try
126 {
127 bool bestEfforts = false; // throw on first error
128 oldFilePI->DesignBlockEnumerate( dbNames, aOldFilePath, bestEfforts, aOldFileProps );
129
130 for( const wxString& dbName : dbNames )
131 {
132 std::unique_ptr<const DESIGN_BLOCK> db( oldFilePI->GetEnumeratedDesignBlock( aOldFilePath, dbName,
133 aOldFileProps ) );
134 kicadPI->DesignBlockSave( aNewFilePath, db.get() );
135 }
136 }
137 catch( ... )
138 {
139 return false;
140 }
141
142 return true;
143}
144
145
147{
148 return IO_BASE::IO_FILE_DESC( _HKI( "KiCad Design Block folders" ), {},
150}
151
152
153long long DESIGN_BLOCK_IO::GetLibraryTimestamp( const wxString& aLibraryPath ) const
154{
155 wxDir libDir( aLibraryPath );
156
157 if( !libDir.IsOpened() )
158 return 0;
159
160 long long ts = 0;
161
162 wxString filename;
163 bool hasMoreFiles = libDir.GetFirst( &filename, wxEmptyString, wxDIR_DIRS );
164
165 while( hasMoreFiles )
166 {
167 wxFileName blockDir( aLibraryPath, filename );
168
169 // Check if the directory ends with ".kicad_block", if so hash all the files in it.
170 if( blockDir.GetFullName().EndsWith( FILEEXT::KiCadDesignBlockPathExtension ) )
171 ts += KIPLATFORM::IO::TimestampDir( blockDir.GetFullPath(), wxT( "*" ) );
172
173 hasMoreFiles = libDir.GetNext( &filename );
174 }
175
176 return ts;
177}
178
179
180void DESIGN_BLOCK_IO::CreateLibrary( const wxString& aLibraryPath,
181 const std::map<std::string, UTF8>* aProperties )
182{
183 if( wxDir::Exists( aLibraryPath ) )
184 {
185 THROW_IO_ERROR( wxString::Format( _( "Cannot overwrite library path '%s'." ),
186 aLibraryPath.GetData() ) );
187 }
188
189 wxFileName dir;
190 dir.SetPath( aLibraryPath );
191
192 if( !dir.Mkdir() )
193 {
195 wxString::Format( _( "Library path '%s' could not be created.\n\n"
196 "Make sure you have write permissions and try again." ),
197 dir.GetPath() ) );
198 }
199}
200
201
202bool DESIGN_BLOCK_IO::DeleteLibrary( const wxString& aLibraryPath,
203 const std::map<std::string, UTF8>* aProperties )
204{
205 wxFileName fn;
206 fn.SetPath( aLibraryPath );
207
208 // Return if there is no library path to delete.
209 if( !fn.DirExists() )
210 return false;
211
212 if( !fn.IsDirWritable() )
213 {
214 THROW_IO_ERROR( wxString::Format( _( "Insufficient permissions to delete folder '%s'." ),
215 aLibraryPath.GetData() ) );
216 }
217
218 wxDir dir( aLibraryPath );
219
220 // Design block folders should only contain sub-folders for each design block
221 if( dir.HasFiles() )
222 {
223 THROW_IO_ERROR( wxString::Format( _( "Library folder '%s' has unexpected files." ),
224 aLibraryPath.GetData() ) );
225 }
226
227 // Must delete all sub-directories before deleting the library directory
228 if( dir.HasSubDirs() )
229 {
230 wxArrayString dirs;
231
232 // Get all sub-directories in the library path
233 dir.GetAllFiles( aLibraryPath, &dirs, wxEmptyString, wxDIR_DIRS );
234
235 for( size_t i = 0; i < dirs.GetCount(); i++ )
236 {
237 wxFileName tmp = dirs[i];
238
240 {
241 THROW_IO_ERROR( wxString::Format( _( "Unexpected folder '%s' found in library path '%s'." ),
242 dirs[i].GetData(), aLibraryPath.GetData() ) );
243 }
244 }
245
246 for( size_t i = 0; i < dirs.GetCount(); i++ )
247 wxRemoveFile( dirs[i] );
248 }
249
250 wxLogTrace( traceDesignBlocks, wxT( "Removing design block library '%s'." ),
251 aLibraryPath.GetData() );
252
253 // Some of the more elaborate wxRemoveFile() crap puts up its own wxLog dialog
254 // we don't want that. we want bare metal portability with no UI here.
255 if( !wxFileName::Rmdir( aLibraryPath, wxPATH_RMDIR_RECURSIVE ) )
256 {
257 THROW_IO_ERROR( wxString::Format( _( "Design block library '%s' cannot be deleted." ),
258 aLibraryPath.GetData() ) );
259 }
260
261 // For some reason removing a directory in Windows is not immediately updated. This delay
262 // prevents an error when attempting to immediately recreate the same directory when over
263 // writing an existing library.
264#ifdef __WINDOWS__
265 wxMilliSleep( 250L );
266#endif
267
268 return true;
269}
270
271
272void DESIGN_BLOCK_IO::DesignBlockEnumerate( wxArrayString& aDesignBlockNames,
273 const wxString& aLibraryPath, bool aBestEfforts,
274 const std::map<std::string, UTF8>* aProperties )
275{
276 // From the starting directory, look for all directories ending in the .kicad_block extension
277 wxDir dir( aLibraryPath );
278
279 if( !dir.IsOpened() )
280 {
281 THROW_IO_ERROR( wxString::Format( _( "Design block '%s' does not exist." ), aLibraryPath ) );
282 }
283
284 wxString dirname;
285 wxString fileSpec = wxT( "*." ) + wxString( FILEEXT::KiCadDesignBlockPathExtension );
286 bool cont = dir.GetFirst( &dirname, fileSpec, wxDIR_DIRS );
287
288 while( cont )
289 {
290 aDesignBlockNames.Add( dirname.Before( wxT( '.' ) ) );
291 cont = dir.GetNext( &dirname );
292 }
293}
294
295
296DESIGN_BLOCK* DESIGN_BLOCK_IO::DesignBlockLoad( const wxString& aLibraryPath,
297 const wxString& aDesignBlockName, bool aKeepUUID,
298 const std::map<std::string, UTF8>* aProperties )
299{
300 wxString dbPath = aLibraryPath + wxFileName::GetPathSeparator() + aDesignBlockName + wxT( "." )
301 + FILEEXT::KiCadDesignBlockPathExtension + wxFileName::GetPathSeparator();
302 wxString dbSchPath = dbPath + aDesignBlockName + wxT( "." )
304 wxString dbPcbPath = dbPath + aDesignBlockName + wxT( "." ) + FILEEXT::KiCadPcbFileExtension;
305 wxString dbMetadataPath = dbPath + aDesignBlockName + wxT( "." ) + FILEEXT::JsonFileExtension;
306
307 if( !wxDir::Exists( dbPath ) )
308 THROW_IO_ERROR( wxString::Format( _( "Design block '%s' does not exist." ), dbPath ) );
309
310 DESIGN_BLOCK* newDB = new DESIGN_BLOCK();
311
312 // Library name needs to be empty for when we fill it in with the correct library nickname
313 // one layer above
314 newDB->SetLibId( LIB_ID( wxEmptyString, aDesignBlockName ) );
315
316 if( wxFileExists( dbSchPath ) )
317 newDB->SetSchematicFile( dbSchPath );
318
319 if( wxFileExists( dbPcbPath ) )
320 newDB->SetBoardFile( dbPcbPath );
321
322 // Parse the JSON file if it exists
323 if( wxFileExists( dbMetadataPath ) )
324 {
325 try
326 {
327 nlohmann::ordered_json dbMetadata;
328 std::ifstream dbMetadataFile( dbMetadataPath.fn_str() );
329
330 dbMetadataFile >> dbMetadata;
331
332 if( dbMetadata.contains( "description" ) )
333 newDB->SetLibDescription( dbMetadata["description"].get<std::string>() );
334
335 if( dbMetadata.contains( "keywords" ) )
336 newDB->SetKeywords( dbMetadata["keywords"].get<std::string>() );
337
338 // Read the "fields" object from the JSON
339 if( dbMetadata.contains( "fields" ) )
340 {
341 for( auto& item : dbMetadata["fields"].items() )
342 {
343 wxString name = wxString::FromUTF8( item.key() );
344 wxString value = wxString::FromUTF8( item.value().get<std::string>() );
345
346 newDB->GetFields()[name] = value;
347 }
348 }
349 }
350 catch( ... )
351 {
352 delete newDB;
353 THROW_IO_ERROR( wxString::Format( _( "Design block metadata file '%s' could not be read." ),
354 dbMetadataPath ) );
355 }
356 }
357
358 return newDB;
359}
360
361
362bool DESIGN_BLOCK_IO::DesignBlockExists( const wxString& aLibraryPath,
363 const wxString& aDesignBlockName,
364 const std::map<std::string, UTF8>* aProperties )
365{
366 wxString dbPath = aLibraryPath + wxFileName::GetPathSeparator() + aDesignBlockName + wxT( "." )
367 + FILEEXT::KiCadDesignBlockPathExtension + wxFileName::GetPathSeparator();
368
369 return wxDir::Exists( dbPath );
370}
371
372
373void DESIGN_BLOCK_IO::DesignBlockSave( const wxString& aLibraryPath,
374 const DESIGN_BLOCK* aDesignBlock,
375 const std::map<std::string, UTF8>* aProperties )
376{
377 // Make sure we have a valid LIB_ID or we can't save the design block
378 if( !aDesignBlock->GetLibId().IsValid() )
379 {
380 THROW_IO_ERROR( _( "Design block does not have a valid library ID." ) );
381 }
382
383 if( aDesignBlock->GetSchematicFile().IsEmpty() && aDesignBlock->GetBoardFile().IsEmpty() )
384 {
385 THROW_IO_ERROR( _( "Design block does not have a schematic or board file." ) );
386 }
387
388 wxFileName schematicFile( aDesignBlock->GetSchematicFile() );
389 wxFileName boardFile( aDesignBlock->GetBoardFile() );
390
391 if( !aDesignBlock->GetSchematicFile().IsEmpty() && !schematicFile.FileExists() )
392 {
394 wxString::Format( _( "Schematic source file '%s' does not exist." ), schematicFile.GetFullPath() ) );
395 }
396
397 if( !aDesignBlock->GetBoardFile().IsEmpty() && !boardFile.FileExists() )
398 {
399 THROW_IO_ERROR( wxString::Format( _( "Board source file '%s' does not exist." ), boardFile.GetFullPath() ) );
400 }
401
402 // Create the design block folder
403 wxFileName dbFolder( aLibraryPath + wxFileName::GetPathSeparator()
404 + aDesignBlock->GetLibId().GetLibItemName() + wxT( "." )
406 + wxFileName::GetPathSeparator() );
407
408 if( !dbFolder.DirExists() )
409 {
410 if( !dbFolder.Mkdir() )
411 {
412 THROW_IO_ERROR( wxString::Format( _( "Design block folder '%s' could not be created." ),
413 dbFolder.GetFullPath().GetData() ) );
414 }
415 }
416
417 if( !aDesignBlock->GetSchematicFile().IsEmpty() )
418 {
419 // The new schematic file name is based on the design block name, not the source sheet name
420 wxString dbSchematicFile = dbFolder.GetFullPath() + aDesignBlock->GetLibId().GetLibItemName() + wxT( "." )
422
423 // If the source and destination files are the same, then we don't need to copy the file
424 // as we are just updating the metadata
425 if( schematicFile.GetFullPath() != dbSchematicFile )
426 {
427 // Copy the source sheet file to the design block folder, under the design block name
428 if( !wxCopyFile( schematicFile.GetFullPath(), dbSchematicFile ) )
429 {
431 wxString::Format( _( "Schematic file '%s' could not be saved as design block at '%s'." ),
432 schematicFile.GetFullPath(), dbSchematicFile ) );
433 }
434 }
435 }
436
437 if( !aDesignBlock->GetBoardFile().IsEmpty() )
438 {
439 // The new Board file name is based on the design block name, not the source sheet name
440 wxString dbBoardFile = dbFolder.GetFullPath() + aDesignBlock->GetLibId().GetLibItemName() + wxT( "." )
442
443 // If the source and destination files are the same, then we don't need to copy the file
444 // as we are just updating the metadata
445 if( boardFile.GetFullPath() != dbBoardFile )
446 {
447 // Copy the source sheet file to the design block folder, under the design block name
448 if( !wxCopyFile( boardFile.GetFullPath(), dbBoardFile ) )
449 {
450 THROW_IO_ERROR( wxString::Format( _( "Board file '%s' could not be saved as design block at '%s'." ),
451 boardFile.GetFullPath(), dbBoardFile ) );
452 }
453 }
454 }
455
456 wxString dbMetadataFile = dbFolder.GetFullPath() + aDesignBlock->GetLibId().GetLibItemName()
457 + wxT( "." ) + FILEEXT::JsonFileExtension;
458
459 // Write the metadata file
460 nlohmann::ordered_json dbMetadata;
461 dbMetadata["description"] = aDesignBlock->GetLibDescription();
462 dbMetadata["keywords"] = aDesignBlock->GetKeywords();
463 dbMetadata["fields"] = aDesignBlock->GetFields();
464
465 bool success = false;
466
467 try
468 {
469 wxFFile mdFile( dbMetadataFile, wxT( "wb" ) );
470
471 if( mdFile.IsOpened() )
472 success = mdFile.Write( dbMetadata.dump( 0 ) );
473
474 // wxFFile dtor will close the file
475 }
476 catch( ... )
477 {
478 success = false;
479 }
480
481 if( !success )
482 {
483 THROW_IO_ERROR( wxString::Format(
484 _( "Design block metadata file '%s' could not be saved." ), dbMetadataFile ) );
485 }
486}
487
488
489void DESIGN_BLOCK_IO::DesignBlockDelete( const wxString& aLibPath, const wxString& aDesignBlockName,
490 const std::map<std::string, UTF8>* aProperties )
491{
492 wxFileName dbDir = wxFileName( aLibPath + wxFileName::GetPathSeparator() + aDesignBlockName
494
495 if( !dbDir.DirExists() )
496 {
498 wxString::Format( _( "Design block '%s' does not exist." ), dbDir.GetFullName() ) );
499 }
500
501 // Delete the whole design block folder
502 if( !wxFileName::Rmdir( dbDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE ) )
503 {
504 THROW_IO_ERROR( wxString::Format( _( "Design block folder '%s' could not be deleted." ),
505 dbDir.GetFullPath().GetData() ) );
506 }
507}
508
509
510bool DESIGN_BLOCK_IO::IsLibraryWritable( const wxString& aLibraryPath )
511{
512 wxFileName path( aLibraryPath );
513 return path.IsOk() && path.IsDirWritable();
514}
const char * name
@ 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)
bool DesignBlockExists(const wxString &aLibraryPath, const wxString &aDesignBlockName, const std::map< std::string, UTF8 > *aProperties=nullptr)
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)
void SetKeywords(const wxString &aKeywords)
void SetSchematicFile(const wxString &aFile)
void SetBoardFile(const wxString &aFile)
const wxString & GetKeywords() const
const wxString & GetLibDescription() const
void SetLibId(const LIB_ID &aName)
const wxString & GetBoardFile() const
const wxString & GetSchematicFile() const
const LIB_ID & GetLibId() const
const nlohmann::ordered_map< wxString, wxString > & GetFields() const
tl::expected< LIBRARY_TABLE_IR, LIBRARY_PARSE_ERROR > Parse(const std::filesystem::path &aPath)
static const wxString TABLE_TYPE_NAME
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
The common library.
#define _(s)
static const std::string KiCadDesignBlockLibPathExtension
static const std::string KiCadDesignBlockPathExtension
static const std::string JsonFileExtension
static const std::string KiCadSchematicFileExtension
static const std::string KiCadPcbFileExtension
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)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
#define KICTL_NONKICAD_ONLY
chosen file is non-KiCad according to user
long long TimestampDir(const wxString &aDirPath, const wxString &aFilespec)
Computes a hash of modification times and sizes for files matching a pattern.
Definition unix/io.cpp:104
#define _HKI(x)
Definition page_info.cpp:44
Container that describes file type info.
Definition io_base.h:43
std::string path
wxLogTrace helper definitions.
Definition of file extensions used in Kicad.