Plugins

Authors: Francois Tadel, Raymundo Cassani

Brainstorm connects with features from many third-party libraries of methods. In Brainstorm jargon, a plugin is an external software that is can be downloaded or updated automatically by Brainstorm when needed. This tutorial presents the API to register and manage plugins.

Interactive management

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

example1.gif

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

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

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

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

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

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

Website: Opens the website documenting the plugin.

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

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

list.gif

Brainstorm plugins by category

By default Brainstorms offers a curated set of plugins organized in following categories:

Generic toolboxes:

Anatomy processing:

Forward modeling:

Inverse modeling:

I/O Libraries for specific file formats:

Simulation:

Statistics:

e-phys:

fNIRS:

sEEG:

If there is a third-party software that you consider useful to the Brainstorm community, please make a post about it in the Brainstorm Forum to start the discussion.

Meanwhile, if you want to add a third-party software that is not available as a plugin, you can register such software as an user-defined plugin, see this section below: User-defined plugins.

Example: FieldTrip

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

Plugin definition

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

Mandatory fields:

Optional fields:

Fields set when installing the plugin:

Fields set when loading the plugin:

User-defined plugins

It is possible for users to register their own plugins. Once the user-defined plugin is registered, the new plugin will appear under the category User defined, and it can be installed. When a plugin is registered by the user, there will be a JSON file with the plugin definition in the Brainstorm plugin folder: $HOME/.brainstorm/plugins/

Registration of user-defined plugins can be done in three different ways:

  1. Manually: Provide the mandatory fields of the plugin definition. All other fields will be set to their default values.

  2. From file: Provide the path to a JSON file with the plugin definition.

  3. From URL: Provide the URL to a JSON file with the plugin definition.

Providing the JSON file (by file path or URL) allows more flexibility in the plugin definition.

User-defined plugins can be removed with the Remove plugin option.

user_def_menu.gif

Example of JSON file

This is an example of a JSON file to add the processes of the bst-users repository as an user-defined plugin.

{ "Name": "bst-users", "Version": "latest", "URLzip": "https://github.com/brainstorm-tools/bst-users/archive/refs/heads/master.zip", "URLinfo": "https://github.com/brainstorm-tools/bst-users/", "TestFile": "processes/examples/process_beamformer_test.m", "LoadFolders": [ "processes/*" ], "ExtraMenus": [["Plugin tutorial","web('https://neuroimage.usc.edu/brainstorm/Tutorials/Plugins', '-browser')"], ["Show Brainstorm license","bst_license"], ["Print cat in Matlab", "disp('=^_^=')"]], "CompiledStatus": 0 }

Command-line management

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

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

Tutorials/Plugins (last edited 2024-07-23 20:37:05 by RaymundoCassani)