Skip to content

Commit

Permalink
[imaging browser] Fix download of JSON, Bval and Bvec files when they…
Browse files Browse the repository at this point in the history
… are on s3 (aces#8354)

This fixes the error 404 users had when they tried to download a JSON, Bval or Bvec file hosted on S3 from the imaging browser.

Fixing this bug also resolved aces/HBCD#227 as new API endpoints needed to be created for those files.
  • Loading branch information
cmadjar authored and zaliqarosli committed Mar 6, 2023
1 parent 595ccfc commit 056b314
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ class Format extends Endpoint
$handler = new Format\Brainbrowser($this->_image);
break;
case 'thumbnail':
$handler = new Format\Thumbnail($this->_image);
case 'nifti':
case 'bval':
case 'bvec':
case 'bidsjson':
$handler = new Format\DownloadFile($this->_image, $format);
break;
default:
return new \LORIS\Http\Response\JSON\UnsupportedMediaType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,50 @@ use \LORIS\api\Endpoint;
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
* @link https://github.com/aces/Loris
*/
class Thumbnail extends Endpoint implements \LORIS\Middleware\ETagCalculator
class DownloadFile extends Endpoint implements \LORIS\Middleware\ETagCalculator
{

/**
* The requested Image
*
* @var \LORIS\Image
*/
private $_image;
private $_format;
private $_info;
private $_file_type;
private $_content_type;

/**
* Contructor
*
* @param \LORIS\Image $image The requested image
* @param \LORIS\Image $image The requested image
* @param string $format The format user wishes to download
*/
public function __construct(\LORIS\Image $image)
public function __construct(\LORIS\Image $image, string $format)
{
$this->_image = $image;
$this->_format = $format;

switch ($this->_format) {
case 'thumbnail':
$this->_info = $image->getThumbnailFileInfo();
$this->_file_type = "Thumbnail";
$this->_content_type = 'image/jpeg';
break;
case 'nifti':
$this->_info = $image->getNiiFileInfo();
$this->_file_type = "NIfTI";
$this->_content_type = 'application/octet-stream';
break;
case 'bval':
$this->_info = $image->getBvalFileInfo();
$this->_file_type = "NIfTI BVAL";
$this->_content_type = 'application/text';
break;
case 'bvec':
$this->_info = $image->getBvecFileInfo();
$this->_file_type = "NIfTI BVEC";
$this->_content_type = 'application/text';
break;
case 'bidsjson':
$this->_info = $image->getBidsJsonFileInfo();
$this->_file_type = "BIDS JSON";
$this->_content_type = 'application/json';
break;
}
}

/**
Expand Down Expand Up @@ -100,23 +126,22 @@ class Thumbnail extends Endpoint implements \LORIS\Middleware\ETagCalculator
*/
private function _handleGET(): ResponseInterface
{
$info = $this->_image->getThumbnailFileInfo();

if (!$info->isFile()) {
error_log('Thumbnail not found');
if (!$this->_info->isFile()) {
$this->logger->error("$this->_file_type file not found");
return new \LORIS\Http\Response\JSON\NotFound();
}

if (!$info->isReadable()) {
error_log('Thumbnail exists but is not readable by webserver');
if (!$this->_info->isReadable()) {
$this->logger->error(
"$this->_file_type file exists but is not readable by webserver"
);
return new \LORIS\Http\Response\JSON\NotFound();
}

$filename = $info->getFilename();

$realpath = $info->getRealPath();
$filename = $this->_info->getFilename();
$realpath = $this->_info->getRealPath();
if ($realpath === false) {
$realpath = $info->getPath() . "/" . $info->getFilename();
$realpath = $this->_info->getPath() . "/" . $this->_info->getFilename();
}

$body = new \LORIS\Http\FileStream($realpath, 'r');
Expand All @@ -125,7 +150,7 @@ class Thumbnail extends Endpoint implements \LORIS\Middleware\ETagCalculator
$body,
200,
[
'Content-Type' => 'image/jpeg',
'Content-Type' => $this->_content_type,
'Content-Disposition' => 'attachment; filename=' . $filename,
]
);
Expand All @@ -140,16 +165,15 @@ class Thumbnail extends Endpoint implements \LORIS\Middleware\ETagCalculator
*/
public function ETag(ServerRequestInterface $request) : string
{
$info = $this->_image->getThumbnailFileInfo();

if (!$info->isFile() || !$info->isReadable()) {
if (!$this->_info->isFile() || !$this->_info->isReadable()) {
return '';
}

$signature = [
'filename' => $info->getFilename(),
'size' => $info->getSize(),
'mtime' => $info->getMTime(),
'filename' => $this->_info->getFilename(),
'size' => $this->_info->getSize(),
'mtime' => $this->_info->getMTime(),
];

return md5(json_encode($signature));
Expand Down
41 changes: 25 additions & 16 deletions modules/imaging_browser/jsx/ImagePanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,7 @@ DownloadButton.propTypes = {
FileName: PropTypes.string,
BaseURL: PropTypes.string,
Label: PropTypes.string,
URL: PropTypes.string,
};


Expand Down Expand Up @@ -828,22 +829,30 @@ class ImageDownloadButtons extends Component {
BaseURL={this.props.BaseURL}
Label="Download NRRD"
/>
<DownloadButton FileName={this.props.NiiFile}
BaseURL={this.props.BaseURL}
Label="Download NIfTI"
/>
<DownloadButton FileName={this.props.BvalFile}
BaseURL={this.props.BaseURL}
Label="Download BVAL"
/>
<DownloadButton FileName={this.props.BvecFile}
BaseURL={this.props.BaseURL}
Label="Download BVEC"
/>
<DownloadButton FileName={this.props.JsonFile}
BaseURL={this.props.BaseURL}
Label="Download BIDS JSON"
/>
{ this.props.NiiFile ?
<DownloadButton URL={this.props.APIFile + '/format/nifti'}
Label="Download NIfTI"
/> :
null
}
{this.props.BvalFile ?
<DownloadButton URL={this.props.APIFile + '/format/bval'}
Label="Download BVAL"
/> :
null
}
{this.props.BvecFile ?
<DownloadButton URL={this.props.APIFile + '/format/bvec'}
Label="Download BVEC"
/> :
null
}
{this.props.JsonFile ?
<DownloadButton URL={this.props.APIFile + '/format/bidsjson'}
Label="Download BIDS JSON"
/> :
null
}
<LongitudinalViewButton FileID={this.props.FileID}
BaseURL={this.props.BaseURL}
OtherTimepoints={this.props.OtherTimepoints}
Expand Down
1 change: 1 addition & 0 deletions modules/imaging_browser/php/viewsession.class.inc
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ class ViewSession extends \NDB_Form
'OtherTimepoints' => $OtherTimepoints,
'CaveatViolationsResolvedID' => $caveatViolationsResolvedID,
];

$this->tpl_data['files'][] = $file;
}
}
Expand Down
48 changes: 44 additions & 4 deletions php/libraries/Image.class.inc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class Image
private \CenterID $_centerid;

private $_entitytype;

/**
* Constructor
*
Expand All @@ -58,7 +59,7 @@ class Image
s.CenterID as centerid,
c.Entity_type as entitytype
FROM
files f
files f
LEFT JOIN session s
ON (f.SessionID = s.ID)
LEFT JOIN candidate c
Expand All @@ -80,7 +81,6 @@ class Image
$this->_filetype = $dbrow['filetype'];
$this->_centerid = new \CenterID($dbrow['centerid']);
$this->_entitytype = $dbrow['entitytype'];

}
}

Expand All @@ -100,7 +100,7 @@ class Image
SELECT
Value
FROM
parameter_file pf
parameter_file pf
JOIN parameter_type pt
USING (ParameterTypeID)
JOIN files f
Expand Down Expand Up @@ -132,7 +132,7 @@ class Image
pt.Name as name,
pf.Value as value
FROM
parameter_file pf
parameter_file pf
JOIN parameter_type pt
USING (ParameterTypeID)
JOIN files f
Expand Down Expand Up @@ -253,6 +253,46 @@ class Image
return $this->_getFullPath($this->_filelocation);
}

/**
* Return a SPLFileInfo object based on this images's properties.
*
* @return \SplFileInfo
*/
public function getNiiFileInfo(): \SplFileInfo
{
return $this->_getFullPath($this->getHeader('check_nii_filename'));
}

/**
* Return a SPLFileInfo object based on this images's properties.
*
* @return \SplFileInfo
*/
public function getBvalFileInfo(): \SplFileInfo
{
return $this->_getFullPath($this->getHeader('check_bval_filename'));
}

/**
* Return a SPLFileInfo object based on this images's properties.
*
* @return \SplFileInfo
*/
public function getBvecFileInfo(): \SplFileInfo
{
return $this->_getFullPath($this->getHeader('check_bvec_filename'));
}

/**
* Return a SPLFileInfo object based on this images's properties.
*
* @return \SplFileInfo
*/
public function getBidsJsonFileInfo(): \SplFileInfo
{
return $this->_getFullPath($this->getHeader('bids_json_file'));
}

/**
* Return a SPLFileInfo object based on this images's thumbnail properties.
*
Expand Down
9 changes: 9 additions & 0 deletions src/Http/Endpoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
*/
abstract class Endpoint implements RequestHandlerInterface
{
use \PSR\Log\LoggerAwareTrait;

/**
* An Endpoint overrides the default LORIS middleware to remove the
* PageDecorationMiddleware.
Expand All @@ -41,6 +43,13 @@ public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$loris = $request->getAttribute('loris');
$loglevel = $loris->getConfiguration()
->getLogSettings()
->getRequestLogLevel();

$this->logger = new \LORIS\Log\ErrorLogLogger($loglevel);

$interfaces = class_implements($handler);
if (in_array('LORIS\Middleware\ETagCalculator', $interfaces)) {
return (new \LORIS\Middleware\ETag())->process($request, $handler);
Expand Down

0 comments on commit 056b314

Please sign in to comment.