clientservicer.cc #1

  • //
  • guest/
  • perforce_software/
  • p4/
  • 2014.2/
  • client/
  • clientservicer.cc
  • View
  • Commits
  • Open Download .zip Download (16 KB)
/*
 * Copyright 1995, 2001 Perforce Software.  All rights reserved.
 *
 * This file is part of Perforce - the FAST SCM System.
 */

# ifdef USE_EBCDIC
# define NEED_EBCDIC
# endif

# include <stdhdrs.h>

# include <strbuf.h>
# include <strdict.h>
# include <strops.h>
# include <strarray.h>
# include <strtable.h>
# include <error.h>
# include <mapapi.h>
# include <handler.h>
# include <rpc.h>
# include <md5.h>
# include <i18napi.h>
# include <charcvt.h>
# include <transdict.h>
# include <ignore.h>
# include <debug.h>
# include <tunable.h>

# include <p4tags.h>
# include <msgclient.h>
# include <msgsupp.h>

# include <filesys.h>
# include <pathsys.h>
# include <enviro.h>

# include <readfile.h>
# include <diffsp.h>
# include <diffan.h>
# include <diff.h>

# include "clientuser.h"
# include "client.h"
# include "clientprog.h"

# include "clientservice.h"

/*
 * ReconcileHandle - handle reconcile's list of files to skip when adding
 */

class ReconcileHandle : public LastChance {

    public:
			ReconcileHandle() 
			{ 
			    pathArray = new StrArray;
			    delCount = 0; 
			}
			~ReconcileHandle() 
			{ 
			    delete pathArray; 
			}

			StrArray *pathArray;
			int delCount;
} ;

void
clientReconcileFlush( Client *client, Error *e )
{
	// Delete the client's reconcile handle

	StrRef skipAdd( "skipAdd" );
	ReconcileHandle *recHandle =
			(ReconcileHandle *)client->handles.Get( &skipAdd );

	if( recHandle )
	    delete recHandle;
}

/*
 * clientReconcileEdit() -- "inquire" about file, for 'p4 reconcile'
 *
 * This routine performs clientCheckFile's scenario 1 checking, but
 * also saves the list of files that are in the depot so they can be
 * compared to the list of files on the client when reconciling later for add.
 *
 */

void
clientReconcileEdit( Client *client, Error *e )
{
	client->NewHandler();
	StrPtr *clientType = client->GetVar( P4Tag::v_type );
	StrPtr *digest = client->GetVar( P4Tag::v_digest );
	StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );
	StrPtr *fileSize = client->GetVar( P4Tag::v_fileSize );
	StrPtr *submitTime = client->GetVar( P4Tag::v_time );

	if( e->Test() && !e->IsFatal() )
	{
	    client->OutputError( e );
	    return;
	}

	const char *status = "exists";
	const char *ntype = clientType ? clientType->Text() : "text";

	// For adding files,  checkSize is a maximum (or use alt type)
	// For flush,  checkSize is an optimization check on binary files.

	offL_t checkSize = fileSize ? fileSize->Atoi64() : 0;

	/*
	 * If we do know the type, we want to know if it's missing.
	 * If it isn't missing and a digest is given, we want to know if
	 * it is the same.
	*/

	FileSys *f = ClientSvc::File( client, e );

	if( e->Test() || !f )
	    return;
	int statVal = f->Stat();

	// Save the list of depot files. We'll diff it against the list of all
	// files on client to find files to add in clientReconcileAdd

	StrRef skipAdd( "skipAdd" );
	ReconcileHandle *recHandle =
			(ReconcileHandle *)client->handles.Get( &skipAdd );

	if( !recHandle )
	{
	    recHandle = new ReconcileHandle;
	    client->handles.Install( &skipAdd, recHandle, e );

	    if( e->Test() )
		return;
	}

	if( !( statVal & ( FSF_SYMLINK|FSF_EXISTS ) ) )
	{
	    status = "missing";
	    recHandle->delCount++;
	} 
	else if ( ( !( statVal & FSF_SYMLINK ) && ( f->IsSymlink() ) ) 
                || ( ( statVal & FSF_SYMLINK ) && !( f->IsSymlink() ) ) ) 
	{
	    recHandle->pathArray->Put()->Set( f->Name() );
	}
	else if( digest )
	{
	    recHandle->pathArray->Put()->Set( f->Name() );
	    if( !checkSize || checkSize == f->GetSize() )
	    {
		StrBuf localDigest;
		f->Translator( ClientSvc::XCharset( client, FromClient ) );

		// Bypass expensive digest computation with -m unless the
		// local file is newer than the submit time. 

		if( !submitTime ||
		    ( submitTime && ( f->StatModTime() > submitTime->Atoi()) ) )
		{
		    f->Digest( &localDigest, e );

		    if( !e->Test() && !localDigest.XCompare( *digest ) )
			status = "same";
		}
		else if( submitTime )
		    status = "same";
	    }

	    // If we can't read the file (for some reason -- wrong type?)
	    // we consider the files different.

	    e->Clear();
	}

	delete f;

	// tell the server 

	client->SetVar( P4Tag::v_type, ntype );
	client->SetVar( P4Tag::v_status, status );
	client->Confirm( confirm );

	// Report non-fatal error and clear it.

	client->OutputError( e );
}

