KiCad PCB EDA Suite
symbol_lib_table.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) 2016 Wayne Stambaugh <stambaughw@gmail.com>
5  * Copyright (C) 2016-2021 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 
26 #include <lib_id.h>
27 #include <lib_table_lexer.h>
28 #include <pgm_base.h>
29 #include <search_stack.h>
31 #include <systemdirsappend.h>
32 #include <symbol_lib_table.h>
33 #include <lib_symbol.h>
34 
35 #define OPT_SEP '|'
36 
37 using namespace LIB_TABLE_T;
38 
39 
40 static const wxString global_tbl_name( "sym-lib-table" );
41 
42 
43 const char* SYMBOL_LIB_TABLE::PropPowerSymsOnly = "pwr_sym_only";
44 const char* SYMBOL_LIB_TABLE::PropNonPowerSymsOnly = "non_pwr_sym_only";
45 int SYMBOL_LIB_TABLE::m_modifyHash = 1; // starts at 1 and goes up
46 
47 
52 
53 
55 {
56  return LIB_TABLE_ROW::operator == ( aRow ) && type == aRow.type;
57 }
58 
59 
60 void SYMBOL_LIB_TABLE_ROW::SetType( const wxString& aType )
61 {
62  type = SCH_IO_MGR::EnumFromStr( aType );
63 
64  if( SCH_IO_MGR::SCH_FILE_T( -1 ) == type )
65  type = SCH_IO_MGR::SCH_LEGACY;
66 
67  plugin.release();
68 }
69 
70 
72 {
73  if( !plugin )
74  {
75  wxArrayString dummyList;
76 
77  plugin.set( SCH_IO_MGR::FindPlugin( type ) );
78  SetLoaded( false );
79  plugin->EnumerateSymbolLib( dummyList, GetFullURI( true ), GetProperties() );
80  SetLoaded( true );
81  return true;
82  }
83 
84  return false;
85 }
86 
87 
89  LIB_TABLE( aFallBackTable )
90 {
91  // not copying fall back, simply search aFallBackTable separately
92  // if "nickName not found".
93 }
94 
95 
97 {
98  return g_symbolLibraryTable;
99 }
100 
101 
102 void SYMBOL_LIB_TABLE::Parse( LIB_TABLE_LEXER* in )
103 {
104  T tok;
105  wxString errMsg; // to collect error messages
106 
107  // This table may be nested within a larger s-expression, or not.
108  // Allow for parser of that optional containing s-epression to have looked ahead.
109  if( in->CurTok() != T_sym_lib_table )
110  {
111  in->NeedLEFT();
112 
113  if( ( tok = in->NextTok() ) != T_sym_lib_table )
114  in->Expecting( T_sym_lib_table );
115  }
116 
117  while( ( tok = in->NextTok() ) != T_RIGHT )
118  {
119  std::unique_ptr< SYMBOL_LIB_TABLE_ROW > row = std::make_unique<SYMBOL_LIB_TABLE_ROW>();
120 
121  if( tok == T_EOF )
122  in->Expecting( T_RIGHT );
123 
124  if( tok != T_LEFT )
125  in->Expecting( T_LEFT );
126 
127  // in case there is a "row integrity" error, tell where later.
128  int lineNum = in->CurLineNumber();
129 
130  if( ( tok = in->NextTok() ) != T_lib )
131  in->Expecting( T_lib );
132 
133  // (name NICKNAME)
134  in->NeedLEFT();
135 
136  if( ( tok = in->NextTok() ) != T_name )
137  in->Expecting( T_name );
138 
139  in->NeedSYMBOLorNUMBER();
140 
141  row->SetNickName( in->FromUTF8() );
142 
143  in->NeedRIGHT();
144 
145  // After (name), remaining (lib) elements are order independent, and in
146  // some cases optional.
147  bool sawType = false;
148  bool sawOpts = false;
149  bool sawDesc = false;
150  bool sawUri = false;
151  bool sawDisabled = false;
152 
153  while( ( tok = in->NextTok() ) != T_RIGHT )
154  {
155  if( tok == T_EOF )
156  in->Unexpected( T_EOF );
157 
158  if( tok != T_LEFT )
159  in->Expecting( T_LEFT );
160 
161  tok = in->NeedSYMBOLorNUMBER();
162 
163  switch( tok )
164  {
165  case T_uri:
166  if( sawUri )
167  in->Duplicate( tok );
168  sawUri = true;
169  in->NeedSYMBOLorNUMBER();
170  row->SetFullURI( in->FromUTF8() );
171  break;
172 
173  case T_type:
174  if( sawType )
175  in->Duplicate( tok );
176  sawType = true;
177  in->NeedSYMBOLorNUMBER();
178  row->SetType( in->FromUTF8() );
179  break;
180 
181  case T_options:
182  if( sawOpts )
183  in->Duplicate( tok );
184  sawOpts = true;
185  in->NeedSYMBOLorNUMBER();
186  row->SetOptions( in->FromUTF8() );
187  break;
188 
189  case T_descr:
190  if( sawDesc )
191  in->Duplicate( tok );
192  sawDesc = true;
193  in->NeedSYMBOLorNUMBER();
194  row->SetDescr( in->FromUTF8() );
195  break;
196 
197  case T_disabled:
198  if( sawDisabled )
199  in->Duplicate( tok );
200  sawDisabled = true;
201  row->SetEnabled( false );
202  break;
203 
204  default:
205  in->Unexpected( tok );
206  }
207 
208  in->NeedRIGHT();
209  }
210 
211  if( !sawType )
212  in->Expecting( T_type );
213 
214  if( !sawUri )
215  in->Expecting( T_uri );
216 
217  // all nickNames within this table fragment must be unique, so we do not
218  // use doReplace in InsertRow(). (However a fallBack table can have a
219  // conflicting nickName and ours will supercede that one since in
220  // FindLib() we search this table before any fall back.)
221  wxString nickname = row->GetNickName(); // store it to be able to used it
222  // after row deletion if an error occurs
223  LIB_TABLE_ROW* tmp = row.release();
224 
225  if( !InsertRow( tmp ) )
226  {
227  delete tmp; // The table did not take ownership of the row.
228 
229  wxString msg = wxString::Format( _( "Duplicate library nickname '%s' found in symbol "
230  "library table file line %d" ),
231  nickname,
232  lineNum );
233 
234  if( !errMsg.IsEmpty() )
235  errMsg << '\n';
236 
237  errMsg << msg;
238  }
239  }
240 
241  if( !errMsg.IsEmpty() )
242  THROW_IO_ERROR( errMsg );
243 }
244 
245 
246 void SYMBOL_LIB_TABLE::Format( OUTPUTFORMATTER* aOutput, int aIndentLevel ) const
247 {
248  aOutput->Print( aIndentLevel, "(sym_lib_table\n" );
249 
250  for( LIB_TABLE_ROWS_CITER it = rows.begin(); it != rows.end(); ++it )
251  {
252  it->Format( aOutput, aIndentLevel+1 );
253  }
254 
255  aOutput->Print( aIndentLevel, ")\n" );
256 }
257 
258 
260 {
261  int hash = 0;
262  std::vector< wxString > libNames = GetLogicalLibs();
263 
264  for( const auto& libName : libNames )
265  {
266  const SYMBOL_LIB_TABLE_ROW* row = FindRow( libName, true );
267 
268  if( !row || !row->plugin )
269  {
270  wxFAIL;
271  continue;
272  }
273 
274  hash += row->plugin->GetModifyHash();
275  }
276 
277  hash += m_modifyHash;
278 
279  return hash;
280 }
281 
282 
283 void SYMBOL_LIB_TABLE::EnumerateSymbolLib( const wxString& aNickname, wxArrayString& aAliasNames,
284  bool aPowerSymbolsOnly )
285 {
286  SYMBOL_LIB_TABLE_ROW* row = FindRow( aNickname, true );
287  wxCHECK( row && row->plugin, /* void */ );
288 
289  wxString options = row->GetOptions();
290 
291  if( aPowerSymbolsOnly )
292  row->SetOptions( row->GetOptions() + " " + PropPowerSymsOnly );
293 
294  row->SetLoaded( false );
295  row->plugin->EnumerateSymbolLib( aAliasNames, row->GetFullURI( true ), row->GetProperties() );
296  row->SetLoaded( true );
297 
298  if( aPowerSymbolsOnly )
299  row->SetOptions( options );
300 }
301 
302 
303 SYMBOL_LIB_TABLE_ROW* SYMBOL_LIB_TABLE::FindRow( const wxString& aNickname, bool aCheckIfEnabled )
304 {
305  SYMBOL_LIB_TABLE_ROW* row =
306  dynamic_cast< SYMBOL_LIB_TABLE_ROW* >( findRow( aNickname, aCheckIfEnabled ) );
307 
308  if( !row )
309  return nullptr;
310 
311  // We've been 'lazy' up until now, but it cannot be deferred any longer,
312  // instantiate a PLUGIN of the proper kind if it is not already in this
313  // SYMBOL_LIB_TABLE_ROW.
314  if( !row->plugin )
315  row->setPlugin( SCH_IO_MGR::FindPlugin( row->type ) );
316 
317  return row;
318 }
319 
320 
321 void SYMBOL_LIB_TABLE::LoadSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolList,
322  const wxString& aNickname, bool aPowerSymbolsOnly )
323 {
324  SYMBOL_LIB_TABLE_ROW* row = FindRow( aNickname, true );
325  wxCHECK( row && row->plugin, /* void */ );
326 
327  wxString options = row->GetOptions();
328 
329  if( aPowerSymbolsOnly )
330  row->SetOptions( row->GetOptions() + " " + PropPowerSymsOnly );
331 
332  row->SetLoaded( false );
333  row->plugin->EnumerateSymbolLib( aSymbolList, row->GetFullURI( true ), row->GetProperties() );
334  row->SetLoaded( true );
335 
336  if( aPowerSymbolsOnly )
337  row->SetOptions( options );
338 
339  // The library cannot know its own name, because it might have been renamed or moved.
340  // Therefore footprints cannot know their own library nickname when residing in
341  // a symbol library.
342  // Only at this API layer can we tell the symbol about its actual library nickname.
343  for( LIB_SYMBOL* symbol : aSymbolList )
344  {
345  LIB_ID id = symbol->GetLibId();
346 
347  id.SetLibNickname( row->GetNickName() );
348  symbol->SetLibId( id );
349  }
350 }
351 
352 
353 LIB_SYMBOL* SYMBOL_LIB_TABLE::LoadSymbol( const wxString& aNickname, const wxString& aSymbolName )
354 {
355  SYMBOL_LIB_TABLE_ROW* row = FindRow( aNickname, true );
356 
357  if( !row || !row->plugin )
358  return nullptr;
359 
360  LIB_SYMBOL* symbol = row->plugin->LoadSymbol( row->GetFullURI( true ), aSymbolName,
361  row->GetProperties() );
362 
363  // The library cannot know its own name, because it might have been renamed or moved.
364  // Therefore footprints cannot know their own library nickname when residing in
365  // a symbol library.
366  // Only at this API layer can we tell the symbol about its actual library nickname.
367  if( symbol )
368  {
369  LIB_ID id = symbol->GetLibId();
370 
371  id.SetLibNickname( row->GetNickName() );
372  symbol->SetLibId( id );
373  }
374 
375  return symbol;
376 }
377 
378 
380  const LIB_SYMBOL* aSymbol, bool aOverwrite )
381 {
382  const SYMBOL_LIB_TABLE_ROW* row = FindRow( aNickname, true );
383  wxCHECK( row && row->plugin, SAVE_SKIPPED );
384 
385  if( !row->plugin->IsSymbolLibWritable( row->GetFullURI( true ) ) )
386  return SAVE_SKIPPED;
387 
388  if( !aOverwrite )
389  {
390  // Try loading the footprint to see if it already exists, caller wants overwrite
391  // protection, which is atypical, not the default.
392 
393  wxString name = aSymbol->GetLibId().GetLibItemName();
394 
395  std::unique_ptr<LIB_SYMBOL> symbol( row->plugin->LoadSymbol( row->GetFullURI( true ),
396  name, row->GetProperties() ) );
397 
398  if( symbol.get() )
399  return SAVE_SKIPPED;
400  }
401 
402  try
403  {
404  row->plugin->SaveSymbol( row->GetFullURI( true ), aSymbol, row->GetProperties() );
405  }
406  catch( const IO_ERROR& )
407  {
408  return SAVE_SKIPPED;
409  }
410 
411  return SAVE_OK;
412 }
413 
414 
415 void SYMBOL_LIB_TABLE::DeleteSymbol( const wxString& aNickname, const wxString& aSymbolName )
416 {
417  const SYMBOL_LIB_TABLE_ROW* row = FindRow( aNickname, true );
418  wxCHECK( row && row->plugin, /* void */ );
419  return row->plugin->DeleteSymbol( row->GetFullURI( true ), aSymbolName, row->GetProperties() );
420 }
421 
422 
423 bool SYMBOL_LIB_TABLE::IsSymbolLibWritable( const wxString& aNickname )
424 {
425  const SYMBOL_LIB_TABLE_ROW* row = FindRow( aNickname, true );
426  wxCHECK( row && row->plugin, false );
427  return row->plugin->IsSymbolLibWritable( row->GetFullURI( true ) );
428 }
429 
430 bool SYMBOL_LIB_TABLE::IsSymbolLibLoaded( const wxString& aNickname )
431 {
432  const SYMBOL_LIB_TABLE_ROW* row = FindRow( aNickname, true );
433  wxCHECK( row, false );
434  return row->GetIsLoaded();
435 }
436 
437 
438 void SYMBOL_LIB_TABLE::DeleteSymbolLib( const wxString& aNickname )
439 {
440  const SYMBOL_LIB_TABLE_ROW* row = FindRow( aNickname, true );
441  wxCHECK( row && row->plugin, /* void */ );
442  row->plugin->DeleteSymbolLib( row->GetFullURI( true ), row->GetProperties() );
443 }
444 
445 
446 void SYMBOL_LIB_TABLE::CreateSymbolLib( const wxString& aNickname )
447 {
448  const SYMBOL_LIB_TABLE_ROW* row = FindRow( aNickname, true );
449  wxCHECK( row && row->plugin, /* void */ );
450  row->plugin->CreateSymbolLib( row->GetFullURI( true ), row->GetProperties() );
451 }
452 
453 
455 {
456  wxString nickname = aLibId.GetLibNickname();
457  wxString name = aLibId.GetLibItemName();
458 
459  if( nickname.size() )
460  {
461  return LoadSymbol( nickname, name );
462  }
463  else
464  {
465  // nickname is empty, sequentially search (alphabetically) all libs/nicks for first match:
466  std::vector<wxString> nicks = GetLogicalLibs();
467 
468  // Search each library going through libraries alphabetically.
469  for( unsigned i = 0; i < nicks.size(); ++i )
470  {
471  // FootprintLoad() returns NULL on not found, does not throw exception
472  // unless there's an IO_ERROR.
473  LIB_SYMBOL* ret = LoadSymbol( nicks[i], name );
474 
475  if( ret )
476  return ret;
477  }
478 
479  return nullptr;
480  }
481 }
482 
483 
485 {
486  return "KICAD6_SYMBOL_DIR";
487 }
488 
489 
491 {
492  bool tableExists = true;
493  wxFileName fn = GetGlobalTableFileName();
494 
495  if( !fn.FileExists() )
496  {
497  tableExists = false;
498 
499  if( !fn.DirExists() && !fn.Mkdir( 0x777, wxPATH_MKDIR_FULL ) )
500  {
501  THROW_IO_ERROR( wxString::Format( _( "Cannot create global library table path '%s'." ),
502  fn.GetPath() ) );
503  }
504 
505  // Attempt to copy the default global file table from the KiCad
506  // template folder to the user's home configuration path.
507  SEARCH_STACK ss;
508 
509  SystemDirsAppend( &ss );
510 
511  wxString templatePath =
512  Pgm().GetLocalEnvVariables().at( wxT( "KICAD6_TEMPLATE_DIR" ) ).GetValue();
513 
514  if( !templatePath.IsEmpty() )
515  ss.AddPaths( templatePath, 0 );
516 
517  wxString fileName = ss.FindValidPath( global_tbl_name );
518 
519  // The fallback is to create an empty global symbol table for the user to populate.
520  if( fileName.IsEmpty() || !::wxCopyFile( fileName, fn.GetFullPath(), false ) )
521  {
522  SYMBOL_LIB_TABLE emptyTable;
523 
524  emptyTable.Save( fn.GetFullPath() );
525  }
526  }
527 
528  aTable.Load( fn.GetFullPath() );
529 
530  return tableExists;
531 }
532 
533 
535 {
536  wxFileName fn;
537 
539  fn.SetName( global_tbl_name );
540 
541  return fn.GetFullPath();
542 }
543 
544 
546 {
547  return global_tbl_name;
548 }
static const wxString & GetSymbolLibTableFileName()
static SYMBOL_LIB_TABLE & GetGlobalLibTable()
void EnumerateSymbolLib(const wxString &aNickname, wxArrayString &aAliasNames, bool aPowerSymbolsOnly=false)
Return a list of symbol alias names contained within the library given by aNickname.
wxString FindValidPath(const wxString &aFileName) const
Definition: search_stack.h:70
const UTF8 & GetLibItemName() const
Definition: lib_id.h:104
Hold a record identifying a symbol library accessed by the appropriate symbol library SCH_PLUGIN obje...
const wxString & GetOptions() const
Return the options string, which may hold a password or anything else needed to instantiate the under...
Hold a record identifying a library accessed by the appropriate plug in object in the LIB_TABLE.
void CreateSymbolLib(const wxString &aNickname)
bool InsertRow(LIB_TABLE_ROW *aRow, bool doReplace=false)
Adds aRow if it does not already exist or if doReplace is true.
SCH_PLUGIN::SCH_PLUGIN_RELEASER plugin
SAVE_T SaveSymbol(const wxString &aNickname, const LIB_SYMBOL *aSymbol, bool aOverwrite=true)
Write aSymbol to an existing library given by aNickname.
void DeleteSymbolLib(const wxString &aNickname)
void SetType(const wxString &aType) override
Change the schematic plugin type represented by this row.
static const wxString GlobalPathEnvVariableName()
Return the name of the environment variable used to hold the directory of locally installed "KiCad sp...
System directories search utilities.
An interface used to output 8 bit text in a convenient way.
Definition: richio.h:309
LIB_SYMBOL * LoadSymbolWithOptionalNickname(const LIB_ID &aId)
Load a LIB_SYMBOL having aFootprintId with possibly an empty library nickname.
Look for files in a number of paths.
Definition: search_stack.h:41
LIB_TABLE_ROWS rows
static const wxString global_tbl_name("sym-lib-table")
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:51
bool IsSymbolLibWritable(const wxString &aNickname)
Return true if the library given by aNickname is writable.
Define a library symbol object.
Definition: lib_symbol.h:96
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:106
bool operator==(const LIB_TABLE_ROW &r) const
bool GetIsLoaded() const
bool IsSymbolLibLoaded(const wxString &aNickname)
Return true if the library given by aNickname was successfully loaded.
bool operator==(const SYMBOL_LIB_TABLE_ROW &aRow) const
const wxString GetFullURI(bool aSubstituted=false) const
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
void SetLoaded(bool aLoaded)
Mark the row as being a loaded library.
const wxString & GetNickName() const
static bool LoadGlobalTable(SYMBOL_LIB_TABLE &aTable)
Load the global symbol library table into aTable.
LIB_ID GetLibId() const override
Definition: lib_symbol.h:135
bool Refresh()
Attempt to reload the library.
#define _(s)
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition: lib_id.h:90
void SystemDirsAppend(SEARCH_STACK *aSearchStack)
Append system places to aSearchStack in a platform specific way and pertinent to KiCad programs.
LIB_TABLE_ROW * findRow(const wxString &aNickname, bool aCheckIfEnabled=false) const
Return a LIB_TABLE_ROW if aNickname is found in this table or in any chained fallBack table fragment,...
virtual void Format(OUTPUTFORMATTER *aOutput, int aIndentLevel) const override
Generate the table in s-expression format to aOutput with an indentation level of aIndentLevel.
void SetLibId(const LIB_ID &aLibId)
Definition: lib_symbol.h:136
void setPlugin(SCH_PLUGIN *aPlugin)
void Load(const wxString &aFileName)
Load the library table using the path defined by aFileName aFallBackTable.
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
int SetLibNickname(const UTF8 &aNickname)
Override the logical library name portion of the LIB_ID to aNickname.
Definition: lib_id.cpp:97
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
void LoadSymbolLib(std::vector< LIB_SYMBOL * > &aAliasList, const wxString &aNickname, bool aPowerSymbolsOnly=false)
see class PGM_BASE
LIB_TABLE_ROWS::const_iterator LIB_TABLE_ROWS_CITER
const char * name
Definition: DXF_plotter.cpp:56
void DeleteSymbol(const wxString &aNickname, const wxString &aSymbolName)
Deletes the aSymbolName from the library given by aNickname.
static SCH_FILE_T EnumFromStr(const wxString &aFileType)
Return the #SCH_FILE_T from the corresponding plugin type name: "kicad", "legacy",...
Definition: sch_io_mgr.cpp:98
static int m_modifyHash
helper for GetModifyHash()
virtual void Parse(LIB_TABLE_LEXER *aLexer) override
Parse the #LIB_TABLE_LEXER s-expression library table format into the appropriate LIB_TABLE_ROW objec...
void Save(const wxString &aFileName) const
Write this library table to aFileName in s-expression form.
LIB_SYMBOL * LoadSymbol(const wxString &aNickname, const wxString &aName)
Load a LIB_SYMBOL having aName from the library given by aNickname.
void SetOptions(const wxString &aOptions)
Change the library options strings.
SAVE_T
The set of return values from SaveSymbol() below.
static const char * PropNonPowerSymsOnly
const PROPERTIES * GetProperties() const
Return the constant PROPERTIES for this library (LIB_TABLE_ROW).
SYMBOL_LIB_TABLE g_symbolLibraryTable
The global symbol library table.
SYMBOL_LIB_TABLE_ROW * FindRow(const wxString &aNickName, bool aCheckIfEnabled=false)
Return an SYMBOL_LIB_TABLE_ROW if aNickName is found in this table or in any chained fallBack table f...
int PRINTF_FUNC Print(int nestLevel, const char *fmt,...)
Format and write text to the output stream.
Definition: richio.cpp:426
SYMBOL_LIB_TABLE(SYMBOL_LIB_TABLE *aFallBackTable=nullptr)
Build a symbol library table by pre-pending this table fragment in front of aFallBackTable.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
Definition: ki_exception.h:75
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:38
static wxString GetGlobalTableFileName()
Fetch the global symbol library table file name.
static const char * PropPowerSymsOnly
void AddPaths(const wxString &aPaths, int aIndex=-1)
Insert or append path(s).
std::vector< wxString > GetLogicalLibs()
Return the logical library names, all of them that are pertinent to a look up done on this LIB_TABLE.
Manage LIB_TABLE_ROW records (rows), and can be searched based on library nickname.