clientuser.cpp #1

  • //
  • guest/
  • sam_stafford/
  • p4hl/
  • src/
  • dlls/
  • clientuser.cpp
  • View
  • Commits
  • Open Download .zip Download (10 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 "strbuf.h"
# include "strops.h"
# include "strdict.h"
# include "enviro.h"
# include "error.h"

# include "signaler.h"
# include "filesys.h"
# include "diff.h"

# include "clientuser.h"
# include "clienterror.h"

# ifdef OS_MAC
# include <strarray.h>
# include "clientusermac.h"
# endif

/*
 * NoEcho - turn on/off echoing
 */

// These next few lines will get ported to hell

# ifdef OS_NT
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
typedef DWORD TIO_T;
# define hstdin (HANDLE)_get_osfhandle( fileno( stdin ) )
# define TIO_GET(tio) GetConsoleMode( hstdin, &tio )
# define TIO_SET(tio) SetConsoleMode( hstdin, tio )
# define TIO_NOECHO(tio) tio &= ~ENABLE_ECHO_INPUT
# endif

# if defined( OS_OS2 ) || defined( OS_MAC ) || \
	defined( OS_VMS ) || defined( OS_AS400 ) || \
	defined( OS_MPEIX )
// no ops
typedef int TIO_T;
# define hstdin fileno( stdin )
# define TIO_GET(tio) {}
# define TIO_SET(tio) {}
# define TIO_NOECHO(tio) {}
# endif

// For now, default to termios

# ifndef TIO_SET
# define USE_TERMIOS
# endif

# ifdef USE_TERMIOS
# include <termios.h>
typedef struct termios TIO_T;
# define TIO_GET(tio) tcgetattr( fileno( stdin ), &tio )
# define TIO_SET(tio) tcsetattr( fileno( stdin ), TCSANOW, &tio )
# define TIO_NOECHO(tio) tio.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL)
# endif

# ifdef USE_SGTTY
# include <sgtty.h>
typedef struct sgttyb TIO_T;
# define TIO_GET(tio) ioctl( fileno( stdin ), TIOCGETP, &tio )
# define TIO_SET(tio) ioctl( fileno( stdin ), TIOCSETP, &tio )
# define TIO_NOECHO(tio) tio.sg_flags &= ~(ECHO)
# endif

class NoEcho;

void EchoCleanup( NoEcho *noEcho );

class NoEcho {

    public:
	NoEcho()
	{
	    TIO_GET( tio );
	    tio2 = tio;
	    TIO_NOECHO( tio );
	    TIO_SET( tio );
	    signaler.OnIntr( (SignalFunc)EchoCleanup, this );
	}

	~NoEcho()
	{
	    TIO_SET( tio2 );
	    fputc( '\n', stdout );
	    signaler.DeleteOnIntr( this );
	}

    private:
	TIO_T tio, tio2;

} ;

void 
EchoCleanup( NoEcho *noEcho )
{
	delete noEcho;
}

/*
 * 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( ErrClient::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.AppendNull();
	    }
	}

	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 msg;
	err->Fmt( &msg );
	OutputError( msg.Text() );
}

void
ClientUser::OutputError( const_char *errBuf )
{
	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->AppendNull();
}

void
ClientUser::OutputStat( StrDict *varList )
{
	int i;
	StrBuf msg;
	StrPtr 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( ErrClient::CantEdit ) << f1->Name();
	    return;
	}

# ifdef OS_MAC
	EditWithExternalEditor( f1->Name(), env, e );
# 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 )
	{
	    if( pager )
	    {
		FileSys *t = FileSys::CreateGlobalTemp( f1->GetType() );

		{
# ifndef OS_NEXT
		    ::
# endif
		    Diff d;
		    d.SetInput( f1->Name(), f2->Name(), 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->Name(), f2->Name(), df, e );
		if( !e->Test() ) d.SetOutput( stdout );
		if( !e->Test() ) d.DiffWithFlags( df );
		d.CloseOutput( e );
	    }

	    return;
	}

# endif /* VMS */

	// Build up flags args
	// Yuk.

	if( !df || !*df )
	{
	    RunCmd( diff, 0, f1->Name(), f2->Name(), 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( ErrClient::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 );
}
# Change User Description Committed
#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.