Completed
Push — master ( b623cc...81ff03 )
by Robin
03:26
created

NativeShare::get()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5.0592

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 13
cts 15
cp 0.8667
rs 9.2088
c 0
b 0
f 0
cc 5
nc 5
nop 2
crap 5.0592
1
<?php
2
/**
3
 * Copyright (c) 2014 Robin Appelman <[email protected]>
4
 * This file is licensed under the Licensed under the MIT license:
5
 * http://opensource.org/licenses/MIT
6
 */
7
8
namespace Icewind\SMB\Native;
9
10
use Icewind\SMB\AbstractShare;
11
use Icewind\SMB\Exception\AlreadyExistsException;
12
use Icewind\SMB\Exception\AuthenticationException;
13
use Icewind\SMB\Exception\ConnectionException;
14
use Icewind\SMB\Exception\DependencyException;
15
use Icewind\SMB\Exception\InvalidHostException;
16
use Icewind\SMB\Exception\InvalidPathException;
17
use Icewind\SMB\Exception\InvalidResourceException;
18
use Icewind\SMB\Exception\InvalidTypeException;
19
use Icewind\SMB\Exception\NotFoundException;
20
use Icewind\SMB\IFileInfo;
21
use Icewind\SMB\INotifyHandler;
22
use Icewind\SMB\IServer;
23
use Icewind\SMB\Wrapped\Server;
24
use Icewind\SMB\Wrapped\Share;
25
26
class NativeShare extends AbstractShare {
27
	/**
28
	 * @var IServer $server
29
	 */
30
	private $server;
31
32
	/**
33
	 * @var string $name
34
	 */
35
	private $name;
36
37
	/**
38
	 * @var ?NativeState $state
0 ignored issues
show
Documentation introduced by
The doc-type ?NativeState could not be parsed: Unknown type name "?NativeState" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
39 494
	 */
40 494
	private $state;
41 494
42 494
	public function __construct(IServer $server, string $name) {
43 494
		parent::__construct();
44
		$this->server = $server;
45
		$this->name = $name;
46
	}
47
48
	/**
49
	 * @throws ConnectionException
50 494
	 * @throws AuthenticationException
51 494
	 * @throws InvalidHostException
52 494
	 */
53
	protected function getState(): NativeState {
54
		if ($this->state and $this->state instanceof NativeState) {
55 494
			return $this->state;
56 494
		}
57 494
58
		$this->state = new NativeState();
59
		$this->state->init($this->server->getAuth(), $this->server->getOptions());
60
		return $this->state;
61
	}
62
63
	/**
64
	 * Get the name of the share
65 2
	 *
66 2
	 * @return string
67
	 */
68
	public function getName(): string {
69 494
		return $this->name;
70 494
	}
71 494
72 494
	private function buildUrl(string $path): string {
73 494
		$this->verifyPath($path);
74 494
		$url = sprintf('smb://%s/%s', $this->server->getHost(), $this->name);
75 494
		if ($path) {
76
			$path = trim($path, '/');
77 494
			$url .= '/';
78
			$url .= implode('/', array_map('rawurlencode', explode('/', $path)));
79
		}
80
		return $url;
81
	}
82
83
	/**
84
	 * List the content of a remote folder
85
	 *
86
	 * @param string $path
87
	 * @return IFileInfo[]
88
	 *
89 494
	 * @throws NotFoundException
90 494
	 * @throws InvalidTypeException
91
	 */
92 494
	public function dir(string $path): array {
93 494
		$files = [];
94 494
95 494
		$dh = $this->getState()->opendir($this->buildUrl($path));
96 186
		while ($file = $this->getState()->readdir($dh, $path)) {
97 186
			$name = $file['name'];
98
			if ($name !== '.' and $name !== '..') {
99
				$fullPath = $path . '/' . $name;
100
				$files [] = new NativeFileInfo($this, $fullPath, $name);
101 494
			}
102 494
		}
103
104
		$this->getState()->closedir($dh, $path);
105
		return $files;
106
	}
107
108
	/**
109 50
	 * @param string $path
110 50
	 * @return IFileInfo
111
	 */
112
	public function stat(string $path): IFileInfo {
113 50
		$info = new NativeFileInfo($this, $path, self::mb_basename($path));
114
115 30
		// trigger attribute loading
116
		$info->getSize();
117
118
		return $info;
119
	}
120
121
	/**
122
	 * Multibyte unicode safe version of basename()
123
	 *
124
	 * @param string $path
125 50
	 * @link http://php.net/manual/en/function.basename.php#121405
126 50
	 * @return string
127 30
	 */
128 20
	protected static function mb_basename(string $path): string {
129 18
		if (preg_match('@^.*[\\\\/]([^\\\\/]+)$@s', $path, $matches)) {
130
			return $matches[1];
131
		} elseif (preg_match('@^([^\\\\/]+)$@s', $path, $matches)) {
132 2
			return $matches[1];
133
		}
134
135
		return '';
136
	}
137
138
	/**
139
	 * Create a folder on the share
140
	 *
141
	 * @param string $path
142
	 * @return bool
143
	 *
144 494
	 * @throws NotFoundException
145 494
	 * @throws AlreadyExistsException
146
	 */
147
	public function mkdir(string $path): bool {
148
		return $this->getState()->mkdir($this->buildUrl($path));
149
	}
150
151
	/**
152
	 * Remove a folder on the share
153
	 *
154
	 * @param string $path
155
	 * @return bool
156
	 *
157 494
	 * @throws NotFoundException
158 494
	 * @throws InvalidTypeException
159
	 */
160
	public function rmdir(string $path): bool {
161
		return $this->getState()->rmdir($this->buildUrl($path));
162
	}
163
164
	/**
165
	 * Delete a file on the share
166
	 *
167
	 * @param string $path
168
	 * @return bool
169
	 *
170 250
	 * @throws NotFoundException
171 250
	 * @throws InvalidTypeException
172
	 */
173
	public function del(string $path): bool {
174
		return $this->getState()->unlink($this->buildUrl($path));
175
	}
176
177
	/**
178
	 * Rename a remote file
179
	 *
180
	 * @param string $from
181
	 * @param string $to
182
	 * @return bool
183
	 *
184 56
	 * @throws NotFoundException
185 56
	 * @throws AlreadyExistsException
186
	 */
187
	public function rename(string $from, string $to): bool{
188
		return $this->getState()->rename($this->buildUrl($from), $this->buildUrl($to));
189
	}
190
191
	/**
192
	 * Upload a local file
193
	 *
194
	 * @param string $source local file
195
	 * @param string $target remove file
196
	 * @return bool
197
	 *
198 210
	 * @throws NotFoundException
199 210
	 * @throws InvalidTypeException
200 210
	 */
201
	public function put(string $source, string $target): bool {
202 192
		$sourceHandle = fopen($source, 'rb');
203
		$targetUrl = $this->buildUrl($target);
204 188
205 188
		$targetHandle = $this->getState()->create($targetUrl);
206
207 188
		while ($data = fread($sourceHandle, NativeReadStream::CHUNK_SIZE)) {
208 188
			$this->getState()->write($targetHandle, $data, $targetUrl);
209
		}
210
		$this->getState()->close($targetHandle, $targetUrl);
211
		return true;
212
	}
213
214
	/**
215
	 * Download a remote file
216
	 *
217
	 * @param string $source remove file
218
	 * @param string $target local file
219
	 * @return bool
220
	 *
221
	 * @throws AuthenticationException
222
	 * @throws ConnectionException
223 90
	 * @throws InvalidHostException
224 90
	 * @throws InvalidPathException
225 18
	 * @throws InvalidResourceException
226
	 */
227
	public function get(string $source, string $target): bool {
228 72
		if (!$target) {
229 68
			throw new InvalidPathException('Invalid target path: Filename cannot be empty');
230
		}
231
232
		$sourceHandle = $this->getState()->open($this->buildUrl($source), 'r');
233 68
234 68
		$targetHandle = @fopen($target, 'wb');
235 2
		if (!$targetHandle) {
236 2
			$error = error_get_last();
237 2
			if (is_array($error)) {
238
				$reason = $error['message'];
239
			} else {
240
				$reason = 'Unknown error';
241 2
			}
242 2
			$this->getState()->close($sourceHandle, $this->buildUrl($source));
243
			throw new InvalidResourceException('Failed opening local file "' . $target . '" for writing: ' . $reason);
244
		}
245 66
246 66
		while ($data = $this->getState()->read($sourceHandle, NativeReadStream::CHUNK_SIZE, $source)) {
247
			fwrite($targetHandle, $data);
248 66
		}
249 66
		$this->getState()->close($sourceHandle, $this->buildUrl($source));
250
		return true;
251
	}
252
253
	/**
254
	 * Open a readable stream to a remote file
255
	 *
256
	 * @param string $source
257
	 * @return resource a read only stream with the contents of the remote file
258
	 *
259
	 * @throws NotFoundException
260
	 * @throws InvalidTypeException
261 58
	 */
262 58
	public function read(string $source) {
263 40
		$url = $this->buildUrl($source);
264 40
		$handle = $this->getState()->open($url, 'r');
265
		return NativeReadStream::wrap($this->getState(), $handle, 'r', $url);
266
	}
267
268
	/**
269
	 * Open a writeable stream to a remote file
270
	 * Note: This method will truncate the file to 0bytes first
271
	 *
272
	 * @param string $source
273
	 * @return resource a writeable stream
274
	 *
275
	 * @throws NotFoundException
276
	 * @throws InvalidTypeException
277 58
	 */
278 58
	public function write(string $source) {
279 40
		$url = $this->buildUrl($source);
280 40
		$handle = $this->getState()->create($url);
281
		return NativeWriteStream::wrap($this->getState(), $handle, 'w', $url);
282
	}
283
284
	/**
285
	 * Open a writeable stream and set the cursor to the end of the stream
286
	 *
287
	 * @param string $source
288
	 * @return resource a writeable stream
289
	 *
290
	 * @throws NotFoundException
291
	 * @throws InvalidTypeException
292 2
	 */
293 2
	public function append(string $source) {
294 2
		$url = $this->buildUrl($source);
295 2
		$handle = $this->getState()->open($url, "a+");
296
		return NativeWriteStream::wrap($this->getState(), $handle, "a", $url);
297
	}
298
299
	/**
300
	 * Get extended attributes for the path
301
	 *
302
	 * @param string $path
303
	 * @param string $attribute attribute to get the info
304
	 * @return string the attribute value
305 210
	 */
306 210
	public function getAttribute(string $path, string $attribute): string {
307
		return $this->getState()->getxattr($this->buildUrl($path), $attribute);
308
	}
309
310
	/**
311
	 * Set extended attributes for the given path
312
	 *
313
	 * @param string $path
314
	 * @param string $attribute attribute to get the info
315
	 * @param string|int $value
316
	 * @return mixed the attribute value
317
	 */
318
	public function setAttribute(string $path, string $attribute, $value) {
319
		if (is_int($value)) {
320
			if ($attribute === 'system.dos_attr.mode') {
321
				$value = '0x' . dechex($value);
322
			} else {
323
				throw new \InvalidArgumentException("Invalid value for attribute");
324
			}
325
		}
326
327
		return $this->getState()->setxattr($this->buildUrl($path), $attribute, $value);
328
	}
329
330
	/**
331
	 * Set DOS comaptible node mode
332
	 *
333
	 * @param string $path
334
	 * @param int $mode a combination of FileInfo::MODE_READONLY, FileInfo::MODE_ARCHIVE, FileInfo::MODE_SYSTEM and FileInfo::MODE_HIDDEN, FileInfo::NORMAL
335
	 * @return mixed
336
	 */
337
	public function setMode(string $path, int $mode) {
338
		return $this->setAttribute($path, 'system.dos_attr.mode', $mode);
339
	}
340
341
	/**
342
	 * Start smb notify listener
343
	 * Note: This is a blocking call
344
	 *
345
	 * @param string $path
346
	 * @return INotifyHandler
347
	 */
348
	public function notify(string $path): INotifyHandler {
349
		// php-smbclient does not support notify (https://github.com/eduardok/libsmbclient-php/issues/29)
350
		// so we use the smbclient based backend for this
351
		if (!Server::available($this->server->getSystem())) {
352
			throw new DependencyException('smbclient not found in path for notify command');
353
		}
354
		$share = new Share($this->server, $this->getName(), $this->server->getSystem());
355
		return $share->notify($path);
356
	}
357 494
358 494
	public function getServer(): IServer {
359 494
		return $this->server;
360
	}
361
362
	public function __destruct() {
363
		unset($this->state);
364
	}
365
}
366