clientuser.cpp #1

  • //
  • guest/
  • andrew_mcdonald/
  • p4hl/
  • src/
  • dlls/
  • clientuser.cpp
  • View
  • Commits
  • Open Download .zip Download (9 KB)
/*
 * Copyright 1995, 2000 Perforce Software.  All rights reserved.
 *
 * This file is part of Perforce - the FAST SCM System.
 */

/*
 * ClientUser -- default implementation of user interface for p4 client
 *
 * This file implements the p4 client command line interface by defining
 * the methods of the ClientUser class.  This interface reads from stdin,
 * writes to stdout and stderr, and invokes the user's editor.
 */

# define NEED_FILE

# ifdef OS_NT
# include <process.h>
# define NEED_FCNTL
# endif

# include <clientapi.h>

# include <diff.h>
# include <enviro.h>
# include <echoctl.h>
# include <signaler.h>
# include <strops.h>

# include <msgclient.h>

# if defined ( OS_MAC ) || defined ( OS_MACOSX )
# include "clientusermac.h"
# endif

/*
 * ClientUser::~ClientUser() - virtual destructor holder
 */

ClientUser::~ClientUser()
{
}

/*
 * ClientUser::Prompt() - prompt the user, and wait for a response.
 */

void
ClientUser::Prompt( const StrPtr &msg, StrBuf &buf, int noEcho, Error *e )
{
	printf( msg.Text() );
# ifdef OS_MAC
	fputc( '\n', stdout );
# endif

	fflush( stdout );
	fflush( stdin );

	// Turn off echoing

	NoEcho *setEcho = noEcho ? new NoEcho : 0;

	// Prompt'em

	buf.Clear();
	char *b = buf.Alloc( 1024 );

	if( !fgets( b, 1024, stdin ) )
	{
	    e->Set( MsgClient::Eof );
	    buf.SetEnd( b );
	}
	else
	{
	    // Set end and truncate any newline

	    buf.SetEnd( b += strlen( b ) );

	    if( buf.Length() && buf.End()[ -1 ] == '\n' )
	    {
		buf.SetEnd( buf.End() - 1 );
		buf.Terminate();
	    }
	}

	delete setEcho;
}

void
ClientUser::Help( const_char *const *help )
{
	for( ; *help; help++ )
	    printf( "%s\n", *help );
}

void
ClientUser::HandleError( Error *err )
{
	// Dumb implementation is just to format 
	// the error and call (old) OutputError.

	// API callers against 99.1+ servers can do better by
	// having HandleError probe 'err' directly for detail.

	StrBuf buf;
	err->Fmt( buf, EF_NEWLINE );
	OutputError( buf.Text() );
}

void
ClientUser::OutputError( const_char *errBuf )
{
	fflush( stdout );
	fwrite( errBuf, 1, strlen( errBuf ), stderr );
}

void
ClientUser::OutputInfo( char level, const_char *data )
{
	switch( level )
	{
	default:
	case '0': break;
	case '1': printf( "... " ); break;
	case '2': printf( "... ... " ); break;
	}

	fwrite( data, 1, strlen( data ), stdout );
	fputc( '\n', stdout );
}

void
ClientUser::OutputText( const_char *data, int length )
{
	fwrite( data, 1, length, stdout );
}

void
ClientUser::OutputBinary( const_char *data, int length )
{
# ifdef OS_NT
	// we rely on a trailing zero length buffer to
	// tell us to turn off binary output.

	if( binaryStdout == !length )
	{
	    // toggle
	    binaryStdout = !!length;
	    fflush( stdout );
	    setmode( fileno( stdout ), binaryStdout ? O_BINARY : O_TEXT );
	}
# endif

	fwrite( data, 1, length, stdout );
}

void
ClientUser::InputData( StrBuf *buf, Error *e )
{
	int n;

	buf->Clear();

	do {
# ifdef OS_MAC
	    // CW PRO 5 fread/read from stdin hangs machine
	    char *b = buf->Alloc( FileBufferSize + 1 );
	    n = fgets( b, FileBufferSize + 1, stdin ) ? strlen( b ) : 0;
# else
	    char *b = buf->Alloc( FileBufferSize );
	    n = read( 0, b, FileBufferSize);
# endif
	    buf->SetEnd( b + n );
	} while( n > 0 );

	buf->Terminate();
}

