/*************************************************************************** * Copyright (C) 2003 by Mario Scalas * * mario.scalas@libero.it * * * * This program 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 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "cvspart.h" #include "cvsdir.h" #include "cvsentry.h" #include "cvsfileinfoprovider.h" /////////////////////////////////////////////////////////////////////////////// // class CVSFileInfoProvider /////////////////////////////////////////////////////////////////////////////// CVSFileInfoProvider::CVSFileInfoProvider( CvsServicePart *parent, CvsService_stub *cvsService ) : KDevVCSFileInfoProvider( parent, "cvsfileinfoprovider" ), m_requestStatusJob( 0 ), m_cvsService( cvsService ), m_cachedDirEntries( 0 ) { connect( this, TQ_SIGNAL(needStatusUpdate(const CVSDir&)), this, TQ_SLOT(updateStatusFor(const CVSDir&))); } /////////////////////////////////////////////////////////////////////////////// CVSFileInfoProvider::~CVSFileInfoProvider() { if (m_requestStatusJob && m_requestStatusJob->isRunning()) m_requestStatusJob->cancel(); delete m_requestStatusJob; delete m_cachedDirEntries; } /////////////////////////////////////////////////////////////////////////////// const VCSFileInfoMap *CVSFileInfoProvider::status( const TQString &dirPath ) { // Same dir: we can do with cache ... if (dirPath != m_previousDirPath) { // ... different dir: flush old cache and cache new dir delete m_cachedDirEntries; CVSDir cvsdir( projectDirectory() + TQDir::separator() + dirPath ); m_previousDirPath = dirPath; m_cachedDirEntries = cvsdir.cacheableDirStatus(); } return m_cachedDirEntries; } /////////////////////////////////////////////////////////////////////////////// bool CVSFileInfoProvider::requestStatus( const TQString &dirPath, void *callerData, bool recursive, bool checkRepos ) { m_savedCallerData = callerData; if (m_requestStatusJob) { delete m_requestStatusJob; m_requestStatusJob = 0; } // Flush old cache if (m_cachedDirEntries) { delete m_cachedDirEntries; m_cachedDirEntries = 0; m_previousDirPath = dirPath; } if (!checkRepos) { kdDebug(9006) << "No repo check reqested; Just read CVS/Entries from: " << dirPath << endl; TQDir qd(projectDirectory()+TQDir::separator()+dirPath); CVSDir cdir(qd); if (cdir.isValid()) { emit needStatusUpdate(cdir); return true; } kdDebug(9006) << dirPath << " is not a valid cvs directory" << endl; return false; } // Fix a possible bug in cvs client: // When "cvs status" get's called nonrecursiv for a directory, it will // not print anything if the path ends with a slash. So we need to ensure // this here. TQString newPath = dirPath; if (newPath.endsWith("/")) newPath.truncate( newPath.length()-1 ); // path, recursive, tagInfo: hmmm ... we may use tagInfo for collecting file tags ... DCOPRef job = m_cvsService->status( newPath, recursive, false ); m_requestStatusJob = new CvsJob_stub( job.app(), job.obj() ); kdDebug(9006) << "Running command : " << m_requestStatusJob->cvsCommand() << endl; connectDCOPSignal( job.app(), job.obj(), "jobExited(bool, int)", "slotJobExited(bool, int)", true ); connectDCOPSignal( job.app(), job.obj(), "receivedStdout(TQString)", "slotReceivedOutput(TQString)", true ); return m_requestStatusJob->execute(); /* kdDebug(9006) << k_funcinfo << "Attempting to parse " << dirPath << " using CVS/Entries" << endl; TQDir qd(dirPath); CVSDir cdir(qd); if (cdir.isValid()) { emit needStatusUpdate(cdir); return true; }*/ } void CVSFileInfoProvider::propagateUpdate() { emit statusReady( *m_cachedDirEntries, m_savedCallerData ); } void CVSFileInfoProvider::updateStatusFor(const CVSDir& dir) { m_cachedDirEntries = dir.cacheableDirStatus(); printOutFileInfoMap( *m_cachedDirEntries ); /* FileTree will call requestStatus() everytime the user expands a directory * Unfortunatly requestStatus() will be called before the * VCSFileTreeViewItem of the directory will be filled with the files * it contains. Meaning, m_savedCallerData contains no childs at that * time. When a dcop call is made to run "cvs status" this is no problem. * The dcop call takes quit long, and so FileTree has enough time the fill * in the childs before we report the status back. * As far as the reading of the CVS/Entries file is very fast, * it will happen that we emit statusReady() here before the directory * item conains any childs. Therefor we need to give FileTree some time * to update the directory item before we give the status infos. */ TQTimer::singleShot( 1000, this, TQ_SLOT(propagateUpdate()) ); } /////////////////////////////////////////////////////////////////////////////// void CVSFileInfoProvider::slotJobExited( bool normalExit, int /*exitStatus*/ ) { kdDebug(9006) << "CVSFileInfoProvider::slotJobExited(bool,int)" << endl; if (!normalExit) return; // m_cachedDirEntries = parse( m_requestStatusJob->output() ); m_cachedDirEntries = parse( m_statusLines ); // Remove me when not debugging printOutFileInfoMap( *m_cachedDirEntries ); emit statusReady( *m_cachedDirEntries, m_savedCallerData ); } /////////////////////////////////////////////////////////////////////////////// void CVSFileInfoProvider::slotReceivedOutput( TQString someOutput ) { TQStringList strings = m_bufferedReader.process( someOutput ); if (strings.count() > 0) { m_statusLines += strings; } } /////////////////////////////////////////////////////////////////////////////// void CVSFileInfoProvider::slotReceivedErrors( TQString /*someErrors*/ ) { /* Nothing to do */ } /////////////////////////////////////////////////////////////////////////////// TQString CVSFileInfoProvider::projectDirectory() const { return owner()->project()->projectDirectory(); } /////////////////////////////////////////////////////////////////////////////// VCSFileInfoMap *CVSFileInfoProvider::parse( TQStringList stringStream ) { TQRegExp rx_recordStart( "^=+$" ); TQRegExp rx_fileName( "^File: (\\.|\\-|\\w)+" ); TQRegExp rx_fileStatus( "Status: (\\.|-|\\s|\\w)+" ); TQRegExp rx_fileWorkRev( "\\bWorking revision:" ); TQRegExp rx_fileRepoRev( "\\bRepository revision:" ); //TQRegExp rx_stickyTag( "\\s+(Sticky Tag:\\W+(w+|\\(none\\)))" ); //TQRegExp rx_stickyDate( "" ); // @todo but are they useful?? :-/ //TQRegExp rx_stickyOptions( "" ); //@todo TQString fileName, fileStatus, workingRevision, repositoryRevision, stickyTag, stickyDate, stickyOptions; VCSFileInfoMap *vcsStates = new VCSFileInfoMap; int state = 0; const int lastAcceptableState = 4; // This is where the dirty parsing is done: from a string stream representing the // 'cvs log' output we build a map with more useful strunctured data ;-) for (TQStringList::const_iterator it=stringStream.begin(); it != stringStream.end(); ++it) { TQString s = (*it).stripWhiteSpace(); kdDebug(9006) << ">> Parsing: " << s << endl; if (rx_recordStart.exactMatch( s )) state = 1; else if (state == 1 && rx_fileName.search( s ) >= 0 && rx_fileStatus.search( s ) >= 0) // FileName { fileName = rx_fileName.cap().replace( "File:", "" ).stripWhiteSpace(); fileStatus = rx_fileStatus.cap().replace( "Status:", "" ).stripWhiteSpace(); ++state; // Next state kdDebug(9006) << ">> " << fileName << ", " << fileStatus << endl; } else if (state == 2 && rx_fileWorkRev.search( s ) >= 0) { workingRevision = s.replace( "Working revision:", "" ).stripWhiteSpace(); TQRegExp rx_revision( "\\b(((\\d)+\\.?)*|New file!)" ); if (rx_revision.search( workingRevision ) >= 0) { workingRevision = rx_revision.cap(); kdDebug(9006) << ">> WorkRev: " << workingRevision << endl; ++state; } } else if (state == 3 && rx_fileRepoRev.search( s ) >= 0) { repositoryRevision = s.replace( "Repository revision:", "" ).stripWhiteSpace(); TQRegExp rx_revision( "\\b(((\\d)+\\.?)*|No revision control file)" ); if (rx_revision.search( s ) >= 0) { repositoryRevision = rx_revision.cap(); kdDebug(9006) << ">> RepoRev: " << repositoryRevision << endl; ++state; } } /* else if (state == 4 && rx_stickyTag.search( s ) >= 0) { stickyTag = rx_stickyTag.cap(); ++state; } */ else if (state >= lastAcceptableState) // OK, parsed all useful info? { // Package stuff, put into map and get ready for a new record VCSFileInfo vcsInfo( fileName, workingRevision, repositoryRevision, String2EnumState( fileStatus ) ); kdDebug(9006) << "== Inserting: " << vcsInfo.toString() << endl; vcsStates->insert( fileName, vcsInfo ); } } return vcsStates; } /////////////////////////////////////////////////////////////////////////////// VCSFileInfo::FileState CVSFileInfoProvider::String2EnumState( TQString stateAsString ) { // @todo add more status as "Conflict" and "Sticky" (but I dunno how CVS writes it so I'm going // to await until I have a conflict or somebody else fix it ;-) // @todo use TQRegExp for better matching since it seems strings have changed between CVS releases :-( // @todo a new state for 'Needs patch' if (stateAsString == "Up-to-date") return VCSFileInfo::Uptodate; else if (stateAsString == "Locally Modified") return VCSFileInfo::Modified; else if (stateAsString == "Locally Added") return VCSFileInfo::Added; else if (stateAsString == "Unresolved Conflict") return VCSFileInfo::Conflict; else if (stateAsString == "Needs Patch") return VCSFileInfo::NeedsPatch; else if (stateAsString == "Needs Checkout") return VCSFileInfo::NeedsCheckout; else return VCSFileInfo::Unknown; /// \FIXME exhaust all the previous cases first ;-) } /////////////////////////////////////////////////////////////////////////////// void CVSFileInfoProvider::printOutFileInfoMap( const VCSFileInfoMap &map ) { kdDebug(9006) << "Files parsed:" << endl; for (VCSFileInfoMap::const_iterator it = map.begin(); it != map.end(); ++it) { const VCSFileInfo &vcsInfo = *it; kdDebug(9006) << vcsInfo.toString() << endl; } } #include "cvsfileinfoprovider.moc"