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