Source of file Samba.php

Size: 9,627 Bytes - Last Modified: 2020-10-24T02:46:31+00:00

/home/travis/build/NextDom/nextdom-core/src/Helpers/Samba.php

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
<?php
/* This file is part of NextDom Software.
 *
 * NextDom is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * NextDom Software 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with NextDom Software. If not, see <http://www.gnu.org/licenses/>.
 */

namespace NextDom\Helpers;

use Icewind\SMB\BasicAuth;
use Icewind\SMB\IFileInfo;
use Icewind\SMB\ServerFactory;
use NextDom\Exceptions\CoreException;
use NextDom\Managers\ConfigManager;

define('SAMBA_DEFAULT_WORKSPACE', 'WORKGROUP');
define('SAMBA_DEFAULT_CONFIG_NAME', 'backup');
define('SAMBA_SORT_BY_TIME', 'mtime');
define('SAMBA_SORT_BY_SIZE', 'size');
define('SAMBA_SORT_ASC_ORDER', 'asc');

/**
 * Samba usage
 *
 * @package NextDom\Helpers
 */
class Samba
{
    /** @var \Icewind\SMB\IServer Samba client */
    private $client = null;
    /** @var string Share name */
    private $share = null;

    /**
     * Initialize Samba process
     *
     * @param string $host Server name or IP
     * @param string $user Samba user
     * @param string $password Samba user password
     * @param string $share Target share
     *
     * @throws CoreException
     */
    public function __construct(string $host, string $user, string $password, string $share)
    {
        try {
            // Server connection
            $serverFactory = new ServerFactory(null, null, null);
            $auth = new BasicAuth($user, SAMBA_DEFAULT_WORKSPACE, $password);
            $this->client = $serverFactory->createServer($host, $auth);
            $this->share = $share;
        } catch (\Exception $e) {
            CoreException::do_throw('{repo.samba.error.connect}: %s', $e->getMessage());
        }
    }

    /**
     * Create connection from config name
     *
     * @param string $target Type of target (backup for restore)
     *
     * @return Samba Connection to Samba server object
     *
     * @throws \Exception
     */
    public static function createFromConfig(string $target = SAMBA_DEFAULT_CONFIG_NAME)
    {
        $baseConfigKey = 'samba::' . $target;
        $configData = ConfigManager::byKeys([
            $baseConfigKey . '::ip',
            $baseConfigKey . '::share',
            $baseConfigKey . '::username',
            $baseConfigKey . '::password',
        ]);

        // Compatibility with old parameter semantic
        $matches = [];
        if (preg_match("%(//[^/]+/)(.*)%", $configData[$baseConfigKey . '::share'], $matches)) {
            $configData[$baseConfigKey . '::share'] = $matches[2];
        }
        return new Samba(
            $configData[$baseConfigKey . '::ip'],
            $configData[$baseConfigKey . '::username'],
            $configData[$baseConfigKey . '::password'],
            $configData[$baseConfigKey . '::share']);
    }

    /**
     * Replace special characters by -
     *
     * @param string $originalFilename Name of the file before replace
     *
     * @return string Clean filename
     */
    public static function cleanName(string $originalFilename)
    {
        return str_replace(['<', '>', ':', '\'', '"', '/', '\\', '|', '?', '*'], '-', $originalFilename);
    }

    /**
     * Copy file from the server
     *
     * @param string $src Path of the file on the server
     * @param string $dest Copy destination
     *
     * @throws CoreException
     */
    public function get($src, $dest)
    {
        try {
            $this->getShare()->get($src, $dest);
        } catch (\Icewind\SMB\Exception\NotFoundException $e) {
            CoreException::do_throw('{repo.samba.error.not-found}: %s', $src);
        } catch (\Throwable $e) {
            CoreException::do_throw('{repo.samba.error.unknown}: %s', $e->getMessage());
        }
    }

    /**
     * Get current share
     *
     * @param string|null $name Share name
     *
     * @return \Icewind\SMB\IShare|null Current share
     *
     * @throws CoreException
     */
    public function getShare(string $name = null)
    {
        if ($name === null) {
            $name = $this->share;
        }
        try {
            return $this->client->getShare($name);
        } catch (\Exception $e) {
            CoreException::do_throw('{repo.samba.error.connect}: %s', $e->getMessage());
        }
        return null;
    }

