FileStorage   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 203
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 20
lcom 1
cbo 4
dl 0
loc 203
c 0
b 0
f 0
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A get_root() 0 4 1
A get_index() 0 4 1
A __construct() 0 10 2
A index() 0 10 1
A add() 0 16 2
B release() 0 24 4
A find() 0 15 2
A find_by_hash() 0 12 2
A hash() 0 4 1
A create_pathname() 0 6 2
A assert_hash_is_used() 0 7 2
1
<?php
2
3
/*
4
 * This file is part of the Icybee package.
5
 *
6
 * (c) Olivier Laviale <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Icybee\Modules\Files\Storage;
13
14
use ICanBoogie\Accessor\AccessorTrait;
15
16
/**
17
 * @property-read string $root
18
 * @property-read FileStorageIndex $index
19
 */
20
class FileStorage
21
{
22
	use AccessorTrait;
23
24
	/**
25
	 * @var string
26
	 */
27
	private $root;
28
29
	protected function get_root()
30
	{
31
		return $this->root;
32
	}
33
34
	/**
35
	 * @var FileStorageIndex
36
	 */
37
	private $index;
38
39
	protected function get_index()
40
	{
41
		return $this->index;
42
	}
43
44
	/**
45
	 * @param string $root
46
	 * @param FileStorageIndex $index
47
	 */
48
	public function __construct($root, FileStorageIndex $index)
49
	{
50
		if (!$root)
51
		{
52
			throw new \InvalidArgumentException("`\$root` parameter cannot be empty.");
53
		}
54
55
		$this->root = rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
56
		$this->index = $index;
57
	}
58
59
	/**
60
	 * Adds a reference.
61
	 *
62
	 * @param int $nid
63
	 * @param string $uuid
64
	 * @param string $hash
65
	 *
66
	 * @return IndexKey
67
	 */
68
	public function index($nid, $uuid, $hash)
69
	{
70
		$this->assert_hash_is_used($hash);
71
72
		$key = IndexKey::from([ $nid, $uuid, $hash ]);
73
74
		$this->index->add($key);
75
76
		return $key;
77
	}
78
79
	/**
80
	 * Adds a file.
81
	 *
82
	 * @param string $source Absolute path to the source file.
83
	 *
84
	 * @return Pathname
85
	 */
86
	public function add($source)
87
	{
88
		$hash = $this->hash($source);
89
		$pathname = $this->find_by_hash($hash);
90
91
		if ($pathname)
92
		{
93
			return $pathname;
94
		}
95
96
		$pathname = $this->create_pathname($source, $hash);
97
98
		copy($source, $pathname);
99
100
		return $pathname;
101
	}
102
103
	/**
104
	 * Releases a reference to a file.
105
	 *
106
	 * If a file has no reference left it is deleted.
107
	 *
108
	 * @param $key_or_id_or_uuid_or_hash
109
	 */
110
	public function release($key_or_id_or_uuid_or_hash)
111
	{
112
		$index = $this->index;
113
		$matches = $index->find($key_or_id_or_uuid_or_hash);
114
115
		foreach ($matches as $key)
116
		{
117
			$index->delete($key);
118
		}
119
120
		foreach ($matches as $key)
121
		{
122
			$hash = $key->hash;
123
124
			if ($index->find($hash))
125
			{
126
				continue;
127
			}
128
129
			$pathname = $this->find_by_hash($hash);
130
131
			unlink($pathname);
132
		}
133
	}
134
135
	/**
136
	 * Finds a file.
137
	 *
138
	 * @param IndexKey|int|string $key_or_nid_or_uuid_or_hash
139
	 *
140
	 * @return Pathname|null
141
	 */
142
	public function find($key_or_nid_or_uuid_or_hash)
143
	{
144
		$matches = $this->index->find($key_or_nid_or_uuid_or_hash);
145
146
		if (!$matches)
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type Icybee\Modules\Files\Storage\IndexKey[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
147
		{
148
			return null;
149
		}
150
151
		/* @var $key IndexKey */
152
153
		$key = reset($matches);
154
155
		return $this->find_by_hash($key->hash);
156
	}
157
158
	/**
159
	 * Finds a file using its hash.
160
	 *
161
	 * @param string $hash A hash from {@link Pathname::hash()}.
162
	 *
163
	 * @return Pathname|null
164
	 */
165
	protected function find_by_hash($hash)
166
	{
167
		$di = new \DirectoryIterator($this->root);
168
		$di = new \RegexIterator($di, '#^' . preg_quote($hash) . '\-#');
169
170
		foreach ($di as $file)
171
		{
172
			return Pathname::from($file->getPathname());
173
		}
174
175
		return null;
176
	}
177
178
	/**
179
	 * Hashes a file.
180
	 *
181
	 * The file is hashed with {@link Pathname::hash()}.
182
	 *
183
	 * @param string $pathname Absolute pathname to the file.
184
	 *
185
	 * @return string The hash of the file.
186
	 *
187
	 * @throws \LogicException if the file cannot be hashed.
188
	 */
189
	public function hash($pathname)
190
	{
191
		return Pathname::hash($pathname);
192
	}
193
194
	/**
195
	 * Creates hash pathname.
196
	 *
197
	 * @param string $pathname
198
	 * @param string $hash A hash from {@link Pathname::hash()}. If empty a hash is computed from
199
	 * the file.
200
	 *
201
	 * @return Pathname
202
	 */
203
	public function create_pathname($pathname, $hash = null)
204
	{
205
		$hash = $hash ?: $this->hash($pathname);
206
207
		return Pathname::from([ $this->root, $hash ]);
208
	}
209
210
	/**
211
	 * Asserts that a hash is used by a file.
212
	 *
213
	 * @param string $hash
214
	 */
215
	protected function assert_hash_is_used($hash)
216
	{
217
		if (!$this->find_by_hash($hash))
218
		{
219
			throw new \LogicException("No file matches the hash `$hash`.");
220
		}
221
	}
222
}
223