/*************************************************************************** * * Copyright (C) 2005 Elad Lahav (elad_lahav@users.sourceforge.net) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ***************************************************************************/ #include #include #include #include "frontend.h" /** * Class constructor. * @param nRecordSize The number of fields in each record * @param bAutoDelete (Optional) true to delete the object when the process * terminates, false (default) otherwise */ Frontend::Frontend(uint nRecordSize, bool bAutoDelete) : TDEProcess(), m_nRecords(0), m_pHeadToken(NULL), m_pTailToken(NULL), m_pCurToken(NULL), m_bAutoDelete(bAutoDelete), m_bInToken(false), m_nRecordSize(nRecordSize) { // Parse data on the standard output connect(this, TQ_SIGNAL(receivedStdout(TDEProcess*, char*, int)), this, TQ_SLOT(slotReadStdout(TDEProcess*, char*, int))); // Parse data on the standard error connect(this, TQ_SIGNAL(receivedStderr(TDEProcess*, char*, int)), this, TQ_SLOT(slotReadStderr(TDEProcess*, char*, int))); // Delete the process object when the process exits connect(this, TQ_SIGNAL(processExited(TDEProcess*)), this, TQ_SLOT(slotProcessExit(TDEProcess*))); } /** * Class destructor. */ Frontend::~Frontend() { // Delete all pending tokens while (m_pHeadToken) removeToken(); } /** * Executes the back-end process. * @param sName The name of the process (for error messages) * @param slArgs A list containing the command-line arguments * @param sWorkDir (Optional) working directory * @param bBlock (Optional) true to block, false otherwise * @return true if the process was executed successfully, false otherwise */ bool Frontend::run(const TQString& sName, const TQStringList& slArgs, const TQString& sWorkDir, bool bBlock) { // Cannot start if another controlled process is currently running if (isRunning()) { m_sError = i18n("Cannot restart while another process is still " "running"); return false; } // Reset variables m_nRecords = 0; m_bKilled = false; // Setup the command-line arguments clearArguments(); *this << slArgs; // Set the working directory, if requested if (!sWorkDir.isEmpty()) setWorkingDirectory(sWorkDir); // Execute the child process if (!start(bBlock ? TDEProcess::Block : TDEProcess::NotifyOnExit, TDEProcess::All)) { m_sError = sName + i18n(": Failed to start process"); return false; } m_sError = i18n("No error"); return true; } /** * Kills the process, and emits the aborted() signal. * This function should not be called unless the process needs to be * interrupted. */ void Frontend::kill() { m_bKilled = true; TDEProcess::kill(); emit aborted(); } /** * Appends a token to the end of the token list. * @param pToken The token to add */ void Frontend::addToken(FrontendToken* pToken) { // Check if this is the firt token if (m_pHeadToken == NULL) { m_pHeadToken = pToken; m_pTailToken = pToken; } else { // Not the first token, append and reset the tail token m_pTailToken->m_pNext = pToken; m_pTailToken = pToken; } } /** * Removes and deletes the token at the head of the token list. */ void Frontend::removeToken() { FrontendToken* pToken; if (m_pHeadToken == NULL) return; pToken = m_pHeadToken; m_pHeadToken = m_pHeadToken->m_pNext; delete pToken; if (m_pHeadToken == NULL) m_pTailToken = NULL; } /** * Removes tokens from the head of the list, according to the size of a * record. */ void Frontend::removeRecord() { uint i; for (i = 0; (i < m_nRecordSize) && (m_pHeadToken != NULL); i++) removeToken(); } /** * Extracts tokens of text out of a given buffer. * @param ppBuf Points to the buffer to parse, and is set to the * beginning of the next token, upon return * @param pBufSize Points to the size of the buffer, and is set to the * remaining size, upon return * @param sResult Holds the token's text, upon successful return * @param delim Holds the delimiter by which the token's end was * determined * @return true if a token was extracted up to the given delimter(s), false * if the buffer ended before a delimiter could be identified */ bool Frontend::tokenize(char** ppBuf, int* pBufSize, TQString& sResult, ParserDelim& delim) { int nSize; char* pBuf; bool bDelim, bWhiteSpace, bFoundToken = false; // Iterate buffer for (nSize = *pBufSize, pBuf = *ppBuf; (nSize > 0) && !bFoundToken; nSize--, pBuf++) { // Test if this is a delimiter character switch (*pBuf) { case '\n': bDelim = ((m_delim & Newline) != 0); bWhiteSpace = true; delim = Newline; break; case ' ': bDelim = ((m_delim & Space) != 0); bWhiteSpace = true; delim = Space; break; case '\t': bDelim = ((m_delim & Tab) != 0); bWhiteSpace = true; delim = Tab; break; default: bDelim = false; bWhiteSpace = false; } if (m_bInToken && bDelim) { m_bInToken = false; *pBuf = 0; bFoundToken = true; } else if (!m_bInToken && !bWhiteSpace) { m_bInToken = true; *ppBuf = pBuf; } } // Either a token was found, or the search through the buffer was // finished without a delimiter character if (bFoundToken) { sResult = TQString::fromLocal8Bit(*ppBuf); *ppBuf = pBuf; *pBufSize = nSize; } else if (m_bInToken) { sResult = TQString::fromLocal8Bit(*ppBuf, *pBufSize); } else { sResult = TQString::null; } return bFoundToken; } /** * Handles text sent by the back-end process to the standard error stream. * By default, this method emits the error() signal with the given text. * @param sText The text sent to the standard error stream */ void Frontend::parseStderr(const TQString& sText) { emit error(sText); } /** * Deletes the process object upon the process' exit. */ void Frontend::slotProcessExit(TDEProcess*) { // Allow specialised clean-up by inheriting classes finalize(); // Signal the process has terminated emit finished(m_nRecords); // Delete the object, if required if (m_bAutoDelete) delete this; } /** * Reads data written on the standard output by the controlled process. * This is a private slot called attached to the readyReadStdout() signal of * the controlled process, which means that it is called whenever data is * ready to be read from the process' stream. * The method reads whatever data is queued, and sends it to be interpreted * by parseStdout(). */ void Frontend::slotReadStdout(TDEProcess*, char* pBuffer, int nSize) { char* pLocalBuf; TQString sToken; bool bTokenEnded; ParserDelim delim; // Do nothing if waiting for process to die if (m_bKilled) return; pLocalBuf = pBuffer; // Iterate over the given buffer while (nSize > 0) { // Create a new token, if the last iteration has completed one if (m_pCurToken == NULL) m_pCurToken = new FrontendToken(); // Extract text until the requested delimiter bTokenEnded = tokenize(&pLocalBuf, &nSize, sToken, delim); // Add the extracted text to the current token m_pCurToken->m_sData += sToken; // If the buffer has ended before the requested delimiter, we need // to wait for more output from the process if (!bTokenEnded) return; // Call the process-specific parser function switch (parseStdout(m_pCurToken->m_sData, delim)) { case DiscardToken: // Token should not be saved delete m_pCurToken; break; case AcceptToken: // Store token in linked list addToken(m_pCurToken); break; case RecordReady: // Store token, and notify the target object that an entry can // be read m_nRecords++; addToken(m_pCurToken); emit dataReady(m_pHeadToken); // Delete all tokens in the entry removeRecord(); break; case Abort: kill(); nSize = 0; break; } m_pCurToken = NULL; } } /** * Reads data written on the standard error by the controlled process. * This is a private slot called attached to the readyReadStderr() signal of * the controlled process, which means that it is called whenever data is * ready to be read from the process' stream. * The method reads whatever data is queued, and sends it to be interpreted * by parseStderr(). */ void Frontend::slotReadStderr(TDEProcess*, char* pBuffer, int nSize) { TQString sBuf; // Do nothing if waiting for process to die if (m_bKilled) return; sBuf = TQString::fromLocal8Bit(pBuffer, nSize); parseStderr(sBuf); } #include "frontend.moc"