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/

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 installing again.

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

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.

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.

Website: Opens the website documenting the plugin.

Usage statistics: Display the number of downloads of the plugin, month by month.

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:

e-phys:

fNIRS:

sEEG:

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

Tutorials/Plugins (last edited 2021-11-26 18:07:20 by FrancoisTadel)