// // Copyright 1997 Nicholas J. Irias. All rights reserved. // // // P4FileStats.cpp #include "stdafx.h" #include "P4Win.h" #include "MainFrm.h" #include "P4FileStats.h" #include "GuiClient.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif // File actions, MUST match the enums in P4FileStats.h static LPCTSTR actions[]= { _T("none"), // not a Perforce type, just padding to match the enum in header _T("unknown"), _T("add"), _T("edit"), _T("delete"), _T("branch"), _T("integrate"), _T("import"), _T("no action"), 0 }; // File types, MUST match the enums in P4FileStats.h static LPCTSTR types[]= { _T("unknown"), _T("text"), _T("ctext"), _T("cxtext"), _T("ltext"), _T("ktext"), _T("ttext"), _T("xtext"), _T("xltext"), _T("kxtext"), _T("binary"), _T("tbinary"), _T("ubinary"), _T("xbinary"), _T("symlink"), _T("resource"), _T("tempobj"), _T("xtempobj"), _T("unicode"), _T("xunicode"), _T("utf16"), 0 }; IMPLEMENT_DYNCREATE(CP4FileStats, CObject) CP4FileStats::CP4FileStats() { Clear(); } void CP4FileStats::Clear() { m_MyOpenAction= m_OtherOpenAction= 0; m_MyLock= m_OtherLock= FALSE; m_OtherUserMyClient= FALSE; m_HeadRev= m_HaveRev= 0; m_HeadAction= 0; m_HeadType= m_Type= _T("unknown"); m_HeadTime=0; m_Unresolved=FALSE; m_Resolved=FALSE; m_OpenChangeNum=m_HeadChangeNum=0; m_OtherOpens=0; m_UserParam=0; m_NotInDepot=FALSE; m_FileSize=0; m_DepotPath=_T(""); m_ClientPath=_T(""); m_OtherUsers=_T(""); m_Digest=_T(""); } void CP4FileStats::Create( CP4FileStats *st ) { m_MyOpenAction= st->m_MyOpenAction; m_OtherOpenAction= st->m_OtherOpenAction; m_OtherUserMyClient= st->m_OtherUserMyClient; m_MyLock= st->m_MyLock; m_OtherLock= st->m_OtherLock; m_HeadRev= st->m_HeadRev; m_HaveRev= st->m_HaveRev; m_HeadTime= st->m_HeadTime; m_HeadAction= st->m_HeadAction; m_Type= st->m_Type; m_HeadType= st->m_HeadType; m_Unresolved= st->m_Unresolved; m_Resolved= st->m_Resolved; m_OpenChangeNum= st->m_OpenChangeNum; m_HeadChangeNum= st->m_HeadChangeNum; m_OtherOpens= st->m_OtherOpens; m_UserParam= st->m_UserParam; m_NotInDepot= st->m_NotInDepot; m_DepotPath= st->m_DepotPath; m_ClientPath= st->m_ClientPath; m_ClientPath.Replace(_T('/'), _T('\\')); m_OtherUsers= st->m_OtherUsers; m_Digest= st->m_Digest; m_FileSize= st->m_FileSize; } CP4FileStats::~CP4FileStats() { } // Create from an fstat result set. BOOL CP4FileStats::Create(StrDict *client) { int i; StrPtr *str; Error err; // Get the depot name str= client->GetVar( "depotFile", &err); // name in depot ASSERT(str || err.Test()); if(err.Test()) goto badFile; m_DepotPath = CharToCString(str->Value()); // If the client path exists, note that file is in client view str= client->GetVar( "clientFile" ); if(str) { m_ClientPath = CharToCString(str->Value()); m_ClientPath.Replace(_T('/'), _T('\\')); } else { // need to determine if the client path doesn't exist or doesn't translate // we can't handle the no translation case. CString txt = FormatError(&err); if(txt.Find(_T("No Translation")) == 0) goto badFile; // there is no client path m_ClientPath=_T(""); } // Concatenate a list of all other users with the file open { char varName[] = "otherOpen "; char varNam2[] = "otherAction "; for(m_OtherOpens=m_OtherOpenAction=0; m_OtherOpens < 100; m_OtherOpens++) { itoa(m_OtherOpens, varName+9, 10); if( (str=client->GetVar( varName )) == 0 ) break; else { if(m_OtherOpens==0) m_OtherUsers = CharToCString(str->Value()); else { m_OtherUsers+=_T("/"); m_OtherUsers+=CharToCString(str->Value()); } if (m_OtherOpenAction != F_DELETE) { itoa(m_OtherOpens, varNam2+11, 10); if ( (str=client->GetVar( varNam2 )) != 0) { for(i=F_UNKNOWNACTION; actions[i]; i++) { if(_tcscmp(actions[i], CharToCString(str->Value()))==0) { m_OtherOpenAction=(BYTE) i; break; } } } } } } } if( (str= client->GetVar( "headRev" )) != NULL) m_HeadRev=atol(str->Value()); if( (str= client->GetVar( "haveRev" )) != NULL) m_HaveRev=atol(str->Value()); if( (str= client->GetVar( "change" )) != NULL) m_OpenChangeNum=atol(str->Value()); if( (str= client->GetVar( "headChange" )) != NULL) m_HeadChangeNum=atol(str->Value()); if( (str= client->GetVar( "headTime" )) != NULL) m_HeadTime=atol(str->Value()); if( (str= client->GetVar( "ourLock" )) != NULL) m_MyLock=TRUE; if( (str= client->GetVar( "otherLock" )) != NULL) m_OtherLock=TRUE; if( (str= client->GetVar( "type" )) != NULL) m_Type= CharToCString(str->Value()); if( (str= client->GetVar( "headType" )) != NULL) m_HeadType= CharToCString(str->Value()); if( (str= client->GetVar( "headAction" )) != NULL) { for(i=F_UNKNOWNACTION; actions[i]; i++) { if(_tcscmp(actions[i], CharToCString(str->Value()))==0) { m_HeadAction=(BYTE) i; break; } } } ASSERT(client->GetVar("headAction")==NULL || m_HeadAction); if( (str= client->GetVar( "action" )) != NULL) { for(i=F_UNKNOWNACTION; actions[i]; i++) { if(_tcscmp(actions[i], CharToCString(str->Value()))==0) { m_MyOpenAction= (BYTE) i; break; } } } ASSERT(client->GetVar("action")==NULL || m_MyOpenAction); if (!m_HaveRev && !m_HeadRev && (m_MyOpenAction == F_ADD || m_MyOpenAction == F_BRANCH)) m_HaveRev = 1; if( (str= client->GetVar( "unresolved" )) != NULL) m_Unresolved=TRUE; str= client->GetVar( "actionOwner" ); if(str) { m_ActionOwner = CharToCString(str->Value()); if (Compare( m_ActionOwner, GET_P4REGPTR()->GetP4User() ) !=0) { m_OtherUserMyClient = TRUE; m_OtherUsers = m_ActionOwner + _T('@') + GET_P4REGPTR()->GetP4Client(); } } str= client->GetVar( "digest" ); if(str) m_Digest = CharToCString(str->Value()); if( (str= client->GetVar( "fileSize" )) != NULL) m_FileSize=atol(str->Value()); // In release builds, these values may be zero for an unrecognized // file type or action, which maps to F_UNKNOWNFILETYPE or F_UNKNOWNACTION // ASSERT(client->GetVar("headType")== NULL || m_HeadType); // commented out as useless and irritating in debug version - leighb 99/11/30 ASSERT(client->GetVar("headAction")==NULL || m_HeadAction); ASSERT(client->GetVar("action")==NULL || m_MyOpenAction); return TRUE; badFile: // most likely a translation failure. Nothing to do but ignore this file. return FALSE; } // This verion of Create() used to process info returned by P4 ADD // looks like : //depot/x_win32/samples/rpc/README.TXT#1 - opened for add BOOL CP4FileStats::Create(LPCTSTR depotName, long changeNumber) { CString line=depotName; int pound=m_DepotPath.ReverseFind(_T('#')); ASSERT(pound != -1); if(pound == 0) { ASSERT(0); return FALSE; } // not a line from P4 add m_DepotPath= line.Left(pound); // File revision m_HaveRev=_ttoi(depotName+pound+1); ASSERT(m_HaveRev==1); m_HeadRev=0; // Not in depot yet m_MyOpenAction=F_ADD; m_HeadAction=F_ADD; m_OpenChangeNum=changeNumber; m_HeadType=m_Type=types[F_UNKNOWNFILETYPE]; return TRUE; } // This version of Create() is only needed till open gets frobbed to // provide data in the fstat format. // // Parses a row returned by P4 opened, of the form: // "//depot/dir/subdir/fname#9 - edit change 25 (text) by user@machine *locked*" // "//depot/x_win32/samples/rpc/README.TXT#1 - add default change (text)" // "//depot/x_win32/embedded - dash/README.TXT#1 - add default change (text)" BOOL CP4FileStats::Create(LPCTSTR openRow) { // Find the revision delimiter '#', and then scan subsequent fileRow // characters for the separator, " - ". We need to look for the // rev number first, because " - " may be embedded within the filename. CString line=openRow; int pound= line.Find(_T('#')); if(pound == -1) { ASSERT(0); return FALSE; } // doesnt look like a fileRow int separator= pound+1; int len= line.GetLength(); for( ; separator < len ; separator++) { if(line[separator]==_T(' ') && line[separator+1]==_T('-') && line[separator+2]==_T(' ')) break; } if(separator == len) { ASSERT(0); return FALSE; } // doesnt look like a fileRow m_DepotPath=line.Left(pound); // File revision - note that this is stored under haveRev, no matter which user has the // file. // note, too that rev can be 0, if the opened command returns a version #null // (so remove the assert that used to be here) // long rev=_ttol(openRow+pound+1); m_HaveRev=rev; CString info=line.Mid(separator+3); CString ModeText=info.Left(info.Find(_T(" "))); // File open action int openAction=F_UNKNOWNACTION; for(int i=F_UNKNOWNACTION; actions[i]; i++) { if(_tcscmp(actions[i], ModeText)==0) { openAction=i; break; } } // File change number info=info.Mid(ModeText.GetLength()+1); if(info.Find(_T("default"))==0) { m_OpenChangeNum=0; // default change } else { if(info.Find(_T("change"))==0) { m_OpenChangeNum=_ttoi(info.Mid(7)); } } // File type info=info.Mid(info.Find(_T("("))+1); CString TypeText=info.Left(info.Find(_T(")"))); m_HeadType = m_Type = TypeText; info=info.Mid( min( info.GetLength()-1, TypeText.GetLength() + 2)); int byStart, userLen; if( (byStart=info.Find(_T("by"))) == 0) { info=info.Mid(byStart+3); // Skip over "by " userLen=info.Find(_T(" ")); if(userLen == -1) m_OtherUsers=info; else m_OtherUsers=info.Left(userLen); if( Compare( m_OtherUsers, GET_P4REGPTR()->GetMyID()) ==0 ) { m_OtherUsers.Empty(); m_OtherOpens=0; m_MyOpenAction= (BYTE) openAction; m_HaveRev= rev; if(info.Find(_T("locked")) > 0) m_MyLock=TRUE; } else { // See if its on my client int at= m_OtherUsers.Find(_T('@')); if( at != -1 && ++at < m_OtherUsers.GetLength() ) { if( Compare( m_OtherUsers.Mid(at), GET_P4REGPTR()->GetP4Client()) ==0 ) m_OtherUserMyClient= TRUE; } else // Why didnt we find client name ASSERT(0); // Update locked and open action info m_OtherOpens=1; if(info.Find(_T("locked")) > 0) m_OtherLock=TRUE; m_OtherOpenAction= (BYTE) openAction; } } else { // didnt find "by", so its my open file m_OtherOpens=0; m_MyOpenAction= (BYTE) openAction; m_HaveRev= rev; if(info.Find(_T("locked")) > 0) m_MyLock=TRUE; } return TRUE; } // This version of Create is used to create an entry // for a file that is not under Perforce control BOOL CP4FileStats::Create( LPCTSTR localsyntax, LPCTSTR depotsyntax ) { Clear(); m_DepotPath = depotsyntax; m_ClientPath= localsyntax; m_ClientPath.Replace(_T('/'), _T('\\')); m_NotInDepot= TRUE; return TRUE; } //////////////////////////////////// // Functions to allow updates to file status info void CP4FileStats::SetClosed() { SetOpenAction(0, FALSE); SetOpenAction(0, TRUE); } void CP4FileStats::SetLocked(BOOL locked, BOOL otherUser) { if(otherUser) m_OtherLock= (BYTE)locked; else m_MyLock= (BYTE)locked; } void CP4FileStats::SetOpenAction(int action, BOOL otherUser) { ASSERT(action >= 0 && action < F_MAXACTION); if(otherUser) { m_OtherOpenAction= (BYTE) action; if(action == 0) { m_Unresolved=FALSE; m_OtherLock= FALSE; m_OtherOpens=0; m_OtherUserMyClient=FALSE; } } else { m_MyOpenAction= (BYTE) action; if(action == 0) { m_Unresolved=FALSE; m_MyLock= FALSE; } } } void CP4FileStats::SetOtherOpens(int num) { m_OtherOpens=num; if(num==0) { m_OtherOpenAction=0; m_OtherUserMyClient=FALSE; SetLocked(FALSE, TRUE); } } void CP4FileStats::SetHeadAction(int action) { ASSERT(action >= 0 && action < F_MAXACTION); m_HeadAction= (BYTE) action; } void CP4FileStats::SetHeadType(int type) { ASSERT(type >= 0 && type < F_MAXTYPE); m_HeadType= types[type]; } void CP4FileStats::SetHeadType(LPCTSTR txttype) { ASSERT(txttype != NULL); ASSERT(_tcslen(txttype) > 0); m_HeadType= txttype; } void CP4FileStats::SetType(int type) { ASSERT(type >= 0 && type < F_MAXTYPE); m_Type= types[type]; } void CP4FileStats::SetType(LPCTSTR txttype) { ASSERT(txttype != NULL); ASSERT(_tcslen(txttype) > 0); m_Type= txttype; } void CP4FileStats::SetHaveRev(long rev) { ASSERT(rev >= 0); m_HaveRev=rev; if(m_HeadRev != 0 && m_HeadRev < rev) m_HeadRev= rev; } void CP4FileStats::SetDepotPath(LPCTSTR path) { ASSERT(_tcslen(path)==0 || _tcsncmp(path, _T("//"), 2) == 0 ); m_DepotPath= path; // If the depot path just got cleared, free the buffer if(path[0]==_T('\0')) m_DepotPath.FreeExtra(); } void CP4FileStats::SetClientPath(LPCTSTR path) { ASSERT(_tcslen(path)==0 || path[1]==_T(':')); m_ClientPath=path; m_ClientPath.Replace(_T('/'), _T('\\')); } // User list looks like: swine@cow/pig@vermin/spion@goon void CP4FileStats::SetOtherUsers(LPCTSTR userlist) { m_OtherUsers=userlist; m_OtherOpens=0; // If the list just got cleared, free the buffer if(userlist[0]==_T('\0')) { m_OtherUsers.FreeExtra(); return; } int hitSlash=TRUE; for(int i=0; i< m_OtherUsers.GetLength(); i++) { if(m_OtherUsers[i] == _T('/')) { ASSERT(!hitSlash); // two slashes without '@' in between hitSlash=TRUE; } if(m_OtherUsers[i] == _T('@')) { ASSERT(hitSlash); hitSlash=FALSE; m_OtherOpens++; } } } /////////////////////////////////////////////////////// // Data access members CString CP4FileStats::GetActionStr(int action) const { ASSERT(action >=0 && action < F_MAXACTION); return CString(actions[action]); } CString CP4FileStats::GetDepotDir() const { int slash=m_DepotPath.ReverseFind(_T('/')); if(slash != -1) return m_DepotPath.Left(slash+1); else { ASSERT(0); return CString(); } } CString CP4FileStats::GetClientDir() const { int slash=m_ClientPath.ReverseFind(_T('\\')); if(slash != -1) return m_ClientPath.Left(slash+1); else { ASSERT(0); return CString(); } } CString CP4FileStats::GetDepotFilename() const { int slash=m_DepotPath.ReverseFind(_T('/')); if(slash != -1) return m_DepotPath.Mid(slash+1); else { ASSERT(0); return CString(); } } CString CP4FileStats::GetClientFilename() const { int slash=m_ClientPath.ReverseFind(_T('\\')); if(slash != -1) return m_ClientPath.Mid(slash+1); else { ASSERT(0); return CString(); } } CString CP4FileStats::GetFormattedFilename(BOOL showFileType) const { CString filename = GET_P4REGPTR( )->ShowEntireDepot( ) <= SDF_DEPOT ? GetDepotFilename() : GetClientFilename(); // Format name + haveRev+headRev for display CString temp; if(m_HeadAction == F_DELETE) { // If the user has the file at < headrev, let the user know if( m_HaveRev > 0 && m_HaveRev < m_HeadRev ) temp.FormatMessage(IDS_FSTAT_s_n_n_s_HEAD_REV_DELETED, filename, m_HaveRev, m_HeadRev, m_HeadType); else if(showFileType) temp.FormatMessage(IDS_FSTAT_s_n_n_s_DELETED, filename, m_HaveRev, m_HeadRev, m_HeadType); else temp.FormatMessage(IDS_FSTAT_s_n_n_DELETED, filename, m_HaveRev, m_HeadRev); } else { if (!m_HeadRev && !m_HaveRev) temp = filename; else if(showFileType) temp.FormatMessage(IDS_FSTAT_s_n_n_s, filename, m_HaveRev, m_HeadRev, m_HeadType); else temp.FormatMessage(IDS_FSTAT_s_n_n, filename, m_HaveRev, m_HeadRev); } return temp; } CString CP4FileStats::GetFormattedChangeFile(BOOL showFileType, BOOL showOpenAction) const { // Format name + haveRev+headRev for display CString temp; int openAction= m_MyOpenAction; if(showOpenAction && m_OtherOpens && !m_MyOpenAction) { openAction= m_OtherOpenAction; } if(showFileType) { CString type = (m_Type == _T("unknown")) ? m_HeadType : m_Type; if(showOpenAction) temp.FormatMessage(IDS_FSTAT_s_n_s_s, m_DepotPath, m_HaveRev, type, actions[openAction]); else temp.FormatMessage(IDS_FSTAT_s_n_s, m_DepotPath, m_HaveRev, type); } else { if(showOpenAction) temp.FormatMessage(IDS_FSTAT_s_n_s, m_DepotPath, m_HaveRev, actions[openAction]); else temp.FormatMessage(IDS_FSTAT_s_n, m_DepotPath, m_HaveRev); } return temp; } CString CP4FileStats::GetFormattedHeadTime() { CString time; struct tm *t; if (!m_HeadTime && !m_HeadRev && !m_HaveRev) { time = _T(""); } else { if(m_HeadTime < 0) m_HeadTime = -m_HeadTime; t = localtime( (const time_t *)&m_HeadTime ); time.Format(_T("%04d/%02d/%02d %02d:%02d:%02d"), t->tm_year+1900, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); } return CString(time); } BOOL CP4FileStats::IsTextFile() const { CString type = m_HeadType.Find(_T("unknown")) == -1 ? m_HeadType : m_Type; return( ((type.Find(_T("text")) != -1) || (type.Find(_T("symlink")) != -1) || (type.Find(_T("unicode")) != -1) || (type.Find(_T("utf16")) != -1)) ? TRUE : FALSE ); } BOOL CP4FileStats::IsOtherOpenExclusive() const { if (IsOtherOpen()) { int i; CString fileType = GetHeadType(); if ((i = fileType.Find(_T('+'))) != -1) { if (fileType.Find(_T('l'),i) != -1) return TRUE; } } return FALSE; } BOOL CP4FileStats::IsMyOpenExclusive() const { if (IsMyOpen()) { int i; CString fileType = GetHeadType(); if ((i = fileType.Find(_T('+'))) != -1) { if (fileType.Find(_T('l'),i) != -1) return TRUE; } } return FALSE; }