function cmdout = tbxmanager(command, varargin)
% Toolbox manager
%
% Supported commands:
% tbxmanager install package1 package2 ...
% tbxmanager show enabled
% tbxmanager show installed
% tbxmanager show available
% tbxmanager show sources
% tbxmanager update
% tbxmanager update package1 package2 ...
% tbxmanager restorepath
% tbxmanager generatepath
% tbxmanager enable package1 package2 ...
% tbxmanager disable package1 package2 ...
% tbxmanager uninstall package1 package2 ...
% tbxmanager source add URL
% tbxmanager source remove URL
% tbxmanager selfupdate
% tbxmanager require package1 package2 ...
%
% For help, contact michal.kvasnica@stuba.sk
% Copyright is with the following author(s):
%
% (c) 2012-2014 Michal Kvasnica, Slovak University of Technology in Bratislava
% michal.kvasnica@stuba.sk
% ------------------------------------------------------------------------
% Legal note:
% This program is free software; you can redistribute it and/or
% modify it under the terms of the GNU General Public
% License as published by the Free Software Foundation; either
% version 2.1 of the License, or (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
% General Public License for more details.
%
% You should have received a copy of the GNU General Public
% License along with this library; if not, write to the
% Free Software Foundation, Inc.,
% 59 Temple Place, Suite 330,
% Boston, MA 02111-1307 USA
% ------------------------------------------------------------------------
cmdout = [];
%% add self to path
ourPath = fileparts(which(mfilename));
if isempty(strfind(path, ourPath))
addpath(ourPath);
end
% prepare the setup
tbx_setup(ourPath);
if nargin==0
help(mfilename);
if nargout==0
clear cmdout
end
return
end
%% validate input arguments
if ~ischar(command)
error('TBXMANAGER:BADCOMMAND', 'The command must be a string.');
end
for i = 1:length(varargin)
if ~ischar(varargin{i})
error('TBXMANAGER:BADCOMMAND', 'All arguments must be strings.');
end
end
%% expand commands
supported_commands = {'install', ...
'update', ...
'restorepath', ...
'generatepath', ...
'enable', ...
'disable', ...
'uninstall', ...
'show', ...
'source', ...
'require', ...
'selfupdate'};
% expand command name, e.g. "sh" -> "show"
command = tbx_expandChoice(lower(command), supported_commands);
%% dispatch commands
args = varargin;
switch lower(command)
case 'install',
requires_java;
cmd = @main_install;
case 'update',
requires_java;
if isempty(args)
cmd = @main_updateall;
else
cmd = @main_update;
end
case 'restorepath',
cmd = @tbx_restorePath;
case 'generatepath',
cmd = @main_generatePath;
case 'enable',
cmd = @main_addpath;
case 'disable',
cmd = @main_rmpath;
case 'uninstall',
cmd = @main_uninstall;
case 'show',
cmd = @main_show;
case 'source'
cmd = @main_source;
case 'require'
cmd = @main_require;
case 'selfupdate'
requires_java;
cmd = @main_selfupdate;
otherwise
help(mfilename);
error('TBXMANAGER:BADCOMMAND', 'Unrecognized command "%s".', command);
end
% safely execute the command
try
if nargout==0
feval(cmd, args);
else
cmdout = feval(cmd, args);
end
catch
err = lasterror;
if ~isempty(err.identifier) && isempty(strfind(err.identifier, 'TBXMANAGER'))
% unexpected error
fprintf('\n----------------------------------------------------\n');
fprintf('Oooops, an error has occurred.\n');
fprintf('\n');
fprintf('Run "tbxmanager selfupdate" and repeat your action.\n');
fprintf('\n');
fprintf('If problems remain, contact michal.kvasnica@stuba.sk\n');
fprintf('----------------------------------------------------\n\n');
rethrow(lasterror);
else
% expected error
fprintf('\n%s\n\n', err.message);
error('TBXMANAGER:ERROR', 'Cannot continue, see message above.');
end
end
if nargout==0
clear cmdout
end
end
%%
function setup = tbx_setup(maindir)
% Sets up parameters of TBXMANAGER
persistent cached_setup
if nargin==0
setup = cached_setup;
return
end
setup = [];
% where toolboxes are stored
setup.tbxdir = [maindir filesep 'toolboxes'];
if ~exist(setup.tbxdir, 'dir')
mkdir(setup.tbxdir);
end
% where the main tbxmanager directory is
setup.maindir = maindir;
% file where available sources are stored
setup.sourcesfile = [maindir filesep 'tbxsources.txt'];
% file where list of enabled toolboxes is stored
setup.enabledfile = [maindir filesep 'tbxenabled.txt'];
% where on the web is tbxmanager?
setup.selfurl = 'http://www.tbxmanager.com/tbxmanager.m';
% version of XML supported by this version of tbxmanager
setup.max_xml_version = 1.2;
% URL of pingback
setup.server_url = 'http://www.tbxmanager.com/package/log';
% default package sources
setup.defaultsources = { 'http://www.tbxmanager.com/package/index.xml' };
cached_setup = setup;
end
%%
function C = tbx_crc32(data)
% returns CRC32 checksum of given data
crc_gen = java.util.zip.CRC32;
crc_gen.update(double(data));
C = crc_gen.getValue();
end
%%
function main_selfupdate(args)
% updates this file
Setup = tbx_setup;
% get a simple CRC of the current version
this_file = [Setup.maindir filesep mfilename '.m'];
this_content = fileread(this_file);
other_content = sub_urlread(Setup.selfurl);
other_crc = sum(other_content);
if tbx_crc32(this_content) == tbx_crc32(other_content)
fprintf('You already have the newest version of tbxmanager.\n');
else
% make a copy of the current version just to be sure
if ~copyfile(this_file, [this_file '.old']);
error('TBXMANAGER:FILEERROR', 'Couldn''t back up %s to %s.', this_file, ...
[this_file '.old']);
end
sub_urlwrite(Setup.selfurl, this_file);
rehash
fprintf('tbxmanager updated to latest version.\n');
end
end
%%
function main_require(packages)
% Throws an error if any of the listed packages is not enabled/installed
%
% tbxmanager require package1 package2 ...
validate_notempty(packages);
enabled = tbx_loadEnabled();
installed = [];
for i = 1:length(packages)
pkg = packages{i};
% convert string name to structure
if ~tbx_isOnList(enabled, pkg)
if isempty(installed)
installed = tbx_listInstalled();
end
if tbx_isOnList(installed, pkg)
% the package is installed but not enabled
fprintf('\nEnabled the required package by "tbxmanager enable %s"\n', pkg);
else
% the package is not even installed
fprintf('\nInstall the required package by "tbxmanager install %s"\n', pkg);
end
error('TBXMANAGER:MissingPackage', 'Required package "%s" not found.', packages{i});
end
end
end
%%
function main_source(args)
% manages sources
%
% source add URL
% source remove URL
if length(args)~=2
error('TBXMANAGER:BADCOMMAND', ...
'The "source" command requires two additional inputs.');
end
% expand the argument, e.g. "a" -> "add", "rem" -> "remove"
arg_choices = { 'add', 'remove' };
main_arg = tbx_expandChoice(lower(args{1}), arg_choices);
Setup = tbx_setup;
switch main_arg
case 'add',
if length(args)<2
error('TBXMANAGER:BADCOMMAND', '"source add" requires an URL.');
end
tbx_addSource(args{2});
case 'remove'
if length(args)<2
error('TBXMANAGER:BADCOMMAND', '"source remove" requires an URL.');
end
tbx_removeSource(args{2});
otherwise
error('TBXMANAGER:BADCOMMAND', ...
'Unrecognized option "%s". Allowed are "add" and "remove".', main_arg);
end
end
%%
function sources = tbx_getSources()
% returns call array of sources loaded from tbxmanager_sources.txt
setup = tbx_setup();
fname = setup.sourcesfile;
if exist(fname, 'file')
content = fileread(fname);
if isempty(content)
sources = {};
else
s = textscan(content, '%s');
sources = s{1};
end
else
sources = {};
end
if isempty(sources)
% default cell list of sources
sources = setup.defaultsources;
tbx_writeSources(sources);
end
end
%%
function tbx_writeSources(sources)
% writes list of soruces to tbxmanager_sources.txt
setup = tbx_setup();
fname = setup.sourcesfile;
fid = fopen(fname, 'w');
if fid < 0
error('TBXMANAGER:FILEERROR', 'Couldn''t open %s for writing.', fname);
end
for i = 1:length(sources)
fprintf(fid, '%s\n', sources{i});
end
fclose(fid);
end
%%
function tbx_addSource(source)
% adds the source to tbxmanager_sources.txt
% is the source valid?
try
sub_urlread(source);
catch
error('TBXMANAGER:URLERROR', 'Unable to connect to %s', source);
end
% load the sources
sources = tbx_getSources();
% is the source there?
if isempty(setdiff({source}, sources))
fprintf('Source "%s" is already on the list.\n', source);
else
% add it
sources{end+1} = source;
% and write back
tbx_writeSources(sources);
end
end
%%
function tbx_removeSource(source)
% removes the source from tbxmanager_sources.txt
% load the sources
sources = tbx_getSources();
nbefore = length(sources);
% remove the source
sources = setdiff(sources, source);
if length(sources)==nbefore
% no source was removed
fprintf('Source "%s" is not on the list.\n', source);
else
% and write back
tbx_writeSources(sources);
end
end
%%
function main_show(args)
% shows available/installed/enabled packages or sources
if isempty(args)
main_show({'available'});
fprintf('\n');
main_show({'installed'});
return
end
% expand the argument, e.g. "a" -> "available", "inst" -> "installed"
arg_choices = { 'available', 'installed', 'enabled', 'sources' };
main_arg = tbx_expandChoice(lower(args{1}), arg_choices);
switch lower(main_arg)
case 'sources'
fprintf('Active sources:\n\n');
sources = tbx_getSources();
for i = 1:length(sources)
fprintf('%s\n', sources{i});
end
return
case 'installed',
L = tbx_listInstalled();
fprintf('Locally installed packages:\n\n');
case 'available',
L = tbx_listAvailable();
fprintf('Packages available for download:\n\n');
case 'enabled',
L = tbx_loadEnabled();
fprintf('List of enabled packages:\n\n');
names = unique(arrayfun(@(x) x.name, L, 'UniformOutput', false));
maxname = max(cellfun('length', names));
for i = 1:length(L)
fprintf('%s %s Version %s\n', L(i).name, ...
repmat(' ', 1, max(1, 1+maxname-length(L(i).name))), ...
L(i).version);
end
return
otherwise,
error('TBXMANAGER:BADCOMMAND', ...
'Unknown mode ''%s''. Allowed are ''installed'', ''available'', ''enabled'', ''sources''.', main_arg);
end
% get just names of toolboxes
names = unique(arrayfun(@(x) x.name, L, 'UniformOutput', false));
maxname = max(cellfun('length', names));
for i = 1:length(names)
Latest = tbx_getLatestVersion(L, names{i});
if isempty(Latest), continue, end
fprintf('%s %s Version %s', Latest.name, ...
repmat(' ', 1, max(1, 1+maxname-length(Latest.name))), ...
Latest.version);
if isfield(Latest, 'date')
fprintf('%s(%s)', repmat(' ', 1, max(1, 10-length(Latest.version))), ...
datestr(Latest.datenum, 1));
end
fprintf('\n');
end
end
%%
function main_install(names)
% installs multiple toolboxes
validate_notempty(names);
validate_available(names);
Available = tbx_listAvailable();
for i = 1:length(names)
Latest = tbx_getLatestVersion(Available, names{i});
if isempty(Latest)
error('TBXMANAGER:BADCOMMAND', ...
'Toolbox "%s" is not available for your platform.', ...
names{i});
end
if tbx_isInstalled(Latest)
fprintf('Latest version of "%s" is already installed.\n', names{i});
else
fprintf('\n');
fprintf('Installing version "%s" of "%s"...\n', Latest.version, ...
Latest.name);
tbx_install(Latest);
tbx_addPath(Latest);
end
end
end
%%
function main_addpath(names)
% adds selected toolboxes to the Matlab path
validate_notempty(names);
validate_installed(names);
Installed = tbx_listInstalled();
for i = 1:length(names)
[dummy, w] = tbx_isOnList(Installed, names{i});
if length(w)>1
% more than one version installed, add the latest
Latest = tbx_getLatestVersion(Installed, names{i});
else
Latest = Installed(w);
end
tbx_addPath(Latest);
end
end
%%
function main_rmpath(names)
% removes selected toolboxes to the Matlab path
validate_notempty(names);
validate_installed(names);
Installed = tbx_listInstalled();
for i = 1:length(names)
[dummy, w] = tbx_isOnList(Installed, names{i});
if length(w)>1
% more than one version installed, add the latest
Latest = tbx_getLatestVersion(Installed, names{i});
else
Latest = Installed(w);
end
tbx_rmPath(Latest);
end
end
%%
function main_updateall(args)
% updates all locally installed toolboxes
Installed = tbx_listInstalled();
% get just names of the toolboxes
names = unique(arrayfun(@(x) x.name, Installed, 'UniformOutput', false));
main_update(names);
end
%%
function main_update(names)
% updates selected locally installed toolboxes
validate_notempty(names);
validate_installed(names);
Available = tbx_listAvailable();
for i = 1:length(names)
Latest = tbx_getLatestVersion(Available, names{i});
if isempty(Latest), continue, end
if tbx_isInstalled(Latest)
fprintf('No new version for toolbox "%s".\n', names{i});
else
% install newer version
fprintf('Toolbox "%s" has new version "%s", installing...\n', ...
Latest.name, Latest.version);
update = true;
tbx_install(Latest, update);
tbx_addPath(Latest);
end
end
end
%%
function main_uninstall(names)
% installs multiple toolboxes
validate_notempty(names);
validate_installed(names);
Installed = tbx_listInstalled();
for i = 1:length(names)
% get all installed versions
[dummy, w] = tbx_isOnList(Installed, names{i});
for j = 1:length(w)
fprintf('\n');
Toolbox = Installed(w(j));
tbx_rmPath(Toolbox);
tbx_uninstall(Toolbox);
end
end
end
%%
function validate_notempty(names)
if isempty(names)
error('TBXMANAGER:BADCOMMAND', 'Name of a toolbox must be provided.');
end
end
%%
function validate_available(names)
% shows an error if some toolbox is not available online
Available = tbx_listAvailable();
for i = 1:length(names)
if ~tbx_isOnList(Available, names{i})
error('TBXMANAGER:BADCOMMAND', 'Toolbox "%s" is not available.', names{i});
end
end
end
%%
function validate_installed(names)
% shows an error if some toolbox is not installed locally
Installed = tbx_listInstalled();
for i = 1:length(names)
if ~tbx_isOnList(Installed, names{i})
error('TBXMANAGER:BADCOMMAND', 'Toolbox "%s" is not installed.', names{i});
end
end
end
%%
function P = tbx_genPath(Toolbox)
% Returns path to all subdirectories of a given toolbox
%
% Specification of the input structure:
% name: short toolbox name
% version: version id
% arch: architecture
if ~tbx_isInstalled(Toolbox)
error('TBXMANAGER:BADCOMMAND', 'Toolbox "%s" is not installed.', tbx_s2n(Toolbox));
end
[archdir, dummy, basedir] = tbx_installationDir(Toolbox);
P = genpath([archdir filesep]);
end
%%
function tbx_addPath(Toolbox)
% Adds the given toolbox from MATLAB path
%
% Specification of the input structure:
% name: short toolbox name
% version: version id
% arch: architecture
if ~tbx_isInstalled(Toolbox)
error('TBXMANAGER:BADCOMMAND', 'Toolbox "%s" is not installed.', tbx_s2n(Toolbox));
end
% remove any previous instances of this toolbox from the path
[archdir, dummy, basedir] = tbx_installationDir(Toolbox);
w = warning; warning('off');
rmpath(genpath(basedir));
warning(w);
% set path to this particular toolbox
addpath(genpath([archdir filesep]));
rehash pathreset
fprintf('Toolbox "%s" added to the Matlab path.\n', tbx_s2n(Toolbox));
tbx_registerEnabled(Toolbox);
end
%%
function Latest = tbx_getLatestVersion(List, name)
% Returns specifications of a new version for a particular architecture
if ~tbx_isOnList(List, name)
error('TBXMANAGER:BADCOMMAND', 'Toolbox "%s" is not available.', name);
end
% get list of versions for this toolbox and architecture
candidates = false(1, length(List));
for i = 1:length(List)
if isequal(List(i).name, name) && ...
tbx_isArchCompatible(List(i).arch)
candidates(i) = true;
end
end
List = List(candidates);
if isempty(List)
Latest = List;
return
end
% get the latest version
dates = zeros(1, length(List));
for i = 1:length(List)
dates(i) = List(i).datenum;
end
[a, b] = sort(dates);
Latest = List(b(end)); % newest is the last in the list
end
%%
function tbx_install(Toolbox, updating)
% Downloads and installs single toolbox
%
% Specification of the input structure:
% name: short toolbox name
% version: version id
% arch: architecture
% url: download url
if nargin<2
updating = false;
end
if tbx_isInstalled(Toolbox)
error('TBXMANAGER:BADCOMMAND', 'Toolbox "%s" is already installed.', tbx_s2n(Toolbox));
end
% register start of the installation
if updating,
command_prefix = 'update';
else
command_prefix = 'install';
end
% ask the user to agree with package's license if necessary
if ~updating
% only prompt when installing for the first time
tbx_promptLicense(Toolbox);
end
% Extract name of the installation package from the URL
seps = find(Toolbox.url=='/');
if isempty(seps)
error('TBXMANAGER:BADCOMMAND', 'Malformed URL.');
end
install_file = Toolbox.url(seps(end)+1:end);
if isempty(install_file)
error('TBXMANAGER:URLERROR', 'No file found in the URL.');
end
% Detect extension
[p, n, extension] = fileparts(install_file);
if isempty(extension)
error('TBXMANAGER:URLERROR', 'No file found in the URL.');
end
switch lower(extension(2:end))
case 'zip',
isarchive = true;
unpacker = @unzip;
case {'tgz', 'tar'},
isarchive = true;
unpacker = @untar;
case 'm',
isarchive = false;
otherwise,
error('TBXMANAGER:URLERROR', 'Unsupported file extension "%s".', extension);
end
% Create installation directory
install_dir = tbx_installationDir(Toolbox);
if ~exist(install_dir, 'dir') && ~mkdir(install_dir)
error('TBXMANAGER:FILEERROR', 'Couldn''t create directory "%s".', install_dir);
end
% Download the package to install_dir
download_to = [install_dir filesep install_file];
fprintf('Downloading "%s"...\n', Toolbox.url);
try
sub_urlwrite(Toolbox.url, download_to);
catch
% remove the created directory
rmdir(install_dir, 's');
rethrow(lasterror);
end
if ~exist(download_to, 'file')
error('TBXMANAGER:URLERROR', 'Download failed.');
end
% Unpack
if isarchive
unpacker(download_to, install_dir);
end
fprintf('"%s" installed to %s\n', tbx_s2n(Toolbox), install_dir);
% Delete the downloaded package
if isarchive
delete(download_to);
end
tbx_notifyServer(command_prefix, Toolbox);
% TODO: Find and run installation scripts
end
%%
function tbx_promptLicense(Toolbox)
% Asks the user to agree with the package's license
% Ask the user to agree to the license
if ~isempty(Toolbox.license.text)
fprintf('You need to agree to the following license to install "%s":\n\n', Toolbox.name);
fprintf('%s\n', repmat('-', 1, 80));
fprintf('%s\n', Toolbox.license.text);
fprintf('%s\n', repmat('-', 1, 80));
fprintf('\n');
agreed = tbx_ask('Do you agree? [y/n]: ', mfilename, 'y', Toolbox);
if isempty(agreed) || lower(agreed(1)) == 'n'
error('TBXMANAGER:BADCOMMAND', ...
'Cannot install "%s" without agreeing to its license.', ...
Toolbox.name);
end
tbx_notifyServer('licensed', Toolbox);
end
% Ask the user to register
if Toolbox.license.require_email
fprintf('Installing "%s" requires you to register:\n', Toolbox.name);
name = tbx_ask('Your name and surname: ', mfilename, 'name', Toolbox);
email = tbx_ask('Your email: ', mfilename, 'email', Toolbox);
if isempty(name) || isempty(email)
error('TBXMANAGER:BADCOMMAND', ...
'Cannot install "%s" without providing a valid name or email.', ...
Toolbox.name);
end
% TODO: send the name/email to the server
tbx_notifyServer('register', Toolbox, 'name', name, 'email', email);
end
end
%%
function answer = tbx_ask(prompt, func, type, Toolbox)
% Internal helper for handling inputs from the command window
answer = input(prompt, 's');
% TODO: auto-complete the inputs when in the test mode
end
%%
function [archdir, versiondir, basedir] = tbx_installationDir(Toolbox)
% Returns directory which contains installation of a given toolbox
%
% Specification of the input structure:
% name: short toolbox name
% version: version id
% arch: architecture
Setup = tbx_setup;
basedir = [Setup.tbxdir filesep Toolbox.name];
versiondir = [basedir filesep Toolbox.version];
archdir = [versiondir filesep Toolbox.arch];
end
%%
function status = tbx_isArchCompatible(arch)
% Returns true if the architecture is compatible with the current system
status = isequal(lower(arch), 'all') || isequal(upper(arch), computer);
end
%%
function flag = tbx_isInstalled(Toolbox)
% Returns true if a given toolbox is installed
%
% Toolbox is described by a structure:
% name: short toolbox name
% version: version id
% arch: architecture
List = tbx_listInstalled;
for i = 1:length(List)
if isempty(Toolbox.version)
List(i).version = '';
end
if isempty(Toolbox.arch)
List(i).arch = '';
end
if isequal(List(i).name, Toolbox.name) && ...
isequal(List(i).version, Toolbox.version) && ...
isequal(List(i).arch, Toolbox.arch)
flag = true;
return
end
end
flag = false;
end
%%
function [status, where] = tbx_isOnList(List, Toolbox)
% Returns true if a particural toolbox is available online
Toolbox = tbx_n2s(Toolbox);
where = false(1, length(List));
for i = 1:length(List)
if isequal(Toolbox.name, List(i).name)
if isequal(Toolbox.version, List(i).version) && ...
isequal(Toolbox.arch, List(i).arch)
status = true;
where(i) = true;
elseif isempty(Toolbox.version) && isempty(Toolbox.arch)
status = true;
where(i) = true;
elseif isempty(Toolbox.version) && isequal(Toolbox.arch, List(i).arch)
status = true;
where(i) = true;
elseif isempty(Toolbox.arch) && isequal(Toolbox.version, List(i).version)
status = true;
where(i) = true;
end
end
end
status = any(where);
where = find(where);
end
%%
function L = tbx_listAvailable
% Loads list of toolboxes from a given source (URL or file)
% TODO: support multiple sources
sources = tbx_getSources();
L = tbx_loadSource(sources{1});
for i = 2:length(sources)
L = [L tbx_loadSource(sources{i})];
end
end
%%
function L = tbx_loadSource(source)
% Internal helper to load list of available toolboxes from a since source
if ~exist(source, 'file')
% make a local copy of the network file
xml = sub_urlread(source);
source = tempname;
fid = fopen(source, 'w');
fprintf(fid, '%s\n', xml);
fclose(fid);
X = xml2struct(source);
delete(source);
else
% read directly from local file
X = xml2struct(source);
end
% is the XML compatible?
Setup = tbx_setup();
if ~isfield(X, 'tbxmanager') || ...
~isfield(X.tbxmanager, 'Attributes') || ...
~isfield(X.tbxmanager.Attributes, 'version') || ...
isempty(str2num(X.tbxmanager.Attributes.version)) || ...
str2num(X.tbxmanager.Attributes.version) > Setup.max_xml_version
% version of the XML is newer than the one supported by this tbxmanager
fprintf('\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n');
fprintf('Run "tbxmanager selfupdate" first.\n');
fprintf('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n');
error('TBXMANAGER:XMLERROR', 'Cannot continue.');
end
L = [];
if ~iscell(X.tbxmanager.package)
X.tbxmanager.package = { X.tbxmanager.package };
end
for i = 1:length(X.tbxmanager.package)
name = lower(X.tbxmanager.package{i}.name.Text);
if isfield(X.tbxmanager.package{i}, 'license')
license = tbx_parseLicense(X.tbxmanager.package{i}.license);
else
license = tbx_parseLicense([]);
end
if ~isfield(X.tbxmanager.package{i}, 'version')
% skip packages which have no versions
continue
end
versions = X.tbxmanager.package{i}.version;
if ~iscell(versions)
versions = { versions };
end
for j = 1:length(versions)
if ~isfield(versions{j}, 'url')
% skip versions which have no downloads
continue
end
if ~iscell(versions{j}.url)
versions{j}.url = { versions{j}.url };
end
for k = 1:length(versions{j}.url)
tbx.name = name;
tbx.version = versions{j}.id.Text;
tbx.date = versions{j}.date.Text;
tbx.datenum = datenum(tbx.date);
tbx.url = versions{j}.url{k}.Text;
tbx.arch = lower(versions{j}.url{k}.Attributes.arch);
tbx.license = license;
if isempty(L)
L = tbx;
else
L = [L tbx];
end
end
end
end
end
%%
function L = tbx_parseLicense(X)
% Extracts information about package's license from the XML
%default settings
L.text = ''; % text of the license
L.require_email = false; % true/false whether installing requires registration
if isempty(X)
return
end
if isfield(X, 'url')
license_url = X.url.Text;
try
L.text = deblank(sub_urlread(license_url));
catch
error('TBXMANAGER:URLERROR', ...
'Couldn''t connect to %s', license_url);
end
end
if isfield(X, 'require_email')
L.require_email = eval(X.require_email.Text);
end
% replace '\r\n' by '\n', otherwise new-line characters would display two
% empty lines (thanks F. Ullmann)
L.text = regexprep(L.text, '\r\n', '\n');
end
%%
function List = tbx_listInstalled(toolboxes)
% Returns list of installed toolboxes
%
% The list is returned as an array of structures:
% name: short toolbox name
% version: version id
% arch: architecture
Setup = tbx_setup;
List = [];
if nargin==1
% only return list of versions for particular toolboxes
if ~iscell(toolboxes), toolboxes = { toolboxes }; end
else
toolboxes = tbx_list_dirs(Setup.tbxdir);
end
for it = 1:length(toolboxes)
tbxdir = [Setup.tbxdir filesep toolboxes{it}];
versions = tbx_list_dirs(tbxdir);
for iv = 1:length(versions)
[archs, dates, datenums] = tbx_list_dirs([tbxdir filesep versions{iv}]);
for ia = 1:length(archs)
L.name = toolboxes{it};
L.version = versions{iv};
L.arch = archs{ia};
L.date = dates{ia};
L.datenum = datenums{ia};
if isempty(List)
List = L;
else
List(end+1) = L;
end
end
end
end
end
%%
function [out, dates, datenums] = tbx_list_dirs(d)
% Helper to list all subdirectories of directory 'd'
dirs = dir(d);
out = {};
dates = {};
datenums = {};
for i = 1:length(dirs)
if dirs(i).isdir && ~isequal(dirs(i).name, '.') && ...
~isequal(dirs(i).name, '..');
out{end+1} = dirs(i).name;
dates{end+1} = dirs(i).date;
datenums{end+1} = dirs(i).datenum;
end
end
end
%%
function S = tbx_n2s(N)
% Converts "name:version:arch" into a structure
if isa(N, 'struct')
S = N;
else
% parse string
colpos = find(N==':');
if length(colpos)==0
S.name = N;
S.version = '';
S.arch = '';
elseif length(colpos)==1
S.name = N(1:colpos-1);
S.version = N(colpos+1:end);
S.arch = '';
elseif length(colpos)==2
S.name = N(1:colpos(1)-1);
S.version = N(colpos(1)+1:colpos(2)-1);
S.arch = N(colpos(2)+1:end);
else
error('TBXMANAGER:BADCOMMAND', 'Malformed string, must be in "name:version:arch" format.');
end
end
end
%%
function Enabled = tbx_loadEnabled
% Loads list of enabled toolboxes
Setup = tbx_setup();
Enabled = [];
try
content = fileread(Setup.enabledfile);
catch
content = [];
end
if isempty(content)
% empty file
return
end
s = textscan(content, '%s');
if ~isempty(s)
for i = 1:length(s{1})
% fix a weird error when settings were saved in R2014a
s{1}{i}(s{1}{i}==0) = '';
Enabled = [Enabled tbx_n2s(s{1}{i})];
end
end
end
%%
function tbx_writeEnabled(Enabled)
% writes list of enabled toolboxes
Setup = tbx_setup;
fid = fopen(Setup.enabledfile, 'w');
if fid < 0
error('TBXMANAGER:FILEERROR', 'Couldn''t open %s for writing.', Setup.enabledfile);
end
for i = 1:length(Enabled)
fprintf(fid, '%s\n', tbx_s2n(Enabled(i)));
end
fclose(fid);
end
%%
function tbx_registerDisabled(Toolbox)
% Registers the toolbox as enabled
% sanitize the Toolbox structure
Toolbox = tbx_n2s(tbx_s2n(Toolbox));
Enabled = tbx_loadEnabled;
% prune any version of this toolbox from the list
keep = true(1, length(Enabled));
for i = 1:length(Enabled)
keep(i) = ~isequal(Enabled(i), Toolbox);
end
tbx_writeEnabled(Enabled(keep));
end
%%
function tbx_registerEnabled(Toolbox)
% Registers the toolbox as enabled
% sanitize the Toolbox structure
Toolbox = tbx_n2s(tbx_s2n(Toolbox));
Enabled = tbx_loadEnabled;
% prune any previous versions of this toolbox from the list of enabled
% toolboxes
keep = true(1, length(Enabled));
for i = 1:length(Enabled)
keep(i) = ~isequal(Enabled(i).name, Toolbox.name);
end
Enabled = [Enabled(keep) Toolbox];
tbx_writeEnabled(Enabled);
end
%%
function tbx_restorePath(args)
% Restores path to all previously active toolboxes
Enabled = tbx_loadEnabled;
for i = 1:length(Enabled)
tbx_addPath(Enabled(i));
end
end
%%
function P = main_generatePath(args)
% Generates path to all enabled toolboxes
P = '';
Enabled = tbx_loadEnabled;
for i = 1:length(Enabled)
P = [P, tbx_genPath(Enabled(i))];
end
end
%%
function tbx_rmPath(Toolbox)
% Removes the given toolbox from MATLAB path
%
% Specification of the input structure:
% name: short toolbox name
% version: version id
% arch: architecture
if ~tbx_isInstalled(Toolbox)
error('TBXMANAGER:BADCOMMAND', 'Toolbox "%s" is not installed.', tbx_s2n(Toolbox));
end
% remove any previous instances of this toolbox from the path
archdir = tbx_installationDir(Toolbox);
w = warning; warning('off');
rmpath(genpath(archdir));
warning(w);
rehash pathreset
fprintf('Toolbox "%s" removed from the Matlab path.\n', tbx_s2n(Toolbox));
tbx_registerDisabled(Toolbox);
end
%%
function N = tbx_s2n(Toolbox)
% Converts toolbox' data into a compact string
%
% Specification of the input structure:
% name: short toolbox name
% version: version id
% arch: architecture
N = [Toolbox.name ':' Toolbox.version ':' Toolbox.arch];
end
%%
function tbx_uninstall(Toolbox)
% Uninstalls a given toolbox
%
% The toolbox to be uninstalled is described by a structure:
% name: short toolbox name
% version: version id
% arch: architecture
if ~tbx_isInstalled(Toolbox)
error('TBXMANAGER:BADCOMMAND', 'Toolbox "%s" is not installed.', tbx_s2n(Toolbox));
end
tbx_notifyServer('uninstall', Toolbox);
% Delete the arch directory
[archdir, versiondir, basedir] = tbx_installationDir(Toolbox);
fprintf('Removing directory "%s"...\n', archdir);
try
rmdir(archdir, 's');
catch
fprintf('Clearing functions from the memory...\n');
clear functions
rmdir(archdir, 's');
end
% Did we delete the last architecture of the version?
archs = tbx_list_dirs(versiondir);
if isempty(archs)
% No more architectures, we can delete the whole version
fprintf('Removing directory "%s"...\n', versiondir);
rmdir(versiondir, 's');
% Did we delete all versions?
vers = tbx_list_dirs(basedir);
if isempty(vers)
% No more versions, delete the whole toolbox
fprintf('Removing directory "%s"...\n', basedir);
rmdir(basedir, 's');
end
end
fprintf('Toolbox "%s" uninstalled.\n', Toolbox.name);
end
%%
function answer = tbx_expandChoice(cmd, choices)
% returns element of cell array "choices" that start with the string "cmd"
candidates = {};
if ~iscell(choices)
choices = { choices };
end
for i = 1:length(choices)
if length(choices{i}) >= length(cmd)
if isequal(choices{i}(1:length(cmd)), cmd)
candidates{end+1} = choices{i};
end
end
end
if isempty(candidates)
error('TBXMANAGER:BADCOMMAND', 'Unrecognized command/option "%s".', cmd);
elseif length(candidates)==1
% unambiguous choice
answer = candidates{1};
else
fprintf('\nYou choice "%s" is ambiguous. Possible matches are:\n', cmd);
for i = 1:length(candidates)
fprintf('\t%s\n', candidates{i});
end
fprintf('\n');
error('TBXMANAGER:BADCOMMAND', ...
'Ambiguous choice, please refine your input.');
end
end
%%
function tbx_notifyServer(command, Toolbox, varargin)
% Notifies the server about a command
% requires java
if ~usejava('jvm')
return
end
Setup = tbx_setup();
url = sprintf('%s/%s?c=%s&v=%s&p=%s', Setup.server_url, ...
urlencode(Toolbox.name), ...
urlencode(command), ...
urlencode(Toolbox.version), ...
urlencode(lower(computer)));
% include parameters
if ~isempty(varargin)
if mod(length(varargin), 2)~=0
error('Parameters must come in key/value pairs.');
end
for i = 1:2:length(varargin)
url = sprintf('%s&%s=%s', url, ...
urlencode(varargin{i}), urlencode(varargin{i+1}));
end
end
% call the url
try
sub_urlread(url);
end
end
%%
function sub_urlwrite(url, filename)
if exist('websave', 'file')
% use websave as it handles https
% disable SSL certificate verification (problem with Let's Encrypt
% certificates)
% see https://www.mathworks.com/matlabcentral/answers/92506-how-can-i-configure-matlab-to-allow-access-to-self-signed-https-servers
try
opts = weboptions('Timeout', 30, 'CertificateFilename', '');
catch
opts = weboptions('Timeout', 30);
end
websave(filename, url, opts);
else
%if length(url)>6 && isequal(lower(url(1:6)), 'https:')
% fprintf('\nWARNING: websave() not available, falling back to urlwrite() which, however, can have issues with https pages.\n');
%end
try
urlwrite(url, filename);
catch
fprintf('WARNING: urlwrite() failed, using slower download method (upgrade to Matlab R2013a to fix this problem)\n');
fid = fopen(filename, 'w');
c = onCleanup(@() fclose(fid));
u = java.net.URL(url);
conn = u.openConnection();
conn.connect();
str = conn.getInputStream();
b = 0;
while b>=0
b = str.read();
fwrite(fid, b);
end
end
end
end
%%
function data = sub_urlread(url)
if exist('webread', 'file')
% use webread() as it handles https
% disable SSL certificate verification (problem with Let's Encrypt
% certificates)
% see https://www.mathworks.com/matlabcentral/answers/92506-how-can-i-configure-matlab-to-allow-access-to-self-signed-https-servers
try
opts = weboptions('CertificateFilename', '');
catch
opts = weboptions;
end
data = webread(url, opts);
else
if length(url)>6 && isequal(lower(url(1:6)), 'https:')
fprintf('\nWARNING: webread() not available, falling back to urlread() which, however, can have issues with https pages.\n');
end
data = urlread(url);
end
end
%%
function requires_java
if ~usejava('jvm')
error('TBXMANAGER:NOJAVA', 'This function requires the Java virtual machine.');
end
end
%%
function [ s ] = xml2struct( file )
%Convert xml file into a MATLAB structure
% [ s ] = xml2struct( file )
%
% A file containing:
%
% Some text
% Some more text
% Even more text
%
%
% Will produce:
% s.XMLname.Attributes.attrib1 = "Some value";
% s.XMLname.Element.Text = "Some text";
% s.XMLname.DifferentElement{1}.Attributes.attrib2 = "2";
% s.XMLname.DifferentElement{1}.Text = "Some more text";
% s.XMLname.DifferentElement{2}.Attributes.attrib3 = "2";
% s.XMLname.DifferentElement{2}.Attributes.attrib4 = "1";
% s.XMLname.DifferentElement{2}.Text = "Even more text";
%
% Please note that the following characters are substituted
% '-' by '_dash_', ':' by '_colon_' and '.' by '_dot_'
%
% Written by W. Falkena, ASTI, TUDelft, 21-08-2010
% Attribute parsing speed increased by 40% by A. Wanner, 14-6-2011
% Added CDATA support by I. Smirnov, 20-3-2012
%
% Modified by X. Mo, University of Wisconsin, 12-5-2012
if (nargin < 1)
clc;
help xml2struct
return
end
if isa(file, 'org.apache.xerces.dom.DeferredDocumentImpl') || isa(file, 'org.apache.xerces.dom.DeferredElementImpl')
% input is a java xml object
xDoc = file;
else
%check for existance
if (exist(file,'file') == 0)
%Perhaps the xml extension was omitted from the file name. Add the
%extension and try again.
if (isempty(strfind(file,'.xml')))
file = [file '.xml'];
end
if (exist(file,'file') == 0)
error('TBXMANAGER:FILEERROR', ['The file ' file ' could not be found']);
end
end
%read the xml file
xDoc = xmlread(file);
end
%parse xDoc into a MATLAB structure
s = parseChildNodes(xDoc);
end
% ----- Subfunction parseChildNodes -----
function [children,ptext,textflag] = parseChildNodes(theNode)
% Recurse over node children.
children = struct;
ptext = struct; textflag = 'Text';
if hasChildNodes(theNode)
childNodes = getChildNodes(theNode);
numChildNodes = getLength(childNodes);
for count = 1:numChildNodes
theChild = item(childNodes,count-1);
[text,name,attr,childs,textflag] = getNodeData(theChild);
if (~strcmp(name,'#text') && ~strcmp(name,'#comment') && ~strcmp(name,'#cdata_dash_section'))
%XML allows the same elements to be defined multiple times,
%put each in a different cell
if (isfield(children,name))
if (~iscell(children.(name)))
%put existsing element into cell format
children.(name) = {children.(name)};
end
index = length(children.(name))+1;
%add new element
children.(name){index} = childs;
if(~isempty(fieldnames(text)))
children.(name){index} = text;
end
if(~isempty(attr))
children.(name){index}.('Attributes') = attr;
end
else
%add previously unknown (new) element to the structure
children.(name) = childs;
if(~isempty(text) && ~isempty(fieldnames(text)))
children.(name) = text;
end
if(~isempty(attr))
children.(name).('Attributes') = attr;
end
end
else
ptextflag = 'Text';
if (strcmp(name, '#cdata_dash_section'))
ptextflag = 'CDATA';
elseif (strcmp(name, '#comment'))
ptextflag = 'Comment';
end
%this is the text in an element (i.e., the parentNode)
if (~isempty(regexprep(text.(textflag),'[\s]*','')))
if (~isfield(ptext,ptextflag) || isempty(ptext.(ptextflag)))
ptext.(ptextflag) = text.(textflag);
else
%what to do when element data is as follows:
%Text More text
%put the text in different cells:
% if (~iscell(ptext)) ptext = {ptext}; end
% ptext{length(ptext)+1} = text;
%just append the text
ptext.(ptextflag) = [ptext.(ptextflag) text.(textflag)];
end
end
end
end
end
end
% ----- Subfunction getNodeData -----
function [text,name,attr,childs,textflag] = getNodeData(theNode)
% Create structure of node info.
%make sure name is allowed as structure name
name = toCharArray(getNodeName(theNode))';
name = strrep(name, '-', '_dash_');
name = strrep(name, ':', '_colon_');
name = strrep(name, '.', '_dot_');
attr = parseAttributes(theNode);
if (isempty(fieldnames(attr)))
attr = [];
end
%parse child nodes
[childs,text,textflag] = parseChildNodes(theNode);
if (isempty(fieldnames(childs)) && isempty(fieldnames(text)))
%get the data of any childless nodes
% faster than if any(strcmp(methods(theNode), 'getData'))
% no need to try-catch (?)
% faster than text = char(getData(theNode));
text.(textflag) = toCharArray(getTextContent(theNode))';
end
end
% ----- Subfunction parseAttributes -----
function attributes = parseAttributes(theNode)
% Create attributes structure.
attributes = struct;
if hasAttributes(theNode)
theAttributes = getAttributes(theNode);
numAttributes = getLength(theAttributes);
for count = 1:numAttributes
%attrib = item(theAttributes,count-1);
%attr_name = regexprep(char(getName(attrib)),'[-:.]','_');
%attributes.(attr_name) = char(getValue(attrib));
%Suggestion of Adrian Wanner
str = toCharArray(toString(item(theAttributes,count-1)))';
k = strfind(str,'=');
attr_name = str(1:(k(1)-1));
attr_name = strrep(attr_name, '-', '_dash_');
attr_name = strrep(attr_name, ':', '_colon_');
attr_name = strrep(attr_name, '.', '_dot_');
attributes.(attr_name) = str((k(1)+2):(end-1));
end
end
end