Plugins

Authors: Francois Tadel

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

Interactive management

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

example1.gif

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

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

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

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

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

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

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

list.gif

Brainstorm plugins by category

Generic toolboxes:

Anatomy processing:

Inverse modeling:

Forward modeling:

Simulation:

Statistics:

I/O Libraries for specific file formats:

fNIRS:

Example: FieldTrip

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

Plugin definition

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

Mandatory fields:

Optional fields:

Fields set when installing the plugin:

Fields set when loading the plugin:

Command-line management

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

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

Tutorials/Plugins (last edited 2021-06-24 14:56:35 by FrancoisTadel)