/******************************************************************************* Copyright (c) 1997-2004, Perforce Software, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTR IBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ /******************************************************************************* * Name : perlclientuser.cc * * Author : Tony Smith <tony@perforce.com> or <tony@smee.org> * * Description : Perl bindings for the Perforce API. User interface class * for getting Perforce results into Perl. * ******************************************************************************/ /* * Include math.h here because it's included by some Perl headers and on * Win32 it must be included with C++ linkage. Including it here prevents it * from being reincluded later when we include the Perl headers with C linkage. */ #ifdef OS_NT # include <math.h> #endif #include <clientapi.h> #include <spec.h> #include <diff.h> /* When including Perl headers, make sure the linkage is C, not C++ */ extern "C" { #include "EXTERN.h" #include "perl.h" #include "XSUB.h" } #ifdef Error // Defined by older versions of Perl to be Perl_Error # undef Error #endif #include "p4result.h" #include "perlclientuser.h" /******************************************************************************* * PerlClientUser - the user interface part. Gets responses from the Perforce * server, and converts the data to Perl format for returning to the caller. ******************************************************************************/ PerlClientUser::PerlClientUser() { debug = 0; input = 0; } void PerlClientUser::Reset(int merged ) { results.Reset( merged ); lastSpecDef.Clear(); // Leave input alone. } void PerlClientUser::Finished() { // Reset input coz we should be done with it now. Decrement the ref count // so it can be reclaimed. if ( debug && input ) printf( "[P4] Cleaning up saved input\n" ); if( input ) { sv_2mortal( input ); input = 0; } } void PerlClientUser::HandleError( Error *e ) { if ( debug ) printf( "[P4] HandleError()\n" ); results.AddError( e ); } void PerlClientUser::OutputText( const_char *data, int length ) { if ( debug ) printf( "[P4] OutputText()\n" ); results.AddOutput( data ); } void PerlClientUser::OutputInfo( char level, const_char *data ) { if ( debug ) printf( "[P4] OutputInfo()\n" ); results.AddOutput( data ); } void PerlClientUser::OutputBinary( const_char *data, int length ) { if ( debug ) printf( "[P4] OutputBinary() %d bytes\n", length ); // // Binary is just stored in a string. Since the char * version of // P4Result::AddOutput() assumes it can strlen() to find the length, // we'll make the String object here. // results.AddOutput( sv_2mortal( newSVpv( data, length) ) ); } void PerlClientUser::OutputStat( StrDict *values ) { StrPtr *spec, *data; // If both specdef and data are set, then we need to parse the form // and return the results. If not, then we just convert it as is. spec = values->GetVar( "specdef" ); data = values->GetVar( "data" ); if ( spec && data ) { if ( debug ) printf( "[P4] OutputStat() - parsing form\n" ); // // Save the spec definition for later retrieval by P4ClientApi // lastSpecDef = spec->Text(); // Parse up the form. Use the ParseNoValid() interface to prevent // errors caused by the use of invalid defaults for select items in // jobspecs. SpecDataTable specData; Spec s( spec->Text(), "" ); Error e; s.ParseNoValid( data->Text(), &specData, &e ); if ( e.Test() ) { HandleError( &e ); return; } results.AddOutput( DictToHash( specData.Dict(), spec ) ); } else { if ( debug ) printf( "[P4] OutputStat() - converting StrDict to hash\n" ); results.AddOutput( DictToHash( values, NULL ) ); } } /* * Diff support for Perl API. Since the Diff class only writes its output * to files, we run the requested diff putting the output into a temporary * file. Then we read the file in and add its contents line by line to the * results. */ void PerlClientUser::Diff( FileSys *f1, FileSys *f2, int doPage, char *diffFlags, Error *e ) { if ( debug ) printf( "[P4] Diff() - comparing files\n" ); // // Duck binary files. Much the same as ClientUser::Diff, we just // put the output into Perl space rather than stdout. // if( !f1->IsTextual() || !f2->IsTextual() ) { if ( f1->Compare( f2, e ) ) results.AddOutput( "(... files differ ...)" ); return; } // Time to diff the two text files. Need to ensure that the // files are in binary mode, so we have to create new FileSys // objects to do this. FileSys *f1_bin = FileSys::Create( FST_BINARY ); FileSys *f2_bin = FileSys::Create( FST_BINARY ); FileSys *t = FileSys::CreateGlobalTemp( f1->GetType() ); f1_bin->Set( f1->Name() ); f2_bin->Set( f2->Name() ); { // // In its own block to make sure that the diff object is deleted // before we delete the FileSys objects. // #ifndef OS_NEXT :: #endif Diff d; d.SetInput( f1_bin, f2_bin, diffFlags, e ); if ( ! e->Test() ) d.SetOutput( t->Name(), e ); if ( ! e->Test() ) d.DiffWithFlags( diffFlags ); d.CloseOutput( e ); // OK, now we have the diff output, read it in and add it to // the output. if ( ! e->Test() ) t->Open( FOM_READ, e ); if ( ! e->Test() ) { StrBuf b; while( t->ReadLine( &b, e ) ) results.AddOutput( b.Text() ); } } delete t; delete f1_bin; delete f2_bin; if ( e->Test() ) HandleError( e ); } /* * Prompt the user for input */ void PerlClientUser::Prompt( const StrPtr &msg, StrBuf &rsp, int noEcho, Error *e ) { if ( debug ) printf( "[P4] Prompt(). Using supplied input\n" ); InputData( &rsp, e ); } /* * convert input from the user into a form digestible to Perforce. This * involves either (a) converting any supplied hash to a Perforce form, or * (b) reading whatever we were given as a string. */ void PerlClientUser::InputData( StrBuf *strbuf, Error *e ) { if ( debug ) printf( "[P4] InputData(). Using supplied input\n" ); if( ! input ) { warn( "InputData() called with no supplied input" ); return; } // // Check that what we've got is a reference. It really ought to be // because of the way SetInput is coded, but just to make sure. // if( ! SvROK( input ) ) { warn( "Bad input data encountered! What did you pass to SetInput()?" ); return; } // // Now de-reference it and try to figure out if we're looking at a PV or // an HV // SV *s = SvRV( input ); if( !SvPOK( s ) ) { // // We have to assume it's a reference to a hash because there // seems to be no way to make sure... great. // HashToForm( (HV *)s, strbuf ); return; } strbuf->Set( SvPV_nolen( s ) ); } /* * Accept input from Perl for later use. We just save what we're given here * because we may not have the specdef available to parse it with at this time. * To deal with Perl's horrible reference count system, we create a new * reference here to whatever we're given. That way we'll increment the * reference count of the object when it's given to us, and we have to * decrement the refcount when we're done with this object. Ugly, but hey, * that's Perl! */ void PerlClientUser::SetInput( SV * i ) { if ( debug ) printf( "[P4] SetInput()\n" ); SV *t = i; if( SvROK( i ) ) t = SvRV( i ); input = newRV( t ); } /* * Convert a Perforce StrDict into a Perl hash. Convert multi-level * data (Files0, Files1 etc. ) into (nested) array members of the hash. If * specDef is NULL, then the specDef member will be skipped over, other * wise it will be saved as a wrapped structure in the hash. */ SV * PerlClientUser::DictToHash( StrDict *d, StrPtr *specDef ) { AV *av = 0; SV *rv = 0; SV **svp = 0; HV *hv = newHV(); int i; int seq; StrBuf key; StrRef var, val; StrPtr *data = d->GetVar( "data" ); for( i = 0; d->GetVar( i, var, val ); i++ ) { if( var == "specdef" && !specDef ) continue; if( var == "func" ) continue; InsertItem( hv, &var, &val ); } return newRV( sv_2mortal( (SV *)hv ) ); } // // Convert a perl hash into a flat Perforce form. // void PerlClientUser::HashToForm( HV *hv, StrBuf *b ) { HV *flatHv = 0; StrPtr *specdef = 0; if ( debug ) printf( "HashToForm: Converting hash input into a form.\n" ); specdef = varList->GetVar( "specdef" ); /* * Also need now to go through the hash looking for AV elements * as they need to be flattened before parsing. Yuk! */ if ( ! ( flatHv = FlattenHash( hv ) ) ) { warn( "Failed to convert Perl hash to Perforce form"); return; } if ( debug ) printf( "HashToForm: Flattened hash input.\n" ); SpecDataTable specData; Spec s( specdef->Text(), "" ); char *key; SV *val; I32 klen; for ( hv_iterinit( flatHv ); val = hv_iternextsv( flatHv, &key, &klen ); ) { if ( !SvPOK( val ) ) continue; specData.Dict()->SetVar( key, SvPV_nolen( val ) ); } s.Format( &specData, b ); if ( debug ) printf( "HashToForm: Form looks like this\n%s\n", b->Text() ); } /* * Split a key into its base name and its index. i.e. for a key "how1,0" * the base name is "how" and they index is "1,0" */ void PerlClientUser::SplitKey( const StrPtr *key, StrBuf &base, StrBuf &index ) { int i; base = *key; index = ""; // Start at the end and work back till we find the first char that is // neither a digit, nor a comma. That's the split point. for ( i = key->Length(); i; i-- ) { char prev = (*key)[ i-1 ]; if ( !isdigit( prev ) && prev != ',' ) { base.Set( key->Text(), i ); index.Set( key->Text() + i ); break; } } } /* * Insert an element into the response structure. The element may need to * be inserted into an array nested deeply within the enclosing hash. */ void PerlClientUser::InsertItem( HV *hv, const StrPtr *var, const StrPtr *val ) { SV **svp = 0; AV *av = 0; StrBuf base, index; StrRef comma( "," ); if ( debug ) printf( "\tInserting key %s, value %s \n", var->Text(), val->Text() ); SplitKey( var, base, index ); if ( debug ) printf( "\t\tbase=%s, index=%s\n", base.Text(), index.Text() ); // If there's no index, then we insert into the top level hash // but if the key is already defined then we need to rename the key. This // is probably one of those special keys like otherOpen which can be // both an array element and a scalar. The scalar comes last, so we // just rename it to "otherOpens" to avoid trashing the previous key // value if ( index == "" ) { svp = hv_fetch( hv, base.Text(), base.Length(), 0 ); if ( svp ) base.Append( "s" ); if ( debug ) printf( "\tCreating new scalar hash member %s\n", base.Text() ); hv_store( hv, base.Text(), base.Length(), newSVpv( val->Text(), val->Length() ), 0 ); return; } // // Get or create the parent AV from the hash. // svp = hv_fetch( hv, base.Text(), base.Length(), 0 ); if ( ! svp ) { if ( debug ) printf( "\tCreating new array hash member %s\n", base.Text() ); av = newAV(); hv_store( hv, base.Text(), base.Length(), newRV( (SV*)av) ,0 ); } if ( svp && ! SvROK( *svp ) ) { StrBuf msg; msg.Set( "Key (" ); msg.Append( base.Text() ); msg.Append( ") not a reference!" ); warn( msg.Text() ); return; } if ( svp && SvROK( *svp ) ) av = (AV *) SvRV( *svp ); // The index may be a simple digit, or it could be a comma separated // list of digits. For each "level" in the index, we need a containing // AV and an HV inside it. if ( debug ) printf( "\tFinding correct index level...\n" ); for( const char *c = 0 ; c = index.Contains( comma ); ) { StrBuf level; level.Set( index.Text(), c - index.Text() ); index.Set( c + 1 ); // Found another level so we need to get/create a nested AV // under the current av. If the level is "0", then we create a new // one, otherwise we just pop the most recent AV off the parent if ( debug ) printf( "\t\tgoing down...\n" ); svp = av_fetch( av, level.Atoi(), 0 ); if ( ! svp ) { AV *tav = newAV(); av_store( av, level.Atoi(), newRV( (SV*)tav) ); av = tav; } else { if ( ! SvROK( *svp ) ) { warn( "Not an array reference." ); return; } if ( SvTYPE( SvRV( *svp ) ) != SVt_PVAV ) { warn( "Not an array reference." ); return; } av = (AV *) SvRV( *svp ); } } if ( debug ) printf( "\tInserting value %s\n", val->Text() ); av_push( av, newSVpv( val->Text(), 0 ) ); } // Flatten array elements in a hash into something Perforce can parse. HV * PerlClientUser::FlattenHash( HV *hv ) { HV *fl; SV *val; char *key; I32 klen; if ( debug ) printf( "FlattenHash: Flattening hash contents\n" ); fl = (HV *)sv_2mortal( (SV *)newHV() ); for ( hv_iterinit( hv ); val = hv_iternextsv( hv, &key, &klen ); ) { if ( SvROK( val ) ) { /* Objects are not permitted in forms. Like it or lump it */ if ( sv_isobject( val ) ) { StrBuf msg; msg << key << " field contains an object. " << "Perforce forms may not contain Perl objects. " "Permitted types are strings, numbers and arrays"; warn( msg.Text() ); return NULL; } if ( SvTYPE( SvRV( val ) ) == SVt_PVAV ) { if ( debug ) printf( "FlattenHash: Flattening %s array\n", key ); // Flatten this array by constructing keys from the parent // hash key and the array index AV *av = (AV *)SvRV( val ); for ( int i = 0; i <= av_len( av ); i++ ) { StrBuf newKey; if ( debug ) printf( "Parsing element %d\n", i ); SV **elem = av_fetch( av, i, 0 ); if ( ! elem ) { StrBuf msg; msg << key << " field contains a bizarre array. " << "Array elements may only contain strings " << "and numbers."; warn( msg.Text() ); return NULL; } if ( debug ) printf( "Fetched element %d\n", i ); newKey.Set( key ); newKey << i; if ( debug ) printf( "Formatted element %d( %s )\n", i, newKey.Text() ); hv_store( fl, newKey.Text(), newKey.Length(), SvREFCNT_inc(*elem), 0 ); if ( debug ) printf( "Stored element %d\n", i ); } } } else { if ( debug ) printf( "FlattenHash: Found non-array member %s\n", key ); // Just store the element as is hv_store( fl, key, klen, SvREFCNT_inc(val), 0 ); } } return fl; }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 4669 | Raymond Danks | Create a branch of P4-3.4608. | ||
//guest/tony_smith/perforce/API/Perl/P4/lib/perlclientuser.cc | |||||
#3 | 4608 | Tony Smith |
Bug fix: The SetInput() method was omitted in the big rewrite so quite a lot was broken in builds 3.4579 and later. This change fixes that omission, and adds support for 'p4 login' too (that was how I discovered that SetInput() was missing). |
||
#2 | 4582 | Tony Smith |
Port new P4Perl architecture to Windows. Fixes a few porting issues and a couple of minor errors in the previous change. |
||
#1 | 4579 | Tony Smith |
Rewrite P4Perl to be more like P4Ruby. This change does away with the old P4/P4::Client split and pulls all the functionality of P4::Client into P4. Hence P4::Client is now deprecated. There are a few gotcha's - see the Changes file, and documentation for the details, but in general it's backwards compatible. As part of this change, I'm also releasing the previous current versions of P4 and P4::Client as released versions - for posterity. P4 now gets a v3.x version number so the old versions will stand out clearly. Hopefully it's all working - works fine for me - but people should consider this a beta build, for now at least. |