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:

Example: How to set up FieldTrip

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

Plugin definition

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

Mandatory fields:

Optional fields:

Fields set when installing the plugin:

Fields set when loading the plugin:

Command-line management

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

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

Tutorials/Plugins (last edited 2021-03-19 12:51:08 by FrancoisTadel)