Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 90 additions & 2 deletions src/installation/fetchToolboxAsset.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
% them, so callers supply the release tag and asset name rather than
% this function hardcoding either.
%
% A download is checked against the SHA-256 checksum that the
% toolbox-assets manifest (release manifest-v3) lists for it. A file whose
% checksum differs is deleted and an error is raised. An asset that the
% manifest does not list, or a manifest that cannot be read, gives a
% warning and the file is kept unchecked.
%
% Parameters
% ----------
% destDir : char
Expand All @@ -30,15 +36,97 @@
if isfile(localPath)
return;
end
url=['https://github.com/CompMetEng/toolbox-assets/releases/download/' releaseTag '/' assetName];
partPath=[localPath '.part'];
fprintf(['Downloading ' assetName '... ']);
try
websave(localPath,['https://github.com/CompMetEng/toolbox-assets/releases/download/' releaseTag '/' assetName]);
websave(partPath,url);
catch ME
if isfile(partPath)
delete(partPath);
end
if strcmp(ME.identifier,'MATLAB:webservices:HTTP404StatusCodeError')
error('Failed to download %s, the server returned a 404 error, try again later. If the problem persists please report it on the RAVEN GitHub Issues page: https://github.com/SysBioChalmers/RAVEN/issues',assetName)
error('Failed to download %s, the server returned a 404 error, try again later. If the problem persists please report it on the RAVEN GitHub Issues page: https://github.com/CompMetEng/RAVEN/issues',assetName)
else
rethrow(ME);
end
end
expected=manifestChecksum(url);
if isempty(expected)
warning('RAVEN:fetchToolboxAsset:unverified',['The toolbox-assets manifest gives no ' ...
'checksum for %s; the download is not verified.'],assetName);
else
actual=sha256File(partPath);
if ~strcmpi(actual,expected)
delete(partPath);
error('RAVEN:fetchToolboxAsset:checksum',['The download of %s is not the published ' ...
'file (SHA-256 %s, expected %s) and was deleted. Try again; if it persists, ' ...
'report it on the RAVEN GitHub Issues page: https://github.com/CompMetEng/RAVEN/issues'], ...
assetName,actual,expected)
end
end
movefile(partPath,localPath);
fprintf('COMPLETE\n');
end

function sha=manifestChecksum(url)
% The sha256 the toolbox-assets manifest lists for an asset URL, or '' when
% it lists none or cannot be read. The manifest is read once per session.
persistent manifest
sha='';
if isempty(manifest)
try
manifest=webread('https://github.com/CompMetEng/toolbox-assets/releases/download/manifest-v3/manifest.json', ...
weboptions('ContentType','json','Timeout',30));
catch ME
warning('RAVEN:fetchToolboxAsset:manifest','Cannot read the toolbox-assets manifest: %s',ME.message);
return
end
end
sha=findUrl(manifest,url);
end

function sha=findUrl(node,url)
% Depth-first search for a struct with this url and a sha256 field. The
% manifest's file names are not valid MATLAB field names, so the search
% goes by value.
sha='';
if isstruct(node)
for k=1:numel(node)
if isfield(node(k),'url') && isfield(node(k),'sha256') && strcmp(node(k).url,url)
sha=node(k).sha256;
return
end
f=fieldnames(node(k));
for i=1:numel(f)
sha=findUrl(node(k).(f{i}),url);
if ~isempty(sha)
return
end
end
end
elseif iscell(node)
for i=1:numel(node)
sha=findUrl(node{i},url);
if ~isempty(sha)
return
end
end
end
end

function sha=sha256File(path)
% SHA-256 of a file as lower-case hex, read in chunks.
md=java.security.MessageDigest.getInstance('SHA-256');
fid=fopen(path,'r');
cleanup=onCleanup(@() fclose(fid));
while true
chunk=fread(fid,2^24,'*uint8');
if isempty(chunk)
break
end
md.update(typecast(chunk,'int8'));
end
digest=typecast(md.digest(),'uint8');
sha=lower(reshape(dec2hex(digest,2)',1,[]));
end
14 changes: 14 additions & 0 deletions tests/function_tests/tInstallation.m
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@ function checkFunctionUniquenessRuns(testCase)
testCase.verifyTrue(islogical(status) || isnumeric(status));
end

function fetchToolboxAssetChecksDownloads(testCase)
% A small published asset is downloaded and matches the
% checksum of the toolbox-assets manifest; a second call reuses
% the file.
d = tempname;
cleanup = onCleanup(@() rmdir(d, 's'));
p = fetchToolboxAsset(d, 'transporters-2026-07-01', 'tcdb_substrates.tsv');
testCase.verifyTrue(isfile(p));
testCase.verifyFalse(isfile([p '.part']));
info = dir(p);
testCase.verifyEqual(info.bytes, 219635);
testCase.verifyEqual(fetchToolboxAsset(d, 'transporters-2026-07-01', 'tcdb_substrates.tsv'), p);
end

function addRavenToUserPathExists(testCase)
testCase.verifyEqual(exist('addRavenToUserPath','file'), 2);
end
Expand Down
Loading