void
ClientUser::OutputStat( StrDict *varList )
{
	int i;
	StrBuf msg;
	StrRef var, val;

	// Dump out the variables, using the GetVar( x ) interface.
	// Don't display the function (duh), which is only relevant to rpc.

	for( i = 0; varList->GetVar( i, var, val ); i++ )
	{
	    if( var == "func" ) continue;

	    // otherAction and otherOpen go at level 2, as per 99.1 + earlier

	    msg.Clear();
	    msg << var << " " << val;
	    char level = strncmp( var.Text(), "other", 5 ) ? '1' : '2';
	    OutputInfo( level, msg.Text() );
	}

	// blank line

	OutputInfo( '0', "" );
}

void
ClientUser::ErrorPause( char *errBuf, Error *e )
{
	StrBuf buf;
	OutputError( errBuf );
# if defined( OS_MAC )
	Prompt( StrRef( "Hit enter to continue..." ), buf, 0, e );
# else
	Prompt( StrRef( "Hit return to continue..." ), buf, 0, e );
# endif
}

void
ClientUser::Edit( FileSys *f1, Error *e )
{
	Edit( f1, enviro, e );
}

void
ClientUser::Edit( FileSys *f1, Enviro * env, Error *e )
{
	// The presence of $SHELL on NT implies MKS, which has vi.
	// On VMS, we look for POSIX$SHELL - if set, we'll use vi.
	// Otherwise, EDIT.

	if( !f1->IsTextual() )
	{
	    e->Set( MsgClient::CantEdit ) << f1->Name();
	    return;
	}

# if defined( OS_MAC ) || defined ( OS_MACOSX )
	ClientUserMacEdit( f1, env, e, true );
# else
	StrBuf n;
	const char *editor;

	if( !( editor = env->Get( "P4EDITOR" ) ) )
	    editor = getenv( "EDITOR" );

# ifdef OS_NT
	if( !editor && !getenv( "SHELL" ) )
	    editor = "notepad";
# endif

# ifdef OS_VMS
	if( !editor && !getenv( "POSIX$SHELL" ) )
	    editor = "edit";
# endif

# ifdef OS_AS400
  if( !editor )
      editor = "edtf";
# endif

	// Bill Joy rules, Emacs maggots!

	if( !editor )
	    editor = "vi";

	RunCmd( editor, f1->Name(), 0, 0, 0, 0, e );

# endif /* OS_MAC */
}

void
ClientUser::Diff( FileSys *f1, FileSys *f2, int doPage, char *df, Error *e )
{
	if( !f1->IsTextual() || !f2->IsTextual() )
	{
	    if( f1->Compare( f2, e ) )
		printf( "(... files differ ...)\n" );
	    return;
	}

	// Call diff to do text compare

	const char *diff;
	const char *pager;

	if( !( diff = enviro->Get( "P4DIFF" ) ) )
	    diff = getenv( "DIFF" );

	if( !doPage )
	    pager = 0;
	else if( !( pager = enviro->Get( "P4PAGER" ) ) )
	    pager = getenv( "PAGER" );

	// Do our own diff
	// VMS ReadFile is busted.

# ifdef OS_VMS

	if( !diff )
	    diff = "diff";

# else /* not VMS */

	if( !diff )
	{
	    // diff expects to read files in raw mode, we must 
	    // create new FileSys to allow this.

	    FileSys *f1_bin = FileSys::Create( FST_BINARY );
	    f1_bin->Set( f1->Name() );

	    FileSys *f2_bin = FileSys::Create( FST_BINARY );
	    f2_bin->Set( f2->Name() );
	    
	    if( pager )
	    {
		FileSys *t = FileSys::CreateGlobalTemp( f1->GetType() );
		{
# ifndef OS_NEXT
		    ::
# endif
		    Diff d;
		    d.SetInput( f1_bin, f2_bin, df, e );
		    if( !e->Test() ) d.SetOutput( t->Name(), e );
		    if( !e->Test() ) d.DiffWithFlags( df );
		    d.CloseOutput( e );
		}

		if( !e->Test() ) RunCmd( pager, t->Name(), 0, 0, 0, 0, e );

		delete t;
	    }
	    else
	    {
# ifndef OS_NEXT
		::
# endif
	    	Diff d;

		d.SetInput( f1_bin, f2_bin, df, e );
		if( !e->Test() ) d.SetOutput( stdout );
		if( !e->Test() ) d.DiffWithFlags( df );
		d.CloseOutput( e );
	    }

	    delete f1_bin;
	    delete f2_bin;
	    return;
	}

# endif /* VMS */

	// Build up flags args
	// Yuk.

	if( !df || !*df )
	{
	    RunCmd( diff, f1->Name(), f2->Name(), 0, 0, pager, e );
	}
	else
	{
	    StrBuf flags;
	    flags.Set( "-", 1 );
	    flags << df;
	    RunCmd( diff, flags.Text(), f1->Name(), f2->Name(), 0, pager, e );
	}
}

