more configurability for library users

- Make TimeZoneProvider and System overridable
- Add system for passing additional options
- make timeout configurable
This commit is contained in:
Robin Appelman 2018-07-12 16:38:25 +02:00
commit 2f261f868d
20 changed files with 303 additions and 107 deletions

View file

@ -36,7 +36,7 @@ abstract class AbstractServer implements IServer {
protected $auth;
/**
* @var \Icewind\SMB\System
* @var ISystem
*/
protected $system;
@ -45,41 +45,41 @@ abstract class AbstractServer implements IServer {
*/
protected $timezoneProvider;
/** @var IOptions */
protected $options;
/**
* @param string $host
* @param IAuth $auth
* @param System $system
* @param ISystem $system
* @param TimeZoneProvider $timeZoneProvider
* @param IOptions $options
*/
public function __construct($host, IAuth $auth, System $system, TimeZoneProvider $timeZoneProvider) {
public function __construct($host, IAuth $auth, ISystem $system, TimeZoneProvider $timeZoneProvider, IOptions $options) {
$this->host = $host;
$this->auth = $auth;
$this->system = $system;
$this->timezoneProvider = $timeZoneProvider;
$this->options = $options;
}
/**
* @return IAuth
*/
public function getAuth() {
return $this->auth;
}
/**
* return string
*/
public function getHost() {
return $this->host;
}
/**
* @return string
*/
public function getTimeZone() {
return $this->timezoneProvider->get();
return $this->timezoneProvider->get($this->host);
}
public function getSystem() {
return $this->system;
}
public function getOptions() {
return $this->options;
}
}

34
src/IOptions.php Normal file
View file

@ -0,0 +1,34 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Icewind\SMB;
interface IOptions {
/**
* @return int
*/
public function getTimeout();
/**
* @param int $timeout
*/
public function setTimeout($timeout);
}

View file

@ -52,13 +52,18 @@ interface IServer {
public function getTimeZone();
/**
* @return System
* @return ISystem
*/
public function getSystem();
/**
* @param System $system
* @return IOptions
*/
public function getOptions();
/**
* @param ISystem $system
* @return bool
*/
public static function available(System $system);
public static function available(ISystem $system);
}

57
src/ISystem.php Normal file
View file

@ -0,0 +1,57 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Icewind\SMB;
/**
* The `ISystem` interface provides a way to access system dependent information
* such as the availability and location of certain binaries.
*/
interface ISystem {
/**
* Get the path to a file descriptor of the current process
*
* @param int $num the file descriptor id
* @return string
*/
public function getFD($num);
/**
* Get the full path to the `smbclient` binary of false if the binary is not available
*
* @return string|bool
*/
public function getSmbclientPath();
/**
* Get the full path to the `net` binary of false if the binary is not available
*
* @return string|bool
*/
public function getNetPath();
/**
* Whether or not the `stdbuf` command is available in the path
*
* @return bool
*/
public function hasStdBuf();
}

32
src/ITimeZoneProvider.php Normal file
View file

@ -0,0 +1,32 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Icewind\SMB;
interface ITimeZoneProvider {
/**
* Get the timezone of the smb server
*
* @param string $host
* @return string
*/
public function get($host);
}

View file

@ -9,7 +9,8 @@ namespace Icewind\SMB\Native;
use Icewind\SMB\AbstractServer;
use Icewind\SMB\IAuth;
use Icewind\SMB\System;
use Icewind\SMB\IOptions;
use Icewind\SMB\ISystem;
use Icewind\SMB\TimeZoneProvider;
class NativeServer extends AbstractServer {
@ -18,14 +19,8 @@ class NativeServer extends AbstractServer {
*/
protected $state;
/**
* @param string $host
* @param IAuth $auth
* @param System $system
* @param TimeZoneProvider $timeZoneProvider
*/
public function __construct($host, IAuth $auth, System $system, TimeZoneProvider $timeZoneProvider) {
parent::__construct($host, $auth, $system, $timeZoneProvider);
public function __construct($host, IAuth $auth, ISystem $system, TimeZoneProvider $timeZoneProvider, IOptions $options) {
parent::__construct($host, $auth, $system, $timeZoneProvider, $options);
$this->state = new NativeState();
}
@ -62,10 +57,10 @@ class NativeServer extends AbstractServer {
/**
* Check if the smbclient php extension is available
*
* @param System $system
* @param ISystem $system
* @return bool
*/
public static function available(System $system) {
public static function available(ISystem $system) {
return function_exists('smbclient_state_new');
}
}

View file

@ -304,7 +304,7 @@ class NativeShare extends AbstractShare {
if (!Server::available($this->server->getSystem())) {
throw new DependencyException('smbclient not found in path for notify command');
}
$share = new Share($this->server, $this->getName());
$share = new Share($this->server, $this->getName(), $this->server->getSystem());
return $share->notify($path);
}

36
src/Options.php Normal file
View file

@ -0,0 +1,36 @@
<?php
/**
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace Icewind\SMB;
class Options implements IOptions {
/** @var int */
private $timeout = 20;
public function getTimeout() {
return $this->timeout;
}
public function setTimeout($timeout) {
$this->timeout = $timeout;
}
}

View file

@ -32,8 +32,40 @@ class ServerFactory {
Server::class
];
/** @var System|null */
private $system = null;
/** @var System */
private $system;
/** @var IOptions */
private $options;
/** @var ITimeZoneProvider */
private $timeZoneProvider;
/**
* ServerFactory constructor.
*
* @param IOptions|null $options
* @param ISystem|null $system
* @param ITimeZoneProvider|null $timeZoneProvider
*/
public function __construct(
IOptions $options = null,
ISystem $system = null,
ITimeZoneProvider $timeZoneProvider = null
) {
if (is_null($options)) {
$options = new Options();
}
if (is_null($system)) {
$system = new System();
}
if (is_null($timeZoneProvider)) {
$system = new TimeZoneProvider($system);
}
$this->options = $options;
$this->system = $system;
}
/**
* @param $host
@ -43,8 +75,8 @@ class ServerFactory {
*/
public function createServer($host, IAuth $credentials) {
foreach (self::BACKENDS as $backend) {
if (call_user_func("$backend::available", $this->getSystem())) {
return new $backend($host, $credentials, $this->getSystem(), new TimeZoneProvider($host, $this->getSystem()));
if (call_user_func("$backend::available", $this->system)) {
return new $backend($host, $credentials, $this->system, $this->timeZoneProvider);
}
}
@ -55,10 +87,6 @@ class ServerFactory {
* @return System
*/
private function getSystem() {
if (is_null($this->system)) {
$this->system = new System();
}
return $this->system;
}
}

View file

@ -9,14 +9,21 @@ namespace Icewind\SMB;
use Icewind\SMB\Exception\Exception;
class System {
class System implements ISystem {
private $smbclient;
private $net;
private $stdbuf;
public static function getFD($num) {
/**
* Get the path to a file descriptor of the current process
*
* @param int $num the file descriptor id
* @return string
* @throws Exception
*/
public function getFD($num) {
$folders = [
'/proc/self/fd',
'/dev/fd'

View file

@ -7,44 +7,41 @@
namespace Icewind\SMB;
class TimeZoneProvider {
class TimeZoneProvider implements ITimeZoneProvider {
/**
* @var string
* @var string[]
*/
private $host;
private $timeZones = [];
/**
* @var string
*/
private $timeZone;
/**
* @var System
* @var ISystem
*/
private $system;
/**
* @param string $host
* @param System $system
* @param ISystem $system
*/
public function __construct($host, System $system) {
$this->host = $host;
public function __construct(ISystem $system) {
$this->system = $system;
}
public function get() {
if (!$this->timeZone) {
public function get($host) {
if (!isset($this->timeZones[$host])) {
$net = $this->system->getNetPath();
if ($net) {
if ($net && $host) {
$command = sprintf('%s time zone -S %s',
$net,
escapeshellarg($this->host)
escapeshellarg($host)
);
$this->timeZone = exec($command);
$timeZone = exec($command);
if (!$timeZone) {
$timeZone = date_default_timezone_get();
}
$this->timeZones[$host] = $timeZone;
} else { // fallback to server timezone
$this->timeZone = date_default_timezone_get();
$this->timeZones[$host] = date_default_timezone_get();
}
}
return $this->timeZone;
return $this->timeZones[$host];
}
}

View file

@ -19,15 +19,19 @@ use Icewind\SMB\Exception\InvalidTypeException;
use Icewind\SMB\Exception\NoLoginServerException;
use Icewind\SMB\Exception\NotEmptyException;
use Icewind\SMB\Exception\NotFoundException;
use Icewind\SMB\TimeZoneProvider;
class Parser {
const MSG_NOT_FOUND = 'Error opening local file ';
/**
* @var \Icewind\SMB\TimeZoneProvider
* @var string
*/
protected $timeZoneProvider;
protected $timeZone;
/**
* @var string
*/
private $host;
// todo replace with static once <5.6 support is dropped
// see error.h
@ -55,10 +59,10 @@ class Parser {
];
/**
* @param TimeZoneProvider $timeZoneProvider
* @param string $timeZone
*/
public function __construct(TimeZoneProvider $timeZoneProvider) {
$this->timeZoneProvider = $timeZoneProvider;
public function __construct($timeZone) {
$this->timeZone = $timeZone;
}
private function getErrorCode($line) {
@ -158,7 +162,7 @@ class Parser {
list(, $name, $mode, $size, $time) = $matches;
if ($name !== '.' and $name !== '..') {
$mode = $this->parseMode($mode);
$time = strtotime($time . ' ' . $this->timeZoneProvider->get());
$time = strtotime($time . ' ' . $this->timeZone);
$content[] = new FileInfo($basePath . '/' . $name, $name, $size, $time, $mode);
}
}

View file

@ -13,22 +13,22 @@ use Icewind\SMB\Exception\ConnectException;
use Icewind\SMB\Exception\ConnectionException;
use Icewind\SMB\Exception\InvalidHostException;
use Icewind\SMB\IShare;
use Icewind\SMB\System;
use Icewind\SMB\ISystem;
class Server extends AbstractServer {
/**
* Check if the smbclient php extension is available
*
* @param System $system
* @param ISystem $system
* @return bool
*/
public static function available(System $system) {
public static function available(ISystem $system) {
return $system->getSmbclientPath();
}
private function getAuthFileArgument() {
if ($this->getAuth()->getUsername()) {
return '--authentication-file=' . System::getFD(3);
return '--authentication-file=' . $this->system->getFD(3);
} else {
return '';
}

View file

@ -15,7 +15,7 @@ use Icewind\SMB\Exception\InvalidTypeException;
use Icewind\SMB\Exception\NotFoundException;
use Icewind\SMB\INotifyHandler;
use Icewind\SMB\IServer;
use Icewind\SMB\System;
use Icewind\SMB\ISystem;
use Icewind\SMB\TimeZoneProvider;
use Icewind\Streams\CallbackWrapper;
@ -41,7 +41,7 @@ class Share extends AbstractShare {
protected $parser;
/**
* @var System
* @var ISystem
*/
private $system;
@ -55,28 +55,29 @@ class Share extends AbstractShare {
/**
* @param IServer $server
* @param string $name
* @param System $system
* @param ISystem $system
*/
public function __construct(IServer $server, $name, System $system = null) {
public function __construct(IServer $server, $name, ISystem $system) {
parent::__construct();
$this->server = $server;
$this->name = $name;
$this->system = (!is_null($system)) ? $system : new System();
$this->parser = new Parser(new TimeZoneProvider($this->server->getHost(), $this->system));
$this->system = $system;
$this->parser = new Parser($server->getTimeZone());
}
private function getAuthFileArgument() {
if ($this->server->getAuth()->getUsername()) {
return '--authentication-file=' . System::getFD(3);
return '--authentication-file=' . $this->system->getFD(3);
} else {
return '';
}
}
protected function getConnection() {
$command = sprintf('%s%s %s %s %s',
$command = sprintf('%s%s -t %s %s %s %s',
$this->system->hasStdBuf() ? 'stdbuf -o0 ' : '',
$this->system->getSmbclientPath(),
$this->server->getOptions()->getTimeout(),
$this->getAuthFileArgument(),
$this->server->getAuth()->getExtraCommandLineArguments(),
escapeshellarg('//' . $this->server->getHost() . '/' . $this->name)
@ -294,7 +295,7 @@ class Share extends AbstractShare {
// since we can't re-use the same file descriptor over multiple calls
$connection = $this->getConnection();
$connection->write('get ' . $source . ' ' . System::getFD(5));
$connection->write('get ' . $source . ' ' . $this->system->getFD(5));
$connection->write('exit');
$fh = $connection->getFileOutputStream();
stream_context_set_option($fh, 'file', 'connection', $connection);
@ -317,7 +318,7 @@ class Share extends AbstractShare {
$connection = $this->getConnection();
$fh = $connection->getFileInputStream();
$connection->write('put ' . System::getFD(4) . ' ' . $target);
$connection->write('put ' . $this->system->getFD(4) . ' ' . $target);
$connection->write('exit');
// use a close callback to ensure the upload is finished before continuing

View file

@ -9,6 +9,7 @@ namespace Icewind\SMB\Test;
use Icewind\SMB\BasicAuth;
use Icewind\SMB\Native\NativeServer;
use Icewind\SMB\Options;
use Icewind\SMB\System;
use Icewind\SMB\TimeZoneProvider;
@ -27,7 +28,8 @@ class NativeShareTest extends AbstractShareTest {
$this->config->password
),
new System(),
new TimeZoneProvider($this->config->host, new System())
new TimeZoneProvider(new System()),
new Options()
);
$this->share = $this->server->getShare($this->config->share);
if ($this->config->root) {

View file

@ -9,6 +9,7 @@ namespace Icewind\SMB\Test;
use Icewind\SMB\BasicAuth;
use Icewind\SMB\Native\NativeServer;
use Icewind\SMB\Options;
use Icewind\SMB\System;
use Icewind\SMB\TimeZoneProvider;
@ -44,7 +45,8 @@ class NativeStreamTest extends TestCase {
$this->config->password
),
new System(),
new TimeZoneProvider($this->config->host, new System())
new TimeZoneProvider(new System()),
new Options()
);
$this->share = $this->server->getShare($this->config->share);
if ($this->config->root) {

View file

@ -12,6 +12,7 @@ use Icewind\SMB\Change;
use Icewind\SMB\Exception\AlreadyExistsException;
use Icewind\SMB\INotifyHandler;
use Icewind\SMB\IShare;
use Icewind\SMB\Options;
use Icewind\SMB\System;
use Icewind\SMB\TimeZoneProvider;
use Icewind\SMB\Wrapped\Server;
@ -35,7 +36,8 @@ class NotifyHandlerTest extends TestCase {
$this->config->password
),
new System(),
new TimeZoneProvider($this->config->host, new System())
new TimeZoneProvider(new System()),
new Options()
);
}

View file

@ -24,26 +24,11 @@ class ParserTest extends \PHPUnit_Framework_TestCase {
array('RAH', IFileInfo::MODE_READONLY | IFileInfo::MODE_ARCHIVE | IFileInfo::MODE_HIDDEN)
);
}
/**
* @param string $timeZone
* @return \Icewind\SMB\TimeZoneProvider
*/
private function getTimeZoneProvider($timeZone) {
$mock = $this->getMockBuilder('\Icewind\SMB\TimeZoneProvider')
->disableOriginalConstructor()
->getMock();
$mock->expects($this->any())
->method('get')
->will($this->returnValue($timeZone));
return $mock;
}
/**
* @dataProvider modeProvider
*/
public function testParseMode($string, $mode) {
$parser = new \Icewind\SMB\Wrapped\Parser($this->getTimeZoneProvider('UTC'));
$parser = new \Icewind\SMB\Wrapped\Parser('UTC');
$this->assertEquals($mode, $parser->parseMode($string), 'Failed parsing ' . $string);
}
@ -104,7 +89,7 @@ class ParserTest extends \PHPUnit_Framework_TestCase {
* @dataProvider statProvider
*/
public function testStat($output, $stat) {
$parser = new \Icewind\SMB\Wrapped\Parser($this->getTimeZoneProvider('UTC'));
$parser = new \Icewind\SMB\Wrapped\Parser('UTC');
$this->assertEquals($stat, $parser->parseStat($output));
}
@ -130,7 +115,7 @@ class ParserTest extends \PHPUnit_Framework_TestCase {
* @dataProvider dirProvider
*/
public function testDir($output, $dir) {
$parser = new \Icewind\SMB\Wrapped\Parser($this->getTimeZoneProvider('CEST'));
$parser = new \Icewind\SMB\Wrapped\Parser('CEST');
$this->assertEquals($dir, $parser->parseDir($output, ''));
}
}

View file

@ -8,6 +8,7 @@
namespace Icewind\SMB\Test;
use Icewind\SMB\BasicAuth;
use Icewind\SMB\Options;
use Icewind\SMB\System;
use Icewind\SMB\TimeZoneProvider;
use Icewind\SMB\Wrapped\Server;
@ -31,7 +32,8 @@ class ServerTest extends TestCase {
$this->config->password
),
new System(),
new TimeZoneProvider($this->config->host, new System())
new TimeZoneProvider(new System()),
new Options()
);
}
@ -58,7 +60,8 @@ class ServerTest extends TestCase {
uniqid()
),
new System(),
new TimeZoneProvider($this->config->host, new System())
new TimeZoneProvider(new System()),
new Options()
);
$server->listShares();
}
@ -75,7 +78,8 @@ class ServerTest extends TestCase {
uniqid()
),
new System(),
new TimeZoneProvider($this->config->host, new System())
new TimeZoneProvider(new System()),
new Options()
);
$server->listShares();
}
@ -92,7 +96,8 @@ class ServerTest extends TestCase {
$this->config->password
),
new System(),
new TimeZoneProvider($this->config->host, new System())
new TimeZoneProvider(new System()),
new Options()
);
$server->listShares();
}
@ -110,7 +115,8 @@ class ServerTest extends TestCase {
$this->config->password
),
new System(),
new TimeZoneProvider($this->config->host, new System())
new TimeZoneProvider(new System()),
new Options()
);
$server->listShares();
}

View file

@ -8,6 +8,7 @@
namespace Icewind\SMB\Test;
use Icewind\SMB\BasicAuth;
use Icewind\SMB\Options;
use Icewind\SMB\System;
use Icewind\SMB\TimeZoneProvider;
use Icewind\SMB\Wrapped\Server as NormalServer;
@ -24,7 +25,8 @@ class ShareTest extends AbstractShareTest {
$this->config->password
),
new System(),
new TimeZoneProvider($this->config->host, new System())
new TimeZoneProvider(new System()),
new Options()
);
$this->share = $this->server->getShare($this->config->share);
if ($this->config->root) {
@ -49,7 +51,8 @@ class ShareTest extends AbstractShareTest {
$this->config->password
),
new System(),
new TimeZoneProvider($this->config->host, new System())
new TimeZoneProvider(new System()),
new Options()
);
$share = $this->server->getShare($this->config->share);
$share->dir($this->root);