1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2004-2016 Jean-Pierre Charras, jp.charras at wanadoo.fr
5  * Copyright (C) 2008 Wayne Stambaugh <stambaughw@gmail.com>
6  * Copyright (C) 2004-2021 KiCad Developers, see AUTHORS.txt for contributors.
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, you may find one here:
20  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21  * or you may search the http://www.gnu.org website for the version 2 license,
22  * or you may write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
24  */
25 
26 #include <algorithm>
27 #include <kiface_base.h>
28 #include <eda_base_frame.h>
29 #include <string_utils.h>
30 #include <macros.h>
31 #include <richio.h>
32 #include <config_params.h>
33 #include <wildcards_and_files_ext.h>
34 #include <project/project_file.h>
35 #include <project_rescue.h>
36 #include <properties.h>
37 #include <widgets/app_progress_dialog.h>
38 
39 #include <general.h>
40 #include <symbol_library.h>
41 #include <sch_plugins/legacy/sch_legacy_plugin.h>
42 
43 #include <wx/log.h>
44 #include <wx/progdlg.h>
45 #include <wx/tokenzr.h>
46 
SYMBOL_LIB(SCH_LIB_TYPE aType,const wxString & aFileName,SCH_IO_MGR::SCH_FILE_T aPluginType)47 SYMBOL_LIB::SYMBOL_LIB( SCH_LIB_TYPE aType, const wxString& aFileName,
48                     SCH_IO_MGR::SCH_FILE_T aPluginType ) :
49     // start @ != 0 so each additional library added
50     // is immediately detectable, zero would not be.
51     m_mod_hash( SYMBOL_LIBS::GetModifyGeneration() ),
52     m_pluginType( aPluginType )
53 {
54     type = aType;
55     isModified = false;
56     timeStamp = 0;
57     timeStamp = wxDateTime::Now();
58     versionMajor = 0;       // Will be updated after reading the lib file
59     versionMinor = 0;       // Will be updated after reading the lib file
60 
61     fileName = aFileName;
62 
63     if( !fileName.IsOk() )
64         fileName = "unnamed.lib";
65 
66     m_plugin.reset( SCH_IO_MGR::FindPlugin( m_pluginType ) );
67     m_properties = std::make_unique<PROPERTIES>();
68 }
69 
70 
~SYMBOL_LIB()71 SYMBOL_LIB::~SYMBOL_LIB()
72 {
73 }
74 
75 
Save(bool aSaveDocFile)76 void SYMBOL_LIB::Save( bool aSaveDocFile )
77 {
78     wxCHECK_RET( m_plugin != nullptr, wxString::Format( "no plugin defined for library `%s`.",
79                                                         fileName.GetFullPath() ) );
80 
81     PROPERTIES props;
82 
83     if( !aSaveDocFile )
84         props[ SCH_LEGACY_PLUGIN::PropNoDocFile ] = "";
85 
86     m_plugin->SaveLibrary( fileName.GetFullPath(), &props );
87     isModified = false;
88 }
89 
90 
Create(const wxString & aFileName)91 void SYMBOL_LIB::Create( const wxString& aFileName )
92 {
93     wxString tmpFileName = fileName.GetFullPath();
94 
95     if( !aFileName.IsEmpty() )
96         tmpFileName = aFileName;
97 
98     m_plugin->CreateSymbolLib( tmpFileName, m_properties.get() );
99 }
100 
101 
SetPluginType(SCH_IO_MGR::SCH_FILE_T aPluginType)102 void SYMBOL_LIB::SetPluginType( SCH_IO_MGR::SCH_FILE_T aPluginType )
103 {
104     if( m_pluginType != aPluginType )
105     {
106         m_pluginType = aPluginType;
107         m_plugin.reset( SCH_IO_MGR::FindPlugin( m_pluginType ) );
108     }
109 }
110 
111 
IsCache() const112 bool SYMBOL_LIB::IsCache() const
113 {
114     return m_properties->Exists( SCH_LEGACY_PLUGIN::PropNoDocFile );
115 }
116 
117 
SetCache()118 void SYMBOL_LIB::SetCache()
119 {
120     (*m_properties)[ SCH_LEGACY_PLUGIN::PropNoDocFile ] = "";
121 }
122 
123 
IsBuffering() const124 bool SYMBOL_LIB::IsBuffering() const
125 {
126     return m_properties->Exists( SCH_LEGACY_PLUGIN::PropBuffering );
127 }
128 
129 
EnableBuffering(bool aEnable)130 void SYMBOL_LIB::EnableBuffering( bool aEnable )
131 {
132     if( aEnable )
133         (*m_properties)[ SCH_LEGACY_PLUGIN::PropBuffering ] = "";
134     else
135         m_properties->Clear( SCH_LEGACY_PLUGIN::PropBuffering );
136 }
137 
138 
GetSymbolNames(wxArrayString & aNames) const139 void SYMBOL_LIB::GetSymbolNames( wxArrayString& aNames ) const
140 {
141     m_plugin->EnumerateSymbolLib( aNames, fileName.GetFullPath(), m_properties.get() );
142 
143     aNames.Sort();
144 }
145 
146 
GetSymbols(std::vector<LIB_SYMBOL * > & aSymbols) const147 void SYMBOL_LIB::GetSymbols( std::vector<LIB_SYMBOL*>& aSymbols ) const
148 {
149     m_plugin->EnumerateSymbolLib( aSymbols, fileName.GetFullPath(), m_properties.get() );
150 
151     std::sort( aSymbols.begin(), aSymbols.end(),
152             [](LIB_SYMBOL *lhs, LIB_SYMBOL *rhs) -> bool
153                 { return lhs->GetName() < rhs->GetName(); });
154 }
155 
156 
FindSymbol(const wxString & aName) const157 LIB_SYMBOL* SYMBOL_LIB::FindSymbol( const wxString& aName ) const
158 {
159     LIB_SYMBOL* symbol = m_plugin->LoadSymbol( fileName.GetFullPath(), aName, m_properties.get() );
160 
161     // Set the library to this even though technically the legacy cache plugin owns the
162     // symbols.  This allows the symbol library table conversion tool to determine the
163     // correct library where the symbol was found.
164     if( symbol && !symbol->GetLib() )
165         symbol->SetLib( const_cast<SYMBOL_LIB*>( this ) );
166 
167     return symbol;
168 }
169 
170 
FindSymbol(const LIB_ID & aLibId) const171 LIB_SYMBOL* SYMBOL_LIB::FindSymbol( const LIB_ID& aLibId ) const
172 {
173     return FindSymbol( aLibId.Format().wx_str() );
174 }
175 
176 
AddSymbol(LIB_SYMBOL * aSymbol)177 void SYMBOL_LIB::AddSymbol( LIB_SYMBOL* aSymbol )
178 {
179     // add a clone, not the caller's copy, the plugin take ownership of the new symbol.
180     m_plugin->SaveSymbol( fileName.GetFullPath(),
181                           new LIB_SYMBOL( *aSymbol->SharedPtr().get(), this ),
182                           m_properties.get() );
183 
184     // If we are not buffering, the library file is updated immediately when the plugin
185     // SaveSymbol() function is called.
186     if( IsBuffering() )
187         isModified = true;
188 
189     ++m_mod_hash;
190 }
191 
192 
RemoveSymbol(LIB_SYMBOL * aEntry)193 LIB_SYMBOL* SYMBOL_LIB::RemoveSymbol( LIB_SYMBOL* aEntry )
194 {
195     wxCHECK_MSG( aEntry != nullptr, nullptr, "NULL pointer cannot be removed from library." );
196 
197     m_plugin->DeleteSymbol( fileName.GetFullPath(), aEntry->GetName(), m_properties.get() );
198 
199     // If we are not buffering, the library file is updated immediately when the plugin
200     // SaveSymbol() function is called.
201     if( IsBuffering() )
202         isModified = true;
203 
204     ++m_mod_hash;
205     return nullptr;
206 }
207 
208 
ReplaceSymbol(LIB_SYMBOL * aOldSymbol,LIB_SYMBOL * aNewSymbol)209 LIB_SYMBOL* SYMBOL_LIB::ReplaceSymbol( LIB_SYMBOL* aOldSymbol, LIB_SYMBOL* aNewSymbol )
210 {
211     wxASSERT( aOldSymbol != nullptr );
212     wxASSERT( aNewSymbol != nullptr );
213 
214     m_plugin->DeleteSymbol( fileName.GetFullPath(), aOldSymbol->GetName(), m_properties.get() );
215 
216     LIB_SYMBOL* my_part = new LIB_SYMBOL( *aNewSymbol, this );
217 
218     m_plugin->SaveSymbol( fileName.GetFullPath(), my_part, m_properties.get() );
219 
220     // If we are not buffering, the library file is updated immediately when the plugin
221     // SaveSymbol() function is called.
222     if( IsBuffering() )
223         isModified = true;
224 
225     ++m_mod_hash;
226     return my_part;
227 }
228 
229 
LoadLibrary(const wxString & aFileName)230 SYMBOL_LIB* SYMBOL_LIB::LoadLibrary( const wxString& aFileName )
231 {
232     std::unique_ptr<SYMBOL_LIB> lib = std::make_unique<SYMBOL_LIB>( SCH_LIB_TYPE::LT_EESCHEMA,
233                                                                     aFileName );
234 
235     std::vector<LIB_SYMBOL*> parts;
236     // This loads the library.
237     lib->GetSymbols( parts );
238 
239     // Now, set the LIB_SYMBOL m_library member but it will only be used
240     // when loading legacy libraries in the future. Once the symbols in the
241     // schematic have a full #LIB_ID, this will not get called.
242     for( size_t ii = 0; ii < parts.size(); ii++ )
243     {
244         LIB_SYMBOL* part = parts[ii];
245 
246         part->SetLib( lib.get() );
247     }
248 
249     SYMBOL_LIB* ret = lib.release();
250     return ret;
251 }
252 
253 
AddLibrary(const wxString & aFileName)254 SYMBOL_LIB* SYMBOL_LIBS::AddLibrary( const wxString& aFileName )
255 {
256     SYMBOL_LIB* lib;
257 
258     wxFileName fn = aFileName;
259     // Don't reload the library if it is already loaded.
260     lib = FindLibrary( fn.GetName() );
261 
262     if( lib )
263         return lib;
264 
265     try
266     {
267         lib = SYMBOL_LIB::LoadLibrary( aFileName );
268         push_back( lib );
269 
270         return lib;
271     }
272     catch( ... )
273     {
274         return nullptr;
275     }
276 }
277 
278 
AddLibrary(const wxString & aFileName,SYMBOL_LIBS::iterator & aIterator)279 SYMBOL_LIB* SYMBOL_LIBS::AddLibrary( const wxString& aFileName, SYMBOL_LIBS::iterator& aIterator )
280 {
281     // Don't reload the library if it is already loaded.
282     wxFileName fn( aFileName );
283     SYMBOL_LIB* lib = FindLibrary( fn.GetName() );
284 
285     if( lib )
286         return lib;
287 
288     try
289     {
290         lib = SYMBOL_LIB::LoadLibrary( aFileName );
291 
292         if( aIterator >= begin() && aIterator < end() )
293             insert( aIterator, lib );
294         else
295             push_back( lib );
296 
297         return lib;
298     }
299     catch( ... )
300     {
301         return nullptr;
302     }
303 }
304 
305 
FindLibrary(const wxString & aName)306 SYMBOL_LIB* SYMBOL_LIBS::FindLibrary( const wxString& aName )
307 {
308     for( SYMBOL_LIBS::iterator it = begin();  it!=end();  ++it )
309     {
310         if( it->GetName() == aName )
311             return &*it;
312     }
313 
314     return nullptr;
315 }
316 
317 
GetCacheLibrary()318 SYMBOL_LIB* SYMBOL_LIBS::GetCacheLibrary()
319 {
320     for( SYMBOL_LIBS::iterator it = begin();  it!=end();  ++it )
321     {
322         if( it->IsCache() )
323             return &*it;
324     }
325 
326     return nullptr;
327 }
328 
329 
FindLibraryByFullFileName(const wxString & aFullFileName)330 SYMBOL_LIB* SYMBOL_LIBS::FindLibraryByFullFileName( const wxString& aFullFileName )
331 {
332     for( SYMBOL_LIBS::iterator it = begin();  it!=end();  ++it )
333     {
334         if( it->GetFullFileName() == aFullFileName )
335             return &*it;
336     }
337 
338     return nullptr;
339 }
340 
341 
GetLibraryNames(bool aSorted)342 wxArrayString SYMBOL_LIBS::GetLibraryNames( bool aSorted )
343 {
344     wxArrayString cacheNames;
345     wxArrayString names;
346 
347     for( SYMBOL_LIB& lib : *this )
348     {
349         if( lib.IsCache() && aSorted )
350             cacheNames.Add( lib.GetName() );
351         else
352             names.Add( lib.GetName() );
353     }
354 
355     // Even sorted, the cache library is always at the end of the list.
356     if( aSorted )
357         names.Sort();
358 
359     for( unsigned int i = 0; i<cacheNames.Count(); i++ )
360         names.Add( cacheNames.Item( i ) );
361 
362     return names;
363 }
364 
365 
FindLibSymbol(const LIB_ID & aLibId,const wxString & aLibraryName)366 LIB_SYMBOL* SYMBOL_LIBS::FindLibSymbol( const LIB_ID& aLibId, const wxString& aLibraryName )
367 {
368     LIB_SYMBOL* part = nullptr;
369 
370     for( SYMBOL_LIB& lib : *this )
371     {
372         if( !aLibraryName.IsEmpty() && lib.GetName() != aLibraryName )
373             continue;
374 
375         part = lib.FindSymbol( aLibId.GetLibItemName().wx_str() );
376 
377         if( part )
378             break;
379     }
380 
381     return part;
382 }
383 
384 
FindLibraryNearEntries(std::vector<LIB_SYMBOL * > & aCandidates,const wxString & aEntryName,const wxString & aLibraryName)385 void SYMBOL_LIBS::FindLibraryNearEntries( std::vector<LIB_SYMBOL*>& aCandidates,
386                                           const wxString& aEntryName,
387                                           const wxString& aLibraryName )
388 {
389     for( SYMBOL_LIB& lib : *this )
390     {
391         if( !aLibraryName.IsEmpty() && lib.GetName() != aLibraryName )
392             continue;
393 
394         wxArrayString partNames;
395 
396         lib.GetSymbolNames( partNames );
397 
398         if( partNames.IsEmpty() )
399             continue;
400 
401         for( size_t i = 0;  i < partNames.size();  i++ )
402         {
403             if( partNames[i].CmpNoCase( aEntryName ) == 0 )
404                 aCandidates.push_back( lib.FindSymbol( partNames[i] ) );
405         }
406     }
407 }
408 
409 
410 int        SYMBOL_LIBS::s_modify_generation = 1;     // starts at 1 and goes up
411 std::mutex SYMBOL_LIBS::s_generationMutex;
412 
413 
GetModifyHash()414 int SYMBOL_LIBS::GetModifyHash()
415 {
416     int hash = 0;
417 
418     for( SYMBOL_LIBS::const_iterator it = begin();  it != end();  ++it )
419     {
420         hash += it->GetModHash();
421     }
422 
423     // Rebuilding the cache (m_cache) does not change the GetModHash() value,
424     // but changes SYMBOL_LIBS::s_modify_generation.
425     // Take this change in account:
426     hash += SYMBOL_LIBS::GetModifyGeneration();
427 
428     return hash;
429 }
430 
431 
LibNamesAndPaths(PROJECT * aProject,bool doSave,wxString * aPaths,wxArrayString * aNames)432 void SYMBOL_LIBS::LibNamesAndPaths( PROJECT* aProject, bool doSave,
433                                     wxString* aPaths, wxArrayString* aNames )
434 {
435     wxCHECK_RET( aProject, "Null PROJECT in LibNamesAndPaths" );
436 
437     PROJECT_FILE& project = aProject->GetProjectFile();
438 
439     if( doSave )
440     {
441         if( aPaths )
442             project.m_LegacyLibDir = *aPaths;
443 
444         if( aNames )
445             project.m_LegacyLibNames = *aNames;
446     }
447     else
448     {
449         if( aPaths )
450             *aPaths = project.m_LegacyLibDir;
451 
452         if( aNames )
453             *aNames = project.m_LegacyLibNames;
454     }
455 }
456 
457 
CacheName(const wxString & aFullProjectFilename)458 const wxString SYMBOL_LIBS::CacheName( const wxString& aFullProjectFilename )
459 {
460     wxFileName  name = aFullProjectFilename;
461 
462     name.SetName( name.GetName() + "-cache" );
463     name.SetExt( LegacySymbolLibFileExtension );
464 
465     if( name.FileExists() )
466         return name.GetFullPath();
467 
468     return wxEmptyString;
469 }
470 
471 
LoadAllLibraries(PROJECT * aProject,bool aShowProgress)472 void SYMBOL_LIBS::LoadAllLibraries( PROJECT* aProject, bool aShowProgress )
473 {
474     wxString        filename;
475     wxString        libs_not_found;
476     SEARCH_STACK*   lib_search = aProject->SchSearchS();
477 
478 #if defined(DEBUG) && 0
479     lib_search->Show( __func__ );
480 #endif
481 
482     wxArrayString   lib_names;
483 
484     LibNamesAndPaths( aProject, false, nullptr, &lib_names );
485 
486     // Post symbol library table, this should be empty.  Only the cache library should get loaded.
487     if( !lib_names.empty() )
488     {
489         APP_PROGRESS_DIALOG lib_dialog( _( "Loading Symbol Libraries" ),
490                                         wxEmptyString,
491                                         lib_names.GetCount(),
492                                         nullptr,
493                                         false,
494                                         wxPD_APP_MODAL );
495 
496         if( aShowProgress )
497         {
498             lib_dialog.Show();
499         }
500 
501         wxString progress_message;
502 
503         for( unsigned i = 0; i < lib_names.GetCount();  ++i )
504         {
505             if( aShowProgress )
506             {
507                 lib_dialog.Update( i, wxString::Format( _( "Loading %s..." ), lib_names[i] ) );
508             }
509 
510             // lib_names[] does not store the file extension. Set it.
511             // Remember lib_names[i] can contain a '.' in name, so using a wxFileName
512             // before adding the extension can create incorrect full filename
513             wxString fullname = lib_names[i] + "." + LegacySymbolLibFileExtension;
514             // Now the full name is set, we can use a wxFileName.
515             wxFileName fn( fullname );
516 
517             // Skip if the file name is not valid..
518             if( !fn.IsOk() )
519                 continue;
520 
521             if( !fn.FileExists() )
522             {
523                 filename = lib_search->FindValidPath( fn.GetFullPath() );
524 
525                 if( !filename )
526                 {
527                     libs_not_found += fn.GetFullPath();
528                     libs_not_found += '\n';
529                     continue;
530                 }
531             }
532             else
533             {   // ensure the lib filename has a absolute path.
534                 // If the lib has no absolute path, and is found in the cwd by fn.FileExists(),
535                 // make a full absolute path, to avoid issues with load library functions which
536                 // expects an absolute path.
537                 if( !fn.IsAbsolute() )
538                     fn.MakeAbsolute();
539 
540                 filename = fn.GetFullPath();
541             }
542 
543             try
544             {
545                 AddLibrary( filename );
546             }
547             catch( const IO_ERROR& ioe )
548             {
549                 wxString msg;
550                 msg.Printf( _( "Symbol library '%s' failed to load." ), filename );
551 
552                 wxLogError( msg + wxS( "\n" ) + ioe.What() );
553             }
554         }
555     }
556 
557     // add the special cache library.
558     wxString cache_name = CacheName( aProject->GetProjectFullName() );
559     SYMBOL_LIB* cache_lib;
560 
561     if( !aProject->IsNullProject() && !cache_name.IsEmpty() )
562     {
563         try
564         {
565             cache_lib = AddLibrary( cache_name );
566 
567             if( cache_lib )
568                 cache_lib->SetCache();
569         }
570         catch( const IO_ERROR& ioe )
571         {
572             wxString msg = wxString::Format( _( "Error loading symbol library '%s'." )
573                                              + wxS( "\n%s" ),
574                                              cache_name,
575                                              ioe.What() );
576 
577             THROW_IO_ERROR( msg );
578         }
579     }
580 
581     // Print the libraries not found
582     if( !libs_not_found.IsEmpty() )
583     {
584         // Use a different exception type so catch()er can route to proper use
585         // of the HTML_MESSAGE_BOX.
586         THROW_PARSE_ERROR( wxEmptyString, __func__, TO_UTF8( libs_not_found ), 0, 0 );
587     }
588 }
589