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:

Forward modeling:

Inverse modeling:

I/O Libraries for specific file formats:

Simulation:

Statistics:

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

Tutorials/Plugins (last edited 2022-03-04 08:16:24 by FrancoisTadel)