示例#1
0
bool PipedProcess::HasInput()
{
    if (IsErrorAvailable())
    {
        cbTextInputStream serr(*GetErrorStream());

        wxString msg;
        msg << serr.ReadLine();

		CodeBlocksEvent event(cbEVT_PIPEDPROCESS_STDERR, m_Id);
        event.SetString(msg);
		wxPostEvent(m_Parent, event);
// 		m_Parent->ProcessEvent(event);

        return true;
    }

    if (IsInputAvailable())
    {
        cbTextInputStream sout(*GetInputStream());

        wxString msg;
        msg << sout.ReadLine();

		CodeBlocksEvent event(cbEVT_PIPEDPROCESS_STDOUT, m_Id);
        event.SetString(msg);
		wxPostEvent(m_Parent, event);
// 		m_Parent->ProcessEvent(event);

        return true;
    }

    return false;
}
示例#2
0
void CamProcess::ProcessStdOut()
{
	if (IsInputAvailable())
	{
		wxTextInputStream tis(*GetInputStream());

		// This assumes that the output is always line buffered
		while (IsInputAvailable())
		{
			wxString line;
			line << tis.ReadLine();
//			TRACEUSER("Gerry", _T("(stdout):%s"), line.c_str());
		}
	}

}
示例#3
0
bool PipedProcess::ReadAll(wxString &input)
{
	bool hasInput = false;
	bool cont1(true), cont2(true);

	wxTextInputStream tis(*GetInputStream());
	wxTextInputStream tie(*GetErrorStream());
	while(cont1 || cont2){
		cont1 = false;
		cont2 = false;
		while( IsInputAvailable() )
		{
			// this assumes that the output is always line buffered
			wxChar ch = tis.GetChar();
			input << ch;
			hasInput = true;
			cont1 = true;
		}

		while( IsErrorAvailable() )
		{
			// this assumes that the output is always line buffered
			wxChar ch = tie.GetChar();
			input << ch;
			hasInput = true;
			cont2 = true;
		}
	}
	return hasInput;
}
示例#4
0
bool MarkdownProcess::HasInput()
{
    bool hasInput = false;

    if ( IsInputAvailable() )
    {
        wxTextInputStream tis(*GetInputStream());

        // this assumes that the output is always line buffered
		m_file << tis.ReadLine() << wxT("\n");

//        m_parent->GetLogListBox()->Append(msg);

        hasInput = true;
    }

    if ( IsErrorAvailable() )
    {
        wxTextInputStream tis(*GetErrorStream());

        // this assumes that the output is always line buffered
		wxMessageBox(tis.ReadLine());
        hasInput = true;
    }

    return hasInput;
}
示例#5
0
void processManager::LogOutput(bool &hasOutput, const wxString &label,float *outputProgression)
{
    hasOutput = false;

    if ( IsInputAvailable() )
    {
        wxTextInputStream tis(*GetInputStream());

        // this assumes that the output is always line buffered
        wxString msg;
        wxString output= tis.ReadLine();
        if(!this->outlogs.empty())
        {
            for(std::vector<smart_ptr<InterfLogger> >::iterator itlogs=this->outlogs.begin(); itlogs!=this->outlogs.end(); itlogs++)
            {
                (*(*itlogs)).LogMessage(output);
            }
        }
        if(outputProgression==NULL || output.Left(1)!="#")
        {
            msg << label << output;
            msg.Replace("%","%%"); //si il y a un seul % alors un bug apparait wxString attend un format du type %s ou %i par exemple
            if(output.Left(1)=="!")
            {
                wxLogWarning(msg);
            } else {
                wxLogMessage(msg);
            }
        } else {
            wxString prog=output.Right(output.Len()-1).Strip();
            *outputProgression=Convertor::ToFloat(prog);
        }

        hasOutput = true;
    }

    while ( IsErrorAvailable() )
    {
        wxTextInputStream tis(*GetErrorStream());
        const wxString& errMsg(tis.ReadLine());
        if(!this->outlogs.empty())
        {
            for(std::vector<smart_ptr<InterfLogger> >::iterator itlogs=this->outlogs.begin(); itlogs!=this->outlogs.end(); itlogs++)
            {
                (*(*itlogs)).LogError(errMsg);
            }
        }
        // this assumes that the output is always line buffered
        wxString msg;
        msg << _("Erreur exécutable :") << errMsg;
        msg.Replace("%","%%"); //si il y a un seul % alors un bug apparait wxString attend un format du type %s ou %i par exemple
        wxLogError(msg);

        hasOutput = true;
    }
}
示例#6
0
size_t nwxProcess::ProcessIO(size_t nLimit)
{
  size_t nRtn = 0;
  bool bInputClosed = false;
  if(!m_bPaused)
  {
    if(!IsInputOpened())
    {
      if(m_sBuffer.Len())
      {
        m_sBuffer += "\n";
        ProcessLine(m_sBuffer.utf8_str(),m_sBuffer.Len(),false);
        m_sBuffer.Empty();
      }
      bInputClosed = true;
    }
    else 
    {
      while(IsInputAvailable() && (nRtn < nLimit))
      {
        nRtn += ProcessIO(GetInputStream(),m_sBuffer,false);
      }
    }
    if(!IsErrorOpened())
    {
      if(m_sBufferError.Len())
      {
        m_sBufferError += "\n";
        ProcessLine(m_sBufferError.utf8_str(),m_sBufferError.Len(),true);
        m_sBufferError.Empty();
      }
      if(bInputClosed && m_bRunning)
      {
    	  m_bRunning = false;
    	      // we are sometimes not notified when process ends
#ifndef __WXMSW__
    	  // need to clean up zombie because wx sometimes
    	  // fails to do this on the macintosh
    	  int nStatLoc;
    	  pid_t nPID;
    	  nPID = waitpid((pid_t)m_nPID,&nStatLoc,0);
    	  OnTerminate(m_nPID,nStatLoc);
#endif
      }
    }
    else 
    {
      while(IsErrorAvailable() && (nRtn < nLimit))
      {
        nRtn += ProcessIO(GetErrorStream(),m_sBufferError,true);
      }
    }
  }
  return nRtn;
}
示例#7
0
void PipedProcess::ForfeitStreams()
{
    char buf[4096];
    if (IsErrorAvailable())
    {
        wxInputStream *in = GetErrorStream();
        while(in->Read(&buf, sizeof(buf)).LastRead())
            ;
    }
    if (IsInputAvailable())
    {
        wxInputStream *in = GetInputStream();
        while(in->Read(&buf, sizeof(buf)).LastRead())
            ;
    }
}
示例#8
0
bool DVBChannel::SwitchToInput(int newInputNum, bool setstarting)
{
    (void)setstarting;

    InputMap::const_iterator it = m_inputs.find(newInputNum);
    if (it == m_inputs.end() || (*it)->startChanNum.isEmpty())
        return false;

    uint mplexid_restriction;
    if (!IsInputAvailable(m_currentInputID, mplexid_restriction))
        return false;

    nextInputID = newInputNum;

    return SetChannelByString((*it)->startChanNum);
}
示例#9
0
bool CscopeProcess::ReadProcessOutput()
{

    //wxProcess::GetInputStream() will capture stdout!
    if (IsInputAvailable())
    {
        wxTextInputStream ts(*GetInputStream());
        wxString line = ts.ReadLine();

        if(line.Length())
        {
            m_parent->OnProcessGeneratedOutputLine(line);
        }
        return true;
    }
    return false;
}
示例#10
0
void CamProcess::OnTerminate(int /*TYPENOTE: Correct*/ pid, int /*TYPENOTE: Correct*/ status)
{
//	TRACEUSER("Gerry", _T("CamProcess::OnTerminate pid = %d  status = %d"), pid, status);
	m_bDead = true;
	m_ReturnCode = status;

	// Process anything remaining on stderr and stdout
	// If we have an output file
	if (m_pOutFile)
	{
		// If there is output from the process
		if (IsInputAvailable())
		{
			// Copy the data to the file
			size_t NumRead = 4096;
			BYTE Buffer[4096];

			while (NumRead > 0)
			{
				// Read a buffer full
				GetInputStream()->Read(Buffer, 4096);

				NumRead = GetInputStream()->LastRead();

				// Write the buffer to the file
				if (NumRead > 0)
				{
					m_pOutFile->write(Buffer, NumRead);
				}
			}
		}
	}
	else
	{
		// Call the virtual function to process the output
		ProcessStdOut();
	}

	ProcessStdErr();
}
示例#11
0
bool AxProcess::HasEnded( )
{
	wxString msg;

	// redirect in now disabled (see constructor)
	// nothing will happen
	wxTextInputStream tis(*GetInputStream());
	wxTextInputStream tes(*GetErrorStream());
	
	// we don't know where Error is going to be output
    while ( IsInputAvailable() )
	{
		msg = tis.ReadLine();
		if ( msg.StartsWith("$ Success!") )
		{
			return true;
		}
		else if ( msg.StartsWith("$ Error: ") )
		{
			m_status = 255;
			return true;
		}
		if ( !msg.IsEmpty() && m_log )
			m_log->WriteString( msg + "\n" );
	}
    while ( IsErrorAvailable() )
	{
		msg = tes.ReadLine();
		if ( msg.StartsWith("$ Error: ") )
		{
			m_status = 255;
			return true;
		}
		if ( !msg.IsEmpty() && m_log )
			m_log->WriteString( msg + "\n" );
    }


    return false;
}
示例#12
0
bool IPTVChannel::SetChannelByString(const QString &channum)
{
    LOG(VB_CHANNEL, LOG_INFO, LOC + QString("SetChannelByString(%1) -- begin")
        .arg(channum));
    QMutexLocker locker(&m_lock);
    LOG(VB_CHANNEL, LOG_INFO, LOC + "SetChannelByString() -- locked");

    InputMap::const_iterator it = m_inputs.find(m_currentInputID);
    if (it == m_inputs.end())
        return false;

    uint mplexid_restriction;
    if (!IsInputAvailable(m_currentInputID, mplexid_restriction))
        return false;

    // Verify that channel exists
    if (!GetChanInfo(channum).isValid())
    {
        LOG(VB_GENERAL, LOG_ERR, LOC +
                QString("SetChannelByString(%1)").arg(channum) +
                " Invalid channel");
        return false;
    }

    // Set the current channum to the new channel's channum
    QString tmp = channum; tmp.detach();
    m_curchannelname = tmp;

    // Set the dtv channel info for any additional multiplex tuning
    SetDTVInfo(/*atsc_major*/ 0, /*atsc_minor*/ 0,
               /*netid*/ 0,
               /*tsid*/ 0, /*mpeg_prog_num*/ 1);

    HandleScript(channum /* HACK treat channum as freqid */);

    LOG(VB_CHANNEL, LOG_INFO, LOC + QString("SetChannelByString(%1) = %2 -- end")
        .arg(channum).arg(m_curchannelname));
    return true;
}
示例#13
0
INT32 CamProcess::Execute(const wxString& cmd)
{
	// We're now running
	m_bDead = false;

	// Make sure redirection happens
	Redirect();

	long pid = wxExecute(cmd, wxEXEC_ASYNC, this);
	if (pid == 0)
	{
		// Report problem
		m_bDead = true;
		return 123;
	}

	BYTE ReadBuffer[4096];
	size_t ReadBytes = 0;
	BYTE* ReadPtr = NULL;
	bool bMoreInput = true;
	size_t InFileLeft = 0;
	if (m_pInFile)
	{
		InFileLeft = m_pInFile->Size();
//		TRACEUSER("Gerry", _T("InFileSize = %d"), InFileLeft);
	}

	// Loop until m_bDead is true

	while (!m_bDead)
	{
		// Call the virtual function to process any output on stderr
		ProcessStdErr();

		wxYield();

		// If we have an output file
		if (m_pOutFile)
		{
			// If there is output from the process
			if (!m_bDead && IsInputAvailable())
			{
				// Copy the data to the file

				size_t NumRead = 4096;
				BYTE Buffer[4096];

				// Read a buffer full
				GetInputStream()->Read(Buffer, 4096);

				NumRead = GetInputStream()->LastRead();

				// Write the buffer to the file
				if (NumRead > 0)
				{
//					TRACEUSER("Gerry", _T("Writing %d bytes of stdout"), NumRead);
					m_pOutFile->write(Buffer, NumRead);
				}
			}
		}
		else
		{
			// Call the virtual function to process the output
			if (!m_bDead)
				ProcessStdOut();
		}

		wxYield();

		// If we have an input file
		if (m_pInFile)
		{
			// Copy some data to the process
			// This was a while loop
			if (!m_bDead && bMoreInput)
			{
				// If there is nothing in the buffer
				if (ReadBytes == 0)
				{
					ReadBytes = 4096;
					if (ReadBytes > InFileLeft)
						ReadBytes = InFileLeft;

					if (ReadBytes > 0)
					{
						// Read a buffer full
//						TRACEUSER("Gerry", _T("Reading %d"), ReadBytes);
						m_pInFile->read(ReadBuffer, ReadBytes);

						InFileLeft -= ReadBytes;
						ReadPtr = ReadBuffer;
					}
				}

				// If there is something in the buffer
				if (ReadBytes > 0 && GetOutputStream()->IsOk())
				{
//					TRACEUSER("Gerry", _T("Buffer contains %d"), ReadBytes);
					// Try to write it to the process
					GetOutputStream()->Write(ReadPtr, ReadBytes);

					size_t Done = GetOutputStream()->LastWrite();
//					TRACEUSER("Gerry", _T("Written %d"), Done);
					// If we couldn't write it all
					if (Done < ReadBytes)
					{
						// Update the buffer pointer
						ReadPtr += Done;
					}
					// This is correct for all, part or none written
					ReadBytes -= Done;
				}
				else
				{
					// Indicate there is no more stdin
//					TRACEUSER("Gerry", _T("Buffer is empty - closing"));
					CloseOutput();
					bMoreInput = false;
				}
			}
		}
		else
		{
			// Call the virtual function to process the input
			if (!m_bDead)
				ProcessStdIn();
		}

		wxYield();
	}

//	TRACEUSER("Gerry", _T("Exiting with %d"), m_ReturnCode);
	return m_ReturnCode;
}
示例#14
0
bool DTVChannel::SetChannelByString(const QString &channum)
{
    QString loc = LOC + QString("SetChannelByString(%1): ").arg(channum);
    LOG(VB_CHANNEL, LOG_INFO, loc);

    ClearDTVInfo();

    if (!IsOpen() && !Open())
    {
        LOG(VB_GENERAL, LOG_ERR, loc + "Channel object "
                "will not open, can not change channels.");

        return false;
    }

    if (m_inputs.size() > 1)
    {
        QString inputName;
        if (!CheckChannel(channum, inputName))
        {
            LOG(VB_GENERAL, LOG_ERR, loc +
                    "CheckChannel failed.\n\t\t\tPlease verify the channel "
                    "in the 'mythtv-setup' Channel Editor.");

            return false;
        }

        // If CheckChannel filled in the inputName then we need to
        // change inputs and return, since the act of changing
        // inputs will change the channel as well.
        if (!inputName.isEmpty())
            return SwitchToInput(inputName, channum);
    }

    InputMap::const_iterator it = m_inputs.find(m_currentInputID);
    if (it == m_inputs.end())
        return false;

    uint mplexid_restriction;
    uint chanid_restriction;
    if (!IsInputAvailable(m_currentInputID, mplexid_restriction,
                          chanid_restriction))
    {
        LOG(VB_GENERAL, LOG_INFO, loc +
            QString("Requested channel '%1' is on input '%2' "
                    "which is in a busy input group")
                .arg(channum).arg(m_currentInputID));

        return false;
    }

    // Fetch tuning data from the database.
    QString tvformat, modulation, freqtable, freqid, si_std;
    int finetune;
    uint64_t frequency;
    int mpeg_prog_num;
    uint atsc_major, atsc_minor, mplexid, chanid, tsid, netid;

    if (!ChannelUtil::GetChannelData(
        (*it)->sourceid, chanid, channum,
        tvformat, modulation, freqtable, freqid,
        finetune, frequency,
        si_std, mpeg_prog_num, atsc_major, atsc_minor, tsid, netid,
        mplexid, m_commfree))
    {
        LOG(VB_GENERAL, LOG_ERR, loc + "Unable to find channel in database.");

        return false;
    }

    if ((mplexid_restriction && (mplexid != mplexid_restriction)) ||
        (chanid_restriction && (chanid != chanid_restriction)))
    {
        LOG(VB_GENERAL, LOG_ERR, loc +
            QString("Requested channel '%1' is not available because "
                    "the tuner is currently in use on another transport.")
                .arg(channum));

        return false;
    }

    // If the frequency is zeroed out, don't use it directly.
    if (frequency == 0)
    {
        frequency = (freqid.toULongLong() + finetune) * 1000;
        mplexid = 0;
    }
    bool isFrequency = (frequency > 10000000);
    bool hasTuneToChan =
        !(*it)->tuneToChannel.isEmpty() && (*it)->tuneToChannel != "Undefined";

    // If we are tuning to a freqid, rather than an actual frequency,
    // we may need to set the frequency table to use.
    if (!isFrequency || hasTuneToChan)
        SetFreqTable(freqtable);

    // Set NTSC, PAL, ATSC, etc.
    SetFormat(tvformat);

    // If a tuneToChannel is set make sure we're still on it
    if (hasTuneToChan)
        Tune((*it)->tuneToChannel, 0);

    // Clear out any old PAT or PMT & save version info
    uint version = 0;
    if (genPAT)
    {
        version = (genPAT->Version()+1) & 0x1f;
        delete genPAT; genPAT = NULL;
        delete genPMT; genPMT = NULL;
    }

    bool ok = true;
    if ((*it)->externalChanger.isEmpty())
    {
        if (IsIPTV())
        {
            int chanid = ChannelUtil::GetChanID((*it)->sourceid, channum);
            IPTVTuningData tuning = ChannelUtil::GetIPTVTuningData(chanid);
            if (!Tune(tuning, false))
            {
                LOG(VB_GENERAL, LOG_ERR, loc + "Tuning to IPTV URL");
                ClearDTVInfo();
                ok = false;
            }
        }
        else if ((*it)->name.contains("composite", Qt::CaseInsensitive) ||
                 (*it)->name.contains("s-video", Qt::CaseInsensitive))
        {
            LOG(VB_GENERAL, LOG_WARNING, loc + "You have not set "
                    "an external channel changing"
                    "\n\t\t\tscript for a composite or s-video "
                    "input. Channel changing will do nothing.");
        }
        else if (isFrequency && Tune(frequency, ""))
        {
        }
        else if (isFrequency)
        {
            // Initialize basic the tuning parameters
            DTVMultiplex tuning;
            if (!mplexid || !tuning.FillFromDB(tunerType, mplexid))
            {
                LOG(VB_GENERAL, LOG_ERR, loc +
                        "Failed to initialize multiplex options");
                ok = false;
            }
            else
            {
                // Try to fix any problems with the multiplex
                CheckOptions(tuning);

                // Tune to proper multiplex
                if (!Tune(tuning, (*it)->name))
                {
                    LOG(VB_GENERAL, LOG_ERR, loc + "Tuning to frequency.");

                    ClearDTVInfo();
                    ok = false;
                }
            }
        }
        else
        {
            // ExternalChannel justs wants the channum
            ok = freqid.isEmpty() && finetune == 0 ?
                 Tune(channum) : Tune(freqid, finetune);
        }
    }

    LOG(VB_CHANNEL, LOG_INFO, loc + ((ok) ? "success" : "failure"));

    if (!ok)
        return false;

    if (atsc_minor || (mpeg_prog_num>=0))
    {
        SetSIStandard(si_std);
        SetDTVInfo(atsc_major, atsc_minor, netid, tsid, mpeg_prog_num);
    }
    else if (IsPIDTuningSupported())
    {
        // We need to pull the pid_cache since there are no tuning tables
        pid_cache_t pid_cache;
        int chanid = ChannelUtil::GetChanID((*it)->sourceid, channum);
        ChannelUtil::GetCachedPids(chanid, pid_cache);
        if (pid_cache.empty())
        {
            LOG(VB_GENERAL, LOG_ERR, loc + "PID cache is empty");
            return false;
        }

        // Now we construct the PAT & PMT
        vector<uint> pnum; pnum.push_back(1);
        vector<uint> pid;  pid.push_back(9999);
        genPAT = ProgramAssociationTable::Create(0,version,pnum,pid);

        int pcrpid = -1;
        vector<uint> pids;
        vector<uint> types;
        pid_cache_t::iterator pit = pid_cache.begin();
        for (; pit != pid_cache.end(); ++pit)
        {
            if (!pit->GetStreamID())
                continue;
            pids.push_back(pit->GetPID());
            types.push_back(pit->GetStreamID());
            if (pit->IsPCRPID())
                pcrpid = pit->GetPID();
            if ((pcrpid < 0) && StreamID::IsVideo(pit->GetStreamID()))
                pcrpid = pit->GetPID();
        }
        if (pcrpid < 0)
            pcrpid = pid_cache[0].GetPID();

        genPMT = ProgramMapTable::Create(
            pnum.back(), pid.back(), pcrpid, version, pids, types);

        SetSIStandard("mpeg");
        SetDTVInfo(0,0,0,0,-1);
    }

    // Set the current channum to the new channel's channum
    m_curchannelname = channum;

    // Setup filters & recording picture attributes for framegrabing recorders
    // now that the new curchannelname has been established.
    if (m_pParent)
        m_pParent->SetVideoFiltersForChannel(GetCurrentSourceID(), channum);
    InitPictureAttributes();

    HandleScript(freqid);

    return ok;
}
示例#15
0
wxString sysProcess::ReadInputStream()
{
	if (IsInputAvailable())
		return ReadStream(GetInputStream());
	return wxEmptyString;
}
示例#16
0
bool DVBChannel::SetChannelByString(const QString &channum)
{
    QString tmp     = QString("SetChannelByString(%1): ").arg(channum);
    QString loc     = LOC + tmp;
    QString loc_err = LOC_ERR + tmp;

    VERBOSE(VB_CHANNEL, loc);

    if (!IsOpen())
    {
        VERBOSE(VB_IMPORTANT, loc_err + "Channel object "
                "will not open, cannot change channels.");

        ClearDTVInfo();
        return false;
    }

    ClearDTVInfo();

    QString inputName;
    if (!CheckChannel(channum, inputName))
    {
        VERBOSE(VB_IMPORTANT, loc_err +
                "CheckChannel failed.\n\t\t\tPlease verify the channel "
                "in the 'mythtv-setup' Channel Editor.");

        return false;
    }

    // If CheckChannel filled in the inputName we need to change inputs.
    if (!inputName.isEmpty() && (nextInputID == m_currentInputID))
        nextInputID = GetInputByName(inputName);

    // Get the input data for the channel
    int inputid = (nextInputID >= 0) ? nextInputID : m_currentInputID;
    InputMap::const_iterator it = m_inputs.find(inputid);
    if (it == m_inputs.end())
        return false;

    uint mplexid_restriction;
    if (!IsInputAvailable(inputid, mplexid_restriction))
    {
        VERBOSE(VB_IMPORTANT, loc_err + "Input is not available");
        return false;
    }

    // Get the input data for the channel
    QString tvformat, modulation, freqtable, freqid, si_std;
    int finetune;
    uint64_t frequency;
    int mpeg_prog_num;
    uint atsc_major, atsc_minor, mplexid, tsid, netid;

    if (!ChannelUtil::GetChannelData(
        (*it)->sourceid, channum,
        tvformat, modulation, freqtable, freqid,
        finetune, frequency,
        si_std, mpeg_prog_num, atsc_major, atsc_minor, tsid, netid,
        mplexid, m_commfree))
    {
        VERBOSE(VB_IMPORTANT, loc_err +
                "Unable to find channel in database.");

        return false;
    }

    if (mplexid_restriction && (mplexid != mplexid_restriction))
    {
        VERBOSE(VB_IMPORTANT, loc_err + "Multiplex is not available");
        return false;
    }

    // Initialize basic the tuning parameters
    DTVMultiplex tuning;
    if (!mplexid || !tuning.FillFromDB(tunerType, mplexid))
    {
        VERBOSE(VB_IMPORTANT, loc_err +
                "Failed to initialize multiplex options");

        return false;
    }

    SetDTVInfo(atsc_major, atsc_minor, netid, tsid, mpeg_prog_num);

    // Try to fix any problems with the multiplex
    CheckOptions(tuning);

    if (!Tune(tuning, inputid))
    {
        VERBOSE(VB_IMPORTANT, loc_err + "Tuning to frequency.");

        ClearDTVInfo();
        return false;
    }

    QString tmpX = channum; tmpX.detach();
    m_curchannelname = tmpX;

    VERBOSE(VB_CHANNEL, loc + "Tuned to frequency.");

    m_currentInputID = nextInputID;
    QString tmpY = m_curchannelname; tmpY.detach();
    m_inputs[m_currentInputID]->startChanNum = tmpY;

    return true;
}