- /*
- * 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
- # ifdef __BORLANDC__
- # define ARGVCAST(x) ((char * const *)(x))
- # else
- # define ARGVCAST(x) (x)
- # endif
- # endif
- # include <clientapi.h>
- # include <diff.h>
- # include <enviro.h>
- # include <echoctl.h>
- # include <signaler.h>
- # include <strops.h>
- # include <runcmd.h>
- # include <i18napi.h>
- # include <charcvt.h>
- # include <msgclient.h>
- # ifdef OS_MACOSX
- # include <CoreFoundation/CoreFoundation.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( "%s", msg.Text() );
- 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 ) );
- # ifdef USE_CR
- /*
- * Codewarrior (7.0?) oddity:
- * fprintf to file translates \n (10) -> \r.
- * fgets from file translates \n (10) <- \r.
- * fprintf to console translates \n (10) -> newline on screen.
- * fgets from stdin DOES NOT translate "enter".
- */
- if( buf.Length() && buf.End()[ -1 ] == '\r' )
- buf.End()[ -1 ] = '\n';
- # endif
- 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 );
- # ifdef OS_VMS
- // Note - VMS likes the args this way, Mac the other,
- // UNIX doesn't care.
- //
- fwrite( errBuf, strlen( errBuf ), 1, stderr );
- # else
- fwrite( errBuf, 1, strlen( errBuf ), stderr );
- # endif
- }
- void
- ClientUser::OutputInfo( char level, const char *data )
- {
- switch( level )
- {
- default:
- case '0': break;
- case '1': printf( "... " ); break;
- case '2': printf( "... ... " ); break;
- }
- # ifdef OS_VMS
- // Note - VMS likes the args this way, Mac the other,
- // UNIX doesn't care.
- //
- fwrite( data, strlen( data ), 1, stdout );
- # else
- fwrite( data, 1, strlen( data ), stdout );
- # endif
- fputc( '\n', stdout );
- }
- void
- ClientUser::OutputText( const char *data, int length )
- {
- # ifdef OS_VMS
- // Note - VMS likes the args this way, Mac the other,
- // UNIX doesn't care.
- //
- fwrite( data, length, 1, stdout );
- # else
- fwrite( data, 1, length, stdout );
- # endif
- }
- 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 * )
- {
- int n;
- int size = FileSys::BufferSize();
- buf->Clear();
- do {
- char *b = buf->Alloc( size );
- n = read( 0, b, size );
- buf->SetEnd( b + ( n >= 0 ? n : 0 ) );
- } 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.
- // Don't display "specFormatted" which is only relevant to us.
- for( i = 0; varList->GetVar( i, var, val ); i++ )
- {
- if( var == "func" || var == P4Tag::v_specFormatted ) 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 );
- Prompt( StrRef( "Hit return to continue..." ), buf, 0, e );
- }
- 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;
- }
- const char *editor = env->Get( "P4EDITOR" );
- if( !editor )
- editor = env->Get( "EDITOR" );
- # ifdef OS_NT
- if( !editor && !env->Get( "SHELL" ) )
- editor = "notepad";
- # endif
- # ifdef OS_VMS
- if( !editor && !env->Get( "POSIX$SHELL" ) )
- editor = "edit";
- # endif
- # ifdef OS_AS400
- if( !editor )
- editor = "edtf";
- # endif
- # ifdef OS_MACOSX
- // open flags: -W wait
- // -n create a new instance even if one is running
- // -t use the default text editor
- // -a use the specified editor
- if( !editor )
- editor = "open -Wnt";
- // It's possible P4EDITOR is set to an actual gui application bundle.
- // Passing bundles to RunCmd won't execute them.
- // Instead, we can prefix it with "open" so the system
- // will open it and bring it forward
- FileSys * editorFile = FileSys::Create( FST_BINARY );
- editorFile->Set( editor );
- StrBuf openCommand("open -Wna ");
- // 1 - the file must exist
- if ( editorFile->Stat() & FSF_EXISTS )
- {
- // 2 - in order to use 'open', the file must also point to a
- // bundled application. We check to see if there is a bundle
- // with an executable
- CFURLRef bundleURL = CFURLCreateFromFileSystemRepresentation(NULL,
- (const UInt8 *)editor, strlen(editor), true);
- CFBundleRef bundle = NULL;
- if ( bundleURL && (bundle = CFBundleCreate(NULL, bundleURL)) )
- {
- CFURLRef executableURL = CFBundleCopyExecutableURL( bundle );
- if ( executableURL )
- {
- CFRelease( executableURL );
- openCommand.Append( "\"" );
- openCommand.Append( editor );
- openCommand.Append( "\"" );
- editor = openCommand.Text();
- }
- }
- if ( bundle ) CFRelease( bundle );
- if ( bundleURL ) CFRelease( bundleURL );
- }
- # endif
- // Bill Joy rules, Emacs maggots!
- if( !editor )
- editor = "vi";
- RunCmd( editor, f1->Name(), 0, 0, 0, 0, 0, e );
- }
- void
- ClientUser::Diff( FileSys *f1, FileSys *f2, int doPage, char *df, Error *e )
- {
- Diff(f1, f2, NULL, doPage, df, e);
- }
- void
- ClientUser::Diff( FileSys *f1, FileSys *f2, FileSys *fout, int doPage, char *df, Error *e )
- {
- if( !f1->IsTextual() || !f2->IsTextual() )
- {
- if( f1->Compare( f2, e ) )
- {
- StrRef s( "(... files differ ...)\n" );
- if( fout )
- {
- fout->Open( FOM_WRITE, e );
- if( !e->Test() )
- {
- fout->Write( s, e );
- fout->Close( e );
- }
- }
- else
- {
- printf( "%s", s.Text() );
- }
- }
- return;
- }
- // Call diff to do text compare
- const char *diffunicode = NULL;
- const char *diff = enviro->Get( "P4DIFF" );
- const char *pager = enviro->Get( "P4PAGER" );
- int charset = 0;
- if( !diff )
- diff = enviro->Get( "DIFF" );
- if( f1->IsUnicode() )
- {
- diffunicode = enviro->Get( "P4DIFFUNICODE" );
- charset = f1->GetContentCharSetPriv();
- }
- if( !doPage )
- pager = 0;
- else if( !pager )
- pager = enviro->Get( "PAGER" );
- // Do our own diff
- // VMS ReadFile is busted.
- # ifdef OS_VMS
- if( !diff )
- diff = "diff";
- # else /* not VMS */
- if( !diffunicode && !diff )
- {
- // diff expects to read files in raw mode, we must
- // create new FileSys to allow this.
- FileSys *f1_bin = File( FST_BINARY );
- FileSys *f2_bin = File( FST_BINARY );
- int content_charset = f1->GetContentCharSetPriv();
- int output_translate = 0;
- if( f1->IsUnicode() &&
- content_charset != outputCharset &&
- content_charset != (int)CharSetApi::UTF_8 )
- {
- // convert files to utf-8
- f1_bin->SetDeleteOnClose();
- f1_bin->MakeGlobalTemp();
- f2_bin->SetDeleteOnClose();
- f2_bin->MakeGlobalTemp();
- CharSetCvt *cvt;
- cvt = CharSetCvt::FindCvt(
- (CharSetApi::CharSet)content_charset,
- CharSetApi::UTF_8 );
- // error out if converter not found? - should not happen
- f1->Translator( cvt );
- f1->Copy( f1_bin, FPM_RW, e );
- if( !e->Test() )
- {
- if( cvt ) cvt->ResetErr();
- f2->Translator( cvt );
- f2->Copy( f2_bin, FPM_RW, e );
- }
- delete cvt;
- if( outputCharset && outputCharset != (int)CharSetApi::UTF_8 )
- output_translate = 1;
- }
- else
- {
- if( f1->IsUnicode() &&
- outputCharset != content_charset )
- output_translate = 1;
- f1_bin->Set( f1->Name() );
- f2_bin->Set( f2->Name() );
- }
- if( !e->Test() )
- {
- DiffFlags flags( df );
- # ifndef OS_NEXT
- ::
- # endif
- Diff d;
- FileSys *t = NULL;
- d.SetInput( f1_bin, f2_bin, flags, e );
- int fileError = e->Test();
- if( !fileError || flags.type == DiffFlags::Unified )
- {
- if( fout )
- {
- t = fout;
- d.SetOutput( t->Name(), e );
- }
- else if( pager || output_translate )
- {
- // this does the work of FileSys::CreateGlobalTemp but
- // uses the client user object's FileSys create method
- t = File( FileSysType( ( f1->GetType() & FST_L_MASK )
- | FST_UNICODE ) );
- t->SetDeleteOnClose();
- t->MakeGlobalTemp();
- d.SetOutput( t->Name(), e );
- }
- else
- {
- d.SetOutput( stdout );
- }
- }
- if( fileError && flags.type == DiffFlags::Unified )
- {
- d.DiffUnifiedDeleteFile( f1_bin, e );
- d.CloseOutput( e );
- }
- else if( fileError )
- {
- d.CloseOutput( e );
- }
- else if( !fileError )
- {
- if( !e->Test() ) d.DiffWithFlags( flags );
- d.CloseOutput( e );
- if( output_translate )
- {
- // set translator
- CharSetCvt *cvt;
- cvt = CharSetCvt::FindCvt(
- CharSetApi::UTF_8,
- (CharSetApi::CharSet)outputCharset );
- // error out if converter not found?
- t->Translator( cvt );
- if( pager )
- {
- FileSys *tr = File( f1->GetType() );
- tr->SetDeleteOnClose();
- tr->MakeGlobalTemp();
- t->Copy( tr, FPM_RW, e );
- if( !fout )
- delete t;
- t = tr;
- }
- else if( !fout )
- {
- // read the file to stdout...
- t->Open( FOM_READ, e );
- if( !e->Test() )
- {
- char buf[2048];
- int i;
- while( (i = t->Read( buf, sizeof(buf), e )) > 0
- && !e->Test() )
- fwrite( buf, i, 1, stdout );
- t->Close( e );
- }
- }
- delete cvt;
- }
- if( pager && !e->Test() )
- RunCmd( pager, t->Name(), 0, 0, 0, 0, 0, e );
- if( !fout || pager )
- delete t;
- }
- }
- delete f1_bin;
- delete f2_bin;
- return;
- }
- # endif /* VMS */
- // Build up flags args
- // Yuk.
- if( !df || !*df )
- {
- if( diffunicode )
- RunCmd( diffunicode,
- CharSetApi::Name( (CharSetApi::CharSet)charset ),
- f1->Name(), f2->Name(), 0, 0, pager, e );
- else
- RunCmd( diff, f1->Name(), f2->Name(), 0, 0, 0, pager, e );
- }
- else
- {
- StrBuf flags;
- flags.Set( "-", 1 );
- flags << df;
- if( diffunicode )
- RunCmd( diffunicode, flags.Text(),
- CharSetApi::Name( (CharSetApi::CharSet)charset ),
- f1->Name(), f2->Name(), 0, pager, e );
- else
- RunCmd( diff, flags.Text(), f1->Name(), f2->Name(),
- 0, 0, pager, e );
- }
- }
- void
- ClientUser::Merge(
- FileSys *base,
- FileSys *leg1,
- FileSys *leg2,
- FileSys *result,
- Error *e )
- {
- char *merger;
- if( result->IsUnicode() )
- {
- int cs = result->GetContentCharSetPriv();
- if( cs != 0 && ( merger = enviro->Get( "P4MERGEUNICODE" ) ) )
- {
- RunCmd( merger, CharSetApi::Name( (CharSetApi::CharSet)cs ),
- base->Name(), leg1->Name(), leg2->Name(),
- result->Name(), 0, e );
- return;
- }
- }
- merger = enviro->Get( "P4MERGE" );
- if( !merger )
- merger = enviro->Get( "MERGE" );
- if( !merger )
- {
- e->Set( MsgClient::NoMerger );
- return;
- }
- RunCmd( merger, base->Name(), leg1->Name(),
- leg2->Name(), result->Name(), 0, 0, e );
- }
- void
- ClientUser::RunCmd(
- const char *command,
- const char *arg1,
- const char *arg2,
- const char *arg3,
- const char *arg4,
- const char *arg5,
- const char *pager,
- Error *e )
- {
- // XXX RunCommand is dynamically allocated
- // to work around linux 2.5 24x86 compiler bug.
- // see job019081.
- RunCommand *rc = new RunCommand;
- fflush( stdout );
- signaler.Block(); // reset SIGINT to SIGDFL
- // Use AddCmd() to handle command which may be a mix of
- // cmd name (with spaces on NT) and flags (following spaces).
- RunArgs cmd;
- cmd.AddCmd( command );
- if( arg1 ) cmd.AddArg( arg1 );
- if( arg2 ) cmd.AddArg( arg2 );
- if( arg3 ) cmd.AddArg( arg3 );
- if( arg4 ) cmd.AddArg( arg4 );
- if( arg5 ) cmd.AddArg( arg5 );
- if( pager )
- {
- cmd.AddArg( "|" );
- cmd.AddArg( pager );
- }
- rc->Run( cmd, e );
- delete rc;
- 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( e );
- }
- void
- ClientUser::Message( Error *err )
- {
- if( err->IsInfo() )
- {
- // Info
- StrBuf buf;
- err->Fmt( buf, EF_PLAIN );
- OutputInfo( (char)err->GetGeneric() + '0', buf.Text() );
- }
- else
- {
- // warn, failed, fatal
- HandleError( err );
- }
- }
- void
- ClientUser::SetOutputCharset( int charset )
- {
- outputCharset = charset;
- }
- void
- ClientUser::DisableTmpCleanup()
- {
- signaler.Disable();
- }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 7893 | Johan Nilsson | OFFLINE CHANGELIST 10 - SUBMITTED ON 2011/03/23 11:18:27 Upgrade project files to VS2010... and switching to msbuild for the entire project. Retargeted everything to .NET4 Client Profile for the time being, due to VS2010 C++ limitations (can't target anything other than 4.0 without complicating the setup too much). Shouldn't be too hard to retarget later if push comes to shove. Added VS2010 P4API stuff directly inside this project also to make things easier to get up and running for the moment. Removed old static P4API libraries. ____________________________________________________________ OFFLINE CHANGELIST 9 - SUBMITTED ON 2011/03/22 07:35:31 Converted to VS2010 « |
14 years ago |