void
ClientUser::Merge( 
	FileSys *base,
	FileSys *leg1,
	FileSys *leg2,
	FileSys *result,
	Error *e )
{
	char *merger;
	
	if( !( merger = enviro->Get( "P4MERGE" ) ) && 
	    !( merger = getenv( "MERGE" ) ) )
	{
	    e->Set( MsgClient::NoMerger );
	    return;
	}

	RunCmd( merger, base->Name(), leg1->Name(), 
		leg2->Name(), result->Name(), 0, e );
}

void
ClientUser::RunCmd(
	const char *command,
	const char *arg1,
	const char *arg2,
	const char *arg3,
	const char *arg4,
	const char *pager,
	Error *e )
{
	StrBuf n;

	fflush( stdout );

	signaler.Block();	// reset SIGINT to SIGDFL

# ifdef OS_MAC

	n << command;

	if( arg1 ) n << " '" << arg1 << "'";
	if( arg2 ) n << " '" << arg2 << "'";
	if( arg3 ) n << " '" << arg3 << "'";
	if( arg4 ) n << " '" << arg4 << "'";

	ToolServerExec( n.Text(), e );

# else
# ifdef OS_NT

	if( !pager )
	{
	    n << command;

	    const char * argv[32];
	    int c = StrOps::Words( n, (char **)argv, 32 );

	    if( arg1 ) argv[ c++ ] = arg1;
	    if( arg2 ) argv[ c++ ] = arg2;
	    if( arg3 ) argv[ c++ ] = arg3;
	    if( arg4 ) argv[ c++ ] = arg4;

	    argv[ c++ ] = 0;

	    if( spawnvp( _P_WAIT, argv[0], argv ) < 0 )
		e->Sys( argv[0], arg1 );

	    signaler.Catch();	// catch SIGINT again

	    return;
	}

# endif /* OS_NT */

# ifdef OS_VMS
# define QUOTE ""
# else
# ifdef OS_OS2
# define QUOTE "\""
# else
# define QUOTE "'"
# endif
# endif

	n << command;

	if( arg1 ) n << " " QUOTE << arg1 << QUOTE;
	if( arg2 ) n << " " QUOTE << arg2 << QUOTE;
	if( arg3 ) n << " " QUOTE << arg3 << QUOTE;
	if( arg4 ) n << " " QUOTE << arg4 << QUOTE;
	if( pager ) n << " | " << pager;

	system( n.Text() );

# endif /* OS_MAC */

	signaler.Catch();	// catch SIGINT again
}

FileSys *
ClientUser::File( FileSysType type )
{
	return FileSys::Create( type );
}

int
ClientUser::Resolve( ClientMerge *m, Error *e )
{
	// pun from ClientMergeStatus
	return (int)m->Resolve( this, e );
}

void
ClientUser::Message( Error *err )
{
	if( err->IsInfo() )
	{
	    // Info
	    StrBuf buf;
	    err->Fmt( buf, EF_PLAIN );
	    OutputInfo( err->GetGeneric() + '0', buf.Text() );
	}
	else
	{
	    // warn, failed, fatal
	    HandleError( err );
	}
}
# Change User Description Committed
#1 7292 Andrew McDonald initial submittal
//guest/sam_stafford/p4hl/src/dlls/clientuser.cpp
#2 1689 Sam Stafford Integrate 02.1 API and code cleanup to P4HL.
 Lots of work.  Phew.
#1 937 Sam Stafford Renaming my guest directory to the more conventional
sam_stafford.
//guest/samwise/p4hl/src/dlls/clientuser.cpp
#1 936 Sam Stafford Adding P4HL to the public depot.
 See relnotes.txt for
installation instructions; all relevant files are under
p4hl/dist.

Source code is under p4hl/src in the form of a VC++ project.