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.

874 lines
24 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2014-2020 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright (C) 2008 Wayne Stambaugh <stambaughw@gmail.com>
  6. * Copyright (C) 1992-2020 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. * @file common.cpp
  27. */
  28. #include <fctsys.h>
  29. #include <eda_base_frame.h>
  30. #include <base_struct.h>
  31. #include <common.h>
  32. #include <macros.h>
  33. #include <base_units.h>
  34. #include <reporter.h>
  35. #include <mutex>
  36. #include <settings/settings_manager.h>
  37. #include <wx/process.h>
  38. #include <wx/config.h>
  39. #include <wx/utils.h>
  40. #include <wx/stdpaths.h>
  41. #include <wx/url.h>
  42. #include <pgm_base.h>
  43. using KIGFX::COLOR4D;
  44. /**
  45. * Global variables definitions.
  46. *
  47. * TODO: All of these variables should be moved into the class were they
  48. * are defined and used. Most of them probably belong in the
  49. * application class.
  50. */
  51. // When reading/writing files, we need to swtich to setlocale( LC_NUMERIC, "C" ).
  52. // Works fine to read/write files with floating point numbers.
  53. // We can call setlocale( LC_NUMERIC, "C" ) of wxLocale( "C", "C", "C", false )
  54. // wxWidgets discourage a direct call to setlocale
  55. // However, for us, calling wxLocale( "C", "C", "C", false ) has a unwanted effect:
  56. // The I18N translations are no longer active, because the English dixtionary is selected.
  57. // To read files, this is not a major issues, but the resul can differ
  58. // from using setlocale(xx, "C").
  59. // Previouly, we called setlocale( LC_NUMERIC, "C" )
  60. // The old code will be removed when calling wxLocale( "C", "C", "C", false )
  61. // is fully tested, and all issues fixed
  62. #define USE_WXLOCALE 1 /* 0 to call setlocale, 1 to call wxLocale */
  63. // On Windows, when using setlocale, a wx alert is generated
  64. // in some cases (reading a bitmap for instance)
  65. // So we disable alerts during the time a file is read or written
  66. #if !USE_WXLOCALE
  67. #if defined( _WIN32 ) && defined( DEBUG )
  68. // a wxAssertHandler_t function to filter wxWidgets alert messages when reading/writing a file
  69. // when switching the locale to LC_NUMERIC, "C"
  70. // It is used in class LOCALE_IO to hide a useless (in kicad) wxWidgets alert message
  71. void KiAssertFilter( const wxString &file, int line,
  72. const wxString &func, const wxString &cond,
  73. const wxString &msg)
  74. {
  75. if( !msg.Contains( "Decimal separator mismatch" ) )
  76. wxTheApp->OnAssertFailure( file.c_str(), line, func.c_str(), cond.c_str(), msg.c_str() );
  77. }
  78. #endif
  79. #endif
  80. std::atomic<unsigned int> LOCALE_IO::m_c_count( 0 );
  81. LOCALE_IO::LOCALE_IO() : m_wxLocale( nullptr )
  82. {
  83. // use thread safe, atomic operation
  84. if( m_c_count++ == 0 )
  85. {
  86. #if USE_WXLOCALE
  87. m_wxLocale = new wxLocale( "C", "C", "C", false );
  88. #else
  89. // Store the user locale name, to restore this locale later, in dtor
  90. m_user_locale = setlocale( LC_NUMERIC, nullptr );
  91. #if defined( _WIN32 ) && defined( DEBUG )
  92. // Disable wxWidgets alerts
  93. wxSetAssertHandler( KiAssertFilter );
  94. #endif
  95. // Switch the locale to C locale, to read/write files with fp numbers
  96. setlocale( LC_NUMERIC, "C" );
  97. #endif
  98. }
  99. }
  100. LOCALE_IO::~LOCALE_IO()
  101. {
  102. // use thread safe, atomic operation
  103. if( --m_c_count == 0 )
  104. {
  105. // revert to the user locale
  106. #if USE_WXLOCALE
  107. delete m_wxLocale; // Deleting m_wxLocale restored previous locale
  108. m_wxLocale = nullptr;
  109. #else
  110. setlocale( LC_NUMERIC, m_user_locale.c_str() );
  111. #if defined( _WIN32 ) && defined( DEBUG )
  112. // Enable wxWidgets alerts
  113. wxSetDefaultAssertHandler();
  114. #endif
  115. #endif
  116. }
  117. }
  118. wxSize GetTextSize( const wxString& aSingleLine, wxWindow* aWindow )
  119. {
  120. wxCoord width;
  121. wxCoord height;
  122. {
  123. wxClientDC dc( aWindow );
  124. dc.SetFont( aWindow->GetFont() );
  125. dc.GetTextExtent( aSingleLine, &width, &height );
  126. }
  127. return wxSize( width, height );
  128. }
  129. bool EnsureTextCtrlWidth( wxTextCtrl* aCtrl, const wxString* aString )
  130. {
  131. wxWindow* window = aCtrl->GetParent();
  132. if( !window )
  133. window = aCtrl;
  134. wxString ctrlText;
  135. if( !aString )
  136. {
  137. ctrlText = aCtrl->GetValue();
  138. aString = &ctrlText;
  139. }
  140. wxSize textz = GetTextSize( *aString, window );
  141. wxSize ctrlz = aCtrl->GetSize();
  142. if( ctrlz.GetWidth() < textz.GetWidth() + 10 )
  143. {
  144. ctrlz.SetWidth( textz.GetWidth() + 10 );
  145. aCtrl->SetSizeHints( ctrlz );
  146. return true;
  147. }
  148. return false;
  149. }
  150. void SelectReferenceNumber( wxTextEntry* aTextEntry )
  151. {
  152. wxString ref = aTextEntry->GetValue();
  153. if( ref.find_first_of( '?' ) != ref.npos )
  154. {
  155. aTextEntry->SetSelection( ref.find_first_of( '?' ), ref.find_last_of( '?' ) + 1 );
  156. }
  157. else
  158. {
  159. wxString num = ref;
  160. while( !num.IsEmpty() && ( !isdigit( num.Last() ) || !isdigit( num.GetChar( 0 ) ) ) )
  161. {
  162. if( !isdigit( num.Last() ) )
  163. num.RemoveLast();
  164. if( !isdigit( num.GetChar ( 0 ) ) )
  165. num = num.Right( num.Length() - 1);
  166. }
  167. aTextEntry->SetSelection( ref.Find( num ), ref.Find( num ) + num.Length() );
  168. if( num.IsEmpty() )
  169. aTextEntry->SetSelection( -1, -1 );
  170. }
  171. }
  172. void wxStringSplit( const wxString& aText, wxArrayString& aStrings, wxChar aSplitter )
  173. {
  174. wxString tmp;
  175. for( unsigned ii = 0; ii < aText.Length(); ii++ )
  176. {
  177. if( aText[ii] == aSplitter )
  178. {
  179. aStrings.Add( tmp );
  180. tmp.Clear();
  181. }
  182. else
  183. tmp << aText[ii];
  184. }
  185. if( !tmp.IsEmpty() )
  186. {
  187. aStrings.Add( tmp );
  188. }
  189. }
  190. int ProcessExecute( const wxString& aCommandLine, int aFlags, wxProcess *callback )
  191. {
  192. return wxExecute( aCommandLine, aFlags, callback );
  193. }
  194. timestamp_t GetNewTimeStamp()
  195. {
  196. static timestamp_t oldTimeStamp;
  197. timestamp_t newTimeStamp;
  198. newTimeStamp = time( NULL );
  199. if( newTimeStamp <= oldTimeStamp )
  200. newTimeStamp = oldTimeStamp + 1;
  201. oldTimeStamp = newTimeStamp;
  202. return newTimeStamp;
  203. }
  204. enum Bracket
  205. {
  206. Bracket_None,
  207. Bracket_Normal = ')',
  208. Bracket_Curly = '}',
  209. #ifdef __WINDOWS__
  210. Bracket_Windows = '%', // yeah, Windows people are a bit strange ;-)
  211. #endif
  212. Bracket_Max
  213. };
  214. //
  215. // Stolen from wxExpandEnvVars and then heavily optimized
  216. //
  217. wxString KIwxExpandEnvVars(const wxString& str)
  218. {
  219. size_t strlen = str.length();
  220. wxString strResult;
  221. strResult.Alloc( strlen );
  222. for( size_t n = 0; n < strlen; n++ )
  223. {
  224. wxUniChar str_n = str[n];
  225. switch( str_n.GetValue() )
  226. {
  227. #ifdef __WINDOWS__
  228. case wxT( '%' ):
  229. #endif // __WINDOWS__
  230. case wxT( '$' ):
  231. {
  232. Bracket bracket;
  233. #ifdef __WINDOWS__
  234. if( str_n == wxT( '%' ) )
  235. bracket = Bracket_Windows;
  236. else
  237. #endif // __WINDOWS__
  238. if( n == strlen - 1 )
  239. {
  240. bracket = Bracket_None;
  241. }
  242. else
  243. {
  244. switch( str[n + 1].GetValue() )
  245. {
  246. case wxT( '(' ):
  247. bracket = Bracket_Normal;
  248. str_n = str[++n]; // skip the bracket
  249. break;
  250. case wxT( '{' ):
  251. bracket = Bracket_Curly;
  252. str_n = str[++n]; // skip the bracket
  253. break;
  254. default:
  255. bracket = Bracket_None;
  256. }
  257. }
  258. size_t m = n + 1;
  259. wxUniChar str_m = str[m];
  260. while( m < strlen && ( wxIsalnum( str_m ) || str_m == wxT( '_' ) ) )
  261. str_m = str[++m];
  262. wxString strVarName( str.c_str() + n + 1, m - n - 1 );
  263. // NB: use wxGetEnv instead of wxGetenv as otherwise variables
  264. // set through wxSetEnv may not be read correctly!
  265. bool expanded = false;
  266. wxString tmp;
  267. if( wxGetEnv( strVarName, &tmp ) )
  268. {
  269. strResult += tmp;
  270. expanded = true;
  271. }
  272. else
  273. {
  274. // variable doesn't exist => don't change anything
  275. #ifdef __WINDOWS__
  276. if ( bracket != Bracket_Windows )
  277. #endif
  278. if ( bracket != Bracket_None )
  279. strResult << str[n - 1];
  280. strResult << str_n << strVarName;
  281. }
  282. // check the closing bracket
  283. if( bracket != Bracket_None )
  284. {
  285. if( m == strlen || str_m != (wxChar)bracket )
  286. {
  287. // under MSW it's common to have '%' characters in the registry
  288. // and it's annoying to have warnings about them each time, so
  289. // ignore them silently if they are not used for env vars
  290. //
  291. // under Unix, OTOH, this warning could be useful for the user to
  292. // understand why isn't the variable expanded as intended
  293. #ifndef __WINDOWS__
  294. wxLogWarning( _( "Environment variables expansion failed: missing '%c' "
  295. "at position %u in '%s'." ),
  296. (char)bracket, (unsigned int) (m + 1), str.c_str() );
  297. #endif // __WINDOWS__
  298. }
  299. else
  300. {
  301. // skip closing bracket unless the variables wasn't expanded
  302. if( !expanded )
  303. strResult << (wxChar)bracket;
  304. m++;
  305. }
  306. }
  307. n = m - 1; // skip variable name
  308. str_n = str[n];
  309. }
  310. break;
  311. case wxT( '\\' ):
  312. // backslash can be used to suppress special meaning of % and $
  313. if( n != strlen - 1 && (str[n + 1] == wxT( '%' ) || str[n + 1] == wxT( '$' )) )
  314. {
  315. str_n = str[++n];
  316. strResult += str_n;
  317. break;
  318. }
  319. // else fall through
  320. default:
  321. strResult += str_n;
  322. }
  323. }
  324. return strResult;
  325. }
  326. const wxString ExpandEnvVarSubstitutions( const wxString& aString )
  327. {
  328. // wxGetenv( wchar_t* ) is not re-entrant on linux.
  329. // Put a lock on multithreaded use of wxGetenv( wchar_t* ), called from wxEpandEnvVars(),
  330. static std::mutex getenv_mutex;
  331. std::lock_guard<std::mutex> lock( getenv_mutex );
  332. // We reserve the right to do this another way, by providing our own member function.
  333. return KIwxExpandEnvVars( aString );
  334. }
  335. const wxString ResolveUriByEnvVars( const wxString& aUri )
  336. {
  337. // URL-like URI: return as is.
  338. wxURL url( aUri );
  339. if( url.GetError() == wxURL_NOERR )
  340. return aUri;
  341. // Otherwise, the path points to a local file. Resolve environment
  342. // variables if any.
  343. return ExpandEnvVarSubstitutions( aUri );
  344. }
  345. bool EnsureFileDirectoryExists( wxFileName* aTargetFullFileName,
  346. const wxString& aBaseFilename,
  347. REPORTER* aReporter )
  348. {
  349. wxString msg;
  350. wxString baseFilePath = wxFileName( aBaseFilename ).GetPath();
  351. // make aTargetFullFileName path, which is relative to aBaseFilename path (if it is not
  352. // already an absolute path) absolute:
  353. if( !aTargetFullFileName->MakeAbsolute( baseFilePath ) )
  354. {
  355. if( aReporter )
  356. {
  357. msg.Printf( _( "Cannot make path \"%s\" absolute with respect to \"%s\"." ),
  358. GetChars( aTargetFullFileName->GetPath() ),
  359. GetChars( baseFilePath ) );
  360. aReporter->Report( msg, REPORTER::RPT_ERROR );
  361. }
  362. return false;
  363. }
  364. // Ensure the path of aTargetFullFileName exists, and create it if needed:
  365. wxString outputPath( aTargetFullFileName->GetPath() );
  366. if( !wxFileName::DirExists( outputPath ) )
  367. {
  368. if( wxMkdir( outputPath ) )
  369. {
  370. if( aReporter )
  371. {
  372. msg.Printf( _( "Output directory \"%s\" created.\n" ), GetChars( outputPath ) );
  373. aReporter->Report( msg, REPORTER::RPT_INFO );
  374. return true;
  375. }
  376. }
  377. else
  378. {
  379. if( aReporter )
  380. {
  381. msg.Printf( _( "Cannot create output directory \"%s\".\n" ),
  382. GetChars( outputPath ) );
  383. aReporter->Report( msg, REPORTER::RPT_ERROR );
  384. }
  385. return false;
  386. }
  387. }
  388. return true;
  389. }
  390. #ifdef __WXMAC__
  391. wxString GetOSXKicadUserDataDir()
  392. {
  393. // According to wxWidgets documentation for GetUserDataDir:
  394. // Mac: ~/Library/Application Support/appname
  395. wxFileName udir( wxStandardPaths::Get().GetUserDataDir(), wxEmptyString );
  396. // Since appname is different if started via launcher or standalone binary
  397. // map all to "kicad" here
  398. udir.RemoveLastDir();
  399. udir.AppendDir( "kicad" );
  400. return udir.GetPath();
  401. }
  402. wxString GetOSXKicadMachineDataDir()
  403. {
  404. return wxT( "/Library/Application Support/kicad" );
  405. }
  406. wxString GetOSXKicadDataDir()
  407. {
  408. // According to wxWidgets documentation for GetDataDir:
  409. // Mac: appname.app/Contents/SharedSupport bundle subdirectory
  410. wxFileName ddir( wxStandardPaths::Get().GetDataDir(), wxEmptyString );
  411. // This must be mapped to main bundle for everything but kicad.app
  412. const wxArrayString dirs = ddir.GetDirs();
  413. if( dirs[dirs.GetCount() - 3] != wxT( "kicad.app" ) )
  414. {
  415. // Bundle structure resp. current path is
  416. // kicad.app/Contents/Applications/<standalone>.app/Contents/SharedSupport
  417. // and will be mapped to
  418. // kicad.app/Contents/SharedSupprt
  419. ddir.RemoveLastDir();
  420. ddir.RemoveLastDir();
  421. ddir.RemoveLastDir();
  422. ddir.RemoveLastDir();
  423. ddir.AppendDir( wxT( "SharedSupport" ) );
  424. }
  425. return ddir.GetPath();
  426. }
  427. #endif
  428. // add this only if it is not in wxWidgets (for instance before 3.1.0)
  429. #ifdef USE_KICAD_WXSTRING_HASH
  430. size_t std::hash<wxString>::operator()( const wxString& s ) const
  431. {
  432. return std::hash<std::wstring>{}( s.ToStdWstring() );
  433. }
  434. #endif
  435. #ifdef USE_KICAD_WXPOINT_LESS_AND_HASH
  436. size_t std::hash<wxPoint>::operator() ( const wxPoint& k ) const
  437. {
  438. auto xhash = std::hash<int>()( k.x );
  439. // 0x9e3779b9 is 2^33 / ( 1 + sqrt(5) )
  440. // Adding this value ensures that consecutive bits of y will not be close to each other
  441. // decreasing the likelihood of hash collision in similar values of x and y
  442. return xhash ^ ( std::hash<int>()( k.y ) + 0x9e3779b9 + ( xhash << 6 ) + ( xhash >> 2 ) );
  443. }
  444. bool std::less<wxPoint>::operator()( const wxPoint& aA, const wxPoint& aB ) const
  445. {
  446. if( aA.x == aB.x )
  447. return aA.y < aB.y;
  448. return aA.x < aB.x;
  449. }
  450. #endif
  451. std::ostream& operator<<( std::ostream& out, const wxSize& size )
  452. {
  453. out << " width=\"" << size.GetWidth() << "\" height=\"" << size.GetHeight() << "\"";
  454. return out;
  455. }
  456. std::ostream& operator<<( std::ostream& out, const wxPoint& pt )
  457. {
  458. out << " x=\"" << pt.x << "\" y=\"" << pt.y << "\"";
  459. return out;
  460. }
  461. /**
  462. * Performance enhancements to file and directory operations.
  463. *
  464. * Note: while it's annoying to have to make copies of wxWidgets stuff and then
  465. * add platform-specific performance optimizations, the following routines offer
  466. * SIGNIFICANT performance benefits.
  467. */
  468. /**
  469. * WX_FILENAME
  470. *
  471. * A wrapper around a wxFileName which avoids expensive calls to wxFileName::SplitPath()
  472. * and string concatenations by caching the path and filename locally and only resolving
  473. * the wxFileName when it has to.
  474. */
  475. WX_FILENAME::WX_FILENAME( const wxString& aPath, const wxString& aFilename ) :
  476. m_fn( aPath, aFilename ),
  477. m_path( aPath ),
  478. m_fullName( aFilename )
  479. { }
  480. void WX_FILENAME::SetFullName( const wxString& aFileNameAndExtension )
  481. {
  482. m_fullName = aFileNameAndExtension;
  483. }
  484. wxString WX_FILENAME::GetName() const
  485. {
  486. size_t dot = m_fullName.find_last_of( wxT( '.' ) );
  487. return m_fullName.substr( 0, dot );
  488. }
  489. wxString WX_FILENAME::GetFullName() const
  490. {
  491. return m_fullName;
  492. }
  493. wxString WX_FILENAME::GetPath() const
  494. {
  495. return m_path;
  496. }
  497. wxString WX_FILENAME::GetFullPath() const
  498. {
  499. return m_path + wxT( '/' ) + m_fullName;
  500. }
  501. // Write locally-cached values to the wxFileName. MUST be called before using m_fn.
  502. void WX_FILENAME::resolve()
  503. {
  504. size_t dot = m_fullName.find_last_of( wxT( '.' ) );
  505. m_fn.SetName( m_fullName.substr( 0, dot ) );
  506. m_fn.SetExt( m_fullName.substr( dot + 1 ) );
  507. }
  508. long long WX_FILENAME::GetTimestamp()
  509. {
  510. resolve();
  511. if( m_fn.FileExists() )
  512. return m_fn.GetModificationTime().GetValue().GetValue();
  513. return 0;
  514. }
  515. /**
  516. * A copy of wxMatchWild(), which wxWidgets attributes to Douglas A. Lewis
  517. * <dalewis@cs.Buffalo.EDU> and ircII's reg.c.
  518. *
  519. * This version is modified to skip any encoding conversions (for performance).
  520. */
  521. bool matchWild( const char* pat, const char* text, bool dot_special )
  522. {
  523. if( !*text )
  524. {
  525. /* Match if both are empty. */
  526. return !*pat;
  527. }
  528. const char *m = pat,
  529. *n = text,
  530. *ma = NULL,
  531. *na = NULL;
  532. int just = 0,
  533. acount = 0,
  534. count = 0;
  535. if( dot_special && (*n == '.') )
  536. {
  537. /* Never match so that hidden Unix files
  538. * are never found. */
  539. return false;
  540. }
  541. for(;;)
  542. {
  543. if( *m == '*' )
  544. {
  545. ma = ++m;
  546. na = n;
  547. just = 1;
  548. acount = count;
  549. }
  550. else if( *m == '?' )
  551. {
  552. m++;
  553. if( !*n++ )
  554. return false;
  555. }
  556. else
  557. {
  558. if( *m == '\\' )
  559. {
  560. m++;
  561. /* Quoting "nothing" is a bad thing */
  562. if( !*m )
  563. return false;
  564. }
  565. if( !*m )
  566. {
  567. /*
  568. * If we are out of both strings or we just
  569. * saw a wildcard, then we can say we have a
  570. * match
  571. */
  572. if( !*n )
  573. return true;
  574. if( just )
  575. return true;
  576. just = 0;
  577. goto not_matched;
  578. }
  579. /*
  580. * We could check for *n == NULL at this point, but
  581. * since it's more common to have a character there,
  582. * check to see if they match first (m and n) and
  583. * then if they don't match, THEN we can check for
  584. * the NULL of n
  585. */
  586. just = 0;
  587. if( *m == *n )
  588. {
  589. m++;
  590. count++;
  591. n++;
  592. }
  593. else
  594. {
  595. not_matched:
  596. /*
  597. * If there are no more characters in the
  598. * string, but we still need to find another
  599. * character (*m != NULL), then it will be
  600. * impossible to match it
  601. */
  602. if( !*n )
  603. return false;
  604. if( ma )
  605. {
  606. m = ma;
  607. n = ++na;
  608. count = acount;
  609. }
  610. else
  611. return false;
  612. }
  613. }
  614. }
  615. }
  616. /**
  617. * A copy of ConvertFileTimeToWx() because wxWidgets left it as a static function
  618. * private to src/common/filename.cpp.
  619. */
  620. #if wxUSE_DATETIME && defined(__WIN32__) && !defined(__WXMICROWIN__)
  621. // Convert between wxDateTime and FILETIME which is a 64-bit value representing
  622. // the number of 100-nanosecond intervals since January 1, 1601 UTC.
  623. //
  624. // This is the offset between FILETIME epoch and the Unix/wxDateTime Epoch.
  625. static wxInt64 EPOCH_OFFSET_IN_MSEC = wxLL(11644473600000);
  626. static void ConvertFileTimeToWx( wxDateTime *dt, const FILETIME &ft )
  627. {
  628. wxLongLong t( ft.dwHighDateTime, ft.dwLowDateTime );
  629. t /= 10000; // Convert hundreds of nanoseconds to milliseconds.
  630. t -= EPOCH_OFFSET_IN_MSEC;
  631. *dt = wxDateTime( t );
  632. }
  633. #endif // wxUSE_DATETIME && __WIN32__
  634. /**
  635. * TimestampDir
  636. *
  637. * This routine offers SIGNIFICANT performance benefits over using wxWidgets to gather
  638. * timestamps from matching files in a directory.
  639. * @param aDirPath the directory to search
  640. * @param aFilespec a (wildcarded) file spec to match against
  641. * @return a hash of the last-mod-dates of all matching files in the directory
  642. */
  643. long long TimestampDir( const wxString& aDirPath, const wxString& aFilespec )
  644. {
  645. long long timestamp = 0;
  646. #if defined( __WIN32__ )
  647. // Win32 version.
  648. // Save time by not searching for each path twice: once in wxDir.GetNext() and once in
  649. // wxFileName.GetModificationTime(). Also cuts out wxWidgets' string-matching and case
  650. // conversion by staying on the MSW side of things.
  651. std::wstring filespec( aDirPath.t_str() );
  652. filespec += '\\';
  653. filespec += aFilespec.t_str();
  654. WIN32_FIND_DATA findData;
  655. wxDateTime lastModDate;
  656. HANDLE fileHandle = ::FindFirstFile( filespec.data(), &findData );
  657. if( fileHandle != INVALID_HANDLE_VALUE )
  658. {
  659. do
  660. {
  661. ConvertFileTimeToWx( &lastModDate, findData.ftLastWriteTime );
  662. timestamp += lastModDate.GetValue().GetValue();
  663. }
  664. while ( FindNextFile( fileHandle, &findData ) != 0 );
  665. }
  666. FindClose( fileHandle );
  667. #else
  668. // POSIX version.
  669. // Save time by not converting between encodings -- do everything on the file-system side.
  670. std::string filespec( aFilespec.fn_str() );
  671. std::string dir_path( aDirPath.fn_str() );
  672. DIR* dir = opendir( dir_path.c_str() );
  673. if( dir )
  674. {
  675. for( dirent* dir_entry = readdir( dir ); dir_entry; dir_entry = readdir( dir ) )
  676. {
  677. if( !matchWild( filespec.c_str(), dir_entry->d_name, true ) )
  678. continue;
  679. std::string entry_path = dir_path + '/' + dir_entry->d_name;
  680. struct stat entry_stat;
  681. if( wxCRT_Lstat( entry_path.c_str(), &entry_stat ) == 0 )
  682. {
  683. // Timestamp the source file, not the symlink
  684. if( S_ISLNK( entry_stat.st_mode ) ) // wxFILE_EXISTS_SYMLINK
  685. {
  686. char buffer[ PATH_MAX + 1 ];
  687. ssize_t pathLen = readlink( entry_path.c_str(), buffer, PATH_MAX );
  688. if( pathLen > 0 )
  689. {
  690. struct stat linked_stat;
  691. buffer[ pathLen ] = '\0';
  692. entry_path = dir_path + buffer;
  693. if( wxCRT_Lstat( entry_path.c_str(), &linked_stat ) == 0 )
  694. {
  695. entry_stat = linked_stat;
  696. }
  697. else
  698. {
  699. // if we couldn't lstat the linked file we'll have to just use
  700. // the symbolic link info
  701. }
  702. }
  703. }
  704. if( S_ISREG( entry_stat.st_mode ) ) // wxFileExists()
  705. timestamp += entry_stat.st_mtime * 1000;
  706. }
  707. else
  708. {
  709. // if we couldn't lstat the file itself all we can do is use the name
  710. timestamp += (signed) std::hash<std::string>{}( std::string( dir_entry->d_name ) );
  711. }
  712. }
  713. closedir( dir );
  714. }
  715. #endif
  716. return timestamp;
  717. }