void
clientTraverseDirs( Client *client, const char *dir, int traverse,
		    int noIgnore, MapApi *map, StrArray *files, 
		    StrArray *sizes, Error *e )
{
	// Return all files in dir, and optionally traverse dirs in dir,
	// while checking each file against map before returning it

	// Scan the directory.

	FileSys *f = client->GetUi()->File( FST_BINARY );
	f->SetContentCharSetPriv( client->content_charset );
	f->Set( StrRef( dir ) );
	int fstat = f->Stat();

	Ignore *ignore = client->GetIgnore();
	StrPtr ignored = client->GetIgnoreFile();
	StrBuf from;
	StrBuf to;
	CharSetCvt *cvt = ( (TransDict *)client->transfname )->ToCvt();
	const char *fileName;

	// Use server case-sensitivity rules to find files to add

	from.SetCaseFolding( client->protocolNocase );

	// With unicode server and client using character set, we need
	// to send files back as utf8.

	if( client != client->translated )
	    fileName = cvt->FastCvt( f->Name(), strlen(f->Name()), 0 );
	else
	    fileName = f->Name();

	// If this is a file, not a directory, and not to be ignored,
	// save the filename 

	if( !( fstat & FSF_DIRECTORY ) )
	{
	    if( ( fstat & FSF_EXISTS ) || ( fstat & FSF_SYMLINK ) )
	    {
		if( noIgnore || !ignore->Reject( StrRef(f->Name()), ignored ) )
		{
		    files->Put()->Set( fileName );
		    sizes->Put()->Set( StrNum( f->GetSize() ) );
		}
	    }
	    delete f;
	    return;
	}

	// If this is a symlink to a directory, and not to be ignored,
	// return filename and quit

	if( ( fstat & FSF_SYMLINK ) && ( fstat & FSF_DIRECTORY ) )
	{
	    if( noIgnore || !ignore->Reject( StrRef(f->Name()), ignored ) )
	    {
		files->Put()->Set( fileName );
		sizes->Put()->Set( StrNum( f->GetSize() ) );
	    }
	    delete f;
	    return;
	}

	// This is a directory to be scanned.

	StrArray *a = f->ScanDir( e );

	if( e->Test() )
	{
	    // report error but keep moving

	    delete f;
	    client->OutputError( e );
	    return;
	}

	a->Sort(0);

	// PathSys for concatenating dir and local path,
	// to get full path

	PathSys *p = PathSys::Create();
	p->SetCharSet( f->GetCharSetPriv() );

	// For each directory entry.

	for( int i = 0; i < a->Count(); i++ )
	{
	    // Check mapping, ignore files before sending file or symlink back

	    p->SetLocal( StrRef( dir ), *a->Get(i) );
	    f->Set( *p );
	    int stat = f->Stat();

	    if( client != client->translated )
		fileName = cvt->FastCvt( f->Name(), strlen(f->Name()) );
	    else
		fileName = f->Name();

	    if( stat & FSF_DIRECTORY )
	    {
		if( stat & FSF_SYMLINK )
		{
		    from.Set( fileName );
		    from << "/";

		    if( !map->Translate( from, to, MapLeftRight ) )
			continue;

		    if( noIgnore || !ignore->Reject( StrRef(f->Name()),ignored))
		    {
			files->Put()->Set( fileName );
			sizes->Put()->Set( StrNum( f->GetSize() ) );
		    }
		}
		else if( traverse )
		    clientTraverseDirs( client, f->Name(), traverse, noIgnore,
					map, files, sizes, e );
	    }
	    else if( ( stat & FSF_EXISTS ) || ( stat & FSF_SYMLINK ) )
	    {
		from.Set( fileName );
		if( !map->Translate( from, to, MapLeftRight ) )
		    continue;

		if( noIgnore || !ignore->Reject( StrRef(f->Name()), ignored ) )
		{
		    files->Put()->Set( fileName );
		    sizes->Put()->Set( StrNum( f->GetSize() ) );
		}
	    }
	}

	delete p;
	delete a;
	delete f;
}

