You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdevelop/vcs/cvsservice/cvsfileinfoprovider.cpp

315 lines
12 KiB

/***************************************************************************
* 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 <tqregexp.h>
#include <tqtimer.h>
#include <kurl.h>
#include <kdebug.h>
#include <urlutil.h>
#include <kdevproject.h>
#include <dcopref.h>
#include <cvsjob_stub.h>
#include <cvsservice_stub.h>
#include "cvspart.h"
#include "cvsdir.h"
#include "cvsentry.h"
#include "cvsfileinfoprovider.h"
///////////////////////////////////////////////////////////////////////////////
// class CVSFileInfoProvider
///////////////////////////////////////////////////////////////////////////////
CVSFileInfoProvider::CVSFileInfoProvider( CvsServicePart *tqparent, CvsService_stub *cvsService )
: KDevVCSFileInfoProvider( tqparent, "cvsfileinfoprovider" ),
m_requestStatusJob( 0 ), m_cvsService( cvsService ), m_cachedDirEntries( 0 )
{
connect( this, TQT_SIGNAL(needStatusUpdate(const CVSDir&)), this, TQT_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.cacheableDirtqStatus();
}
return m_cachedDirEntries;
}
///////////////////////////////////////////////////////////////////////////////
bool CVSFileInfoProvider::requesttqStatus( 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.cacheableDirtqStatus();
printOutFileInfoMap( *m_cachedDirEntries );
/* FileTree will call requesttqStatus() everytime the user expands a directory
* Unfortunatly requesttqStatus() 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, TQT_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_filetqStatus( "tqStatus: (\\.|-|\\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,
filetqStatus,
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_filetqStatus.search( s ) >= 0) // FileName
{
fileName = rx_fileName.cap().replace( "File:", "" ).stripWhiteSpace();
filetqStatus = rx_filetqStatus.cap().replace( "tqStatus:", "" ).stripWhiteSpace();
++state; // Next state
kdDebug(9006) << ">> " << fileName << ", " << filetqStatus << 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( filetqStatus ) );
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"
// kate: space-indent on; indent-width 4; replace-tabs on;