Merge pull request #47 from icewind1991/notify

SMB notify support
This commit is contained in:
Robin Appelman 2016-04-26 15:26:01 +02:00
commit 399ce981c0
4 changed files with 106 additions and 25 deletions

View file

@ -15,6 +15,7 @@ use Icewind\SMB\Exception\NoLoginServerException;
class Connection extends RawConnection {
const DELIMITER = 'smb:';
const DELIMITER_LENGTH = 4;
/**
* send input to smbclient
@ -28,6 +29,7 @@ class Connection extends RawConnection {
/**
* get all unprocessed output from smbclient until the next prompt
*
* @param callable $callback (optional) callback to call for every line read
* @return string
* @throws AuthenticationException
* @throws ConnectException
@ -35,7 +37,7 @@ class Connection extends RawConnection {
* @throws InvalidHostException
* @throws NoLoginServerException
*/
public function read() {
public function read(callable $callback = null) {
if (!$this->isValid()) {
throw new ConnectionException('Connection not valid');
}
@ -45,25 +47,49 @@ class Connection extends RawConnection {
$output = array();
$line = $this->readLine();
if ($line === false) {
if ($promptLine) { //maybe we have some error we missed on the previous line
throw new ConnectException('Unknown error (' . $promptLine . ')');
} else {
$error = $this->readError(); // maybe something on stderr
if ($error) {
throw new ConnectException('Unknown error (' . $error . ')');
} else {
throw new ConnectException('Unknown error');
}
}
$this->unknownError($promptLine);
}
$length = mb_strlen(self::DELIMITER);
while (mb_substr($line, 0, $length) !== self::DELIMITER) { //next prompt functions as delimiter
$output[] .= $line;
while (!$this->isPrompt($line)) { //next prompt functions as delimiter
if (is_callable($callback)) {
$result = $callback($line);
if ($result === false) { // allow the callback to close the connection for infinite running commands
$this->close(true);
}
} else {
$output[] .= $line;
}
$line = $this->readLine();
}
return $output;
}
/**
* Check
*
* @param $line
* @return bool
*/
private function isPrompt($line) {
return mb_substr($line, 0, self::DELIMITER_LENGTH) === self::DELIMITER || $line === false;
}
/**
* @param string $promptLine (optional) prompt line that might contain some info about the error
* @throws ConnectException
*/
private function unknownError($promptLine = '') {
if ($promptLine) { //maybe we have some error we missed on the previous line
throw new ConnectException('Unknown error (' . $promptLine . ')');
} else {
$error = $this->readError(); // maybe something on stderr
if ($error) {
throw new ConnectException('Unknown error (' . $error . ')');
} else {
throw new ConnectException('Unknown error');
}
}
}
/**
* check if the first line holds a connection failure
*

View file

@ -8,6 +8,17 @@
namespace Icewind\SMB;
interface IShare {
// https://msdn.microsoft.com/en-us/library/dn392331.aspx
const NOTIFY_ADDED = 1;
const NOTIFY_REMOVED = 2;
const NOTIFY_MODIFIED = 3;
const NOTIFY_RENAMED_OLD = 4;
const NOTIFY_RENAMED_NEW = 5;
const NOTIFY_ADDED_STREAM = 6;
const NOTIFY_REMOVED_STREAM = 7;
const NOTIFY_MODIFIED_STREAM = 8;
const NOTIFY_REMOVED_BY_DELETE = 9;
/**
* Get the name of the share
*
@ -131,4 +142,11 @@ interface IShare {
* @return mixed
*/
public function setMode($path, $mode);
/**
* @param string $path
* @param callable $callback callable which will be called for each received change
* @return mixed
*/
public function notify($path, callable $callback);
}

View file

@ -301,6 +301,18 @@ class NativeShare extends AbstractShare {
return $this->setAttribute($path, 'system.dos_attr.mode', $mode);
}
/**
* @param string $path
* @param callable $callback callable which will be called for each received change
* @return mixed
*/
public function notify($path, callable $callback) {
// php-smbclient does support notify (https://github.com/eduardok/libsmbclient-php/issues/29)
// so we use the smbclient based backend for this
$share = new Share($this->server, $this->getName());
$share->notify($path, $callback);
}
public function __destruct() {
unset($this->state);
}

View file

@ -51,6 +51,22 @@ class Share extends AbstractShare {
$this->parser = new Parser(new TimeZoneProvider($this->server->getHost(), $this->system));
}
protected function getConnection() {
$workgroupArgument = ($this->server->getWorkgroup()) ? ' -W ' . escapeshellarg($this->server->getWorkgroup()) : '';
$command = sprintf('stdbuf -o0 %s %s --authentication-file=%s %s',
$this->system->getSmbclientPath(),
$workgroupArgument,
System::getFD(3),
escapeshellarg('//' . $this->server->getHost() . '/' . $this->name)
);
$connection = new Connection($command);
$connection->writeAuthentication($this->server->getUser(), $this->server->getPassword());
if (!$connection->isValid()) {
throw new ConnectionException();
}
return $connection;
}
/**
* @throws \Icewind\SMB\Exception\ConnectionException
* @throws \Icewind\SMB\Exception\AuthenticationException
@ -60,18 +76,7 @@ class Share extends AbstractShare {
if ($this->connection and $this->connection->isValid()) {
return;
}
$workgroupArgument = ($this->server->getWorkgroup()) ? ' -W ' . escapeshellarg($this->server->getWorkgroup()) : '';
$command = sprintf('%s %s --authentication-file=%s %s',
$this->system->getSmbclientPath(),
$workgroupArgument,
System::getFD(3),
escapeshellarg('//' . $this->server->getHost() . '/' . $this->name)
);
$this->connection = new Connection($command);
$this->connection->writeAuthentication($this->server->getUser(), $this->server->getPassword());
if (!$this->connection->isValid()) {
throw new ConnectionException();
}
$this->connection = $this->getConnection();
}
protected function reconnect() {
@ -344,6 +349,26 @@ class Share extends AbstractShare {
return $this->parseOutput($output, $path);
}
/**
* @param string $path
* @param callable $callback callable which will be called for each received change
* @return mixed
*/
public function notify($path, callable $callback) {
$connection = $this->getConnection(); // use a fresh connection since the notify command blocks the process
$command = 'notify ' . $this->escapePath($path);
$connection->write($command . PHP_EOL);
$connection->read(function ($line) use ($callback, $path) {
$code = (int)substr($line, 0, 4);
$subPath = substr($line, 5);
if ($path === '') {
return $callback($code, $subPath);
} else {
return $callback($code, $path . '/' . $subPath);
}
});
}
/**
* @param string $command
* @return array