Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- /**
- * Abstract class for Perforce Connection implementations.
- *
- * @copyright 2011 Perforce Software. All rights reserved.
- * @license Please see LICENSE.txt in top-level folder of this distribution.
- * @version <release>/<patch>
- * @todo verify need to call disconnect() on setClient/Password/etc.
- */
- namespace P4\Connection;
- use P4;
- use P4\Connection\Exception\ServiceNotFoundException;
- use P4\Counter\Counter;
- use P4\Environment\Environment;
- use P4\Log\Logger;
- use P4\Time\Time;
- use P4\Validate;
- abstract class AbstractConnection implements ConnectionInterface
- {
- const LOG_MAX_STRING_LENGTH = 1024;
- const DEFAULT_CHARSET = 'utf8unchecked';
- protected $client;
- protected $info;
- protected $password;
- protected $port;
- protected $ticket;
- protected $user;
- protected $charset;
- protected $host;
- protected $appName;
- protected $progName;
- protected $progVersion;
- protected $services;
- protected $disconnectCallbacks = array();
- protected $slowCommandLogging = array();
- /**
- * Create an Interface instance.
- *
- * @param string $port optional - the port to connect to.
- * @param string $user optional - the user to connect as.
- * @param string $client optional - the client spec to use.
- * @param string $password optional - the password to use.
- * @param string $ticket optional - a ticket to use.
- */
- public function __construct(
- $port = null,
- $user = null,
- $client = null,
- $password = null,
- $ticket = null
- ) {
- $this->setPort($port);
- $this->setUser($user);
- $this->setClient($client);
- $this->setPassword($password);
- $this->setTicket($ticket);
- // ensure we disconnect on shutdown.
- Environment::addShutdownCallback(
- array($this, 'disconnect')
- );
- }
- /**
- * Return the p4 port.
- *
- * @return string the port.
- */
- public function getPort()
- {
- return $this->port;
- }
- /**
- * Set the p4 port.
- * Forces a disconnect if already connected.
- *
- * @param string $port the port to connect to.
- * @return ConnectionInterface provides fluent interface.
- * @todo validate port using port validator - make validator work with 'rsh:' ports.
- */
- public function setPort($port)
- {
- $this->port = (string) $port;
- // disconnect on port change.
- $this->disconnect();
- return $this;
- }
- /**
- * Return the name of the p4 user.
- *
- * @return string the user.
- */
- public function getUser()
- {
- return $this->user;
- }
- /**
- * Set the name of the p4 user.
- * Forces a disconnect if already connected.
- *
- * @param string $user the user to connect as.
- * @return ConnectionInterface provides fluent interface.
- * @throws P4\Exception if the user is not valid.
- */
- public function setUser($user)
- {
- $validator = new Validate\UserName;
- if ($user !== null && !$validator->isValid($user)) {
- throw new P4\Exception("Username: " . implode("\n", $validator->getMessages()));
- }
- $this->user = $user;
- // disconnect on user change.
- $this->disconnect();
- return $this;
- }
- /**
- * Return the p4 user's client.
- *
- * @return string the client.
- */
- public function getClient()
- {
- return $this->client;
- }
- /**
- * Set the p4 user's client.
- * Forces a disconnect if already connected.
- *
- * @param string $client the name of the client workspace to use.
- * @return ConnectionInterface provides fluent interface.
- * @throws P4\Exception if the client is not valid.
- */
- public function setClient($client)
- {
- $validator = new Validate\SpecName;
- if ($client !== null && !$validator->isValid($client)) {
- throw new P4\Exception("Client name: " . implode("\n", $validator->getMessages()));
- }
- $this->client = $client;
- // clear cached p4 info
- $this->info = null;
- return $this;
- }
- /**
- * Retrieves the password set for this perforce connection.
- *
- * @return string password used to authenticate against perforce server.
- */
- public function getPassword()
- {
- return $this->password;
- }
- /**
- * Sets the password to use for this perforce connection.
- *
- * @param string $password the password to use as authentication.
- * @return ConnectionInterface provides fluent interface.
- */
- public function setPassword($password)
- {
- $this->password = $password;
- return $this;
- }
- /**
- * Retrieves the ticket set for this perforce connection.
- *
- * @return string ticket as generated by perforce server.
- */
- public function getTicket()
- {
- return $this->ticket;
- }
- /**
- * Sets the ticket to use for this perforce connection.
- * Forces a disconnect if already connected.
- *
- * @param string $ticket the ticket to use as authentication.
- * @return ConnectionInterface provides fluent interface.
- */
- public function setTicket($ticket)
- {
- $this->ticket = $ticket;
- // disconnect on ticket change.
- $this->disconnect();
- return $this;
- }
- /**
- * Retrieves the character set used by this connection.
- *
- * @return string charset used for this connection.
- */
- public function getCharset()
- {
- return $this->charset;
- }
- /**
- * Sets the character set to use for this perforce connection.
- *
- * You should only set a character set when connecting to a
- * 'unicode enabled' server, or when setting the special value
- * of 'none'.
- *
- * @param string $charset the charset to use (e.g. 'utf8').
- * @return ConnectionInterface provides fluent interface.
- */
- public function setCharset($charset)
- {
- $this->charset = $charset;
- return $this;
- }
- /**
- * Retrieves the client host set for this connection.
- *
- * @return string host name used for this connection.
- */
- public function getHost()
- {
- return $this->host;
- }
- /**
- * Sets the client host name overriding the environment.
- *
- * @param string|null $host the host name to use.
- * @return ConnectionInterface provides fluent interface.
- */
- public function setHost($host)
- {
- $this->host = $host;
- return $this;
- }
- /**
- * Set the name of the application that is using this connection.
- *
- * The application name will be reported to the server and might
- * be necessary to satisfy certain licensing restrictions.
- *
- * @param string|null $name the app name to report to the server.
- * @return ConnectionInterface provides fluent interface.
- */
- public function setAppName($name)
- {
- $this->appName = is_null($name) ? $name : (string) $name;
- return $this;
- }
- /**
- * Get the application name being reported to the server.
- *
- * @return string|null the app name reported to the server.
- */
- public function getAppName()
- {
- return $this->appName;
- }
- /**
- * Set the name of the program that is using this connection.
- *
- * The program name will be reported in the server logs
- *
- * @param string|null $name the program name to report to the server.
- * @return ConnectionInterface provides fluent interface.
- */
- public function setProgName($name)
- {
- $this->progName = is_null($name) ? $name : (string) $name;
- return $this;
- }
- /**
- * Get the program name being reported to the server.
- *
- * @return string|null the program name reported to the server.
- */
- public function getProgName()
- {
- return $this->progName;
- }
- /**
- * Set the program version of the program that is using this connection.
- *
- * The program version will be reported in the server logs
- *
- * @param string|null $version the program version to report to the server.
- * @return ConnectionInterface provides fluent interface.
- */
- public function setProgVersion($version)
- {
- $this->progVersion = is_null($version) ? $version : (string) $version;
- return $this;
- }
- /**
- * Get the program version being reported to the server.
- *
- * @return string|null the program version reported to the server.
- */
- public function getProgVersion()
- {
- return $this->progVersion;
- }
- /**
- * Get the current client's root directory with no trailing slash.
- *
- * @return string the full path to the current client's root.
- */
- public function getClientRoot()
- {
- $info = $this->getInfo();
- if (isset($info['clientRoot'])) {
- return rtrim($info['clientRoot'], '/\\');
- }
- return false;
- }
- /**
- * Return an array of connection information.
- * Due to caching, server date may be stale.
- *
- * @return array the connection information ('p4 info').
- */
- public function getInfo()
- {
- // if info cache is populated and connection is up, return cached info.
- if (isset($this->info) && $this->isConnected()) {
- return $this->info;
- }
- // run p4 info.
- $result = $this->run("info");
- $this->info = array();
- // gather all data (multiple arrays returned when connecting through broker).
- foreach ($result->getData() as $data) {
- $this->info += $data;
- }
- return $this->info;
- }
- /**
- * Clear the info cache. This method is primarily used during testing,
- * and would not normally be used.
- *
- * @return ConnectionInterface provides fluent interface.
- */
- public function clearInfo()
- {
- $this->info = null;
- return $this;
- }
- /**
- * Get the server identity of this connection.
- * Resulting array will contain:
- * - name
- * - platform
- * - version
- * - build
- * - apiversion (same value as version, included for consistency)
- * - apibuild (same value as build, included for consistency)
- * - date
- * - original (all text for server version from 'info' response)
- *
- * @return array an array of server information for this connection
- * @throws P4\Exception if the returned server version string is invalid
- */
- public function getServerIdentity()
- {
- $info = $this->getInfo();
- $parts = isset($info['serverVersion'])
- ? preg_split('/\/| \(|\)/', $info['serverVersion'])
- : null;
- if (count($parts) < 6) {
- $message = 'p4 info returned an invalid server version string';
- throw new P4\Exception($message);
- }
- // build server identity array of version components, including original string
- return array(
- 'name' => $parts[0],
- 'platform' => $parts[1],
- 'version' => $parts[2],
- 'build' => $parts[3],
- 'apiversion' => $parts[2],
- 'apibuild' => $parts[3],
- 'date' => $parts[4] . '/' . $parts[5] . '/' . $parts[6],
- 'original' => $info['serverVersion']
- );
- }
- /**
- * Return perforce server version in the form of '<year>.<release>'.
- *
- * @return string server version as '<year>.<release>'
- * @throws P4\Exception if server version cannot be determined
- */
- public function getServerVersion()
- {
- $identity = $this->getServerIdentity();
- $version = $identity['version'];
- // keep only '<year>.<release>' of the version
- $parts = explode('.', $version);
- if (count($parts) < 2) {
- throw new P4\Exception(
- 'Cannot get version from server identity: unknown version format.'
- );
- }
- $version = implode('.', array_slice($parts, 0, 2));
- return $version;
- }
- /**
- * Check if the server version for this connection is same or higher than
- * the version passed in the parameter.
- *
- * @param string $version version to compare in format <year>.<release>
- * @return bool true if server version is same or higher than $version
- */
- public function isServerMinVersion($version)
- {
- return version_compare($this->getServerVersion(), $version) >= 0;
- }
- /**
- * Check if the P4API version for this connection is same or higher than
- * the version passed in the parameter.
- *
- * @param string $version version to compare in format <year>.<release>
- * @return bool true if P4API version is same or higher than $version
- * @throws P4\Exception if the apiVersion string is invalid
- */
- public function isApiMinVersion($version)
- {
- $identity = $this->getConnectionIdentity();
- $apiVersion = isset($identity['apiversion']) ? $identity['apiversion'] : '';
- // keep only '<year>.<release>' of the apiVersion
- $parts = explode('.', $apiVersion);
- if (count($parts) < 2) {
- throw new P4\Exception(
- 'Cannot get version from connection identity: unknown version format.'
- );
- }
- $apiVersion = implode('.', array_slice($parts, 0, 2));
- return version_compare($apiVersion, $version) >= 0;
- }
- /**
- * Return option limit (server-side limit on the number of flags)
- * based on the server version.
- *
- * @return int option limit
- */
- public function getOptionLimit()
- {
- $limit = 20;
- if ($this->isServerMinVersion('2012.1')) {
- $limit = 256;
- }
- return $limit;
- }
- /**
- * Authenticate the user with 'p4 login'.
- *
- * @return string|null the ticket issued by the server or null if
- * no ticket issued (ie. user has no password).
- * @throws Exception\LoginException if login fails.
- */
- public function login()
- {
- // ensure user name is set.
- if (!strlen($this->getUser())) {
- throw new Exception\LoginException(
- "Login failed. Username is empty.",
- Exception\LoginException::IDENTITY_AMBIGUOUS
- );
- }
- // try to login.
- try {
- $result = $this->run('login', '-p', $this->password ?: '');
- } catch (Exception\CommandException $e) {
- // user doesn't exist.
- if (stristr($e->getMessage(), "doesn't exist") ||
- stristr($e->getMessage(), "has not been enabled by 'p4 protect'")
- ) {
- throw new Exception\LoginException(
- "Login failed. " . $e->getMessage(),
- Exception\LoginException::IDENTITY_NOT_FOUND
- );
- }
- // invalid password.
- if (stristr($e->getMessage(), "password invalid")) {
- throw new Exception\LoginException(
- "Login failed. " . $e->getMessage(),
- Exception\LoginException::CREDENTIAL_INVALID
- );
- }
- // generic login exception.
- throw new Exception\LoginException(
- "Login failed. " . $e->getMessage()
- );
- }
- // we can get several output blocks
- // we want the first block that looks like a ticket
- // if user has no password, the last block will be a message
- // if using external auth, early blocks could be trigger output
- // if talking to a replica, the last block will be a ticket for the master
- $response = end($result->getData());
- foreach ($result->getData() as $data) {
- if (preg_match('/^[A-F0-9]{32}$/', $data)) {
- $response = $data;
- break;
- }
- }
- // check if no password set for this user.
- // fail if a password was provided - succeed otherwise.
- if (stristr($response, "no password set for this user")) {
- if ($this->password) {
- throw new Exception\LoginException(
- "Login failed. " . $response,
- Exception\LoginException::CREDENTIAL_INVALID
- );
- } else {
- return null;
- }
- }
- // capture ticket from output.
- $this->ticket = $response;
- // if ticket wasn't captured correctly, fail with unknown code.
- if (!$this->ticket) {
- throw new Exception\LoginException(
- "Login failed. Unable to capture login ticket."
- );
- }
- return $this->ticket;
- }
- /**
- * Executes the specified command and returns a perforce result object.
- * No need to call connect() first. Run will connect automatically.
- *
- * Performs common pre/post-run work. Hands off to doRun() for the
- * actual mechanics of running commands.
- *
- * @param string $command the command to run.
- * @param array|string $params optional - one or more arguments.
- * @param array|string $input optional - input for the command - should be provided
- * in array form when writing perforce spec records.
- * @param boolean $tagged optional - true/false to enable/disable tagged output.
- * defaults to true.
- * @param boolean $ignoreErrors optional - true/false to ignore errors - default false
- * @return P4\Connection\CommandResult the perforce result object.
- */
- public function run(
- $command,
- $params = array(),
- $input = null,
- $tagged = true,
- $ignoreErrors = false
- ) {
- // establish connection to perforce server.
- if (!$this->isConnected()) {
- $this->connect();
- }
- // ensure params is an array.
- if (!is_array($params)) {
- if (!empty($params)) {
- $params = array($params);
- } else {
- $params = array();
- }
- }
- // log the start of the command w. params.
- $message = "P4 (" . spl_object_hash($this) . ") start command: "
- . $command . " " . implode(" ", $params);
- Logger::log(
- Logger::DEBUG,
- substr($message, 0, static::LOG_MAX_STRING_LENGTH)
- );
- // prepare input for passing to perforce.
- $input = $this->prepareInput($input, $command);
- // defer to sub-classes to actually issue the command.
- $start = microtime(true);
- $result = $this->doRun($command, $params, $input, $tagged);
- $lapse = microtime(true) - $start;
- // if the command was slow, log a warning
- // we determine the threshold for slow based on the command being run
- $slow = 0;
- foreach ($this->getSlowCommandLogging() as $key => $value) {
- if (ctype_digit((string) $value)) {
- $slow = max($slow, $value);
- } elseif (in_array($command, (array) $value)) {
- $slow = max($slow, (int) $key);
- }
- }
- if ($slow && $lapse >= $slow) {
- $message = "P4 (" . spl_object_hash($this) . ") slow command (" . round($lapse, 3) . "s): "
- . $command . " " . implode(" ", $params);
- Logger::log(
- Logger::WARN,
- substr($message, 0, static::LOG_MAX_STRING_LENGTH)
- );
- }
- // log errors - log them and throw an exception.
- if ($result->hasErrors() && !$ignoreErrors) {
- $message = "P4 (" . spl_object_hash($this) . ") command failed: "
- . implode("\n", $result->getErrors());
- Logger::log(
- Logger::DEBUG,
- substr($message, 0, static::LOG_MAX_STRING_LENGTH)
- );
- // if we have no charset, and the command failed because we are
- // talking to a unicode server, automatically use the default
- // charset and run the command again.
- $errors = $result->getErrors();
- $needle = 'Unicode server permits only unicode enabled clients.';
- if (!$this->getCharset() && stripos($errors[0], $needle) !== false) {
- Logger::log(
- Logger::DEBUG,
- "P4 (" . spl_object_hash($this) . ") automatically enabling unicode and re-trying."
- );
- $this->setCharset(static::DEFAULT_CHARSET);
- // run the command again now that we have a charset.
- return call_user_func_array(
- array($this, 'run'),
- func_get_args()
- );
- }
- // if connect failed due to an untrusted server, trust it and retry
- $needle = "To allow connection use the 'p4 trust' command";
- if (stripos($errors[0], $needle) !== false && !$this->hasTrusted) {
- Logger::log(
- Logger::DEBUG,
- "P4 (" . spl_object_hash($this) . ") automatically trusting and re-trying."
- );
- // add a property to avoid re-recursing on this test
- $this->hasTrusted = true;
- // work around @job066722 by disconnecting to clear the argument buffer
- // -fixed in P4API 2013.2
- if (!$this->isApiMinVersion('2013.2')) {
- $this->disconnect();
- }
- // trust the connection as this is the first time we have seen it
- $this->run('trust', '-y');
- // run the command again now that we have trusted it
- return call_user_func_array(
- array($this, 'run'),
- func_get_args()
- );
- }
- $this->handleError($result);
- }
- return $result;
- }
- /**
- * Runs the specified command using the passed output handler.
- * Ensures the output handler is turned back off at completion.
- *
- * If the handler has a 'reset' method it will be called. This is intended
- * to give the handler an opportunity to prepare itself for a fresh run.
- *
- * @param P4_OutputHandlerAbstract $handler the output handler to use
- * @param string $command the command to run.
- * @param array|string $params optional - one or more arguments.
- * @param array|string $input optional - input for the command - should be provided
- * in array form when writing perforce spec records.
- * @param boolean $tagged optional - true/false to enable/disable tagged output.
- * defaults to true.
- * @param boolean $ignoreErrors optional - true/false to ignore errors - default false
- * @throws \P4\Exception if the implementation doesn't define a runHandler
- * @return \P4\Connection\CommandResult the perforce result object.
- */
- public function runHandler(
- $handler,
- $command,
- $params = array(),
- $input = null,
- $tagged = true,
- $ignoreErrors = false
- ) {
- throw new P4\Exception('Implementing class must define a runHandler implementation!');
- }
- /**
- * Check if the user we are connected as has super user privileges.
- *
- * @return bool true if the user has super, false otherwise.
- * @throws Exception\CommandException if unanticipated error from protects -m.
- */
- public function isSuperUser()
- {
- return $this->getMaxAccess() === 'super';
- }
- /**
- * Check if the user we are connected as has admin user privileges.
- * By default, 'super' connection will return false on this check.
- * This behaviour can be modified by optional $allowSuper flag
- * to also include 'super' users.
- *
- * @param bool $allowSuper optional - if true, then this check will
- * return true also if the connection is super
- * @return bool true if the user is admin (or super if $allowSuper is true),
- * false otherwise.
- * @throws Exception\CommandException if unanticipated error from protects -m.
- */
- public function isAdminUser($allowSuper = false)
- {
- $maxAccess = $this->getMaxAccess();
- return ($allowSuper && $maxAccess === 'super') || $maxAccess === 'admin';
- }
- /**
- * Check if the server we are connected to is case sensitive.
- *
- * @return bool true if the server is case sensitive, false otherwise.
- * @throws P4\Exception if unable to determine server case handling.
- */
- public function isCaseSensitive()
- {
- $info = $this->getInfo();
- // throw exception if case handling unknown.
- if (!isset($info['caseHandling'])) {
- throw new P4\Exception("Cannot determine server case-handling.");
- }
- return $info['caseHandling'] === 'sensitive';
- }
- /**
- * Check if the server we are connected to is using external authentication
- *
- * @return bool true if the server is using external authentication, false otherwise.
- */
- public function hasExternalAuth()
- {
- $info = $this->getInfo();
- if (isset($info['externalAuth']) && ($info['externalAuth'] === 'enabled')) {
- return true;
- }
- return false;
- }
- /**
- * Check if the server we are connected to has a auth-set trigger configured.
- *
- * @return bool true, if the server has configured an auth-set trigger,
- * false, otherwise.
- */
- public function hasAuthSetTrigger()
- {
- // exit early if the server is not using external authentication
- if (!$this->hasExternalAuth()) {
- return false;
- }
- try {
- // try to set the password, the server without an auth-set trigger
- // throws a Exception\CommandException with the error message:
- // "Command unavailable: external authentication 'auth-set' trigger not found."
- $this->run('passwd');
- } catch (Exception\CommandException $e) {
- if (stristr($e->getMessage(), "'auth-set' trigger not found.")) {
- return false;
- }
- }
- return true;
- }
- /**
- * Connect to a Perforce Server.
- * Hands off to doConnect() for the actual mechanics of connecting.
- *
- * @return ConnectionInterface provides fluent interface.
- * @throws Exception\ConnectException if the connection fails.
- */
- public function connect()
- {
- if (!$this->isConnected()) {
- // refuse to connect if no port or no user set.
- if (!strlen($this->getPort()) || !strlen($this->getUser())) {
- throw new Exception\ConnectException(
- "Cannot connect. You must specify both a port and a user."
- );
- }
- $this->doConnect();
- }
- return $this;
- }
- /**
- * Run disconnect callbacks.
- *
- * @return ConnectionInterface provides fluent interface.
- */
- public function disconnect()
- {
- return $this->runDisconnectCallbacks();
- }
- /**
- * Add a function to run when connection is closed.
- * Callbacks are removed after they are executed
- * unless persistent is set to true.
- *
- * @param callable $callback the function to execute on disconnect
- * (will be passed connection).
- * @param bool $persistent optional - defaults to false - set to true to
- * run callback on repeated disconnects.
- * @return ConnectionInterface provides fluent interface.
- * @throws \InvalidArgumentException if callback supplied is not callable
- */
- public function addDisconnectCallback($callback, $persistent = false)
- {
- if (!is_callable($callback)) {
- throw new \InvalidArgumentException(
- "Cannot add disconnect callback. Not callable"
- );
- }
- $this->disconnectCallbacks[] = array(
- 'callback' => $callback,
- 'persistent' => $persistent
- );
- return $this;
- }
- /**
- * Clear disconnect callbacks.
- *
- * @return ConnectionInterface provides fluent interface.
- */
- public function clearDisconnectCallbacks()
- {
- $this->disconnectCallbacks = array();
- return $this;
- }
- /**
- * Run disconnect callbacks.
- *
- * @return ConnectionInterface provides fluent interface.
- */
- public function runDisconnectCallbacks()
- {
- foreach ($this->disconnectCallbacks as $key => $callback) {
- call_user_func($callback['callback'], $this);
- if (!$callback['persistent']) {
- unset($this->disconnectCallbacks[$key]);
- }
- }
- return $this;
- }
- /**
- * Get the server's security level.
- *
- * @return int the security level of the server (e.g. 0, 1, 2, 3)
- */
- public function getSecurityLevel()
- {
- if (!Counter::exists('security', $this)) {
- return 0;
- }
- return (int) Counter::fetch('security', $this)->get();
- }
- /**
- * This function will throw the appropriate exception for the error(s) found
- * in the passed result object.
- *
- * @param P4\Connection\CommandResult $result The result containing errors
- * @throws Exception\ConflictException if there are file conflicts to resolve.
- * @throws Exception\CommandException if there are any other command errors.
- */
- public function handleError($result)
- {
- $message = "Command failed: " . implode("\n", $result->getErrors());
- // create appropriate exception based on error condition
- if (preg_match("/must (sync\/ )?(be )?resolved?/", $message) ||
- preg_match("/Merges still pending/", $message)) {
- $e = new Exception\ConflictException($message);
- } else {
- $e = new Exception\CommandException($message);
- }
- $e->setConnection($this);
- $e->setResult($result);
- throw $e;
- }
- /**
- * Get the maximum allowable length of all command arguments.
- *
- * @return int the max length of combined arguments - zero for no limit
- */
- public function getArgMax()
- {
- return 0;
- }
- /**
- * Return arguments split into chunks (batches) where each batch contains as many
- * arguments as possible to not exceed ARG_MAX or option limit.
- *
- * ARG_MAX is a character limit that affects command line programs (p4).
- * Option limit is a server-side limit on the number of flags (e.g. '-n').
- *
- * @param array $arguments list of arguments to split into chunks.
- * @param array|null $prefixArgs arguments to begin all batches with.
- * @param array|null $suffixArgs arguments to end all batches with.
- * @param int $groupSize keep arguments together in groups of this size
- * for example, when clearing attributes you want to
- * keep pairs of -n and attr-name together.
- * @return array list of batches of arguments where every batch contains as many
- * arguments as possible and arg-max is not exceeded.
- * @throws P4\Exception if a argument (or set of arguments) exceed arg-max.
- */
- public function batchArgs(array $arguments, array $prefixArgs = null, array $suffixArgs = null, $groupSize = 1)
- {
- // determine size of leading and trailing arguments.
- $initialLength = 0;
- $initialOptions = 0;
- $argMax = $this->getArgMax();
- $optionLimit = $this->getOptionLimit();
- $prefixArgs = (array) $prefixArgs;
- $suffixArgs = (array) $suffixArgs;
- foreach (array_merge($prefixArgs, $suffixArgs) as $argument) {
- // if we have an arg-max limit, determine length of common args.
- // compute length by adding length of escaped argument + 1 space
- if ($argMax) {
- $initialLength += strlen(static::escapeArg($argument)) + 1;
- }
- // if the first character is a dash ('-'), it's an option
- if (substr($argument, 0, 1) === '-') {
- $initialOptions++;
- }
- }
- $batches = array();
- while (!empty($arguments)) {
- // determine how many arguments we can move into this batch.
- $count = 0;
- $length = $initialLength;
- $options = $initialOptions;
- foreach ($arguments as $argument) {
- // if we have an arg-max limit, enforce it.
- // compute length by adding length of escaped argument + 1 space
- if ($argMax) {
- $length += strlen(static::escapeArg($argument)) + 1;
- // if we exceed arg-max, break
- if ($length >= $argMax) {
- break;
- }
- }
- // if we exceed the option-limit, break
- if ($options > $optionLimit) {
- break;
- }
- // if the first character is a dash ('-'), it's an option
- if (substr($argument, 0, 1) === '-') {
- $options++;
- }
- $count++;
- }
- // adjust count down to largest divisible group size
- // and move that number of arguments into this batch.
- $count -= $count % $groupSize;
- $batches[] = array_merge($prefixArgs, array_splice($arguments, 0, $count), $suffixArgs);
- // handle the case of a given argument group not fitting in a batch
- // this informs the caller of indivisble args and avoids infinite loops
- if (!empty($arguments) && $count < $groupSize) {
- throw new P4\Exception(
- "Cannot batch arguments. Arguments exceed arg-max and/or option-limit."
- );
- }
- }
- return $batches;
- }
- /**
- * Escape a string for use as a command argument.
- * Escaping is a no-op for the abstract implementation,
- * but is needed by batchArgs.
- *
- * @param string $arg the string to escape
- * @return string the escaped string
- */
- public static function escapeArg($arg)
- {
- return $arg;
- }
- /**
- * Get the server's timezone.
- *
- * @return DateTimeZone the server's timezone
- * @throws \Exception if the server's timezone isn't parsable
- */
- public function getTimeZone()
- {
- // the 'serverDate' object lists the offset in the format -0800 and the short timezone name as the
- // last two components. strip these off and convert to a long name if possible.
- $info = $this->getInfo();
- if (isset($info['serverDate'])
- && preg_match('#^[0-9/]+ [0-9:]+ (?P<offset>[0-9-+]+) (?P<name>.+)$#', $info['serverDate'], $timezone)
- ) {
- // converting the string to a DateTimeZone is tricky; outsource the heavy lifting
- return Time::toDateTimeZone($timezone['name'], $timezone['offset']);
- }
- // if we couldn't preg match out the details; throw
- throw new \Exception('Unable to get timezone, p4 info does not contain a parsable serverDate');
- }
- /**
- * Attach a service to this connection.
- * Allows the connection to act as a service locator (e.g. for logging, caching, etc.)
- *
- * @param string $name the name of the service to set (e.g. 'cache')
- * @param object|callable|null $service the service instance or factory function (or null to clear)
- * factory is called with the connection and service name
- * @return ConnectionInterface to maintain a fluent interface
- * @throws \InvalidArgumentException if the name or service is invalid
- */
- public function setService($name, $service)
- {
- if (!is_string($name) || !strlen($name)) {
- throw new \InvalidArgumentException("Cannot set service. Name must be a non-empty string.");
- }
- // if service is null, remove it
- if ($service === null) {
- unset($this->services[$name]);
- return $this;
- }
- if (!is_object($service) && !is_callable($service)) {
- throw new \InvalidArgumentException("Cannot set service. Service must be an object or callable.");
- }
- $this->services[$name] = array(
- 'instance' => is_callable($service) ? null : $service,
- 'factory' => is_callable($service) ? $service : null
- );
- return $this;
- }
- /**
- * Retrieve a service from this connection.
- *
- * @param string $name the name of the service to get (e.g. 'cache')
- * @return object the service instance (factory functions are resolved automatically)
- * @throws ServiceNotFoundException if the requested service does not exist
- */
- public function getService($name)
- {
- if (!isset($this->services[$name])) {
- throw new ServiceNotFoundException("Cannot get service. No such service ('$name').");
- }
- // construct the service instance if necessary
- $service = $this->services[$name];
- if (!isset($service['instance'])) {
- $service['instance'] = $service['factory']($this, $name);
- $this->services[$name] = $service;
- }
- return $service['instance'];
- }
- /**
- * Set the threshold(s) for logging slow commands.
- * Pass false or an empty array to disable logging.
- *
- * You may specify a default limit (in seconds) as well as limits that
- * apply to only specific commands. The longest applicable limit is used
- * for a given command if more than one candidate occurs.
- *
- * The format is:
- * $limits => array(
- * 3, // numeric value is a default (any command) limit
- * 30 => array('print', 'submit') // seconds as key with command(s) as value for command specific limit
- * 60 => 'unshelve'
- * );
- *
- * In the above example, the command fstat would have a limit of 3, print 30 and unshelve 60.
- *
- * @param array|bool $thresholds the limit(s) to trigger slow command logging or false
- * @return ConnectionInterface to maintain a fluent interface
- */
- public function setSlowCommandLogging($thresholds)
- {
- $this->slowCommandLogging = (array) $thresholds;
- return $this;
- }
- /**
- * Return the currently specified slow command thresholds.
- *
- * @return array the slow command thresholds, see setSlowCommandLimits for format details
- */
- public function getSlowCommandLogging()
- {
- return (array) $this->slowCommandLogging;
- }
- /**
- * Get maximum access level for this connection.
- *
- * @param string|null $host optional - if set, max access level will be determined
- * for the given host
- * @return string|false maximum access level or false
- */
- public function getMaxAccess($host = null)
- {
- // get max access level from Perforce
- $flags = array('-m');
- if ($host) {
- $flags[] = '-h';
- $flags[] = $host;
- }
- try {
- $result = $this->run("protects", $flags);
- } catch (Exception\CommandException $e) {
- // if protections table is empty, everyone is super
- $errors = $e->getResult()->getErrors();
- if (stristr($errors[0], "empty")) {
- return 'super';
- } elseif (stristr($errors[0], "password must be set")) {
- return false;
- }
- throw $e;
- }
- return $result->getData(0, "permMax");
- }
- /**
- * Actually issues a command. Called by run() to perform the dirty work.
- *
- * @param string $command the command to run.
- * @param array $params optional - arguments.
- * @param array|string $input optional - input for the command - should be provided
- * in array form when writing perforce spec records.
- * @param boolean $tagged optional - true/false to enable/disable tagged output.
- * defaults to true.
- * @return P4\Connection\CommandResult the perforce result object.
- */
- abstract protected function doRun($command, $params = array(), $input = null, $tagged = true);
- /**
- * Prepare input for passing to Perforce.
- *
- * @param string|array $input the input to prepare for p4.
- * @param string $command the command to prepare input for.
- * @return string|array the prepared input.
- */
- abstract protected function prepareInput($input, $command);
- /**
- * Does real work of establishing connection. Called by connect().
- *
- * @throws Exception\ConnectException if the connection fails.
- */
- abstract protected function doConnect();
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement