classdef (Sealed) InstallBundleBuilder < handle
% InstallBundleBuilder creates bundle.zip, which will be embedded in the EZDeploy installer.

% Copyright 2021 MathWorks, Inc.

    properties (Access = private)
        appName
        guid
        version
        
        applicationFiles = {};
        nonApplicationFiles
        
        bundleFile        
        manifestBuilder        
    end
    
    properties (Constant)        
        MANIFEST_FILE_NAME = 'ApplicationInstallerManifest.xml';
        TEMP_WORKING_DIR_SUFFIX = 'bundle_generation';
        USER_APP_FOLDER = 'application';
    end

    methods (Access = public)
        function obj = InstallBundleBuilder(parameters)            
            obj.appName = parameters.userApplicationName;
            obj.version = parameters.version;
            obj.guid    = parameters.guid;
            obj.bundleFile = parameters.bundleFile;

            obj.nonApplicationFiles = { parameters.applicationIcon ...
                                        parameters.applicationLogo};

            [~, fname, ext] = fileparts(parameters.applicationIcon);
            iconFilename = [fname ext];
            [~, fname, ext] = fileparts(parameters.applicationLogo);
            logoFilename = [fname ext];
            
            obj.manifestBuilder = compiler.internal.ManifestBuilder( ...
                struct('userApplicationName', obj.appName, ...
                       'modifiedExecutablePath', [compiler.internal.InstallBundleBuilder.USER_APP_FOLDER filesep parameters.shortcut],...
                       'guid', obj.guid, ...
                       'defaultInstallDirectory', parameters.defaultInstallDirectory, ...
                       'applicationIcon', iconFilename, ...
                       'applicationLogo', logoFilename, ...
                       'version', obj.version, ...
                       'mcrExternalProductIdentifiers', parameters.mcrExternalProductIdentifiers) );
        end
        
        function setApplicationFiles(obj, files)
            obj.applicationFiles = files;
        end
        
        function setInstallationNotes(obj, installationNotes)
            obj.manifestBuilder.setInstallationNotes(installationNotes);
        end

        function setAuthorOrganization(obj, authorOrganization)
            obj.manifestBuilder.setAuthorOrganization(authorOrganization);
        end
        
        function setApplicationSummary(obj, applicationSummary)
            obj.manifestBuilder.setApplicationSummary(applicationSummary);
        end
        
        function setWelcomePanelText(obj, welcomePanelText)
            obj.manifestBuilder.setWelcomePanelText(welcomePanelText);
        end
        
        function setAddRemoveProgramIcon(obj, addRemoveProgramIcon)
            [~, fname, ext] = fileparts(addRemoveProgramIcon);
            obj.manifestBuilder.setAddRemoveProgramIcon(fullfile(compiler.internal.InstallBundleBuilder.USER_APP_FOLDER, [fname ext]));
        end

        function createBundle(obj)
            fs = filesep;
            tempWorkingDir = [tempname fs compiler.internal.InstallBundleBuilder.TEMP_WORKING_DIR_SUFFIX];
            mkdir(tempWorkingDir);
            myCleanup = onCleanup(@() rmdir(tempWorkingDir, 's'));
            
            manifestFile = fullfile(tempWorkingDir, compiler.internal.InstallBundleBuilder.MANIFEST_FILE_NAME);
            obj.manifestBuilder.build(manifestFile);
            
            result = dir(tempWorkingDir);
            result([result.isdir]) = [];
            obj.nonApplicationFiles = [obj.nonApplicationFiles fullfile({result.folder}, {result.name})];
            
            tmp_bundle_dir = [tempWorkingDir fs 'bundle_being_build'];
            mkdir(tmp_bundle_dir);
            tmp_bundle_application_dir = [tmp_bundle_dir fs compiler.internal.InstallBundleBuilder.USER_APP_FOLDER];
            mkdir(tmp_bundle_application_dir);
            
            function copyWithDiagnose(src, dst)
                if isfolder(src)
                    [~,n,e] = fileparts(src);
                    dst = fullfile(dst, [n,e]);
                end
                [succeeded, msg] = copyfile(src, dst, 'f');
                if ~succeeded
                    warning(msg);
                end
            end
            
            cellfun(@(s)copyWithDiagnose(s, tmp_bundle_application_dir), ...
                obj.applicationFiles, 'UniformOutput', false);
            cellfun(@(s)copyWithDiagnose(s, tmp_bundle_dir), ...
                obj.nonApplicationFiles, 'UniformOutput', false);
            % To preserve the code signing file encoding, using ditto on mac
	        % instead of the zip command
            if ismac
               zipcmd = ['ditto -c -k "' tmp_bundle_dir '" "' obj.bundleFile '"'];
               status = system(zipcmd);
               if status ~= 0
                   error(message('Compiler:installer:failedToCreateOfflineInstaller'));
               end
	        else
	            zip(obj.bundleFile, [tmp_bundle_dir fs '*']);
            end
        end
    end
end
