image thumbnail
from Check4updates by Bjorn Gustavsson
Check4updates tracks updates of your favourite FeX packages, with GUI-selection for download.

Check4updates.m
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

Contact us