Brainstorm
  • Comments
  • Menu
    • Attachments
    • Versions
    • Raw Text
    • Print View
  • Login

Software

  • Introduction

  • Gallery

  • Download

  • Installation

Users

  • Tutorials

  • Forum

  • Courses

  • Community

  • Publications

Development

  • What's new

  • What's next

  • About us

  • Contact us

  • Contribute

Revision 8 as of 2021-03-10 17:55:47
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

Plugins

Authors: Francois Tadel

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

Interactive management

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

example1.gif

  • Install: The package is downloaded in the Brainstorm user folder: $HOME/.brainstorm/plugins/

  • Uninstall: Delete the plugin folder and all its subfolders.

  • Load: Adds all the subfolders needed by the plugin to the Matlab path, plus other optional taks.

  • Unload: Removes all the plugin folders from the Matlab path.

  • 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.

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

  • List: You can list all the installed plugins with the menu List:<<BR>><<BR>> list.gif

Command-line management

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

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

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:

  • Name: String, Plugin name = subfolder in the Brainstorm user folder

  • Version: String, version of the plugin (eg. '1.2', '21a', 'github-master', 'latest')

  • URLzip: Download URL (zip file accessible over HTTP/HTTPS/FTP)

  • URLinfo: Information URL: Software website

Optional fields:

  • Category: String, sub-menu in which the plugin is listed

  • AutoUpdate: If 1, plugin is updated automatically when there is a new version available

  • ExtraMenus: Cell matrix {Nx2} with list of entries to add to the plugins menu, eg. {'Download page', 'web(http://...'')'; 'Tutorial', 'web(http://...)'}

  • TestFile: Function/file name to check the existence of the plugin outside of the Brainstorm user folder

  • ReadmeFile: Text filename (relative to the plugin path) - If empty, try using brainstorm3/doc/plugin/<Name>_readme.txt

  • LogoFile: Logo filename (relative to the plugin path) - If empty, try using brainstorm3/doc/plugin/<Name>_logo.[gif|png]

  • MinMatlabVer: Minimum Matlab version, as returned by bst_get('MatlabVersion')

  • RequiredPlugs: Cell-array of required plugin names, to install/load before this one: {Nx2}=>{'plugname','version';...} or {Nx1}=>{'plugname';...}

  • UnloadPlugs: Cell-array of incompatible plugin names, to remove from path before adding

  • LoadFolders: Cell-array of subfolders to add to the path when setting the plugin up (use {'*'} to load all subfolders)

  • GetVersionFcn: String to eval to get the version (after installation)

  • InstalledFcn: String to eval or function handle to call after installing the plugin

  • UninstalledFcn: String to eval or function handle to call after uninstalling the plugin

  • LoadedFcn: String to eval or function handle to call after loading the plugin

  • UnloadedFcn: String to eval or function handle to call after unloading the plugin

Fields set when installing or loading the plugin:

  • SubFolder: If all the code is in a subfolder: detect this at installation time

  • Path: Set at runtime: Installation path for this plugin

  • Processes: List of process functions to be added to the pipeline manager

  • isLoaded: Set at runtime: 0=Not loaded, 1=Loaded (folder and specific subfolders added to Matlab path)

  • isManaged: Set at runtime: 0=Installed by the user, 1=Installed automatically by Brainstorm

  • MoinMoin Powered
  • Python Powered
  • GPL licensed
  • Valid HTML 4.01