Plugins

Authors: Francois Tadel

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

Interactive management

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

example1.gif

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

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

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

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

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

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

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

list.gif

Brainstorm plugins by category

Generic toolboxes:

Anatomy processing:

Forward modeling:

Simulation:

Statistics:

I/O Libraries for specific file formats:

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

Tutorials/Plugins (last edited 2021-03-14 11:22:30 by FrancoisTadel)