<?php

namespace Files\Backend\SMB;

require_once __DIR__ . "/../../files/php/Files/Backend/class.abstract_backend.php";
require_once __DIR__ . "/../../files/php/Files/Backend/class.exception.php";
require(__DIR__ . '/lib/SMB/vendor/autoload.php');

use Files\Backend\AbstractBackend;
use Files\Backend\iFeatureQuota;
use Files\Backend\iFeatureStreaming;
use Files\Backend\iFeatureVersionInfo;
use Files\Backend\Exception as BackendException;
use \Sabre\DAV\Exception as Exception;

// SMB Client files
use Icewind\SMB\Server;
use Icewind\SMB\NativeServer;

use Icewind\SMB\Exception\TimedOutException;
use Icewind\SMB\Exception\AccessDeniedException;
use Icewind\SMB\Exception\AuthenticationException;
use Icewind\SMB\Exception\ConnectionRefusedException;
use Icewind\SMB\Exception\ForbiddenException;
use Icewind\SMB\Exception\NotFoundException;
use Icewind\SMB\Exception\InvalidTypeException;
use Icewind\SMB\Exception\AlreadyExistsException;

/**
 * This is a file backend for SMB servers.
 *
 * Don't forget to build the SMB direcory with "composer create-project"
 * if you have updated it!
 *
 * @class   Backend
 * @extends AbstractBackend
 */
class Backend extends AbstractBackend implements iFeatureQuota, iFeatureVersionInfo, iFeatureStreaming
{
	/**
	 * Error codes
	 * see @parseErrorCodeToMessage for description
	 */
	const SMB_ERR_UNAUTHORIZED = 13;
	const SMB_ERR_FORBIDDEN = 1;
	const SMB_ERR_NOTFOUND = 404;
	const SMB_ERR_TIMEOUT = 408;
	const SMB_ERR_INTERNAL = 500;
	const SMB_ERR_UNREACHABLE = 111;
	const SMB_ERR_TMP = 801;
	const SMB_ERR_FEATURES = 802;
	const SMB_ERR_NO_SMB_LIB = 803;
	const SMB_ERR_UNIMPLEMENTED = 804;
	const SMB_ERR_FAILED_DEPENDENCY = 805;
	const SMB_ERR_INV_TYPE = 806;
	const SMB_ERR_ALREADY_EXISTS = 807;

	/**
	 * SMB Client connection timeout in ms.
	 */
	const SMB_CONN_TIMEOUT = 100;

	/**
	 * Configuration data for the extjs metaform.
	 */
	private $formConfig;
	private $formFields;
	private $metaConfig;

	/**
	 * @var boolean debuggin flag, if true, debugging is enabled
	 */
	var $debug = false;

	/**
	 * @var server address
	 */
	var $server = "localhost";

	/**
	 * @var string global path prefix for all requests
	 */
	var $share = "myshare";

	/**
	 * @var string workgroup
	 */
	var $workgroup = null;

	/**
	 * @var string the username
	 */
	var $user = "";

	/**
	 * @var string the password
	 */
	var $pass = "";

	/**
	 * @var the SMB share object.
	 */
	var $smb_share = null;

	/**
	 * @constructor
	 */
	function __construct()
	{

		// Initialization
		$this->debug = PLUGIN_FILESBROWSER_LOGLEVEL === "DEBUG" ? true : false;

		$this->init_form();

		// Set backend description
		$this->backendDescription = dgettext('plugin_filesbackendSMB', "With this backend, you can connect to any samba server (e.g. Windows Share).");
		
		// Set backend display name
		$this->backendDisplayName = "SMB";

		// Set backend version
		// TODO: this should be changed on every release
		$this->backendVersion = "3.0";
		
		// Backend name used in translations	
		$this->backendTransName = dgettext('plugin_filesbackendSMB', 'Files SMB Backend: ');
	}

