PageRenderTime 493ms CodeModel.GetById 60ms app.highlight 362ms RepoModel.GetById 22ms app.codeStats 2ms

/src/libtomahawk/utils/TomahawkUtils.cpp

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