398 lines
9.6 KiB
398 lines
9.6 KiB
<?php |
|
|
|
namespace Drupal\islandora\Flysystem\Adapter; |
|
|
|
use Islandora\Chullo\IFedoraApi; |
|
use League\Flysystem\AdapterInterface; |
|
use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait; |
|
use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait; |
|
use League\Flysystem\Config; |
|
use GuzzleHttp\Psr7; |
|
use GuzzleHttp\Psr7\Response; |
|
use GuzzleHttp\Psr7\StreamWrapper; |
|
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface; |
|
|
|
/** |
|
* Fedora adapter for Flysystem. |
|
*/ |
|
class FedoraAdapter implements AdapterInterface { |
|
|
|
use StreamedCopyTrait; |
|
use NotSupportingVisibilityTrait; |
|
|
|
/** |
|
* Fedora client. |
|
* |
|
* @var \Islandora\Chullo\IFedoraApi |
|
*/ |
|
protected $fedora; |
|
|
|
/** |
|
* Mimetype guesser. |
|
* |
|
* @var \Symfony\Component\HttpFoundation\File\Mimetype\MimeTypeGuesserInterface |
|
*/ |
|
protected $mimeTypeGuesser; |
|
|
|
/** |
|
* Constructs a Fedora adapter for Flysystem. |
|
* |
|
* @param \Islandora\Chullo\IFedoraApi $fedora |
|
* Fedora client. |
|
* @param \Symfony\Component\HttpFoundation\File\Mimetype\MimeTypeGuesserInterface $mime_type_guesser |
|
* Mimetype guesser. |
|
*/ |
|
public function __construct(IFedoraApi $fedora, MimeTypeGuesserInterface $mime_type_guesser) { |
|
$this->fedora = $fedora; |
|
$this->mimeTypeGuesser = $mime_type_guesser; |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function has($path) { |
|
$response = $this->fedora->getResourceHeaders($path); |
|
return $response->getStatusCode() == 200; |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function read($path) { |
|
$meta = $this->readStream($path); |
|
|
|
if (!$meta) { |
|
return FALSE; |
|
} |
|
|
|
if (isset($meta['stream'])) { |
|
$meta['contents'] = stream_get_contents($meta['stream']); |
|
fclose($meta['stream']); |
|
unset($meta['stream']); |
|
} |
|
|
|
return $meta; |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function readStream($path) { |
|
$response = $this->fedora->getResource($path); |
|
|
|
if ($response->getStatusCode() != 200) { |
|
return FALSE; |
|
} |
|
|
|
$meta = $this->getMetadataFromHeaders($response); |
|
$meta['path'] = $path; |
|
if ($meta['type'] == 'file') { |
|
$meta['stream'] = StreamWrapper::getResource($response->getBody()); |
|
} |
|
|
|
return $meta; |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function getMetadata($path) { |
|
$response = $this->fedora->getResourceHeaders($path); |
|
|
|
if ($response->getStatusCode() != 200) { |
|
return FALSE; |
|
} |
|
|
|
$meta = $this->getMetadataFromHeaders($response); |
|
$meta['path'] = $path; |
|
return $meta; |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function getSize($path) { |
|
return $this->getMetadata($path); |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function getMimetype($path) { |
|
return $this->getMetadata($path); |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function getTimestamp($path) { |
|
return $this->getMetadata($path); |
|
} |
|
|
|
/** |
|
* Gets metadata from response headers. |
|
* |
|
* @param \GuzzleHttp\Psr7\Response $response |
|
* Response. |
|
*/ |
|
protected function getMetadataFromHeaders(Response $response) { |
|
$last_modified = \DateTime::createFromFormat( |
|
\DateTime::RFC1123, |
|
$response->getHeader('Last-Modified')[0] |
|
); |
|
|
|
// NonRDFSource's are considered files. Everything else is a |
|
// directory. |
|
$type = 'dir'; |
|
$links = Psr7\parse_header($response->getHeader('Link')); |
|
foreach ($links as $link) { |
|
if ($link['rel'] == 'type' && $link[0] == '<http://www.w3.org/ns/ldp#NonRDFSource>') { |
|
$type = 'file'; |
|
break; |
|
} |
|
} |
|
|
|
$meta = [ |
|
'type' => $type, |
|
'timestamp' => $last_modified->getTimestamp(), |
|
]; |
|
|
|
if ($type == 'file') { |
|
$meta['size'] = $response->getHeader('Content-Length')[0]; |
|
$meta['mimetype'] = $response->getHeader('Content-Type')[0]; |
|
} |
|
|
|
return $meta; |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function listContents($directory = '', $recursive = FALSE) { |
|
// Strip leading and trailing whitespace and /'s. |
|
$normalized = trim($directory, ' \t\n\r\0\x0B/'); |
|
|
|
// Exit early if it's a file. |
|
$meta = $this->getMetadata($normalized); |
|
if ($meta['type'] == 'file') { |
|
return []; |
|
} |
|
// Get the resource from Fedora. |
|
$response = $this->fedora->getResource($normalized, ['Accept' => 'application/ld+json']); |
|
$jsonld = (string) $response->getBody(); |
|
$graph = json_decode($jsonld, TRUE); |
|
|
|
$uri = $this->fedora->getBaseUri() . $normalized; |
|
|
|
// Hack it out of the graph. |
|
// There may be more than one resource returned. |
|
$resource = []; |
|
foreach ($graph as $elem) { |
|
if (isset($elem['@id']) && $elem['@id'] == $uri) { |
|
$resource = $elem; |
|
break; |
|
} |
|
} |
|
|
|
// Exit early if resource doesn't contain other resources. |
|
if (!isset($resource['http://www.w3.org/ns/ldp#contains'])) { |
|
return []; |
|
} |
|
|
|
// Collapse uris to a single array. |
|
$contained = array_map( |
|
function ($elem) { |
|
return $elem['@id']; |
|
}, |
|
$resource['http://www.w3.org/ns/ldp#contains'] |
|
); |
|
|
|
// Exit early if not recursive. |
|
if (!$recursive) { |
|
// Transform results to their flysystem metadata. |
|
return array_map( |
|
[$this, 'transformToMetadata'], |
|
$contained |
|
); |
|
} |
|
|
|
// Recursively get containment for ancestors. |
|
$ancestors = []; |
|
|
|
foreach ($contained as $child_uri) { |
|
$child_directory = explode($this->fedora->getBaseUri(), $child_uri)[1]; |
|
$ancestors = array_merge($this->listContents($child_directory, $recursive), $ancestors); |
|
} |
|
|
|
// // Transform results to their flysystem metadata. |
|
return array_map( |
|
[$this, 'transformToMetadata'], |
|
array_merge($ancestors, $contained) |
|
); |
|
} |
|
|
|
/** |
|
* Normalizes data for listContents(). |
|
* |
|
* @param string $uri |
|
* Uri. |
|
*/ |
|
protected function transformToMetadata($uri) { |
|
if (is_array($uri)) { |
|
return $uri; |
|
} |
|
$exploded = explode($this->fedora->getBaseUri(), $uri); |
|
return $this->getMetadata($exploded[1]); |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function write($path, $contents, Config $config) { |
|
$headers = [ |
|
'Content-Type' => $this->mimeTypeGuesser->guess($path), |
|
]; |
|
if ($this->has($path)) { |
|
$fedora_url = $path; |
|
$date = new \DateTime(); |
|
$timestamp = $date->format("D, d M Y H:i:s O"); |
|
// Create version in Fedora. |
|
try { |
|
$response = $this->fedora->createVersion( |
|
$fedora_url, |
|
$timestamp, |
|
NULL, |
|
$headers |
|
); |
|
if (isset($response) && $response->getStatusCode() == 201) { |
|
\Drupal::logger('fedora_flysystem')->info('Created a version in Fedora for ' . $fedora_url); |
|
} |
|
else { |
|
\Drupal::logger('fedora_flysystem')->error( |
|
"Client error: `Failed to create a Fedora version of $fedora_url`. Response is " . print_r($response, TRUE) |
|
); |
|
|
|
} |
|
} |
|
catch (\Exception $e) { |
|
\Drupal::logger('fedora_flysystem')->error('Caught exception when creating version: ' . $e->getMessage() . "\n"); |
|
} |
|
} |
|
|
|
$response = $this->fedora->saveResource( |
|
$path, |
|
$contents, |
|
$headers |
|
); |
|
|
|
$code = $response->getStatusCode(); |
|
if (!in_array($code, [201, 204])) { |
|
return FALSE; |
|
} |
|
|
|
return $this->getMetadata($path); |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function writeStream($path, $contents, Config $config) { |
|
return $this->write($path, $contents, $config); |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function update($path, $contents, Config $config) { |
|
return $this->write($path, $contents, $config); |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function updateStream($path, $contents, Config $config) { |
|
return $this->write($path, $contents, $config); |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function delete($path) { |
|
$response = $this->fedora->deleteResource($path); |
|
$code = $response->getStatusCode(); |
|
if ($code == 204) { |
|
// Deleted so check for a tombstone as well. |
|
$tomb_code = $this->deleteTombstone($path); |
|
if (!is_null($tomb_code)) { |
|
return $tomb_code; |
|
} |
|
} |
|
return in_array($code, [204, 404]); |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function deleteDir($dirname) { |
|
return $this->delete($dirname); |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function rename($path, $newpath) { |
|
if ($this->copy($path, $newpath)) { |
|
return $this->delete($path); |
|
} |
|
return FALSE; |
|
} |
|
|
|
/** |
|
* {@inheritdoc} |
|
*/ |
|
public function createDir($dirname, Config $config) { |
|
$response = $this->fedora->saveResource( |
|
$dirname |
|
); |
|
|
|
$code = $response->getStatusCode(); |
|
if (!in_array($code, [201, 204])) { |
|
return FALSE; |
|
} |
|
|
|
return $this->getMetadata($dirname); |
|
} |
|
|
|
/** |
|
* Delete a tombstone for a path if it exists. |
|
* |
|
* @param string $path |
|
* The original deleted resource path. |
|
* |
|
* @return bool|null |
|
* NULL if no tombstone, TRUE if tombstone deleted, FALSE otherwise. |
|
*/ |
|
private function deleteTombstone($path) { |
|
$response = $this->fedora->getResourceHeaders($path); |
|
$return = NULL; |
|
if ($response->getStatusCode() == 410) { |
|
$return = FALSE; |
|
$link_headers = Psr7\parse_header($response->getHeader('Link')); |
|
if ($link_headers) { |
|
$tombstones = array_filter($link_headers, function ($o) { |
|
return (isset($o['rel']) && $o['rel'] == 'hasTombstone'); |
|
}); |
|
foreach ($tombstones as $tombstone) { |
|
// Trim <> from URL. |
|
$url = rtrim(ltrim($tombstone[0], '<'), '>'); |
|
$response = $this->fedora->deleteResource($url); |
|
if ($response->getStatusCode() == 204) { |
|
$return = TRUE; |
|
} |
|
} |
|
} |
|
} |
|
return $return; |
|
} |
|
|
|
}
|
|
|