Plugins

Authors: Francois Tadel

Brainstorm connects with features from many third-party libraries of methods. The external software can be downloaded or updated automatically by Brainstorm when needed. This tutorial presents the API to register and manage plugins.

Interactive management

The Brainstorm interface offers menus to Install/Update/Uninstall plugins.

example1.gif

Install: Downloads and unzips the package and all its dependencies in the Brainstorm user folder:
$HOME/.brainstorm/plugins/

Uninstall: Deletes the plugin folder, all its subfolders, and all the other plugins that depend on it.

Load: Adds all the subfolders needed by the plugin to the Matlab path, plus other optional tasks. Loading a plugin causes the recursive load of all the other plugins required by this plugin.

Unload: Removes all the plugin folders from the Matlab path, and all the plugins that depend on it.

Update: Some plugins are designed to update themselves automatically whenever a new version is available online, or requested by Brainstorm. Others plugins must be updated manually. Updating is equivalent to uninstalling without removing the dependencies, and then nstalling again.

Manual install: If you already have a given plugin installed on your computer (eg. FieldTrip, SPM12) and don't want Brainstorm to manage the download/update or the Matlab path for you, reference it with the menu: Custom install > Set installation folder.

List: You can list all the installed plugins with the menu List:

list.gif

Brainstorm plugins by category

Generic toolboxes:

Anatomy processing:

Forward modeling:

Simulation:

Statistics:

I/O Libraries for specific file formats:

Example: How to set up FieldTrip

We have the possibility to call some of the FieldTrip toolbox functions from the Brainstorm environment. If you are running the compiled version of Brainstorm these functions are already packaged with Brainstorm, otherwise you need to install FieldTrip on your computer, either manually or as a Brainstorm plugin.

Plugin definition

The plugins registered in Brainstorm are listed in bst_plugin.m / GetSupported. Each one is an entry in the PlugDesc array, following the structure defined in db_template('plugdesc'). The fields allowed are described below.

Mandatory fields:

Optional fields:

Fields set when installing the plugin:

Fields set when loading the plugin:

Command-line management

The calls to install or manage plugins are all documented in the header of bst_plugin.m:

