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 35 as of 2021-12-28 10:21:48
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.

Contents

  1. Interactive management
  2. Brainstorm plugins by category
  3. Example: FieldTrip
  4. Plugin definition
  5. Command-line management

Interactive management

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

example1.gif

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

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

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

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

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

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

Website: Opens the website documenting the plugin.

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

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

list.gif

Brainstorm plugins by category

Generic toolboxes:

  • SPM12: https://www.fil.ion.ucl.ac.uk/spm/

  • FieldTrip: http://www.fieldtriptoolbox.org

Anatomy processing:

  • Brain2Mesh: http://mcx.space/brain2mesh/

    • Warning: Requires the Imaging Processing Toolbox
  • CAT12: https://neuroimage.usc.edu/brainstorm/Tutorials/SegCAT12

  • Iso2Mesh: http://iso2mesh.sourceforge.net

  • ROAST: https://www.parralab.org/roast/

Inverse modeling:

  • BrainEntropy: https://neuroimage.usc.edu/brainstorm/Tutorials/TutBEst

Forward modeling:

  • OpenMEEG: https://neuroimage.usc.edu/brainstorm/Tutorials/TutBem

  • DUNEuro: https://neuroimage.usc.edu/brainstorm/Tutorials/Duneuro

Simulation:

  • SimMEEG: https://neuroimage.usc.edu/brainstorm/Tutorials/Simulations#SimMEEG

Statistics:

  • LibSVM: https://www.csie.ntu.edu.tw/~cjlin/libsvm/

I/O Libraries for specific file formats:

  • Philips-EGI EEG (.mff): https://github.com/arnodelorme/mffmatlabio

  • Blackrock NeuroPort (.nev, .nsx): https://github.com/BlackrockMicrosystems/NPMK

  • AD Instruments SDK: https://github.com/JimHokanson/adinstruments_sdk_matlab

  • Neurodata Without Borders: https://github.com/NeurodataWithoutBorders/matnwb

  • Plotly export: https://plotly.com/matlab/

  • Tucker-Davis Technologies SDK: https://www.tdt.com/support/matlab-sdk/

e-phys:

  • DeriveLFP: https://journals.physiology.org/doi/full/10.1152/jn.00642.2010

fNIRS:

  • NIRSTORM: https://github.com/Nirstorm/nirstorm

  • MCXlab: http://mcx.space/wiki/

sEEG:

  • MIA: http://www.neurotrack.fr/mia/

Example: FieldTrip

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

  • Already in path: If you already have a recent version of FieldTrip set up on your computer and available in your Matlab path, the fieldtrip plugin entry should show a green dot, and everything should work without doing anything special.

    ft_path.gif

  • Custom install: If FieldTrip is somewhere on your computer, but not currently in your Matlab path, you can simply tell Brainstorm where it is with the menu: Plugins > fieldtrip > Custom install > Set installation folder.

    ft_custom.gif

  • Plugin install: Otherwise, the easiest solution is to click on Plugins > fieldtrip > Install, and let Brainstorm manage everything automatically: downloading the latest version, setting the path, managing incompatibilities with other toolboxes (e.g. SPM or ROAST).

    ft_install.gif

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: String: Download URL, zip or tgz file accessible over HTTP/HTTPS/FTP

  • URLinfo: String: Information URL = Software website

Optional fields:

  • AutoUpdate: Boolean: If true, the plugin is updated automatically when there is a new version available (default: true).

  • AutoLoad: Boolean: If true, the plugin is loaded automatically at Brainstorm startup

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

  • ExtraMenus: Cell matrix {Nx2}: List of entries to add to the plugins menu

    • ExtraMenus{i,1}: String: Label of the menu

    • ExtraMenus{i,2}: String: Matlab code to eval when the menu is clicked

  • TestFile: String: Name of a file that should be located in one of the loaded folders of the plugin (eg. 'spm.m' for SPM12). This is used to test whether the plugin was correctly installed, or whether it is available somewhere else in the Matlab path.

  • ReadmeFile: String: Name of the text file to display after installing the plugin (must be in the plugin folder). If empty, it tries using brainstorm3/doc/plugin/plugname_readme.txt

  • LogoFile: String: Name of the image file to display during the plugin download, installation, and associated computations (must be in the plugin folder). Supported extensions: gif, png. If empty, try using brainstorm3/doc/plugin/<Name>_logo.[gif|png]

  • MinMatlabVer: Integer: Minimum Matlab version required for using this plugin, as returned by bst_get('MatlabVersion')

  • CompiledStatus: Integer: Behavior of this plugin in the compiled version of Brainstorm:

    • 0: Plugin is not available in the compiled distribution of Brainstorm
    • 1: Plugin is available for download (only for plugins based on native compiled code)
    • 2: Plugin is included in the compiled distribution of Brainstorm
  • RequiredPlugs: Cell-array: Additional plugins required by this plugin, that must be installed/loaded beforehand.

    • {Nx2} => {'plugname','version'; ...} or

    • {Nx1} => {'plugname'; ...}

  • UnloadPlugs: Cell-array of names of incompatible plugin, to unload before loaing this one

  • LoadFolders: Cell-array of subfolders to add to the Matlab path when setting up the plugin. Use {'*'} to add all the plugin subfolders.

  • GetVersionFcn: String to eval or function handle to call 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

  • DeleteFiles: Cell-array of files to delete after unzipping the plugin package (path relative to the plugin folder)

Fields set when installing the plugin:

  • InstallDate: Installation date: 'dd-mmm-yyyy HH:MM:SS'

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

Fields set when loading the plugin:

  • Path: Installation path (eg. /home/username/.brainstorm/plugins/fieldtrip)

  • SubFolder: If all the code is in a single subfolder (eg. /plugins/fieldtrip/fieldtrip-20210304), this is detected and the full path to the TestFile would be typically fullfile(Path, SubFolder).

  • isLoaded: 0=Not loaded, 1=Loaded

  • isManaged: 0=Installed manually by the user, 1=Installed automatically by Brainstorm

Command-line management

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

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