function Check4updates(CheckdataBaseMatches,wideTable,fnc2ReadFrom)
% check4updates - file-exchange function update tracker
% check4updates tracks mathworks file exchange function updates for
% contributions of interest that are written into the
% FUNCTIONS2CHECK4UPDATES.M file, and makes a gui-figure the where
% the user can select files for which updates will be downloaded.
% Files will by default be downloaded to a subdirectory "Updates"
% in the directory where the check4updates.m file is located. From
% there the user have to/can manually judge what changes have been
% made and determine which contributions to install updates for.
%
% Calling:
% check4updates([CheckdataBaseMatches],[wideTable],[fnc2ReadFrom])
% Input:
% CheckdataBaseMatches - optional scalar logical, 0 default set
% to 1 to check that file exchange
% web-page contains filename/string from
% the data-base variable, set to 0 to skip
% this check and download as if the
% filename was found on the webpage.
% wideTable - optional scalar logical, 0 default, set
% to 1 to also display update information
% string in the uitable.
% fnc2ReadFrom - optional function handle, should be a
% function handle to a function that
% returns a cell-array [nF x 2] with
% file/directory/package name in column 1
% and file exchange identifier nr in
% column 2. To use functions that require
% input arguments create a temporary or
% anonymous function that takes no input
% argument. Say that function ReadMyFile
% requires a filename and another argument
% do something like this:
% fcnTmp = @() ReadMyFile('files.name',arg2,...)
% then use fcnTmp. functions2check4updates
% can be used as a template for such a
% function, it produces output in the
% correct format. If no fnc2ReadFrom is
% given functions2check4updates is
% called.
% Example:
% check4updates(0,0)
% Check4updates(0,1,@() fcnWithInpArgsExmpl('Tjoho!',1:7))
%
% See also: functions2check4updates
%
% More: There are a couple of obvious preference-configuration
% points in the file, for example the download-directory, the
% figure and table layout. In order to make it easier for users to
% find where customisations should go a comment starting with
% %% Configuration precedes the location.
%
% Check4updates is built on (or rather around) code from
% checkVersion.m FexId: 39993 by Yair Altman,
%
% See also: functions2check4updates
% Copyright 2012 Bjorn Gustavsson, <bjorn.gustavsson@irf.se>
% This is free software, licensed under BSD-license
%
% check4Version2update, update2newVersion are modifications of
% parts of checkVersion.m for which Yair Altman has copyright.
if nargin < 1 || isempty(CheckdataBaseMatches)
CheckdataBaseMatches = 0;
end
if nargin < 2 || isempty(wideTable)
wideTable = 0;
end
%% Load/run/evaluate the fex-contribution-to-track script:
if nargin < 3 || isempty(fnc2ReadFrom)
fcns2check4updates = functions2check4updates;
else
fcns2check4updates = fnc2ReadFrom();
end
Hwb = waitbar(0,'Checking files');
iAvailable = 0;
iAvailable2 = 0;
for iF = 1:size(fcns2check4updates,1),
%% Check each tracked function for updates
filename = fcns2check4updates{iF,1};
fexId = fcns2check4updates{iF,2};
waitbar(iF/size(fcns2check4updates,1),Hwb,['Checking file: ',filename]);
[status,filename,fexId,latestDate,currDate,msg] = check4Version2update(filename,fexId,CheckdataBaseMatches);
if strcmp(status,'available')
%% Then there is a version available that is newer than the one
% found on matlab's search path, so store away those for
% querying the user for action...
iAvailable = iAvailable + 1;
FileNames{iAvailable} = filename;
FexIds(iAvailable) = str2double(fexId);
LatestDates{iAvailable} = latestDate;
CurrDates{iAvailable} = currDate;
MSGs{iAvailable} = msg;
elseif strcmp(status,'downloadable')
%% Then there is a version available that is newer than the one
% found on matlab's search path, so store away those for
% querying the user for action...
iAvailable2 = iAvailable2 + 1;
FileNames2{iAvailable2} = filename;
FexIds2(iAvailable2) = str2double(fexId);
LatestDates2{iAvailable2} = latestDate;
CurrDates2{iAvailable2} = currDate;
MSGs2{iAvailable2} = msg;
end
end
if iAvailable > 0 & iAvailable2 > 0
% There's both newer and uninstalled packages, merge them
iAvailable = iAvailable + iAvailable2;
FileNames = [FileNames,FileNames2];
FexIds = [FexIds,FexIds2];
LatestDates = [LatestDates,LatestDates2];
CurrDates = [CurrDates,CurrDates2];
MSGs = [MSGs,MSGs2];
elseif iAvailable == 0 & iAvailable2 > 0
% There's no newer but some uninstalled packages, use only the
% uninstalled, so swap the variables here
iAvailable = iAvailable + iAvailable2;
FileNames = FileNames2;
FexIds = FexIds2;
LatestDates = LatestDates2;
CurrDates = CurrDates2;
MSGs = MSGs2;
elseif iAvailable > 0 & iAvailable2 == 0
% There's only newer packages, no need to merge or swap varibles
end
close(Hwb)
%% If there are at least one newer or downloadable version available
if iAvailable > 0
% then create a table where the user can select which
% contributions to download updates for
f = createTable4Updates(FileNames,CurrDates,LatestDates,FexIds,MSGs,wideTable);
else
disp('All tracked files are up to date.')
end
function f = createTable4Updates(FileNames,CurrDates,LatestDates,FexIds,MSGs,wideTable)
% createTable4Updates - makes table with updateable fex-contributions
% for the user to select which to download. To select a
% file/contribution for update downloading the check-boxes has to
% be checked. Then the "Download updates" button has to be pushed.
%
% Calling:
% f = createTable4Updates(FileNames,CurrDates,LatestDates,FexIds,MSGs)
%
% This function uses parts of the callback function of Matt Fig's GUI_9
% example from the 41-complete-gui-examples (FexId:24861)
%% GUI set-up
%% Default figure position and size:
f = figure('Position',[100 100 480 450],'menuBar','none');
%% Size and position of table:
T_dx0 = 0.04;
T_lx = 1-2*T_dx0;
T_y0 = 0.2;
T_ly = 1-T_y0-0.05;
Tpos = [T_dx0 T_y0 T_lx T_ly];
%% Size and position for the push-button:
B_dx0 = 0.25;
B_lx = 1-2*B_dx0;
B_y0 = 0.03;
B_ly = T_y0-B_y0-0.03;
Bpos = [B_dx0 B_y0 B_lx B_ly];
if wideTable == 0
%% Configuration of Normal Table content:
columnformat={'logical', 'char', 'char', 'char', 'numeric'};
columnname = {'Update', 'File', 'Inst ver', 'Available ver', 'FexId'};
% Data = {false, 'hist2d', '2012-01-12', '2012-01-12',123;...
% false,'inpaint_nan', '2012-01-12', '2012-01-12',321;...
% false, 'chebfun', '2012-01-12', '2012-01-12',213};
% The table data structure should have the above pattern.
% Loop it up
for i1 = size(FileNames,2):-1:1,
dat(i1,:) = {false, FileNames{i1},CurrDates{i1},LatestDates{i1},FexIds(i1)};
end
%% Configuration of Table layout:
fcnnmlngth = size(str2mat(dat{:,2}),2)*8;
columnwidth = {'auto',fcnnmlngth,92,92,60};
columneditable = [true false false false,false];
else
%% Configuration of Wide Table content (with the extra update message):
columnformat={'logical', 'char', 'char', 'char', 'numeric','char'};
columnname = {'Update', 'File', 'Inst ver','Available ver', 'FexId','Update message'};
% Data = {false, 'hist2d', '2012-01-12', '2012-01-12', 123,'Bug fixed',
% false,'inpaint_nan', '2012-01-12', '2012-01-12', 321,'New fancy feature',
% false, 'chebfun', '2012-01-12', '2012-01-12', 213,'Bug fixed and new feature'};
% The table data structure should have the above pattern.
% Loop it up
for i1 = size(FileNames,2):-1:1,
cMSG = MSGs{i1}{3};
iCBstop = findstr(cMSG,'}');
if isempty(iCBstop)
iCBstop = 0;
end
iBS = findstr(cMSG,'\');
if isempty(iBS)
iBS(2) = length(cMSG)+1;
end
cMSG = cMSG([iCBstop(1)+1:iBS(2)-1]);
dat(i1,:) = {false, FileNames{i1},CurrDates{i1},LatestDates{i1},FexIds(i1),cMSG};
end
%% Configuration of Table layout:
fcnnmlngth = size(str2mat(dat{:,2}),2)*8;
columnwidth = {'auto',fcnnmlngth,92,92,60,500};
columneditable = [true false false false,false,false];
set(f,'Position',[100 100 480+500 450]);
end
%% Table generation
TB = uitable('Units','normalized',...
'Position',Tpos,...
'Data', dat,...
'ColumnName', columnname,...
'ColumnWidth',columnwidth,...
'ColumnFormat', columnformat,...
'ColumnEditable', columneditable,...
'RowName',[]);
%% Stash away the table for later access:
set(gcf,'UserData',TB)
set(TB,'UserData',MSGs) % The update information is hidden even
% further down, dont want that string in
% the table, if you want it in there add a
% column to the table data and table above
% and everything should be "fine" - except
% the update information can be very long.
%% Make push-button for actual action:
BT = uicontrol('style','push',...
'unit','normalized',...
'Position',Bpos,...
'string','Download Updates',...
'Fontsize',15,...
'callback',{@download_updates},...
'backgroundc',[0.94 .94 .94],...
'busyaction','cancel',...% So multiple pushes don't stack.
'interrupt','off');
function [] = download_updates(varargin)
% Callback for pushbutton.
h = varargin{1}; % Get the caller's handle.
col = get(h,'backg'); % Get the background color of the figure.
set(h,...
'str','RUNNING...',...
'backg',[1 .7 .5]) % Change color of button.
drawnow % Force the button to be redrawn
%% Extract the table data
TB = get(gcf,'UserData');
Table_data = get(TB,'data');
MSGs = get(TB,'UserData');
disp(' ')
worksDone = 0;
%% Loop over all tracked files
for i1 = 1:size(Table_data),
% First column in the Table_data contains the logical value of
% the checkbox
if Table_data{i1,1}
% if that is true then try to download the newer version
filename = Table_data{i1,2};
fexId = Table_data{i1,5};
[status,message] = update2newVersion(filename,fexId);
cMSG = MSGs{i1}{3};
iCBstop = findstr(cMSG,'}');
if isempty(iCBstop)
iCBstop = 0;
end
iBS = findstr(cMSG,'\');
if isempty(iBS)
iBS(2) = length(cMSG)+1;
end
cMSG = cMSG([iCBstop(1)+1:iBS(2)-1]);
if length(filename)<6
fprintf(1,'Update for: %s %s %s\t\t\t%s\n',filename,status,message,cMSG)
elseif length(filename)<14
fprintf(1,'Update for: %s %s %s\t\t%s\n',filename,status,message,cMSG)
else
fprintf(1,'Update for: %s %s %s\t%s\n',filename,status,message,cMSG)
end
worksDone = 1;
end
end
% After all that is done
set(h,'str','Done/OK','backg',col) % reset the features.
if worksDone
pause(1) % Pause for a second to show the user that it's done
end
close(gcf) % Then close.
function [status,filename,fexId,latestDate,FileCurrDate,msg] = check4Version2update(filename,fexId,mode)
% check4Version2update Check for existence of a newer version of a file on Matlab's File Exchange
%
% Calling:
% [status,filename,fexId,latestDate,FileCurrDate,msg] = check4Version2update(filename,fexId,mode)
%
% Description:
% check4Version2update check for a newer version of a file on
% the Matlab File Exchange (FEX).
%
% checkVersion(filename,fexId) checks on FILENAME's File Exchange webpage
% whether any newer version of this utility has been uploaded.
%
% [status,filename,fexId,latestDate,FileCurrDate,msg,message] =
% check4Version2update(...) returns a string with the update
% check's status, along with filename, the fexId, latest and
% current date, and an optional descriptive message, the status
% messages are:
% 'ignored' (message=empty) - user requested not to be reminded
% 'unknown' (message=webpage URL) - no newer version was ever uploaded to FEX, or FEX changed webpage format
% 'up-to-date' (message=upload date) - no newer version exists on FEX
% 'available' (message=upload date) - newer version exists but not downloaded
% 'downloaded' (message=upload date) - newer version exists and downloaded
% 'error' (message=error text) - FEX download or parsing error
%
% Known issues/limitations:
% This utility will silently fail if and when mathworks will ever modify the
% File Exchange webpage format.
%
% This is a version of Yair Altman's checkVersion.m (FexId: 39993)
% that only looks for the existence of a newer version, that is: all
% the automatic updating is removed.
try
% Initialization (not really needed - just in case I forget somewhere below...)
statusStr = ''; %#ok<NASGU>
messageStr = ''; %#ok<NASGU>
msg = {''};
latestDate = '';
FileCurrDate = '';
% Sanity checks
error(nargchk(2,3,nargin,'struct')); %#ok<NCHKN>
if nargin < 3
checkFileMatchingMode = true;
else
checkFileMatchingMode = mode;
end
% Normalize the specified filename (strip path, extension)
[fpath,filename,fext] = fileparts(filename); %#ok<ASGLU,NASGU>
% Download the relevant FEX webpage
baseUrl = 'http://www.mathworks.com/matlabcentral/fileexchange/'; % old URL format: '/loadFile.do?objectId='
fexId = num2str(fexId); % accepts both 123 and '123' formats
webUrl = [baseUrl fexId];
webPage = urlread(webUrl);
% If "checkFileMatchingMode" and the filename is not found on the
% corresponding FeX-webpage
if checkFileMatchingMode && isempty(strfind(lower(webPage),lower(filename)))
% Then ask user about whether to continue anyway or to abort.
msg = {['The term "' filename '" is not mentioned anywhere on ' webUrl], '', ...
'Are you sure you wish to continue checking?'};
answer = questdlg(msg,[filename ' update'],'Yes','No','No');
switch answer
case 'Yes' % => Yes: continue normally
% ignore
otherwise % => No or cancel
% Was: error('Mismatched filename, fexId were specified');
disp('Mismatched filename, fexId were specified');
status = 'Unknown';
messageStr = 'Mismatched filename, fexId were specified';
msg = {'Mismatched filename, fexId were specified'};
return
end
end
% Get the latest version date from the File Exchange webpage
modIdx = strfind(webPage,'>Updates<');
if ~isempty(modIdx)
webPage = webPage(modIdx:end);
% Note: regexp hangs if substr not found, so use strfind instead...
%latestWebVersion = regexprep(webPage,'.*?>(20[\d-]+)</td>.*','$1');
dateIdx = strfind(webPage,'class="date">');
if ~isempty(dateIdx)
latestDate = webPage(dateIdx(end)+13 : dateIdx(end)+23);
messageStr = latestDate;
try
startIdx = dateIdx(end)+27;
descStartIdx = startIdx + strfind(webPage(startIdx:startIdx+999),'<td>');
descEndIdx = startIdx + strfind(webPage(startIdx:startIdx+999),'</td>');
descStr = webPage(descStartIdx(1)+3 : descEndIdx(1)-2);
descStr = regexprep(descStr,'</?[pP]>','');
catch
descStr = '';
end
% Determine which file it is:
thisFileName = which(filename); %#ok
% if it isn't a file it might be a directory, look for that
if isempty(thisFileName)
qPath = path;
idxFilename = strfind(qPath,filename);
if ~isempty(idxFilename)
idxColon = strfind(qPath,':');
idx2 = [idxColon(find(idxColon<idxFilename,1,'last')),idxColon(find(idxColon>idxFilename,1,'first'))];
if length(idx2) == 1
idx2 = [idx2,length(path)+1];
end
thisFileName = qPath(idx2(1)+1:idx2(2)-1);
end
end
% Get this file's latest date
if isempty(thisFileName)
thisFileDatenum = 0;
else
try
% Try to get the file's date from the file-system
thisFileData = dir(thisFileName);
thisFileData = thisFileData(1); % In case thisFileName is
% a directory, just work
% on . not dir-contents
try
thisFileDatenum = thisFileData.datenum;
catch % old ML versions...
thisFileDatenum = datenum(thisFileData.date);
end
catch
% Failed for some reason - check whether an internal change-log can be found in the file
thisFileText = evalc('type(thisFileName)');
thisFileLatestDate = regexprep(thisFileText,'.*Change log:[\s%]+([\d-]+).*','$1');
thisFileDatenum = datenum(thisFileLatestDate,'yyyy-mm-dd');
end
end
if (thisFileDatenum < datenum(latestDate,'dd mmm yyyy')-3)
% interactive (default) mode - prompt the user
% Ask the user whether to download the newer version (YES, no, no & don't ask again)
FileCurrDate = datestr(thisFileDatenum,'yyyy-mm-dd');
latestDate = datestr(datenum(latestDate,'dd mmm yyyy'),'yyyy-mm-dd');
if isempty(thisFileName)
msg = {[filename ' is apparently not installed in your Matlab path.'], '', ...
'Download this utility from the Matlab File Exchange to your download folder?'};
statusStr = 'downloadable';
disp(msg{1})
else
msg = {['A newer version (' latestDate ') of ' filename ' is available on the MathWorks File Exchange:'], '', ...
['\color{blue}' descStr '\color{black}'], '', ...
'Download the new version into the Updates folder?'};
statusStr = 'available';
end
else
statusStr = 'up-to-date';
msg = {[filename, ' is up-to-date']};
end
else
% FEX webpage has probably changed its format once again...
statusStr = 'unknown';
messageStr = webUrl;
keyboard
end
elseif ~isempty(strfind(lower(webPage),'</html>'))
% No updates information found but maybe webpage changed format - bail out...
statusStr = 'No update info found';
messageStr = webUrl;
else
% Maybe webpage not fully loaded - bail out...
statusStr = 'error';
messageStr = [webUrl ' was not fully loaded for some reason'];
keyboard
end
catch
% Never mind...
statusStr = 'error';
messageStr = lasterr; %#ok<LERR>
keyboard
end
% Return the status & message, if requested
if nargout > 0, status = statusStr; end
if nargout > 1, message = messageStr; end
%end % checkVersion
function [status,message] = update2newVersion(filename,fexId,msg)
% update2newVersion - function that downloads updated matlab-Fex
% contributions.
%
% Calling:
% [status,message] = update2newVersion(filename,fexId,msg)
% Input:
% filename - char array with name of function or contribution to
% download
% fexId - File exchange Id number
% msg - message (cell-array) as generated by
% check4Version2update.
% Output:
% status -
% message -
%
% This function is the download part of Yair Altman's
% checkVersion.m, with the automatic install step removed. Instead
% the function downloads files to a temporary repository directory
% where the user can investigate what modifications is done in the
% newer version.
%% Configuration: Here is where the download directory is set.
[upd2nvDir,upd2nvName,upd2nvExt] = fileparts(which('Check4updates'));
DownloadPath = fullfile(upd2nvDir,'Updateds');
% Download the relevant FEX webpage
baseUrl = 'http://www.mathworks.com/matlabcentral/fileexchange/'; % old URL format: '/loadFile.do?objectId='
fexId = num2str(fexId); % accepts both 123 and '123' formats
webUrl = [baseUrl fexId];
% webPage = urlread(webUrl);
%% Configuration
% This is commented out in case one would want to have yet another
% layer of checks before downloading - then just uncomment this
% $$$ createStruct.Interpreter = 'tex';
% $$$ createStruct.Default = 'Yes';
% $$$ answer = questdlg(msg,[filename ' update'],'Yes','No','No & never ask again',createStruct);
answer = 'Yes';
switch answer
case 'Yes' % => Yes: download & install newer file
try
fileUrl = [baseUrl '/' fexId '?controller=file_infos&download=true'];
thisFileName = filename;
% [fpath,fname,fext] = fileparts(thisFileName); %#ok<NASGU>
[~,fname,fext] = fileparts(thisFileName); %#ok<NASGU>
zipFileName = fullfile(DownloadPath,[fname '.zip']);
try fileattrib(zipFileName,'+w'); catch, end % make zip-file writable (Thierry Dalon 21/2/2013)
urlwrite(fileUrl,zipFileName);
statusStr = 'downloaded';
messageStr = 'OK';
catch
% Error downloading: inform the user
statusStr = 'error';
messageStr = lasterr; %#ok<LERR>
msgbox(['Error in downloading: ' lasterr], filename, 'warn'); %#ok<LERR>
web(webUrl);
end
otherwise % => No or cancel
% ignore (this time only)...
statusStr = 'available';
messageStr = lasterr;
end
if nargout > 0, status = statusStr; end
if nargout > 1, message = messageStr; end