1 function [varargout] = bst_plugin(varargin) 2 % BST_PLUGIN: Manages Brainstorm plugins 3 % 4 % USAGE: PlugDesc = bst_plugin('GetSupported') % List all the plugins supported by Brainstorm 5 % PlugDesc = bst_plugin('GetSupported', PlugName/PlugDesc) % Get only one specific supported plugin 6 % PlugDesc = bst_plugin('GetInstalled') % Get all the installed plugins 7 % PlugDesc = bst_plugin('GetInstalled', PlugName/PlugDesc) % Get a specific installed plugin 8 % [PlugDesc, errMsg] = bst_plugin('GetDescription', PlugName/PlugDesc) % Get a full structure representing a plugin 9 % [Version, URLzip] = bst_plugin('GetVersionOnline', PlugName, URLzip, isCache) % Get the latest online version of some plugins 10 % sha = bst_plugin('GetGithubCommit', URLzip) % Get SHA of the last commit of a GitHub repository from a master.zip url 11 % ReadmeFile = bst_plugin('GetReadmeFile', PlugDesc) % Get full path to plugin readme file 12 % LogoFile = bst_plugin('GetLogoFile', PlugDesc) % Get full path to plugin logo file 13 % Version = bst_plugin('CompareVersions', v1, v2) % Compare two version strings 14 % [isOk, errMsg, PlugDesc] = bst_plugin('Load', PlugName/PlugDesc, isVerbose=1) 15 % [isOk, errMsg, PlugDesc] = bst_plugin('LoadInteractive', PlugName/PlugDesc) 16 % [isOk, errMsg, PlugDesc] = bst_plugin('Unload', PlugName/PlugDesc, isVerbose=1) 17 % [isOk, errMsg, PlugDesc] = bst_plugin('UnloadInteractive', PlugName/PlugDesc) 18 % [isOk, errMsg, PlugDesc] = bst_plugin('Install', PlugName, isInteractive=0, minVersion=[]) 19 % [isOk, errMsg, PlugDesc] = bst_plugin('InstallMultipleChoice',PlugNames, isInteractive=0) % Install at least one of the input plugins 20 % [isOk, errMsg, PlugDesc] = bst_plugin('InstallInteractive', PlugName) 21 % [isOk, errMsg] = bst_plugin('Uninstall', PlugName, isInteractive=0, isDependencies=1) 22 % [isOk, errMsg] = bst_plugin('UninstallInteractive', PlugName) 23 % bst_plugin('Configure', PlugDesc) % Execute some additional tasks after loading or installation 24 % bst_plugin('SetCustomPath', PlugName, PlugPath) 25 % bst_plugin('List', Target='installed') % Target={'supported','installed'} 26 % bst_plugin('Archive', OutputFile=[ask]) % Archive software environment 27 % bst_plugin('MenuCreate', jMenu) 28 % bst_plugin('MenuUpdate', jMenu) 29 % bst_plugin('LinkCatSpm', Action) % 0=Delete/1=Create/2=Check a symbolic link for CAT12 in SPM12 toolbox folder 30 % 31 % 32 % PLUGIN DEFINITION 33 % ================= 34 % 35 % The plugins registered in Brainstorm are listed in function GetSupported(). 36 % Each one is an entry in the PlugDesc array, following the structure defined in db_template('plugdesc'). 37 % The fields allowed are described below. 38 % 39 % Mandatory fields 40 % ================ 41 % - Name : String: Plugin name = subfolder in the Brainstorm user folder 42 % - Version : String: Version of the plugin (eg. '1.2', '21a', 'github-master', 'latest') 43 % - URLzip : String: Download URL, zip or tgz file accessible over HTTP/HTTPS/FTP 44 % - URLinfo : String: Information URL = Software website 45 % 46 % Optional fields 47 % =============== 48 % - AutoUpdate : Boolean: If true, the plugin is updated automatically when there is a new version available (default: true). 49 % - AutoLoad : Boolean: If true, the plugin is loaded automatically at Brainstorm startup 50 % - Category : String: Sub-menu in which the plugin is listed 51 % - ExtraMenus : Cell matrix {Nx2}: List of entries to add to the plugins menu 52 % | ExtraMenus{i,1}: String: Label of the menu 53 % | ExtraMenus{i,2}: String: Matlab code to eval when the menu is clicked 54 % - TestFile : String: Name of a file that should be located in one of the loaded folders of the plugin (eg. 'spm.m' for SPM12). 55 % | This is used to test whether the plugin was correctly installed, or whether it is available somewhere else in the Matlab path. 56 % - ReadmeFile : String: Name of the text file to display after installing the plugin (must be in the plugin folder). 57 % | If empty, it tries using brainstorm3/doc/plugin/plugname_readme.txt 58 % - LogoFile : String: Name of the image file to display during the plugin download, installation, and associated computations (must be in the plugin folder). 59 % | Supported extensions: gif, png. If empty, try using brainstorm3/doc/plugin/<Name>_logo.[gif|png] 60 % - MinMatlabVer : Integer: Minimum Matlab version required for using this plugin, as returned by bst_get('MatlabVersion') 61 % - CompiledStatus : Integer: Behavior of this plugin in the compiled version of Brainstorm: 62 % | 0: Plugin is not available in the compiled distribution of Brainstorm 63 % | 1: Plugin is available for download (only for plugins based on native compiled code) 64 % | 2: Plugin is included in the compiled distribution of Brainstorm 65 % - RequiredPlugs : Cell-array: Additional plugins required by this plugin, that must be installed/loaded beforehand. 66 % | {Nx2} => {'plugname','version'; ...} or 67 % | {Nx1} => {'plugname'; ...} 68 % - UnloadPlugs : Cell-array of names of incompatible plugin, to unload before loaing this one 69 % - LoadFolders : Cell-array of subfolders to add to the Matlab path when setting up the plugin. Use {'*'} to add all the plugin subfolders. 70 % - GetVersionFcn : String to eval or function handle to call to get the version after installation 71 % - InstalledFcn : String to eval or function handle to call after installing the plugin 72 % - UninstalledFcn : String to eval or function handle to call after uninstalling the plugin 73 % - LoadedFcn : String to eval or function handle to call after loading the plugin 74 % - UnloadedFcn : String to eval or function handle to call after unloading the plugin 75 % - DeleteFiles : List of files to delete after installation 76 % 77 % Fields set when installing the plugin 78 % ===================================== 79 % - Processes : List of process functions to be added to the pipeline manager 80 % 81 % Fields set when loading the plugin 82 % ================================== 83 % - Path : Installation path (eg. /home/username/.brainstorm/plugins/fieldtrip) 84 % - SubFolder : If all the code is in a single subfolder (eg. /plugins/fieldtrip/fieldtrip-20210304), 85 % this is detected and the full path to the TestFile would be typically fullfile(Path, SubFolder). 86 % - isLoaded : 0=Not loaded, 1=Loaded 87 % - isManaged : 0=Installed manually by the user, 1=Installed automatically by Brainstorm 88 % 89 90 % @============================================================================= 91 % This function is part of the Brainstorm software: 92 % https://neuroimage.usc.edu/brainstorm 93 % 94 % Copyright (c) University of Southern California & McGill University 95 % This software is distributed under the terms of the GNU General Public License 96 % as published by the Free Software Foundation. Further details on the GPLv3 97 % license can be found at http://www.gnu.org/copyleft/gpl.html. 98 % 99 % FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE 100 % UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY 101 % WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF 102 % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY 103 % LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. 104 % 105 % For more information type "brainstorm license" at command prompt. 106 % =============================================================================@ 107 % 108 % Authors: Francois Tadel 2021-2022 109 110 eval(macro_method); 111 end 112 113 114 %% ===== GET SUPPORTED PLUGINS ===== 115 % USAGE: PlugDesc = bst_plugin('GetSupported') % List all the plugins supported by Brainstorm 116 % PlugDesc = bst_plugin('GetSupported', PlugName/PlugDesc) % Get only one specific supported plugin 117 function PlugDesc = GetSupported(SelPlug) 118 % Parse inputs 119 if (nargin < 1) || isempty(SelPlug) 120 SelPlug = []; 121 end 122 % Initialized returned structure 123 PlugDesc = repmat(db_template('PlugDesc'), 0); 124 % Get OS 125 OsType = bst_get('OsType', 0); 126 127 % ================================================================================================================ 128 % === ANATOMY: BRAIN2MESH === 129 PlugDesc(end+1) = GetStruct('brain2mesh'); 130 PlugDesc(end).Version = 'github-master'; 131 PlugDesc(end).Category = 'Anatomy'; 132 PlugDesc(end).URLzip = 'https://github.com/fangq/brain2mesh/archive/master.zip'; 133 PlugDesc(end).URLinfo = 'http://mcx.space/brain2mesh/'; 134 PlugDesc(end).TestFile = 'brain2mesh.m'; 135 PlugDesc(end).ReadmeFile = 'README.md'; 136 PlugDesc(end).CompiledStatus = 2; 137 PlugDesc(end).RequiredPlugs = {'spm12'; 'iso2mesh'}; 138 PlugDesc(end).DeleteFiles = {'examples', 'brain1020.m', 'closestnode.m', 'label2tpm.m', 'slicesurf.m', 'slicesurf3.m', 'tpm2label.m', 'polylineinterp.m', 'polylinelen.m', 'polylinesimplify.m'}; 139 140 % === ANATOMY: CAT12 === 141 PlugDesc(end+1) = GetStruct('cat12'); 142 PlugDesc(end).Version = 'latest'; 143 PlugDesc(end).Category = 'Anatomy'; 144 PlugDesc(end).AutoUpdate = 1; 145 PlugDesc(end).URLzip = 'http://www.neuro.uni-jena.de/cat12/cat12_latest.zip'; 146 PlugDesc(end).URLinfo = 'http://www.neuro.uni-jena.de/cat/'; 147 PlugDesc(end).TestFile = 'cat_version.m'; 148 PlugDesc(end).ReadmeFile = 'Contents.txt'; 149 PlugDesc(end).CompiledStatus = 0; 150 PlugDesc(end).RequiredPlugs = {'spm12'}; 151 PlugDesc(end).GetVersionFcn = 'bst_getoutvar(2, @cat_version)'; 152 PlugDesc(end).InstalledFcn = 'LinkCatSpm(1);'; 153 PlugDesc(end).UninstalledFcn = 'LinkCatSpm(0);'; 154 PlugDesc(end).LoadedFcn = 'LinkCatSpm(2);'; 155 PlugDesc(end).ExtraMenus = {'Online tutorial', 'web(''https://neuroimage.usc.edu/brainstorm/Tutorials/SegCAT12'', ''-browser'')'}; 156 157 % === ANATOMY: ISO2MESH === 158 PlugDesc(end+1) = GetStruct('iso2mesh'); 159 PlugDesc(end).Version = '1.9.6'; 160 PlugDesc(end).Category = 'Anatomy'; 161 PlugDesc(end).AutoUpdate = 1; 162 PlugDesc(end).URLzip = 'https://github.com/fangq/iso2mesh/releases/download/v1.9.6/iso2mesh-1.9.6-allinone.zip'; 163 PlugDesc(end).URLinfo = 'http://iso2mesh.sourceforge.net'; 164 PlugDesc(end).TestFile = 'iso2meshver.m'; 165 PlugDesc(end).ReadmeFile = 'README.txt'; 166 PlugDesc(end).CompiledStatus = 2; 167 PlugDesc(end).LoadedFcn = 'assignin(''base'', ''ISO2MESH_TEMP'', bst_get(''BrainstormTmpDir''));'; 168 PlugDesc(end).DeleteFiles = {'doc', 'tools', '.git_filters', 'sample', ... 169 'bin/cgalmesh.exe', 'bin/cgalmesh.mexglx', 'bin/cgalmesh.mexmaci', ... 170 'bin/cgalpoly.exe', 'bin/cgalpoly.mexglx', 'bin/cgalpoly.mexmaci', 'bin/cgalpoly.mexa64', 'bin/cgalpoly.mexmaci64', 'bin/cgalpoly_x86-64.exe', ... % Removing cgalpoly completely (not used) 171 'bin/cgalsimp2.exe', 'bin/cgalsimp2.mexglx', 'bin/cgalsimp2.mexmaci', 'bin/cgalsimp2.mexmac', ... 172 'bin/cgalsurf.exe', 'bin/cgalsurf.mexglx', 'bin/cgalsurf.mexmaci', ... 173 'bin/cork.exe', ... 174 'bin/gtsrefine.mexglx', 'bin/gtsrefine.mexmaci', 'bin/gtsrefine.mexarmhf', 'bin/gtsrefine.exe', 'bin/gtsrefine.mexmaci64', ... % Removing gtsrefine completely (not used) 175 'bin/jmeshlib.exe', 'bin/jmeshlib.mexglx', 'bin/jmeshlib.mexmaci', 'bin/jmeshlib.mexmac', 'bin/jmeshlib.mexarmhf', ... 176 'bin/meshfix.exe', 'bin/meshfix.mexglx', 'bin/meshfix.mexmaci', 'bin/meshfix.mexarmhf', ... 177 'bin/tetgen.exe', 'bin/tetgen.mexglx', 'bin/tetgen.mexmaci', 'bin/tetgen.mexmac', 'bin/tetgen.mexarmhf', 'bin/tetgen.mexa64', 'bin/tetgen.mexmaci64', 'bin/tetgen_x86-64.exe', ... % Removing older tetgen completely (not used) 178 'bin/tetgen1.5.exe', 'bin/tetgen1.5.mexglx'}; 179 180 % === ANATOMY: ROAST === 181 PlugDesc(end+1) = GetStruct('roast'); 182 PlugDesc(end).Version = '3.0'; 183 PlugDesc(end).Category = 'Anatomy'; 184 PlugDesc(end).AutoUpdate = 1; 185 PlugDesc(end).URLzip = 'https://www.parralab.org/roast/roast-3.0.zip'; 186 PlugDesc(end).URLinfo = 'https://www.parralab.org/roast/'; 187 PlugDesc(end).TestFile = 'roast.m'; 188 PlugDesc(end).ReadmeFile = 'README.md'; 189 PlugDesc(end).CompiledStatus = 0; 190 PlugDesc(end).UnloadPlugs = {'spm12', 'iso2mesh'}; 191 PlugDesc(end).LoadFolders = {'lib/spm12', 'lib/iso2mesh', 'lib/cvx', 'lib/ncs2daprox', 'lib/NIFTI_20110921'}; 192 193 % === FORWARD: OPENMEEG === 194 PlugDesc(end+1) = GetStruct('openmeeg'); 195 PlugDesc(end).Version = '2.4.1'; 196 PlugDesc(end).Category = 'Forward'; 197 PlugDesc(end).AutoUpdate = 1; 198 switch(OsType) 199 case 'linux64' 200 PlugDesc(end).URLzip = 'https://files.inria.fr/OpenMEEG/download/OpenMEEG-2.4.1-Linux.tar.gz'; 201 PlugDesc(end).TestFile = 'libOpenMEEG.so'; 202 case 'mac64' 203 PlugDesc(end).URLzip = 'https://files.inria.fr/OpenMEEG/download/OpenMEEG-2.4.1-MacOSX.tar.gz'; 204 PlugDesc(end).TestFile = 'libOpenMEEG.1.1.0.dylib'; 205 case 'win32' 206 PlugDesc(end).URLzip = 'https://files.inria.fr/OpenMEEG/download/release-2.2/OpenMEEG-2.2.0-win32-x86-cl-OpenMP-shared.tar.gz'; 207 PlugDesc(end).TestFile = 'om_assemble.exe'; 208 case 'win64' 209 PlugDesc(end).URLzip = 'https://files.inria.fr/OpenMEEG/download/OpenMEEG-2.4.1-Win64.tar.gz'; 210 PlugDesc(end).TestFile = 'om_assemble.exe'; 211 end 212 PlugDesc(end).URLinfo = 'https://openmeeg.github.io/'; 213 PlugDesc(end).ExtraMenus = {'Alternate versions', 'web(''https://files.inria.fr/OpenMEEG/download/'', ''-browser'')'; ... 214 'Download Visual C++', 'web(''http://www.microsoft.com/en-us/download/details.aspx?id=14632'', ''-browser'')'; ... 215 'Online tutorial', 'web(''https://neuroimage.usc.edu/brainstorm/Tutorials/TutBem'', ''-browser'')'}; 216 PlugDesc(end).CompiledStatus = 1; 217 PlugDesc(end).LoadFolders = {'bin', 'lib'}; 218 219 % === FORWARD: DUNEURO === 220 PlugDesc(end+1) = GetStruct('duneuro'); 221 PlugDesc(end).Version = 'latest'; 222 PlugDesc(end).Category = 'Forward'; 223 PlugDesc(end).AutoUpdate = 1; 224 PlugDesc(end).URLzip = 'http://neuroimage.usc.edu/bst/getupdate.php?d=bst_duneuro.zip'; 225 PlugDesc(end).URLinfo = 'https://neuroimage.usc.edu/brainstorm/Tutorials/Duneuro'; 226 PlugDesc(end).TestFile = 'bst_duneuro_meeg_win64.exe'; 227 PlugDesc(end).CompiledStatus = 1; 228 PlugDesc(end).LoadFolders = {'bin'}; 229 230 % === INVERSE: BRAINENTROPY === 231 PlugDesc(end+1) = GetStruct('brainentropy'); 232 PlugDesc(end).Version = 'github-master'; 233 PlugDesc(end).Category = 'Inverse'; 234 PlugDesc(end).AutoUpdate = 1; 235 PlugDesc(end).URLzip = 'https://github.com/multi-funkim/best-brainstorm/archive/master.zip'; 236 PlugDesc(end).URLinfo = 'https://neuroimage.usc.edu/brainstorm/Tutorials/TutBEst'; 237 PlugDesc(end).TestFile = 'process_inverse_mem.m'; 238 PlugDesc(end).AutoLoad = 1; 239 PlugDesc(end).CompiledStatus = 2; 240 PlugDesc(end).LoadFolders = {'*'}; 241 PlugDesc(end).DeleteFiles = {'docs', '.github'}; 242 243 % === I/O: ADI-SDK === ADInstrument SDK for reading LabChart files 244 PlugDesc(end+1) = GetStruct('adi-sdk'); 245 PlugDesc(end).Version = 'github-master'; 246 PlugDesc(end).Category = 'I/O'; 247 switch (OsType) 248 case 'win64', PlugDesc(end).URLzip = 'https://github.com/JimHokanson/adinstruments_sdk_matlab/archive/master.zip'; 249 end 250 PlugDesc(end).URLinfo = 'https://github.com/JimHokanson/adinstruments_sdk_matlab'; 251 PlugDesc(end).TestFile = 'adi.m'; 252 PlugDesc(end).CompiledStatus = 0; 253 254 % === I/O: AXION === 255 PlugDesc(end+1) = GetStruct('axion'); 256 PlugDesc(end).Version = '1.0'; 257 PlugDesc(end).Category = 'I/O'; 258 PlugDesc(end).URLzip = 'http://neuroimage.usc.edu/bst/getupdate.php?d=AxionBioSystems.zip'; 259 PlugDesc(end).URLinfo = 'https://www.axionbiosystems.com/products/software/neural-module'; 260 PlugDesc(end).TestFile = 'AxisFile.m'; 261 % PlugDesc(end).ReadmeFile = 'README.md'; 262 PlugDesc(end).CompiledStatus = 0; 263 264 % === I/O: BCI2000 === 265 PlugDesc(end+1) = GetStruct('bci2000'); 266 PlugDesc(end).Version = 'latest'; 267 PlugDesc(end).Category = 'I/O'; 268 PlugDesc(end).URLzip = 'https://bci2000.org/downloads/mex.zip'; 269 PlugDesc(end).URLinfo = 'https://www.bci2000.org/mediawiki/index.php/User_Reference:Matlab_MEX_Files'; 270 PlugDesc(end).TestFile = 'load_bcidat.m'; 271 PlugDesc(end).CompiledStatus = 0; 272 273 % === I/O: BLACKROCK === 274 PlugDesc(end+1) = GetStruct('blackrock'); 275 PlugDesc(end).Version = '5.5.2.0'; 276 PlugDesc(end).Category = 'I/O'; 277 PlugDesc(end).URLzip = 'https://github.com/BlackrockMicrosystems/NPMK/archive/refs/tags/5.5.2.0.zip'; 278 PlugDesc(end).URLinfo = 'https://github.com/BlackrockMicrosystems/NPMK/blob/master/NPMK/Users%20Guide.pdf'; 279 PlugDesc(end).TestFile = 'openNSx.m'; 280 PlugDesc(end).ReadmeFile = 'Versions.txt'; 281 PlugDesc(end).CompiledStatus = 2; 282 PlugDesc(end).LoadFolders = {'*'}; 283 PlugDesc(end).DeleteFiles = {'NPMK/installNPMK.m', 'NPMK/Users Guide.pdf', 'NPMK/Versions.txt', ... 284 'NPMK/@KTUEAImpedanceFile', 'NPMK/@KTNSPOnline', 'NPMK/@KTNEVComments', 'NPMK/@KTFigureAxis', 'NPMK/@KTFigure', 'NPMK/@KTUEAMapFile/.svn', ... 285 'NPMK/openNSxSync.m', 'NPMK/NTrode Utilities', 'NPMK/NSx Utilities', 'NPMK/NEV Utilities', 'NPMK/LoadingEngines', ... 286 'NPMK/Other tools/.svn', 'NPMK/Other tools/edgeDetect.m', 'NPMK/Other tools/kshuffle.m', 'NPMK/Other tools/openCCF.m', 'NPMK/Other tools/parseCCF.m', ... 287 'NPMK/Other tools/periEventPlot.asv', 'NPMK/Other tools/periEventPlot.m', 'NPMK/Other tools/playSound.m', ... 288 'NPMK/Dependent Functions/.svn', 'NPMK/Dependent Functions/.DS_Store', 'NPMK/Dependent Functions/bnsx.dat', 'NPMK/Dependent Functions/syncPatternDetectNEV.m', ... 289 'NPMK/Dependent Functions/syncPatternDetectNSx.m', 'NPMK/Dependent Functions/syncPatternFinderNSx.m'}; 290 291 % === I/O: MFF === 292 PlugDesc(end+1) = GetStruct('mff'); 293 PlugDesc(end).Version = 'github-master'; 294 PlugDesc(end).Category = 'I/O'; 295 PlugDesc(end).AutoUpdate = 0; 296 PlugDesc(end).URLzip = 'https://github.com/arnodelorme/mffmatlabio/archive/master.zip'; 297 PlugDesc(end).URLinfo = 'https://github.com/arnodelorme/mffmatlabio'; 298 PlugDesc(end).TestFile = 'eegplugin_mffmatlabio.m'; 299 PlugDesc(end).ReadmeFile = 'README.md'; 300 PlugDesc(end).MinMatlabVer = 803; % 2014a 301 PlugDesc(end).CompiledStatus = 0; 302 PlugDesc(end).GetVersionFcn = @eegplugin_mffmatlabio; 303 PlugDesc(end).LoadedFcn = @Configure; 304 % Stable version: http://neuroimage.usc.edu/bst/getupdate.php?d='mffmatlabio-3.5.zip' 305 306 % === I/O: NEUROELECTRICS === 307 PlugDesc(end+1) = GetStruct('neuroelectrics'); 308 PlugDesc(end).Version = '1.8'; 309 PlugDesc(end).Category = 'I/O'; 310 PlugDesc(end).AutoUpdate = 0; 311 PlugDesc(end).URLzip = 'https://sccn.ucsd.edu/eeglab/plugins/Neuroelectrics1.8.zip'; 312 PlugDesc(end).URLinfo = 'https://www.neuroelectrics.com/wiki/index.php/EEGLAB'; 313 PlugDesc(end).TestFile = 'pop_nedf.m'; 314 PlugDesc(end).ReadmeFile = 'README.txt'; 315 PlugDesc(end).CompiledStatus = 2; 316 PlugDesc(end).InstalledFcn = ['d=pwd; cd(fileparts(which(''pop_nedf''))); mkdir(''private''); ' ... 317 'f=fopen(''private' filesep 'eeg_emptyset.m'',''wt''); fprintf(f,''function EEG=eeg_emptyset()\nEEG=struct();''); fclose(f);' ... 318 'f=fopen(''private' filesep 'eeg_checkset.m'',''wt''); fprintf(f,''function EEG=eeg_checkset(EEG)''); fclose(f);' ... 319 'cd(d);']; 320 321 % === I/O: NWB === 322 PlugDesc(end+1) = GetStruct('nwb'); 323 PlugDesc(end).Version = 'github-master'; 324 PlugDesc(end).Category = 'I/O'; 325 PlugDesc(end).URLzip = 'https://github.com/NeurodataWithoutBorders/matnwb/archive/master.zip'; 326 PlugDesc(end).URLinfo = 'https://github.com/NeurodataWithoutBorders/matnwb'; 327 PlugDesc(end).TestFile = 'nwbRead.m'; 328 PlugDesc(end).ReadmeFile = 'README.md'; 329 PlugDesc(end).MinMatlabVer = 901; % 2016b 330 PlugDesc(end).CompiledStatus = 0; 331 PlugDesc(end).LoadFolders = {'*'}; 332 PlugDesc(end).LoadedFcn = @Configure; 333 334 % === I/O: PLEXON === 335 PlugDesc(end+1) = GetStruct('plexon'); 336 PlugDesc(end).Version = '1.8.4'; 337 PlugDesc(end).Category = 'I/O'; 338 PlugDesc(end).URLzip = 'https://plexon.com/wp-content/uploads/2017/08/OmniPlex-and-MAP-Offline-SDK-Bundle_0.zip'; 339 PlugDesc(end).URLinfo = 'https://plexon.com/software-downloads/#software-downloads-SDKs'; 340 PlugDesc(end).TestFile = 'plx_info.m'; 341 PlugDesc(end).ReadmeFile = 'Change Log.txt'; 342 PlugDesc(end).CompiledStatus = 0; 343 PlugDesc(end).DownloadedFcn = ['d = fullfile(PlugDesc.Path, ''OmniPlex and MAP Offline SDK Bundle'');' ... 344 'unzip(fullfile(d, ''Matlab Offline Files SDK.zip''), PlugDesc.Path);' ... 345 'file_delete(d,1,3);']; 346 347 % === I/O: PLOTLY === 348 PlugDesc(end+1) = GetStruct('plotly'); 349 PlugDesc(end).Version = 'github-master'; 350 PlugDesc(end).Category = 'I/O'; 351 PlugDesc(end).URLzip = 'https://github.com/plotly/plotly_matlab/archive/master.zip'; 352 PlugDesc(end).URLinfo = 'https://plotly.com/matlab/'; 353 PlugDesc(end).TestFile = 'plotlysetup_online.m'; 354 PlugDesc(end).ReadmeFile = 'README.mkdn'; 355 PlugDesc(end).CompiledStatus = 0; 356 PlugDesc(end).LoadFolders = {'*'}; 357 PlugDesc(end).ExtraMenus = {'Online tutorial', 'web(''https://neuroimage.usc.edu/brainstorm/Tutorials/Plotly'', ''-browser'')'}; 358 359 % === I/O: TDT-SDK === Tucker-Davis Technologies Matlab SDK 360 PlugDesc(end+1) = GetStruct('tdt-sdk'); 361 PlugDesc(end).Version = 'latest'; 362 PlugDesc(end).Category = 'I/O'; 363 PlugDesc(end).URLzip = 'https://www.tdt.com/files/examples/TDTMatlabSDK.zip'; 364 PlugDesc(end).URLinfo = 'https://www.tdt.com/support/matlab-sdk/'; 365 PlugDesc(end).TestFile = 'TDT_Matlab_Tools.pdf'; 366 PlugDesc(end).CompiledStatus = 0; 367 PlugDesc(end).LoadFolders = {'*'}; 368 369 % === SIMULATION: SIMMEEG === 370 PlugDesc(end+1) = GetStruct('simmeeg'); 371 PlugDesc(end).Version = 'github-master'; 372 PlugDesc(end).Category = 'Simulation'; 373 PlugDesc(end).AutoUpdate = 1; 374 PlugDesc(end).URLzip = 'https://github.com/branelab/SimMEEG/archive/master.zip'; 375 PlugDesc(end).URLinfo = 'https://audiospeech.ubc.ca/research/brane/brane-lab-software/'; 376 PlugDesc(end).TestFile = 'SimMEEG_GUI.m'; 377 PlugDesc(end).ReadmeFile = 'SIMMEEG_TERMS_OF_USE.txt'; 378 PlugDesc(end).CompiledStatus = 0; 379 PlugDesc(end).RequiredPlugs = {'fieldtrip', '20200911'}; 380 381 % === STATISTICS: LIBSVM === 382 PlugDesc(end+1) = GetStruct('libsvm'); 383 PlugDesc(end).Version = 'github-master'; 384 PlugDesc(end).Category = 'Statistics'; 385 PlugDesc(end).URLzip = 'https://github.com/cjlin1/libsvm/archive/master.zip'; 386 PlugDesc(end).URLinfo = 'https://www.csie.ntu.edu.tw/~cjlin/libsvm/'; 387 PlugDesc(end).TestFile = 'svm.cpp'; 388 PlugDesc(end).ReadmeFile = 'README'; 389 PlugDesc(end).MinMatlabVer = 803; % 2014a 390 PlugDesc(end).CompiledStatus = 2; 391 PlugDesc(end).LoadFolders = {'*'}; 392 PlugDesc(end).InstalledFcn = 'd=pwd; cd(fileparts(which(''make''))); make; cd(d);'; 393 394 % === ELECTROPHYSIOLOGY: DERIVELFP === 395 PlugDesc(end+1) = GetStruct('derivelfp'); 396 PlugDesc(end).Version = '1.0'; 397 PlugDesc(end).Category = 'e-phys'; 398 PlugDesc(end).AutoUpdate = 0; 399 PlugDesc(end).URLzip = 'http://packlab.mcgill.ca/despikingtoolbox.zip'; 400 PlugDesc(end).URLinfo = 'https://journals.physiology.org/doi/full/10.1152/jn.00642.2010'; 401 PlugDesc(end).TestFile = 'despikeLFP.m'; 402 PlugDesc(end).ReadmeFile = 'readme.txt'; 403 PlugDesc(end).CompiledStatus = 2; 404 PlugDesc(end).LoadFolders = {'toolbox'}; 405 PlugDesc(end).DeleteFiles = {'ExampleDespiking.m', 'appendixpaper.pdf', 'downsample2x.m', 'examplelfpdespiking.mat', 'sta.m', ... 406 'toolbox/delineSignal.m', 'toolbox/despikeLFPbyChunks.asv', 'toolbox/despikeLFPbyChunks.m'}; 407 408 % === ELECTROPHYSIOLOGY: Kilosort === 409 PlugDesc(end+1) = GetStruct('kilosort'); 410 PlugDesc(end).Version = 'github-master'; 411 PlugDesc(end).Category = 'e-phys'; 412 PlugDesc(end).URLzip = 'https://github.com/cortex-lab/KiloSort/archive/refs/heads/master.zip'; 413 PlugDesc(end).URLinfo = 'https://papers.nips.cc/paper/2016/hash/1145a30ff80745b56fb0cecf65305017-Abstract.html'; 414 PlugDesc(end).TestFile = 'fitTemplates.m'; 415 PlugDesc(end).ReadmeFile = 'readme.md'; 416 PlugDesc(end).CompiledStatus = 0; 417 PlugDesc(end).LoadFolders = {'*'}; 418 PlugDesc(end).RequiredPlugs = {'kilosort-wrapper'; 'phy'; 'npy-matlab'}; 419 PlugDesc(end).InstalledFcn = 'process_spikesorting_kilosort(''copyKilosortConfig'', bst_fullfile(bst_get(''UserPluginsDir''), ''kilosort'', ''Kilosort-master'', ''configFiles'', ''StandardConfig_MOVEME.m''), bst_fullfile(bst_get(''UserPluginsDir''), ''kilosort'', ''Kilosort-master'', ''KilosortStandardConfig.m''));'; 420 421 422 % === ELECTROPHYSIOLOGY: Kilosort Wrapper === 423 PlugDesc(end+1) = GetStruct('kilosort-wrapper'); 424 PlugDesc(end).Version = 'github-master'; 425 PlugDesc(end).Category = 'e-phys'; 426 PlugDesc(end).URLzip = 'https://github.com/brendonw1/KilosortWrapper/archive/refs/heads/master.zip'; 427 PlugDesc(end).URLinfo = 'https://zenodo.org/record/3604165'; 428 PlugDesc(end).TestFile = 'Kilosort2Neurosuite.m'; 429 PlugDesc(end).ReadmeFile = 'README.md'; 430 PlugDesc(end).CompiledStatus = 0; 431 432 % === ELECTROPHYSIOLOGY: phy === 433 PlugDesc(end+1) = GetStruct('phy'); 434 PlugDesc(end).Version = 'github-master'; 435 PlugDesc(end).Category = 'e-phys'; 436 PlugDesc(end).URLzip = 'https://github.com/cortex-lab/phy/archive/refs/heads/master.zip'; 437 PlugDesc(end).URLinfo = 'https://phy.readthedocs.io/en/latest/'; 438 PlugDesc(end).TestFile = 'feature_view_custom_grid.py'; 439 PlugDesc(end).LoadFolders = {'*'}; 440 PlugDesc(end).ReadmeFile = 'README.md'; 441 PlugDesc(end).CompiledStatus = 0; 442 PlugDesc(end).RequiredPlugs = {'npy-matlab'}; 443 444 % === ELECTROPHYSIOLOGY: npy-matlab === 445 PlugDesc(end+1) = GetStruct('npy-matlab'); 446 PlugDesc(end).Version = 'github-master'; 447 PlugDesc(end).Category = 'e-phys'; 448 PlugDesc(end).URLzip = 'https://github.com/kwikteam/npy-matlab/archive/refs/heads/master.zip'; 449 PlugDesc(end).URLinfo = 'https://github.com/kwikteam/npy-matlab'; 450 PlugDesc(end).TestFile = 'constructNPYheader.m'; 451 PlugDesc(end).LoadFolders = {'*'}; 452 PlugDesc(end).ReadmeFile = 'README.md'; 453 PlugDesc(end).CompiledStatus = 0; 454 455 % === ELECTROPHYSIOLOGY: ultramegasort2000 === 456 PlugDesc(end+1) = GetStruct('ultramegasort2000'); 457 PlugDesc(end).Version = 'github-master'; 458 PlugDesc(end).Category = 'e-phys'; 459 PlugDesc(end).URLzip = 'https://github.com/danamics/UMS2K/archive/refs/heads/master.zip'; 460 PlugDesc(end).URLinfo = 'https://github.com/danamics/UMS2K/blob/master/UltraMegaSort2000%20Manual.pdf'; 461 PlugDesc(end).TestFile = 'UltraMegaSort2000 Manual.pdf'; 462 PlugDesc(end).LoadFolders = {'*'}; 463 PlugDesc(end).ReadmeFile = 'README.md'; 464 PlugDesc(end).CompiledStatus = 0; 465 466 % === ELECTROPHYSIOLOGY: waveclus === 467 PlugDesc(end+1) = GetStruct('waveclus'); 468 PlugDesc(end).Version = 'github-master'; 469 PlugDesc(end).Category = 'e-phys'; 470 PlugDesc(end).URLzip = 'https://github.com/csn-le/wave_clus/archive/refs/heads/master.zip'; 471 PlugDesc(end).URLinfo = 'https://journals.physiology.org/doi/full/10.1152/jn.00339.2018'; 472 PlugDesc(end).TestFile = 'wave_clus.m'; 473 PlugDesc(end).LoadFolders = {'*'}; 474 PlugDesc(end).ReadmeFile = 'README.md'; 475 PlugDesc(end).CompiledStatus = 0; 476 477 % === NIRSTORM === 478 PlugDesc(end+1) = GetStruct('nirstorm'); 479 PlugDesc(end).Version = 'github-master'; 480 PlugDesc(end).Category = 'fNIRS'; 481 PlugDesc(end).AutoUpdate = 0; 482 PlugDesc(end).AutoLoad = 1; 483 PlugDesc(end).CompiledStatus = 2; 484 PlugDesc(end).URLzip = 'https://github.com/Nirstorm/nirstorm/archive/master.zip'; 485 PlugDesc(end).URLinfo = 'https://github.com/Nirstorm/nirstorm'; 486 PlugDesc(end).LoadFolders = {'bst_plugin/core','bst_plugin/forward','bst_plugin/GLM', 'bst_plugin/inverse' , 'bst_plugin/io','bst_plugin/math' ,'bst_plugin/mbll' ,'bst_plugin/misc', 'bst_plugin/OM', 'bst_plugin/preprocessing', 'bst_plugin/ppl'}; 487 PlugDesc(end).TestFile = 'process_nst_mbll.m'; 488 PlugDesc(end).ReadmeFile = 'README.md'; 489 PlugDesc(end).GetVersionFcn = 'nst_get_version'; 490 PlugDesc(end).RequiredPlugs = {'brainentropy'}; 491 PlugDesc(end).MinMatlabVer = 803; % 2014a 492 PlugDesc(end).DeleteFiles = {'scripts', 'test', 'run_tests.m', 'test_suite_bak.m', '.gitignore'}; 493 494 % === MCXLAB CUDA === 495 PlugDesc(end+1) = GetStruct('mcxlab-cuda'); 496 PlugDesc(end).Version = '2021.12.04'; 497 PlugDesc(end).Category = 'fNIRS'; 498 PlugDesc(end).AutoUpdate = 1; 499 PlugDesc(end).URLzip = 'http://mcx.space/nightly/release/v2020/lite/mcxlab-allinone-x86_64-v2020.zip'; 500 PlugDesc(end).TestFile = 'mcxlab.m'; 501 PlugDesc(end).URLinfo = 'http://mcx.space/wiki/'; 502 PlugDesc(end).CompiledStatus = 0; 503 PlugDesc(end).LoadFolders = {'*'}; 504 PlugDesc(end).UnloadPlugs = {'mcxlab-cl'}; 505 506 % === MCXLAB CL === 507 PlugDesc(end+1) = GetStruct('mcxlab-cl'); 508 PlugDesc(end).Version = '2020'; 509 PlugDesc(end).Category = 'fNIRS'; 510 PlugDesc(end).AutoUpdate = 0; 511 PlugDesc(end).URLzip = 'http://mcx.space/nightly/release/v2020/lite/mcxlabcl-allinone-x86_64-v2020.zip'; 512 PlugDesc(end).TestFile = 'mcxlabcl.m'; 513 PlugDesc(end).URLinfo = 'http://mcx.space/wiki/'; 514 PlugDesc(end).CompiledStatus = 2; 515 PlugDesc(end).LoadFolders = {'*'}; 516 PlugDesc(end).UnloadPlugs = {'mcxlab-cuda'}; 517 518 % === MIA === 519 PlugDesc(end+1) = GetStruct('mia'); 520 PlugDesc(end).Version = 'github-master'; 521 PlugDesc(end).Category = 'sEEG'; 522 PlugDesc(end).AutoUpdate = 0; 523 PlugDesc(end).AutoLoad = 1; 524 PlugDesc(end).CompiledStatus = 2; 525 PlugDesc(end).URLzip = 'https://github.com/MIA-iEEG/mia/archive/refs/heads/master.zip'; 526 PlugDesc(end).URLinfo = 'http://www.neurotrack.fr/mia/'; 527 PlugDesc(end).ReadmeFile = 'README.md'; 528 PlugDesc(end).MinMatlabVer = 803; % 2014a 529 PlugDesc(end).LoadFolders = {'*'}; 530 PlugDesc(end).TestFile = 'process_mia_export_db.m'; 531 PlugDesc(end).ExtraMenus = {'Start MIA', 'mia', 'loaded'}; 532 533 % === FIELDTRIP === 534 PlugDesc(end+1) = GetStruct('fieldtrip'); 535 PlugDesc(end).Version = 'latest'; 536 PlugDesc(end).AutoUpdate = 0; 537 PlugDesc(end).URLzip = 'https://download.fieldtriptoolbox.org/fieldtrip-lite-20220228.zip'; 538 PlugDesc(end).URLinfo = 'http://www.fieldtriptoolbox.org'; 539 PlugDesc(end).TestFile = 'ft_defaults.m'; 540 PlugDesc(end).ReadmeFile = 'README'; 541 PlugDesc(end).CompiledStatus = 2; 542 PlugDesc(end).UnloadPlugs = {'spm12', 'roast'}; 543 PlugDesc(end).LoadFolders = {'specest', 'preproc', 'forward', 'src', 'utilities'}; 544 PlugDesc(end).GetVersionFcn = 'ft_version'; 545 PlugDesc(end).LoadedFcn = ['global ft_default; ' ... 546 'ft_default = []; ' ... 547 'if exist(''filtfilt'', ''file''), ft_default.toolbox.signal=''matlab''; end; ' ... 548 'if exist(''nansum'', ''file''), ft_default.toolbox.stats=''matlab''; end; ' ... 549 'if exist(''rgb2hsv'', ''file''), ft_default.toolbox.images=''matlab''; end; ' ... 550 'ft_defaults;']; 551 552 % === SPM12 === 553 PlugDesc(end+1) = GetStruct('spm12'); 554 PlugDesc(end).Version = 'latest'; 555 PlugDesc(end).AutoUpdate = 0; 556 PlugDesc(end).URLzip = 'https://www.fil.ion.ucl.ac.uk/spm/download/restricted/eldorado/spm12.zip'; 557 PlugDesc(end).URLinfo = 'https://www.fil.ion.ucl.ac.uk/spm/'; 558 PlugDesc(end).TestFile = 'spm.m'; 559 PlugDesc(end).ReadmeFile = 'README.md'; 560 PlugDesc(end).CompiledStatus = 2; 561 PlugDesc(end).UnloadPlugs = {'fieldtrip', 'roast'}; 562 PlugDesc(end).LoadFolders = {'matlabbatch'}; 563 PlugDesc(end).GetVersionFcn = 'bst_getoutvar(2, @spm, ''Ver'')'; 564 PlugDesc(end).LoadedFcn = 'spm(''defaults'',''EEG'');'; 565 % ================================================================================================================ 566 567 % Select only one plugin 568 if ~isempty(SelPlug) 569 % Get plugin name 570 if ischar(SelPlug) 571 PlugName = SelPlug; 572 else 573 PlugName = SelPlug.Name; 574 end 575 % Find in the list of plugins 576 iPlug = find(strcmpi({PlugDesc.Name}, PlugName)); 577 if ~isempty(iPlug) 578 PlugDesc = PlugDesc(iPlug); 579 else 580 PlugDesc = []; 581 end 582 end 583 end 584 585 586 %% ===== PLUGIN STRUCT ===== 587 function s = GetStruct(PlugName) 588 s = db_template('PlugDesc'); 589 s.Name = PlugName; 590 end 591 592 593 %% ===== CONFIGURE PLUGIN ===== 594 function Configure(PlugDesc) 595 switch (PlugDesc.Name) 596 case 'mff' 597 % Add .jar file to static classpath 598 if ~exist('com.egi.services.mff.api.MFFFactory', 'class') 599 jarList = dir(bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, 'MFF-*.jar')); 600 jarPath = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, jarList(1).name); 601 disp(['BST> Adding to Java classpath: ' jarPath]); 602 warning off 603 javaaddpathstatic(jarPath); 604 javaaddpath(jarPath); 605 warning on 606 end 607 608 case 'nwb' 609 % Add .jar file to static classpath 610 if ~exist('Schema', 'class') 611 jarPath = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, 'jar', 'schema.jar'); 612 disp(['BST> Adding to Java classpath: ' jarPath]); 613 warning off 614 javaaddpathstatic(jarPath); 615 javaaddpath(jarPath); 616 warning on 617 schema = Schema(); 618 end 619 % Go to NWB folder 620 curDir = pwd; 621 cd(bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder)); 622 % Generate the NWB Schema (must be executed from the NWB folder) 623 generateCore(); 624 % Restore current directory 625 cd(curDir); 626 end 627 end 628 629 630 %% ===== GET ONLINE VERSION ===== 631 % Get the latest online version of some plugins 632 function [Version, URLzip] = GetVersionOnline(PlugName, URLzip, isCache) 633 global GlobalData; 634 Version = []; 635 % Parse inputs 636 if (nargin < 2) || isempty(URLzip) 637 URLzip = []; 638 end 639 % Use cache by default, to avoid fetching online too many times the same info 640 if (nargin < 3) || isempty(isCache) 641 isCache = 1; 642 end 643 % No internet: skip 644 if ~GlobalData.Program.isInternet 645 return; 646 end 647 % Check for existing plugin cache 648 strCache = [PlugName, '_online_', strrep(date,'-','')]; 649 if isCache && isfield(GlobalData.Program.PluginCache, strCache) && isfield(GlobalData.Program.PluginCache.(strCache), 'Version') 650 Version = GlobalData.Program.PluginCache.(strCache).Version; 651 URLzip = GlobalData.Program.PluginCache.(strCache).URLzip; 652 return; 653 end 654 % Get version online 655 try 656 switch (PlugName) 657 case 'spm12' 658 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 659 disp(['BST> Checking latest online version for ' PlugName '...']); 660 s = bst_webread('http://www.fil.ion.ucl.ac.uk/spm/download/spm12_updates/'); 661 if ~isempty(s) 662 n = regexp(s,'spm12_updates_r(\d.*?)\.zip','tokens','once'); 663 if ~isempty(n) && ~isempty(n{1}) 664 Version = n{1}; 665 end 666 end 667 case 'cat12' 668 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 669 disp(['BST> Checking latest online version for ' PlugName '...']); 670 s = bst_webread('http://www.neuro.uni-jena.de/cat12/'); 671 if ~isempty(s) 672 n = regexp(s,'cat12_r(\d.*?)\.zip','tokens'); 673 if ~isempty(n) 674 Version = max(cellfun(@str2double, [n{:}])); 675 Version = num2str(Version); 676 end 677 end 678 case 'fieldtrip' 679 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 680 disp(['BST> Checking latest online version for ' PlugName '...']); 681 s = bst_webread('https://download.fieldtriptoolbox.org'); 682 if ~isempty(s) 683 n = regexp(s,'fieldtrip-lite-(\d.*?)\.zip','tokens'); 684 if ~isempty(n) 685 Version = max(cellfun(@str2double, [n{:}])); 686 Version = num2str(Version); 687 URLzip = ['https://download.fieldtriptoolbox.org/fieldtrip-lite-' Version '.zip']; 688 end 689 end 690 case 'duneuro' 691 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 692 disp(['BST> Checking latest online version for ' PlugName '...']); 693 str = bst_webread('http://neuroimage.usc.edu/bst/getversion_duneuro.php'); 694 Version = str(1:6); 695 case 'nirstorm' 696 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 697 disp(['BST> Checking latest online version for ' PlugName '...']); 698 str = bst_webread('https://raw.githubusercontent.com/Nirstorm/nirstorm/master/bst_plugin/VERSION'); 699 Version = strtrim(str(9:end)); 700 otherwise 701 % If downloading from github: Get last GitHub commit SHA 702 if isGithubMaster(URLzip) 703 Version = GetGithubCommit(URLzip); 704 else 705 return; 706 end 707 end 708 % Executed only if the version was fetched successfully: Keep cached version 709 GlobalData.Program.PluginCache.(strCache).Version = Version; 710 GlobalData.Program.PluginCache.(strCache).URLzip = URLzip; 711 catch 712 disp(['BST> Error: Could not get online version for plugin: ' PlugName]); 713 end 714 end 715 716 717 %% ===== IS GITHUB MASTER ====== 718 % Returns 1 if the URL is a github master/main branch 719 function isMaster = isGithubMaster(URLzip) 720 isMaster = ~isempty(strfind(URLzip, 'https://github.com/')) && (~isempty(strfind(URLzip, 'master.zip')) || ~isempty(strfind(URLzip, 'main.zip'))); 721 end 722 723 724 %% ===== GET GITHUB COMMIT ===== 725 % Get SHA of the GitHub HEAD commit 726 function sha = GetGithubCommit(URLzip) 727 % Default result 728 sha = 'github-master'; 729 % Only available after Matlab 2016b (because of matlab.net.http.RequestMessage) 730 if (bst_get('MatlabVersion') < 901) 731 return; 732 end 733 % Try getting the SHA from the GitHub API 734 try 735 % Get GitHub repository path 736 zipUri = matlab.net.URI(URLzip); 737 gitUser = char(zipUri.Path(2)); 738 gitRepo = char(zipUri.Path(3)); 739 % Request last commit SHA with GitHub API 740 apiUri = matlab.net.URI(['https://api.github.com/repos/' gitUser '/' gitRepo '/commits/master']); 741 request = matlab.net.http.RequestMessage; 742 request = request.addFields(matlab.net.http.HeaderField('Accept', 'application/vnd.github.VERSION.sha')); 743 r = send(request, apiUri); 744 sha = char(r.Body.Data); 745 catch 746 disp(['BST> Warning: Could not get GitHub version for URL: ' zipUrl]); 747 end 748 end 749 750 751 %% ===== COMPARE VERSIONS ===== 752 % Returns: 0: v1==v2 753 % -1: v1<v2 754 % 1: v1>v2 755 function res = CompareVersions(v1, v2) 756 % Get numbers 757 iNum1 = find(ismember(v1, '0123456789')); 758 iNum2 = find(ismember(v2, '0123456789')); 759 iDot1 = find(v1 == '.'); 760 iDot2 = find(v2 == '.'); 761 % Equality (or one input empty) 762 if isequal(v1,v2) || isempty(v1) || isempty(v2) 763 res = 0; 764 % Only numbers 765 elseif (length(iNum1) == length(v1)) && (length(iNum2) == length(v2)) 766 n1 = str2double(v1); 767 n2 = str2double(v2); 768 if (n1 > n2) 769 res = 1; 770 elseif (n1 < n2) 771 res = -1; 772 else 773 res = 0; 774 end 775 % Format '1.2.3' 776 elseif (~isempty(iDot1) || ~isempty(iDot2)) && ~isempty(iNum1) && ~isempty(iNum2) 777 % Get subversions 1 778 split1 = str_split(v1, '.'); 779 sub1 = []; 780 for i = 1:length(split1) 781 t = str2num(split1{i}(ismember(split1{i},'0123456789'))); 782 if ~isempty(t) 783 sub1(end+1) = t; 784 else 785 break; 786 end 787 end 788 % Get subversions 1 789 split2 = str_split(v2, '.'); 790 sub2 = []; 791 for i = 1:length(split2) 792 t = str2num(split2{i}(ismember(split2{i},'0123456789'))); 793 if ~isempty(t) 794 sub2(end+1) = t; 795 else 796 break; 797 end 798 end 799 % Add extra zeros to the shortest (so that "1.2" is higher than "1") 800 if (length(sub1) < length(sub2)) 801 tmp = sub1; 802 sub1 = zeros(size(sub2)); 803 sub1(1:length(tmp)) = tmp; 804 elseif (length(sub1) > length(sub2)) 805 tmp = sub2; 806 sub2 = zeros(size(sub1)); 807 sub2(1:length(tmp)) = tmp; 808 end 809 % Compare number by number 810 for i = 1:length(sub1) 811 if (sub1(i) > sub2(i)) 812 res = 1; 813 return; 814 elseif (sub1(i) < sub2(i)) 815 res = -1; 816 return; 817 else 818 res = 0; 819 end 820 end 821 % Mixture of numbers and digits: natural sorting of strings 822 else 823 [s,I] = sort_nat({v1, v2}); 824 if (I(1) == 1) 825 res = -1; 826 else 827 res = 1; 828 end 829 end 830 end 831 832 833 %% ===== EXECUTE CALLBACK ===== 834 function [isOk, errMsg] = ExecuteCallback(PlugDesc, f) 835 isOk = 0; 836 errMsg = ''; 837 if ~isempty(PlugDesc.(f)) 838 try 839 if ischar(PlugDesc.(f)) 840 disp(['BST> Executing callback ' f ': ' PlugDesc.(f)]); 841 eval(PlugDesc.(f)); 842 elseif isa(PlugDesc.(f), 'function_handle') 843 disp(['BST> Executing callback ' f ': ' func2str(PlugDesc.(f))]); 844 feval(PlugDesc.(f), PlugDesc); 845 end 846 catch 847 errMsg = ['Error executing callback ' f ': ' 10 lasterr]; 848 return; 849 end 850 end 851 isOk = 1; 852 end 853 854 855 %% ===== GET INSTALLED PLUGINS ===== 856 % USAGE: [PlugDesc, SearchPlugs] = bst_plugin('GetInstalled', PlugName/PlugDesc) % Get one installed plugin 857 % [PlugDesc, SearchPlugs] = bst_plugin('GetInstalled') % Get all installed plugins 858 function [PlugDesc, SearchPlugs] = GetInstalled(SelPlug) 859 % Parse inputs 860 if (nargin < 1) || isempty(SelPlug) 861 SelPlug = []; 862 end 863 864 % === DEFINE SEARCH LIST === 865 % Looking for a single plugin 866 if ~isempty(SelPlug) 867 SearchPlugs = GetSupported(SelPlug); 868 % Looking for all supported plugins 869 else 870 SearchPlugs = GetSupported(); 871 end 872 % Brainstorm plugin folder 873 UserPluginsDir = bst_get('UserPluginsDir'); 874 % Custom plugin paths 875 PluginCustomPath = bst_get('PluginCustomPath'); 876 % Matlab path 877 matlabPath = str_split(path, pathsep); 878 % Compiled distribution 879 isCompiled = bst_iscompiled(); 880 881 % === LOOK FOR SUPPORTED PLUGINS === 882 % Empty plugin structure 883 PlugDesc = repmat(db_template('PlugDesc'), 0); 884 % Look for each plugin in the search list 885 for iSearch = 1:length(SearchPlugs) 886 % Compiled: skip plugins that are not available 887 if isCompiled && (SearchPlugs(iSearch).CompiledStatus == 0) 888 continue; 889 end 890 % Theoretical plugin path 891 PlugName = SearchPlugs(iSearch).Name; 892 PlugPath = bst_fullfile(UserPluginsDir, PlugName); 893 % Check if test function is available in the Matlab path 894 TestFilePath = GetTestFilePath(SearchPlugs(iSearch)); 895 % If installed software found in Matlab path 896 if ~isempty(TestFilePath) 897 % Register loaded plugin 898 iPlug = length(PlugDesc) + 1; 899 PlugDesc(iPlug) = SearchPlugs(iSearch); 900 PlugDesc(iPlug).isLoaded = 1; 901 % Check if the file is inside the Brainstorm user folder (where it is supposed to be) => Managed plugin 902 if ~isempty(strfind(TestFilePath, PlugPath)) 903 PlugDesc(iPlug).isManaged = 1; 904 % Process compiled together with Brainstorm 905 elseif isCompiled && ~isempty(strfind(TestFilePath, ['.brainstorm' filesep 'plugins' filesep PlugName])) 906 compiledDir = ['.brainstorm' filesep 'plugins' filesep PlugName]; 907 iPath = strfind(TestFilePath, compiledDir); 908 PlugPath = [TestFilePath(1:iPath-2), filesep, compiledDir]; 909 % Otherwise: Custom installation 910 else 911 % If the test file was found in a defined subfolder: remove the subfolder from the plugin path 912 PlugPath = TestFilePath; 913 for iSub = 1:length(PlugDesc(iPlug).LoadFolders) 914 subDir = strrep(PlugDesc(iPlug).LoadFolders{iSub}, '/', filesep); 915 if (length(PlugPath) > length(subDir)) && isequal(PlugPath(end-length(subDir)+1:end), subDir) 916 PlugPath = PlugPath(1:end - length(subDir) - 1); 917 break; 918 end 919 end 920 PlugDesc(iPlug).isManaged = 0; 921 end 922 PlugDesc(iPlug).Path = PlugPath; 923 % Plugin installed: Managed by Brainstorm 924 elseif isdir(PlugPath) && file_exist(bst_fullfile(PlugPath, 'plugin.mat')) 925 iPlug = length(PlugDesc) + 1; 926 PlugDesc(iPlug) = SearchPlugs(iSearch); 927 PlugDesc(iPlug).Path = PlugPath; 928 PlugDesc(iPlug).isLoaded = 0; 929 PlugDesc(iPlug).isManaged = 1; 930 % Plugin installed: Custom path 931 elseif isfield(PluginCustomPath, PlugName) && ~isempty(PluginCustomPath.(PlugName)) && file_exist(PluginCustomPath.(PlugName)) 932 iPlug = length(PlugDesc) + 1; 933 PlugDesc(iPlug) = SearchPlugs(iSearch); 934 PlugDesc(iPlug).Path = PluginCustomPath.(PlugName); 935 PlugDesc(iPlug).isLoaded = 0; 936 PlugDesc(iPlug).isManaged = 0; 937 end 938 end 939 940 % === LOOK FOR UNREFERENCED PLUGINS === 941 % Compiled: do not look for unreferenced plugins 942 if isCompiled 943 PlugList = []; 944 % Get a specific unlisted plugin 945 elseif ~isempty(SelPlug) 946 % Get plugin name 947 if ischar(SelPlug) 948 PlugName = lower(SelPlug); 949 else 950 PlugName = SelPlug.Name; 951 end 952 % If plugin is already referenced: skip 953 if ismember(PlugName, {PlugDesc.Name}) 954 PlugList = []; 955 % Else: Try to get target plugin as unreferenced 956 else 957 PlugList = struct('name', PlugName); 958 end 959 % Get all folders in Brainstorm plugins folder 960 else 961 PlugList = dir(UserPluginsDir); 962 end 963 % Process folders containing a plugin.mat file 964 for iDir = 1:length(PlugList) 965 % Ignore entry if plugin name is already in list of documented plugins 966 PlugName = PlugList(iDir).name; 967 if ismember(PlugName, {PlugDesc.Name}) 968 continue; 969 end 970 % Process only folders 971 PlugDir = bst_fullfile(UserPluginsDir, PlugName); 972 if ~isdir(PlugDir) || (PlugName(1) == '.') 973 continue; 974 end 975 % Process only folders containing a 'plugin.mat' file 976 PlugMatFile = bst_fullfile(PlugDir, 'plugin.mat'); 977 if ~file_exist(PlugMatFile) 978 continue; 979 end 980 % If selecting only one plugin 981 if ~isempty(SelPlug) && ischar(SelPlug) && ~strcmpi(PlugName, SelPlug) 982 continue; 983 end 984 % Add plugin to list 985 iPlug = length(PlugDesc) + 1; 986 PlugDesc(iPlug) = GetStruct(PlugList(iDir).name); 987 PlugDesc(iPlug).Path = PlugDir; 988 PlugDesc(iPlug).isManaged = 1; 989 PlugDesc(iPlug).isLoaded = ismember(PlugDir, matlabPath); 990 end 991 992 % === READ PLUGIN.MAT === 993 for iPlug = 1:length(PlugDesc) 994 % Try to load the plugin.mat file in the plugin folder 995 PlugMatFile = bst_fullfile(PlugDesc(iPlug).Path, 'plugin.mat'); 996 if file_exist(PlugMatFile) 997 try 998 PlugMat = load(PlugMatFile); 999 catch 1000 PlugMat = struct(); 1001 end 1002 % Copy fields 1003 excludedFields = {'Name', 'Path', 'isLoaded', 'isManaged', 'LoadedFcn', 'UnloadedFcn', 'DownloadedFcn', 'InstalledFcn', 'UninstalledFcn'}; 1004 loadFields = setdiff(fieldnames(db_template('PlugDesc')), excludedFields); 1005 for iField = 1:length(loadFields) 1006 if isfield(PlugMat, loadFields{iField}) && ~isempty(PlugMat.(loadFields{iField})) 1007 PlugDesc(iPlug).(loadFields{iField}) = PlugMat.(loadFields{iField}); 1008 end 1009 end 1010 else 1011 PlugDesc(iPlug).URLzip = []; 1012 end 1013 end 1014 end 1015 1016 1017 %% ===== GET DESCRIPTION ===== 1018 % USAGE: [PlugDesc, errMsg] = GetDescription(PlugName/PlugDesc) 1019 function [PlugDesc, errMsg] = GetDescription(PlugName) 1020 % Initialize returned values 1021 errMsg = ''; 1022 PlugDesc = []; 1023 % CALL: GetDescription(PlugDesc) 1024 if isstruct(PlugName) 1025 % Add the missing fields 1026 PlugDesc = struct_copy_fields(PlugName, db_template('PlugDesc'), 0); 1027 % CALL: GetDescription(PlugName) 1028 elseif ischar(PlugName) 1029 % Get supported plugins 1030 AllPlugs = GetSupported(); 1031 % Find plugin in supported plugins 1032 iPlug = find(strcmpi({AllPlugs.Name}, PlugName)); 1033 if isempty(iPlug) 1034 errMsg = ['Unknown plugin: ' PlugName]; 1035 return; 1036 end 1037 % Return found plugin 1038 PlugDesc = AllPlugs(iPlug); 1039 else 1040 errMsg = 'Invalid call to GetDescription().'; 1041 end 1042 end 1043 1044 1045 %% ===== GET TEST FILE PATH ===== 1046 function TestFilePath = GetTestFilePath(PlugDesc) 1047 % If a test file is defined 1048 if ~isempty(PlugDesc.TestFile) 1049 % Try to find the test function in the path 1050 whichTest = which(PlugDesc.TestFile); 1051 % If it was found: use the parent folder 1052 if ~isempty(whichTest) 1053 % Get the test file path 1054 TestFilePath = bst_fileparts(whichTest); 1055 % FieldTrip: Ignore if found embedded in SPM12 1056 if strcmpi(PlugDesc.Name, 'fieldtrip') 1057 p = which('spm.m'); 1058 if ~isempty(p) && ~isempty(strfind(TestFilePath, bst_fileparts(p))) 1059 TestFilePath = []; 1060 end 1061 % SPM12: Ignore if found embedded in ROAST 1062 elseif strcmpi(PlugDesc.Name, 'spm12') 1063 p = which('roast.m'); 1064 if ~isempty(p) && ~isempty(strfind(TestFilePath, bst_fileparts(p))) 1065 TestFilePath = []; 1066 end 1067 % Iso2mesh: Ignore if found embedded in ROAST 1068 elseif strcmpi(PlugDesc.Name, 'iso2mesh') 1069 p = which('roast.m'); 1070 if ~isempty(p) && ~isempty(strfind(TestFilePath, bst_fileparts(p))) 1071 TestFilePath = []; 1072 end 1073 end 1074 else 1075 TestFilePath = []; 1076 end 1077 else 1078 TestFilePath = []; 1079 end 1080 end 1081 1082 1083 %% ===== GET README FILE ==== 1084 % Get full path to the readme file 1085 function ReadmeFile = GetReadmeFile(PlugDesc) 1086 ReadmeFile = []; 1087 % If readme file is defined in the plugin structure 1088 if ~isempty(PlugDesc.ReadmeFile) 1089 % If full path already set: use it 1090 if file_exist(PlugDesc.ReadmeFile) 1091 ReadmeFile = PlugDesc.ReadmeFile; 1092 % Else: check in the plugin Path/SubFolder 1093 else 1094 tmpFile = bst_fullfile(PlugDesc.Path, PlugDesc.ReadmeFile); 1095 if file_exist(tmpFile) 1096 ReadmeFile = tmpFile; 1097 elseif ~isempty(PlugDesc.SubFolder) 1098 tmpFile = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, PlugDesc.ReadmeFile); 1099 if file_exist(tmpFile) 1100 ReadmeFile = tmpFile; 1101 end 1102 end 1103 end 1104 end 1105 % Search for default readme 1106 if isempty(ReadmeFile) 1107 tmpFile = bst_fullfile(bst_get('BrainstormDocDir'), 'plugins', [PlugDesc.Name '_readme.txt']); 1108 if file_exist(tmpFile) 1109 ReadmeFile = tmpFile; 1110 end 1111 end 1112 end 1113 1114 1115 %% ===== GET LOGO FILE ==== 1116 % Get full path to the logo file 1117 function LogoFile = GetLogoFile(PlugDesc) 1118 LogoFile = []; 1119 % If logo file is defined in the plugin structure 1120 if ~isempty(PlugDesc.LogoFile) 1121 % If full path already set: use it 1122 if file_exist(PlugDesc.LogoFile) 1123 LogoFile = PlugDesc.LogoFile; 1124 % Else: check in the plugin Path/SubFolder 1125 else 1126 tmpFile = bst_fullfile(PlugDesc.Path, PlugDesc.LogoFile); 1127 if file_exist(tmpFile) 1128 LogoFile = tmpFile; 1129 elseif ~isempty(PlugDesc.SubFolder) 1130 tmpFile = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, PlugDesc.LogoFile); 1131 if file_exist(tmpFile) 1132 LogoFile = tmpFile; 1133 end 1134 end 1135 end 1136 end 1137 % Search for default logo 1138 if isempty(LogoFile) 1139 tmpFile = bst_fullfile(bst_get('BrainstormDocDir'), 'plugins', [PlugDesc.Name '_logo.gif']); 1140 if file_exist(tmpFile) 1141 LogoFile = tmpFile; 1142 end 1143 end 1144 if isempty(LogoFile) 1145 tmpFile = bst_fullfile(bst_get('BrainstormDocDir'), 'plugins', [PlugDesc.Name '_logo.png']); 1146 if file_exist(tmpFile) 1147 LogoFile = tmpFile; 1148 end 1149 end 1150 end 1151 1152 1153 %% ===== INSTALL ===== 1154 % USAGE: [isOk, errMsg, PlugDesc] = bst_plugin('Install', PlugName, isInteractive=0, minVersion=[]) 1155 function [isOk, errMsg, PlugDesc] = Install(PlugName, isInteractive, minVersion) 1156 % Returned variables 1157 isOk = 0; 1158 % Parse inputs 1159 if (nargin < 3) || isempty(minVersion) 1160 minVersion = []; 1161 elseif isnumeric(minVersion) 1162 minVersion = num2str(minVersion); 1163 end 1164 if (nargin < 2) || isempty(isInteractive) 1165 isInteractive = 0; 1166 end 1167 if ~ischar(PlugName) 1168 errMsg = 'Invalid call to Install()'; 1169 PlugDesc = []; 1170 return; 1171 end 1172 % Get plugin structure from name 1173 [PlugDesc, errMsg] = GetDescription(PlugName); 1174 if ~isempty(errMsg) 1175 return; 1176 end 1177 % Check if there is a URL to download 1178 if isempty(PlugDesc.URLzip) 1179 errMsg = ['No download URL for ', bst_get('OsType', 0), ': ', PlugName '']; 1180 return; 1181 end 1182 % Compiled version 1183 isCompiled = bst_iscompiled(); 1184 if isCompiled && (PlugDesc.CompiledStatus == 0) 1185 errMsg = ['Plugin ', PlugName ' is not available in the compiled version of Brainstorm.']; 1186 return; 1187 end 1188 % Minimum Matlab version 1189 if ~isempty(PlugDesc.MinMatlabVer) && (PlugDesc.MinMatlabVer > 0) && (bst_get('MatlabVersion') < PlugDesc.MinMatlabVer) 1190 strMinVer = sprintf('%d.%d', ceil(PlugDesc.MinMatlabVer / 100), mod(PlugDesc.MinMatlabVer, 100)); 1191 errMsg = ['Plugin ', PlugName ' is not supported for versions of Matlab <= ' strMinVer]; 1192 return; 1193 end 1194 % Get online update (use existing cache) 1195 [newVersion, newURLzip] = GetVersionOnline(PlugName, PlugDesc.URLzip, 1); 1196 if ~isempty(newVersion) 1197 PlugDesc.Version = newVersion; 1198 end 1199 if ~isempty(newURLzip) 1200 PlugDesc.URLzip = newURLzip; 1201 end 1202 1203 % === PROCESS DEPENDENCIES === 1204 % Check required plugins 1205 if ~isempty(PlugDesc.RequiredPlugs) 1206 bst_progress('text', ['Processing dependencies for ' PlugName '...']); 1207 disp(['BST> Processing dependencies: ' PlugName ' requires: ' sprintf('%s ', PlugDesc.RequiredPlugs{:,1})]); 1208 % Get the list of plugins that need to be installed 1209 installPlugs = {}; 1210 installVer = {}; 1211 strInstall = ''; 1212 for iPlug = 1:size(PlugDesc.RequiredPlugs,1) 1213 PlugCheck = GetInstalled(PlugDesc.RequiredPlugs{iPlug,1}); 1214 % Plugin not install: Install it 1215 if isempty(PlugCheck) 1216 installPlugs{end+1} = PlugDesc.RequiredPlugs{iPlug,1}; 1217 installVer{end+1} = []; 1218 strInstall = [strInstall, '<B>' installPlugs{end} '</B> ']; 1219 % Plugin installed: check version 1220 elseif (size(PlugDesc.RequiredPlugs,2) == 2) 1221 minVerDep = PlugDesc.RequiredPlugs{iPlug,2}; 1222 if ~isempty(minVerDep) && (CompareVersions(minVerDep, PlugCheck.Version) > 0) 1223 installPlugs{end+1} = PlugDesc.RequiredPlugs{iPlug,1}; 1224 installVer{end+1} = PlugDesc.RequiredPlugs{iPlug,2}; 1225 strInstall = [strInstall, '<B>' installPlugs{end} '</B>(' installVer{end} ') ']; 1226 end 1227 end 1228 end 1229 % If there are plugins to install 1230 if ~isempty(installPlugs) 1231 if isInteractive 1232 java_dialog('msgbox', ['<HTML>Plugin <B>' PlugName '</B> requires: ' strInstall ... 1233 '<BR><BR>Brainstorm will now install these plugins.' 10 10], 'Plugin manager'); 1234 end 1235 for iPlug = 1:length(installPlugs) 1236 [isInstalled, errMsg] = Install(installPlugs{iPlug}, isInteractive, installPlugs{iPlug}); 1237 if ~isInstalled 1238 errMsg = ['Error processing dependency: ' PlugDesc.RequiredPlugs{iPlug,1} 10 errMsg]; 1239 return; 1240 end 1241 end 1242 end 1243 end 1244 1245 % === UPDATE: CHECK PREVIOUS INSTALL === 1246 % Check if installed 1247 OldPlugDesc = GetInstalled(PlugName); 1248 % If already installed 1249 if ~isempty(OldPlugDesc) 1250 % If the plugin is not managed by Brainstorm: do not check versions 1251 if ~OldPlugDesc.isManaged 1252 isUpdate = 0; 1253 % If the requested version is higher 1254 elseif ~isempty(minVersion) && (CompareVersions(minVersion, OldPlugDesc.Version) > 0) 1255 isUpdate = 1; 1256 strUpdate = ['the installed version is outdated.<BR>Minimum version required: <I>' minVersion '</I>']; 1257 % If an update is available and auto-updates are requested 1258 elseif (PlugDesc.AutoUpdate == 1) && bst_get('AutoUpdates') && ... % If updates are enabled 1259 ((isGithubMaster(PlugDesc.URLzip) && ~strcmpi(PlugDesc.Version, OldPlugDesc.Version)) || ... % GitHub-master: update if different commit SHA strings 1260 (~isGithubMaster(PlugDesc.URLzip) && (CompareVersions(PlugDesc.Version, OldPlugDesc.Version) > 0))) % Regular stable version: update if online version is newer 1261 isUpdate = 1; 1262 strUpdate = 'an update is available online.'; 1263 else 1264 isUpdate = 0; 1265 end 1266 % Update plugin 1267 if isUpdate 1268 % Compare versions 1269 strCompare = ['<FONT color="#707070">' ... 1270 'Old version :     <I>' OldPlugDesc.Version '</I><BR>' ... 1271 'New version :   <I>' PlugDesc.Version '</I></FONT><BR><BR>']; 1272 % Ask user for updating 1273 if isInteractive 1274 isConfirm = java_dialog('confirm', ... 1275 ['<HTML>Plugin <B>' PlugName '</B>: ' strUpdate '<BR>' ... 1276 'Download and install the latest version?<BR><BR>' strCompare], 'Plugin manager'); 1277 % If update not confirmed: simply load the existing plugin 1278 if ~isConfirm 1279 [isOk, errMsg, PlugDesc] = Load(PlugDesc); 1280 return; 1281 end 1282 end 1283 disp(['BST> Plugin ' PlugName ' is outdated and will be updated.']); 1284 % Uninstall existing plugin 1285 [isOk, errMsg] = Uninstall(PlugName, 0, 0); 1286 if ~isOk 1287 errMsg = ['An error occurred while updating plugin ' PlugName ':' 10 10 errMsg 10]; 1288 return; 1289 end 1290 1291 % No update: Load existing plugin and return 1292 else 1293 % Load plugin 1294 if ~OldPlugDesc.isLoaded 1295 [isLoaded, errMsg, PlugDesc] = Load(OldPlugDesc); 1296 if ~isLoaded 1297 errMsg = ['Could not load plugin ' PlugName ':' 10 errMsg]; 1298 return; 1299 end 1300 else 1301 disp(['BST> Plugin ' PlugName ' already loaded: ' OldPlugDesc.Path]); 1302 end 1303 % Return old plugin 1304 PlugDesc = OldPlugDesc; 1305 isOk = 1; 1306 return; 1307 end 1308 else 1309 % Get user confirmation 1310 if isInteractive 1311 if ~isempty(PlugDesc.Version) && ~isequal(PlugDesc.Version, 'github-master') && ~isequal(PlugDesc.Version, 'latest') 1312 strVer = ['<FONT color="#707070">Latest version: ' PlugDesc.Version '</FONT><BR><BR>']; 1313 else 1314 strVer = ''; 1315 end 1316 isConfirm = java_dialog('confirm', ... 1317 ['<HTML>Plugin <B>' PlugName '</B> is not installed on your computer.<BR>' ... 1318 '<B>Download</B> the latest version of ' PlugName ' now?<BR><BR>' ... 1319 strVer, ... 1320 '<FONT color="#707070">If this program is available on your computer,<BR>' ... 1321 'cancel this installation and use the menu: Plugins > <BR>' ... 1322 PlugName ' > Custom install > Set installation folder.</FONT><BR><BR>'], 'Plugin manager'); 1323 if ~isConfirm 1324 errMsg = 'Installation aborted by user.'; 1325 return; 1326 end 1327 end 1328 end 1329 1330 % === INSTALL PLUGIN === 1331 bst_progress('text', ['Installing plugin ' PlugName '...']); 1332 % Managed plugin folder 1333 PlugPath = bst_fullfile(bst_get('UserPluginsDir'), PlugName); 1334 % Delete existing folder 1335 if isdir(PlugPath) 1336 file_delete(PlugPath, 1, 3); 1337 end 1338 % Create folder 1339 if ~isdir(PlugPath) 1340 res = mkdir(PlugPath); 1341 if ~res 1342 errMsg = ['Error: Cannot create folder' 10 PlugPath]; 1343 return 1344 end 1345 end 1346 % Setting progressbar image 1347 LogoFile = GetLogoFile(PlugDesc); 1348 if ~isempty(LogoFile) 1349 bst_progress('setimage', LogoFile); 1350 end 1351 % Get package file format 1352 if strcmpi(PlugDesc.URLzip(end-3:end), '.zip') 1353 pkgFormat = 'zip'; 1354 elseif strcmpi(PlugDesc.URLzip(end-6:end), '.tar.gz') || strcmpi(PlugDesc.URLzip(end-3:end), '.tgz') 1355 pkgFormat = 'tgz'; 1356 else 1357 disp('BST> Could not guess file format, trying ZIP...'); 1358 pkgFormat = 'zip'; 1359 end 1360 % Download file 1361 pkgFile = bst_fullfile(PlugPath, ['plugin.' pkgFormat]); 1362 disp(['BST> Downloading URL : ' PlugDesc.URLzip]); 1363 disp(['BST> Saving to file : ' pkgFile]); 1364 errMsg = gui_brainstorm('DownloadFile', PlugDesc.URLzip, pkgFile, ['Download plugin: ' PlugName], LogoFile); 1365 % If file was not downloaded correctly 1366 if ~isempty(errMsg) 1367 errMsg = ['Impossible to download ' PlugName ' automatically:' 10 errMsg]; 1368 if ~isCompiled 1369 errMsg = [errMsg 10 10 ... 1370 'Alternative download solution:' 10 ... 1371 '1) Copy the URL below from the Matlab command window: ' 10 ... 1372 ' ' PlugDesc.URLzip 10 ... 1373 '2) Paste it in a web browser' 10 ... 1374 '3) Save the file and unzip it' 10 ... 1375 '4) Add to the Matlab path the folder containing ' PlugDesc.TestFile '.']; 1376 end 1377 bst_progress('removeimage'); 1378 return; 1379 end 1380 % Update progress bar 1381 bst_progress('text', ['Installing plugin: ' PlugName '...']); 1382 if ~isempty(LogoFile) 1383 bst_progress('setimage', LogoFile); 1384 end 1385 % Unzip file 1386 switch (pkgFormat) 1387 case 'zip' 1388 bst_unzip(pkgFile, PlugPath); 1389 case 'tgz' 1390 if ispc 1391 untar(pkgFile, PlugPath); 1392 else 1393 curdir = pwd; 1394 cd(PlugPath); 1395 system(['tar -xf ' pkgFile]); 1396 cd(curdir); 1397 end 1398 end 1399 file_delete(pkgFile, 1, 3); 1400 1401 % === SAVE PLUGIN.MAT === 1402 PlugDesc.Path = PlugPath; 1403 PlugMatFile = bst_fullfile(PlugDesc.Path, 'plugin.mat'); 1404 excludedFields = {'LoadedFcn', 'UnloadedFcn', 'DownloadedFcn', 'InstalledFcn', 'UninstalledFcn', 'Path', 'isLoaded', 'isManaged'}; 1405 PlugDescSave = rmfield(PlugDesc, excludedFields); 1406 bst_save(PlugMatFile, PlugDescSave, 'v6'); 1407 1408 % === CALLBACK: POST-DOWNLOADED === 1409 [isOk, errMsg] = ExecuteCallback(PlugDesc, 'DownloadedFcn'); 1410 if ~isOk 1411 return; 1412 end 1413 1414 % === LOAD PLUGIN === 1415 % Load plugin 1416 [isOk, errMsg, PlugDesc] = Load(PlugDesc); 1417 if ~isOk 1418 bst_progress('removeimage'); 1419 return; 1420 end 1421 1422 % === DELETE UNWANTED FILES === 1423 if ~isempty(PlugDesc.DeleteFiles) && iscell(PlugDesc.DeleteFiles) 1424 warning('off', 'MATLAB:RMDIR:RemovedFromPath'); 1425 for iDel = 1:length(PlugDesc.DeleteFiles) 1426 if ~isempty(PlugDesc.SubFolder) 1427 fileDel = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, PlugDesc.DeleteFiles{iDel}); 1428 else 1429 fileDel = bst_fullfile(PlugDesc.Path, PlugDesc.DeleteFiles{iDel}); 1430 end 1431 if file_exist(fileDel) 1432 try 1433 file_delete(fileDel, 1, 3); 1434 catch 1435 disp(['BST> Plugin ' PlugName ': Could not delete file: ' PlugDesc.DeleteFiles{iDel}]); 1436 end 1437 else 1438 disp(['BST> Plugin ' PlugName ': Missing file: ' PlugDesc.DeleteFiles{iDel}]); 1439 end 1440 end 1441 warning('on', 'MATLAB:RMDIR:RemovedFromPath'); 1442 end 1443 1444 % === SEARCH PROCESSES === 1445 % Look for process_* functions in the process folder 1446 PlugProc = file_find(PlugPath, 'process_*.m', Inf, 0); 1447 if ~isempty(PlugProc) 1448 % Remove absolute path: use only path relative to the plugin Path 1449 PlugDesc.Processes = cellfun(@(c)file_win2unix(strrep(c, [PlugPath, filesep], '')), PlugProc, 'UniformOutput', 0); 1450 end 1451 1452 % === SAVE PLUGIN.MAT === 1453 % Save installation date 1454 c = clock(); 1455 PlugDesc.InstallDate = datestr(datenum(c(1), c(2), c(3), c(4), c(5), c(6)), 'dd-mmm-yyyy HH:MM:SS'); 1456 % Get readme and logo 1457 PlugDesc.ReadmeFile = GetReadmeFile(PlugDesc); 1458 PlugDesc.LogoFile = GetLogoFile(PlugDesc); 1459 % Update plugin.mat after loading 1460 PlugDescSave = rmfield(PlugDesc, excludedFields); 1461 bst_save(PlugMatFile, PlugDescSave, 'v6'); 1462 1463 % === CALLBACK: POST-INSTALL === 1464 [isOk, errMsg] = ExecuteCallback(PlugDesc, 'InstalledFcn'); 1465 if ~isOk 1466 return; 1467 end 1468 1469 % === GET INSTALLED VERSION === 1470 % Get installed version 1471 if ~isempty(PlugDesc.GetVersionFcn) 1472 testVer = []; 1473 try 1474 if ischar(PlugDesc.GetVersionFcn) 1475 testVer = eval(PlugDesc.GetVersionFcn); 1476 elseif isa(PlugDesc.GetVersionFcn, 'function_handle') 1477 testVer = feval(PlugDesc.GetVersionFcn); 1478 end 1479 catch 1480 disp(['BST> Could not get installed version with callback: ' PlugDesc.GetVersionFcn]); 1481 end 1482 if ~isempty(testVer) 1483 PlugDesc.Version = testVer; 1484 % Update plugin.mat 1485 PlugDescSave.Version = testVer; 1486 bst_save(PlugMatFile, PlugDescSave, 'v6'); 1487 end 1488 end 1489 1490 % === SHOW PLUGIN INFO === 1491 % Log install 1492 bst_webread(['http://neuroimage.usc.edu/bst/pluglog.php?c=K8Yda7B&plugname=' PlugDesc.Name '&action=install']); 1493 % Show plugin information (interactive mode only) 1494 if isInteractive 1495 % Hide progress bar 1496 isProgress = bst_progress('isVisible'); 1497 if isProgress 1498 bst_progress('hide'); 1499 end 1500 % Message box: aknowledgements 1501 java_dialog('msgbox', ['<HTML>Plugin <B>' PlugName '</B> was sucessfully installed.<BR><BR>' ... 1502 'This software is not distributed by the Brainstorm developers.<BR>' ... 1503 'Please take a few minutes to read the license information,<BR>' ... 1504 'check the authors'' website and register online if recommended.<BR><BR>' ... 1505 '<B>Cite the authors</B> in your publications if you are using their software.<BR><BR>'], 'Plugin manager'); 1506 % Show the readme file 1507 if ~isempty(PlugDesc.ReadmeFile) 1508 view_text(PlugDesc.ReadmeFile, ['Installed plugin: ' PlugName], 1, 1); 1509 end 1510 % Open the website 1511 if ~isempty(PlugDesc.URLinfo) 1512 web(PlugDesc.URLinfo, '-browser') 1513 end 1514 % Restore progress bar 1515 if isProgress 1516 bst_progress('show'); 1517 end 1518 end 1519 % Remove logo 1520 bst_progress('removeimage'); 1521 % Return success 1522 isOk = 1; 1523 end 1524 1525 1526 %% ===== INSTALL INTERACTIVE ===== 1527 % USAGE: [isOk, errMsg, PlugDesc] = bst_plugin('InstallInteractive', PlugName) 1528 function [isOk, errMsg, PlugDesc] = InstallInteractive(PlugName) 1529 % Open progress bar 1530 isProgress = bst_progress('isVisible'); 1531 if ~isProgress 1532 bst_progress('start', 'Plugin manager', 'Initialization...'); 1533 end 1534 % Call silent function 1535 [isOk, errMsg, PlugDesc] = Install(PlugName, 1); 1536 % Handle errors 1537 if ~isOk 1538 bst_error(['Installation error:' 10 10 errMsg 10], 'Plugin manager', 0); 1539 elseif ~isempty(errMsg) 1540 java_dialog('msgbox', ['Installation message:' 10 10 errMsg 10], 'Plugin manager'); 1541 end 1542 % Close progress bar 1543 if ~isProgress 1544 bst_progress('stop'); 1545 end 1546 end 1547 1548 1549 %% ===== INSTALL MULTIPLE CHOICE ===== 1550 % If multiple plugins provide the same functions (eg. FieldTrip and SPM): make sure at least one is installed 1551 % USAGE: [isOk, errMsg, PlugDesc] = bst_plugin('InstallMultipleChoice', PlugNames, isInteractive) 1552 function [isOk, errMsg, PlugDesc] = InstallMultipleChoice(PlugNames, isInteractive) 1553 % Check if one of the plugins is loaded 1554 for iPlug = 1:length(PlugNames) 1555 PlugInst = GetInstalled(PlugNames{iPlug}); 1556 if ~isempty(PlugInst) 1557 [isOk, errMsg, PlugDesc] = Load(PlugNames{iPlug}); 1558 if isOk 1559 return; 1560 end 1561 end 1562 end 1563 % If no plugin is loaded: Install the first in the list 1564 [isOk, errMsg, PlugDesc] = Install(PlugNames{1}, isInteractive); 1565 end 1566 1567 1568 %% ===== UNINSTALL ===== 1569 % USAGE: [isOk, errMsg] = bst_plugin('Uninstall', PlugName, isInteractive=0, isDependencies=1) 1570 function [isOk, errMsg] = Uninstall(PlugName, isInteractive, isDependencies) 1571 % Returned variables 1572 isOk = 0; 1573 errMsg = ''; 1574 % Parse inputs 1575 if (nargin < 3) || isempty(isDependencies) 1576 isDependencies = 1; 1577 end 1578 if (nargin < 2) || isempty(isInteractive) 1579 isInteractive = 0; 1580 end 1581 if ~ischar(PlugName) 1582 errMsg = 'Invalid call to Uninstall()'; 1583 return; 1584 end 1585 1586 % === CHECK INSTALLATION === 1587 % Get installation 1588 PlugDesc = GetInstalled(PlugName); 1589 % External plugin 1590 if ~isempty(PlugDesc) && ~isequal(PlugDesc.isManaged, 1) 1591 errMsg = ['<HTML>Plugin <B>' PlugName '</B> is not managed by Brainstorm.' 10 'Delete folder manually:' 10 PlugDesc.Path]; 1592 return; 1593 % Plugin not installed: check if folder exists 1594 elseif isempty(PlugDesc) || isempty(PlugDesc.Path) 1595 % Get plugin structure from name 1596 [PlugDesc, errMsg] = GetDescription(PlugName); 1597 if ~isempty(errMsg) 1598 return; 1599 end 1600 % Managed plugin folder 1601 PlugPath = bst_fullfile(bst_get('UserPluginsDir'), PlugName); 1602 else 1603 PlugPath = PlugDesc.Path; 1604 end 1605 % Plugin not installed 1606 if ~file_exist(PlugPath) 1607 errMsg = ['Plugin ' PlugName ' is not installed.']; 1608 return; 1609 end 1610 1611 % === USER CONFIRMATION === 1612 if isInteractive 1613 isConfirm = java_dialog('confirm', ['<HTML>Delete permanently plugin <B>' PlugName '</B>?' 10 10 PlugPath 10 10], 'Plugin manager'); 1614 if ~isConfirm 1615 errMsg = 'Uninstall aborted by user.'; 1616 return; 1617 end 1618 end 1619 1620 % === PROCESS DEPENDENCIES === 1621 % Uninstall dependent plugins 1622 if isDependencies 1623 AllPlugs = GetSupported(); 1624 for iPlug = 1:length(AllPlugs) 1625 if ~isempty(AllPlugs(iPlug).RequiredPlugs) && ismember(PlugDesc.Name, AllPlugs(iPlug).RequiredPlugs(:,1)) 1626 disp(['BST> Uninstalling dependent plugin: ' AllPlugs(iPlug).Name]); 1627 Uninstall(AllPlugs(iPlug).Name, isInteractive); 1628 end 1629 end 1630 end 1631 1632 % === UNLOAD === 1633 if isequal(PlugDesc.isLoaded, 1) 1634 [isUnloaded, errMsgUnload] = Unload(PlugDesc); 1635 if ~isempty(errMsgUnload) 1636 disp(['BST> Error unloading plugin ' PlugName ': ' errMsgUnload]); 1637 end 1638 end 1639 1640 % === UNINSTALL === 1641 disp(['BST> Deleting plugin ' PlugName ': ' PlugPath]); 1642 % Delete plugin folder 1643 isDeleted = file_delete(PlugPath, 1, 3); 1644 if (isDeleted ~= 1) 1645 errMsg = ['Could not delete plugin folder: ' 10 PlugPath 10 10 ... 1646 'There is probably a file in that folder that is currently ' 10 ... 1647 'loaded in Matlab, but that cannot be unloaded dynamically.' 10 10 ... 1648 'Brainstorm will now close Matlab.' 10 ... 1649 'Restart Matlab and install again the plugin.' 10 10]; 1650 if isInteractive 1651 java_dialog('error', errMsg, 'Restart Matlab'); 1652 else 1653 disp([10 10 'BST> ' errMsg]); 1654 end 1655 quit('force'); 1656 end 1657 1658 % === CALLBACK: POST-UNINSTALL === 1659 [isOk, errMsg] = ExecuteCallback(PlugDesc, 'UninstalledFcn'); 1660 if ~isOk 1661 return; 1662 end 1663 1664 % Return success 1665 isOk = 1; 1666 end 1667 1668 1669 %% ===== UNINSTALL INTERACTIVE ===== 1670 % USAGE: [isOk, errMsg] = bst_plugin('UninstallInteractive', PlugName) 1671 function [isOk, errMsg] = UninstallInteractive(PlugName) 1672 % Open progress bar 1673 isProgress = bst_progress('isVisible'); 1674 if ~isProgress 1675 bst_progress('start', 'Plugin manager', 'Initialization...'); 1676 end 1677 % Call silent function 1678 [isOk, errMsg] = Uninstall(PlugName, 1); 1679 % Handle errors 1680 if ~isOk 1681 bst_error(['An error occurred while uninstalling plugin ' PlugName ':' 10 10 errMsg 10], 'Plugin manager', 0); 1682 elseif ~isempty(errMsg) 1683 java_dialog('msgbox', ['Uninstall message:' 10 10 errMsg 10], 'Plugin manager'); 1684 end 1685 % Close progress bar 1686 if ~isProgress 1687 bst_progress('stop'); 1688 end 1689 end 1690 1691 1692 %% ===== UPDATE INTERACTIVE ===== 1693 % USAGE: [isOk, errMsg] = bst_plugin('UpdateInteractive', PlugName) 1694 function [isOk, errMsg] = UpdateInteractive(PlugName) 1695 % Open progress bar 1696 isProgress = bst_progress('isVisible'); 1697 if ~isProgress 1698 bst_progress('start', 'Plugin manager', 'Initialization...'); 1699 end 1700 % Get new plugin 1701 [PlugRef, errMsg] = GetDescription(PlugName); 1702 isOk = isempty(errMsg); 1703 % Get installed plugin 1704 if isOk 1705 PlugInst = GetInstalled(PlugName); 1706 if isempty(PlugInst) || ~PlugInst.isManaged 1707 isOk = 0; 1708 errMsg = ['Plugin ' PlugName ' is not installed or not managed by Brainstorm.']; 1709 end 1710 end 1711 % Get online update (use cache when available) 1712 [newVersion, newURLzip] = GetVersionOnline(PlugName, PlugRef.URLzip, 1); 1713 if ~isempty(newVersion) 1714 PlugRef.Version = newVersion; 1715 end 1716 if ~isempty(newURLzip) 1717 PlugRef.URLzip = newURLzip; 1718 end 1719 % User confirmation 1720 if isOk 1721 isOk = java_dialog('confirm', ['<HTML>Update plugin <B>' PlugName '</B> ?<BR><BR><FONT color="#707070">' ... 1722 'Old version :     <I>' PlugInst.Version '</I><BR>' ... 1723 'New version :   <I>' PlugRef.Version '</I><BR><BR></FONT>'], 'Plugin manager'); 1724 if ~isOk 1725 errMsg = 'Update aborted by user.'; 1726 end 1727 end 1728 % Uninstall old 1729 if isOk 1730 [isOk, errMsg] = Uninstall(PlugName, 0, 0); 1731 end 1732 % Install new 1733 if isOk 1734 [isOk, errMsg, PlugDesc] = Install(PlugName, 0); 1735 else 1736 PlugDesc = []; 1737 end 1738 % Handle errors 1739 if ~isOk 1740 bst_error(['An error occurred while updating plugin ' PlugName ':' 10 10 errMsg 10], 'Plugin manager', 0); 1741 elseif ~isempty(errMsg) 1742 java_dialog('msgbox', ['Update message:' 10 10 errMsg 10], 'Plugin manager'); 1743 end 1744 % Close progress bar 1745 if ~isProgress 1746 bst_progress('stop'); 1747 end 1748 % Plugin was updated successfully 1749 if ~isempty(PlugDesc) 1750 % Show the readme file 1751 if ~isempty(PlugDesc.ReadmeFile) 1752 view_text(PlugDesc.ReadmeFile, ['Installed plugin: ' PlugName], 1, 1); 1753 end 1754 % Open the website 1755 if ~isempty(PlugDesc.URLinfo) 1756 web(PlugDesc.URLinfo, '-browser') 1757 end 1758 end 1759 end 1760 1761 1762 %% ===== LOAD ===== 1763 % USAGE: [isOk, errMsg, PlugDesc] = Load(PlugDesc, isVerbose=1) 1764 function [isOk, errMsg, PlugDesc] = Load(PlugDesc, isVerbose) 1765 % Parse inputs 1766 if (nargin < 2) || isempty(isVerbose) 1767 isVerbose = 1; 1768 end 1769 % Initialize returned variables 1770 isOk = 0; 1771 % Get plugin structure from name 1772 [PlugDesc, errMsg] = GetDescription(PlugDesc); 1773 if ~isempty(errMsg) 1774 return; 1775 end 1776 % Minimum Matlab version 1777 if ~isempty(PlugDesc.MinMatlabVer) && (PlugDesc.MinMatlabVer > 0) && (bst_get('MatlabVersion') < PlugDesc.MinMatlabVer) 1778 strMinVer = sprintf('%d.%d', ceil(PlugDesc.MinMatlabVer / 100), mod(PlugDesc.MinMatlabVer, 100)); 1779 errMsg = ['Plugin ', PlugDesc.Name ' is not supported for versions of Matlab <= ' strMinVer]; 1780 return; 1781 end 1782 1783 % === ALREADY LOADED === 1784 % If plugin is already full loaded 1785 if isequal(PlugDesc.isLoaded, 1) && ~isempty(PlugDesc.Path) 1786 if isVerbose 1787 errMsg = ['Plugin ' PlugDesc.Name ' already loaded: ' PlugDesc.Path]; 1788 end 1789 return; 1790 end 1791 % Managed plugin path 1792 PlugPath = bst_fullfile(bst_get('UserPluginsDir'), PlugDesc.Name); 1793 if file_exist(PlugPath) 1794 PlugDesc.isManaged = 1; 1795 % Custom installation 1796 else 1797 PluginCustomPath = bst_get('PluginCustomPath'); 1798 if isfield(PluginCustomPath, PlugDesc.Name) && ~isempty(bst_fullfile(PluginCustomPath.(PlugDesc.Name))) && file_exist(bst_fullfile(PluginCustomPath.(PlugDesc.Name))) 1799 PlugPath = PluginCustomPath.(PlugDesc.Name); 1800 end 1801 PlugDesc.isManaged = 0; 1802 end 1803 % Managed install: Detect if there is a single subfolder containing all the files 1804 if PlugDesc.isManaged && ~isempty(PlugDesc.TestFile) && ~file_exist(bst_fullfile(PlugPath, PlugDesc.TestFile)) 1805 dirList = dir(PlugPath); 1806 for iDir = 1:length(dirList) 1807 % Not folder or . : skip 1808 if (dirList(iDir).name(1) == '.') || ~dirList(iDir).isdir 1809 continue; 1810 end 1811 % Check if test file is in the folder 1812 if file_exist(bst_fullfile(PlugPath, dirList(iDir).name, PlugDesc.TestFile)) 1813 PlugDesc.SubFolder = dirList(iDir).name; 1814 break; 1815 % Otherwise, check in any of the subfolders 1816 elseif ~isempty(PlugDesc.LoadFolders) 1817 for iSubDir = 1:length(PlugDesc.LoadFolders) 1818 if file_exist(bst_fullfile(PlugPath, dirList(iDir).name, PlugDesc.LoadFolders{iSubDir}, PlugDesc.TestFile)) 1819 PlugDesc.SubFolder = dirList(iDir).name; 1820 break; 1821 end 1822 end 1823 end 1824 end 1825 end 1826 % Check if test function already available in the path 1827 TestFilePath = GetTestFilePath(PlugDesc); 1828 if ~isempty(TestFilePath) 1829 PlugDesc.isLoaded = 1; 1830 PlugDesc.isManaged = ~isempty(strfind(which(PlugDesc.TestFile), PlugPath)); 1831 if PlugDesc.isManaged 1832 PlugDesc.Path = PlugPath; 1833 else 1834 PlugDesc.Path = TestFilePath; 1835 end 1836 if isVerbose 1837 disp(['BST> Plugin ' PlugDesc.Name ' already loaded: ' PlugDesc.Path]); 1838 end 1839 isOk = 1; 1840 return; 1841 end 1842 1843 % === CHECK LOADABILITY === 1844 PlugDesc.Path = PlugPath; 1845 if ~file_exist(PlugDesc.Path) 1846 errMsg = ['Plugin ' PlugDesc.Name ' not installed.' 10 'Missing folder: ' PlugDesc.Path]; 1847 return; 1848 end 1849 % Set logo 1850 LogoFile = GetLogoFile(PlugDesc); 1851 if ~isempty(LogoFile) 1852 bst_progress('setimage', LogoFile); 1853 end 1854 1855 % === PROCESS DEPENDENCIES === 1856 % Unload incompatible plugins 1857 if ~isempty(PlugDesc.UnloadPlugs) 1858 for iPlug = 1:length(PlugDesc.UnloadPlugs) 1859 % disp(['BST> Unloading incompatible plugin: ' PlugDesc.UnloadPlugs{iPlug}]); 1860 Unload(PlugDesc.UnloadPlugs{iPlug}, isVerbose); 1861 end 1862 end 1863 % Load required plugins 1864 if ~isempty(PlugDesc.RequiredPlugs) 1865 for iPlug = 1:size(PlugDesc.RequiredPlugs,1) 1866 % disp(['BST> Loading required plugin: ' PlugDesc.RequiredPlugs{iPlug,1}]); 1867 [isOk, errMsg] = Load(PlugDesc.RequiredPlugs{iPlug,1}, isVerbose); 1868 if ~isOk 1869 errMsg = ['Error processing dependencies: ', PlugDesc.Name, 10, errMsg]; 1870 bst_progress('removeimage'); 1871 return; 1872 end 1873 end 1874 end 1875 1876 % === LOAD PLUGIN === 1877 % Add plugin folder to path 1878 if ~isempty(PlugDesc.SubFolder) 1879 PlugHomeDir = bst_fullfile(PlugPath, PlugDesc.SubFolder); 1880 else 1881 PlugHomeDir = PlugPath; 1882 end 1883 % Do not modify path in compiled mode 1884 isCompiled = bst_iscompiled(); 1885 if ~isCompiled 1886 addpath(PlugHomeDir); 1887 if isVerbose 1888 disp(['BST> Adding plugin ' PlugDesc.Name ' to path: ' PlugHomeDir]); 1889 end 1890 % Add specific subfolders to path 1891 if ~isempty(PlugDesc.LoadFolders) 1892 % Load all all subfolders 1893 if isequal(PlugDesc.LoadFolders, '*') || isequal(PlugDesc.LoadFolders, {'*'}) 1894 if isVerbose 1895 disp(['BST> Adding plugin ' PlugDesc.Name ' to path: ', PlugHomeDir, filesep, '*']); 1896 end 1897 addpath(genpath(PlugHomeDir)); 1898 % Load specific subfolders 1899 else 1900 for i = 1:length(PlugDesc.LoadFolders) 1901 subDir = PlugDesc.LoadFolders{i}; 1902 if isequal(filesep, '\') 1903 subDir = strrep(subDir, '/', '\'); 1904 end 1905 if isdir([PlugHomeDir, filesep, subDir]) 1906 if isVerbose 1907 disp(['BST> Adding plugin ' PlugDesc.Name ' to path: ', PlugHomeDir, filesep, subDir]); 1908 end 1909 addpath([PlugHomeDir, filesep, subDir]); 1910 end 1911 end 1912 end 1913 end 1914 end 1915 1916 % === TEST FUNCTION === 1917 % Check if test function is available on path 1918 if ~isCompiled && ~isempty(PlugDesc.TestFile) && (exist(PlugDesc.TestFile, 'file') == 0) 1919 errMsg = ['Plugin ' PlugDesc.Name ' successfully loaded from:' 10 PlugHomeDir 10 10 ... 1920 'However, the function ' PlugDesc.TestFile ' is not accessible in the Matlab path.' 10 ... 1921 'Try restarting Matlab and Brainstorm.']; 1922 bst_progress('removeimage') 1923 return; 1924 end 1925 1926 % === CALLBACK: POST-LOAD === 1927 [isOk, errMsg] = ExecuteCallback(PlugDesc, 'LoadedFcn'); 1928 1929 % Remove logo 1930 bst_progress('removeimage'); 1931 % Return success 1932 PlugDesc.isLoaded = isOk; 1933 end 1934 1935 1936 %% ===== LOAD INTERACTIVE ===== 1937 % USAGE: [isOk, errMsg, PlugDesc] = LoadInteractive(PlugName/PlugDesc) 1938 function [isOk, errMsg, PlugDesc] = LoadInteractive(PlugDesc) 1939 % Open progress bar 1940 isProgress = bst_progress('isVisible'); 1941 if ~isProgress 1942 bst_progress('start', 'Plugin manager', 'Loading plugin...'); 1943 end 1944 % Call silent function 1945 [isOk, errMsg, PlugDesc] = Load(PlugDesc); 1946 % Handle errors 1947 if ~isOk 1948 bst_error(['Load error:' 10 10 errMsg 10], 'Plugin manager', 0); 1949 elseif ~isempty(errMsg) 1950 java_dialog('msgbox', ['Load message:' 10 10 errMsg 10], 'Plugin manager'); 1951 end 1952 % Close progress bar 1953 if ~isProgress 1954 bst_progress('stop'); 1955 end 1956 end 1957 1958 1959 %% ===== UNLOAD ===== 1960 % USAGE: [isOk, errMsg, PlugDesc] = Unload(PlugName/PlugDesc, isVerbose) 1961 function [isOk, errMsg, PlugDesc] = Unload(PlugDesc, isVerbose) 1962 % Parse inputs 1963 if (nargin < 2) || isempty(isVerbose) 1964 isVerbose = 1; 1965 end 1966 % Initialize returned variables 1967 isOk = 0; 1968 errMsg = ''; 1969 % Get installation 1970 InstPlugDesc = GetInstalled(PlugDesc); 1971 % Plugin not installed: check if folder exists 1972 if isempty(InstPlugDesc) || isempty(InstPlugDesc.Path) 1973 % Get plugin structure from name 1974 [PlugDesc, errMsg] = GetDescription(PlugDesc); 1975 if ~isempty(errMsg) 1976 return; 1977 end 1978 % Managed plugin folder 1979 PlugPath = bst_fullfile(bst_get('UserPluginsDir'), PlugDesc.Name); 1980 else 1981 PlugDesc = InstPlugDesc; 1982 PlugPath = PlugDesc.Path; 1983 end 1984 % Plugin not installed 1985 if ~file_exist(PlugPath) 1986 errMsg = ['Plugin ' PlugDesc.Name ' is not installed.' 10 'Missing folder: ' PlugPath]; 1987 return; 1988 end 1989 % Get plugin structure from name 1990 [PlugDesc, errMsg] = GetDescription(PlugDesc); 1991 if ~isempty(errMsg) 1992 return; 1993 end 1994 1995 % === PROCESS DEPENDENCIES === 1996 % Unload dependent plugins 1997 AllPlugs = GetSupported(); 1998 for iPlug = 1:length(AllPlugs) 1999 if ~isempty(AllPlugs(iPlug).RequiredPlugs) && ismember(PlugDesc.Name, AllPlugs(iPlug).RequiredPlugs(:,1)) 2000 Unload(AllPlugs(iPlug)); 2001 end 2002 end 2003 2004 % === UNLOAD PLUGIN === 2005 % Do not modify path in compiled mode 2006 if ~bst_iscompiled() 2007 matlabPath = str_split(path, pathsep); 2008 % Remove plugin folder and subfolders from path 2009 allSubFolders = str_split(genpath(PlugPath), pathsep); 2010 for i = 1:length(allSubFolders) 2011 if ismember(allSubFolders{i}, matlabPath) 2012 rmpath(allSubFolders{i}); 2013 if isVerbose 2014 disp(['BST> Removing plugin ' PlugDesc.Name ' from path: ' allSubFolders{i}]); 2015 end 2016 end 2017 end 2018 end 2019 2020 % === TEST FUNCTION === 2021 % Check if test function is still available on path 2022 if ~isempty(PlugDesc.TestFile) && ~isempty(which(PlugDesc.TestFile)) 2023 errMsg = ['Plugin ' PlugDesc.Name ' successfully unloaded from: ' 10 PlugPath 10 10 ... 2024 'However, another version is still accessible on the Matlab path:' 10 which(PlugDesc.TestFile) 10 10 ... 2025 'Please remove this folder from the Matlab path.']; 2026 return; 2027 end 2028 2029 % === CALLBACK: POST-UNLOAD === 2030 [isOk, errMsg] = ExecuteCallback(PlugDesc, 'UnloadedFcn'); 2031 if ~isOk 2032 return; 2033 end 2034 2035 % Return success 2036 PlugDesc.isLoaded = 0; 2037 isOk = 1; 2038 end 2039 2040 2041 %% ===== UNLOAD INTERACTIVE ===== 2042 % USAGE: [isOk, errMsg, PlugDesc] = UnloadInteractive(PlugName/PlugDesc) 2043 function [isOk, errMsg, PlugDesc] = UnloadInteractive(PlugDesc) 2044 % Open progress bar 2045 isProgress = bst_progress('isVisible'); 2046 if ~isProgress 2047 bst_progress('start', 'Plugin manager', 'Unloading plugin...'); 2048 end 2049 % Call silent function 2050 [isOk, errMsg, PlugDesc] = Unload(PlugDesc); 2051 % Handle errors 2052 if ~isOk 2053 bst_error(['Unload error:' 10 10 errMsg 10], 'Plugin manager', 0); 2054 elseif ~isempty(errMsg) 2055 java_dialog('msgbox', ['Unload message:' 10 10 errMsg 10], 'Plugin manager'); 2056 end 2057 % Close progress bar 2058 if ~isProgress 2059 bst_progress('stop'); 2060 end 2061 end 2062 2063 2064 %% ===== LIST ===== 2065 % USAGE: strList = bst_plugin('List', Target='installed', isGui=0) % Target={'supported','installed', 'loaded'} 2066 function strList = List(Target, isGui) 2067 % Parse inputs 2068 if (nargin < 2) || isempty(isGui) 2069 isGui = 0; 2070 end 2071 if (nargin < 1) || isempty(Target) 2072 Target = 'Installed'; 2073 else 2074 Target = [upper(Target(1)), lower(Target(2:end))]; 2075 end 2076 % Get plugins to list 2077 strTitle = sprintf('%s plugins', Target); 2078 switch (Target) 2079 case 'Supported' 2080 PlugDesc = GetSupported(); 2081 isInstalled = 0; 2082 case 'Installed' 2083 strTitle = [strTitle ' (*=Loaded)']; 2084 PlugDesc = GetInstalled(); 2085 isInstalled = 1; 2086 case 'Loaded' 2087 PlugDesc = GetInstalled(); 2088 PlugDesc = PlugDesc([PlugDesc.isLoaded] == 1); 2089 isInstalled = 1; 2090 otherwise 2091 error(['Invalid target: ' Target]); 2092 end 2093 if isempty(PlugDesc) 2094 return; 2095 end 2096 % Sort by plugin names 2097 [tmp,I] = sort({PlugDesc.Name}); 2098 PlugDesc = PlugDesc(I); 2099 2100 % Get Brainstorm info 2101 bstVer = bst_get('Version'); 2102 bstDir = bst_get('BrainstormHomeDir'); 2103 % Cut version string (short github SHA) 2104 if (length(bstVer.Commit) > 13) 2105 bstGit = ['git @', bstVer.Commit(1:7)]; 2106 bstURL = ['https://github.com/brainstorm-tools/brainstorm3/archive/' bstVer.Commit '.zip']; 2107 structVer = bstGit; 2108 else 2109 bstGit = ''; 2110 bstURL = ''; 2111 structVer = bstVer.Version; 2112 end 2113 2114 % Max lengths 2115 headerName = ' Name'; 2116 headerVersion = 'Version'; 2117 headerPath = 'Install path'; 2118 headerUrl = 'Downloaded from'; 2119 headerDate = 'Install date'; 2120 maxName = max(cellfun(@length, {PlugDesc.Name, headerName, 'brainstorm'})); 2121 maxVer = min(13, max(cellfun(@length, {PlugDesc.Version, headerVersion, bstGit}))); 2122 maxUrl = max(cellfun(@length, {PlugDesc.URLzip, headerUrl, bstURL})); 2123 maxDate = 12; 2124 if isInstalled 2125 strDate = [' | ', headerDate, repmat(' ', 1, maxDate-length(headerDate))]; 2126 strDateSep = ['-|-', repmat('-',1,maxDate)]; 2127 maxPath = max(cellfun(@length, {PlugDesc.Path, headerPath})); 2128 strPath = [' | ', headerPath, repmat(' ', 1, maxPath-length(headerPath))]; 2129 strPathSep = ['-|-', repmat('-',1,maxPath)]; 2130 strBstVer = [' | ', bstVer.Date, repmat(' ', 1, maxDate-length(bstVer.Date))]; 2131 strBstDir = [' | ', bstDir, repmat(' ', 1, maxPath-length(bstDir))]; 2132 else 2133 strDate = ''; 2134 strDateSep = ''; 2135 strPath = ''; 2136 strPathSep = ''; 2137 strBstVer = ''; 2138 strBstDir = ''; 2139 end 2140 % Print column headers 2141 strList = [headerName, repmat(' ', 1, maxName-length(headerName) + 2) ... 2142 ' | ', headerVersion, repmat(' ', 1, maxVer-length(headerVersion)), ... 2143 strDate, strPath, ... 2144 ' | ' headerUrl 10 ... 2145 repmat('-',1,maxName + 2), '-|-', repmat('-',1,maxVer), strDateSep, strPathSep, '-|-', repmat('-',1,maxUrl) 10]; 2146 2147 % Print Brainstorm information 2148 strList = [strList '* ', ... 2149 'brainstorm', repmat(' ', 1, maxName-length('brainstorm')) ... 2150 ' | ', bstGit, repmat(' ', 1, maxVer-length(bstGit)), ... 2151 strBstVer, strBstDir, ... 2152 ' | ' bstURL 10]; 2153 2154 % Print installed plugins to standard output 2155 for iPlug = 1:length(PlugDesc) 2156 % Loaded plugin 2157 if PlugDesc(iPlug).isLoaded 2158 strLoaded = '* '; 2159 else 2160 strLoaded = ' '; 2161 end 2162 % Cut installation date: Only date, no time 2163 if (length(PlugDesc(iPlug).InstallDate) > 11) 2164 plugDate = PlugDesc(iPlug).InstallDate(1:11); 2165 else 2166 plugDate = PlugDesc(iPlug).InstallDate; 2167 end 2168 % Installed listing 2169 if isInstalled 2170 strDate = [' | ', plugDate, repmat(' ', 1, maxDate-length(plugDate))]; 2171 strPath = [' | ', PlugDesc(iPlug).Path, repmat(' ', 1, maxPath-length(PlugDesc(iPlug).Path))]; 2172 else 2173 strDate = ''; 2174 strPath = ''; 2175 end 2176 % Cut version string (short github SHA) 2177 if (length(PlugDesc(iPlug).Version) > 13) 2178 plugVer = ['git @', PlugDesc(iPlug).Version(1:7)]; 2179 else 2180 plugVer = PlugDesc(iPlug).Version; 2181 end 2182 % Assemble plugin text row 2183 strList = [strList strLoaded, ... 2184 PlugDesc(iPlug).Name, repmat(' ', 1, maxName-length(PlugDesc(iPlug).Name)) ... 2185 ' | ', plugVer, repmat(' ', 1, maxVer-length(plugVer)), ... 2186 strDate, strPath, ... 2187 ' | ' PlugDesc(iPlug).URLzip 10]; 2188 end 2189 % Display output 2190 if isGui 2191 view_text(strList, strTitle); 2192 % No string returned: display it in the command window 2193 elseif (nargout == 0) 2194 disp([10 strTitle 10 10 strList]); 2195 end 2196 end 2197 2198 2199 %% ===== MENUS: CREATE ===== 2200 function j = MenuCreate(jMenu, fontSize) 2201 import org.brainstorm.icon.*; 2202 % Get all the supported plugins 2203 PlugDesc = GetSupported(); 2204 % Get Matlab version 2205 MatlabVersion = bst_get('MatlabVersion'); 2206 isCompiled = bst_iscompiled(); 2207 % Submenus 2208 jSub = {}; 2209 % Process each plugin 2210 j = repmat(struct(), 0); 2211 for iPlug = 1:length(PlugDesc) 2212 Plug = PlugDesc(iPlug); 2213 % Skip if Matlab is too old 2214 if ~isempty(Plug.MinMatlabVer) && (Plug.MinMatlabVer > 0) && (MatlabVersion < Plug.MinMatlabVer) 2215 continue; 2216 end 2217 % Skip if not supported in compiled version 2218 if isCompiled && (Plug.CompiledStatus == 0) 2219 continue; 2220 end 2221 % Category=submenu 2222 if ~isempty(Plug.Category) 2223 if isempty(jSub) || ~ismember(Plug.Category, jSub(:,1)) 2224 jParent = gui_component('Menu', jMenu, [], Plug.Category, IconLoader.ICON_FOLDER_OPEN, [], [], fontSize); 2225 jSub(end+1,1:2) = {Plug.Category, jParent}; 2226 else 2227 iSub = find(strcmpi(jSub(:,1), Plug.Category)); 2228 jParent = jSub{iSub,2}; 2229 end 2230 else 2231 jParent = jMenu; 2232 end 2233 % One menu per plugin 2234 ij = length(j) + 1; 2235 j(ij).name = Plug.Name; 2236 % Compiled and included: Simple static menu 2237 if isCompiled && (Plug.CompiledStatus == 2) 2238 j(ij).menu = gui_component('MenuItem', jParent, [], Plug.Name, [], [], [], fontSize); 2239 % Do not create submenus for compiled version 2240 else 2241 % Main menu 2242 j(ij).menu = gui_component('Menu', jParent, [], Plug.Name, [], [], [], fontSize); 2243 % Version 2244 j(ij).version = gui_component('MenuItem', j(ij).menu, [], 'Version', [], [], [], fontSize); 2245 j(ij).versep = java_create('javax.swing.JSeparator'); 2246 j(ij).menu.add(j(ij).versep); 2247 % Install 2248 j(ij).install = gui_component('MenuItem', j(ij).menu, [], 'Install', IconLoader.ICON_DOWNLOAD, [], @(h,ev)InstallInteractive(Plug.Name), fontSize); 2249 % Update 2250 j(ij).update = gui_component('MenuItem', j(ij).menu, [], 'Update', IconLoader.ICON_RELOAD, [], @(h,ev)UpdateInteractive(Plug.Name), fontSize); 2251 % Uninstall 2252 j(ij).uninstall = gui_component('MenuItem', j(ij).menu, [], 'Uninstall', IconLoader.ICON_DELETE, [], @(h,ev)UninstallInteractive(Plug.Name), fontSize); 2253 j(ij).menu.addSeparator(); 2254 % Custom install 2255 j(ij).custom = gui_component('Menu', j(ij).menu, [], 'Custom install', IconLoader.ICON_FOLDER_OPEN, [], [], fontSize); 2256 j(ij).customset = gui_component('MenuItem', j(ij).custom, [], 'Select installation folder', [], [], @(h,ev)SetCustomPath(Plug.Name), fontSize); 2257 j(ij).custompath = gui_component('MenuItem', j(ij).custom, [], 'Path not set', [], [], [], fontSize); 2258 j(ij).custompath.setEnabled(0); 2259 j(ij).custom.addSeparator(); 2260 j(ij).customdel = gui_component('MenuItem', j(ij).custom, [], 'Ignore local installation', [], [], @(h,ev)SetCustomPath(Plug.Name, 0), fontSize); 2261 j(ij).menu.addSeparator(); 2262 % Load 2263 j(ij).load = gui_component('MenuItem', j(ij).menu, [], 'Load', IconLoader.ICON_GOOD, [], @(h,ev)LoadInteractive(Plug.Name), fontSize); 2264 j(ij).unload = gui_component('MenuItem', j(ij).menu, [], 'Unload', IconLoader.ICON_BAD, [], @(h,ev)UnloadInteractive(Plug.Name), fontSize); 2265 j(ij).menu.addSeparator(); 2266 % Website 2267 j(ij).web = gui_component('MenuItem', j(ij).menu, [], 'Website', IconLoader.ICON_EXPLORER, [], @(h,ev)web(Plug.URLinfo, '-browser'), fontSize); 2268 j(ij).usage = gui_component('MenuItem', j(ij).menu, [], 'Usage statistics', IconLoader.ICON_TS_DISPLAY, [], @(h,ev)bst_userstat(0,Plug.Name), fontSize); 2269 % Extra menus 2270 if ~isempty(Plug.ExtraMenus) 2271 j(ij).menu.addSeparator(); 2272 for iMenu = 1:size(Plug.ExtraMenus,1) 2273 j(ij).extra(iMenu) = gui_component('MenuItem', j(ij).menu, [], Plug.ExtraMenus{iMenu,1}, IconLoader.ICON_EXPLORER, [], @(h,ev)bst_call(@eval, Plug.ExtraMenus{iMenu,2}), fontSize); 2274 end 2275 end 2276 end 2277 end 2278 % List 2279 if ~isCompiled 2280 jMenu.addSeparator(); 2281 gui_component('MenuItem', jMenu, [], 'List', IconLoader.ICON_EDIT, [], @(h,ev)List('Installed', 1), fontSize); 2282 end 2283 end 2284 2285 2286 %% ===== MENUS: UPDATE ===== 2287 function MenuUpdate(jPlugs) 2288 import org.brainstorm.icon.*; 2289 % If compiled: disable most menus 2290 isCompiled = bst_iscompiled(); 2291 % Interface scaling 2292 InterfaceScaling = bst_get('InterfaceScaling'); 2293 % Update all the plugins 2294 for iPlug = 1:length(jPlugs) 2295 j = jPlugs(iPlug); 2296 PlugName = j.name; 2297 % Is installed? 2298 PlugRef = GetSupported(PlugName); 2299 Plug = GetInstalled(PlugName); 2300 if ~isempty(Plug) 2301 isInstalled = 1; 2302 elseif ~isempty(PlugRef) 2303 Plug = PlugRef; 2304 isInstalled = 0; 2305 else 2306 disp(['BST> Error: Description not found for plugin: ' PlugName]); 2307 continue; 2308 end 2309 isLoaded = isInstalled && Plug.isLoaded; 2310 isManaged = isInstalled && Plug.isManaged; 2311 % Compiled included: no submenus 2312 if isCompiled && (PlugRef.CompiledStatus == 2) 2313 j.menu.setEnabled(1); 2314 if (InterfaceScaling ~= 100) 2315 j.menu.setIcon(IconLoader.scaleIcon(IconLoader.ICON_GOOD, InterfaceScaling / 100)); 2316 else 2317 j.menu.setIcon(IconLoader.ICON_GOOD); 2318 end 2319 % Otherwise: all available 2320 else 2321 % Main menu: Available/Not available 2322 j.menu.setEnabled(isInstalled || ~isempty(Plug.URLzip)); 2323 % Current version 2324 if ~isInstalled 2325 j.version.setText('<HTML><FONT color="#707070"><I>Not installed</I></FONT>'); 2326 elseif ~isManaged && ~isempty(Plug.Path) 2327 j.version.setText('<HTML><FONT color="#707070"><I>Custom install</I></FONT>') 2328 elseif ~isempty(Plug.Version) && ischar(Plug.Version) 2329 strVer = Plug.Version; 2330 % If downloading from github 2331 if isGithubMaster(Plug.URLzip) 2332 % Show installation date, if available 2333 if ~isempty(Plug.InstallDate) 2334 strVer = Plug.InstallDate(1:11); 2335 % Show only the short SHA (7 chars) 2336 elseif (length(Plug.Version) >= 30) 2337 strVer = Plug.Version(1:7); 2338 end 2339 end 2340 j.version.setText(['<HTML><FONT color="#707070"><I>Installed version: ' strVer '</I></FONT>']) 2341 elseif isInstalled 2342 j.version.setText('<HTML><FONT color="#707070"><I>Installed</I></FONT>'); 2343 end 2344 % Main menu: Icon 2345 if isCompiled && isInstalled 2346 menuIcon = IconLoader.ICON_GOOD; 2347 elseif isLoaded % Loaded 2348 menuIcon = IconLoader.ICON_GOOD; 2349 elseif isInstalled % Not loaded 2350 menuIcon = IconLoader.ICON_BAD; 2351 else 2352 menuIcon = IconLoader.ICON_NEUTRAL; 2353 end 2354 if (InterfaceScaling ~= 100) 2355 j.menu.setIcon(IconLoader.scaleIcon(menuIcon, InterfaceScaling / 100)); 2356 else 2357 j.menu.setIcon(menuIcon); 2358 end 2359 % Install 2360 j.install.setEnabled(~isInstalled); 2361 if ~isInstalled && ~isempty(PlugRef.Version) && ischar(PlugRef.Version) 2362 j.install.setText(['<HTML>Install    <FONT color="#707070"><I>(' PlugRef.Version ')</I></FONT>']) 2363 else 2364 j.install.setText('Install'); 2365 end 2366 % Update 2367 j.update.setEnabled(isManaged); 2368 if isInstalled && ~isempty(PlugRef.Version) && ischar(PlugRef.Version) 2369 j.update.setText(['<HTML>Update    <FONT color="#707070"><I>(' PlugRef.Version ')</I></FONT>']) 2370 else 2371 j.update.setText('Update'); 2372 end 2373 % Uninstall 2374 j.uninstall.setEnabled(isManaged); 2375 % Custom install 2376 j.custom.setEnabled(~isManaged); 2377 if ~isempty(Plug.Path) 2378 j.custompath.setText(Plug.Path); 2379 else 2380 j.custompath.setText('Path not set'); 2381 end 2382 % Load/Unload 2383 j.load.setEnabled(isInstalled && ~isLoaded && ~isCompiled); 2384 j.unload.setEnabled(isLoaded && ~isCompiled); 2385 % Web 2386 j.web.setEnabled(~isempty(Plug.URLinfo)); 2387 % Extra menus: Update availability 2388 if ~isempty(Plug.ExtraMenus) 2389 for iMenu = 1:size(Plug.ExtraMenus,1) 2390 if (size(Plug.ExtraMenus,2) == 3) && ~isempty(Plug.ExtraMenus{3}) 2391 if (strcmpi(Plug.ExtraMenus{3}, 'loaded') && isLoaded) ... 2392 || (strcmpi(Plug.ExtraMenus{3}, 'installed') && isInstalled) ... 2393 || (strcmpi(Plug.ExtraMenus{3}, 'always')) 2394 j.extra(iMenu).setEnabled(1); 2395 else 2396 j.extra(iMenu).setEnabled(0); 2397 end 2398 end 2399 end 2400 end 2401 end 2402 end 2403 j.menu.repaint() 2404 j.menu.getParent().repaint() 2405 end 2406 2407 2408 %% ===== SET CUSTOM PATH ===== 2409 function SetCustomPath(PlugName, PlugPath) 2410 % Parse inputs 2411 if (nargin < 2) || isempty(PlugPath) 2412 PlugPath = []; 2413 end 2414 % Custom plugin paths 2415 PluginCustomPath = bst_get('PluginCustomPath'); 2416 % Get plugin description 2417 PlugDesc = GetSupported(PlugName); 2418 if isempty(PlugDesc) 2419 return; 2420 end 2421 % Get installed plugin 2422 PlugInst = GetInstalled(PlugName); 2423 isInstalled = ~isempty(PlugInst); 2424 isManaged = isInstalled && PlugInst.isManaged; 2425 if isManaged 2426 bst_error(['Plugin ' PlugName ' is already installed by Brainstorm, uninstall it first.'], 0); 2427 return; 2428 end 2429 % Ask install path to user 2430 isWarning = 1; 2431 if isempty(PlugPath) 2432 PlugPath = uigetdir(PlugInst.Path, ['Select ' PlugName ' directory.']); 2433 if isequal(PlugPath, 0) 2434 PlugPath = []; 2435 end 2436 % If removal is requested 2437 elseif isequal(PlugPath, 0) 2438 PlugPath = []; 2439 isWarning = 0; 2440 end 2441 % If the directory did not change: nothing to do 2442 if (isInstalled && isequal(PlugInst.Path, PlugPath)) || (~isInstalled && isempty(PlugPath)) 2443 return; 2444 end 2445 % Unload previous version 2446 if isInstalled && ~isempty(PlugInst.Path) && PlugInst.isLoaded 2447 Unload(PlugName); 2448 end 2449 % Check if this is a valid plugin folder 2450 if isempty(PlugPath) || ~file_exist(PlugPath) 2451 PlugPath = []; 2452 end 2453 if ~isempty(PlugPath) && ~isempty(PlugDesc.TestFile) 2454 isValid = 0; 2455 if file_exist(bst_fullfile(PlugPath, PlugDesc.TestFile)) 2456 isValid = 1; 2457 elseif ~isempty(PlugDesc.LoadFolders) 2458 for iFolder = 1:length(PlugDesc.LoadFolders) 2459 if file_exist(bst_fullfile(PlugPath, PlugDesc.LoadFolders{iFolder}, PlugDesc.TestFile)) 2460 isValid = 1; 2461 end 2462 end 2463 end 2464 if ~isValid 2465 PlugPath = []; 2466 end 2467 end 2468 % Save path 2469 PluginCustomPath.(PlugName) = PlugPath; 2470 bst_set('PluginCustomPath', PluginCustomPath); 2471 % Load plugin 2472 if ~isempty(PlugPath) 2473 [isOk, errMsg, PlugDesc] = Load(PlugName); 2474 % Ignored warnings 2475 elseif ~isWarning 2476 isOk = 1; 2477 errMsg = []; 2478 % Invalid path 2479 else 2480 isOk = 0; 2481 if ~isempty(PlugDesc.TestFile) 2482 errMsg = ['The file ' PlugDesc.TestFile ' could not be found in selected folder.']; 2483 else 2484 errMsg = 'No valid folder was found.'; 2485 end 2486 end 2487 % Handle errors 2488 if ~isOk 2489 bst_error(['An error occurred while configuring plugin ' PlugName ':' 10 10 errMsg 10], 'Plugin manager', 0); 2490 elseif ~isempty(errMsg) 2491 java_dialog('msgbox', ['Configuration message:' 10 10 errMsg 10], 'Plugin manager'); 2492 elseif isWarning 2493 java_dialog('msgbox', ['Plugin ' PlugName ' successfully loaded.']); 2494 end 2495 end 2496 2497 2498 %% ===== ARCHIVE SOFTWARE ENVIRONMENT ===== 2499 % USAGE: Archive(OutputFile=[ask]) 2500 function Archive(OutputFile) 2501 % Parse inputs 2502 if (nargin < 1) || isempty(OutputFile) 2503 OutputFile = []; 2504 end 2505 % Get date string 2506 c = clock(); 2507 strDate = sprintf('%02d%02d%02d', c(1)-2000, c(2), c(3)); 2508 % Get output filename 2509 if isempty(OutputFile) 2510 % Get default directories 2511 LastUsedDirs = bst_get('LastUsedDirs'); 2512 % Default output filename 2513 OutputFile = bst_fullfile(LastUsedDirs.ExportScript, ['bst_env_' strDate '.zip']); 2514 % File selection 2515 OutputFile = java_getfile('save', 'Export environment', OutputFile, 'single', 'files', ... 2516 {{'.zip'}, 'Zip files (*.zip)', 'ZIP'}, 1); 2517 if isempty(OutputFile) 2518 return 2519 end 2520 % Save new default export path 2521 LastUsedDirs.ExportScript = bst_fileparts(OutputFile); 2522 bst_set('LastUsedDirs', LastUsedDirs); 2523 end 2524 2525 % ===== TEMP FOLDER ===== 2526 bst_progress('start', 'Export environment', 'Creating temporary folder...'); 2527 % Empty temporary folder 2528 gui_brainstorm('EmptyTempFolder'); 2529 % Create temporary folder for storing all the files to package 2530 EnvDir = bst_fullfile(bst_get('BrainstormTmpDir'), ['bst_env_' strDate]); 2531 mkdir(EnvDir); 2532 2533 % ===== COPY BRAINSTORM ===== 2534 bst_progress('text', 'Copying: brainstorm...'); 2535 % Get Brainstorm path and version 2536 bstVer = bst_get('Version'); 2537 bstDir = bst_get('BrainstormHomeDir'); 2538 % Get brainstorm3 destination folder: add version number 2539 if ~isempty(bstVer.Version) && ~any(bstVer.Version == '?') 2540 envBst = bst_fullfile(EnvDir, ['brainstorm', bstVer.Version]); 2541 else 2542 [tmp, bstName] = bst_fileparts(bstDir); 2543 envBst = bst_fullfile(EnvDir, bstName); 2544 end 2545 % Add git commit hash 2546 if (length(bstVer.Commit) >= 30) 2547 envBst = [envBst, '_', bstVer.Commit(1:7)]; 2548 end 2549 % Copy brainstorm3 folder 2550 isOk = file_copy(bstDir, envBst); 2551 if ~isOk 2552 error(['Cannot copy folder: "' bstDir '" to "' envBst '"']); 2553 end 2554 2555 % ===== COPY DEFAULTS ===== 2556 bst_progress('text', 'Copying: user defaults...'); 2557 % Get user defaults folder 2558 userDef = bst_get('UserDefaultsDir'); 2559 envDef = bst_fullfile(envBst, 'defaults'); 2560 isOk = file_copy(userDef, envDef); 2561 if ~isOk 2562 error(['Cannot merge folder: "' userDef '" into "' envDef '"']); 2563 end 2564 2565 % ===== COPY USER PROCESSES ===== 2566 bst_progress('text', 'Copying: user processes...'); 2567 % Get user process folder 2568 userProc = bst_get('UserProcessDir'); 2569 envProc = bst_fullfile(envBst, 'toolbox', 'process', 'functions'); 2570 isOk = file_copy(userProc, envProc); 2571 if ~isOk 2572 error(['Cannot merge folder: "' userProc '" into "' envProc '"']); 2573 end 2574 2575 % ===== COPY PLUGINS ====== 2576 % Get list of plugins to package 2577 PlugDesc = GetInstalled(); 2578 % Destination plugin directory 2579 envPlugins = bst_fullfile(envBst, 'plugins'); 2580 % Copy each installed plugin 2581 for iPlug = 1:length(PlugDesc) 2582 bst_progress('text', ['Copying plugin: ' PlugDesc(iPlug).Name '...']); 2583 envPlug = bst_fullfile(envPlugins, PlugDesc(iPlug).Name); 2584 isOk = file_copy(PlugDesc(iPlug).Path, envPlug); 2585 if ~isOk 2586 error(['Cannot copy folder: "' userProc '" into "' envProc '"']); 2587 end 2588 end 2589 2590 % ===== SAVE LIST OF VERSIONS ===== 2591 strList = bst_plugin('List', 'installed', 0); 2592 % Open file versions.txt 2593 VersionFile = bst_fullfile(EnvDir, 'versions.txt'); 2594 fid = fopen(VersionFile, 'wt'); 2595 if (fid < 0) 2596 error(['Cannot save file: ' VersionFile]); 2597 end 2598 % Save Brainstorm plugins list 2599 fwrite(fid, strList); 2600 % Save Matlab ver command 2601 strMatlab = evalc('ver'); 2602 fwrite(fid, [10 10 strMatlab]); 2603 % Close file 2604 fclose(fid); 2605 2606 % ===== ZIP FILES ===== 2607 bst_progress('text', 'Zipping environment...'); 2608 % Zip files with bst_env_* being the first level 2609 zip(OutputFile, EnvDir, bst_fileparts(EnvDir)); 2610 % Cleaning up 2611 gui_brainstorm('EmptyTempFolder'); 2612 % Close progress bar 2613 bst_progress('stop'); 2614 end 2615 2616 2617 %% ============================================================================ 2618 % ===== PLUGIN-SPECIFIC FUNCTIONS ============================================ 2619 % ============================================================================ 2620 2621 %% ===== LINK CAT-SPM ===== 2622 % USAGE: bst_plugin('LinkCatSpm', Action) 2623 % 0=Delete/1=Create/2=Check a symbolic link for CAT12 in SPM12 toolbox folder 2624 function LinkCatSpm(Action) 2625 % Get SPM12 plugin 2626 PlugSpm = GetInstalled('spm12'); 2627 if isempty(PlugSpm) 2628 error('Plugin SPM12 is not loaded.'); 2629 elseif ~PlugSpm.isLoaded 2630 [isOk, errMsg, PlugSpm] = Load('spm12'); 2631 if ~isOk 2632 error('Plugin SPM12 cannot be loaded.'); 2633 end 2634 end 2635 % Get SPM plugin path 2636 if ~isempty(PlugSpm.SubFolder) 2637 spmToolboxDir = bst_fullfile(PlugSpm.Path, PlugSpm.SubFolder, 'toolbox'); 2638 else 2639 spmToolboxDir = bst_fullfile(PlugSpm.Path, 'toolbox'); 2640 end 2641 if ~file_exist(spmToolboxDir) 2642 error(['Could not find SPM12 toolbox folder: ' spmToolboxDir]); 2643 end 2644 % CAT12 plugin path 2645 spmCatDir = bst_fullfile(spmToolboxDir, 'cat12'); 2646 % Check link 2647 if (Action == 2) 2648 % Link exists and works: return here 2649 if file_exist(bst_fullfile(spmCatDir, 'cat12.m')) 2650 return; 2651 % Link doesn't exist: Create it 2652 else 2653 Action = 1; 2654 end 2655 end 2656 % If folder already exists 2657 if file_exist(spmCatDir) 2658 % If setting install and SPM is not managed by Brainstorm: do not risk deleting user's install of CAT12 2659 if (Action == 1) && ~PlugSpm.isManaged 2660 error(['CAT12 seems already set up: ' spmCatDir]); 2661 end 2662 % All the other cases: delete existing CAT12 folder 2663 if ispc 2664 rmCall = ['rmdir /q /s "' spmCatDir '"']; 2665 else 2666 rmCall = ['rm -rf "' spmCatDir '"']; 2667 end 2668 disp(['BST> Deleting existing SPM12 toolbox: ' rmCall]); 2669 [status,result] = system(rmCall); 2670 if (status ~= 0) 2671 error(['Error deleting link: ' result]); 2672 end 2673 end 2674 % Create new link 2675 if (Action == 1) 2676 % Get CAT12 plugin 2677 PlugCat = GetInstalled('cat12'); 2678 if isempty(PlugCat) || ~PlugCat.isLoaded 2679 error('Plugin CAT12 is not loaded.'); 2680 end 2681 % Define source and target for the link 2682 if ~isempty(PlugCat.SubFolder) 2683 linkTarget = bst_fullfile(PlugCat.Path, PlugCat.SubFolder); 2684 else 2685 linkTarget = PlugCat.Path; 2686 end 2687 linkFile = spmCatDir; 2688 % Create link 2689 if ispc 2690 linkCall = ['mklink /D "' linkFile '" "' linkTarget '"']; 2691 else 2692 linkCall = ['ln -s "' linkTarget '" "' linkFile '"']; 2693 end 2694 disp(['BST> Creating symbolic link: ' linkCall]); 2695 [status,result] = system(linkCall); 2696 if (status ~= 0) 2697 error(['Error creating link: ' result]); 2698 end 2699 end 2700 end 2701 2702 2703 %% ===== SET PROGRESS LOGO ===== 2704 % USAGE: SetProgressLogo(PlugDesc/PlugName) % Set progress bar image 2705 % SetProgressLogo([]) % Remove progress bar image 2706 function SetProgressLogo(PlugDesc) 2707 % Remove image 2708 if (nargin < 1) || isempty(PlugDesc) 2709 bst_progress('removeimage'); 2710 bst_progress('removelink'); 2711 % Set image 2712 else 2713 % Get plugin description 2714 if ischar(PlugDesc) 2715 PlugDesc = GetSupported(PlugDesc); 2716 end 2717 % Set logo file 2718 if isempty(PlugDesc.LogoFile) 2719 PlugDesc.LogoFile = GetLogoFile(PlugDesc); 2720 end 2721 if ~isempty(PlugDesc.LogoFile) 2722 bst_progress('setimage', PlugDesc.LogoFile); 2723 end 2724 % Set link 2725 if ~isempty(PlugDesc.URLinfo) 2726 bst_progress('setlink', 'http://openmeeg.github.io'); 2727 end 2728 end 2729 end 2730

Tutorials/Plugins (last edited 2021-03-19 12:51:08 by FrancoisTadel)