void
clientReconcileAdd( Client *client, Error *e )
{
	/*
	 * Reconcile add confirm
	 *
	 * Scans the directory (local syntax) and returns
	 * files in the directory using the full path.  This
	 * differs from clientScanDir in that it returns full
	 * paths, supports traversing subdirectories, and checks
	 * against a mapTable.
	 */

	client->NewHandler();
	StrPtr *dir = client->transfname->GetVar( P4Tag::v_dir, e );
	StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );
	StrPtr *traverse = client->GetVar( "traverse" );
	StrPtr *skipIgnore = client->GetVar( "skipIgnore" );
	StrPtr *mapItem;

	if( e->Test() )
	    return;

	MapApi *map = new MapApi;
	StrArray *files = new StrArray();
	StrArray *sizes = new StrArray();

	// Construct a MapTable object from the strings passed in by server

	for( int i = 0; mapItem = client->GetVar( StrRef("mapTable"), i ); i++)
	{
	    MapType m;
	    int j;
	    char *c = mapItem->Text();
	    switch( *c )
	    {
	    case '-': m = MapExclude; j = 1; break;
	    case '+': m = MapOverlay; j = 1; break;
	    default:  m = MapInclude; j = 0; break;
	    }
	    map->Insert( StrRef( c+j ), StrRef( c+j ), m );
	}

	clientTraverseDirs( client, dir->Text(), traverse != 0, skipIgnore != 0,
			    map, files, sizes, e );
	delete map;

	// If we have a list of files we know are in the depot already,
	// filter them out of our list of files to add

	StrRef skipAdd( "skipAdd" );
	ReconcileHandle *recHandle =
			(ReconcileHandle *)client->handles.Get( &skipAdd );

	if( recHandle )
	{
	    recHandle->pathArray->Sort(0);

	    int i1 = 0, i2 = 0, i0 = 0, l = 0;

	    while( i1 < files->Count() )
	    {
		if( i2 >= recHandle->pathArray->Count())
		    l = -1;
		else
		    l = files->Get( i1 )->SCompare( 
		        *recHandle->pathArray->Get( i2 ) );

		if( !l )
		{
		    ++i1;
		    ++i2;
		}
		else if( l < 0 )
		{
		    client->SetVar( P4Tag::v_file, i0, *files->Get( i1 ) );
		    if( recHandle->delCount )
		    {
			// Deleted files?  Send filesize info so the
			// server can try to pair up moves.

			client->SetVar( P4Tag::v_fileSize, 
					i0, *sizes->Get( i1 ) );
		    }
		    ++i0;
		    ++i1;
		}
		else
		{
		    ++i2;
		}
	    }
	}
	else
	{
	    for( int j = 0; j < files->Count(); j++ )
		client->SetVar( P4Tag::v_file, j, *files->Get(j) );
	}

	client->Confirm( confirm );
	delete files;
	delete sizes;
}

