/* * 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 # define NEED_STAT # 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 <clientprog.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> # include <msgserver.h> # ifdef OS_MACOSX # include <CoreFoundation/CoreFoundation.h> # endif int commandChaining = 0; // can be set by clientmain to support '-x run' /* * 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 ) { Prompt( msg, buf, noEcho, 0, e ); } void ClientUser::Prompt( const StrPtr &msg, StrBuf &buf, int noEcho, int noOutput, Error *e ) { if ( !noOutput ) printf( "%s", msg.Text() ); fflush( stdout ); int flushStdin = 1; # ifdef OS_NT struct _stat statBuf; _fstat( _fileno( stdin ), &statBuf ); if( statBuf.st_mode != _S_IFCHR ) flushStdin = 0; # endif if( flushStdin ) fflush( stdin ); // Turn off echoing NoEcho *setEcho = noEcho ? new NoEcho : 0; // Prompt'em buf.Clear(); char *b = buf.Alloc( 2048 ); if( !fgets( b, 2048, 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(); } # ifdef OS_NT if( buf.Length() && buf.End()[ -1 ] == '\r' ) { buf.SetEnd( buf.End() - 1 ); buf.Terminate(); } # endif } 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 ) { if( quiet ) return; 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 * ) { if( commandChaining ) { for( ;; ) { StrBuf lb; char *b = lb.Alloc( 2048 ); if( !fgets( b, 2048, stdin ) ) break; int l = strlen( b ); if( l > 0 && l <= 3 && b[0] == '.' && ( b[1] == '\r' || b[1] == '\n' ) ) break; buf->Append( b ); } buf->Terminate(); return; } 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 ); if( editFile.Length() > 0 ) { FileSys *f = File( FST_UNICODE ); f->Set( editFile ); f->Unlink( e ); delete f; editFile.Clear(); } } void ClientUser::Edit( FileSys *f1, Error *e ) { Edit( f1, enviro, e ); editFile.Set( f1->Name() ); f1->ClearDeleteOnClose(); } 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; int output = outputCharset; if( !diff ) diff = enviro->Get( "DIFF" ); if( f1->IsUnicode() ) { diffunicode = enviro->Get( "P4DIFFUNICODE" ); charset = f1->GetContentCharSetPriv(); if( !output && charset == f2->GetContentCharSetPriv() ) output = charset; } 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 != output && 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( output && output != (int)CharSetApi::UTF_8 ) output_translate = 1; } else { if( f1->IsUnicode() && output != 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)output ); // 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::ProgressIndicator() { return 0; } ClientProgress * ClientUser::CreateProgress( int ) { return NULL; } int ClientUserProgress::ProgressIndicator() { return 1; } ClientProgress * ClientUserProgress::CreateProgress( int t ) { return new ClientProgressText( t ); } int ClientUser::Resolve( ClientMerge *m, Error *e ) { // pun from ClientMergeStatus return (int)m->Resolve( e ); } int ClientUser::Resolve( ClientResolveA *r, int preview, Error *e ) { // pun from ClientMergeStatus return (int)r->Resolve( preview, e ); } void ClientUser::Message( Error *err ) { int keepfile = 0; if( err->IsInfo() ) { // Info StrBuf buf; err->Fmt( buf, EF_PLAIN ); OutputInfo( (char)err->GetGeneric() + '0', buf.Text() ); if( err->CheckId( MsgServer::SpecNotCorrect ) ) keepfile = 1; } else { // warn, failed, fatal HandleError( err ); // report file name left if( !err->CheckId( MsgServer::ErrorInSpec ) ) keepfile = 1; } if( editFile.Length() > 0 ) { if( keepfile ) { Error other; other.Set( MsgClient::FileKept ) << editFile.Text(); HandleError( &other ); } else { FileSys *f = File( FST_UNICODE ); f->Set( editFile ); f->Unlink( err ); delete f; } editFile.Clear(); } } void ClientUser::SetOutputCharset( int charset ) { outputCharset = charset; } void ClientUser::DisableTmpCleanup() { signaler.Disable(); } void ClientUser::SetQuiet() { quiet = 1; }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 16129 | tjuricek |
Rename/move files again... this time to the hyphenated-approach. |
||
//guest/tjuricek/file_system_client/main/vendor/p4api-15.1/macosx105x86_64/sample/clientuser.cc | |||||
#1 | 16119 | tjuricek | Rename/move to meet workshop project conventions. | ||
//guest/tjuricek/fsclient/vendor/p4api-15.1/macosx105x86_64/sample/clientuser.cc | |||||
#1 | 16118 | tjuricek |
FSClient initial version: handles add, edit This is a proof-of-concept app that mirrors an existing Perforce workspace to handle running commands like "p4 add" and "p4 edit" automatically when your apps add and write files. See the readme for more information. |