2018-02-27 18:06:05 +01:00
/*
* Strawberry Music Player
* This file was part of Clementine .
* Copyright 2010 , David Sansome < me @ davidsansome . com >
2019-09-23 19:17:41 +02:00
* Copyright 2018 - 2019 , Jonas Kvinge < jonas @ jkvinge . net >
2018-02-27 18:06:05 +01:00
*
* Strawberry is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* Strawberry is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with Strawberry . If not , see < http : //www.gnu.org/licenses/>.
2018-08-09 18:39:44 +02:00
*
2018-02-27 18:06:05 +01:00
*/
# include "config.h"
# include <memory>
2020-06-14 23:54:18 +02:00
# include <cstdlib>
2019-07-14 03:08:19 +02:00
# include <iconv.h>
2018-02-27 18:06:05 +01:00
2018-05-01 00:41:33 +02:00
# include <QtGlobal>
2018-02-27 18:06:05 +01:00
# include <QApplication>
2018-05-01 00:41:33 +02:00
# include <QCoreApplication>
# include <QWidget>
# include <QObject>
# include <QIODevice>
# include <QByteArray>
# include <QMetaObject>
# include <QChar>
# include <QCryptographicHash>
# include <QDate>
2018-02-27 18:06:05 +01:00
# include <QDateTime>
# include <QDesktopServices>
# include <QDir>
# include <QFile>
2018-05-01 00:41:33 +02:00
# include <QFileInfo>
2020-03-15 01:21:30 +01:00
# include <QProcess>
2018-05-01 00:41:33 +02:00
# include <QHostAddress>
# include <QSet>
2020-02-08 03:40:30 +01:00
# include <QList>
# include <QMap>
# include <QVariant>
2018-05-01 00:41:33 +02:00
# include <QString>
2018-02-27 18:06:05 +01:00
# include <QStringList>
2020-02-08 03:40:30 +01:00
# include <QUrl>
2018-05-01 00:41:33 +02:00
# include <QRegExp>
2018-02-27 18:06:05 +01:00
# include <QTcpServer>
# include <QTemporaryFile>
2020-02-08 03:40:30 +01:00
# include <QPoint>
# include <QRect>
# include <QSize>
2020-04-27 00:22:46 +02:00
# include <QColor>
2018-05-01 00:41:33 +02:00
# include <QMetaEnum>
2018-02-27 18:06:05 +01:00
# include <QXmlStreamReader>
2018-05-01 00:41:33 +02:00
# include <QSettings>
# include <QtEvents>
2019-06-20 17:00:10 +02:00
# include <QMessageBox>
2019-09-22 17:04:57 +02:00
# include <QNetworkInterface>
2018-05-01 00:41:33 +02:00
# include <QtDebug>
2020-05-29 17:37:46 +02:00
# if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
# include <QRandomGenerator>
# endif
2018-02-27 18:06:05 +01:00
2020-06-14 23:54:18 +02:00
# include <cstdio>
2020-02-08 03:40:30 +01:00
2018-02-27 18:06:05 +01:00
# ifdef Q_OS_LINUX
2018-07-01 22:26:46 +02:00
# include <unistd.h>
# include <sys / syscall.h>
2018-02-27 18:06:05 +01:00
# endif
2018-07-01 22:26:46 +02:00
# ifdef Q_OS_MACOS
# include <sys / resource.h>
# include <sys / sysctl.h>
# include <sys / param.h>
2018-02-27 18:06:05 +01:00
# endif
# if defined(Q_OS_UNIX)
2018-07-01 22:26:46 +02:00
# include <sys / statvfs.h>
# elif defined(Q_OS_WIN)
# include <windows.h>
2018-02-27 18:06:05 +01:00
# endif
2018-07-01 22:26:46 +02:00
# ifdef Q_OS_MACOS
# include "CoreServices / CoreServices.h"
# include "IOKit / ps / IOPSKeys.h"
# include "IOKit / ps / IOPowerSources.h"
2018-02-27 18:06:05 +01:00
# endif
# include "core/logging.h"
2019-12-22 12:09:05 +01:00
# include "core/song.h"
2018-05-01 00:41:33 +02:00
# include "utilities.h"
# include "timeconstants.h"
# include "application.h"
2018-02-27 18:06:05 +01:00
2018-07-01 22:26:46 +02:00
# ifdef Q_OS_MACOS
# include "mac_startup.h"
# include "mac_utilities.h"
# include "scoped_cftyperef.h"
2018-02-27 18:06:05 +01:00
# endif
namespace Utilities {
static QString tr ( const char * str ) {
return QCoreApplication : : translate ( " " , str ) ;
}
QString PrettyTimeDelta ( int seconds ) {
return ( seconds > = 0 ? " + " : " - " ) + PrettyTime ( seconds ) ;
}
QString PrettyTime ( int seconds ) {
2018-04-06 22:13:11 +02:00
// last.fm sometimes gets the track length wrong, so you end up with negative times.
2018-02-27 18:06:05 +01:00
seconds = qAbs ( seconds ) ;
int hours = seconds / ( 60 * 60 ) ;
int minutes = ( seconds / 60 ) % 60 ;
seconds % = 60 ;
QString ret ;
2019-12-21 21:56:48 +01:00
if ( hours ) ret = QString : : asprintf ( " %d:%02d:%02d " , hours , minutes , seconds ) ;
else ret = QString : : asprintf ( " %d:%02d " , minutes , seconds ) ;
2018-02-27 18:06:05 +01:00
return ret ;
}
QString PrettyTimeNanosec ( qint64 nanoseconds ) {
return PrettyTime ( nanoseconds / kNsecPerSec ) ;
}
QString WordyTime ( quint64 seconds ) {
quint64 days = seconds / ( 60 * 60 * 24 ) ;
// TODO: Make the plural rules translatable
QStringList parts ;
if ( days ) parts < < ( days = = 1 ? tr ( " 1 day " ) : tr ( " %1 days " ) . arg ( days ) ) ;
parts < < PrettyTime ( seconds - days * 60 * 60 * 24 ) ;
return parts . join ( " " ) ;
}
QString WordyTimeNanosec ( qint64 nanoseconds ) {
return WordyTime ( nanoseconds / kNsecPerSec ) ;
}
QString Ago ( int seconds_since_epoch , const QLocale & locale ) {
const QDateTime now = QDateTime : : currentDateTime ( ) ;
const QDateTime then = QDateTime : : fromTime_t ( seconds_since_epoch ) ;
const int days_ago = then . date ( ) . daysTo ( now . date ( ) ) ;
const QString time = then . time ( ) . toString ( locale . timeFormat ( QLocale : : ShortFormat ) ) ;
if ( days_ago = = 0 ) return tr ( " Today " ) + " " + time ;
if ( days_ago = = 1 ) return tr ( " Yesterday " ) + " " + time ;
if ( days_ago < = 7 ) return tr ( " %1 days ago " ) . arg ( days_ago ) ;
return then . date ( ) . toString ( locale . dateFormat ( QLocale : : ShortFormat ) ) ;
}
QString PrettyFutureDate ( const QDate & date ) {
const QDate now = QDate : : currentDate ( ) ;
const int delta_days = now . daysTo ( date ) ;
if ( delta_days < 0 ) return QString ( ) ;
if ( delta_days = = 0 ) return tr ( " Today " ) ;
if ( delta_days = = 1 ) return tr ( " Tomorrow " ) ;
if ( delta_days < = 7 ) return tr ( " In %1 days " ) . arg ( delta_days ) ;
if ( delta_days < = 14 ) return tr ( " Next week " ) ;
return tr ( " In %1 weeks " ) . arg ( delta_days / 7 ) ;
}
QString PrettySize ( quint64 bytes ) {
QString ret ;
if ( bytes > 0 ) {
if ( bytes < = 1000 )
ret = QString : : number ( bytes ) + " bytes " ;
else if ( bytes < = 1000 * 1000 )
2019-12-21 21:56:48 +01:00
ret = QString : : asprintf ( " %.1f KB " , float ( bytes ) / 1000 ) ;
2018-02-27 18:06:05 +01:00
else if ( bytes < = 1000 * 1000 * 1000 )
2019-12-21 21:56:48 +01:00
ret = QString : : asprintf ( " %.1f MB " , float ( bytes ) / ( 1000 * 1000 ) ) ;
2018-02-27 18:06:05 +01:00
else
2019-12-21 21:56:48 +01:00
ret = QString : : asprintf ( " %.1f GB " , float ( bytes ) / ( 1000 * 1000 * 1000 ) ) ;
2018-02-27 18:06:05 +01:00
}
return ret ;
}
quint64 FileSystemCapacity ( const QString & path ) {
# if defined(Q_OS_UNIX)
struct statvfs fs_info ;
if ( statvfs ( path . toLocal8Bit ( ) . constData ( ) , & fs_info ) = = 0 )
return quint64 ( fs_info . f_blocks ) * quint64 ( fs_info . f_bsize ) ;
# elif defined(Q_OS_WIN32)
_ULARGE_INTEGER ret ;
2018-04-06 22:13:11 +02:00
if ( GetDiskFreeSpaceEx ( QDir : : toNativeSeparators ( path ) . toLocal8Bit ( ) . constData ( ) , nullptr , & ret , nullptr ) ! = 0 )
2018-02-27 18:06:05 +01:00
return ret . QuadPart ;
# endif
return 0 ;
}
quint64 FileSystemFreeSpace ( const QString & path ) {
# if defined(Q_OS_UNIX)
struct statvfs fs_info ;
if ( statvfs ( path . toLocal8Bit ( ) . constData ( ) , & fs_info ) = = 0 )
return quint64 ( fs_info . f_bavail ) * quint64 ( fs_info . f_bsize ) ;
# elif defined(Q_OS_WIN32)
_ULARGE_INTEGER ret ;
2018-04-06 22:13:11 +02:00
if ( GetDiskFreeSpaceEx ( QDir : : toNativeSeparators ( path ) . toLocal8Bit ( ) . constData ( ) , & ret , nullptr , nullptr ) ! = 0 )
2018-02-27 18:06:05 +01:00
return ret . QuadPart ;
# endif
return 0 ;
}
QString MakeTempDir ( const QString template_name ) {
QString path ;
{
QTemporaryFile tempfile ;
if ( ! template_name . isEmpty ( ) ) tempfile . setFileTemplate ( template_name ) ;
tempfile . open ( ) ;
path = tempfile . fileName ( ) ;
}
QDir d ;
d . mkdir ( path ) ;
return path ;
}
bool RemoveRecursive ( const QString & path ) {
QDir dir ( path ) ;
for ( const QString & child : dir . entryList ( QDir : : NoDotAndDotDot | QDir : : Dirs | QDir : : Hidden ) ) {
if ( ! RemoveRecursive ( path + " / " + child ) )
return false ;
}
for ( const QString & child : dir . entryList ( QDir : : NoDotAndDotDot | QDir : : Files | QDir : : Hidden ) ) {
if ( ! QFile : : remove ( path + " / " + child ) )
return false ;
}
2019-04-08 18:46:11 +02:00
return dir . rmdir ( path ) ;
2018-02-27 18:06:05 +01:00
}
bool CopyRecursive ( const QString & source , const QString & destination ) {
2018-10-02 00:38:52 +02:00
2018-02-27 18:06:05 +01:00
// Make the destination directory
QString dir_name = source . section ( ' / ' , - 1 , - 1 ) ;
QString dest_path = destination + " / " + dir_name ;
QDir ( ) . mkpath ( dest_path ) ;
QDir dir ( source ) ;
for ( const QString & child : dir . entryList ( QDir : : NoDotAndDotDot | QDir : : Dirs ) ) {
if ( ! CopyRecursive ( source + " / " + child , dest_path ) ) {
qLog ( Warning ) < < " Failed to copy dir " < < source + " / " + child < < " to " < < dest_path ;
return false ;
}
}
for ( const QString & child : dir . entryList ( QDir : : NoDotAndDotDot | QDir : : Files ) ) {
if ( ! QFile : : copy ( source + " / " + child , dest_path + " / " + child ) ) {
qLog ( Warning ) < < " Failed to copy file " < < source + " / " + child < < " to " < < dest_path ;
return false ;
}
}
return true ;
}
bool Copy ( QIODevice * source , QIODevice * destination ) {
2018-10-02 00:38:52 +02:00
2018-02-27 18:06:05 +01:00
if ( ! source - > open ( QIODevice : : ReadOnly ) ) return false ;
if ( ! destination - > open ( QIODevice : : WriteOnly ) ) return false ;
const qint64 bytes = source - > size ( ) ;
std : : unique_ptr < char [ ] > data ( new char [ bytes ] ) ;
qint64 pos = 0 ;
qint64 bytes_read ;
do {
bytes_read = source - > read ( data . get ( ) + pos , bytes - pos ) ;
if ( bytes_read = = - 1 ) return false ;
pos + = bytes_read ;
} while ( bytes_read > 0 & & pos ! = bytes ) ;
pos = 0 ;
qint64 bytes_written ;
do {
bytes_written = destination - > write ( data . get ( ) + pos , bytes - pos ) ;
if ( bytes_written = = - 1 ) return false ;
pos + = bytes_written ;
} while ( bytes_written > 0 & & pos ! = bytes ) ;
return true ;
}
QString ColorToRgba ( const QColor & c ) {
return QString ( " rgba(%1, %2, %3, %4) " )
. arg ( c . red ( ) )
. arg ( c . green ( ) )
. arg ( c . blue ( ) )
. arg ( c . alpha ( ) ) ;
}
2020-03-15 01:21:30 +01:00
# if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
2020-04-23 21:08:28 +02:00
void OpenInFileManager ( const QString & path ) ;
2020-03-15 01:21:30 +01:00
void OpenInFileManager ( const QString & path ) {
QProcess proc ;
proc . start ( " xdg-mime " , QStringList ( ) < < " query " < < " default " < < " inode/directory " ) ;
proc . waitForFinished ( ) ;
QString desktop_file = proc . readLine ( ) . simplified ( ) ;
QStringList data_dirs = QString ( getenv ( " XDG_DATA_DIRS " ) ) . split ( " : " ) ;
QString command ;
QStringList command_params ;
for ( const QString & data_dir : data_dirs ) {
QString desktop_file_path = QString ( " %1/applications/%2 " ) . arg ( data_dir , desktop_file ) ;
if ( ! QFile : : exists ( desktop_file_path ) ) continue ;
QSettings setting ( desktop_file_path , QSettings : : IniFormat ) ;
setting . beginGroup ( " Desktop Entry " ) ;
if ( setting . contains ( " Exec " ) ) {
QString cmd = setting . value ( " Exec " ) . toString ( ) ;
if ( cmd . isEmpty ( ) ) break ;
command_params = cmd . split ( ' ' ) ;
command = command_params . first ( ) ;
command_params . removeFirst ( ) ;
}
setting . endGroup ( ) ;
if ( ! command . isEmpty ( ) ) break ;
}
if ( command_params . contains ( " %u " ) ) {
command_params . removeAt ( command_params . indexOf ( " %u " ) ) ;
}
if ( command_params . contains ( " %U " ) ) {
command_params . removeAt ( command_params . indexOf ( " %U " ) ) ;
}
2020-05-14 22:19:26 +02:00
if ( command . startsWith ( " /usr/bin/ " ) ) {
command = command . split ( " / " ) . last ( ) ;
}
2020-03-15 01:21:30 +01:00
if ( command . isEmpty ( ) | | command = = " exo-open " ) {
QFileInfo info ( path ) ;
if ( ! info . exists ( ) ) return ;
QString directory = info . dir ( ) . path ( ) ;
if ( directory . isEmpty ( ) ) return ;
QDesktopServices : : openUrl ( QUrl : : fromLocalFile ( directory ) ) ;
}
else if ( command . startsWith ( " nautilus " ) ) {
proc . startDetached ( command , QStringList ( ) < < command_params < < " --select " < < path ) ;
}
else if ( command . startsWith ( " dolphin " ) | | command . startsWith ( " konqueror " ) | | command . startsWith ( " kfmclient " ) ) {
proc . startDetached ( command , QStringList ( ) < < command_params < < " --select " < < " --new-window " < < path ) ;
}
else if ( command . startsWith ( " caja " ) ) {
2020-04-06 04:33:06 +02:00
QFileInfo info ( path ) ;
if ( ! info . exists ( ) ) return ;
QString directory = info . dir ( ) . path ( ) ;
proc . startDetached ( command , QStringList ( ) < < command_params < < " --no-desktop " < < directory ) ;
2020-03-15 01:21:30 +01:00
}
else {
proc . startDetached ( command , QStringList ( ) < < command_params < < path ) ;
}
}
# endif
2018-07-01 22:26:46 +02:00
# ifdef Q_OS_MACOS
2018-02-27 18:06:05 +01:00
// Better than openUrl(dirname(path)) - also highlights file at path
void RevealFileInFinder ( QString const & path ) {
QProcess : : execute ( " /usr/bin/open " , QStringList ( ) < < " -R " < < path ) ;
}
2018-07-01 22:26:46 +02:00
# endif // Q_OS_MACOS
2018-02-27 18:06:05 +01:00
# ifdef Q_OS_WIN
2020-04-23 21:50:50 +02:00
void ShowFileInExplorer ( QString const & path ) ;
2018-02-27 18:06:05 +01:00
void ShowFileInExplorer ( QString const & path ) {
QProcess : : execute ( " explorer.exe " , QStringList ( ) < < " /select, " < < QDir : : toNativeSeparators ( path ) ) ;
}
# endif
void OpenInFileBrowser ( const QList < QUrl > & urls ) {
2019-06-20 17:00:10 +02:00
if ( urls . count ( ) > 50 ) {
2019-06-20 17:02:29 +02:00
QMessageBox messagebox ( QMessageBox : : Critical , tr ( " Show in file browser " ) , tr ( " Too many songs selected. " ) ) ;
2019-06-20 17:00:10 +02:00
messagebox . exec ( ) ;
return ;
}
if ( urls . count ( ) > 5 ) {
QMessageBox messagebox ( QMessageBox : : Information , tr ( " Show in file browser " ) , tr ( " %1 songs selected, are you sure you want to open them all? " ) . arg ( urls . count ( ) ) , QMessageBox : : Open | QMessageBox : : Cancel ) ;
messagebox . setTextFormat ( Qt : : RichText ) ;
int result = messagebox . exec ( ) ;
switch ( result ) {
case QMessageBox : : Open :
break ;
case QMessageBox : : Cancel :
default :
return ;
}
}
2018-02-27 18:06:05 +01:00
QSet < QString > dirs ;
for ( const QUrl & url : urls ) {
2019-07-09 21:43:56 +02:00
if ( ! url . isLocalFile ( ) ) {
2018-02-27 18:06:05 +01:00
continue ;
}
QString path = url . toLocalFile ( ) ;
if ( ! QFile : : exists ( path ) ) continue ;
const QString directory = QFileInfo ( path ) . dir ( ) . path ( ) ;
if ( dirs . contains ( directory ) ) continue ;
dirs . insert ( directory ) ;
2020-03-15 01:21:30 +01:00
# if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
OpenInFileManager ( path ) ;
2020-03-15 02:00:13 +01:00
# elif defined(Q_OS_MACOS)
2018-05-01 00:41:33 +02:00
// Revealing multiple files in the finder only opens one window, so it also makes sense to reveal at most one per directory
2018-02-27 18:06:05 +01:00
RevealFileInFinder ( path ) ;
# elif defined(Q_OS_WIN32)
ShowFileInExplorer ( path ) ;
# endif
}
}
2020-06-14 18:58:24 +02:00
QByteArray Hmac ( const QByteArray & key , const QByteArray & data , const HashFunction method ) {
2018-02-27 18:06:05 +01:00
const int kBlockSize = 64 ; // bytes
Q_ASSERT ( key . length ( ) < = kBlockSize ) ;
QByteArray inner_padding ( kBlockSize , char ( 0x36 ) ) ;
QByteArray outer_padding ( kBlockSize , char ( 0x5c ) ) ;
for ( int i = 0 ; i < key . length ( ) ; + + i ) {
inner_padding [ i ] = inner_padding [ i ] ^ key [ i ] ;
outer_padding [ i ] = outer_padding [ i ] ^ key [ i ] ;
}
if ( Md5_Algo = = method ) {
return QCryptographicHash : : hash ( outer_padding + QCryptographicHash : : hash ( inner_padding + data , QCryptographicHash : : Md5 ) , QCryptographicHash : : Md5 ) ;
}
else if ( Sha1_Algo = = method ) {
2018-05-01 00:41:33 +02:00
return QCryptographicHash : : hash ( outer_padding + QCryptographicHash : : hash ( inner_padding + data , QCryptographicHash : : Sha1 ) , QCryptographicHash : : Sha1 ) ;
2018-02-27 18:06:05 +01:00
}
else { // Sha256_Algo, currently default
2018-12-02 19:12:27 +01:00
return QCryptographicHash : : hash ( outer_padding + QCryptographicHash : : hash ( inner_padding + data , QCryptographicHash : : Sha256 ) , QCryptographicHash : : Sha256 ) ;
2018-02-27 18:06:05 +01:00
}
}
QByteArray HmacSha256 ( const QByteArray & key , const QByteArray & data ) {
return Hmac ( key , data , Sha256_Algo ) ;
}
QByteArray HmacMd5 ( const QByteArray & key , const QByteArray & data ) {
return Hmac ( key , data , Md5_Algo ) ;
}
QByteArray HmacSha1 ( const QByteArray & key , const QByteArray & data ) {
return Hmac ( key , data , Sha1_Algo ) ;
}
// File must not be open and will be closed afterwards!
QByteArray Sha1File ( QFile & file ) {
file . open ( QIODevice : : ReadOnly ) ;
QCryptographicHash hash ( QCryptographicHash : : Sha1 ) ;
QByteArray data ;
while ( ! file . atEnd ( ) ) {
data = file . read ( 1000000 ) ; // 1 mib
hash . addData ( data . data ( ) , data . length ( ) ) ;
data . clear ( ) ;
}
file . close ( ) ;
return hash . result ( ) ;
}
QByteArray Sha1CoverHash ( const QString & artist , const QString & album ) {
QCryptographicHash hash ( QCryptographicHash : : Sha1 ) ;
hash . addData ( artist . toLower ( ) . toUtf8 ( ) . constData ( ) ) ;
hash . addData ( album . toLower ( ) . toUtf8 ( ) . constData ( ) ) ;
2018-10-02 00:38:52 +02:00
2018-02-27 18:06:05 +01:00
return hash . result ( ) ;
}
QString PrettySize ( const QSize & size ) {
return QString : : number ( size . width ( ) ) + " x " + QString : : number ( size . height ( ) ) ;
}
void ForwardMouseEvent ( const QMouseEvent * e , QWidget * target ) {
QMouseEvent c ( e - > type ( ) , target - > mapFromGlobal ( e - > globalPos ( ) ) , e - > globalPos ( ) , e - > button ( ) , e - > buttons ( ) , e - > modifiers ( ) ) ;
QApplication : : sendEvent ( target , & c ) ;
}
bool IsMouseEventInWidget ( const QMouseEvent * e , const QWidget * widget ) {
return widget - > rect ( ) . contains ( widget - > mapFromGlobal ( e - > globalPos ( ) ) ) ;
}
quint16 PickUnusedPort ( ) {
forever {
2020-05-29 17:37:46 +02:00
# if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
const quint64 port = QRandomGenerator : : global ( ) - > bounded ( 49152 , 65535 ) ;
# else
2018-02-27 18:06:05 +01:00
const quint16 port = 49152 + qrand ( ) % 16384 ;
2020-05-29 17:37:46 +02:00
# endif
2018-02-27 18:06:05 +01:00
QTcpServer server ;
if ( server . listen ( QHostAddress : : Any , port ) ) {
return port ;
}
}
}
void ConsumeCurrentElement ( QXmlStreamReader * reader ) {
int level = 1 ;
while ( level ! = 0 & & ! reader - > atEnd ( ) ) {
switch ( reader - > readNext ( ) ) {
case QXmlStreamReader : : StartElement : + + level ; break ;
case QXmlStreamReader : : EndElement : - - level ; break ;
default : break ;
}
}
}
bool ParseUntilElement ( QXmlStreamReader * reader , const QString & name ) {
while ( ! reader - > atEnd ( ) ) {
QXmlStreamReader : : TokenType type = reader - > readNext ( ) ;
switch ( type ) {
case QXmlStreamReader : : StartElement :
if ( reader - > name ( ) = = name ) {
return true ;
}
break ;
default :
break ;
}
}
return false ;
}
2019-11-20 19:34:57 +01:00
bool ParseUntilElementCI ( QXmlStreamReader * reader , const QString & name ) {
while ( ! reader - > atEnd ( ) ) {
QXmlStreamReader : : TokenType type = reader - > readNext ( ) ;
switch ( type ) {
case QXmlStreamReader : : StartElement : {
QString element = reader - > name ( ) . toString ( ) . toLower ( ) ;
if ( element = = name ) {
return true ;
}
break ;
}
default :
break ;
}
}
return false ;
}
2018-02-27 18:06:05 +01:00
QDateTime ParseRFC822DateTime ( const QString & text ) {
2018-05-01 00:41:33 +02:00
QRegExp regexp ( " ( \\ d{1,2}) ( \ \ w { 3 , 12 } ) ( \ \ d + ) ( \ \ d { 1 , 2 } ) : ( \ \ d { 1 , 2 } ) : ( \ \ d { 1 , 2 } ) " );
2018-02-27 18:06:05 +01:00
if ( regexp . indexIn ( text ) = = - 1 ) {
return QDateTime ( ) ;
}
enum class MatchNames { DAYS = 1 , MONTHS , YEARS , HOURS , MINUTES , SECONDS } ;
QMap < QString , int > monthmap ;
monthmap [ " Jan " ] = 1 ;
monthmap [ " Feb " ] = 2 ;
monthmap [ " Mar " ] = 3 ;
monthmap [ " Apr " ] = 4 ;
monthmap [ " May " ] = 5 ;
monthmap [ " Jun " ] = 6 ;
monthmap [ " Jul " ] = 7 ;
monthmap [ " Aug " ] = 8 ;
monthmap [ " Sep " ] = 9 ;
monthmap [ " Oct " ] = 10 ;
monthmap [ " Nov " ] = 11 ;
monthmap [ " Dec " ] = 12 ;
monthmap [ " January " ] = 1 ;
monthmap [ " February " ] = 2 ;
monthmap [ " March " ] = 3 ;
monthmap [ " April " ] = 4 ;
monthmap [ " May " ] = 5 ;
monthmap [ " June " ] = 6 ;
monthmap [ " July " ] = 7 ;
monthmap [ " August " ] = 8 ;
monthmap [ " September " ] = 9 ;
monthmap [ " October " ] = 10 ;
monthmap [ " November " ] = 11 ;
monthmap [ " December " ] = 12 ;
const QDate date ( regexp . cap ( static_cast < int > ( MatchNames : : YEARS ) ) . toInt ( ) , monthmap [ regexp . cap ( static_cast < int > ( MatchNames : : MONTHS ) ) ] , regexp . cap ( static_cast < int > ( MatchNames : : DAYS ) ) . toInt ( ) ) ;
const QTime time ( regexp . cap ( static_cast < int > ( MatchNames : : HOURS ) ) . toInt ( ) , regexp . cap ( static_cast < int > ( MatchNames : : MINUTES ) ) . toInt ( ) , regexp . cap ( static_cast < int > ( MatchNames : : SECONDS ) ) . toInt ( ) ) ;
return QDateTime ( date , time ) ;
}
const char * EnumToString ( const QMetaObject & meta , const char * name , int value ) {
int index = meta . indexOfEnumerator ( name ) ;
if ( index = = - 1 ) return " [UnknownEnum] " ;
QMetaEnum metaenum = meta . enumerator ( index ) ;
const char * result = metaenum . valueToKey ( value ) ;
2020-06-14 17:02:47 +02:00
if ( ! result ) return " [UnknownEnumValue] " ;
2018-02-27 18:06:05 +01:00
return result ;
}
QStringList Prepend ( const QString & text , const QStringList & list ) {
QStringList ret ( list ) ;
for ( int i = 0 ; i < ret . count ( ) ; + + i ) ret [ i ] . prepend ( text ) ;
return ret ;
}
QStringList Updateify ( const QStringList & list ) {
QStringList ret ( list ) ;
for ( int i = 0 ; i < ret . count ( ) ; + + i ) ret [ i ] . prepend ( ret [ i ] + " = : " ) ;
return ret ;
}
QString DecodeHtmlEntities ( const QString & text ) {
QString copy ( text ) ;
copy . replace ( " & " , " & " ) ;
copy . replace ( " " " , " \" " ) ;
copy . replace ( " ' " , " ' " ) ;
copy . replace ( " < " , " < " ) ;
copy . replace ( " > " , " > " ) ;
return copy ;
}
int SetThreadIOPriority ( IoPriority priority ) {
# ifdef Q_OS_LINUX
2018-05-01 00:41:33 +02:00
return syscall ( SYS_ioprio_set , IOPRIO_WHO_PROCESS , GetThreadId ( ) , 4 | priority < < IOPRIO_CLASS_SHIFT ) ;
2018-07-01 22:26:46 +02:00
# elif defined(Q_OS_MACOS)
2018-05-01 00:41:33 +02:00
return setpriority ( PRIO_DARWIN_THREAD , 0 , priority = = IOPRIO_CLASS_IDLE ? PRIO_DARWIN_BG : 0 ) ;
2018-02-27 18:06:05 +01:00
# else
2019-09-19 17:44:14 +02:00
Q_UNUSED ( priority ) ;
2018-02-27 18:06:05 +01:00
return 0 ;
# endif
}
int GetThreadId ( ) {
# ifdef Q_OS_LINUX
return syscall ( SYS_gettid ) ;
# else
return 0 ;
# endif
}
bool IsLaptop ( ) {
# ifdef Q_OS_WIN
SYSTEM_POWER_STATUS status ;
if ( ! GetSystemPowerStatus ( & status ) ) {
return false ;
}
return ! ( status . BatteryFlag & 128 ) ; // 128 = no system battery
# elif defined(Q_OS_LINUX)
return ! QDir ( " /proc/acpi/battery " ) . entryList ( QDir : : Dirs | QDir : : NoDotAndDotDot ) . isEmpty ( ) ;
2018-06-28 01:15:32 +02:00
# elif defined(Q_OS_MACOS)
2018-02-27 18:06:05 +01:00
ScopedCFTypeRef < CFTypeRef > power_sources ( IOPSCopyPowerSourcesInfo ( ) ) ;
ScopedCFTypeRef < CFArrayRef > power_source_list ( IOPSCopyPowerSourcesList ( power_sources . get ( ) ) ) ;
for ( CFIndex i = 0 ; i < CFArrayGetCount ( power_source_list . get ( ) ) ; + + i ) {
CFTypeRef ps = CFArrayGetValueAtIndex ( power_source_list . get ( ) , i ) ;
CFDictionaryRef description = IOPSGetPowerSourceDescription ( power_sources . get ( ) , ps ) ;
if ( CFDictionaryContainsKey ( description , CFSTR ( kIOPSBatteryHealthKey ) ) ) {
return true ;
}
}
return false ;
# else
return false ;
# endif
}
bool UrlOnSameDriveAsStrawberry ( const QUrl & url ) {
2019-07-09 21:43:56 +02:00
if ( ! url . isValid ( ) | | ! url . isLocalFile ( ) | | url . toLocalFile ( ) . isEmpty ( ) ) return false ;
2018-02-27 18:06:05 +01:00
# ifdef Q_OS_WIN
QUrl appUrl = QUrl : : fromLocalFile ( QCoreApplication : : applicationDirPath ( ) ) ;
if ( url . toLocalFile ( ) . left ( 1 ) = = appUrl . toLocalFile ( ) . left ( 1 ) )
return true ;
else
return false ;
# else
// Non windows systems have always a / in the path
return true ;
# endif
}
QUrl GetRelativePathToStrawberryBin ( const QUrl & url ) {
2019-03-10 21:09:05 +01:00
if ( ! url . isValid ( ) ) return QUrl ( ) ;
2018-02-27 18:06:05 +01:00
QDir appPath ( QCoreApplication : : applicationDirPath ( ) ) ;
return QUrl : : fromLocalFile ( appPath . relativeFilePath ( url . toLocalFile ( ) ) ) ;
}
QString PathWithoutFilenameExtension ( const QString & filename ) {
2018-09-22 15:37:42 +02:00
if ( filename . section ( ' / ' , - 1 , - 1 ) . contains ( ' . ' ) ) return filename . section ( ' . ' , 0 , - 2 ) ;
2018-02-27 18:06:05 +01:00
return filename ;
}
QString FiddleFileExtension ( const QString & filename , const QString & new_extension ) {
return PathWithoutFilenameExtension ( filename ) + " . " + new_extension ;
}
2019-01-25 21:36:28 +01:00
QString GetEnv ( const QString & key ) {
return QString : : fromLocal8Bit ( qgetenv ( key . toLocal8Bit ( ) ) ) ;
}
2018-02-27 18:06:05 +01:00
void SetEnv ( const char * key , const QString & value ) {
# ifdef Q_OS_WIN32
putenv ( QString ( " %1=%2 " ) . arg ( key , value ) . toLocal8Bit ( ) . constData ( ) ) ;
# else
setenv ( key , value . toLocal8Bit ( ) . constData ( ) , 1 ) ;
# endif
}
void IncreaseFDLimit ( ) {
2018-07-01 22:26:46 +02:00
# ifdef Q_OS_MACOS
2018-04-06 22:13:11 +02:00
// Bump the soft limit for the number of file descriptors from the default of 256 to the maximum (usually 10240).
2018-02-27 18:06:05 +01:00
struct rlimit limit ;
getrlimit ( RLIMIT_NOFILE , & limit ) ;
// getrlimit() lies about the hard limit so we have to check sysctl.
int max_fd = 0 ;
size_t len = sizeof ( max_fd ) ;
sysctlbyname ( " kern.maxfilesperproc " , & max_fd , & len , nullptr , 0 ) ;
limit . rlim_cur = max_fd ;
int ret = setrlimit ( RLIMIT_NOFILE , & limit ) ;
if ( ret = = 0 ) {
qLog ( Debug ) < < " Max fd: " < < max_fd ;
}
# endif
}
2018-09-22 15:37:42 +02:00
QString GetRandomStringWithChars ( const int len ) {
const QString UseCharacters ( " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz " ) ;
return GetRandomString ( len , UseCharacters ) ;
}
QString GetRandomStringWithCharsAndNumbers ( const int len ) {
const QString UseCharacters ( " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 " ) ;
return GetRandomString ( len , UseCharacters ) ;
}
2019-06-10 02:29:57 +02:00
QString CryptographicRandomString ( const int len ) {
const QString UseCharacters ( " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~ " ) ;
return GetRandomString ( len , UseCharacters ) ;
}
2018-09-22 15:37:42 +02:00
QString GetRandomString ( const int len , const QString & UseCharacters ) {
QString randstr ;
2020-05-29 17:37:46 +02:00
for ( int i = 0 ; i < len ; + + i ) {
# if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
const int index = QRandomGenerator : : global ( ) - > bounded ( 0 , UseCharacters . length ( ) ) ;
# else
const int index = qrand ( ) % UseCharacters . length ( ) ;
# endif
2018-09-22 15:37:42 +02:00
QChar nextchar = UseCharacters . at ( index ) ;
randstr . append ( nextchar ) ;
}
return randstr ;
}
2019-01-25 21:36:28 +01:00
QString DesktopEnvironment ( ) {
const QString de = GetEnv ( " XDG_CURRENT_DESKTOP " ) ;
if ( ! de . isEmpty ( ) ) return de ;
if ( ! qEnvironmentVariableIsEmpty ( " KDE_FULL_SESSION " ) ) return " KDE " ;
if ( ! qEnvironmentVariableIsEmpty ( " GNOME_DESKTOP_SESSION_ID " ) ) return " Gnome " ;
QString session = GetEnv ( " DESKTOP_SESSION " ) ;
int slash = session . lastIndexOf ( ' / ' ) ;
if ( slash ! = - 1 ) {
QSettings desktop_file ( QString ( session + " .desktop " ) , QSettings : : IniFormat ) ;
desktop_file . beginGroup ( " Desktop Entry " ) ;
QString name = desktop_file . value ( " DesktopNames " ) . toString ( ) ;
desktop_file . endGroup ( ) ;
if ( ! name . isEmpty ( ) ) return name ;
session = session . mid ( slash + 1 ) ;
}
if ( session = = " kde " ) return " KDE " ;
else if ( session = = " gnome " ) return " Gnome " ;
else if ( session = = " xfce " ) return " XFCE " ;
return " Unknown " ;
}
2019-07-14 03:08:19 +02:00
QString UnicodeToAscii ( const QString & unicode ) {
2019-08-26 01:37:22 +02:00
# ifdef LC_ALL
2019-07-14 03:08:19 +02:00
setlocale ( LC_ALL , " " ) ;
2019-08-26 01:37:22 +02:00
# endif
2019-07-14 03:08:19 +02:00
iconv_t conv = iconv_open ( " ASCII//TRANSLIT " , " UTF-8 " ) ;
2020-02-22 17:39:06 +01:00
if ( conv = = ( iconv_t ) - 1 ) return unicode ;
2019-07-14 03:08:19 +02:00
QByteArray utf8 = unicode . toUtf8 ( ) ;
2019-09-15 20:27:32 +02:00
2019-07-14 03:08:19 +02:00
size_t input_len = utf8 . length ( ) + 1 ;
2020-02-22 17:39:06 +01:00
char * input_ptr = new char [ input_len ] ;
char * input = input_ptr ;
2019-07-14 03:08:19 +02:00
2020-02-22 17:39:06 +01:00
size_t output_len = input_len * 2 ;
char * output_ptr = new char [ output_len ] ;
char * output = output_ptr ;
2019-07-14 03:08:19 +02:00
2019-09-15 20:27:32 +02:00
snprintf ( input , input_len , " %s " , utf8 . constData ( ) ) ;
2019-07-14 03:08:19 +02:00
2019-09-15 20:27:32 +02:00
iconv ( conv , & input , & input_len , & output , & output_len ) ;
2019-07-14 03:08:19 +02:00
iconv_close ( conv ) ;
2019-09-15 20:27:32 +02:00
QString ret ( output_ptr ) ;
delete [ ] input_ptr ;
delete [ ] output_ptr ;
return ret ;
2019-07-14 03:08:19 +02:00
}
2019-09-22 17:04:57 +02:00
QString MacAddress ( ) {
QString ret ;
2019-09-23 00:34:29 +02:00
for ( QNetworkInterface & netif : QNetworkInterface : : allInterfaces ( ) ) {
2019-09-22 17:04:57 +02:00
if (
2019-09-23 00:34:29 +02:00
( netif . hardwareAddress ( ) = = " 00:00:00:00:00:00 " ) | |
( netif . flags ( ) & QNetworkInterface : : IsLoopBack ) | |
! ( netif . flags ( ) & QNetworkInterface : : IsUp ) | |
! ( netif . flags ( ) & QNetworkInterface : : IsRunning )
2019-09-22 17:04:57 +02:00
) { continue ; }
2019-09-22 22:43:54 +02:00
if ( ret . isEmpty ( )
# if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
2019-09-23 00:34:29 +02:00
| | netif . type ( ) = = QNetworkInterface : : Ethernet | | netif . type ( ) = = QNetworkInterface : : Wifi
2019-09-22 22:43:54 +02:00
# endif
) {
2019-09-23 00:34:29 +02:00
ret = netif . hardwareAddress ( ) ;
2019-09-22 17:04:57 +02:00
}
}
if ( ret . isEmpty ( ) ) ret = " 00:00:00:00:00:00 " ;
return ret ;
}
2019-12-22 12:09:05 +01:00
QString ReplaceMessage ( const QString & message , const Song & song , const QString & newline ) {
QRegExp variable_replacer ( " [%][a-z]+[%] " ) ;
QString copy ( message ) ;
// Replace the first line
int pos = 0 ;
variable_replacer . indexIn ( message ) ;
while ( ( pos = variable_replacer . indexIn ( message , pos ) ) ! = - 1 ) {
QStringList captured = variable_replacer . capturedTexts ( ) ;
copy . replace ( captured [ 0 ] , ReplaceVariable ( captured [ 0 ] , song , newline ) ) ;
pos + = variable_replacer . matchedLength ( ) ;
}
2020-05-15 22:15:52 +02:00
int index_of = copy . indexOf ( QRegExp ( " - (>|$) " ) ) ;
if ( index_of > = 0 ) copy = copy . remove ( index_of , 3 ) ;
2019-12-22 12:09:05 +01:00
return copy ;
}
QString ReplaceVariable ( const QString & variable , const Song & song , const QString & newline ) {
QString return_value ;
if ( variable = = " %artist% " ) {
return song . artist ( ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %album% " ) {
return song . album ( ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %title% " ) {
return song . PrettyTitle ( ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %albumartist% " ) {
return song . effective_albumartist ( ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %year% " ) {
return song . PrettyYear ( ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %composer% " ) {
return song . composer ( ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %performer% " ) {
return song . performer ( ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %grouping% " ) {
return song . grouping ( ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %length% " ) {
return song . PrettyLength ( ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %disc% " ) {
return return_value . setNum ( song . disc ( ) ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %track% " ) {
return return_value . setNum ( song . track ( ) ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %genre% " ) {
return song . genre ( ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %playcount% " ) {
return return_value . setNum ( song . playcount ( ) ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %skipcount% " ) {
return return_value . setNum ( song . skipcount ( ) ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %filename% " ) {
return song . basefilename ( ) . toHtmlEscaped ( ) ;
}
else if ( variable = = " %newline% " ) {
return QString ( newline ) ;
}
//if the variable is not recognized, just return it
return variable ;
}
2020-04-27 00:22:46 +02:00
bool IsColorDark ( const QColor & color ) {
return ( ( 30 * color . red ( ) + 59 * color . green ( ) + 11 * color . blue ( ) ) / 100 ) < = 130 ;
}
2020-05-12 19:48:37 +02:00
QList < QByteArray > ImageFormatsForMimeType ( const QByteArray & mimetype ) {
if ( mimetype = = " image/bmp " ) return QList < QByteArray > ( ) < < " BMP " ;
else if ( mimetype = = " image/gif " ) return QList < QByteArray > ( ) < < " GIF " ;
else if ( mimetype = = " image/jpeg " ) return QList < QByteArray > ( ) < < " JPG " ;
else if ( mimetype = = " image/png " ) return QList < QByteArray > ( ) < < " PNG " ;
else return QList < QByteArray > ( ) ;
}
2018-02-27 18:06:05 +01:00
} // namespace Utilities
ScopedWCharArray : : ScopedWCharArray ( const QString & str )
: chars_ ( str . length ( ) ) , data_ ( new wchar_t [ chars_ + 1 ] ) {
str . toWCharArray ( data_ . get ( ) ) ;
data_ [ chars_ ] = ' \0 ' ;
}