void
clientExactMatch( Client *client, Error *e )
{
	// Compare existing digest to list of
	// new client files, return match, or not.

	// Args:
	// type     = existing file type (clientpart)
	// digest   = existing file digest
	// fileSize = existing file size
	// charSet  = existing file charset
	// toFileN  = new file local path
	// indexN   = new file index
	// confirm  = return callback
	//
	// Return:
	// toFile   = exact match
	// index    = exact match

	client->NewHandler();
	StrPtr *clientType = client->GetVar( P4Tag::v_type );
	StrPtr *digest = client->GetVar( P4Tag::v_digest );
	StrPtr *fileSize = client->GetVar( P4Tag::v_fileSize );
	StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );

	if( e->Test() )
	    return;

	StrPtr *matchFile = 0;
	StrPtr *matchIndex = 0;
	FileSys *f = 0;

	for( int i = 0 ; 
	     client->GetVar( StrRef( P4Tag::v_toFile ), i ) ;
	     i++ )
	{
	    delete f;

	    StrVarName path = StrVarName( StrRef( P4Tag::v_toFile ), i );
	    f = ClientSvc::FileFromPath( client, path.Text(), e );

	    // If we encounter a problem with a file, we just don't return
	    // it as a match.  No need to blat out lots of errors.

	    if( e->Test() || !f )
	    {
		e->Clear();
		continue;
	    }

	    int statVal = f->Stat();

	    // Skip files that are symlinks when we
	    // aren't looking for symlinks.

	    if( ( !( statVal & ( FSF_SYMLINK|FSF_EXISTS ) ) )
		|| ( !( statVal & FSF_SYMLINK ) && ( f->IsSymlink() ) )  
                || ( ( statVal & FSF_SYMLINK ) && !( f->IsSymlink() ) ) )
		continue;

	    if( !digest )
	        continue;

	    StrBuf localDigest;
	    f->Translator( ClientSvc::XCharset( client, FromClient ) );
	    f->Digest( &localDigest, e );

	    if( e->Test() )
	    {
		e->Clear();
		continue;
	    }

	    if( !localDigest.XCompare( *digest ) )
	    {
		matchFile  = client->GetVar( StrRef(P4Tag::v_toFile), i );
		matchIndex = client->GetVar( StrRef(P4Tag::v_index), i );
		break; // doesn't get any better
	    }
	}

	delete f;

	if( matchFile && matchIndex )
	{
	    client->SetVar( P4Tag::v_toFile, matchFile );
	    client->SetVar( P4Tag::v_index, matchIndex );
	}

	client->Confirm( confirm );
}

void
clientOpenMatch( Client *client, ClientFile *f, Error *e )
{
	// Follow on from clientOpenFile, not called by server directly.

	// Grab RPC vars and attach them to the file handle so that
	// clientCloseMatch can use them for N-way diffing.

	StrPtr *fromFile = client->GetVar( P4Tag::v_fromFile, e );
	StrPtr *key	 = client->GetVar( P4Tag::v_key, e );
	StrPtr *flags    = client->GetVar( P4Tag::v_diffFlags );
	if( e->Test() )
	    return;
	
	f->matchDict = new StrBufDict;
	f->matchDict->SetVar( P4Tag::v_fromFile, fromFile );
	f->matchDict->SetVar( P4Tag::v_key, key );
	if( flags )
	    f->matchDict->SetVar( P4Tag::v_diffFlags, flags );
	for( int i = 0 ; ; i++ )
	{
	    StrPtr *index = client->GetVar( 
		    StrRef( P4Tag::v_index ),  i );
	    StrPtr *file  = client->GetVar( 
		    StrRef( P4Tag::v_toFile ), i );
	    if( !index || !file )
		break;
	    f->matchDict->SetVar( 
		   StrRef( P4Tag::v_index ),  i, *index );
	    f->matchDict->SetVar( 
		   StrRef( P4Tag::v_toFile ), i, *file );
	}
}

