File indexing completed on 2024-05-19 04:49:44

0001 /****************************************************************************************
0002  * Copyright (c) 2013 Konrad Zemek <konrad.zemek@gmail.com>                             *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #include "AmarokEmbeddedSqlConnection.h"
0018 
0019 #include "core/support/Debug.h"
0020 
0021 #include <ThreadWeaver/Thread>
0022 
0023 #include <QEventLoop>
0024 #include <QFileSystemWatcher>
0025 #include <QMutexLocker>
0026 #include <QRandomGenerator>
0027 #include <QStringList>
0028 #include <QTemporaryFile>
0029 
0030 using namespace StatSyncing;
0031 
0032 AmarokEmbeddedSqlConnection::AmarokEmbeddedSqlConnection( const QFileInfo &mysqld,
0033                                                           const QDir &datadir )
0034     : ImporterSqlConnection()
0035     , m_mysqld( mysqld )
0036     , m_datadir( datadir )
0037 {
0038     connect( &m_shutdownTimer, &QTimer::timeout,
0039              this, &AmarokEmbeddedSqlConnection::stopServer );
0040     m_shutdownTimer.setSingleShot( true );
0041 }
0042 
0043 AmarokEmbeddedSqlConnection::~AmarokEmbeddedSqlConnection()
0044 {
0045     if( isTransaction() )
0046         rollback();
0047     stopServer();
0048 }
0049 
0050 QSqlDatabase
0051 AmarokEmbeddedSqlConnection::connection()
0052 {
0053     Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() );
0054 
0055     QMutexLocker lock( &m_srvMutex );
0056 
0057     // The server's already running; only refresh its shutdown timer
0058     if( m_srv.state() == QProcess::Running )
0059     {
0060         m_shutdownTimer.start( SERVER_SHUTDOWN_AFTER );
0061         return QSqlDatabase::database( m_connectionName );
0062     }
0063 
0064     QTemporaryFile pidFile( QDir::temp().filePath( "amarok_importer-XXXXXX.pid" ) );
0065     QTemporaryFile socket( QDir::temp().filePath( "amarok_importer-XXXXXX.socket" ) );
0066     pidFile.open();
0067     socket.open();
0068 
0069     // Get random port in range 3307 - 65535
0070     const int port = ( QRandomGenerator::global()->generate() % ( 65536 - 3307 ) ) + 3307;
0071 
0072     QSqlDatabase::removeDatabase( m_connectionName );
0073     QSqlDatabase db = QSqlDatabase::addDatabase( "QMYSQL", m_connectionName );
0074     db.setDatabaseName  ( "amarok"    );
0075     db.setHostName      ( "localhost" );
0076     db.setUserName      ( "root"      );
0077     db.setPassword      ( ""          );
0078     db.setPort          ( port        );
0079     db.setConnectOptions( "UNIX_SOCKET=" + QFileInfo( socket ).absoluteFilePath() );
0080 
0081     if( startServer( port, QFileInfo( socket ).absoluteFilePath(),
0082                      QFileInfo( pidFile ).absoluteFilePath() ) )
0083     {
0084         // Give tempfiles ownership over to mysqld
0085         pidFile.setAutoRemove( false );
0086         socket.setAutoRemove( false );
0087 
0088         m_shutdownTimer.start( SERVER_SHUTDOWN_AFTER );
0089     }
0090 
0091     db.open();
0092     return db;
0093 }
0094 
0095 bool
0096 AmarokEmbeddedSqlConnection::startServer( const int port, const QString &socketPath,
0097                                           const QString &pidPath )
0098 {
0099     DEBUG_BLOCK
0100     Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() );
0101 
0102     if( !m_mysqld.isExecutable() )
0103     {
0104         warning() << __PRETTY_FUNCTION__ << m_mysqld.absoluteFilePath()
0105                   << "is not executable";
0106         return false;
0107     }
0108 
0109     if( !m_datadir.isReadable() )
0110     {
0111         warning() << __PRETTY_FUNCTION__ << m_datadir.absolutePath() << "is not readable";
0112         return false;
0113     }
0114 
0115     QEventLoop loop;
0116     QFileSystemWatcher watcher;
0117     QTimer timer;
0118 
0119     // Set conditions on which we stop waiting for the startup
0120     connect( &timer,   &QTimer::timeout,
0121              &loop,    &QEventLoop::quit, Qt::QueuedConnection );
0122     connect( &watcher, &QFileSystemWatcher::fileChanged,
0123              &loop,    &QEventLoop::quit, Qt::QueuedConnection );
0124     connect( &m_srv,   QOverload<QProcess::ProcessError>::of( &QProcess::errorOccurred ),
0125              &loop,    &QEventLoop::quit, Qt::QueuedConnection );
0126 
0127     // Important: we use modification of pidfile as a cue that the server is ready
0128     // This is consistent with behavior of mysqld startup scripts
0129     watcher.addPath( pidPath );
0130     timer.start( SERVER_START_TIMEOUT );
0131 
0132     const QStringList args = QStringList()
0133          << "--no-defaults"
0134          << "--port=" + QString::number( port )
0135          << "--datadir=" + m_datadir.absolutePath()
0136          << "--default-storage-engine=MyISAM"
0137          << "--skip-grant-tables"
0138          << "--myisam-recover-options=FORCE"
0139          << "--key-buffer-size=16777216"
0140          << "--character-set-server=utf8"
0141          << "--collation-server=utf8_bin"
0142          << "--skip-innodb"
0143          << "--bind-address=localhost"
0144          << "--socket=" + socketPath
0145          << "--pid-file=" + pidPath;
0146 
0147     m_srv.start( m_mysqld.absoluteFilePath(), args );
0148     debug() << __PRETTY_FUNCTION__ << m_mysqld.absoluteFilePath() + ' ' + args.join(' ');
0149 
0150     // Wait for any of the startup conditions to be true
0151     loop.exec();
0152 
0153     if( m_srv.state() != QProcess::Running )
0154     {
0155         warning() << __PRETTY_FUNCTION__ << "error starting server application:"
0156                   << m_srv.errorString();
0157         return false;
0158     }
0159 
0160     return true;
0161 }
0162 
0163 void
0164 AmarokEmbeddedSqlConnection::stopServer()
0165 {
0166     DEBUG_BLOCK
0167     Q_ASSERT( this->thread() == ThreadWeaver::Thread::currentThread() );
0168 
0169     QMutexLocker lock( &m_srvMutex );
0170     if( isTransaction() || m_srv.state() == QProcess::NotRunning )
0171         return;
0172 
0173     m_shutdownTimer.stop();
0174     QSqlDatabase::removeDatabase( m_connectionName );
0175 
0176     m_srv.terminate();
0177     if( !m_srv.waitForFinished() )
0178     {
0179         m_srv.kill();
0180         m_srv.waitForFinished();
0181     }
0182 }