/src/libtomahawk/utils/TomahawkUtils.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 991 lines · 733 code · 189 blank · 69 comment · 107 complexity · 199e672e6069f10cade376cc750cab66 MD5 · raw file

  1. /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2. *
  3. * Copyright 2010-2014, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4. * Copyright 2010-2011, Leo Franchi <lfranchi@kde.org>
  5. * Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
  6. * Copyright 2013, Teo Mrnjavac <teo@kde.org>
  7. *
  8. * Tomahawk is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * Tomahawk 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 Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. #include "utils/TomahawkUtils.h"
  22. #include "config.h"
  23. #include "BinaryExtractWorker.h"
  24. #include "Query.h"
  25. #include "SharedTimeLine.h"
  26. #include "Source.h"
  27. #include "TomahawkSettings.h"
  28. #include "TomahawkVersion.h"
  29. #include "Track.h"
  30. #ifdef LIBLASTFM_FOUND
  31. #include <lastfm5/ws.h>
  32. #endif
  33. #include <quazip5/quazip.h>
  34. #include <quazip5/quazipfile.h>
  35. // We need this for the version info (if available)
  36. #include <taglib/taglib.h>
  37. #include <QNetworkConfiguration>
  38. #include <QNetworkAccessManager>
  39. #include <QNetworkProxy>
  40. #include <QCoreApplication>
  41. #include <QDateTime>
  42. #include <QDir>
  43. #include <QMutex>
  44. #include <QCryptographicHash>
  45. #include <QProcess>
  46. #include <QStringList>
  47. #include <QTranslator>
  48. #include <QUrlQuery>
  49. #ifdef Q_OS_WIN
  50. #include <windows.h>
  51. #include <shlobj.h>
  52. #endif
  53. #ifdef Q_OS_MAC
  54. #include <Carbon/Carbon.h>
  55. #include <sys/sysctl.h>
  56. #endif
  57. #ifdef QCA2_FOUND
  58. #include <QtCrypto>
  59. #endif
  60. #include "Logger.h"
  61. namespace TomahawkUtils
  62. {
  63. static quint64 s_infosystemRequestId = 0;
  64. static QMutex s_infosystemRequestIdMutex;
  65. static bool s_headless = false;
  66. #ifdef Q_OS_MAC
  67. QString
  68. appSupportFolderPath()
  69. {
  70. // honestly, it is *always* this --mxcl
  71. return QDir::home().filePath( "Library/Application Support" );
  72. }
  73. #endif // Q_OS_MAC
  74. bool
  75. headless()
  76. {
  77. return s_headless;
  78. }
  79. void
  80. setHeadless( bool headless )
  81. {
  82. tLog() << Q_FUNC_INFO << "headless is" << (headless? "true" : "false");
  83. s_headless = headless;
  84. }
  85. QString
  86. appFriendlyVersion()
  87. {
  88. QStringList l = QString( TOMAHAWK_VERSION ).split( ".", QString::SkipEmptyParts );
  89. while ( l.count() > 3 )
  90. l.removeLast();
  91. return l.join( "." );
  92. }
  93. QDir
  94. appConfigDir()
  95. {
  96. QDir ret;
  97. #ifdef Q_OS_MAC
  98. if ( getenv( "HOME" ) )
  99. {
  100. return QDir( QString( "%1" ).arg( getenv( "HOME" ) ) );
  101. }
  102. else
  103. {
  104. tDebug() << "Error, $HOME not set.";
  105. throw "$HOME not set";
  106. return QDir( "/tmp" );
  107. }
  108. #elif defined(Q_OS_WIN)
  109. throw "TODO";
  110. return QDir( "c:\\" ); //TODO refer to Qt documentation to get code to do this
  111. #else
  112. if ( getenv( "XDG_CONFIG_HOME" ) )
  113. {
  114. ret = QDir( QString( "%1/" TOMAHAWK_APPLICATION_NAME ).arg( getenv( "XDG_CONFIG_HOME" ) ) );
  115. }
  116. else if ( getenv( "HOME" ) )
  117. {
  118. ret = QDir( QString( "%1/.config/" TOMAHAWK_APPLICATION_NAME ).arg( getenv( "HOME" ) ) );
  119. }
  120. else
  121. {
  122. tDebug() << "Error, $HOME or $XDG_CONFIG_HOME not set.";
  123. throw "Error, $HOME or $XDG_CONFIG_HOME not set.";
  124. ret = QDir( "/tmp" );
  125. }
  126. if ( !ret.exists() )
  127. {
  128. ret.mkpath( ret.canonicalPath() );
  129. }
  130. return ret;
  131. #endif
  132. }
  133. QDir
  134. appDataDir()
  135. {
  136. QString path;
  137. #ifdef Q_OS_WIN
  138. if ( ( QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based ) == 0 )
  139. {
  140. // Use this for non-DOS-based Windowses
  141. wchar_t acPath[MAX_PATH];
  142. HRESULT h = SHGetFolderPathW( NULL, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE,
  143. NULL, 0, acPath );
  144. if ( h == S_OK )
  145. {
  146. path = QString::fromUtf16( (ushort*)acPath );
  147. }
  148. }
  149. #elif defined(Q_OS_MAC)
  150. path = appSupportFolderPath();
  151. #elif defined(Q_OS_LINUX)
  152. path = QDir::home().filePath( ".local/share" );
  153. #else
  154. path = QCoreApplication::applicationDirPath();
  155. #endif
  156. path += "/" + QCoreApplication::organizationName();
  157. QDir d( path );
  158. d.mkpath( path );
  159. return d;
  160. }
  161. QDir
  162. appLogDir()
  163. {
  164. #ifndef Q_OS_MAC
  165. return appDataDir();
  166. #else
  167. return QDir( QDir::homePath() + "/Library/Logs" );
  168. #endif
  169. }
  170. const QString
  171. logFilePath()
  172. {
  173. return TomahawkUtils::appLogDir().filePath( TOMAHAWK_APPLICATION_NAME ".log" );
  174. }
  175. QString
  176. timeToString( int seconds )
  177. {
  178. int hrs = seconds / 60 / 60;
  179. int mins = seconds / 60 % 60;
  180. int secs = seconds % 60;
  181. if ( seconds < 0 )
  182. {
  183. hrs = mins = secs = 0;
  184. }
  185. return QString( "%1%2:%3" ).arg( hrs > 0 ? hrs < 10 ? "0" + QString::number( hrs ) + ":" : QString::number( hrs ) + ":" : "" )
  186. .arg( mins < 10 ? "0" + QString::number( mins ) : QString::number( mins ) )
  187. .arg( secs < 10 ? "0" + QString::number( secs ) : QString::number( secs ) );
  188. }
  189. QString
  190. ageToString( const QDateTime& time, bool appendAgoString )
  191. {
  192. if ( time.toTime_t() == 0 )
  193. return QString();
  194. QDateTime now = QDateTime::currentDateTime();
  195. int mins = time.secsTo( now ) / 60;
  196. int hours = mins / 60;
  197. int days = time.daysTo( now );
  198. int weeks = days / 7;
  199. int months = days / 30.42;
  200. int years = months / 12;
  201. if ( mins > 0 )
  202. {
  203. if ( years )
  204. {
  205. if ( appendAgoString )
  206. return QObject::tr( "%n year(s) ago", "", years );
  207. else
  208. return QObject::tr( "%n year(s)", "", years );
  209. }
  210. if ( months )
  211. {
  212. if ( appendAgoString )
  213. return QObject::tr( "%n month(s) ago", "", months );
  214. else
  215. return QObject::tr( "%n month(s)", "", months );
  216. }
  217. if ( weeks )
  218. {
  219. if ( appendAgoString )
  220. return QObject::tr( "%n week(s) ago", "", weeks );
  221. else
  222. return QObject::tr( "%n week(s)", "", weeks );
  223. }
  224. if ( days )
  225. {
  226. if ( appendAgoString )
  227. return QObject::tr( "%n day(s) ago", "", days );
  228. else if ( hours >= 24 )
  229. return QObject::tr( "%n day(s)", "", days );
  230. }
  231. if ( hours )
  232. {
  233. if ( appendAgoString )
  234. return QObject::tr( "%n hour(s) ago", "", hours );
  235. else
  236. return QObject::tr( "%n hour(s)", "", hours );
  237. }
  238. if ( mins > 1 )
  239. {
  240. if ( appendAgoString )
  241. return QObject::tr( "%1 minutes ago" ).arg( mins );
  242. else
  243. return QObject::tr( "%1 minutes" ).arg( mins );
  244. }
  245. }
  246. return QObject::tr( "just now" );
  247. }
  248. QString
  249. filesizeToString( unsigned int size )
  250. {
  251. if ( size == 0 )
  252. return QString();
  253. int kb = size / 1024;
  254. int mb = kb / 1024;
  255. if ( mb )
  256. {
  257. return QString( "%1.%2 MB" ).arg( mb ).arg( int( ( kb % 1024 ) / 102.4 ) );
  258. }
  259. else if ( kb )
  260. {
  261. return QString( "%1 KB" ).arg( kb );
  262. }
  263. else
  264. return QString::number( size );
  265. }
  266. QStringList
  267. supportedExtensions()
  268. {
  269. //TODO supportedExtensions() and extensionToMimetype could share a QMap
  270. //TODO and this method should just return map.keys()
  271. static QStringList s_extensions;
  272. if ( s_extensions.isEmpty() )
  273. {
  274. s_extensions << "mp3"
  275. << "ogg" << "oga"
  276. << "mpc"
  277. << "wma"
  278. << "aac" << "m4a" << "mp4"
  279. << "flac"
  280. << "aiff" << "aif"
  281. << "wv";
  282. #if defined(TAGLIB_MAJOR_VERSION) && defined(TAGLIB_MINOR_VERSION)
  283. #if TAGLIB_MAJOR_VERSION >= 1 && TAGLIB_MINOR_VERSION >= 9
  284. s_extensions << "opus";
  285. #endif
  286. #endif
  287. }
  288. return s_extensions;
  289. }
  290. QString
  291. extensionToMimetype( const QString& extension )
  292. {
  293. static QMap<QString, QString> s_ext2mime;
  294. if ( s_ext2mime.isEmpty() )
  295. {
  296. s_ext2mime.insert( "mp3", "audio/mpeg" );
  297. s_ext2mime.insert( "ogg", "application/ogg" );
  298. s_ext2mime.insert( "oga", "application/ogg" );
  299. #if defined(TAGLIB_MAJOR_VERSION) && defined(TAGLIB_MINOR_VERSION)
  300. #if TAGLIB_MAJOR_VERSION >= 1 && TAGLIB_MINOR_VERSION >= 9
  301. s_ext2mime.insert( "opus", "application/opus" );
  302. #endif
  303. #endif
  304. s_ext2mime.insert( "mpc", "audio/x-musepack" );
  305. s_ext2mime.insert( "wma", "audio/x-ms-wma" );
  306. s_ext2mime.insert( "aac", "audio/mp4" );
  307. s_ext2mime.insert( "m4a", "audio/mp4" );
  308. s_ext2mime.insert( "mp4", "audio/mp4" );
  309. s_ext2mime.insert( "flac", "audio/flac" );
  310. s_ext2mime.insert( "aiff", "audio/aiff" );
  311. s_ext2mime.insert( "aif", "audio/aiff" );
  312. s_ext2mime.insert( "wv", "audio/x-wavpack" );
  313. }
  314. return s_ext2mime.value( extension.toLower(), "unknown" );
  315. }
  316. void
  317. msleep( unsigned int ms )
  318. {
  319. #ifdef WIN32
  320. Sleep( ms );
  321. #else
  322. ::usleep( ms * 1000 );
  323. #endif
  324. }
  325. int
  326. levenshtein( const QString& source, const QString& target )
  327. {
  328. // Step 1
  329. const int n = source.length();
  330. const int m = target.length();
  331. if ( n == 0 )
  332. return m;
  333. if ( m == 0 )
  334. return n;
  335. // Good form to declare a TYPEDEF
  336. typedef QVector< QVector<int> > Tmatrix;
  337. Tmatrix matrix;
  338. matrix.resize( n + 1 );
  339. // Size the vectors in the 2.nd dimension. Unfortunately C++ doesn't
  340. // allow for allocation on declaration of 2.nd dimension of vec of vec
  341. for ( int i = 0; i <= n; i++ )
  342. {
  343. QVector<int> tmp;
  344. tmp.resize( m + 1 );
  345. matrix.insert( i, tmp );
  346. }
  347. // Step 2
  348. for ( int i = 0; i <= n; i++ )
  349. matrix[i][0] = i;
  350. for ( int j = 0; j <= m; j++ )
  351. matrix[0][j] = j;
  352. // Step 3
  353. for ( int i = 1; i <= n; i++ )
  354. {
  355. const QChar s_i = source[i - 1];
  356. // Step 4
  357. for ( int j = 1; j <= m; j++ )
  358. {
  359. const QChar t_j = target[j - 1];
  360. // Step 5
  361. int cost;
  362. if ( s_i == t_j )
  363. cost = 0;
  364. else
  365. cost = 1;
  366. // Step 6
  367. const int above = matrix[i - 1][j];
  368. const int left = matrix[i][j - 1];
  369. const int diag = matrix[i - 1][j - 1];
  370. int cell = ( ( ( left + 1 ) > ( diag + cost ) ) ? diag + cost : left + 1 );
  371. if ( above + 1 < cell )
  372. cell = above + 1;
  373. // Step 6A: Cover transposition, in addition to deletion,
  374. // insertion and substitution. This step is taken from:
  375. // Berghel, Hal ; Roach, David : "An Extension of Ukkonen's
  376. // Enhanced Dynamic Programming ASM Algorithm"
  377. // (http://www.acm.org/~hlb/publications/asm/asm.html)
  378. if ( i > 2 && j > 2 )
  379. {
  380. int trans = matrix[i - 2][j - 2] + 1;
  381. if ( source[ i - 2 ] != t_j ) trans++;
  382. if ( s_i != target[ j - 2 ] ) trans++;
  383. if ( cell > trans ) cell = trans;
  384. }
  385. matrix[i][j] = cell;
  386. }
  387. }
  388. // Step 7
  389. return matrix[n][m];
  390. }
  391. bool
  392. newerVersion( const QString& oldVersion, const QString& newVersion )
  393. {
  394. if ( oldVersion.isEmpty() || newVersion.isEmpty() )
  395. return false;
  396. QStringList oldVList = oldVersion.split( ".", QString::SkipEmptyParts );
  397. QStringList newVList = newVersion.split( ".", QString::SkipEmptyParts );
  398. int i = 0;
  399. foreach ( const QString& nvPart, newVList )
  400. {
  401. if ( i + 1 > oldVList.count() )
  402. return true;
  403. int nviPart = nvPart.toInt();
  404. int oviPart = oldVList.at( i++ ).toInt();
  405. if ( nviPart > oviPart )
  406. return true;
  407. if ( nviPart < oviPart )
  408. return false;
  409. }
  410. return false;
  411. }
  412. QList< Tomahawk::query_ptr >
  413. mergePlaylistChanges( const QList< Tomahawk::query_ptr >& orig, const QList< Tomahawk::query_ptr >& newTracks, bool& changed )
  414. {
  415. int sameCount = 0;
  416. QList< Tomahawk::query_ptr > tosave = newTracks;
  417. changed = false;
  418. foreach ( const Tomahawk::query_ptr& newquery, newTracks )
  419. {
  420. foreach ( const Tomahawk::query_ptr& oldq, orig )
  421. {
  422. if ( newquery->queryTrack()->track() == oldq->queryTrack()->track() &&
  423. newquery->queryTrack()->artist() == oldq->queryTrack()->artist() &&
  424. newquery->queryTrack()->album() == oldq->queryTrack()->album() )
  425. {
  426. sameCount++;
  427. if ( tosave.contains( newquery ) )
  428. tosave.replace( tosave.indexOf( newquery ), oldq );
  429. break;
  430. }
  431. }
  432. }
  433. // No work to be done if all are the same
  434. if ( orig.size() == newTracks.size() && sameCount == orig.size() )
  435. return orig;
  436. changed = true;
  437. return tosave;
  438. }
  439. // taken from util/fileutils.cpp in kdevplatform
  440. bool
  441. removeDirectory( const QString& dir )
  442. {
  443. return QDir( dir ).removeRecursively();
  444. }
  445. quint64
  446. infosystemRequestId()
  447. {
  448. QMutexLocker locker( &s_infosystemRequestIdMutex );
  449. quint64 result = s_infosystemRequestId;
  450. s_infosystemRequestId++;
  451. return result;
  452. }
  453. QString
  454. md5( const QByteArray& data )
  455. {
  456. QByteArray const digest = QCryptographicHash::hash( data, QCryptographicHash::Md5 );
  457. return QString::fromLatin1( digest.toHex() ).rightJustified( 32, '0' );
  458. }
  459. bool
  460. isHttpResult( const QString& url )
  461. {
  462. return url.startsWith( "http://" ); // || url.startsWith( "https://" );
  463. }
  464. bool
  465. isHttpsResult( const QString& url )
  466. {
  467. return url.startsWith( "https://" );
  468. }
  469. bool
  470. isLocalResult( const QString& url )
  471. {
  472. return url.startsWith( "file://" );
  473. }
  474. bool
  475. isRtmpResult( const QString& url )
  476. {
  477. return url.startsWith( "rtmp://" );
  478. }
  479. void
  480. crash()
  481. {
  482. volatile int* a = (int*)(NULL);
  483. *a = 1;
  484. }
  485. const QString
  486. operatingSystemVersionDetail()
  487. {
  488. #ifdef Q_OS_LINUX
  489. return QSettings( "/etc/os-release", QSettings::IniFormat ).value( "PRETTY_NAME", "Linux" ).toString();
  490. #elif defined ( Q_OS_WIN )
  491. QString version( "Windows" );
  492. OSVERSIONINFOEX osvi;
  493. BOOL bOsVersionInfoEx;
  494. ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
  495. osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
  496. bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO*) &osvi);
  497. if(bOsVersionInfoEx == 0)
  498. return version;
  499. version.append( QString( " %1.%2" ).arg( osvi.dwMajorVersion ).arg( osvi.dwMinorVersion ) );
  500. return version;
  501. #elif defined ( Q_OS_MAC )
  502. return "OS X";
  503. #else
  504. return "Unknown";
  505. #endif
  506. }
  507. const QString
  508. userAgentString( const QString& applicationName, const QString& applicationVersion )
  509. {
  510. return QString( "%1/%2 (%3)" )
  511. .arg( applicationName )
  512. .arg( applicationVersion )
  513. .arg( operatingSystemVersionDetail() );
  514. }
  515. class TomahawkTranslator : public QTranslator
  516. {
  517. public:
  518. TomahawkTranslator( QObject* parent ) : QTranslator( parent )
  519. {
  520. };
  521. QString translate( const char * context, const char * sourceText, const char * disambiguation = 0, int n = -1) const Q_DECL_OVERRIDE
  522. {
  523. QString translation = QTranslator::translate( context, sourceText, disambiguation, n );
  524. if( translation.isEmpty() )
  525. {
  526. translation = QString::fromUtf8( sourceText );
  527. }
  528. // // lowercase all strings not on whats new page ...
  529. // // TODO: rather scan disambiguation for nolowercase, but too lazy for that right now
  530. // if( strcmp( context, "WhatsNewWidget_0_8" ) )
  531. // {
  532. // translation = translation.toLower();
  533. // }
  534. return translation.replace( "%applicationName", TOMAHAWK_APPLICATION_NAME, Qt::CaseInsensitive );
  535. }
  536. };
  537. void
  538. installTranslator( QObject* parent )
  539. {
  540. QString locale = QLocale::system().uiLanguages().first().replace( "-", "_" );
  541. if ( locale == "C" )
  542. locale = "en";
  543. // Tomahawk translations
  544. QTranslator* translator = new TomahawkTranslator( parent );
  545. if ( translator->load( QString( ":/lang/tomahawk_" ) + locale ) )
  546. {
  547. qDebug() << "Translation: Tomahawk: Using system locale:" << locale;
  548. }
  549. else
  550. {
  551. qDebug() << "Translation: Tomahawk: Using default locale, system locale one not found:" << locale;
  552. translator->load( QString( ":/lang/tomahawk_en" ) );
  553. }
  554. QCoreApplication::installTranslator( translator );
  555. // Qt translations
  556. translator = new QTranslator( parent );
  557. if ( translator->load( QString( ":/lang/qt_" ) + locale ) )
  558. {
  559. qDebug() << "Translation: Qt: Using system locale:" << locale;
  560. }
  561. else
  562. {
  563. qDebug() << "Translation: Qt: Using default locale, system locale one not found:" << locale;
  564. }
  565. QCoreApplication::installTranslator( translator );
  566. }
  567. bool
  568. verifyFile( const QString& filePath, const QString& signature )
  569. {
  570. QCA::Initializer init;
  571. if ( !QCA::isSupported( "sha1" ) )
  572. {
  573. qWarning() << "SHA1 not supported by QCA, aborting.";
  574. return false;
  575. }
  576. // The signature for the resolver.zip was created like so:
  577. // openssl dgst -sha1 -binary < "#{tarball}" | openssl dgst -dss1 -sign "#{ARGV[2]}" | openssl enc -base64
  578. // which means we need to decode it with QCA's DSA public key signature verification tools
  579. // The input data is:
  580. // file -> SHA1 binary format -> DSS1/DSA signed -> base64 encoded.
  581. // Step 1: Load the public key
  582. // Public key is in :/data/misc/tomahawk_pubkey.pem
  583. QFile f( ":/data/misc/tomahawk_pubkey.pem" );
  584. if ( !f.open( QIODevice::ReadOnly ) )
  585. {
  586. qWarning() << "Unable to read public key from resources!";
  587. return false;
  588. }
  589. const QString pubkeyData = QString::fromUtf8( f.readAll() );
  590. QCA::ConvertResult conversionResult;
  591. QCA::PublicKey publicKey = QCA::PublicKey::fromPEM( pubkeyData, &conversionResult );
  592. if ( QCA::ConvertGood != conversionResult)
  593. {
  594. qWarning() << "Public key reading/loading failed! Tried to load public key:" << pubkeyData;
  595. return false;
  596. }
  597. if ( !publicKey.canVerify() )
  598. {
  599. qWarning() << "Loaded Tomahawk public key but cannot use it to verify! What is up....";
  600. return false;
  601. }
  602. // Step 2: Get the SHA1 of the file contents
  603. QFile toVerify( filePath );
  604. if ( !toVerify.exists() || !toVerify.open( QIODevice::ReadOnly ) )
  605. {
  606. qWarning() << "Failed to open file we are trying to verify!" << filePath;
  607. return false;
  608. }
  609. const QByteArray fileHashData = QCA::Hash( "sha1" ).hash( toVerify.readAll() ).toByteArray();
  610. toVerify.close();
  611. // Step 3: Base64 decode the signature
  612. QCA::Base64 decoder( QCA::Decode );
  613. const QByteArray decodedSignature = decoder.decode( QCA::SecureArray( signature.trimmed().toUtf8() ) ).toByteArray();
  614. if ( decodedSignature.isEmpty() )
  615. {
  616. qWarning() << "Got empty signature after we tried to decode it from Base64:" << signature.trimmed().toUtf8() << decodedSignature.toBase64();
  617. return false;
  618. }
  619. // Step 4: Do the actual verifying!
  620. const bool result = publicKey.verifyMessage( fileHashData, decodedSignature, QCA::EMSA1_SHA1, QCA::DERSequence );
  621. if ( !result )
  622. {
  623. qWarning() << "File" << filePath << "FAILED VERIFICATION against our input signature!";
  624. return false;
  625. }
  626. tDebug( LOGVERBOSE ) << "Successfully verified signature of downloaded file:" << filePath;
  627. return true;
  628. }
  629. QString
  630. extractScriptPayload( const QString& filename, const QString& resolverId, const QString& dirName )
  631. {
  632. // uses QuaZip to extract the temporary zip file to the user's tomahawk data/resolvers directory
  633. QDir resolverDir = appDataDir();
  634. if ( !resolverDir.mkpath( QString( "%1/%2" ).arg( dirName )
  635. .arg( resolverId ) ) )
  636. {
  637. tLog() << "Failed to mkdir resolver save dir:" << TomahawkUtils::appDataDir().absoluteFilePath( QString( "%1/%2" ).arg( dirName ).arg( resolverId ) );
  638. return QString();
  639. }
  640. resolverDir.cd( QString( "%1/%2" ).arg( dirName ).arg( resolverId ) );
  641. if ( !unzipFileInFolder( filename, resolverDir ) )
  642. {
  643. qWarning() << "Failed to unzip resolver. Ooops.";
  644. return QString();
  645. }
  646. return resolverDir.absolutePath();
  647. }
  648. bool
  649. unzipFileInFolder( const QString& zipFileName, const QDir& folder )
  650. {
  651. Q_ASSERT( !zipFileName.isEmpty() );
  652. Q_ASSERT( folder.exists() );
  653. QuaZip zipFile( zipFileName );
  654. if ( !zipFile.open( QuaZip::mdUnzip ) )
  655. {
  656. qWarning() << "Failed to QuaZip open" << zipFileName
  657. << "with error:" << zipFile.getZipError();
  658. return false;
  659. }
  660. if ( !zipFile.goToFirstFile() )
  661. {
  662. tLog() << "Failed to go to first file in zip archive:" << zipFile.getZipError();
  663. return false;
  664. }
  665. tDebug( LOGVERBOSE ) << "Unzipping files to:" << folder.absolutePath();
  666. QuaZipFile fileInZip( &zipFile );
  667. do
  668. {
  669. QuaZipFileInfo info;
  670. zipFile.getCurrentFileInfo( &info );
  671. if ( !fileInZip.open( QIODevice::ReadOnly ) )
  672. {
  673. tLog() << "Failed to open file inside zip archive:" << info.name << zipFile.getZipName() << "with error:" << zipFile.getZipError();
  674. continue;
  675. }
  676. QFile out( folder.absoluteFilePath( fileInZip.getActualFileName() ) );
  677. // make dir if there is one needed
  678. QStringList parts = fileInZip.getActualFileName().split( "/" );
  679. if ( parts.size() > 1 )
  680. {
  681. QStringList dirs = parts.mid( 0, parts.size() - 1 );
  682. QString dirPath = dirs.join( "/" ); // QDir translates / to \ internally if necessary
  683. folder.mkpath( dirPath );
  684. }
  685. tDebug( LOGVERBOSE ) << "Writing to output file..." << out.fileName();
  686. if ( !out.open( QIODevice::WriteOnly ) )
  687. {
  688. tLog() << "Failed to open zip extract file:" << out.errorString() << info.name;
  689. fileInZip.close();
  690. continue;
  691. }
  692. out.write( fileInZip.readAll() );
  693. out.close();
  694. fileInZip.close();
  695. } while ( zipFile.goToNextFile() );
  696. return true;
  697. }
  698. void
  699. extractBinaryResolver( const QString& zipFilename, QObject* receiver )
  700. {
  701. BinaryExtractWorker* worker = new BinaryExtractWorker( zipFilename, receiver );
  702. worker->start( QThread::LowPriority );
  703. }
  704. bool
  705. whitelistedHttpResultHint( const QUrl& url )
  706. {
  707. // For now, just http/https
  708. return ( url.scheme().startsWith( "http" ) && !url.host().endsWith( "youtube.com" ) );
  709. }
  710. int
  711. compareVersionStrings( const QString& first, const QString& second )
  712. {
  713. QStringList a = first.split( '.', QString::SkipEmptyParts );
  714. QStringList b = second.split( '.', QString::SkipEmptyParts );
  715. const int depth = qMax( a.count(), b.count() );
  716. while ( a.count() < depth )
  717. a.append( "0" );
  718. while ( b.count() < depth )
  719. b.append( "0" );
  720. int verdict = 0;
  721. for ( int i = 0; i < depth; ++i )
  722. {
  723. bool aOk;
  724. int aNumber = a.at( i ).toInt( &aOk );
  725. bool bOk;
  726. int bNumber = b.at( i ).toInt( &bOk );
  727. if ( aOk && bOk )
  728. {
  729. if ( aNumber < bNumber )
  730. {
  731. verdict = -1;
  732. break;
  733. }
  734. if ( aNumber > bNumber )
  735. {
  736. verdict = 1;
  737. break;
  738. }
  739. }
  740. else //fallback: string comparison
  741. {
  742. verdict = a.at( i ).compare( b.at( i ) );
  743. if ( verdict != 0 )
  744. break;
  745. }
  746. }
  747. return verdict;
  748. }
  749. void
  750. urlAddQueryItem( QUrl& url, const QString& key, const QString& value )
  751. {
  752. QUrlQuery urlQuery( url );
  753. urlQuery.addQueryItem( key, value );
  754. url.setQuery( urlQuery );
  755. }
  756. QString
  757. urlQueryItemValue( const QUrl& url, const QString& key )
  758. {
  759. return QUrlQuery( url ).queryItemValue( key ).replace( "+", " " );
  760. }
  761. bool
  762. urlHasQueryItem( const QUrl& url, const QString& key )
  763. {
  764. return QUrlQuery( url ).hasQueryItem( key );
  765. }
  766. QList<QPair<QString, QString> >
  767. urlQueryItems( const QUrl& url )
  768. {
  769. return QUrlQuery( url ).queryItems();
  770. }
  771. void
  772. urlSetQuery( QUrl& url, const QString& query )
  773. {
  774. url.setQuery( query );
  775. }
  776. QByteArray
  777. percentEncode( const QUrl& url )
  778. {
  779. //NOTE: this function does not exhaustively replace things that QUrl
  780. //sometimes misses, however adding those in this function (like in
  781. //encodedQuery()) causes some things like toma.hk link generation to
  782. //fail, so leave them out here
  783. QByteArray data = url.toEncoded();
  784. // QUrl doesn't encode ', which it doesn't have to. Some apps don't like ' though, and want %27. Both are valid.
  785. data.replace( "'", "%27" );
  786. data.replace( "%20", "+" );
  787. return data;
  788. }
  789. QByteArray
  790. encodedQuery( const QUrl& url )
  791. {
  792. QByteArray data;
  793. data = url.query(QUrl::FullyEncoded).toUtf8();
  794. // QUrl doesn't encode : or ; which it should, as well as some other things, so be safer here in general.
  795. data.replace( "'", "%27" );
  796. data.replace( ".", "%2E" );
  797. data.replace( "*", "%2A" );
  798. data.replace( ":", "%3A" );
  799. data.replace( ";", "%3B" );
  800. return data;
  801. }
  802. } // ns