"Fossies" - the Fresh Open Source Software Archive

Member "MP3Diags-unstable-1.5.01/src/ExternalToolDlgImpl.cpp" (11 Feb 2019, 14158 Bytes) of package /linux/privat/MP3Diags-unstable-1.5.01.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ExternalToolDlgImpl.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.3.04_vs_1.5.01.

    1 /***************************************************************************
    2  *   MP3 Diags - diagnosis, repairs and tag editing for MP3 files          *
    3  *                                                                         *
    4  *   Copyright (C) 2009 by Marian Ciobanu                                  *
    5  *   ciobi@inbox.com                                                       *
    6  *                                                                         *
    7  *   This program is free software; you can redistribute it and/or modify  *
    8  *   it under the terms of the GNU General Public License version 2 as     *
    9  *   published by the Free Software Foundation.                            *
   10  *                                                                         *
   11  *   This program is distributed in the hope that it will be useful,       *
   12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
   13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
   14  *   GNU General Public License for more details.                          *
   15  *                                                                         *
   16  *   You should have received a copy of the GNU General Public License     *
   17  *   along with this program; if not, write to the                         *
   18  *   Free Software Foundation, Inc.,                                       *
   19  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
   20  ***************************************************************************/
   21 
   22 
   23 #include  <string>
   24 
   25 #include  <QProcess>
   26 #include  <QScrollBar>
   27 #include  <QMessageBox>
   28 #include  <QCloseEvent>
   29 #include  <QTextCodec>
   30 
   31 #include  "ExternalToolDlgImpl.h"
   32 
   33 #include  "Widgets.h"
   34 #include  "StoredSettings.h"
   35 
   36 #include  "CommonData.h"
   37 
   38 ////#include  <QFile> // ttt remove
   39 
   40 
   41 using namespace std;
   42 
   43 /*
   44 ttt2 random 30-second freeze: norm completed but not detected; window was set to stay open; abort + close sort of worked, but the main window froze and the program had to be killed; on a second run it looked like the UI freeze is not permanent, but lasts 30 seconds (so waiting some more the first time might have unfrozen the app)
   45 
   46 What seems to happen is this: QProcess loses contact with the actual process (or perhaps the program becomes a zombie or something similar, not really finished but not running anymore either); then 2 things happen: first, waitForFinished() doesn't return for 30 seconds (which is the default timeout, and can be made smaller); then, when closing the norm window there's another 30 seconds freeze, probably caused by the dialog destructor's attempt to destroy the QProcess member (which again tries to kill a dead process)
   47 
   48 This might be fixed in newer versions of Qt.
   49 
   50 Might be related to the comment "!!! needed because sometimes" below, which also suggests that the issue is in Qt rather than MP3 Diags
   51 
   52 
   53 Note that this also happens during normal operation, even if "abort" is not pressed. The dialog might fail to detect that normalization is done. If that happens, the solution is to press Abort.
   54 
   55 ttt2 - perhaps detect that no output is coming from the program, so just assume it's dead; still, the destructor would have to be detached and put on a secondary thread, or just leave a memory leak; (having a timer "clean" a vector with QProcess objects doesn't work, because it would freeze whatever object it's attached to)
   56 
   57 ttt2 doc: might seem frozen at the end; just press abort and wait for at most a minute. i'm investigating the cause
   58 same may happen after pressing abort while the normalization is running
   59 */
   60 
   61 ExternalToolDlgImpl::ExternalToolDlgImpl(QWidget* pParent, bool bKeepOpen, SessionSettings& settings, const CommonData* pCommonData, const std::string& strCommandName, const char* szHelpFile) : QDialog(pParent, getDialogWndFlags()), Ui::ExternalToolDlg(), m_pProc(0), m_bFinished(false), m_settings(settings), m_pCommonData(pCommonData), m_strCommandName(strCommandName), m_szHelpFile(szHelpFile)
   62 {
   63     setupUi(this);
   64     m_pKeepOpenCkM->setChecked(bKeepOpen);
   65 
   66     int nWidth, nHeight;
   67     m_settings.loadExternalToolSettings(nWidth, nHeight);
   68     if (nWidth > 400 && nHeight > 300) { resize(nWidth, nHeight); }
   69 
   70     setWindowTitle(convStr(strCommandName));
   71 
   72     { QAction* p (new QAction(this)); p->setShortcut(QKeySequence("F1")); connect(p, SIGNAL(triggered()), this, SLOT(onHelp())); addAction(p); }
   73 }
   74 
   75 
   76 ExternalToolDlgImpl::~ExternalToolDlgImpl()
   77 {
   78     CursorOverrider crs;
   79     delete m_pProc;
   80 }
   81 
   82 
   83 void logTransformation(const string& strLogFile, const char* szActionName, const string& strMp3File);
   84 
   85 /*static*/ void ExternalToolDlgImpl::prepareArgs(const QString& qstrCommand, const QStringList& lFiles, QString& qstrProg, QStringList& lArgs)
   86 {
   87     if (qstrCommand.contains('"'))
   88     {
   89         bool bInsideQuotes (false);
   90         qstrProg.clear();
   91         QString qstrCrt;
   92         for (int i = 0; i < qstrCommand.size(); ++i)
   93         {
   94             QChar c (qstrCommand[i]);
   95             if (' ' == c)
   96             {
   97                 if (bInsideQuotes)
   98                 {
   99                     qstrCrt += c;
  100                 }
  101                 else
  102                 {
  103                     if (!qstrCrt.isEmpty())
  104                     {
  105                         if (qstrProg.isEmpty())
  106                         {
  107                             qstrProg = qstrCrt;
  108                         }
  109                         else
  110                         {
  111                             lArgs << qstrCrt;
  112                         }
  113                         qstrCrt.clear();
  114                     }
  115                 }
  116             }
  117             else if ('"' == c)
  118             {
  119                 bInsideQuotes = !bInsideQuotes;
  120                 if (!bInsideQuotes && qstrCrt.isEmpty())
  121                 {
  122                     if (i == qstrCommand.size() - 1 || qstrCommand[i + 1] == ' ')
  123                     { // add an empty param
  124                         lArgs << qstrCrt;
  125                     }
  126                 }
  127             }
  128             else
  129             {
  130                 qstrCrt += c;
  131             }
  132         }
  133 
  134         if (!qstrCrt.isEmpty())
  135         {
  136             if (qstrProg.isEmpty())
  137             {
  138                 qstrProg = qstrCrt;
  139             }
  140             else
  141             {
  142                 lArgs << qstrCrt;
  143             }
  144             qstrCrt.clear();
  145         }
  146         //ttt2 maybe: trigger some error if bInsideQuotes is "false" at the end
  147         //ttt3 maybe allow <<ab "" cd>> to be interpreted as 3 params, with the second one empty; currently it is interpreted as 2 non-empty params
  148     }
  149     else
  150     {
  151         int k (1);
  152         for (; k < qstrCommand.size() && (qstrCommand[k - 1] != ' '  || (qstrCommand[k] != '-' && qstrCommand[k] != '/')); ++k) {} //ttt2 perhaps better: look for spaces from the end and stop when a dir exists from the beginning of the name till the current space
  153         qstrProg = qstrCommand.left(k).trimmed();
  154         QString qstrArg (qstrCommand.right(qstrCommand.size() - k).trimmed());
  155         //qDebug("prg <%s>  arg <%s>", qstrProg.toUtf8().constData(), qstrArg.toUtf8().constData());
  156 
  157         lArgs = qstrArg.split(" ", QString::SkipEmptyParts); // ttt2 perhaps accomodate params that contain spaces, but mp3gain doesn't seem to need them;
  158         //QString qstrName (l.front());
  159         //l.removeFirst();
  160     }
  161     lArgs << lFiles;
  162 }
  163 
  164 
  165 void ExternalToolDlgImpl::run(const QString& qstrProg1, const QStringList& lFiles) //ttt2 in Windows MP3Gain doesn't seem to care about Unicode (well, the GUI version does, but that doesn't help). aacgain doesn't work either; see if there's a good way to deal with this; doc about using short filenames
  166 {
  167     m_pProc = new QProcess(this);
  168     //m_pProc = new QProcess(); // !!! m_pProc is not owned; it will be destroyed
  169     connect(m_pProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onFinished()));
  170     connect(m_pProc, SIGNAL(readyReadStandardOutput()), this, SLOT(onOutputTxt()));
  171     connect(m_pProc, SIGNAL(readyReadStandardError()), this, SLOT(onErrorTxt()));
  172 
  173     if (m_pCommonData->m_bLogTransf)
  174     {
  175         for (int i = 0; i < lFiles.size(); ++i)
  176         {
  177             logTransformation(m_pCommonData->m_strTransfLog, m_strCommandName.c_str(), convStr(lFiles[i]));
  178         }
  179     }
  180 
  181     QString qstrProg;
  182     QStringList l;
  183     prepareArgs(qstrProg1, lFiles, qstrProg, l);
  184 
  185     m_pProc->start(qstrProg, l);
  186 
  187     if (!m_pProc->waitForStarted(5000))
  188     {
  189         showCritical(this, tr("Error"), tr("Cannot start process. Check that the executable name and the parameters are correct."));
  190         return;
  191     }
  192 
  193     {
  194         QAction* p (new QAction(this));
  195         p->setShortcut(QKeySequence(Qt::Key_Escape));
  196         connect(p, SIGNAL(triggered()), this, SLOT(on_m_pAbortB_clicked()));
  197         addAction(p);
  198     }
  199 
  200     exec();
  201 
  202     m_settings.saveExternalToolSettings(width(), height());
  203 }
  204 
  205 
  206 void ExternalToolDlgImpl::onOutputTxt()
  207 {
  208     addText(m_pProc->readAllStandardOutput()); //ttt2 perhaps use different colors for std and err, or use 2 separate text boxes
  209 }
  210 
  211 
  212 void ExternalToolDlgImpl::onErrorTxt()
  213 {
  214     //addText("####" + m_pProc->readAllStandardError());
  215     QString s (m_pProc->readAllStandardError().trimmed());
  216     if (s.isEmpty()) { return; }
  217 //qDebug("err %s", s.toUtf8().constData());
  218     //inspect(s.toUtf8().constData(), s.size());
  219     int n (s.lastIndexOf("\r"));
  220     if (n > 0)
  221     {
  222         s.remove(0, n + 1);
  223     }
  224 
  225     n = s.lastIndexOf("\n");
  226     if (n > 0)
  227     {
  228         s.remove(0, n + 1);
  229     }
  230     //inspect(s.toUtf8().constData(), s.size());
  231     while (!s.isEmpty() && s[0] == ' ') { s.remove(0, 1); }
  232 
  233     m_pDetailE->setText(s);
  234 }
  235 
  236 
  237 void ExternalToolDlgImpl::addText(QString s)
  238 {
  239     s = s.trimmed();
  240     if (s.isEmpty()) { return; }
  241 
  242     for (;;)
  243     {
  244         int n (s.indexOf("\n\n"));
  245         if (-1 == n) { break; }
  246         s.remove(n, 1);
  247     }
  248 
  249 #if !defined(WIN32) && !defined(__OS2__) //ttt1 not clear what should happen on Windows, where the main client (mp3gain) cannot even process non-ASCII file names
  250     {
  251         // convert to UTF8: the input is really UTF8 but it was considered as Latin1 when creating the QString, so we need to rebuild the string as UTF8; see https://sourceforge.net/p/mp3diags/tickets/3087/
  252         //s = QString::fromUtf8(s.toLatin1().data()); // simplest way but we cannot check for errors
  253 
  254         QTextCodec::ConverterState state;
  255         QTextCodec* pCodec = QTextCodec::codecForName("UTF-8");
  256         QByteArray byteArray (s.toLatin1());
  257         const QString s1 = pCodec->toUnicode(byteArray.constData(), byteArray.size(), &state);
  258         if (state.invalidChars == 0)
  259         {
  260             s = s1; //ttt1 not sure this is right in all cases, although seems fine for mp3gain in Linux; perhaps in other cases "s" is proper UTF8 and no conversion is needed
  261         }
  262         else
  263         {
  264             //qDebug() << "Not a valid UTF-8 sequence.";
  265         }
  266     }
  267 #endif
  268 
  269     m_qstrText = (m_qstrText.isEmpty() ? s : m_qstrText + "\n" + s);
  270 
  271     m_pOutM->setText(m_qstrText);
  272 
  273     QScrollBar* p (m_pOutM->verticalScrollBar());
  274     if (p->isVisible())
  275     {
  276         p->setValue(p->maximum());
  277     }
  278 }
  279 
  280 
  281 void ExternalToolDlgImpl::onFinished()
  282 {
  283     if (m_bFinished) { return; } // !!! needed because sometimes terminating with kill() triggers onFinished() and sometimes it doesn't
  284     m_bFinished = true;
  285     // !!! doesn't need to destroy m_pProc and QAction, because they will be destroyed anyway when the dialog will be destroyed, which is going to be pretty soon
  286     if (m_pKeepOpenCkM->isChecked()) //ttt2 perhaps save in config
  287     {
  288         addText("==================================");
  289         addText(tr("Finished"));
  290         m_pDetailE->setText("");
  291     }
  292     else
  293     {
  294         reject();
  295     }
  296 }
  297 
  298 void ExternalToolDlgImpl::on_m_pCloseB_clicked()
  299 {
  300     if (!m_bFinished)
  301     {
  302         showWarning(this, tr("Warning"), tr("Cannot close while \"%1\" is running.").arg(convStr(m_strCommandName)));
  303         return;
  304     }
  305     accept();
  306 }
  307 
  308 
  309 void ExternalToolDlgImpl::on_m_pAbortB_clicked()
  310 {
  311 qDebug("proc state %d", int(m_pProc->state()));
  312     if (m_bFinished)
  313     {
  314         on_m_pCloseB_clicked();
  315         return;
  316     }
  317 
  318     if (0 == showMessage(this, QMessageBox::Warning, 1, 1, tr("Confirm"), tr("Stopping \"%1\" may leave the files in an inconsistent state or may prevent temporary files from being deleted. Are you sure you want to abort \"%1\"?").arg(convStr(m_strCommandName)), tr("Yes, abort"), tr("Don't abort")))
  319     {
  320         CursorOverrider crs;
  321         m_pProc->kill();
  322         m_pProc->waitForFinished(5000);
  323         onFinished();
  324     }
  325 }
  326 
  327 
  328 // !!! Can't allow the top-right close button or the ESC key to close the dialog, because that would kill the thread too and leave everything in an inconsistent state. So the corresponding events are intercepted and "ignore()"d and abort() is called instead
  329 
  330 /*override*/ void ExternalToolDlgImpl::closeEvent(QCloseEvent* pEvent)
  331 {
  332     /*
  333     Not sure if this should work: from the doc for QDialog:
  334 
  335     Escape Key
  336     If the user presses the Esc key in a dialog, QDialog::reject() will be called. This will cause the window to close: The close event cannot be ignored.
  337 
  338     ttt2 see Qt::Key_Escape in MainFormDlgImpl for a different approach, decide which is better
  339     */
  340     pEvent->ignore();
  341     on_m_pCloseB_clicked();
  342 //    on_m_pAbortB_clicked();
  343 }
  344 
  345 #if 0
  346 /*override*/ void ExternalToolDlgImpl::keyPressEvent(QKeyEvent* pEvent)
  347 {
  348 //qDebug("key prs %d", pEvent->key());
  349 
  350     m_nLastKey = pEvent->key();
  351 
  352     pEvent->ignore(); // ttt2 not sure this is the way to do it, but the point is to disable the ESC key
  353 }
  354 
  355 
  356 /*override*/ void ExternalToolDlgImpl::keyReleaseEvent(QKeyEvent* pEvent)
  357 {
  358 //qDebug("key rel %d", pEvent->key());
  359     if (Qt::Key_Escape == pEvent->key())
  360     {
  361         on_m_pAbortB_clicked();
  362     }
  363     pEvent->ignore(); // ttt2 not sure this is the way to do it, but the point is to disable the ESC key
  364 }
  365 #endif
  366 
  367 
  368 void ExternalToolDlgImpl::onHelp()
  369 {
  370     openHelp(m_szHelpFile);
  371 }
  372 
  373 //ttt2 timer in normalizer
  374 //ttt2 look at normalized loudness in tracks, maybe warn
  375 
  376 //ttt1 non-ASCII characters are not shown correctly
  377