void
clientCloseMatch( Client *client, ClientFile *f1, Error *e )
{
	// Follow on from clientCloseFile, not called by server directly.

	// Compare temp file to existing client files.  Figure out the
	// best match, along with a quantitative measure of how good
	// the match was (lines matched vs total lines).  Stash it
	// in the handle so clientAckMatch can report it back.

	if( !f1->matchDict )
	{
	    e->Set( MsgSupp::NoParm ) << "clientCloseMatch";
	    return;
	}

	StrPtr *matchFile = 0;
	StrPtr *matchIndex = 0;

	StrPtr *fname;
	FileSys *f2 = 0;
	DiffFlags flags;
	if( StrPtr* diffFlags = f1->matchDict->GetVar( P4Tag::v_diffFlags ) )
	    flags.Init( diffFlags );

	int bestNum = 0;
	int bestSame = 0; 
	int totalLines = 0;

	for( int i = 0 ; 
	     fname = f1->matchDict->GetVar( StrRef( P4Tag::v_toFile ), i ) ;
	     i++ )
	{
	    delete f2;

	    f2 = client->GetUi()->File( f1->file->GetType() );
	    f2->SetContentCharSetPriv( f1->file->GetContentCharSetPriv() );
	    f2->Set( *fname );

	    if( e->Test() || !f2 )
	    {
		// don't care
		e->Clear();
		continue;
	    }

	    Sequence s1( f1->file, flags, e );
	    Sequence s2( f2,       flags, e );
	    if ( e->Test() )
	    {
		// still don't care
		e->Clear();
		continue;
	    }

	    DiffAnalyze diff( &s1, &s2 );

	    int same = 0;
	    for( Snake *s = diff.GetSnake() ; s ; s = s->next )
	    {
		same += ( s->u - s->x );
		if( s->u > totalLines )
		    totalLines = s->u;
	    }

	    if( same > bestSame )
	    {
		bestNum = i;
		bestSame = same;
	    }
	}

	delete f2;
	f1->file->Close( e );

	totalLines++; // snake lines start at zero

	if( bestSame )
	{
	    f1->matchDict->SetVar( P4Tag::v_index,
		f1->matchDict->GetVar( StrRef( P4Tag::v_index ), bestNum ) );
	    f1->matchDict->SetVar( P4Tag::v_toFile, 
		f1->matchDict->GetVar( StrRef( P4Tag::v_toFile ), bestNum ) );

	    f1->matchDict->SetVar( P4Tag::v_lower, bestSame );
	    f1->matchDict->SetVar( P4Tag::v_upper, totalLines );
	}

	// clientAckMatch will send this back
}

void
clientAckMatch( Client *client, Error *e )
{
	StrPtr *handle = client->GetVar( P4Tag::v_handle, e );
	StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );
	
	if( e->Test() )
	    return;

	// Get handle.

	ClientFile *f = (ClientFile *)client->handles.Get( handle, e );

	if( e->Test() )
	    return;

	// Fire everything back.

	StrPtr *fromFile = f->matchDict->GetVar( P4Tag::v_fromFile );
	StrPtr *key	 = f->matchDict->GetVar( P4Tag::v_key );
	StrPtr *toFile   = f->matchDict->GetVar( P4Tag::v_toFile );
	StrPtr *index    = f->matchDict->GetVar( P4Tag::v_index );
	StrPtr *lower    = f->matchDict->GetVar( P4Tag::v_lower );
	StrPtr *upper    = f->matchDict->GetVar( P4Tag::v_upper );

	if( fromFile && key )
	{
	    client->SetVar( P4Tag::v_fromFile,	fromFile );
	    client->SetVar( P4Tag::v_key,	key );
	}
	else
	{
	    e->Set( MsgSupp::NoParm ) << "fromFile/key";
	    return;
	}
	if( toFile && index && lower && upper )
	{
	    client->SetVar( P4Tag::v_toFile, toFile );
	    client->SetVar( P4Tag::v_index,  index );
	    client->SetVar( P4Tag::v_lower,  lower );
	    client->SetVar( P4Tag::v_upper,  upper );
	}

	client->Confirm( confirm );
	delete f;	    
}
# Change User Description Committed
#2 15901 Matt Attaway Clean up code to fit modern Workshop naming standards
#1 12189 Matt Attaway Initial (and much belated) drop of 2014.2 p4 source code