    /**
     * Copy file on the server
     *
     * @param string $src Path of the file to copy
     * @param string $dest Server path destination
     *
     * @throws CoreException
     */
    public function put($src, $dest)
    {
        try {
            $this->getShare()->put($src, $dest);
        } catch (\Icewind\SMB\Exception\NotFoundException $e) {
            CoreException::do_throw('{repo.samba.error.not-found}: %s', $src);
        } catch (\Throwable $e) {
            CoreException::do_throw('{repo.samba.error.unknown}: %s', $e->getMessage());
        }
    }

    /**
     * Delete a file from the server
     *
     * @param string $path Path of the file
     *
     * @return IFileInfo[]|null Content of the folder
     *
     * @throws CoreException
     */
    public function del($path)
    {
        try {
            $this->getShare()->del($path);
        } catch (\Icewind\SMB\Exception\NotFoundException $e) {
            CoreException::do_throw('{repo.samba.error.not-found}: %s', $path);
        } catch (\Throwable $e) {
            CoreException::do_throw('{repo.samba.error.unknown}: %s', $e->getMessage());
        }
        return null;
    }

    /**
     * Get files list in folder
     *
     * @param string $path Folder path
     * @param string $sort Sort method (mtime, size or name)
     * @param string $order Order (asc or desc)
     *
     * @return IFileInfo[] Folder content
     *
     * @throws CoreException
     */
    public function getFiles($path = '/', $sort = SAMBA_SORT_BY_TIME, $order = SAMBA_SORT_ASC_ORDER)
    {
        $entries = $this->getEntries($path, $sort, $order);
        $filteredEntries = array_filter($entries, [$this, 'filterByFile']);
        return $filteredEntries;
    }

    /**
     * Get folder content
     *
     * @param string $path Folder path
     * @param string $sort Sort method (mtime, size or name)
     * @param string $order Order (asc or desc)
     *
     * @return IFileInfo[]|null Folder content
     *
     * @throws CoreException
     */
    public function getEntries($path = '/', $sort = SAMBA_SORT_BY_TIME, $order = SAMBA_SORT_ASC_ORDER)
    {
        $entries = $this->dir($path);

        if ($entries !== null) {
            $sortMethodName = 'compareByName';
            switch ($sort) {
                case SAMBA_SORT_BY_TIME:
                    $sortMethodName = 'compareByTime';
                    break;
                case SAMBA_SORT_BY_SIZE:
                    $sortMethodName = 'compareBySize';
                    break;
            }
            usort($entries, [$this, $sortMethodName]);
            if ($order !== SAMBA_SORT_ASC_ORDER) {
                $entries = array_reverse($entries);
            }
        }
        return $entries;
    }

    /**
     * List folder content on the server
     *
     * @param string $path Path of the folder
     *
     * @return IFileInfo[]|null Content of the folder
     *
     * @throws CoreException
     */
    public function dir($path)
    {
        try {
            return $this->getShare()->dir($path);
        } catch (\Icewind\SMB\Exception\NotFoundException $e) {
            CoreException::do_throw('{repo.samba.error.not-found}: %s', $path);
        } catch (\Throwable $e) {
            CoreException::do_throw('{repo.samba.error.unknown}: %s', $e->getMessage());
        }
        return null;
    }

    /**
     * Test if item is a file.
     *
     * @param IFileInfo $currentItem Item to test
     *
     * @return True if item if file
     */
    private function filterByFile($currentItem)
    {
        return $currentItem->isDirectory() === false;
    }

    /**
     * Compare by name for sort list
     *
     * @param IFileInfo $firstFile Data of the first file
     * @param IFileInfo $secondFile Data of the second file
     *
     * @return int Sort information
     */
    private function compareByName($firstFile, $secondFile)
    {
        return strcmp($firstFile->getName(), $secondFile->getName());
    }

    /**
     * Compare by size for sort list
     *
     * @param IFileInfo $firstFile Data of the first file
     * @param IFileInfo $secondFile Data of the second file
     *
     * @return int Sort information
     */
    private function compareBySize($firstFile, $secondFile)
    {
        if ($firstFile->getSize() === $secondFile->getSize()) {
            return 0;
        }
        return ($firstFile->getSize() < $secondFile->getSize()) ? -1 : 1;
    }

    /**
     * Compare by time for sort list
     *
     * @param IFileInfo $firstFile Data of the first file
     * @param IFileInfo $secondFile Data of the second file
     *
     * @return int Sort information
     */
    private function compareByTime($firstFile, $secondFile)
    {
        if ($firstFile->getMTime() === $secondFile->getMTime()) {
            return 0;
        }
        return ($firstFile->getMTime() < $secondFile->getMTime()) ? -1 : 1;
    }
}