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 7 as of 2021-03-10 17:52:57
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:

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