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:

Inverse modeling:

Forward modeling:

Simulation:

Statistics:

I/O Libraries for specific file formats:

fNIRS:

Example: 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, isCache) % Get the latest online version of some plugins 10 % ReadmeFile = bst_plugin('GetReadmeFile', PlugDesc) % Get full path to plugin readme file 11 % LogoFile = bst_plugin('GetLogoFile', PlugDesc) % Get full path to plugin logo file 12 % Version = bst_plugin('CompareVersions', v1, v2) % Compare two version strings 13 % [isOk, errMsg, PlugDesc] = bst_plugin('Load', PlugName/PlugDesc) 14 % [isOk, errMsg, PlugDesc] = bst_plugin('LoadInteractive', PlugName/PlugDesc) 15 % [isOk, errMsg, PlugDesc] = bst_plugin('Unload', PlugName/PlugDesc) 16 % [isOk, errMsg, PlugDesc] = bst_plugin('UnloadInteractive', PlugName/PlugDesc) 17 % [isOk, errMsg, PlugDesc] = bst_plugin('Install', PlugName, isInteractive=0, minVersion=[]) 18 % [isOk, errMsg, PlugDesc] = bst_plugin('InstallMultipleChoice',PlugNames, isInteractive=0) % Install at least one of the input plugins 19 % [isOk, errMsg, PlugDesc] = bst_plugin('InstallInteractive', PlugName) 20 % [isOk, errMsg] = bst_plugin('Uninstall', PlugName, isInteractive=0, isDependencies=1) 21 % [isOk, errMsg] = bst_plugin('UninstallInteractive', PlugName) 22 % bst_plugin('Configure', PlugDesc) % Execute some additional tasks after loading or installation 23 % bst_plugin('SetCustomPath', PlugName, PlugPath) 24 % bst_plugin('List', Target='installed') % Target={'supported','installed'} 25 % bst_plugin('MenuCreate', jMenu) 26 % bst_plugin('MenuUpdate', jMenu) 27 % bst_plugin('LinkCatSpm', isSet) % Create/delete a symbolic link for CAT12 in SPM12 toolbox folder 28 % 29 % 30 % PLUGIN DEFINITION 31 % ================= 32 % 33 % The plugins registered in Brainstorm are listed in function GetSupported(). 34 % Each one is an entry in the PlugDesc array, following the structure defined in db_template('plugdesc'). 35 % The fields allowed are described below. 36 % 37 % Mandatory fields 38 % ================ 39 % - Name : String: Plugin name = subfolder in the Brainstorm user folder 40 % - Version : String: Version of the plugin (eg. '1.2', '21a', 'github-master', 'latest') 41 % - URLzip : String: Download URL, zip or tgz file accessible over HTTP/HTTPS/FTP 42 % - URLinfo : String: Information URL = Software website 43 % 44 % Optional fields 45 % =============== 46 % - AutoUpdate : Boolean: If true, the plugin is updated automatically when there is a new version available (default: false). 47 % - AutoLoad : Boolean: If true, the plugin is loaded automatically at Brainstorm startup 48 % - Category : String: Sub-menu in which the plugin is listed 49 % - ExtraMenus : Cell matrix {Nx2}: List of entries to add to the plugins menu 50 % | ExtraMenus{i,1}: String: Label of the menu 51 % | ExtraMenus{i,2}: String: Matlab code to eval when the menu is clicked 52 % - TestFile : String: Name of a file that should be located in one of the loaded folders of the plugin (eg. 'spm.m' for SPM12). 53 % | This is used to test whether the plugin was correctly installed, or whether it is available somewhere else in the Matlab path. 54 % - ReadmeFile : String: Name of the text file to display after installing the plugin (must be in the plugin folder). 55 % | If empty, it tries using brainstorm3/doc/plugin/plugname_readme.txt 56 % - LogoFile : String: Name of the image file to display during the plugin download, installation, and associated computations (must be in the plugin folder). 57 % | Supported extensions: gif, png. If empty, try using brainstorm3/doc/plugin/<Name>_logo.[gif|png] 58 % - MinMatlabVer : Integer: Minimum Matlab version required for using this plugin, as returned by bst_get('MatlabVersion') 59 % - CompiledStatus : Integer: Behavior of this plugin in the compiled version of Brainstorm: 60 % | 0: Plugin is not available in the compiled distribution of Brainstorm 61 % | 1: Plugin is available for download (only for plugins based on native compiled code) 62 % | 2: Plugin is included in the compiled distribution of Brainstorm 63 % - RequiredPlugs : Cell-array: Additional plugins required by this plugin, that must be installed/loaded beforehand. 64 % | {Nx2} => {'plugname','version'; ...} or 65 % | {Nx1} => {'plugname'; ...} 66 % - UnloadPlugs : Cell-array of names of incompatible plugin, to unload before loaing this one 67 % - LoadFolders : Cell-array of subfolders to add to the Matlab path when setting up the plugin. Use {'*'} to add all the plugin subfolders. 68 % - GetVersionFcn : String to eval or function handle to call to get the version after installation 69 % - InstalledFcn : String to eval or function handle to call after installing the plugin 70 % - UninstalledFcn : String to eval or function handle to call after uninstalling the plugin 71 % - LoadedFcn : String to eval or function handle to call after loading the plugin 72 % - UnloadedFcn : String to eval or function handle to call after unloading the plugin 73 % 74 % Fields set when installing the plugin 75 % ===================================== 76 % - Processes : List of process functions to be added to the pipeline manager 77 % 78 % Fields set when loading the plugin 79 % ================================== 80 % - Path : Installation path (eg. /home/username/.brainstorm/plugins/fieldtrip) 81 % - SubFolder : If all the code is in a single subfolder (eg. /plugins/fieldtrip/fieldtrip-20210304), 82 % this is detected and the full path to the TestFile would be typically fullfile(Path, SubFolder). 83 % - isLoaded : 0=Not loaded, 1=Loaded 84 % - isManaged : 0=Installed manually by the user, 1=Installed automatically by Brainstorm 85 % 86 87 % @============================================================================= 88 % This function is part of the Brainstorm software: 89 % https://neuroimage.usc.edu/brainstorm 90 % 91 % Copyright (c)2000-2020 University of Southern California & McGill University 92 % This software is distributed under the terms of the GNU General Public License 93 % as published by the Free Software Foundation. Further details on the GPLv3 94 % license can be found at http://www.gnu.org/copyleft/gpl.html. 95 % 96 % FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE 97 % UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY 98 % WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF 99 % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY 100 % LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. 101 % 102 % For more information type "brainstorm license" at command prompt. 103 % =============================================================================@ 104 % 105 % Authors: Francois Tadel 2021 106 107 eval(macro_method); 108 end 109 110 111 %% ===== GET SUPPORTED PLUGINS ===== 112 % USAGE: PlugDesc = bst_plugin('GetSupported') % List all the plugins supported by Brainstorm 113 % PlugDesc = bst_plugin('GetSupported', PlugName/PlugDesc) % Get only one specific supported plugin 114 function PlugDesc = GetSupported(SelPlug) 115 % Parse inputs 116 if (nargin < 1) || isempty(SelPlug) 117 SelPlug = []; 118 end 119 % Initialized returned structure 120 PlugDesc = repmat(db_template('PlugDesc'), 0); 121 % Get OS 122 OsType = bst_get('OsType', 0); 123 124 % ================================================================================================================ 125 % === ANATOMY: BRAIN2MESH === 126 PlugDesc(end+1) = GetStruct('brain2mesh'); 127 PlugDesc(end).Version = 'github-master'; 128 PlugDesc(end).Category = 'Anatomy'; 129 PlugDesc(end).URLzip = 'https://github.com/fangq/brain2mesh/archive/master.zip'; 130 PlugDesc(end).URLinfo = 'http://mcx.space/brain2mesh/'; 131 PlugDesc(end).TestFile = 'brain2mesh.m'; 132 PlugDesc(end).ReadmeFile = 'README.md'; 133 PlugDesc(end).CompiledStatus = 2; 134 PlugDesc(end).RequiredPlugs = {'spm12'; 'iso2mesh'}; 135 136 % === ANATOMY: CAT12 === 137 PlugDesc(end+1) = GetStruct('cat12'); 138 PlugDesc(end).Version = 'latest'; 139 PlugDesc(end).Category = 'Anatomy'; 140 PlugDesc(end).AutoUpdate = 1; 141 PlugDesc(end).URLzip = 'http://www.neuro.uni-jena.de/cat12/cat12_latest.zip'; 142 PlugDesc(end).URLinfo = 'http://www.neuro.uni-jena.de/cat/'; 143 PlugDesc(end).TestFile = 'cat_version.m'; 144 PlugDesc(end).ReadmeFile = 'Contents.txt'; 145 PlugDesc(end).CompiledStatus = 0; 146 PlugDesc(end).RequiredPlugs = {'spm12'}; 147 PlugDesc(end).GetVersionFcn = 'bst_getoutvar(2, @cat_version)'; 148 PlugDesc(end).InstalledFcn = 'LinkCatSpm(1);'; 149 PlugDesc(end).UninstalledFcn = 'LinkCatSpm(0);'; 150 PlugDesc(end).ExtraMenus = {'Online tutorial', 'web(''https://neuroimage.usc.edu/brainstorm/Tutorials/SegCAT12'', ''-browser'')'}; 151 152 % === ANATOMY: ISO2MESH === 153 PlugDesc(end+1) = GetStruct('iso2mesh'); 154 PlugDesc(end).Version = '1.9.6'; 155 PlugDesc(end).Category = 'Anatomy'; 156 PlugDesc(end).AutoUpdate = 1; 157 PlugDesc(end).URLzip = 'https://github.com/fangq/iso2mesh/releases/download/v1.9.6/iso2mesh-1.9.6-allinone.zip'; 158 PlugDesc(end).URLinfo = 'http://iso2mesh.sourceforge.net'; 159 PlugDesc(end).TestFile = 'iso2meshver.m'; 160 PlugDesc(end).ReadmeFile = 'README.txt'; 161 PlugDesc(end).CompiledStatus = 2; 162 PlugDesc(end).LoadedFcn = 'assignin(''base'', ''ISO2MESH_TEMP'', bst_get(''BrainstormTmpDir''));'; 163 164 % === ANATOMY: ROAST === 165 PlugDesc(end+1) = GetStruct('roast'); 166 PlugDesc(end).Version = '3.0'; 167 PlugDesc(end).Category = 'Anatomy'; 168 PlugDesc(end).AutoUpdate = 1; 169 PlugDesc(end).URLzip = 'https://www.parralab.org/roast/roast-3.0.zip'; 170 PlugDesc(end).URLinfo = 'https://www.parralab.org/roast/'; 171 PlugDesc(end).TestFile = 'roast.m'; 172 PlugDesc(end).ReadmeFile = 'README.md'; 173 PlugDesc(end).CompiledStatus = 0; 174 PlugDesc(end).UnloadPlugs = {'spm12', 'iso2mesh'}; 175 PlugDesc(end).LoadFolders = {'lib/spm12', 'lib/iso2mesh', 'lib/cvx', 'lib/ncs2daprox', 'lib/NIFTI_20110921'}; 176 177 % === FORWARD: OPENMEEG === 178 PlugDesc(end+1) = GetStruct('openmeeg'); 179 PlugDesc(end).Version = '2.4.1'; 180 PlugDesc(end).Category = 'Forward'; 181 PlugDesc(end).AutoUpdate = 1; 182 switch(OsType) 183 case 'linux64' 184 PlugDesc(end).URLzip = 'https://files.inria.fr/OpenMEEG/download/OpenMEEG-2.4.1-Linux.tar.gz'; 185 PlugDesc(end).TestFile = 'libOpenMEEG.so'; 186 case 'mac64' 187 PlugDesc(end).URLzip = 'https://files.inria.fr/OpenMEEG/download/OpenMEEG-2.4.1-MacOSX.tar.gz'; 188 PlugDesc(end).TestFile = 'libOpenMEEG.1.1.0.dylib'; 189 case 'win32' 190 PlugDesc(end).URLzip = 'https://files.inria.fr/OpenMEEG/download/release-2.2/OpenMEEG-2.2.0-win32-x86-cl-OpenMP-shared.tar.gz'; 191 PlugDesc(end).TestFile = 'om_assemble.exe'; 192 case 'win64' 193 PlugDesc(end).URLzip = 'https://files.inria.fr/OpenMEEG/download/OpenMEEG-2.4.1-Win64.tar.gz'; 194 PlugDesc(end).TestFile = 'om_assemble.exe'; 195 end 196 PlugDesc(end).URLinfo = 'https://openmeeg.github.io/'; 197 PlugDesc(end).ExtraMenus = {'Alternate versions', 'web(''https://files.inria.fr/OpenMEEG/download/'', ''-browser'')'; ... 198 'Download Visual C++', 'web(''http://www.microsoft.com/en-us/download/details.aspx?id=14632'', ''-browser'')'; ... 199 'Online tutorial', 'web(''https://neuroimage.usc.edu/brainstorm/Tutorials/TutBem'', ''-browser'')'}; 200 PlugDesc(end).CompiledStatus = 1; 201 PlugDesc(end).LoadFolders = {'bin', 'lib'}; 202 203 % === FORWARD: DUNEURO === 204 PlugDesc(end+1) = GetStruct('duneuro'); 205 PlugDesc(end).Version = 'latest'; 206 PlugDesc(end).Category = 'Forward'; 207 PlugDesc(end).AutoUpdate = 1; 208 PlugDesc(end).URLzip = 'http://neuroimage.usc.edu/bst/getupdate.php?d=bst_duneuro.zip'; 209 PlugDesc(end).URLinfo = 'https://neuroimage.usc.edu/brainstorm/Tutorials/Duneuro'; 210 PlugDesc(end).TestFile = 'bst_duneuro_meeg_win64.exe'; 211 PlugDesc(end).CompiledStatus = 1; 212 PlugDesc(end).LoadFolders = {'bin'}; 213 214 % === INVERSE: BRAINENTROPY === 215 PlugDesc(end+1) = GetStruct('brainentropy'); 216 PlugDesc(end).Version = 'github-master'; 217 PlugDesc(end).Category = 'Inverse'; 218 PlugDesc(end).AutoUpdate = 1; 219 PlugDesc(end).URLzip = 'https://github.com/multi-funkim/best-brainstorm/archive/master.zip'; 220 PlugDesc(end).URLinfo = 'https://neuroimage.usc.edu/brainstorm/Tutorials/TutBEst'; 221 PlugDesc(end).TestFile = 'process_inverse_mem.m'; 222 PlugDesc(end).AutoLoad = 1; 223 PlugDesc(end).CompiledStatus = 2; 224 PlugDesc(end).LoadFolders = {'*'}; 225 226 % === I/O: ADI-SDK === ADInstrument SDK for reading LabChart files 227 PlugDesc(end+1) = GetStruct('adi-sdk'); 228 PlugDesc(end).Version = 'github-master'; 229 PlugDesc(end).Category = 'I/O'; 230 switch (OsType) 231 case 'win64', PlugDesc(end).URLzip = 'https://github.com/JimHokanson/adinstruments_sdk_matlab/archive/master.zip'; 232 end 233 PlugDesc(end).URLinfo = 'https://github.com/JimHokanson/adinstruments_sdk_matlab'; 234 PlugDesc(end).TestFile = 'adi.m'; 235 PlugDesc(end).CompiledStatus = 0; 236 237 % === I/O: AXION === 238 PlugDesc(end+1) = GetStruct('axion'); 239 PlugDesc(end).Version = '1.0'; 240 PlugDesc(end).Category = 'I/O'; 241 PlugDesc(end).URLzip = 'http://neuroimage.usc.edu/bst/getupdate.php?d=AxionBioSystems.zip'; 242 PlugDesc(end).URLinfo = 'https://www.axionbiosystems.com/products/software/neural-module'; 243 PlugDesc(end).TestFile = 'AxisFile.m'; 244 % PlugDesc(end).ReadmeFile = 'README.md'; 245 PlugDesc(end).CompiledStatus = 0; 246 247 % === I/O: BLACKROCK === 248 PlugDesc(end+1) = GetStruct('blackrock'); 249 PlugDesc(end).Version = '5.5.2.0'; 250 PlugDesc(end).Category = 'I/O'; 251 PlugDesc(end).URLzip = 'https://github.com/BlackrockMicrosystems/NPMK/archive/refs/tags/5.5.2.0.zip'; 252 PlugDesc(end).URLinfo = 'https://github.com/BlackrockMicrosystems/NPMK/blob/master/NPMK/Users%20Guide.pdf'; 253 PlugDesc(end).TestFile = 'openNSx.m'; 254 PlugDesc(end).ReadmeFile = 'Versions.txt'; 255 PlugDesc(end).CompiledStatus = 2; 256 PlugDesc(end).LoadFolders = {'*'}; 257 258 % === I/O: MFF === 259 PlugDesc(end+1) = GetStruct('mff'); 260 PlugDesc(end).Version = 'github-master'; 261 PlugDesc(end).Category = 'I/O'; 262 PlugDesc(end).URLzip = 'https://github.com/arnodelorme/mffmatlabio/archive/master.zip'; 263 PlugDesc(end).URLinfo = 'https://github.com/arnodelorme/mffmatlabio'; 264 PlugDesc(end).TestFile = 'eegplugin_mffmatlabio.m'; 265 PlugDesc(end).ReadmeFile = 'README.md'; 266 PlugDesc(end).MinMatlabVer = 803; % 2014a 267 PlugDesc(end).CompiledStatus = 0; 268 PlugDesc(end).GetVersionFcn = @eegplugin_mffmatlabio; 269 PlugDesc(end).LoadedFcn = @Configure; 270 % Stable version: http://neuroimage.usc.edu/bst/getupdate.php?d='mffmatlabio-3.5.zip' 271 272 % === I/O: NWB === 273 PlugDesc(end+1) = GetStruct('nwb'); 274 PlugDesc(end).Version = 'github-master'; 275 PlugDesc(end).Category = 'I/O'; 276 PlugDesc(end).URLzip = 'https://github.com/NeurodataWithoutBorders/matnwb/archive/master.zip'; 277 PlugDesc(end).URLinfo = 'https://github.com/NeurodataWithoutBorders/matnwb'; 278 PlugDesc(end).TestFile = 'nwbRead.m'; 279 PlugDesc(end).ReadmeFile = 'README.md'; 280 PlugDesc(end).MinMatlabVer = 901; % 2016b 281 PlugDesc(end).CompiledStatus = 0; 282 PlugDesc(end).LoadFolders = {'*'}; 283 PlugDesc(end).LoadedFcn = @Configure; 284 285 % === I/O: PLOTLY === 286 PlugDesc(end+1) = GetStruct('plotly'); 287 PlugDesc(end).Version = 'github-master'; 288 PlugDesc(end).Category = 'I/O'; 289 PlugDesc(end).URLzip = 'https://github.com/plotly/plotly-graphing-library-for-matlab/archive/master.zip'; 290 PlugDesc(end).URLinfo = 'https://plotly.com/matlab/'; 291 PlugDesc(end).TestFile = 'plotlysetup.m'; 292 PlugDesc(end).ReadmeFile = 'README.mkdn'; 293 PlugDesc(end).CompiledStatus = 0; 294 PlugDesc(end).LoadFolders = {'*'}; 295 PlugDesc(end).ExtraMenus = {'Online tutorial', 'web(''https://neuroimage.usc.edu/brainstorm/Tutorials/Plotly'', ''-browser'')'}; 296 297 % === I/O: TDT-SDK === Tucker-Davis Technologies Matlab SDK 298 PlugDesc(end+1) = GetStruct('tdt-sdk'); 299 PlugDesc(end).Version = 'latest'; 300 PlugDesc(end).Category = 'I/O'; 301 PlugDesc(end).URLzip = 'https://www.tdt.com/files/examples/TDTMatlabSDK.zip'; 302 PlugDesc(end).URLinfo = 'https://www.tdt.com/support/matlab-sdk/'; 303 PlugDesc(end).TestFile = 'TDT_Matlab_Tools.pdf'; 304 PlugDesc(end).CompiledStatus = 0; 305 PlugDesc(end).LoadFolders = {'*'}; 306 307 % % === I/O: PLEXON-SDK === 308 % PlugDesc(end+1) = GetStruct('plexon'); 309 % PlugDesc(end).Version = 'latest'; 310 % PlugDesc(end).Category = 'I/O'; 311 % PlugDesc(end).URLzip = 'https://plexon.com/wp-content/uploads/2017/08/OmniPlex-and-MAP-Offline-SDK-Bundle_0.zip'; 312 % PlugDesc(end).URLinfo = 'https://plexon.com/software-downloads/#software-downloads-SDKs'; 313 % PlugDesc(end).TestFile = ''; 314 % PlugDesc(end).CompiledStatus = 0; 315 316 % === SIMULATION: SIMMEEG === 317 PlugDesc(end+1) = GetStruct('simmeeg'); 318 PlugDesc(end).Version = '21a'; 319 PlugDesc(end).Category = 'Simulation'; 320 PlugDesc(end).AutoUpdate = 1; 321 PlugDesc(end).URLzip = 'https://github.com/docath/BRANELab/raw/SimMEEG/SimMEEG_v21a.zip'; 322 PlugDesc(end).URLinfo = 'https://audiospeech.ubc.ca/research/brane/brane-lab-software/'; 323 PlugDesc(end).TestFile = 'SimMEEG_GUI_v21a.m'; 324 PlugDesc(end).ReadmeFile = 'SIMMEEG_TERMS_OF_USE.txt'; 325 PlugDesc(end).CompiledStatus = 0; 326 PlugDesc(end).RequiredPlugs = {'fieldtrip', '20200911'}; 327 328 % === STATISTICS: LIBSVM === 329 PlugDesc(end+1) = GetStruct('libsvm'); 330 PlugDesc(end).Version = 'github-master'; 331 PlugDesc(end).Category = 'Statistics'; 332 PlugDesc(end).URLzip = 'https://github.com/cjlin1/libsvm/archive/master.zip'; 333 PlugDesc(end).URLinfo = 'https://www.csie.ntu.edu.tw/~cjlin/libsvm/'; 334 PlugDesc(end).TestFile = 'svm.cpp'; 335 PlugDesc(end).ReadmeFile = 'README'; 336 PlugDesc(end).MinMatlabVer = 803; % 2014a 337 PlugDesc(end).CompiledStatus = 2; 338 PlugDesc(end).LoadFolders = {'*'}; 339 PlugDesc(end).InstalledFcn = 'd=pwd; cd(fileparts(which(''make''))); make; cd(d);'; 340 341 % === NIRSTORM === 342 PlugDesc(end+1) = GetStruct('nirstorm'); 343 PlugDesc(end).Version = 'github-master'; 344 PlugDesc(end).Category = 'fNIRS'; 345 PlugDesc(end).AutoUpdate = 0; 346 PlugDesc(end).AutoLoad = 1; 347 PlugDesc(end).CompiledStatus = 2; 348 PlugDesc(end).URLzip = 'https://github.com/Nirstorm/nirstorm/archive/master.zip'; 349 PlugDesc(end).URLinfo = 'https://github.com/Nirstorm/nirstorm'; 350 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'}; 351 PlugDesc(end).TestFile = 'process_nst_mbll.m'; 352 PlugDesc(end).ReadmeFile = 'README.md'; 353 PlugDesc(end).GetVersionFcn = 'nst_get_version'; 354 PlugDesc(end).RequiredPlugs = {'brainentropy'}; 355 PlugDesc(end).MinMatlabVer = 803; % 2014a 356 357 % === MIA === 358 PlugDesc(end+1) = GetStruct('mia'); 359 PlugDesc(end).Version = 'github-master'; 360 PlugDesc(end).Category = 'sEEG'; 361 PlugDesc(end).AutoUpdate = 0; 362 PlugDesc(end).AutoLoad = 1; 363 PlugDesc(end).CompiledStatus = 2; 364 PlugDesc(end).URLzip = 'https://github.com/MIA-iEEG/mia/archive/refs/heads/master.zip'; 365 PlugDesc(end).URLinfo = 'http://www.neurotrack.fr/mia/'; 366 PlugDesc(end).ReadmeFile = 'README.md'; 367 PlugDesc(end).GetVersionFcn = 'mia_get_version'; 368 PlugDesc(end).MinMatlabVer = 803; % 2014a 369 PlugDesc(end).LoadFolders = {'*'}; 370 PlugDesc(end).TestFile = 'process_mia_export_db.m'; 371 372 % === FIELDTRIP === 373 PlugDesc(end+1) = GetStruct('fieldtrip'); 374 PlugDesc(end).Version = 'latest'; 375 PlugDesc(end).AutoUpdate = 0; 376 PlugDesc(end).URLzip = 'ftp://ftp.fieldtriptoolbox.org/pub/fieldtrip/fieldtrip-lite-20210212.zip'; 377 PlugDesc(end).URLinfo = 'http://www.fieldtriptoolbox.org'; 378 PlugDesc(end).TestFile = 'ft_defaults.m'; 379 PlugDesc(end).ReadmeFile = 'README'; 380 PlugDesc(end).CompiledStatus = 2; 381 PlugDesc(end).UnloadPlugs = {'spm12', 'roast'}; 382 PlugDesc(end).LoadFolders = {'specest', 'preproc', 'forward', 'src', 'utilities', 'external/stats'}; 383 PlugDesc(end).GetVersionFcn = 'ft_version'; 384 PlugDesc(end).LoadedFcn = 'ft_defaults;'; 385 386 % === SPM12 === 387 PlugDesc(end+1) = GetStruct('spm12'); 388 PlugDesc(end).Version = 'latest'; 389 PlugDesc(end).AutoUpdate = 0; 390 PlugDesc(end).URLzip = 'https://www.fil.ion.ucl.ac.uk/spm/download/restricted/eldorado/spm12.zip'; 391 PlugDesc(end).URLinfo = 'https://www.fil.ion.ucl.ac.uk/spm/'; 392 PlugDesc(end).TestFile = 'spm.m'; 393 PlugDesc(end).ReadmeFile = 'README.md'; 394 PlugDesc(end).CompiledStatus = 2; 395 PlugDesc(end).UnloadPlugs = {'fieldtrip', 'roast'}; 396 PlugDesc(end).GetVersionFcn = 'bst_getoutvar(2, @spm, ''Ver'')'; 397 PlugDesc(end).LoadedFcn = 'spm(''defaults'',''EEG'');'; 398 % ================================================================================================================ 399 400 % Select only one plugin 401 if ~isempty(SelPlug) 402 % Get plugin name 403 if ischar(SelPlug) 404 PlugName = SelPlug; 405 else 406 PlugName = SelPlug.Name; 407 end 408 % Find in the list of plugins 409 iPlug = find(strcmpi({PlugDesc.Name}, PlugName)); 410 if ~isempty(iPlug) 411 PlugDesc = PlugDesc(iPlug); 412 else 413 PlugDesc = []; 414 end 415 end 416 end 417 418 419 %% ===== PLUGIN STRUCT ===== 420 function s = GetStruct(PlugName) 421 s = db_template('PlugDesc'); 422 s.Name = PlugName; 423 end 424 425 426 %% ===== CONFIGURE PLUGIN ===== 427 function Configure(PlugDesc) 428 switch (PlugDesc.Name) 429 case 'mff' 430 % Add .jar file to static classpath 431 if ~exist('com.egi.services.mff.api.MFFFactory', 'class') 432 jarList = dir(bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, 'MFF-*.jar')); 433 jarPath = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, jarList(1).name); 434 disp(['BST> Adding to Java classpath: ' jarPath]); 435 warning off 436 javaaddpathstatic(jarPath); 437 javaaddpath(jarPath); 438 warning on 439 end 440 441 case 'nwb' 442 % Add .jar file to static classpath 443 if ~exist('Schema', 'class') 444 jarPath = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, 'jar', 'schema.jar'); 445 disp(['BST> Adding to Java classpath: ' jarPath]); 446 warning off 447 javaaddpathstatic(jarPath); 448 javaaddpath(jarPath); 449 warning on 450 schema = Schema(); 451 end 452 % Go to NWB folder 453 curDir = pwd; 454 cd(bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder)); 455 % Generate the NWB Schema (must be executed from the NWB folder) 456 generateCore(); 457 % Restore current directory 458 cd(curDir); 459 end 460 end 461 462 463 %% ===== GET ONLINE VERSION ===== 464 % Get the latest online version of some plugins 465 function [Version, URLzip] = GetVersionOnline(PlugName, isCache) 466 global GlobalData; 467 Version = []; 468 URLzip = []; 469 % Ignore cache 470 if (nargin < 2) || isempty(isCache) 471 isCache = 1; 472 end 473 % No internet: skip 474 if ~GlobalData.Program.isInternet 475 return; 476 end 477 % Check for existing plugin cache 478 strCache = [PlugName, '_online_', strrep(date,'-','')]; 479 if isCache && isfield(GlobalData.Program.PluginCache, strCache) && isfield(GlobalData.Program.PluginCache.(strCache), 'Version') 480 Version = GlobalData.Program.PluginCache.(strCache).Version; 481 URLzip = GlobalData.Program.PluginCache.(strCache).URLzip; 482 return; 483 end 484 % Get version online 485 try 486 switch (PlugName) 487 case 'spm12' 488 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 489 disp(['BST> Checking latest online version for ' PlugName '...']); 490 s = bst_webread('http://www.fil.ion.ucl.ac.uk/spm/download/spm12_updates/'); 491 if ~isempty(s) 492 n = regexp(s,'spm12_updates_r(\d.*?)\.zip','tokens','once'); 493 if ~isempty(n) && ~isempty(n{1}) 494 Version = n{1}; 495 end 496 end 497 case 'cat12' 498 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 499 disp(['BST> Checking latest online version for ' PlugName '...']); 500 s = bst_webread('http://www.neuro.uni-jena.de/cat12/'); 501 if ~isempty(s) 502 n = regexp(s,'cat12_r(\d.*?)\.zip','tokens'); 503 if ~isempty(n) 504 Version = max(cellfun(@str2double, [n{:}])); 505 Version = num2str(Version); 506 end 507 end 508 case 'fieldtrip' 509 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 510 disp(['BST> Checking latest online version for ' PlugName '...']); 511 s = bst_webread('ftp://ftp.fieldtriptoolbox.org/pub/fieldtrip/'); 512 if ~isempty(s) 513 n = regexp(s,'fieldtrip-lite-(\d.*?)\.zip','tokens'); 514 if ~isempty(n) 515 Version = max(cellfun(@str2double, [n{:}])); 516 Version = num2str(Version); 517 URLzip = ['ftp://ftp.fieldtriptoolbox.org/pub/fieldtrip/fieldtrip-lite-' Version '.zip']; 518 end 519 end 520 case 'duneuro' 521 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 522 disp(['BST> Checking latest online version for ' PlugName '...']); 523 str = bst_webread('http://neuroimage.usc.edu/bst/getversion_duneuro.php'); 524 Version = str(1:6); 525 case 'nirstorm' 526 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 527 disp(['BST> Checking latest online version for ' PlugName '...']); 528 str = bst_webread('https://raw.githubusercontent.com/Nirstorm/nirstorm/master/bst_plugin/VERSION'); 529 Version = strtrim(str(9:end)); 530 otherwise 531 return; 532 end 533 % Executed only if the version was fetched successfully: Keep cached version 534 GlobalData.Program.PluginCache.(strCache).Version = Version; 535 GlobalData.Program.PluginCache.(strCache).URLzip = URLzip; 536 catch 537 disp(['BST> Error: Could not get online version for plugin: ' PlugName]); 538 end 539 end 540 541 542 %% ===== COMPARE VERSIONS ===== 543 % Returns: 0: v1==v2 544 % -1: v1<v2 545 % 1: v1>v2 546 function res = CompareVersions(v1, v2) 547 % Get numbers 548 iNum1 = find(ismember(v1, '0123456789')); 549 iNum2 = find(ismember(v2, '0123456789')); 550 iDot1 = find(v1 == '.'); 551 iDot2 = find(v2 == '.'); 552 % Equality (or one input empty) 553 if isequal(v1,v2) || isempty(v1) || isempty(v2) 554 res = 0; 555 % Only numbers 556 elseif (length(iNum1) == length(v1)) && (length(iNum2) == length(v2)) 557 n1 = str2double(v1); 558 n2 = str2double(v2); 559 if (n1 > n2) 560 res = 1; 561 elseif (n1 < n2) 562 res = -1; 563 else 564 res = 0; 565 end 566 % Format '1.2.3' 567 elseif (~isempty(iDot1) || ~isempty(iDot2)) && ~isempty(iNum1) && ~isempty(iNum2) 568 % Get subversions 1 569 split1 = str_split(v1, '.'); 570 sub1 = []; 571 for i = 1:length(split1) 572 t = str2num(split1{i}(ismember(split1{i},'0123456789'))); 573 if ~isempty(t) 574 sub1(end+1) = t; 575 else 576 break; 577 end 578 end 579 % Get subversions 1 580 split2 = str_split(v2, '.'); 581 sub2 = []; 582 for i = 1:length(split2) 583 t = str2num(split2{i}(ismember(split2{i},'0123456789'))); 584 if ~isempty(t) 585 sub2(end+1) = t; 586 else 587 break; 588 end 589 end 590 % Add extra zeros to the shortest (so that "1.2" is higher than "1") 591 if (length(sub1) < length(sub2)) 592 tmp = sub1; 593 sub1 = zeros(size(sub2)); 594 sub1(1:length(tmp)) = tmp; 595 elseif (length(sub1) > length(sub2)) 596 tmp = sub2; 597 sub2 = zeros(size(sub1)); 598 sub2(1:length(tmp)) = tmp; 599 end 600 % Compare number by number 601 for i = 1:length(sub1) 602 if (sub1(i) > sub2(i)) 603 res = 1; 604 return; 605 elseif (sub1(i) < sub2(i)) 606 res = -1; 607 return; 608 else 609 res = 0; 610 end 611 end 612 % Mixture of numbers and digits: natural sorting of strings 613 else 614 [s,I] = sort_nat({v1, v2}); 615 if (I(1) == 1) 616 res = -1; 617 else 618 res = 1; 619 end 620 end 621 end 622 623 624 %% ===== EXECUTE CALLBACK ===== 625 function [isOk, errMsg] = ExecuteCallback(PlugDesc, f) 626 isOk = 0; 627 errMsg = ''; 628 if ~isempty(PlugDesc.(f)) 629 try 630 if ischar(PlugDesc.(f)) 631 disp(['BST> Executing callback ' f ': ' PlugDesc.(f)]); 632 eval(PlugDesc.(f)); 633 elseif isa(PlugDesc.(f), 'function_handle') 634 disp(['BST> Executing callback ' f ': ' func2str(PlugDesc.(f))]); 635 feval(PlugDesc.(f), PlugDesc); 636 end 637 catch 638 errMsg = ['Error executing callback ' f ': ' 10 lasterr]; 639 return; 640 end 641 end 642 isOk = 1; 643 end 644 645 646 %% ===== GET INSTALLED PLUGINS ===== 647 % USAGE: [PlugDesc, SearchPlugs] = bst_plugin('GetInstalled', PlugName/PlugDesc) % Get one installed plugin 648 % [PlugDesc, SearchPlugs] = bst_plugin('GetInstalled') % Get all installed plugins 649 function [PlugDesc, SearchPlugs] = GetInstalled(SelPlug) 650 % Parse inputs 651 if (nargin < 1) || isempty(SelPlug) 652 SelPlug = []; 653 end 654 655 % === DEFINE SEARCH LIST === 656 % Looking for a single plugin 657 if ~isempty(SelPlug) 658 SearchPlugs = GetSupported(SelPlug); 659 % Looking for all supported plugins 660 else 661 SearchPlugs = GetSupported(); 662 end 663 % Brainstorm plugin folder 664 UserPluginsDir = bst_get('UserPluginsDir'); 665 % Custom plugin paths 666 PluginCustomPath = bst_get('PluginCustomPath'); 667 % Matlab path 668 matlabPath = str_split(path, pathsep); 669 % Compiled distribution 670 isCompiled = bst_iscompiled(); 671 672 % === LOOK FOR SUPPORTED PLUGINS === 673 % Empty plugin structure 674 PlugDesc = repmat(db_template('PlugDesc'), 0); 675 % Look for each plugin in the search list 676 for iSearch = 1:length(SearchPlugs) 677 % Compiled: skip plugins that are not available 678 if isCompiled && (SearchPlugs(iSearch).CompiledStatus == 0) 679 continue; 680 end 681 % Theoretical plugin path 682 PlugName = SearchPlugs(iSearch).Name; 683 PlugPath = bst_fullfile(UserPluginsDir, PlugName); 684 % Check if test function is available in the Matlab path 685 TestFilePath = GetTestFilePath(SearchPlugs(iSearch)); 686 % If installed software found in Matlab path 687 if ~isempty(TestFilePath) 688 % Register loaded plugin 689 iPlug = length(PlugDesc) + 1; 690 PlugDesc(iPlug) = SearchPlugs(iSearch); 691 PlugDesc(iPlug).isLoaded = 1; 692 % Check if the file is inside the Brainstorm user folder (where it is supposed to be) => Managed plugin 693 if ~isempty(strfind(TestFilePath, PlugPath)) 694 PlugDesc(iPlug).isManaged = 1; 695 % Process compiled together with Brainstorm 696 elseif isCompiled && ~isempty(strfind(TestFilePath, ['.brainstorm' filesep 'plugins' filesep PlugName])) 697 compiledDir = ['.brainstorm' filesep 'plugins' filesep PlugName]; 698 iPath = strfind(TestFilePath, compiledDir); 699 PlugPath = [TestFilePath(1:iPath-2), filesep, compiledDir]; 700 % Otherwise: Custom installation 701 else 702 % If the test file was found in a defined subfolder: remove the subfolder from the plugin path 703 PlugPath = TestFilePath; 704 for iSub = 1:length(PlugDesc(iPlug).LoadFolders) 705 subDir = strrep(PlugDesc(iPlug).LoadFolders{iSub}, '/', filesep); 706 if (length(PlugPath) > length(subDir)) && isequal(PlugPath(end-length(subDir)+1:end), subDir) 707 PlugPath = PlugPath(1:end - length(subDir) - 1); 708 break; 709 end 710 end 711 PlugDesc(iPlug).isManaged = 0; 712 end 713 PlugDesc(iPlug).Path = PlugPath; 714 % Plugin installed: Managed by Brainstorm 715 elseif isdir(PlugPath) && file_exist(bst_fullfile(PlugPath, 'plugin.mat')) 716 iPlug = length(PlugDesc) + 1; 717 PlugDesc(iPlug) = SearchPlugs(iSearch); 718 PlugDesc(iPlug).Path = PlugPath; 719 PlugDesc(iPlug).isLoaded = 0; 720 PlugDesc(iPlug).isManaged = 1; 721 % Plugin installed: Custom path 722 elseif isfield(PluginCustomPath, PlugName) && ~isempty(PluginCustomPath.(PlugName)) && file_exist(PluginCustomPath.(PlugName)) 723 iPlug = length(PlugDesc) + 1; 724 PlugDesc(iPlug) = SearchPlugs(iSearch); 725 PlugDesc(iPlug).Path = PluginCustomPath.(PlugName); 726 PlugDesc(iPlug).isLoaded = 0; 727 PlugDesc(iPlug).isManaged = 0; 728 end 729 end 730 731 % === LOOK FOR UNREFERENCED PLUGINS === 732 % Compiled: do not look for unreferenced plugins 733 if isCompiled 734 PlugList = []; 735 % Get folders in Brainstorm user folder 736 elseif ~isempty(SelPlug) 737 if ischar(SelPlug) 738 PlugList = dir(bst_fullfile(UserPluginsDir, SelPlug)); 739 else 740 PlugList = dir(bst_fullfile(UserPluginsDir, SelPlug.Name)); 741 end 742 else 743 PlugList = dir(UserPluginsDir); 744 end 745 % Process folders containing a plugin.mat file 746 for iDir = 1:length(PlugList) 747 % Process only folders containing a 'plugin.mat' file and not already referenced 748 PlugDir = bst_fullfile(UserPluginsDir, PlugList(iDir).name); 749 PlugMatFile = bst_fullfile(PlugDir, 'plugin.mat'); 750 if ~isdir(PlugDir) || (PlugList(iDir).name(1) == '.') || ~file_exist(PlugMatFile) || ismember(PlugList(iDir).name, {PlugDesc.Name}) 751 continue; 752 end 753 % If selecting only one plugin 754 if ~isempty(SelPlug) && ~strcmpi(PlugList(iDir).name, SelPlug) 755 continue; 756 end 757 % Add plugin to list 758 iPlug = length(PlugDesc) + 1; 759 PlugDesc(iPlug) = GetStruct(PlugList(iDir).name); 760 PlugDesc(iPlug).Path = PlugDir; 761 PlugDesc(iPlug).isManaged = 1; 762 PlugDesc(iPlug).isLoaded = ismember(PlugDir, matlabPath); 763 end 764 765 % === READ PLUGIN.MAT === 766 for iPlug = 1:length(PlugDesc) 767 % Try to load the plugin.mat file in the plugin folder 768 PlugMatFile = bst_fullfile(PlugDesc(iPlug).Path, 'plugin.mat'); 769 if file_exist(PlugMatFile) 770 try 771 PlugMat = load(PlugMatFile); 772 catch 773 PlugMat = struct(); 774 end 775 % Copy fields 776 excludedFields = {'Name', 'Path', 'isLoaded', 'isManaged', 'LoadedFcn', 'UnloadedFcn', 'InstalledFcn', 'UninstalledFcn'}; 777 loadFields = setdiff(fieldnames(db_template('PlugDesc')), excludedFields); 778 for iField = 1:length(loadFields) 779 if isfield(PlugMat, loadFields{iField}) && ~isempty(PlugMat.(loadFields{iField})) 780 PlugDesc(iPlug).(loadFields{iField}) = PlugMat.(loadFields{iField}); 781 end 782 end 783 else 784 PlugDesc(iPlug).URLzip = []; 785 end 786 end 787 end 788 789 790 %% ===== GET DESCRIPTION ===== 791 % USAGE: [PlugDesc, errMsg] = GetDescription(PlugName/PlugDesc) 792 function [PlugDesc, errMsg] = GetDescription(PlugName) 793 % Initialize returned values 794 errMsg = ''; 795 PlugDesc = []; 796 % CALL: GetDescription(PlugDesc) 797 if isstruct(PlugName) 798 % Add the missing fields 799 PlugDesc = struct_copy_fields(PlugName, db_template('PlugDesc'), 0); 800 % CALL: GetDescription(PlugName) 801 elseif ischar(PlugName) 802 % Get supported plugins 803 AllPlugs = GetSupported(); 804 % Find plugin in supported plugins 805 iPlug = find(strcmpi({AllPlugs.Name}, PlugName)); 806 if isempty(iPlug) 807 errMsg = ['Unknown plugin: ' PlugName]; 808 return; 809 end 810 % Return found plugin 811 PlugDesc = AllPlugs(iPlug); 812 else 813 errMsg = 'Invalid call to GetDescription().'; 814 end 815 end 816 817 818 %% ===== GET TEST FILE PATH ===== 819 function TestFilePath = GetTestFilePath(PlugDesc) 820 % If a test file is defined 821 if ~isempty(PlugDesc.TestFile) 822 % Try to find the test function in the path 823 whichTest = which(PlugDesc.TestFile); 824 % If it was found: use the parent folder 825 if ~isempty(whichTest) 826 % Get the test file path 827 TestFilePath = bst_fileparts(whichTest); 828 % FieldTrip: Ignore if found embedded in SPM12 829 if strcmpi(PlugDesc.Name, 'fieldtrip') 830 p = which('spm.m'); 831 if ~isempty(p) && ~isempty(strfind(TestFilePath, bst_fileparts(p))) 832 TestFilePath = []; 833 end 834 % SPM12: Ignore if found embedded in ROAST 835 elseif strcmpi(PlugDesc.Name, 'spm12') 836 p = which('roast.m'); 837 if ~isempty(p) && ~isempty(strfind(TestFilePath, bst_fileparts(p))) 838 TestFilePath = []; 839 end 840 % Iso2mesh: Ignore if found embedded in ROAST 841 elseif strcmpi(PlugDesc.Name, 'iso2mesh') 842 p = which('roast.m'); 843 if ~isempty(p) && ~isempty(strfind(TestFilePath, bst_fileparts(p))) 844 TestFilePath = []; 845 end 846 end 847 else 848 TestFilePath = []; 849 end 850 else 851 TestFilePath = []; 852 end 853 end 854 855 856 %% ===== GET README FILE ==== 857 % Get full path to the readme file 858 function ReadmeFile = GetReadmeFile(PlugDesc) 859 ReadmeFile = []; 860 % If readme file is defined in the plugin structure 861 if ~isempty(PlugDesc.ReadmeFile) 862 % If full path already set: use it 863 if file_exist(PlugDesc.ReadmeFile) 864 ReadmeFile = PlugDesc.ReadmeFile; 865 % Else: check in the plugin Path/SubFolder 866 else 867 tmpFile = bst_fullfile(PlugDesc.Path, PlugDesc.ReadmeFile); 868 if file_exist(tmpFile) 869 ReadmeFile = tmpFile; 870 elseif ~isempty(PlugDesc.SubFolder) 871 tmpFile = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, PlugDesc.ReadmeFile); 872 if file_exist(tmpFile) 873 ReadmeFile = tmpFile; 874 end 875 end 876 end 877 end 878 % Search for default readme 879 if isempty(ReadmeFile) 880 tmpFile = bst_fullfile(bst_get('BrainstormDocDir'), 'plugins', [PlugDesc.Name '_readme.txt']); 881 if file_exist(tmpFile) 882 ReadmeFile = tmpFile; 883 end 884 end 885 end 886 887 888 %% ===== GET LOGO FILE ==== 889 % Get full path to the logo file 890 function LogoFile = GetLogoFile(PlugDesc) 891 LogoFile = []; 892 % If logo file is defined in the plugin structure 893 if ~isempty(PlugDesc.LogoFile) 894 % If full path already set: use it 895 if file_exist(PlugDesc.LogoFile) 896 LogoFile = PlugDesc.LogoFile; 897 % Else: check in the plugin Path/SubFolder 898 else 899 tmpFile = bst_fullfile(PlugDesc.Path, PlugDesc.LogoFile); 900 if file_exist(tmpFile) 901 LogoFile = tmpFile; 902 elseif ~isempty(PlugDesc.SubFolder) 903 tmpFile = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, PlugDesc.LogoFile); 904 if file_exist(tmpFile) 905 LogoFile = tmpFile; 906 end 907 end 908 end 909 end 910 % Search for default logo 911 if isempty(LogoFile) 912 tmpFile = bst_fullfile(bst_get('BrainstormDocDir'), 'plugins', [PlugDesc.Name '_logo.gif']); 913 if file_exist(tmpFile) 914 LogoFile = tmpFile; 915 end 916 end 917 if isempty(LogoFile) 918 tmpFile = bst_fullfile(bst_get('BrainstormDocDir'), 'plugins', [PlugDesc.Name '_logo.png']); 919 if file_exist(tmpFile) 920 LogoFile = tmpFile; 921 end 922 end 923 end 924 925 926 %% ===== INSTALL ===== 927 % USAGE: [isOk, errMsg, PlugDesc] = bst_plugin('Install', PlugName, isInteractive=0, minVersion=[]) 928 function [isOk, errMsg, PlugDesc] = Install(PlugName, isInteractive, minVersion) 929 % Returned variables 930 isOk = 0; 931 % Parse inputs 932 if (nargin < 3) || isempty(minVersion) 933 minVersion = []; 934 elseif isnumeric(minVersion) 935 minVersion = num2str(minVersion); 936 end 937 if (nargin < 2) || isempty(isInteractive) 938 isInteractive = 0; 939 end 940 if ~ischar(PlugName) 941 errMsg = 'Invalid call to Install()'; 942 PlugDesc = []; 943 return; 944 end 945 % Get plugin structure from name 946 [PlugDesc, errMsg] = GetDescription(PlugName); 947 if ~isempty(errMsg) 948 return; 949 end 950 % Check if there is a URL to download 951 if isempty(PlugDesc.URLzip) 952 errMsg = ['No download URL for ', bst_get('OsType', 0), ': ', PlugName '']; 953 return; 954 end 955 % Compiled version 956 isCompiled = bst_iscompiled(); 957 if isCompiled && (PlugDesc.CompiledStatus == 0) 958 errMsg = ['Plugin ', PlugName ' is not available in the compiled version of Brainstorm.']; 959 return; 960 end 961 % Minimum Matlab version 962 if ~isempty(PlugDesc.MinMatlabVer) && (PlugDesc.MinMatlabVer > 0) && (bst_get('MatlabVersion') < PlugDesc.MinMatlabVer) 963 strMinVer = sprintf('%d.%d', ceil(PlugDesc.MinMatlabVer / 100), mod(PlugDesc.MinMatlabVer, 100)); 964 errMsg = ['Plugin ', PlugName ' is not supported for versions of Matlab <= ' strMinVer]; 965 return; 966 end 967 % Get online update 968 [newVersion, newURLzip] = GetVersionOnline(PlugName); 969 if ~isempty(newVersion) 970 PlugDesc.Version = newVersion; 971 end 972 if ~isempty(newURLzip) 973 PlugDesc.URLzip = newURLzip; 974 end 975 976 % === PROCESS DEPENDENCIES === 977 % Check required plugins 978 if ~isempty(PlugDesc.RequiredPlugs) 979 bst_progress('text', ['Processing dependencies for ' PlugName '...']); 980 disp(['BST> Processing dependencies: ' PlugName ' requires: ' sprintf('%s ', PlugDesc.RequiredPlugs{:,1})]); 981 % Get the list of plugins that need to be installed 982 installPlugs = {}; 983 installVer = {}; 984 strInstall = ''; 985 for iPlug = 1:size(PlugDesc.RequiredPlugs,1) 986 PlugCheck = GetInstalled(PlugDesc.RequiredPlugs{iPlug,1}); 987 % Plugin not install: Install it 988 if isempty(PlugCheck) 989 installPlugs{end+1} = PlugDesc.RequiredPlugs{iPlug,1}; 990 installVer{end+1} = []; 991 strInstall = [strInstall, '<B>' installPlugs{end} '</B> ']; 992 % Plugin installed: check version 993 elseif (size(PlugDesc.RequiredPlugs,2) == 2) 994 minVerDep = PlugDesc.RequiredPlugs{iPlug,2}; 995 if ~isempty(minVerDep) && (CompareVersions(minVerDep, PlugCheck.Version) > 0) 996 installPlugs{end+1} = PlugDesc.RequiredPlugs{iPlug,1}; 997 installVer{end+1} = PlugDesc.RequiredPlugs{iPlug,2}; 998 strInstall = [strInstall, '<B>' installPlugs{end} '</B>(' installVer{end} ') ']; 999 end 1000 end 1001 end 1002 % If there are plugins to install 1003 if ~isempty(installPlugs) 1004 if isInteractive 1005 java_dialog('msgbox', ['<HTML>Plugin <B>' PlugName '</B> requires: ' strInstall ... 1006 '<BR><BR>Brainstorm will now install these plugins.' 10 10], 'Plugin manager'); 1007 end 1008 for iPlug = 1:length(installPlugs) 1009 [isInstalled, errMsg] = Install(installPlugs{iPlug}, isInteractive, installPlugs{iPlug}); 1010 if ~isInstalled 1011 errMsg = ['Error processing dependency: ' PlugDesc.RequiredPlugs{iPlug,1} 10 errMsg]; 1012 return; 1013 end 1014 end 1015 end 1016 end 1017 1018 % === UPDATE: CHECK PREVIOUS INSTALL === 1019 % Check if installed 1020 OldPlugDesc = GetInstalled(PlugName); 1021 % If already installed 1022 if ~isempty(OldPlugDesc) 1023 % If the plugin is not managed by Brainstorm: do not check versions 1024 if ~OldPlugDesc.isManaged 1025 isUpdate = 0; 1026 % If the requested version is higher 1027 elseif ~isempty(minVersion) && (CompareVersions(minVersion, OldPlugDesc.Version) > 0) 1028 isUpdate = 1; 1029 strUpdate = ['the installed version is outdated.<BR>Minimum version required: <I>' minVersion '</I>']; 1030 % If an update is available and auto-updates are requested 1031 elseif (PlugDesc.AutoUpdate == 1) && bst_get('AutoUpdates') && (CompareVersions(PlugDesc.Version, OldPlugDesc.Version) > 0) 1032 isUpdate = 1; 1033 strUpdate = 'an update is available online.'; 1034 else 1035 isUpdate = 0; 1036 end 1037 % Update plugin 1038 if isUpdate 1039 % Compare versions 1040 strCompare = ['<FONT color="#707070">' ... 1041 'Old version :     <I>' OldPlugDesc.Version '</I><BR>' ... 1042 'New version :   <I>' PlugDesc.Version '</I></FONT><BR><BR>']; 1043 % Ask user for updating 1044 if isInteractive 1045 isConfirm = java_dialog('confirm', ... 1046 ['<HTML>Plugin <B>' PlugName '</B>: ' strUpdate '<BR>' ... 1047 'Download and install the latest version?<BR><BR>' strCompare], 'Plugin manager'); 1048 % If update not confirmed: simply load the existing plugin 1049 if ~isConfirm 1050 [isOk, errMsg, PlugDesc] = Load(PlugDesc); 1051 return; 1052 end 1053 end 1054 disp(['BST> Plugin ' PlugName ' is outdated and will be updated.']); 1055 % Uninstall existing plugin 1056 [isOk, errMsg] = Uninstall(PlugName, 0, 0); 1057 if ~isOk 1058 errMsg = ['An error occurred while updating plugin ' PlugName ':' 10 10 errMsg 10]; 1059 return; 1060 end 1061 1062 % No update: Load existing plugin and return 1063 else 1064 % Load plugin 1065 if ~OldPlugDesc.isLoaded 1066 [isLoaded, errMsg, PlugDesc] = Load(OldPlugDesc); 1067 if ~isLoaded 1068 errMsg = ['Could not load plugin ' PlugName ':' 10 errMsg]; 1069 return; 1070 end 1071 else 1072 disp(['BST> Plugin ' PlugName ' already loaded: ' OldPlugDesc.Path]); 1073 end 1074 % Return old plugin 1075 PlugDesc = OldPlugDesc; 1076 isOk = 1; 1077 return; 1078 end 1079 else 1080 % Get user confirmation 1081 if isInteractive 1082 if ~isempty(PlugDesc.Version) && ~isequal(PlugDesc.Version, 'github-master') && ~isequal(PlugDesc.Version, 'latest') 1083 strVer = ['<FONT color="#707070">Latest version: ' PlugDesc.Version '</FONT><BR><BR>']; 1084 else 1085 strVer = ''; 1086 end 1087 isConfirm = java_dialog('confirm', ... 1088 ['<HTML>Plugin <B>' PlugName '</B> is not installed on your computer.<BR>' ... 1089 '<B>Download</B> the latest version of ' PlugName ' now?<BR><BR>' ... 1090 strVer, ... 1091 '<FONT color="#707070">If this program is available on your computer,<BR>' ... 1092 'cancel this installation and use the menu: Plugins > <BR>' ... 1093 PlugName ' > Custom install > Set installation folder.</FONT><BR><BR>'], 'Plugin manager'); 1094 if ~isConfirm 1095 errMsg = 'Installation aborted by user.'; 1096 return; 1097 end 1098 end 1099 end 1100 1101 % === INSTALL PLUGIN === 1102 bst_progress('text', ['Installing plugin ' PlugName '...']); 1103 % Managed plugin folder 1104 PlugPath = bst_fullfile(bst_get('UserPluginsDir'), PlugName); 1105 % Delete existing folder 1106 if isdir(PlugPath) 1107 file_delete(PlugPath, 1, 3); 1108 end 1109 % Create folder 1110 if ~isdir(PlugPath) 1111 res = mkdir(PlugPath); 1112 if ~res 1113 errMsg = ['Error: Cannot create folder' 10 PlugPath]; 1114 return 1115 end 1116 end 1117 % Setting progressbar image 1118 LogoFile = GetLogoFile(PlugDesc); 1119 if ~isempty(LogoFile) 1120 bst_progress('setimage', LogoFile); 1121 end 1122 % Get package file format 1123 if strcmpi(PlugDesc.URLzip(end-3:end), '.zip') 1124 pkgFormat = 'zip'; 1125 elseif strcmpi(PlugDesc.URLzip(end-6:end), '.tar.gz') || strcmpi(PlugDesc.URLzip(end-3:end), '.tgz') 1126 pkgFormat = 'tgz'; 1127 else 1128 disp('BST> Could not guess file format, trying ZIP...'); 1129 pkgFormat = 'zip'; 1130 end 1131 % Download file 1132 pkgFile = bst_fullfile(PlugPath, ['plugin.' pkgFormat]); 1133 disp(['BST> Downloading URL : ' PlugDesc.URLzip]); 1134 disp(['BST> Saving to file : ' pkgFile]); 1135 errMsg = gui_brainstorm('DownloadFile', PlugDesc.URLzip, pkgFile, ['Download plugin: ' PlugName], LogoFile); 1136 % If file was not downloaded correctly 1137 if ~isempty(errMsg) 1138 errMsg = ['Impossible to download ' PlugName ' automatically:' 10 errMsg]; 1139 if ~isCompiled 1140 errMsg = [errMsg 10 10 ... 1141 'Alternative download solution:' 10 ... 1142 '1) Copy the URL below from the Matlab command window: ' 10 ... 1143 ' ' PlugDesc.URLzip 10 ... 1144 '2) Paste it in a web browser' 10 ... 1145 '3) Save the file and unzip it' 10 ... 1146 '4) Add to the Matlab path the folder containing ' PlugDesc.TestFile '.']; 1147 end 1148 bst_progress('removeimage'); 1149 return; 1150 end 1151 % Update progress bar 1152 bst_progress('text', ['Installing plugin: ' PlugName '...']); 1153 if ~isempty(LogoFile) 1154 bst_progress('setimage', LogoFile); 1155 end 1156 % Unzip file 1157 switch (pkgFormat) 1158 case 'zip' 1159 bst_unzip(pkgFile, PlugPath); 1160 case 'tgz' 1161 if ispc 1162 untar(pkgFile, PlugPath); 1163 else 1164 curdir = pwd; 1165 cd(PlugPath); 1166 system(['tar -xf ' pkgFile]); 1167 cd(curdir); 1168 end 1169 end 1170 file_delete(pkgFile, 1, 3); 1171 % Save plugin.mat 1172 PlugDesc.Path = PlugPath; 1173 PlugMatFile = bst_fullfile(PlugDesc.Path, 'plugin.mat'); 1174 excludedFields = {'LoadedFcn', 'UnloadedFcn', 'InstalledFcn', 'UninstalledFcn', 'Path', 'isLoaded', 'isManaged'}; 1175 PlugDescSave = rmfield(PlugDesc, excludedFields); 1176 bst_save(PlugMatFile, PlugDescSave, 'v6'); 1177 1178 % === SEARCH PROCESSES === 1179 % Look for process_* functions in the process folder 1180 PlugProc = file_find(PlugPath, 'process_*.m', Inf, 0); 1181 if ~isempty(PlugProc) 1182 % Remove absolute path: use only path relative to the plugin Path 1183 PlugDesc.Processes = cellfun(@(c)file_win2unix(strrep(c, [PlugPath, filesep], '')), PlugProc, 'UniformOutput', 0); 1184 end 1185 1186 % === LOAD PLUGIN === 1187 % Load plugin 1188 [isOk, errMsg, PlugDesc] = Load(PlugDesc); 1189 if ~isOk 1190 bst_progress('removeimage'); 1191 return; 1192 end 1193 1194 % === SAVE PLUGIN.MAT === 1195 % Get readme and logo 1196 PlugDesc.ReadmeFile = GetReadmeFile(PlugDesc); 1197 PlugDesc.LogoFile = GetLogoFile(PlugDesc); 1198 % Update plugin.mat after loading 1199 PlugDescSave = rmfield(PlugDesc, excludedFields); 1200 bst_save(PlugMatFile, PlugDescSave, 'v6'); 1201 1202 % === CALLBACK: POST-INSTALL === 1203 [isOk, errMsg] = ExecuteCallback(PlugDesc, 'InstalledFcn'); 1204 if ~isOk 1205 return; 1206 end 1207 1208 % === GET INSTALLED VERSION === 1209 % Get installed version 1210 if ~isempty(PlugDesc.GetVersionFcn) 1211 testVer = []; 1212 try 1213 if ischar(PlugDesc.GetVersionFcn) 1214 testVer = eval(PlugDesc.GetVersionFcn); 1215 elseif isa(PlugDesc.GetVersionFcn, 'function_handle') 1216 testVer = feval(PlugDesc.GetVersionFcn); 1217 end 1218 catch 1219 disp(['BST> Could not get installed version with callback: ' PlugDesc.GetVersionFcn]); 1220 end 1221 if ~isempty(testVer) 1222 PlugDesc.Version = testVer; 1223 % Update plugin.mat 1224 PlugDescSave.Version = testVer; 1225 bst_save(PlugMatFile, PlugDescSave, 'v6'); 1226 end 1227 end 1228 1229 % === SHOW PLUGIN INFO === 1230 % Log install 1231 bst_webread(['http://neuroimage.usc.edu/bst/pluglog.php?c=K8Yda7B&plugname=' PlugDesc.Name '&action=install']); 1232 % Show plugin information (interactive mode only) 1233 if isInteractive 1234 % Hide progress bar 1235 isProgress = bst_progress('isVisible'); 1236 if isProgress 1237 bst_progress('hide'); 1238 end 1239 % Message box: aknowledgements 1240 java_dialog('msgbox', ['<HTML>Plugin <B>' PlugName '</B> was sucessfully installed.<BR><BR>' ... 1241 'This software is not distributed by the Brainstorm developers.<BR>' ... 1242 'Please take a few minutes to read the license information,<BR>' ... 1243 'check the authors'' website and register online if recommended.<BR><BR>' ... 1244 '<B>Cite the authors</B> in your publications if you are using their software.<BR><BR>'], 'Plugin manager'); 1245 % Show the readme file 1246 if ~isempty(PlugDesc.ReadmeFile) 1247 view_text(PlugDesc.ReadmeFile, ['Installed plugin: ' PlugName], 1, 1); 1248 end 1249 % Open the website 1250 if ~isempty(PlugDesc.URLinfo) 1251 web(PlugDesc.URLinfo, '-browser') 1252 end 1253 % Restore progress bar 1254 if isProgress 1255 bst_progress('show'); 1256 end 1257 end 1258 % Remove logo 1259 bst_progress('removeimage'); 1260 % Return success 1261 isOk = 1; 1262 end 1263 1264 1265 %% ===== INSTALL INTERACTIVE ===== 1266 % USAGE: [isOk, errMsg, PlugDesc] = bst_plugin('InstallInteractive', PlugName) 1267 function [isOk, errMsg, PlugDesc] = InstallInteractive(PlugName) 1268 % Open progress bar 1269 isProgress = bst_progress('isVisible'); 1270 if ~isProgress 1271 bst_progress('start', 'Plugin manager', 'Initialization...'); 1272 end 1273 % Call silent function 1274 [isOk, errMsg, PlugDesc] = Install(PlugName, 1); 1275 % Handle errors 1276 if ~isOk 1277 bst_error(['Installation error:' 10 10 errMsg 10], 'Plugin manager', 0); 1278 elseif ~isempty(errMsg) 1279 java_dialog('msgbox', ['Installation message:' 10 10 errMsg 10], 'Plugin manager'); 1280 end 1281 % Close progress bar 1282 if ~isProgress 1283 bst_progress('stop'); 1284 end 1285 end 1286 1287 1288 %% ===== INSTALL MULTIPLE CHOICE ===== 1289 % If multiple plugins provide the same functions (eg. FieldTrip and SPM): make sure at least one is installed 1290 % USAGE: [isOk, errMsg, PlugDesc] = bst_plugin('InstallMultipleChoice', PlugNames, isInteractive) 1291 function [isOk, errMsg, PlugDesc] = InstallMultipleChoice(PlugNames, isInteractive) 1292 % Check if one of the plugins is loaded 1293 for iPlug = 1:length(PlugNames) 1294 PlugInst = GetInstalled(PlugNames{iPlug}); 1295 if ~isempty(PlugInst) 1296 [isOk, errMsg, PlugDesc] = Load(PlugNames{iPlug}); 1297 if isOk 1298 return; 1299 end 1300 end 1301 end 1302 % If no plugin is loaded: Install the first in the list 1303 [isOk, errMsg, PlugDesc] = Install(PlugNames{1}, isInteractive); 1304 end 1305 1306 1307 %% ===== UNINSTALL ===== 1308 % USAGE: [isOk, errMsg] = bst_plugin('Uninstall', PlugName, isInteractive=0, isDependencies=1) 1309 function [isOk, errMsg] = Uninstall(PlugName, isInteractive, isDependencies) 1310 % Returned variables 1311 isOk = 0; 1312 errMsg = ''; 1313 % Parse inputs 1314 if (nargin < 3) || isempty(isDependencies) 1315 isDependencies = 1; 1316 end 1317 if (nargin < 2) || isempty(isInteractive) 1318 isInteractive = 0; 1319 end 1320 if ~ischar(PlugName) 1321 errMsg = 'Invalid call to Uninstall()'; 1322 return; 1323 end 1324 1325 % === CHECK INSTALLATION === 1326 % Get installation 1327 PlugDesc = GetInstalled(PlugName); 1328 % External plugin 1329 if ~isempty(PlugDesc) && ~isequal(PlugDesc.isManaged, 1) 1330 errMsg = ['<HTML>Plugin <B>' PlugName '</B> is not managed by Brainstorm.' 10 'Delete folder manually:' 10 PlugDesc.Path]; 1331 return; 1332 % Plugin not installed: check if folder exists 1333 elseif isempty(PlugDesc) || isempty(PlugDesc.Path) 1334 % Get plugin structure from name 1335 [PlugDesc, errMsg] = GetDescription(PlugName); 1336 if ~isempty(errMsg) 1337 return; 1338 end 1339 % Managed plugin folder 1340 PlugPath = bst_fullfile(bst_get('UserPluginsDir'), PlugName); 1341 else 1342 PlugPath = PlugDesc.Path; 1343 end 1344 % Plugin not installed 1345 if ~file_exist(PlugPath) 1346 errMsg = ['Plugin ' PlugName ' is not installed.']; 1347 return; 1348 end 1349 1350 % === USER CONFIRMATION === 1351 if isInteractive 1352 isConfirm = java_dialog('confirm', ['<HTML>Delete permanently plugin <B>' PlugName '</B>?' 10 10 PlugPath 10 10], 'Plugin manager'); 1353 if ~isConfirm 1354 errMsg = 'Uninstall aborted by user.'; 1355 return; 1356 end 1357 end 1358 1359 % === PROCESS DEPENDENCIES === 1360 % Uninstall dependent plugins 1361 if isDependencies 1362 AllPlugs = GetSupported(); 1363 for iPlug = 1:length(AllPlugs) 1364 if ~isempty(AllPlugs(iPlug).RequiredPlugs) && ismember(PlugDesc.Name, AllPlugs(iPlug).RequiredPlugs(:,1)) 1365 disp(['BST> Uninstalling dependent plugin: ' AllPlugs(iPlug).Name]); 1366 Uninstall(AllPlugs(iPlug).Name, isInteractive); 1367 end 1368 end 1369 end 1370 1371 % === UNLOAD === 1372 if isequal(PlugDesc.isLoaded, 1) 1373 [isUnloaded, errMsgUnload] = Unload(PlugDesc); 1374 if ~isempty(errMsgUnload) 1375 disp(['BST> Error unloading plugin ' PlugName ': ' errMsgUnload]); 1376 end 1377 end 1378 1379 % === UNINSTALL === 1380 disp(['BST> Deleting plugin ' PlugName ': ' PlugPath]); 1381 % Delete plugin folder 1382 isDeleted = file_delete(PlugPath, 1, 3); 1383 if (isDeleted ~= 1) 1384 errMsg = ['Could not delete plugin folder: ' 10 PlugPath 10 10 ... 1385 'There is probably a file in that folder that is currently ' 10 ... 1386 'loaded in Matlab, but that cannot be unloaded dynamically.' 10 10 ... 1387 'Brainstorm will now close Matlab.' 10 ... 1388 'Restart Matlab and install again the plugin.' 10 10]; 1389 if isInteractive 1390 java_dialog('error', errMsg, 'Restart Matlab'); 1391 else 1392 disp([10 10 'BST> ' errMsg]); 1393 end 1394 quit('force'); 1395 end 1396 1397 % === CALLBACK: POST-UNINSTALL === 1398 [isOk, errMsg] = ExecuteCallback(PlugDesc, 'UninstalledFcn'); 1399 if ~isOk 1400 return; 1401 end 1402 1403 % Return success 1404 isOk = 1; 1405 end 1406 1407 1408 %% ===== UNINSTALL INTERACTIVE ===== 1409 % USAGE: [isOk, errMsg] = bst_plugin('UninstallInteractive', PlugName) 1410 function [isOk, errMsg] = UninstallInteractive(PlugName) 1411 % Open progress bar 1412 isProgress = bst_progress('isVisible'); 1413 if ~isProgress 1414 bst_progress('start', 'Plugin manager', 'Initialization...'); 1415 end 1416 % Call silent function 1417 [isOk, errMsg] = Uninstall(PlugName, 1); 1418 % Handle errors 1419 if ~isOk 1420 bst_error(['An error occurred while uninstalling plugin ' PlugName ':' 10 10 errMsg 10], 'Plugin manager', 0); 1421 elseif ~isempty(errMsg) 1422 java_dialog('msgbox', ['Uninstall message:' 10 10 errMsg 10], 'Plugin manager'); 1423 end 1424 % Close progress bar 1425 if ~isProgress 1426 bst_progress('stop'); 1427 end 1428 end 1429 1430 1431 %% ===== UPDATE INTERACTIVE ===== 1432 % USAGE: [isOk, errMsg] = bst_plugin('UpdateInteractive', PlugName) 1433 function [isOk, errMsg] = UpdateInteractive(PlugName) 1434 % Open progress bar 1435 isProgress = bst_progress('isVisible'); 1436 if ~isProgress 1437 bst_progress('start', 'Plugin manager', 'Initialization...'); 1438 end 1439 % Get new plugin 1440 [PlugRef, errMsg] = GetDescription(PlugName); 1441 isOk = isempty(errMsg); 1442 % Get installed plugin 1443 if isOk 1444 PlugInst = GetInstalled(PlugName); 1445 if isempty(PlugInst) || ~PlugInst.isManaged 1446 isOk = 0; 1447 errMsg = ['Plugin ' PlugName ' is not installed or not managed by Brainstorm.']; 1448 end 1449 end 1450 % Get online update 1451 [newVersion, newURLzip] = GetVersionOnline(PlugName); 1452 if ~isempty(newVersion) 1453 PlugRef.Version = newVersion; 1454 end 1455 if ~isempty(newURLzip) 1456 PlugRef.URLzip = newURLzip; 1457 end 1458 % User confirmation 1459 if isOk 1460 isOk = java_dialog('confirm', ['<HTML>Update plugin <B>' PlugName '</B> ?<BR><BR><FONT color="#707070">' ... 1461 'Old version :     <I>' PlugInst.Version '</I><BR>' ... 1462 'New version :   <I>' PlugRef.Version '</I><BR><BR></FONT>'], 'Plugin manager'); 1463 if ~isOk 1464 errMsg = 'Update aborted by user.'; 1465 end 1466 end 1467 % Uninstall old 1468 if isOk 1469 [isOk, errMsg] = Uninstall(PlugName, 0, 0); 1470 end 1471 % Install new 1472 if isOk 1473 [isOk, errMsg, PlugDesc] = Install(PlugName, 0); 1474 else 1475 PlugDesc = []; 1476 end 1477 % Handle errors 1478 if ~isOk 1479 bst_error(['An error occurred while updating plugin ' PlugName ':' 10 10 errMsg 10], 'Plugin manager', 0); 1480 elseif ~isempty(errMsg) 1481 java_dialog('msgbox', ['Update message:' 10 10 errMsg 10], 'Plugin manager'); 1482 end 1483 % Close progress bar 1484 if ~isProgress 1485 bst_progress('stop'); 1486 end 1487 % Plugin was updated successfully 1488 if ~isempty(PlugDesc) 1489 % Show the readme file 1490 if ~isempty(PlugDesc.ReadmeFile) 1491 view_text(PlugDesc.ReadmeFile, ['Installed plugin: ' PlugName], 1, 1); 1492 end 1493 % Open the website 1494 if ~isempty(PlugDesc.URLinfo) 1495 web(PlugDesc.URLinfo, '-browser') 1496 end 1497 end 1498 end 1499 1500 1501 %% ===== LOAD ===== 1502 % USAGE: [isOk, errMsg, PlugDesc] = Load(PlugDesc) 1503 function [isOk, errMsg, PlugDesc] = Load(PlugDesc) 1504 % Initialize returned variables 1505 isOk = 0; 1506 % Get plugin structure from name 1507 [PlugDesc, errMsg] = GetDescription(PlugDesc); 1508 if ~isempty(errMsg) 1509 return; 1510 end 1511 % Minimum Matlab version 1512 if ~isempty(PlugDesc.MinMatlabVer) && (PlugDesc.MinMatlabVer > 0) && (bst_get('MatlabVersion') < PlugDesc.MinMatlabVer) 1513 strMinVer = sprintf('%d.%d', ceil(PlugDesc.MinMatlabVer / 100), mod(PlugDesc.MinMatlabVer, 100)); 1514 errMsg = ['Plugin ', PlugDesc.Name ' is not supported for versions of Matlab <= ' strMinVer]; 1515 return; 1516 end 1517 1518 % === ALREADY LOADED === 1519 % If plugin is already full loaded 1520 if isequal(PlugDesc.isLoaded, 1) && ~isempty(PlugDesc.Path) 1521 errMsg = ['Plugin ' PlugDesc.Name ' already loaded: ' PlugDesc.Path]; 1522 return; 1523 end 1524 % Managed plugin path 1525 PlugPath = bst_fullfile(bst_get('UserPluginsDir'), PlugDesc.Name); 1526 if file_exist(PlugPath) 1527 PlugDesc.isManaged = 1; 1528 % Custom installation 1529 else 1530 PluginCustomPath = bst_get('PluginCustomPath'); 1531 if isfield(PluginCustomPath, PlugDesc.Name) && ~isempty(bst_fullfile(PluginCustomPath.(PlugDesc.Name))) && file_exist(bst_fullfile(PluginCustomPath.(PlugDesc.Name))) 1532 PlugPath = PluginCustomPath.(PlugDesc.Name); 1533 end 1534 PlugDesc.isManaged = 0; 1535 end 1536 % Managed install: Detect if there is a single subfolder containing all the files 1537 if PlugDesc.isManaged && ~isempty(PlugDesc.TestFile) && ~file_exist(bst_fullfile(PlugPath, PlugDesc.TestFile)) 1538 dirList = dir(PlugPath); 1539 for iDir = 1:length(dirList) 1540 % Not folder or . : skip 1541 if (dirList(iDir).name(1) == '.') || ~dirList(iDir).isdir 1542 continue; 1543 end 1544 % Check if test file is in the folder 1545 if file_exist(bst_fullfile(PlugPath, dirList(iDir).name, PlugDesc.TestFile)) 1546 PlugDesc.SubFolder = dirList(iDir).name; 1547 break; 1548 % Otherwise, check in any of the subfolders 1549 elseif ~isempty(PlugDesc.LoadFolders) 1550 for iSubDir = 1:length(PlugDesc.LoadFolders) 1551 if file_exist(bst_fullfile(PlugPath, dirList(iDir).name, PlugDesc.LoadFolders{iSubDir}, PlugDesc.TestFile)) 1552 PlugDesc.SubFolder = dirList(iDir).name; 1553 break; 1554 end 1555 end 1556 end 1557 end 1558 end 1559 % Check if test function already available in the path 1560 TestFilePath = GetTestFilePath(PlugDesc); 1561 if ~isempty(TestFilePath) 1562 PlugDesc.isLoaded = 1; 1563 PlugDesc.isManaged = ~isempty(strfind(which(PlugDesc.TestFile), PlugPath)); 1564 if PlugDesc.isManaged 1565 PlugDesc.Path = PlugPath; 1566 else 1567 PlugDesc.Path = TestFilePath; 1568 end 1569 disp(['BST> Plugin ' PlugDesc.Name ' already loaded: ' PlugDesc.Path]); 1570 isOk = 1; 1571 return; 1572 end 1573 1574 % === CHECK LOADABILITY === 1575 PlugDesc.Path = PlugPath; 1576 if ~file_exist(PlugDesc.Path) 1577 errMsg = ['Plugin ' PlugDesc.Name ' not installed.' 10 'Missing folder: ' PlugDesc.Path]; 1578 return; 1579 end 1580 % Set logo 1581 LogoFile = GetLogoFile(PlugDesc); 1582 if ~isempty(LogoFile) 1583 bst_progress('setimage', LogoFile); 1584 end 1585 1586 % === PROCESS DEPENDENCIES === 1587 % Unload incompatible plugins 1588 if ~isempty(PlugDesc.UnloadPlugs) 1589 for iPlug = 1:length(PlugDesc.UnloadPlugs) 1590 % disp(['BST> Unloading incompatible plugin: ' PlugDesc.UnloadPlugs{iPlug}]); 1591 Unload(PlugDesc.UnloadPlugs{iPlug}); 1592 end 1593 end 1594 % Load required plugins 1595 if ~isempty(PlugDesc.RequiredPlugs) 1596 for iPlug = 1:size(PlugDesc.RequiredPlugs,1) 1597 % disp(['BST> Loading required plugin: ' PlugDesc.RequiredPlugs{iPlug,1}]); 1598 [isOk, errMsg] = Load(PlugDesc.RequiredPlugs{iPlug,1}); 1599 if ~isOk 1600 errMsg = ['Error processing dependencies: ', PlugDesc.Name, 10, errMsg]; 1601 bst_progress('removeimage'); 1602 return; 1603 end 1604 end 1605 end 1606 1607 % === LOAD PLUGIN === 1608 % Add plugin folder to path 1609 if ~isempty(PlugDesc.SubFolder) 1610 PlugHomeDir = bst_fullfile(PlugPath, PlugDesc.SubFolder); 1611 else 1612 PlugHomeDir = PlugPath; 1613 end 1614 % Do not modify path in compiled mode 1615 isCompiled = bst_iscompiled(); 1616 if ~isCompiled 1617 addpath(PlugHomeDir); 1618 disp(['BST> Adding plugin ' PlugDesc.Name ' to path: ' PlugHomeDir]); 1619 % Add specific subfolders to path 1620 if ~isempty(PlugDesc.LoadFolders) 1621 % Load all all subfolders 1622 if isequal(PlugDesc.LoadFolders, '*') || isequal(PlugDesc.LoadFolders, {'*'}) 1623 disp(['BST> Adding plugin ' PlugDesc.Name ' to path: ', PlugHomeDir, filesep, '*']); 1624 addpath(genpath(PlugHomeDir)); 1625 % Load specific subfolders 1626 else 1627 for i = 1:length(PlugDesc.LoadFolders) 1628 subDir = PlugDesc.LoadFolders{i}; 1629 if isequal(filesep, '\') 1630 subDir = strrep(subDir, '/', '\'); 1631 end 1632 if isdir([PlugHomeDir, filesep, subDir]) 1633 disp(['BST> Adding plugin ' PlugDesc.Name ' to path: ', PlugHomeDir, filesep, subDir]); 1634 addpath([PlugHomeDir, filesep, subDir]); 1635 end 1636 end 1637 end 1638 end 1639 end 1640 1641 % === TEST FUNCTION === 1642 % Check if test function is available on path 1643 if ~isCompiled && ~isempty(PlugDesc.TestFile) && (exist(PlugDesc.TestFile, 'file') == 0) 1644 errMsg = ['Plugin ' PlugDesc.Name ' successfully loaded from:' 10 PlugHomeDir 10 10 ... 1645 'However, the function ' PlugDesc.TestFile ' is not accessible in the Matlab path.' 10 ... 1646 'Try restarting Matlab and Brainstorm.']; 1647 bst_progress('removeimage') 1648 return; 1649 end 1650 1651 % === CALLBACK: POST-LOAD === 1652 [isOk, errMsg] = ExecuteCallback(PlugDesc, 'LoadedFcn'); 1653 1654 % Remove logo 1655 bst_progress('removeimage'); 1656 % Return success 1657 PlugDesc.isLoaded = isOk; 1658 end 1659 1660 1661 %% ===== LOAD INTERACTIVE ===== 1662 % USAGE: [isOk, errMsg, PlugDesc] = LoadInteractive(PlugName/PlugDesc) 1663 function [isOk, errMsg, PlugDesc] = LoadInteractive(PlugDesc) 1664 % Open progress bar 1665 isProgress = bst_progress('isVisible'); 1666 if ~isProgress 1667 bst_progress('start', 'Plugin manager', 'Loading plugin...'); 1668 end 1669 % Call silent function 1670 [isOk, errMsg, PlugDesc] = Load(PlugDesc); 1671 % Handle errors 1672 if ~isOk 1673 bst_error(['Load error:' 10 10 errMsg 10], 'Plugin manager', 0); 1674 elseif ~isempty(errMsg) 1675 java_dialog('msgbox', ['Load message:' 10 10 errMsg 10], 'Plugin manager'); 1676 end 1677 % Close progress bar 1678 if ~isProgress 1679 bst_progress('stop'); 1680 end 1681 end 1682 1683 1684 %% ===== UNLOAD ===== 1685 % USAGE: [isOk, errMsg, PlugDesc] = Unload(PlugName/PlugDesc) 1686 function [isOk, errMsg, PlugDesc] = Unload(PlugDesc) 1687 % Initialize returned variables 1688 isOk = 0; 1689 errMsg = ''; 1690 % Get installation 1691 InstPlugDesc = GetInstalled(PlugDesc); 1692 % Plugin not installed: check if folder exists 1693 if isempty(InstPlugDesc) || isempty(InstPlugDesc.Path) 1694 % Get plugin structure from name 1695 [PlugDesc, errMsg] = GetDescription(PlugDesc); 1696 if ~isempty(errMsg) 1697 return; 1698 end 1699 % Managed plugin folder 1700 PlugPath = bst_fullfile(bst_get('UserPluginsDir'), PlugDesc.Name); 1701 else 1702 PlugDesc = InstPlugDesc; 1703 PlugPath = PlugDesc.Path; 1704 end 1705 % Plugin not installed 1706 if ~file_exist(PlugPath) 1707 errMsg = ['Plugin ' PlugDesc.Name ' is not installed.' 10 'Missing folder: ' PlugPath]; 1708 return; 1709 end 1710 % Get plugin structure from name 1711 [PlugDesc, errMsg] = GetDescription(PlugDesc); 1712 if ~isempty(errMsg) 1713 return; 1714 end 1715 1716 % === PROCESS DEPENDENCIES === 1717 % Unload dependent plugins 1718 AllPlugs = GetSupported(); 1719 for iPlug = 1:length(AllPlugs) 1720 if ~isempty(AllPlugs(iPlug).RequiredPlugs) && ismember(PlugDesc.Name, AllPlugs(iPlug).RequiredPlugs(:,1)) 1721 Unload(AllPlugs(iPlug)); 1722 end 1723 end 1724 1725 % === UNLOAD PLUGIN === 1726 % Do not modify path in compiled mode 1727 if ~bst_iscompiled() 1728 matlabPath = str_split(path, pathsep); 1729 % Remove plugin folder and subfolders from path 1730 allSubFolders = str_split(genpath(PlugPath), pathsep); 1731 for i = 1:length(allSubFolders) 1732 if ismember(allSubFolders{i}, matlabPath) 1733 rmpath(allSubFolders{i}); 1734 disp(['BST> Removing plugin ' PlugDesc.Name ' from path: ' allSubFolders{i}]); 1735 end 1736 end 1737 end 1738 1739 % === TEST FUNCTION === 1740 % Check if test function is still available on path 1741 if ~isempty(PlugDesc.TestFile) && ~isempty(which(PlugDesc.TestFile)) 1742 errMsg = ['Plugin ' PlugDesc.Name ' successfully unloaded from: ' 10 PlugPath 10 10 ... 1743 'However, another version is still accessible on the Matlab path:' 10 which(PlugDesc.TestFile) 10 10 ... 1744 'Please remove this folder from the Matlab path.']; 1745 return; 1746 end 1747 1748 % === CALLBACK: POST-UNLOAD === 1749 [isOk, errMsg] = ExecuteCallback(PlugDesc, 'UnloadedFcn'); 1750 if ~isOk 1751 return; 1752 end 1753 1754 % Return success 1755 PlugDesc.isLoaded = 0; 1756 isOk = 1; 1757 end 1758 1759 1760 %% ===== UNLOAD INTERACTIVE ===== 1761 % USAGE: [isOk, errMsg, PlugDesc] = UnloadInteractive(PlugName/PlugDesc) 1762 function [isOk, errMsg, PlugDesc] = UnloadInteractive(PlugDesc) 1763 % Open progress bar 1764 isProgress = bst_progress('isVisible'); 1765 if ~isProgress 1766 bst_progress('start', 'Plugin manager', 'Unloading plugin...'); 1767 end 1768 % Call silent function 1769 [isOk, errMsg, PlugDesc] = Unload(PlugDesc); 1770 % Handle errors 1771 if ~isOk 1772 bst_error(['Unload error:' 10 10 errMsg 10], 'Plugin manager', 0); 1773 elseif ~isempty(errMsg) 1774 java_dialog('msgbox', ['Unload message:' 10 10 errMsg 10], 'Plugin manager'); 1775 end 1776 % Close progress bar 1777 if ~isProgress 1778 bst_progress('stop'); 1779 end 1780 end 1781 1782 1783 %% ===== LIST ===== 1784 % USAGE: bst_plugin('List', Target='installed', isGui=0) % Target={'supported','installed'} 1785 function List(Target, isGui) 1786 % Parse inputs 1787 if (nargin < 2) || isempty(isGui) 1788 isGui = 0; 1789 end 1790 if (nargin < 1) || isempty(Target) 1791 Target = 'Installed'; 1792 else 1793 Target = [upper(Target(1)), lower(Target(2:end))]; 1794 end 1795 % Print banner 1796 strFinal = sprintf('\n%s plugins:\n\n', Target); 1797 % Indent 1798 if isGui 1799 strIndent = ''; 1800 else 1801 strIndent = ' '; 1802 end 1803 % Get plugins to list 1804 switch (Target) 1805 case 'Installed' 1806 PlugDesc = GetInstalled(); 1807 isInstalled = 1; 1808 case 'Supported' 1809 PlugDesc = GetSupported(); 1810 isInstalled = 0; 1811 otherwise, error(['Invalid target: ' Target]); 1812 end 1813 if isempty(PlugDesc) 1814 return; 1815 end 1816 % Max lengths 1817 headerName = 'Name'; 1818 headerVersion = 'Version'; 1819 headerPath = 'Installation path'; 1820 headerUrl = 'Downloaded from'; 1821 maxName = max(cellfun(@length, {PlugDesc.Name, headerName})); 1822 maxVer = max(cellfun(@length, {PlugDesc.Version, headerVersion})); 1823 maxUrl = max(cellfun(@length, {PlugDesc.URLzip, headerUrl})); 1824 if isInstalled 1825 maxPath = max(cellfun(@length, {PlugDesc.Path, headerPath})); 1826 strPath = [' | ', headerPath, repmat(' ', 1, maxPath-length(headerPath))]; 1827 strPathSep = ['-|-', repmat('-',1,maxPath)]; 1828 else 1829 strPath = ''; 1830 strPathSep = ''; 1831 end 1832 % Print column headers 1833 strFinal = [strFinal strIndent, ... 1834 headerName, repmat(' ', 1, maxName-length(headerName)) ... 1835 ' | ', headerVersion, repmat(' ', 1, maxVer-length(headerVersion)), ... 1836 strPath, ... 1837 ' | ' headerUrl 10 ... 1838 strIndent, repmat('-',1,maxName), '-|-', repmat('-',1,maxVer), strPathSep, '-|-', repmat('-',1,maxUrl) 10]; 1839 % Print installed plugins to standard output 1840 for iPlug = 1:length(PlugDesc) 1841 if isInstalled 1842 strPath = [' | ', PlugDesc(iPlug).Path, repmat(' ', 1, maxPath-length(PlugDesc(iPlug).Path))]; 1843 else 1844 strPath = ''; 1845 end 1846 strFinal = [strFinal strIndent, ... 1847 PlugDesc(iPlug).Name, repmat(' ', 1, maxName-length(PlugDesc(iPlug).Name)) ... 1848 ' | ', PlugDesc(iPlug).Version, repmat(' ', 1, maxVer-length(PlugDesc(iPlug).Version)), ... 1849 strPath, ... 1850 ' | ' PlugDesc(iPlug).URLzip 10]; 1851 end 1852 % Display 1853 if isGui 1854 view_text(strFinal); 1855 else 1856 disp([strFinal 10]); 1857 end 1858 end 1859 1860 1861 %% ===== MENUS: CREATE ===== 1862 function j = MenuCreate(jMenu, fontSize) 1863 import org.brainstorm.icon.*; 1864 % Get all the supported plugins 1865 PlugDesc = GetSupported(); 1866 % Get Matlab version 1867 MatlabVersion = bst_get('MatlabVersion'); 1868 isCompiled = bst_iscompiled(); 1869 % Submenus 1870 jSub = {}; 1871 % Process each plugin 1872 j = repmat(struct(), 0); 1873 for iPlug = 1:length(PlugDesc) 1874 Plug = PlugDesc(iPlug); 1875 % Skip if Matlab is too old 1876 if ~isempty(Plug.MinMatlabVer) && (Plug.MinMatlabVer > 0) && (MatlabVersion < Plug.MinMatlabVer) 1877 continue; 1878 end 1879 % Skip if not supported in compiled version 1880 if isCompiled && (Plug.CompiledStatus == 0) 1881 continue; 1882 end 1883 % Category=submenu 1884 if ~isempty(Plug.Category) 1885 if isempty(jSub) || ~ismember(Plug.Category, jSub(:,1)) 1886 jParent = gui_component('Menu', jMenu, [], Plug.Category, IconLoader.ICON_FOLDER_OPEN, [], [], fontSize); 1887 jSub(end+1,1:2) = {Plug.Category, jParent}; 1888 else 1889 iSub = find(strcmpi(jSub(:,1), Plug.Category)); 1890 jParent = jSub{iSub,2}; 1891 end 1892 else 1893 jParent = jMenu; 1894 end 1895 % One menu per plugin 1896 ij = length(j) + 1; 1897 j(ij).name = Plug.Name; 1898 % Compiled and included: Simple static menu 1899 if isCompiled && (Plug.CompiledStatus == 2) 1900 j(ij).menu = gui_component('MenuItem', jParent, [], Plug.Name, [], [], [], fontSize); 1901 % Do not create submenus for compiled version 1902 else 1903 % Main menu 1904 j(ij).menu = gui_component('Menu', jParent, [], Plug.Name, [], [], [], fontSize); 1905 % Version 1906 j(ij).version = gui_component('MenuItem', j(ij).menu, [], 'Version', [], [], [], fontSize); 1907 j(ij).versep = java_create('javax.swing.JSeparator'); 1908 j(ij).menu.add(j(ij).versep); 1909 % Install 1910 j(ij).install = gui_component('MenuItem', j(ij).menu, [], 'Install', IconLoader.ICON_DOWNLOAD, [], @(h,ev)InstallInteractive(Plug.Name), fontSize); 1911 % Update 1912 j(ij).update = gui_component('MenuItem', j(ij).menu, [], 'Update', IconLoader.ICON_RELOAD, [], @(h,ev)UpdateInteractive(Plug.Name), fontSize); 1913 % Uninstall 1914 j(ij).uninstall = gui_component('MenuItem', j(ij).menu, [], 'Uninstall', IconLoader.ICON_DELETE, [], @(h,ev)UninstallInteractive(Plug.Name), fontSize); 1915 j(ij).menu.addSeparator(); 1916 % Custom install 1917 j(ij).custom = gui_component('Menu', j(ij).menu, [], 'Custom install', IconLoader.ICON_FOLDER_OPEN, [], [], fontSize); 1918 j(ij).customset = gui_component('MenuItem', j(ij).custom, [], 'Select installation folder', [], [], @(h,ev)SetCustomPath(Plug.Name), fontSize); 1919 j(ij).custompath = gui_component('MenuItem', j(ij).custom, [], 'Path not set', [], [], [], fontSize); 1920 j(ij).custompath.setEnabled(0); 1921 j(ij).menu.addSeparator(); 1922 % Load 1923 j(ij).load = gui_component('MenuItem', j(ij).menu, [], 'Load', IconLoader.ICON_GOOD, [], @(h,ev)LoadInteractive(Plug.Name), fontSize); 1924 j(ij).unload = gui_component('MenuItem', j(ij).menu, [], 'Unload', IconLoader.ICON_BAD, [], @(h,ev)UnloadInteractive(Plug.Name), fontSize); 1925 j(ij).menu.addSeparator(); 1926 % Website 1927 j(ij).web = gui_component('MenuItem', j(ij).menu, [], 'Website', IconLoader.ICON_EXPLORER, [], @(h,ev)web(Plug.URLinfo, '-browser'), fontSize); 1928 % Extra menus 1929 if ~isempty(Plug.ExtraMenus) 1930 j(ij).menu.addSeparator(); 1931 for iMenu = 1:size(Plug.ExtraMenus,1) 1932 j(ij).web = gui_component('MenuItem', j(ij).menu, [], Plug.ExtraMenus{iMenu,1}, IconLoader.ICON_EXPLORER, [], @(h,ev)bst_call(@eval, Plug.ExtraMenus{iMenu,2}), fontSize); 1933 end 1934 end 1935 end 1936 end 1937 % List 1938 if ~isCompiled 1939 jMenu.addSeparator(); 1940 gui_component('MenuItem', jMenu, [], 'List', IconLoader.ICON_EDIT, [], @(h,ev)List('Installed', 1), fontSize); 1941 end 1942 end 1943 1944 1945 %% ===== MENUS: UPDATE ===== 1946 function MenuUpdate(jPlugs) 1947 import org.brainstorm.icon.*; 1948 % If compiled: disable most menus 1949 isCompiled = bst_iscompiled(); 1950 % Interface scaling 1951 InterfaceScaling = bst_get('InterfaceScaling'); 1952 % Update all the plugins 1953 for iPlug = 1:length(jPlugs) 1954 j = jPlugs(iPlug); 1955 PlugName = j.name; 1956 % Is installed? 1957 PlugRef = GetSupported(PlugName); 1958 Plug = GetInstalled(PlugName); 1959 if ~isempty(Plug) 1960 isInstalled = 1; 1961 elseif ~isempty(PlugRef) 1962 Plug = PlugRef; 1963 isInstalled = 0; 1964 else 1965 disp(['BST> Error: Description not found for plugin: ' PlugName]); 1966 continue; 1967 end 1968 isLoaded = isInstalled && Plug.isLoaded; 1969 isManaged = isInstalled && Plug.isManaged; 1970 % Compiled included: no submenus 1971 if isCompiled && (PlugRef.CompiledStatus == 2) 1972 j.menu.setEnabled(1); 1973 if (InterfaceScaling ~= 100) 1974 j.menu.setIcon(IconLoader.scaleIcon(IconLoader.ICON_GOOD, InterfaceScaling / 100)); 1975 else 1976 j.menu.setIcon(IconLoader.ICON_GOOD); 1977 end 1978 % Otherwise: all available 1979 else 1980 % Main menu: Available/Not available 1981 j.menu.setEnabled(isInstalled || ~isempty(Plug.URLzip)); 1982 % Current version 1983 if ~isInstalled 1984 j.version.setText('<HTML><FONT color="#707070"><I>Not installed</I></FONT>'); 1985 elseif ~isManaged && ~isempty(Plug.Path) 1986 j.version.setText('<HTML><FONT color="#707070"><I>Custom install</I></FONT>') 1987 elseif ~isempty(Plug.Version) && ischar(Plug.Version) 1988 j.version.setText(['<HTML><FONT color="#707070"><I>Installed version: ' Plug.Version '</I></FONT>']) 1989 elseif isInstalled 1990 j.version.setText('<HTML><FONT color="#707070"><I>Installed</I></FONT>'); 1991 end 1992 % Main menu: Icon 1993 if isCompiled && isInstalled 1994 menuIcon = IconLoader.ICON_GOOD; 1995 elseif isLoaded % Loaded 1996 menuIcon = IconLoader.ICON_GOOD; 1997 elseif isInstalled % Not loaded 1998 menuIcon = IconLoader.ICON_BAD; 1999 else 2000 menuIcon = IconLoader.ICON_NEUTRAL; 2001 end 2002 if (InterfaceScaling ~= 100) 2003 j.menu.setIcon(IconLoader.scaleIcon(menuIcon, InterfaceScaling / 100)); 2004 else 2005 j.menu.setIcon(menuIcon); 2006 end 2007 % Install 2008 j.install.setEnabled(~isInstalled); 2009 if ~isInstalled && ~isempty(PlugRef.Version) && ischar(PlugRef.Version) 2010 j.install.setText(['<HTML>Install    <FONT color="#707070"><I>(' PlugRef.Version ')</I></FONT>']) 2011 else 2012 j.install.setText('Install'); 2013 end 2014 % Update 2015 j.update.setEnabled(isManaged); 2016 if isInstalled && ~isempty(PlugRef.Version) && ischar(PlugRef.Version) 2017 j.update.setText(['<HTML>Update    <FONT color="#707070"><I>(' PlugRef.Version ')</I></FONT>']) 2018 else 2019 j.update.setText('Update'); 2020 end 2021 % Uninstall 2022 j.uninstall.setEnabled(isManaged); 2023 % Custom install 2024 j.custom.setEnabled(~isManaged); 2025 if ~isempty(Plug.Path) 2026 j.custompath.setText(Plug.Path); 2027 else 2028 j.custompath.setText('Path not set'); 2029 end 2030 % Load/Unload 2031 j.load.setEnabled(isInstalled && ~isLoaded && ~isCompiled); 2032 j.unload.setEnabled(isLoaded && ~isCompiled); 2033 % Web 2034 j.web.setEnabled(~isempty(Plug.URLinfo)); 2035 end 2036 end 2037 j.menu.repaint() 2038 j.menu.getParent().repaint() 2039 end 2040 2041 2042 %% ===== SET CUSTOM PATH ===== 2043 function SetCustomPath(PlugName, PlugPath) 2044 % Parse inputs 2045 if (nargin < 2) || isempty(PlugPath) 2046 PlugPath = []; 2047 end 2048 % Custom plugin paths 2049 PluginCustomPath = bst_get('PluginCustomPath'); 2050 % Get plugin description 2051 PlugDesc = GetSupported(PlugName); 2052 if isempty(PlugDesc) 2053 return; 2054 end 2055 % Get installed plugin 2056 PlugInst = GetInstalled(PlugName); 2057 isInstalled = ~isempty(PlugInst); 2058 isManaged = isInstalled && PlugInst.isManaged; 2059 if isManaged 2060 bst_error(['Plugin ' PlugName ' is already installed by Brainstorm, uninstall it first.'], 0); 2061 return; 2062 end 2063 % Ask install path to user 2064 if isempty(PlugPath) 2065 PlugPath = uigetdir(PlugInst.Path, ['Select ' PlugName ' directory.']); 2066 if isequal(PlugPath, 0) 2067 PlugPath = []; 2068 end 2069 end 2070 % If the directory did not change: nothing to do 2071 if (isInstalled && isequal(PlugInst.Path, PlugPath)) || (~isInstalled && isempty(PlugPath)) 2072 return; 2073 end 2074 % Unload previous version 2075 if isInstalled && ~isempty(PlugInst.Path) && PlugInst.isLoaded 2076 Unload(PlugName); 2077 end 2078 % Check if this is a valid plugin folder 2079 if isempty(PlugPath) || ~file_exist(PlugPath) 2080 PlugPath = []; 2081 end 2082 if ~isempty(PlugPath) && ~isempty(PlugDesc.TestFile) 2083 isValid = 0; 2084 if file_exist(bst_fullfile(PlugPath, PlugDesc.TestFile)) 2085 isValid = 1; 2086 elseif ~isempty(PlugDesc.LoadFolders) 2087 for iFolder = 1:length(PlugDesc.LoadFolders) 2088 if file_exist(bst_fullfile(PlugPath, PlugDesc.LoadFolders{iFolder}, PlugDesc.TestFile)) 2089 isValid = 1; 2090 end 2091 end 2092 end 2093 if ~isValid 2094 PlugPath = []; 2095 end 2096 end 2097 % Save path 2098 PluginCustomPath.(PlugName) = PlugPath; 2099 bst_set('PluginCustomPath', PluginCustomPath); 2100 % Load plugin 2101 if ~isempty(PlugPath) 2102 [isOk, errMsg, PlugDesc] = Load(PlugName); 2103 % Invalid path 2104 else 2105 isOk = 0; 2106 if ~isempty(PlugDesc.TestFile) 2107 errMsg = ['The file ' PlugDesc.TestFile ' could not be found in selected folder.']; 2108 else 2109 errMsg = 'No valid folder was found.'; 2110 end 2111 end 2112 % Handle errors 2113 if ~isOk 2114 bst_error(['An error occurred while configuring plugin ' PlugName ':' 10 10 errMsg 10], 'Plugin manager', 0); 2115 elseif ~isempty(errMsg) 2116 java_dialog('msgbox', ['Configuration message:' 10 10 errMsg 10], 'Plugin manager'); 2117 else 2118 java_dialog('msgbox', ['Plugin ' PlugName ' successfully loaded.']); 2119 end 2120 end 2121 2122 2123 %% ============================================================================ 2124 % ===== PLUGIN-SPECIFIC FUNCTIONS ============================================ 2125 % ============================================================================ 2126 2127 %% ===== LINK CAT-SPM ===== 2128 function LinkCatSpm(isSet) 2129 % Get SPM12 plugin 2130 PlugSpm = GetInstalled('spm12'); 2131 if isempty(PlugSpm) 2132 error('Plugin SPM12 is not loaded.'); 2133 elseif ~PlugSpm.isLoaded 2134 [isOk, errMsg, PlugSpm] = Load('spm12'); 2135 if ~isOk 2136 error('Plugin SPM12 cannot be loaded.'); 2137 end 2138 end 2139 % Get SPM plugin path 2140 if ~isempty(PlugSpm.SubFolder) 2141 spmToolboxDir = bst_fullfile(PlugSpm.Path, PlugSpm.SubFolder, 'toolbox'); 2142 else 2143 spmToolboxDir = bst_fullfile(PlugSpm.Path, 'toolbox'); 2144 end 2145 if ~file_exist(spmToolboxDir) 2146 error(['Could not find SPM12 toolbox folder: ' spmToolboxDir]); 2147 end 2148 % CAT12 plugin path 2149 spmCatDir = bst_fullfile(spmToolboxDir, 'cat12'); 2150 % If folder already exists 2151 if file_exist(spmCatDir) 2152 % If setting install and SPM is not managed by Brainstorm: do not risk deleting user's install of CAT12 2153 if isSet && ~PlugSpm.isManaged 2154 error(['CAT12 seems already set up: ' spmCatDir]); 2155 end 2156 % All the other cases: delete existing CAT12 folder 2157 if ispc 2158 rmCall = ['rmdir /q /s "' spmCatDir '"']; 2159 else 2160 rmCall = ['rm -rf "' spmCatDir '"']; 2161 end 2162 disp(['BST> Deleting existing SPM12 toolbox: ' rmCall]); 2163 [status,result] = system(rmCall); 2164 if (status ~= 0) 2165 error(['Error deleting link: ' result]); 2166 end 2167 end 2168 % Create new link 2169 if isSet 2170 % Get CAT12 plugin 2171 PlugCat = GetInstalled('cat12'); 2172 if isempty(PlugCat) || ~PlugCat.isLoaded 2173 error('Plugin CAT12 is not loaded.'); 2174 end 2175 % Define source and target for the link 2176 if ~isempty(PlugCat.SubFolder) 2177 linkTarget = bst_fullfile(PlugCat.Path, PlugCat.SubFolder); 2178 else 2179 linkTarget = PlugCat.Path; 2180 end 2181 linkFile = spmCatDir; 2182 % Create link 2183 if ispc 2184 linkCall = ['mklink /D "' linkFile '" "' linkTarget '"']; 2185 else 2186 linkCall = ['ln -s "' linkTarget '" "' linkFile '"']; 2187 end 2188 disp(['BST> Creating symbolic link: ' linkCall]); 2189 [status,result] = system(linkCall); 2190 if (status ~= 0) 2191 error(['Error creating link: ' result]); 2192 end 2193 end 2194 end 2195 2196 2197 %% ===== SET PROGRESS LOGO ===== 2198 % USAGE: SetProgressLogo(PlugDesc/PlugName) % Set progress bar image 2199 % SetProgressLogo([]) % Remove progress bar image 2200 function SetProgressLogo(PlugDesc) 2201 % Remove image 2202 if (nargin < 1) || isempty(PlugDesc) 2203 bst_progress('removeimage'); 2204 bst_progress('removelink'); 2205 % Set image 2206 else 2207 % Get plugin description 2208 if ischar(PlugDesc) 2209 PlugDesc = GetSupported(PlugDesc); 2210 end 2211 % Set logo file 2212 if isempty(PlugDesc.LogoFile) 2213 PlugDesc.LogoFile = GetLogoFile(PlugDesc); 2214 end 2215 if ~isempty(PlugDesc.LogoFile) 2216 bst_progress('setimage', PlugDesc.LogoFile); 2217 end 2218 % Set link 2219 if ~isempty(PlugDesc.URLinfo) 2220 bst_progress('setlink', 'http://openmeeg.github.io'); 2221 end 2222 end 2223 end 2224

Tutorials/Plugins (last edited 2021-08-30 11:48:33 by FrancoisTadel)