	/**
	 * Initialise form fields
	 */
	private function init_form()
	{
		$this->formConfig = array(
			"labelAlign" => "left",
			"columnCount" => 1,
			"labelWidth" => 80,
			"defaults" => array(
				"width" => 292
			)
		);

		$this->formFields = array(
			array(
				"name" => "server_address",
				"fieldLabel" => dgettext('plugin_filesbackendSMB', 'Server address'),
				"editor" => array(
					"allowBlank" => false
				)
			),
			array(
				"name" => "server_path",
				"fieldLabel" => dgettext('plugin_filesbackendSMB', 'Share path'),
				"editor" => array(
					"allowBlank" => false
				)
			),
			array(
				"name" => "workgroup",
				"fieldLabel" => dgettext('plugin_filesbackendSMB', 'Workgroup (optional)'),
				"editor" => array(
					"ref" => "../../workgroupField",
					"allowBlank" => true
				)
			),
			array(
				"name" => "user",
				"fieldLabel" => dgettext('plugin_filesbackendSMB', 'Username'),
				"editor" => array(
					"ref" => "../../usernameField",
					"allowBlank" => false
				)
			),
			array(
				"name" => "password",
				"fieldLabel" => dgettext('plugin_filesbackendSMB', 'Password'),
				"editor" => array(
					"ref" => "../../passwordField",
					"inputType" => "password",
					"allowBlank" => false
				)
			),
			array(
				"name" => "use_zarafa_credentials",
				"fieldLabel" => dgettext('plugin_filesbackendSMB', 'Use Kopano credentials'),
				"editor" => array(
					"xtype" => "checkbox",
					"listeners" => array(
						"check" => "Zarafa.plugins.files.data.Actions.onCheckCredentials" // this javascript function will be called!
					)
				)
			),
		);

		$this->metaConfig = array(
			"success" => true,
			"metaData" => array(
				"fields" => $this->formFields,
				"formConfig" => $this->formConfig
			),
			"data" => array( // here we can specify the default values.
				"server_address" => "192.168.0.22",
				"server_path" => "myshare"
			)
		);
	}

	/**
	 * Initialize backend from $backend_config array
	 * @param $backend_config
	 */
	public function init_backend($backend_config)
	{
		$this->set_server($backend_config["server_address"]);
		$this->set_share($backend_config["server_path"]);
		$this->set_workgroup($backend_config["workgroup"]);

		//This reveals the password in the apache log, but might be useful some time.
		//error_log(print_r($backend_config, true));

		// set user and password
		if ($backend_config["use_zarafa_credentials"] === FALSE) {
			$this->set_user($backend_config["user"]);
			$this->set_pass($backend_config["password"]);
		} else {
			// For backward compatibility we will check if the Encryption store exists. If not,
			// we will fall back to the old way of retrieving the password from the session.
			if (class_exists('EncryptionStore')) {
				// Get the username and password from the Encryption store
				$encryptionStore = \EncryptionStore::getInstance();
				$this->set_user($encryptionStore->get('username'));
				$this->set_pass($encryptionStore->get('password'));
			} else {
				$this->set_user($GLOBALS['mapisession']->getUserName());
				$password = $_SESSION['password']; 
				if(function_exists('openssl_decrypt')) {
					$this->set_pass(openssl_decrypt($password, "des-ede3-cbc", PASSWORD_KEY, 0, PASSWORD_IV));
				}
			}
		}

		// do a check if PHP Posix extension is available
		// needed for: https://github.com/icewind1991/SMB/pull/22/files
		if (!function_exists('posix_kill')) {
			$this->log('[FATAL] posix_kill not found! PHP Posix extension should be installed - otherwise your system might slow down!');
		}
	}

	/**
	 * Set smb server. FQN or IP address.
	 *
	 * @param string $server hostname or ip of the ftp server
	 *
	 * @return void
	 */
	public function set_server($server)
	{
		$this->server = $server;
	}

	/**
	 * Set share name
	 *
	 * @param string $share
	 *
	 * @return void
	 */
	public function set_share($share)
	{
		$this->share = $share;
		$this->log('Share path set to ' . $this->share);
	}

	/**
	 * Set samba workgroup for user. Null if default.
	 *
	 * @param $workgroup
	 *
	 * @return void
	 */
	public function set_workgroup($workgroup)
	{
		$this->workgroup = $workgroup;
	}

	/**
	 * set user name for authentification
	 *
	 * @param string $user username
	 *
	 * @return void
	 */
	public function set_user($user)
	{
		$this->user = $user;
	}

