Skip to content

Commit 792b0c6

Browse files
authored
Merge pull request #31 from chrisnharvey/augment-dirs
Fix deep file and directory listing
2 parents 453860a + ae47026 commit 792b0c6

File tree

2 files changed

+132
-10
lines changed

2 files changed

+132
-10
lines changed

src/SwiftAdapter.php

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,23 @@ public function listContents(string $path, bool $deep): iterable
236236
{
237237
$location = $this->prefixer->prefixDirectoryPath($path);
238238

239-
// Fetch objects with preudo-directory support. See:
240-
// https://docs.openstack.org/swift/latest/api/pseudo-hierarchical-folders-directories.html
241-
$objectList = $this->container->listObjects([
242-
'prefix' => $location,
243-
'delimiter' => '/',
244-
]);
239+
$config = ['prefix' => $location];
240+
241+
// Fetch objects with preudo-directory support. This limits the returned list of
242+
// objects to only those that are direct children of the queried location.
243+
// See: https://docs.openstack.org/swift/latest/api/pseudo-hierarchical-folders-directories.html
244+
if (!$deep) {
245+
$config['delimiter'] = '/';
246+
}
247+
248+
$objectList = $this->container->listObjects($config);
249+
250+
// Swift only returns directories when the delimiter is used (see above).
251+
// But this does not recurse the directory structure. In this case we have
252+
// to generate the pseudo directories from the list of objects ourselves.
253+
if ($deep) {
254+
$objectList = $this->augmentPseudoDirectories($location, $objectList);
255+
}
245256

246257
foreach ($objectList as $object) {
247258
yield $this->normalizeObject($object);
@@ -375,4 +386,43 @@ protected function getStreamFromResource($resource): Stream
375386
{
376387
return new Stream($resource);
377388
}
389+
390+
/**
391+
* Adds pseudo-directories to a list of storage objects.
392+
*
393+
* @param string $location
394+
* @param iterable $objectList
395+
*
396+
* @return \Generator
397+
*/
398+
protected function augmentPseudoDirectories(string $location, iterable $objectList): \Generator
399+
{
400+
$processedDirectories = [];
401+
$prefixLength = strlen($location);
402+
403+
foreach ($objectList as $object) {
404+
// Strip the prefix from the path. We only want to augment directories from
405+
// the prefix down.
406+
$path = explode('/', substr($object->name, $prefixLength));
407+
$filename = array_pop($path);
408+
$fullPath = '';
409+
foreach ($path as $part) {
410+
$fullPath .= $part.'/';
411+
if (!array_key_exists($fullPath, $processedDirectories)) {
412+
$processedDirectories[$fullPath] = null;
413+
$dirObject = clone $object;
414+
// Re-apply the prefix here.
415+
$dirObject->name = $location.$fullPath;
416+
$dirObject->hash = null;
417+
$dirObject->contentType = null;
418+
$dirObject->contentLength = null;
419+
$dirObject->lastModified = null;
420+
421+
yield $dirObject;
422+
}
423+
}
424+
425+
yield $object;
426+
}
427+
}
378428
}

tests/SwiftAdapterTest.php

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,9 @@ public function testListContents()
191191

192192
public function testListContentsPseudoDirectory()
193193
{
194-
$object = Mockery::mock(StorageObject::class);
195-
$object->name = 'name/';
196-
$generator = function () use ($object) {
197-
yield $object;
194+
$this->object->name = 'name/';
195+
$generator = function () {
196+
yield $this->object;
198197
};
199198
$objects = $generator();
200199
$this->container->shouldReceive('listObjects')
@@ -217,6 +216,79 @@ public function testListContentsPseudoDirectory()
217216
$this->assertEquals($expect, $contents[0]->jsonSerialize());
218217
}
219218

219+
public function testListContentsDeep()
220+
{
221+
$times = mt_rand(1, 10);
222+
223+
$generator = function () {
224+
yield $this->object;
225+
};
226+
227+
$objects = $generator();
228+
229+
$this->container->shouldReceive('listObjects')
230+
->once()
231+
->with([
232+
'prefix' => 'hello/',
233+
])
234+
->andReturn($objects);
235+
236+
$expect = [
237+
'path' => 'name',
238+
'type' => 'file',
239+
'last_modified' => 1628624822,
240+
'mime_type' => 'text/html; charset=UTF-8',
241+
'visibility' => null,
242+
'file_size' => 0,
243+
'extra_metadata' => [],
244+
];
245+
246+
$contents = $this->adapter->listContents('hello', true);
247+
248+
foreach ($contents as $file) {
249+
$this->assertEquals($expect, $file->jsonSerialize());
250+
}
251+
}
252+
253+
public function testListContentsPseudoDirectoryDeep()
254+
{
255+
$this->object->name = 'pseudo/directory/name';
256+
$generator = function () {
257+
yield $this->object;
258+
};
259+
$objects = $generator();
260+
$this->container->shouldReceive('listObjects')
261+
->once()
262+
->with([
263+
'prefix' => 'pseudo/',
264+
])
265+
->andReturn($objects);
266+
267+
$expect = [
268+
[
269+
'path' => 'pseudo/directory',
270+
'type' => 'dir',
271+
'visibility' => null,
272+
'extra_metadata' => [],
273+
'last_modified' => null,
274+
],
275+
[
276+
'path' => 'pseudo/directory/name',
277+
'type' => 'file',
278+
'last_modified' => 1628624822,
279+
'mime_type' => 'text/html; charset=UTF-8',
280+
'visibility' => null,
281+
'file_size' => 0,
282+
'extra_metadata' => [],
283+
],
284+
];
285+
286+
$contents = $this->adapter->listContents('pseudo', true);
287+
foreach ($contents as $index => $value) {
288+
$this->assertEquals($expect[$index], $value->jsonSerialize());
289+
}
290+
}
291+
220292
public function testMove()
221293
{
222294
$this->object->shouldReceive('copy')->once()->with([

0 commit comments

Comments
 (0)