// // Created by Tristan Juricek on 9/19/15. // #include <algorithm> #include <iostream> #include <map> #include <memory> #include <p4/clientapi.h> #include <pwd.h> #include <set> #include <string> #include <sys/types.h> #include <uuid/uuid.h> #include <vector> #include "Workspace.h" using std::back_inserter; using std::cout; using std::cerr; using std::endl; using std::find; using std::map; using std::set; using std::shared_ptr; using std::string; using std::transform; using std::vector; class ServerMessage { public: string text; int severity; int code; ServerMessage() = default; ServerMessage(const Error *err) { StrBuf strBuf; err->Fmt(&strBuf); text = strBuf.Text(); severity = err->GetSeverity(); code = err->GetGeneric(); } }; //---------------------------------------------------------------------------- // CommandResult //---------------------------------------------------------------------------- // Encapsulates both the return from a command, and the collected results. // class CommandResult { public: CommandResult(const vector<ServerMessage> & messages, const vector<map<string, string>> & results) : _messages(messages), _results(results) { } ~CommandResult() = default; bool hasError() const { for (ServerMessage m : _messages) { if (m.severity >= E_FAILED) return true; } return false; } const vector<ServerMessage> & messages() const { return _messages; } const vector<map<string, string>> & results() const { return _results; } int processReturn() const { if (hasError()) { for (ServerMessage message : _messages) { cerr << message.code << ": " << message.text << endl; } return Workspace::COMMAND_FAILURE; } return 0; } private: vector<ServerMessage> _messages; vector<map<string, string>> _results; }; //---------------------------------------------------------------------------- // CachingClientUser //---------------------------------------------------------------------------- // Handles converting much of the output from the ClientApi to something // less ... attached... to the p4api code. class CachingClientUser : public ClientUser { public: CachingClientUser() = default; ~CachingClientUser() = default; virtual void OutputStat(StrDict *varList) override; virtual void Message(Error *err) override; const vector<map<string, string>> & results() const { return _results; }; const vector<ServerMessage> & messages() const { return _messages; } void clear() { _results.clear(); _messages.clear(); } private: vector<map<string, string>> _results; vector<ServerMessage> _messages; }; void CachingClientUser::OutputStat(StrDict *varList) { StrRef var, val; map<string, string> result; for (int index = 0; varList->GetVar(index, var, val); ++index) { if (var == "specdef" || var == "func" || var == "specFormatted") continue; result[var.Text()] = val.Text(); } _results.push_back(result); } void CachingClientUser::Message(Error *err) { _messages.push_back(ServerMessage(err)); } //---------------------------------------------------------------------------- // ClientApiHandle //---------------------------------------------------------------------------- // Use RAII around connecting and disconnecting to the Perforce server. // We do not handle re-using connections just yet. // // Since the ClientApi is rather verbose, this contains some high-level methods // to collate data. class ClientApiHandle { public: static ClientApiHandle *create(string workspace); ClientApiHandle(); ~ClientApiHandle(); const ClientApi & api() const { return *_api; } ClientApi & api() { return *_api; } // High-level method to execute the command with optional args and return // results. template<typename... Strings> CommandResult run(const string & cmd, Strings...args); CommandResult run(const string & cmd, vector<const char *> argv); CommandResult run(const string & cmd, vector<string> argv); bool isConnected() const { return _connected; } void setConnected(bool c) { _connected = c; } private: CachingClientUser *_user; ClientApi *_api; bool _connected; }; // This will set the context of the ClientApi to the workspace path. // It's assumed that the workspace path has a .p4config file set up with // proper connection settings, etc, for you to commence work. ClientApiHandle * ClientApiHandle::create(string workspace) { ClientApiHandle *handle = new ClientApiHandle; handle->api().SetCwd(workspace.c_str()); // Setting the ticket file explicitly to the home directory, mostly because // it's our current default convention. // // TODO There has to be a better way to figure out the ticket file string ticketFile; struct passwd *pwdinfo = getpwuid(getuid()); if (pwdinfo) { ticketFile = pwdinfo->pw_dir; } ticketFile = ticketFile + "/.p4tickets"; cout << "Setting ticket file " << ticketFile << endl; handle->api().SetTicketFile(ticketFile.c_str()); return handle; } ClientApiHandle::ClientApiHandle() : _user(new CachingClientUser), _api(new ClientApi), _connected(false) { } ClientApiHandle::~ClientApiHandle() { if (_connected) { Error e; _api->Final(&e); if (e.IsError()) { StrBuf strBuf; e.Fmt(&strBuf); cerr << "ClientApi::Final failed: " << strBuf.Text() << endl; } } delete _api; delete _user; } void collect(vector<const char *> & v) { // Do nothing } void collect(vector<const char *> & v, const string & a) { v.push_back(a.c_str()); } template<typename... Strings> void collect(vector<const char *> & v, const string & a, Strings...rest) { v.push_back(a.c_str()); return collect(v, a, rest...); } template<typename... Strings> CommandResult ClientApiHandle::run(const string & cmd, Strings... args) { vector<const char *> argv; collect(argv, args...); return run(cmd, argv); } CommandResult ClientApiHandle::run(const string & cmd, vector<const char *> argv) { _user->clear(); if (!argv.empty()) { _api->SetArgv(argv.size(), const_cast<char **>(argv.data())); } _api->Run(cmd.c_str(), _user); return CommandResult(_user->messages(), _user->results()); } CommandResult ClientApiHandle::run(const string & cmd, vector<string> argv) { vector<const char *> chargv; transform(argv.begin(), argv.end(), back_inserter(chargv), [](string & s) -> const char * { return s.c_str(); }); return run(cmd, chargv); } // Will call "p4 opened" and then pass those depot paths to "p4 where" int loadLocalOpenedPaths(ClientApiHandle & handle, vector<string> & paths) { CommandResult openedResult = handle.run("opened"); if (openedResult.hasError()) return openedResult.processReturn(); // If there's no results, there's really nothing to do if (openedResult.results().empty()) return 0; vector<string> depotPaths; transform(openedResult.results().begin(), openedResult.results().end(), back_inserter(depotPaths), [](const map<string, string> & m) -> string { return m.at("depotFile"); }); CommandResult whereResult = handle.run("where", depotPaths); if (whereResult.hasError()) return whereResult.processReturn(); transform(whereResult.results().begin(), whereResult.results().end(), back_inserter(paths), [](const map<string, string> & m) -> string { return m.at("path"); }); return 0; } //---------------------------------------------------------------------------- // WorkspaceImpl //---------------------------------------------------------------------------- // We use a PIMPL technique on this class to ensure use of the P4API is // restricted to *only* this file. class WorkspaceImpl { public: WorkspaceImpl(Workspace *parent); ~WorkspaceImpl() = default; int add(const string & path); int edit(const string & path); int rename(const string & from, const string & to); template<typename Lambda> int execute(Lambda lambda); private: Workspace *_parent; }; WorkspaceImpl::WorkspaceImpl(Workspace *parent) : _parent(parent) { } int WorkspaceImpl::add(const string & path) { printf("Workspace::add %s\n", path.c_str()); return execute([path](ClientApiHandle & handle) { CommandResult result = handle.run("add", path); return result.processReturn(); }); } int WorkspaceImpl::edit(const string & path) { printf("Workspace::edit %s\n", path.c_str()); return execute([path](ClientApiHandle & handle) { vector<string> opened; int status = loadLocalOpenedPaths(handle, opened); if (status) return status; // If the path is already opened, do nothing. if (find(opened.begin(), opened.end(), path) != opened.end()) return 0; // Path is not opened, so we need to determine if this file should be // added or edited. // // We do this by first checking the output of `p4 files [local path]`. // // If the results are not empty, and the action of the file is not // delete, we run 'p4 edit'. CommandResult filesResult = handle.run("files", path); if (filesResult.hasError()) return filesResult.processReturn(); if (!filesResult.results().empty() && filesResult.results().front().at("action") != "delete") { // Right now just do everything on the pending changelist CommandResult editResult = handle.run("edit", path); if (editResult.hasError()) return editResult.processReturn(); } return 0; }); } int WorkspaceImpl::rename(const string & from, const string & to) { printf("Workspace::rename(%s,%s)\n", from.c_str(), to.c_str()); return 0; } // Basically handles connecting and disconnecting the ClientApiHandle, which // creates our initial approach to connection management. // // The Lambda should be a function that returns an int and takes the // ClientApiHandle as a reference. template<typename Lambda> int WorkspaceImpl::execute(Lambda lambda) { shared_ptr<ClientApiHandle> apiHandle( ClientApiHandle::create(_parent->workspace())); apiHandle->api().SetProtocol("tag", ""); Error e; apiHandle->api().Init(&e); if (e.Test()) { StrBuf strBuf; e.Fmt(&strBuf); cerr << strBuf.Text() << endl; return Workspace::CONNECTION_FAILURE; } apiHandle->setConnected(true); return lambda(*apiHandle); } //---------------------------------------------------------------------------- // Workspace //---------------------------------------------------------------------------- Workspace::~Workspace() { delete _impl; } Workspace::Workspace() : _workspace(), _impl(new WorkspaceImpl(this)) { } Workspace::Workspace(const std::string & workspace) : _workspace(workspace), _impl(new WorkspaceImpl(this)) { } std::string Workspace::path(const std::string & p) const { // TODO replace this with a real filesystem abstraction // ... but this will generally work. :/ return _workspace + p; } int Workspace::add(const char *p) { return _impl->add(path(p)); } int Workspace::edit(const char *p) { return _impl->edit(path(p)); } int Workspace::rename(const char *from, const char *to) { return _impl->rename(path(from), path(to)); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#2 | 16129 | tjuricek |
Rename/move files again... this time to the hyphenated-approach. |
||
#1 | 16119 | tjuricek | Rename/move to meet workshop project conventions. | ||
//guest/tjuricek/fsclient/Workspace.cpp | |||||
#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. |