	/**
	 * Set password for authentification
	 *
	 * @param string $pass password
	 *
	 * @return void
	 */
	public function set_pass($pass)
	{
		$this->pass = $pass;
	}

	/**
	 * set debug on (1) or off (0).
	 * produces a lot of debug messages in webservers error log if set to on (1).
	 *
	 * @param boolean $debug enable or disable debugging
	 *
	 * @return void
	 */
	public function set_debug($debug)
	{
		$this->debug = $debug;
	}

	/**
	 * Opens the connection to the webdav server.
	 *
	 * @throws BackendException if connection is not successful
	 * @return boolean true if action succeeded
	 */
	public function open()
	{
		$user = $this->user;

		// Add workgroup to username
		if (!empty($this->workgroup)) {
			$user = $this->workgroup . "\\" . $user;
		}

		if (Server::NativeAvailable()) {
			$this->log("Opening a nativ smb connection via libsmbclient-php");
			$server = new NativeServer($this->server, $user, $this->pass);
		} else {
			$this->log("libsmbclient-php not available, falling back to wrapping smbclient");
			$server = new Server($this->server, $user, $this->pass);
		}

		try {
			$this->smb_share = $server->getShare($this->share);
		} catch (AccessDeniedException $e) {
			$this->log("AccessDeniedException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_UNAUTHORIZED), self::SMB_ERR_UNAUTHORIZED);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Access denied'));
			throw $e;
		} catch (ForbiddenException $e) {
			$this->log("ForbiddenException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_FORBIDDEN), self::SMB_ERR_FORBIDDEN);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Forbidden'));
			throw $e;
		} catch (AuthenticationException $e) {
			$this->log("AuthenticationException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_UNAUTHORIZED), self::SMB_ERR_UNAUTHORIZED);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Authentication failed'));
			throw $e;
		} catch (TimedOutException $e) {
			$this->log("TimedOutException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_UNREACHABLE), self::SMB_ERR_UNREACHABLE);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Timeout'));
			throw $e;
		} catch (ConnectionRefusedException $e) {
			$this->log("ConnectionRefusedException " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_UNREACHABLE), self::SMB_ERR_UNREACHABLE);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Connection refused'));
			throw $e;
		} catch (\Exception $e) {
			// Something else failed...
			$this->log("General exception, opening failed: " . print_r($e, true));
			$e = new BackendException($e->getMessage(), self::SMB_ERR_INTERNAL);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Connection failed'));
			throw $e;
		}

		$this->log("SMB Share opened successfully");
		return true;
	}

	/**
	 * show content of a diretory
	 *
	 * @param string $path directory path
	 * @param boolean $hidefirst Optional parameter to hide the root entry. Default true
	 *
	 * @throws BackendException if request is not successful
	 *
	 * @return mixed array with directory content
	 */
	public function ls($dir, $hidefirst = true)
	{
		$lsdata = array();
		$this->log("[LS] start for dir: $dir");

		// Open a directory
		try {
			$content = $this->smb_share->dir($dir);
		} catch (ForbiddenException $e) {
			$this->log("LS ForbiddenException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_FORBIDDEN), self::SMB_ERR_FORBIDDEN);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Forbidden'));
			throw $e;
		} catch (NotFoundException $e) {
			$this->log("LS NotFoundException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_NOTFOUND), self::SMB_ERR_NOTFOUND);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Not found'));
			throw $e;
		} catch (InvalidTypeException $e) {
			$this->log("LS InvalidTypeException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_INV_TYPE), self::SMB_ERR_INV_TYPE);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Invalid item type'));
			throw $e;
		} catch (\Exception $e) {
			$this->log("LS General exception thrown, failed: " . print_r($e, true));
			$e = new BackendException($e->getMessage(), self::SMB_ERR_NOTFOUND);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Failed to list items'));
			throw $e;
		}

		foreach ($content as $record) {
			$name = $record->getName();

			if ($hidefirst && ($name === "." || $name === "..")) {
				continue;
			}

			$type = $record->isDirectory() ? "collection" : "file";
			$size = $record->getSize() === null ? 0 : $record->getSize();
			$lastmodified = date('m/d/Y H:i:s', $record->getMTime());

			$lsdata[rtrim($dir, "/") . "/" . $name] = array(
				"resourcetype" => $type,
				"getcontentlength" => $size,
				"getlastmodified" => $lastmodified,
				"getcontenttype" => null,
				"quota-used-bytes" => null,
				"quota-available-bytes" => null,
			);

		}

		return $lsdata;
	}

	/**
	 * create a new diretory
	 *
	 * @param string $dir directory path
	 *
	 * @throws BackendException if request is not successful
	 *
	 * @return boolean true if action succeeded
	 */
	public function mkcol($dir)
	{
		$this->log("Creating dir: " . $dir);
		try {
			$this->smb_share->mkdir($dir);
		} catch (NotFoundException $e) {
			$this->log("MKCOL NotFoundException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_NOTFOUND), self::SMB_ERR_NOTFOUND);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Directory not found'));
			throw $e;
		} catch (AlreadyExistsException $e) {
			$this->log("MKCOL AlreadyExistsException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_ALREADY_EXISTS), self::SMB_ERR_ALREADY_EXISTS);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Directory already exist'));
			throw $e;
		} catch (ForbiddenException $e) {
			$this->log("MKCOL ForbiddenException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_FORBIDDEN), self::SMB_ERR_FORBIDDEN);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Forbidden'));
			throw $e;
		} catch (\Exception $e) {
			$this->log("MKCOL general exception: " . print_r($e, true));
			$e = new BackendException($e->getMessage(), self::SMB_ERR_INTERNAL);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Failed'));
			throw $e;
		}

		return true;
	}

	/**
	 * delete a file or directory
	 *
	 * @param string $path file/directory path
	 *
	 * @throws BackendException if request is not successful
	 *
	 * @return boolean true if action succeeded
	 */
	public function delete($path)
	{
		// Check if we need to delete a file or folder
		try {
			$info = $this->smb_share->stat($path);
		} catch (\Exception $e) {
			$this->log("MKCOL failed: " . print_r($e, true));
		}

		if ($info && $info->isDirectory()) {
			try {
				$content = $this->smb_share->dir($path);
				// If folder is not empty then delete all content of folder.
				if (empty($content)) {
					$info = $this->smb_share->rmdir($path);
				} else {
					$info = $this->deleteAllContent($path);
				}
			} catch (NotFoundException $e) {
				$this->log("Remove directory NotFoundException: " . print_r($e, true));
				$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_NOTFOUND), self::SMB_ERR_NOTFOUND);
				$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Directory not found'));
				throw $e;
			} catch (InvalidTypeException $e) {
				$this->log("Remove directory InvalidTypeException: " . print_r($e, true));
				$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_INV_TYPE), self::SMB_ERR_INV_TYPE);
				$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Invalid Type'));
				throw $e;
			} catch (ForbiddenException $e) {
				$this->log("Remove directory ForbiddenException: " . print_r($e, true));
				$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_FORBIDDEN), self::SMB_ERR_FORBIDDEN);
				$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Forbidden'));
				throw $e;
			} catch (\Exception $e) {
				$this->log("Remove directory general exception: " . print_r($e, true));
				$e = new BackendException($e->getMessage(), self::SMB_ERR_INTERNAL);
				$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Deletion failed'));
				throw $e;
			}
		} else {
			try {
				$info = $this->smb_share->del($path);
			} catch (NotFoundException $e) {
				$this->log("Remove file NotFoundException: " . print_r($e, true));
				$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_NOTFOUND), self::SMB_ERR_NOTFOUND);
				$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Not found'));
				throw $e;
			} catch (InvalidTypeException $e) {
				$this->log("Remove file InvalidTypeException: " . print_r($e, true));
				$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_INV_TYPE), self::SMB_ERR_INV_TYPE);
				$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Invalid type'));
				throw $e;
			} catch (ForbiddenException $e) {
				$this->log("Remove file ForbiddenException: " . print_r($e, true));
				$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_FORBIDDEN), self::SMB_ERR_FORBIDDEN);
				$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Forbidden'));
				throw $e;
			} catch (\Exception $e) {
				$this->log("Remove file general exception, failed: " . print_r($e, true));
				$e = new BackendException($e->getMessage(), self::SMB_ERR_INTERNAL);
				$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Deletion failed'));
				throw $e;
			}
		}

		return true;
	}

	/**
	 * Delete all content of directory
	 *
	 * @param string $path file/directory path
	 */
	function deleteAllContent($path)
	{
		$content = $this->ls($path);
		// Delete all contents of folder.
		foreach ($content as $key => $record) {
			$this->delete($key);
		}
		// Finally delete the folder.
		$this->delete($path);
	}

	/**
	 * Move a file or collection on webdav server (serverside)
	 * If you set param overwrite as true, the target will be overwritten.
	 *
	 * @param string $src_path Source path
	 * @param string $dest_path Destination path
	 * @param boolean $overwrite Overwrite file if exists in $dest_path
	 *
	 * @throws BackendException if request is not successful
	 *
	 * @return boolean true if action succeeded
	 */
	public function move($src_path, $dst_path, $overwrite = false)
	{
		// Check if we need to delete the existing folder
		if ($overwrite) {
			try {
				$this->delete($dst_path);
			} catch (BackendException $ex) {
				// ignore - the file does not exist
			}
		}

		try {
			$this->smb_share->rename($src_path, $dst_path);
		} catch (NotFoundException $e) {
			$this->log("Move NotFoundException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_NOTFOUND), self::SMB_ERR_NOTFOUND);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Not found'));
			throw $e;
		} catch (AlreadyExistsException $e) {
			$this->log("Move AlreadyExistsException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_ALREADY_EXISTS), self::SMB_ERR_ALREADY_EXISTS);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Already exist'));
			throw $e;
		} catch (ForbiddenException $e) {
			$this->log("Move ForbiddenException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_FORBIDDEN), self::SMB_ERR_FORBIDDEN);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Forbidden'));
			throw $e;
		} catch (\Exception $e) {
			$this->log("Move general exception, failed: " . print_r($e, true));
			$e = new BackendException($e->getMessage(), self::SMB_ERR_INTERNAL);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Moving failed'));
			throw $e;
		}

		return true;
	}

	/**
	 * Puts a file into a collection.
	 *
	 * @param string $path Destination path
	 *
	 * @string mixed $data Any kind of data
	 * @throws BackendException if request is not successful
	 *
	 * @return boolean true if action succeeded
	 */
	public function put($path, $data)
	{
		$temp_file = tempnam(TMP_PATH, "$path");
		$fresult = file_put_contents($temp_file, $data);
		$result = $this->put_file($path, $temp_file);

		if ($fresult !== FALSE) {
			return $result;
		} else {
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_TMP), self::SMB_ERR_TMP);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Issues with TMP directory'));
			throw $e;
		}
	}

	/**
	 * Upload a local file
	 *
	 * @param string $path Destination path on the server
	 * @param string $filename Local filename for the file that should be uploaded
	 *
	 * @throws BackendException if request is not successful
	 *
	 * @return boolean true if action succeeded
	 */
	public function put_file($path, $filename)
	{
		$this->log("uploading file: " . $path . " - " . $filename);
		try {
			$this->smb_share->put($filename, $path);
		} catch (NotFoundException $e) {
			$this->log("put_file NotFoundException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_NOTFOUND), self::SMB_ERR_NOTFOUND);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Not found'));
			throw $e;
		} catch (InvalidTypeException $e) {
			$this->log("put_file InvalidTypeException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_INV_TYPE), self::SMB_ERR_INV_TYPE);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Invalid type'));
			throw $e;
		} catch (ForbiddenException $e) {
			$this->log("put_file ForbiddenException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_FORBIDDEN), self::SMB_ERR_FORBIDDEN);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Forbidden'));
			throw $e;
		} catch (\Exception $e) {
			$this->log("put_file general exception, failed: " . print_r($e, true));
			$e = new BackendException($e->getMessage(), self::SMB_ERR_INTERNAL);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Uploading failed'));
			throw $e;
		}

		return true;
	}

	/**
	 * Gets a file from a webdav collection.
	 *
	 * @param string $path The source path on the server
	 * @param mixed $buffer Buffer for the received data
	 *
	 * @throws BackendException if request is not successful
	 *
	 * @return boolean true if action succeeded
	 */
	public function get($path, &$buffer)
	{
		$temp_file = tempnam(TMP_PATH, stripslashes(base64_encode($path)));
		$result = $this->get_file($path, $temp_file);
		$buffer = file_get_contents($temp_file);
		unlink($temp_file);

		if ($result) {
			if ($buffer !== FALSE) {
				return $result;
			} else {
				$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_TMP), self::SMB_ERR_TMP);
				$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Issues with TMP directory'));
				throw $e;
			}
		} else {
			return $result;
		}
	}

	/**
	 * Gets a file from a collection into local filesystem.
	 *
	 * @param string $srcpath Source path on server
	 * @param string $localpath Destination path on local filesystem
	 *
	 * @throws BackendException if request is not successful
	 *
	 * @return boolean true if action succeeded
	 */
	public function get_file($srcpath, $localpath)
	{
		$this->log("getting file: " . $srcpath);
		try {
			$this->smb_share->get($srcpath, $localpath);
		} catch (NotFoundException $e) {
			$this->log("get_file NotFoundException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_NOTFOUND), self::SMB_ERR_NOTFOUND);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Not found'));
			throw $e;
		} catch (InvalidTypeException $e) {
			$this->log("get_file InvalidTypeException: " . print_r($e, true));
			$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_INV_TYPE), self::SMB_ERR_INV_TYPE);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Invalid type'));
			throw $e;
		} catch (\Exception $e) {
			$this->log("get_file general exception, failed: " . print_r($e, true));
			$e = new BackendException($e->getMessage(), self::SMB_ERR_INTERNAL);
			$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Failed to get file'));
			throw $e;
		}

		return true;
	}

	/**
	 * Public method copy_file
	 *
	 * Copy a file on webdav server
	 * Duplicates a file on the webdav server (serverside).
	 * All work is done on the webdav server. If you set param overwrite as true,
	 * the target will be overwritten.
	 *
	 * @param string $src_path Source path
	 * @param string $dest_path Destination path
	 * @param bool $overwrite Overwrite if file exists in $dst_path
	 *
	 * @throws BackendException if request is not successful
	 *
	 * @return boolean true if action succeeded
	 */
	public function copy_file($src_path, $dst_path, $overwrite = false)
	{
		// TODO Fix or remove me
		$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_UNIMPLEMENTED), self::SMB_ERR_UNIMPLEMENTED);
		$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Not implemented'));
		throw $e;
	}

	/**
	 * Public method copy_coll
	 *
	 * Copy a collection on webdav server
	 * Duplicates a collection on the webdav server (serverside).
	 * All work is done on the webdav server. If you set param overwrite as true,
	 * the target will be overwritten.
	 *
	 * @param string $src_path Source path
	 * @param string $dest_path Destination path
	 * @param bool $overwrite Overwrite if collection exists in $dst_path
	 *
	 * @throws BackendException if request is not successful
	 *
	 * @return boolean true if action succeeded
	 */
	public function copy_coll($src_path, $dst_path, $overwrite = false)
	{
		// TODO Fix or remove me
		$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_UNIMPLEMENTED), self::SMB_ERR_UNIMPLEMENTED);
		$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Not implemented'));
		throw $e;
	}

	/**
	 * Get's path information from samba server for one element
	 *
	 * @param string $path Path to file or folder
	 *
	 * @throws BackendException if request is not successful
	 *
	 * @return array directory info
	 */
	public function gpi($path)
	{
		$list = $this->ls(dirname($path), false); // get contents of the parent dir

		// be sure it is an array
		if (is_array($list)) {
			return $list[$path];
		}

		$this->log('gpi: wrong response from ls');
		$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_FAILED_DEPENDENCY), self::SMB_ERR_FAILED_DEPENDENCY);
		$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Request failed'));
		throw $e;
	}

	/**
	 * Get's server information
	 *
	 * @throws BackendException if request is not successful
	 * @return array with all header fields returned from webdav server.
	 */
	public function options()
	{
		// TODO Fix or remove me
		$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_UNIMPLEMENTED), self::SMB_ERR_UNIMPLEMENTED);
		$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Not implemented'));
		throw $e;
	}

	/**
	 * Gather whether a path points to a file or not
	 *
	 * @param string $path Path to file or folder
	 *
	 * @return boolean true if path points to a file, false otherwise
	 */
	public function is_file($path)
	{
		// Check if we have a file
		try {
			$info = $this->smb_share->stat($path);
		} catch (\Exception $e) {
			$this->log("is_file failed: " . print_r($e, true));
			return false;
		}

		return ($info && !$info->isDirectory());
	}

	/**
	 * Gather whether a path points to a directory
	 *
	 * @param string $path Path to file or folder
	 *
	 * @return boolean true if path points to a directory, false otherwise
	 */
	public function is_dir($path)
	{
		// Check if we have a folder
		try {
			$info = $this->smb_share->stat($path);
		} catch (\Exception $e) {
			$this->log("is_dir failed: " . print_r($e, true));
			return false;
		}

		return ($info && $info->isDirectory());
	}

	/**
	 * check if file/directory exists
	 *
	 * @param string $path Path to file or folder
	 *
	 * @return boolean true if path exists, false otherwise
	 */
	public function exists($path)
	{
		// Check if we have a folder
		try {
			$info = $this->smb_share->stat($path);
		} catch (\Exception $e) {
			$this->log("exist failed: " . print_r($e, true));
			return false;
		}

		return isset($info);
	}

	/**
	 * Copy a collection on webdav server
	 * Duplicates a collection on the webdav server (serverside).
	 * All work is done on the webdav server. If you set param overwrite as true,
	 * the target will be overwritten.
	 *
	 * @access private
	 *
	 * @param string $src_path Source path
	 * @param string $dest_path Destination path
	 * @param bool $overwrite Overwrite if collection exists in $dst_path
	 *
	 * @throws BackendException if request is not successful
	 *
	 * @return boolean true if action succeeded
	 */
	private function copy($src_path, $dst_path, $overwrite, $coll)
	{
		// TODO Fix or remove me
		$e = new BackendException($this->parseErrorCodeToMessage(self::SMB_ERR_UNIMPLEMENTED), self::SMB_ERR_UNIMPLEMENTED);
		$e->setTitle($this->backendTransName . dgettext('plugin_filesbackendSMB', 'Not implemented'));
		throw $e;
	}

	/**
	 * This function will return a user friendly error string.
	 *
	 * @param number $error_code A error code
	 *
	 * @return string userfriendly error message
	 */
	private function parseErrorCodeToMessage($error_code)
	{
		$error = intval($error_code);
		$msg = dgettext('plugin_filesbackendSMB', 'Unknown error');
		$contactAdmin = dgettext('plugin_filesbackendSMB', 'Please contact your system administrator.');

		switch ($error) {
			case self::SMB_ERR_UNAUTHORIZED:
				$msg = dgettext('plugin_filesbackendSMB', 'Unauthorized. Wrong username or password.');
				break;
			case self::SMB_ERR_UNREACHABLE:
				$msg = dgettext('plugin_filesbackendSMB', 'File server is not reachable. Please verify the connection.');
				break;
			case self::SMB_ERR_FORBIDDEN:
				$msg = dgettext('plugin_filesbackendSMB', 'You don\'t have enough permissions to view this file or folder.');
				break;
			case self::SMB_ERR_NOTFOUND:
				$msg = dgettext('plugin_filesbackendSMB', 'The file or folder is not available anymore.');
				break;
			case self::SMB_ERR_TIMEOUT:
				$msg = dgettext('plugin_filesbackendSMB', 'Connection to the file server timed out. Please check again later.');
				break;
			case self::SMB_ERR_FAILED_DEPENDENCY:
				$msg = dgettext('plugin_filesbackendSMB', 'The request failed.') . ' ' . $contactAdmin;
				break;
			case self::SMB_ERR_INTERNAL:
				// This is a general error, might be thrown due to a wrong IP, but we don't know.
				$msg = dgettext('plugin_filesbackendSMB', 'The file server encountered an internal problem.') . ' ' . $contactAdmin;
				break;
			case self::SMB_ERR_TMP:
				$msg = dgettext('plugin_filesbackendSMB', 'We could not write to temporary directory.') . ' ' . $contactAdmin;
				break;
			case self::SMB_ERR_FEATURES:
				$msg = dgettext('plugin_filesbackendSMB', 'We could not retrieve list of server features.') . ' ' . $contactAdmin;
				break;
			case self::SMB_ERR_NO_SMB_LIB:
				$msg = dgettext('plugin_filesbackendSMB', 'libsmbclient-php is not available.') . ' ' . $contactAdmin;
				break;
			case self::SMB_ERR_INV_TYPE:
				$msg = dgettext('plugin_filesbackendSMB', 'Destination is not valid. Please select a valid folder.');
				break;
			case self::SMB_ERR_ALREADY_EXISTS:
				$msg = dgettext('plugin_filesbackendSMB', 'File or folder already exists.');
				break;
			case self::SMB_ERR_UNIMPLEMENTED:
				$msg = dgettext('plugin_filesbackendSMB', 'This function is not yet implemented.');
				break;
		}

		return $msg;
	}

	public function getFormConfig()
	{
		$json = json_encode($this->metaConfig);

		if ($json === FALSE) {
			error_log(json_last_error());
		}

		return $json;
	}

	public function getFormConfigWithData()
	{
		return json_encode($this->metaConfig);
	}

	/**
	 * a simple php error_log wrapper.
	 *
	 * @access private
	 *
	 * @param string $err_string error message
	 *
	 * @return void
	 */
	private function log($err_string)
	{
		if ($this->debug) {
			error_log("[BACKEND_SMB]: " . $err_string);
		}
	}

	/**
	 * ============================ FEATURE FUNCTIONS ========================
	 */

	/**
	 * Returns the bytes that are currently used.
	 *
	 * @param string $dir directory to check
	 *
	 * @return int bytes that are used or -1 on error
	 */
	public function getQuotaBytesUsed($dir)
	{
		// get directory informations for the root folder
		if (function_exists("smbclient_statvfs")) {
			// Create new state:
			$state = @smbclient_state_new();

			// Initialize the state with workgroup, username and password:
			@smbclient_state_init($state, $this->workgroup, $this->user, $this->pass);

			// Open a directory:
			$stats = @smbclient_statvfs($state, 'smb://' . $this->server . '/' . $this->share);

			// Free the state:
			@smbclient_state_free($state);

			if ($stats) {
				$total = ceil($stats["blocks"] * $stats["bsize"]);
				$available = ceil($stats["bfree"] * $stats["bsize"]);
				$used = $total - $available;

				return $used;
			}
		}

		return -1;
	}

	/**
	 * Returns the bytes that are currently available.
	 *
	 * @param string $dir directory to check
	 *
	 * @return int bytes that are available or -1 on error
	 */
	public function getQuotaBytesAvailable($dir)
	{
		// get directory informations for the root folder
		if (function_exists("smbclient_statvfs")) {
			// Create new state:
			$state = @smbclient_state_new();

			// Initialize the state with workgroup, username and password:
			@smbclient_state_init($state, $this->workgroup, $this->user, $this->pass);

			// Open a directory:
			$stats = @smbclient_statvfs($state, 'smb://' . $this->server . '/' . $this->share . $dir);

			// Free the state:
			@smbclient_state_free($state);

			if ($stats) {
				$available = ceil($stats["bfree"] * $stats["bsize"]);
				return $available;
			}
		}

		return -1;
	}

	/**
	 * Return the version string of the server backend.
	 * @return String
	 */
	public function getServerVersion()
	{
		if (function_exists("smbclient_library_version")) {
			$version = @smbclient_library_version();
		} else {
			$version = "smbphpwrapper-ededbfbaa3"; // TODO: update the hash if you update the lib
		}

		return $version;
	}

	/**
	 * Open a readable stream to a remote file
	 *
	 * @param string $path
	 * @return resource a read only stream with the contents of the remote file or false on failure
	 */
	public function getStreamreader($path)
	{
		try {
			return $this->smb_share->read($path);
		} catch (\Exception $e) {
			$this->log("STREAMREADER failed: " . print_r($e, true));
			return false;
		}
	}

	/**
	 * Open a writable stream to a remote file
	 *
	 * @param string $path
	 * @return resource a write only stream to upload a remote file or false on failure
	 */
	public function getStreamwriter($path)
	{
		try {
			return $this->smb_share->write(rawurldecode($path));
		} catch (\Exception $e) {
			$this->log("STREAMWRITER failed: " . print_r($e, true));
			return false;
		}
	}
}

?>
