/* Copyright (C) Sven Erik Knop, Perforce Software, 2009 * */ #include <windows.h> #include <time.h> #include <clientapi.h> #include <i18napi.h> #include <strtable.h> #include <enviro.h> #include "PerforceClient.h" #include "PluginInterface.h" #include "resource.h" extern NppData nppData; extern HANDLE module; INT_PTR PluginDialogBox(WORD idDlg, DLGPROC lpDlgFunc) { return ::DialogBox( (HINSTANCE) module, MAKEINTRESOURCE(idDlg), nppData._nppHandle, lpDlgFunc ); } PerforceClient::PerforceClient( ) { TCHAR path[MAX_PATH]; ::SendMessage(nppData._nppHandle, NPPM_GETCURRENTDIRECTORY, 0, (LPARAM) path); // Since this is a Unicode wchar, I need to convert it to ANSI first for Perforce char cwd[MAX_PATH]; WideCharToMultiByte(CP_ACP, 0, path, -1, cwd, MAX_PATH, NULL, NULL); enviro = new Enviro; enviro->Config( StrRef(cwd) ); const char *lc; if( ( lc = enviro->Get( "P4CHARSET" )) ) { CharSetApi::CharSet cs = CharSetApi::NOCONV; cs = CharSetApi::Lookup( lc ); if( cs >= 0 ) { client.SetTrans( cs, cs, cs, cs ); } } client.SetCwd( cwd ); Error e; client.Init( &e ); if ( e.Test() ) { except( "Connection failed.", &e ); } } PerforceClient::~PerforceClient() { Error e; client.Final( &e ); delete enviro; } void PerforceClient::except(const char *msg, Error *e) { StrBuf error; e->Fmt( &error ); StrBuf message; message << msg << "\n" << error; int length = MultiByteToWideChar(CP_ACP, 0, message.Text(), -1, NULL , 0); LPWSTR wmessage = new WCHAR[length]; (void) MultiByteToWideChar(CP_ACP, 0, message.Text(), -1, wmessage , length); ::MessageBox(nppData._nppHandle, wmessage, TEXT("Perforce error"), MB_OK); delete[] wmessage; } class OutputClient : public ClientUser { public: OutputClient() : error(false), expired(false), invalid(false) { } virtual void OutputInfo( char level, const char *data ) { buf << data << "\n"; } virtual void HandleError( Error *e ) { error = true; err = *e; ErrorId * id = e->GetId(0); errFmt = id->fmt; // 807672853 - ticket invalid // 805575718 - password invalid // 805575992 - session has expired // 805576135 - password expired // 268442937 - no login necessary switch ( id->code ) { case 807672853: case 805575992: expired = true; break; case 805575718: invalid = true; break; default: ; // pass - nothing to do } } virtual void OutputStat( StrDict *varList ) { StrRef var, val; for( int i = 0; varList->GetVar( i, var, val ); i++ ) { if( var == "func" || var == P4Tag::v_specFormatted ) continue; buf << var << " ... " << val << "\n"; dict.VSetVar(var, val); } } virtual void Prompt(const StrPtr &msg, StrBuf &rsp, int noEcho, Error *e) { rsp = input; } void setInput(const StrBuf& buf) { input = buf; } bool errorExists() const { return error; } bool ticketExpired() const { return expired; } bool passwordInvalid() const { return invalid; } const StrBuf& getError() const { return errFmt; } const StrBuf& getBuf() const { return buf; } StrDict& getDict() { return dict; } void reset() { expired = false; invalid = false; error = false; errFmt.Clear(); buf.Clear(); dict.Clear(); err.Clear(); } private: bool expired; bool invalid; bool error; StrBuf errFmt; StrBuf buf; StrBuf input; StrBufDict dict; Error err; }; void PerforceClient::showInfo() { StrBuf result; { OutputClient cu; client.Run("info", &cu); result = cu.getBuf(); result << "\n\r"; } { OutputClient cu; int argc = 1; char * argv[] = { "-s" }; client.SetArgv(argc, argv); client.Run("login", &cu); if ( cu.getBuf() != "") { result << cu.getBuf(); } else { result << "Not logged in" << "\n\r"; } } displayMessage(TEXT("Perforce connection info"), result); } BOOL AnyWindow_CenterWindow(HWND hWnd, HWND hParentWnd, BOOL bRepaint) { RECT rectParent; RECT rect; INT height, width; INT x, y; ::GetWindowRect(hParentWnd, &rectParent); ::GetWindowRect(hWnd, &rect); width = rect.right - rect.left; height = rect.bottom - rect.top; x = ((rectParent.right - rectParent.left) - width) / 2; x += rectParent.left; y = ((rectParent.bottom - rectParent.top) - height) / 2; y += rectParent.top; return ::MoveWindow(hWnd, x, y, width, height, bRepaint); } void LoginDlg_OnInitDialog(HWND hDlg) { AnyWindow_CenterWindow(hDlg, nppData._nppHandle, FALSE); } static StrBuf providedPassword; bool LoginDlg_OnOK(HWND hDlg) { TCHAR password[1024]; HWND hEd = GetDlgItem(hDlg, IDC_PASSWORD); if ( hEd ) { GetWindowText(hEd, password, 1023); char pwd[1024]; WideCharToMultiByte(CP_ACP, 0, password, -1, pwd, 1024, NULL, NULL); providedPassword = pwd; return true; } return false; } INT_PTR CALLBACK LoginDlgProc( HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam ) { if ( uMessage == WM_COMMAND ) { switch ( LOWORD(wParam) ) { case IDOK: LoginDlg_OnOK(hDlg); EndDialog(hDlg, 1); // OK - returns 1 return 1; case IDCANCEL: providedPassword = ""; EndDialog(hDlg, 0); // Cancel - returns 0 return 1; default: break; return 0; } } else if ( uMessage == WM_NOTIFY ) { } else if ( uMessage == WM_INITDIALOG ) { LoginDlg_OnInitDialog(hDlg); } return 0; } bool PerforceClient::login() { providedPassword = ""; PluginDialogBox(IDD_LOGIN, LoginDlgProc); if ( providedPassword != "") { // attempt to log in with this password OutputClient ui; ui.setInput(providedPassword); client.Run("login", &ui); if ( ui.passwordInvalid() ) { displayMessage(TEXT("Login failure"), ui.getError()); } else if ( ui.errorExists() ) { displayMessage(TEXT("Problem"), ui.getError()); } else { displayMessage(TEXT("Login result"), ui.getBuf()); return true; } } return false; } void PerforceClient::logout() { OutputClient ui; client.Run("logout",&ui); if ( ui.errorExists() ) { displayMessage(TEXT("Problem"), ui.getError()); } else { displayMessage(TEXT("Logout result"), ui.getBuf()); } } void PerforceClient::callP4V(const char * cmd) { TCHAR path[MAX_PATH]; ::SendMessage(nppData._nppHandle, NPPM_GETFULLCURRENTPATH, 0, (LPARAM)path); char file[MAX_PATH]; WideCharToMultiByte(CP_ACP, 0, path, -1, file, MAX_PATH, NULL, NULL); StrBuf cmdLine("p4vc.exe"); cmdLine << " -p " << client.GetPort(); cmdLine << " -c " << client.GetClient(); cmdLine << " -u " << client.GetUser(); cmdLine << " " << cmd; if (strcmp(cmd, "submit") != 0) { cmdLine << " \"" << file << "\""; } // OK, here we actually are going to call P4VC. // displayMessage(TEXT("P4VC"), cmdLine); PROCESS_INFORMATION procInfo; STARTUPINFO startInfo; GetStartupInfo( &startInfo ); startInfo.lpReserved = startInfo.lpDesktop = NULL; startInfo.dwFlags |= STARTF_USESHOWWINDOW; startInfo.wShowWindow = SW_SHOWNORMAL; int length = MultiByteToWideChar(CP_ACP, 0, cmdLine.Text(), -1, NULL , 0); LPWSTR cmdString = new WCHAR[length]; (void) MultiByteToWideChar(CP_ACP, 0, cmdLine.Text(), -1, cmdString , length); CreateProcess( NULL, cmdString, //da command NULL, NULL, //evil windows "security" stuff FALSE, NORMAL_PRIORITY_CLASS,//inheritance & creation flags NULL, NULL, //default env and current dir &startInfo, &procInfo ); CloseHandle( procInfo.hThread ); } bool PerforceClient::processCmd(OutputClient * ui, int argc, char* argv[], const char * cmd) { bool retry = true; do { ui->reset(); client.SetArgv( argc, argv ); client.Run(cmd, ui); if( ui->ticketExpired() ) { if ( login() ) continue; } if( ui->errorExists() ) { displayMessage(TEXT("Perforce error"), ui->getError()); return false; } retry = false; } while ( retry ); return true; } void PerforceClient::showFileInfo() { TCHAR path[MAX_PATH]; ::SendMessage(nppData._nppHandle, NPPM_GETFULLCURRENTPATH, 0, (LPARAM)path); char file[MAX_PATH]; WideCharToMultiByte(CP_ACP, 0, path, -1, file, MAX_PATH, NULL, NULL); int argc = 2; char * argv[] = { "-l", file }; OutputClient ui; processCmd(&ui, argc, argv, "fstat"); StrBuf buf; StrDict &dict = ui.getDict(); if ( dict.GetVar("depotFile") ) { buf << "Depot File :\t" << dict.GetVar("depotFile") << "\n"; buf << "Client File :\t" << dict.GetVar("clientFile") << "\n"; if ( dict.GetVar("haveRev") && dict.GetVar("headRev") ) { buf << "Revision : \t\t#" << dict.GetVar("haveRev") << " of " << dict.GetVar("headRev") << "\n"; buf << "Change : \t\t" << dict.GetVar("headChange") << "\n"; const size_t MAX_DATE = 128; char dateBuffer[MAX_DATE]; time_t rawtime = dict.GetVar("headTime")->Atoi(); struct tm timeinfo; localtime_s(&timeinfo, &rawtime); strftime(dateBuffer, MAX_DATE, "%#c", &timeinfo); buf << "Last changed : \t" << dateBuffer << "\n"; buf << "Type : \t\t" << dict.GetVar("headType") << "\n"; } else { buf << "Revision : \t\t#0/0\n"; buf << "Type : \t\t" << dict.GetVar("type") << "\n"; } if( dict.GetVar("fileSize") ) { buf << "File size : \t\t" << dict.GetVar("fileSize") << "\n"; } StrBuf openBy("Opened by "); if( dict.GetVar("action") ) { buf << "\nOpen for " << dict.GetVar("action") << " - change " << dict.GetVar("change") << " (" << dict.GetVar("type") << ")\n"; openBy = "\tAlso opened by "; } if( dict.GetVar("otherOpen") ) { int otherOpen = atoi(dict.GetVar("otherOpen")->Text()); buf << "\n"; for( int i = 0; i < otherOpen; i++) { StrBuf name("otherOpen"); StrBuf action("otherAction"); name << i; action << i; buf << openBy << dict.GetVar(name) << " for " << dict.GetVar(action) << "\n"; } } } else { buf << "Not stored in Perforce"; } displayMessage(TEXT("Perforce file info"), buf /*ui.getBuf() */); } void PerforceClient::processFile(const char *cmd) { TCHAR path[MAX_PATH]; ::SendMessage(nppData._nppHandle, NPPM_GETFULLCURRENTPATH, 0, (LPARAM)path); char file[MAX_PATH]; WideCharToMultiByte(CP_ACP, 0, path, -1, file, MAX_PATH, NULL, NULL); int argc = 1; char * argv[] = { file }; OutputClient ui; processCmd(&ui, argc, argv, cmd); //if( !strcmp(cmd, "sync") ) { // int currentBufferId = ::SendMessage(nppData._nppHandle, NPPM_GETCURRENTBUFFERID, 0, 0); // (void) ::SendMessage(nppData._nppHandle, NPPM_RELOADBUFFERID, currentBufferId, 0); //} //else { // refresh Notepad++ - currently a hack ::SendMessage(nppData._nppHandle, NPPMSG + 53, TRUE, 0); //} } bool PerforceClient::displayMessage(LPWSTR title, const StrRef &buf, bool allowCancel) { UINT type = allowCancel ? MB_OKCANCEL : MB_OK; int length = MultiByteToWideChar(CP_ACP, 0, buf.Text(), -1, NULL , 0); LPWSTR wmessage = new WCHAR[length]; (void) MultiByteToWideChar(CP_ACP, 0, buf.Text(), -1, wmessage , length); int result = ::MessageBox(nppData._nppHandle, wmessage, title, type); delete[] wmessage; return (result == IDOK); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#13 | 11396 | Sven Erik Knop |
Fixed login issue from file command (endless loop). Bumped version to 1.0.7 Final version in this iteration (I hope). |
||
#12 | 11391 | Sven Erik Knop | Added login status to connection info output. | ||
#11 | 11384 | Sven Erik Knop | Added logout menu item (more for testing than practical use) | ||
#10 | 11381 | Sven Erik Knop |
Added login dialog (after many years of waiting) Currently needs to log on in a separate step, next change should prompt for password if ticket expired in any case. Also fixed a bug with showing file information for freshly added files. |
||
#9 | 10986 | Sven Erik Knop |
Updated for new-style P4V. Now using P4VC (there is no more P4V -cmd). Currently only Revgraph, Timelapse View and Submit are supported. |
||
#8 | 7822 | Sven Erik Knop |
Preparations for log-in dialog. The dialog is written, but not hooked up yet, but all the parts compile and link. Error dialog now displays a different error text if the ticket has expired, in preparation for the next step. |
||
#7 | 7821 | Sven Erik Knop |
Small change to Notepad++ plugin: improved error handling, in that errors are actually printed out as errors. Especially important if the user's session has expired and the user needs to log on again. Next up: Login dialog. |
||
#6 | 7482 | Sven Erik Knop |
Added P4V support to the plugin. If p4v.exe is in the path, additional menu items become available. |
||
#5 | 7469 | Sven Erik Knop | Solved a problem with Unicode enabled servers. | ||
#4 | 7468 | Sven Erik Knop | Show the otherAction as well. | ||
#3 | 7467 | Sven Erik Knop |
Updated the file info one more time. Now it shows the last change date, the last change number and the fact that files are open in other workspaces even if they are not open in the local workspace. |
||
#2 | 7465 | Sven Erik Knop |
Updated the fstat output for the plugin. Now the file type of the head revision is shown as well. |
||
#1 | 7440 | Sven Erik Knop |
Notepad++ Perforce Plugin. The provided DLL will only work for the Unicode version of Notepad++. The code currently contains no provision for ASCII builds. The plugin can : Add Edit Delete Revert Sync (to head) --- Show file info Show connection info |