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