| | |
| | | /** When true indicates that the user has canceled this operation. */ |
| | | protected boolean canceled = false; |
| | | |
| | | /** Map containing information about what has been configured remotely. */ |
| | | Map<ServerDescriptor, ConfiguredReplication> hmConfiguredRemoteReplication = |
| | | new HashMap<ServerDescriptor, ConfiguredReplication>(); |
| | | |
| | | // Constants used to do checks |
| | | private static final int MIN_DIRECTORY_MANAGER_PWD = 1; |
| | | |
| | |
| | | { |
| | | SUBSTEPS.add(Step.CREATE_GLOBAL_ADMINISTRATOR); |
| | | SUBSTEPS.add(Step.SUFFIXES_OPTIONS); |
| | | // TODO: remove this comment once we want to display the replication options |
| | | // in setup. |
| | | //SUBSTEPS.add(Step.NEW_SUFFIX_OPTIONS); |
| | | SUBSTEPS.add(Step.NEW_SUFFIX_OPTIONS); |
| | | SUBSTEPS.add(Step.REMOTE_REPLICATION_PORTS); |
| | | } |
| | | |
| | |
| | | |
| | | private char[] selfSignedCertPw = null; |
| | | |
| | | private boolean registeredNewServerOnRemote; |
| | | private boolean createdAdministrator; |
| | | private boolean createdRemoteAds; |
| | | |
| | | /** |
| | | * An static String that contains the class name of ConfigFileHandler. |
| | | */ |
| | |
| | | public Installer() { |
| | | lstSteps.add(WELCOME); |
| | | lstSteps.add(SERVER_SETTINGS); |
| | | // TODO: remove this comment once we want to display the replication options |
| | | // in setup. |
| | | /* |
| | | lstSteps.add(REPLICATION_OPTIONS); |
| | | lstSteps.add(CREATE_GLOBAL_ADMINISTRATOR); |
| | | lstSteps.add(SUFFIXES_OPTIONS); |
| | | lstSteps.add(REMOTE_REPLICATION_PORTS); |
| | | */ |
| | | lstSteps.add(NEW_SUFFIX_OPTIONS); |
| | | lstSteps.add(REVIEW); |
| | | lstSteps.add(PROGRESS); |
| | | lstSteps.add(FINISHED); |
| | | try { |
| | | if (!QuickSetupLog.isInitialized()) |
| | | QuickSetupLog.initLogFileHandler( |
| | |
| | | */ |
| | | public boolean canGoBack(WizardStep step) { |
| | | return step != WELCOME && |
| | | step != PROGRESS; |
| | | step != PROGRESS && |
| | | step != FINISHED; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public boolean canGoForward(WizardStep step) { |
| | | return step != REVIEW && |
| | | step != PROGRESS; |
| | | step != PROGRESS && |
| | | step != FINISHED; |
| | | } |
| | | |
| | | /** |
| | |
| | | * {@inheritDoc} |
| | | */ |
| | | public boolean canQuit(WizardStep step) { |
| | | return step != PROGRESS; |
| | | return step != PROGRESS && |
| | | step != FINISHED; |
| | | } |
| | | |
| | | /** |
| | |
| | | { |
| | | isVisible = true; |
| | | } |
| | | // TODO: remove this line once we want to display the replication options |
| | | // in setup. |
| | | isVisible = true; |
| | | return isVisible; |
| | | } |
| | | |
| | |
| | | "Cannot click on next from progress step"); |
| | | } else if (cStep == REVIEW) { |
| | | throw new IllegalStateException("Cannot click on next from review step"); |
| | | } else if (cStep == FINISHED) { |
| | | throw new IllegalStateException( |
| | | "Cannot click on next from finished step"); |
| | | } |
| | | } |
| | | |
| | |
| | | getMsg("confirm-close-install-title"))) { |
| | | qs.quit(); |
| | | } |
| | | } |
| | | else if (cStep == FINISHED) |
| | | { |
| | | qs.quit(); |
| | | } else { |
| | | throw new IllegalStateException( |
| | | "Close only can be clicked on PROGRESS step"); |
| | |
| | | * {@inheritDoc} |
| | | */ |
| | | public void cancel() { |
| | | setStatus(InstallProgressStep.WAITING_TO_CANCEL); |
| | | setCurrentProgressStep(InstallProgressStep.WAITING_TO_CANCEL); |
| | | notifyListeners(null); |
| | | this.canceled = true; |
| | | } |
| | |
| | | * {@inheritDoc} |
| | | */ |
| | | public void quitClicked(WizardStep cStep, QuickSetup qs) { |
| | | if (cStep == PROGRESS) { |
| | | if (cStep == FINISHED) |
| | | { |
| | | qs.quit(); |
| | | } |
| | | else if (cStep == PROGRESS) { |
| | | throw new IllegalStateException( |
| | | "Cannot click on quit from progress step"); |
| | | } else if (installStatus.isInstalled()) { |
| | |
| | | p = new InstallReviewPanel(this); |
| | | } else if (step == PROGRESS) { |
| | | p = new ProgressPanel(this); |
| | | } else if (step == FINISHED) { |
| | | p = new FinishedPanel(this); |
| | | } |
| | | return p; |
| | | } |
| | |
| | | } else if (cStep == PROGRESS) { |
| | | throw new IllegalStateException( |
| | | "Cannot click on previous from progress step"); |
| | | } else if (cStep == FINISHED) { |
| | | throw new IllegalStateException( |
| | | "Cannot click on previous from finished step"); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** Indicates the current progress step. */ |
| | | private InstallProgressStep status = |
| | | private InstallProgressStep currentProgressStep = |
| | | InstallProgressStep.NOT_STARTED; |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | protected void setWizardDialogState(QuickSetupDialog dlg, |
| | | public void setWizardDialogState(QuickSetupDialog dlg, |
| | | UserData userData, |
| | | WizardStep step) { |
| | | if (!installStatus.isInstalled() || forceToDisplaySetup) { |
| | |
| | | } else if (step == WELCOME) { |
| | | dlg.setDefaultButton(ButtonName.NEXT); |
| | | dlg.setFocusOnButton(ButtonName.NEXT); |
| | | } else if (step == REVIEW) { |
| | | dlg.setDefaultButton(ButtonName.NEXT); |
| | | } else if ((step == PROGRESS) || (step == FINISHED)) { |
| | | dlg.setDefaultButton(ButtonName.CLOSE); |
| | | dlg.setFocusOnButton(ButtonName.CLOSE); |
| | | } else { |
| | | dlg.setDefaultButton(ButtonName.NEXT); |
| | | } |
| | |
| | | */ |
| | | public ProgressStep getCurrentProgressStep() |
| | | { |
| | | return status; |
| | | return currentProgressStep; |
| | | } |
| | | |
| | | /** |
| | |
| | | LinkedHashSet<WizardStep> orderedSteps = new LinkedHashSet<WizardStep>(); |
| | | orderedSteps.add(WELCOME); |
| | | orderedSteps.add(SERVER_SETTINGS); |
| | | // TODO: remove this comment once we want to display the replication options |
| | | // in setup. |
| | | /* |
| | | orderedSteps.add(REPLICATION_OPTIONS); |
| | | orderedSteps.add(CREATE_GLOBAL_ADMINISTRATOR); |
| | | orderedSteps.add(SUFFIXES_OPTIONS); |
| | | orderedSteps.add(REMOTE_REPLICATION_PORTS); |
| | | */ |
| | | orderedSteps.add(NEW_SUFFIX_OPTIONS); |
| | | orderedSteps.add(REVIEW); |
| | | orderedSteps.add(PROGRESS); |
| | | orderedSteps.add(FINISHED); |
| | | return orderedSteps; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public WizardStep getFinishedStep() { |
| | | return Step.FINISHED; |
| | | } |
| | | |
| | | /** |
| | | * Uninstalls installed services. This is to be used when the user |
| | | * has elected to cancel an installation. |
| | | */ |
| | |
| | | } |
| | | } |
| | | |
| | | if (completedProgress.contains( |
| | | InstallProgressStep.CONFIGURING_REPLICATION)) { |
| | | // TODO: undo replication |
| | | } |
| | | unconfigureRemote(); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * This method undoes the modifications made in other servers in terms of |
| | | * replication. This method assumes that we are aborting the Installer and |
| | | * that is why it does not call checkAbort. |
| | | */ |
| | | private void unconfigureRemote() |
| | | { |
| | | InitialLdapContext ctx = null; |
| | | if (registeredNewServerOnRemote || createdAdministrator || |
| | | createdRemoteAds) |
| | | { |
| | | // Try to connect |
| | | DataReplicationOptions repl = getUserData().getReplicationOptions(); |
| | | AuthenticationData auth = repl.getAuthenticationData(); |
| | | String ldapUrl = getLdapUrl(auth); |
| | | String dn = auth.getDn(); |
| | | String pwd = auth.getPwd(); |
| | | notifyListeners(getFormattedWithPoints( |
| | | getMsg("progress-unconfiguring-ads-on-remote", |
| | | getHostDisplay(auth)))); |
| | | try |
| | | { |
| | | if (auth.useSecureConnection()) |
| | | { |
| | | ApplicationTrustManager trustManager = getTrustManager(); |
| | | trustManager.setHost(auth.getHostName()); |
| | | ctx = Utils.createLdapsContext(ldapUrl, dn, pwd, |
| | | Utils.getDefaultLDAPTimeout(), null, trustManager); |
| | | } |
| | | else |
| | | { |
| | | ctx = Utils.createLdapContext(ldapUrl, dn, pwd, |
| | | Utils.getDefaultLDAPTimeout(), null); |
| | | } |
| | | |
| | | ADSContext adsContext = new ADSContext(ctx); |
| | | if (createdRemoteAds) |
| | | { |
| | | adsContext.removeAdminData(); |
| | | } |
| | | else |
| | | { |
| | | if (registeredNewServerOnRemote) |
| | | { |
| | | adsContext.unregisterServer(getNewServerAdsProperties()); |
| | | } |
| | | if (createdAdministrator) |
| | | { |
| | | adsContext.deleteAdministrator(getAdministratorProperties()); |
| | | } |
| | | } |
| | | notifyListeners(getFormattedDone()); |
| | | notifyListeners(getLineBreak()); |
| | | } |
| | | catch (Throwable t) |
| | | { |
| | | String html = getFormattedError(t, true); |
| | | notifyListeners(html); |
| | | } |
| | | finally |
| | | { |
| | | if (ctx != null) |
| | | { |
| | | try |
| | | { |
| | | ctx.close(); |
| | | } |
| | | catch (Throwable t) |
| | | { |
| | | } |
| | | } |
| | | } |
| | | } |
| | | InstallerHelper helper = new InstallerHelper(); |
| | | for (ServerDescriptor server : hmConfiguredRemoteReplication.keySet()) |
| | | { |
| | | notifyListeners(getFormattedWithPoints( |
| | | getMsg("progress-unconfiguring-replication-remote", |
| | | server.getHostPort(true)))); |
| | | try |
| | | { |
| | | ctx = getRemoteConnection(server, getTrustManager()); |
| | | helper.unconfigureReplication(ctx, |
| | | hmConfiguredRemoteReplication.get(server), |
| | | server.getHostPort(true)); |
| | | } |
| | | catch (ApplicationException ae) |
| | | { |
| | | String html = getFormattedError(ae, true); |
| | | notifyListeners(html); |
| | | } |
| | | if (ctx != null) |
| | | { |
| | | try |
| | | { |
| | | ctx.close(); |
| | | } |
| | | catch (Throwable t) |
| | | { |
| | | } |
| | | } |
| | | notifyListeners(getFormattedDone()); |
| | | notifyListeners(getLineBreak()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * This method creates the replication configuration for the suffixes on the |
| | | * the local server (and eventually in the remote servers) to synchronize |
| | | * things. |
| | |
| | | } |
| | | } |
| | | notifyListeners(getFormattedDone()); |
| | | notifyListeners(getLineBreak()); |
| | | checkAbort(); |
| | | |
| | | if (rep.getType() == DataReplicationOptions.Type.IN_EXISTING_TOPOLOGY) |
| | | { |
| | |
| | | } |
| | | for (ServerDescriptor server : hm.keySet()) |
| | | { |
| | | notifyListeners(getLineBreak()); |
| | | notifyListeners(getFormattedWithPoints( |
| | | getMsg("progress-configuring-replication-remote", |
| | | server.getHostPort(true)))); |
| | |
| | | } |
| | | |
| | | ctx = getRemoteConnection(server, getTrustManager()); |
| | | helper.configureReplication(ctx, dns, replicationServers, |
| | | replicationPort, server.getHostPort(true), |
| | | knownReplicationServerIds, knownServerIds); |
| | | ConfiguredReplication repl = |
| | | helper.configureReplication(ctx, dns, replicationServers, |
| | | replicationPort, server.getHostPort(true), |
| | | knownReplicationServerIds, knownServerIds); |
| | | hmConfiguredRemoteReplication.put(server, repl); |
| | | |
| | | try |
| | | { |
| | |
| | | { |
| | | } |
| | | notifyListeners(getFormattedDone()); |
| | | notifyListeners(getLineBreak()); |
| | | checkAbort(); |
| | | } |
| | | } |
| | | } |
| | |
| | | */ |
| | | protected void checkAbort() throws ApplicationException { |
| | | if (canceled) { |
| | | setStatus(InstallProgressStep.CANCELING); |
| | | setCurrentProgressStep(InstallProgressStep.CANCELING); |
| | | notifyListeners(null); |
| | | throw new ApplicationException( |
| | | ApplicationException.Type.CANCEL, |
| | |
| | | } |
| | | |
| | | /** |
| | | * Sets the current status of the installation process. |
| | | * @param status the current status of the installation process. |
| | | * Sets the current progress step of the installation process. |
| | | * @param currentProgressStep the current progress step of the installation |
| | | * process. |
| | | */ |
| | | protected void setStatus(InstallProgressStep status) |
| | | protected void setCurrentProgressStep(InstallProgressStep currentProgressStep) |
| | | { |
| | | if (status != null) { |
| | | this.completedProgress.add(status); |
| | | if (currentProgressStep != null) { |
| | | this.completedProgress.add(currentProgressStep); |
| | | } |
| | | this.status = status; |
| | | this.currentProgressStep = currentProgressStep; |
| | | } |
| | | |
| | | /** |
| | |
| | | switch (getUserData().getNewSuffixOptions().getType()) |
| | | { |
| | | case CREATE_BASE_ENTRY: |
| | | status = InstallProgressStep.CREATING_BASE_ENTRY; |
| | | currentProgressStep = InstallProgressStep.CREATING_BASE_ENTRY; |
| | | notifyListeners(getTaskSeparator()); |
| | | createBaseEntry(); |
| | | break; |
| | | case IMPORT_FROM_LDIF_FILE: |
| | | status = InstallProgressStep.IMPORTING_LDIF; |
| | | currentProgressStep = InstallProgressStep.IMPORTING_LDIF; |
| | | notifyListeners(getTaskSeparator()); |
| | | importLDIF(); |
| | | break; |
| | | case IMPORT_AUTOMATICALLY_GENERATED_DATA: |
| | | status = InstallProgressStep.IMPORTING_AUTOMATICALLY_GENERATED; |
| | | currentProgressStep = |
| | | InstallProgressStep.IMPORTING_AUTOMATICALLY_GENERATED; |
| | | notifyListeners(getTaskSeparator()); |
| | | importAutomaticallyGenerated(); |
| | | break; |
| | |
| | | } |
| | | } |
| | | } |
| | | try |
| | | { |
| | | Thread.sleep(3000); |
| | | } |
| | | catch (Throwable t) {} |
| | | int nTries = 4; |
| | | boolean initDone = false; |
| | | while (!initDone) |
| | |
| | | notifyListeners(getLineBreak()); |
| | | } |
| | | i++; |
| | | checkAbort(); |
| | | } |
| | | } |
| | | |
| | |
| | | notifyListeners(getFormattedWithPoints( |
| | | getMsg("progress-creating-administrator", arg))); |
| | | adsContext.createAdministrator(getAdministratorProperties()); |
| | | createdAdministrator = true; |
| | | notifyListeners(getFormattedDone()); |
| | | notifyListeners(getLineBreak()); |
| | | checkAbort(); |
| | | } |
| | | catch (ADSContextException ade) |
| | | { |
| | |
| | | adsContext.registerServer( |
| | | getRemoteServerProperties(auth.getHostName(), |
| | | adsContext.getDirContext())); |
| | | |
| | | createdRemoteAds = true; |
| | | notifyListeners(getFormattedDone()); |
| | | notifyListeners(getLineBreak()); |
| | | checkAbort(); |
| | | } |
| | | /* Configure local server to have an ADS */ |
| | | notifyListeners(getFormattedWithPoints( |
| | |
| | | throw new ApplicationException( |
| | | ApplicationException.Type.CONFIGURATION_ERROR, failedMsg, t); |
| | | } |
| | | createLocalAds(localCtx); |
| | | createLocalAds(localCtx, false); |
| | | notifyListeners(getFormattedDone()); |
| | | notifyListeners(getLineBreak()); |
| | | checkAbort(); |
| | | |
| | | lastLoadedCache = new TopologyCache(adsContext, getTrustManager()); |
| | | lastLoadedCache.reloadTopology(); |
| | |
| | | try |
| | | { |
| | | adsContext.registerServer(getNewServerAdsProperties()); |
| | | registeredNewServerOnRemote = true; |
| | | } |
| | | catch (ADSContextException adse) |
| | | { |
| | | if (adse.getError() == |
| | | ADSContextException.ErrorType.ALREADY_REGISTERED) |
| | | { |
| | | LOG.log(Level.WARNING, "Server already registered. Unregistering "+ |
| | | "and registering server"); |
| | | /* This might occur after registering and unregistering a server */ |
| | | adsContext.unregisterServer(getNewServerAdsProperties()); |
| | | adsContext.registerServer(getNewServerAdsProperties()); |
| | |
| | | getMsg("progress-initializing-ads"))); |
| | | |
| | | int replicationId = replica.getReplicationId(); |
| | | if (replicationId == -1) |
| | | { |
| | | /** |
| | | * This occurs if the remote server had not replication |
| | | * configured. |
| | | */ |
| | | InitialLdapContext rCtx = null; |
| | | try |
| | | { |
| | | rCtx = getRemoteConnection(server, getTrustManager()); |
| | | ServerDescriptor s = ServerDescriptor.createStandalone(rCtx); |
| | | for (ReplicaDescriptor r : s.getReplicas()) |
| | | { |
| | | if (Utils.areDnsEqual(r.getSuffix().getDN(), dn)) |
| | | { |
| | | replicationId = r.getReplicationId(); |
| | | } |
| | | } |
| | | } |
| | | catch (NamingException ne) |
| | | { |
| | | String[] arg = {server.getHostPort(true)}; |
| | | throw new ApplicationException( |
| | | ApplicationException.Type.CONFIGURATION_ERROR, |
| | | getMsg("cannot-connect-to-remote-generic", arg), ne); |
| | | } |
| | | finally |
| | | { |
| | | try |
| | | { |
| | | rCtx.close(); |
| | | } |
| | | catch (Throwable t) |
| | | { |
| | | } |
| | | } |
| | | } |
| | | int nTries = 4; |
| | | int nTries = 1; |
| | | boolean initDone = false; |
| | | while (!initDone) |
| | | { |
| | | try |
| | | { |
| | | initializeSuffix(localCtx, replica.getReplicationId(), |
| | | initializeSuffix(localCtx, replicationId, |
| | | ADSContext.getAdministrationSuffixDN(), |
| | | true, server.getHostPort(true)); |
| | | false, server.getHostPort(true)); |
| | | initDone = true; |
| | | } |
| | | catch (PeerNotFoundException pnfe) |
| | |
| | | } |
| | | notifyListeners(getFormattedDone()); |
| | | notifyListeners(getLineBreak()); |
| | | checkAbort(); |
| | | break; |
| | | } |
| | | } |
| | |
| | | throw new ApplicationException( |
| | | ApplicationException.Type.CONFIGURATION_ERROR, failedMsg, t); |
| | | } |
| | | createLocalAds(localCtx); |
| | | createLocalAds(localCtx, true); |
| | | int replicationPort = |
| | | getUserData().getReplicationOptions().getReplicationPort(); |
| | | Set<String> dns = new HashSet<String>(); |
| | |
| | | trustManager.resetLastRefusedItems(); |
| | | try |
| | | { |
| | | effectiveDn[0] = dn; |
| | | try |
| | | { |
| | | if (isSecure) |
| | |
| | | { |
| | | // Try using a global administrator |
| | | dn = ADSContext.getAdministratorDN(dn); |
| | | effectiveDn[0] = dn; |
| | | if (isSecure) |
| | | { |
| | | ctx = Utils.createLdapsContext(ldapUrl, dn, pwd, |
| | |
| | | isCertificateException(e.getCause())) |
| | | { |
| | | UserDataCertificateException.Type excType; |
| | | ApplicationTrustManager.Cause cause = |
| | | trustManager.getLastRefusedCause(); |
| | | ApplicationTrustManager.Cause cause = null; |
| | | if (e.getTrustManager() != null) |
| | | { |
| | | cause = e.getTrustManager().getLastRefusedCause(); |
| | | } |
| | | LOG.log(Level.INFO, "Certificate exception cause: "+cause); |
| | | if (cause == ApplicationTrustManager.Cause.NOT_TRUSTED) |
| | | { |
| | |
| | | getMsg("certificate-exception", h, String.valueOf(p)), |
| | | e.getCause(), h, p, |
| | | e.getTrustManager().getLastRefusedChain(), |
| | | trustManager.getLastRefusedAuthType(), excType); |
| | | e.getTrustManager().getLastRefusedAuthType(), excType); |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | } |
| | | effectiveDn[0] = dn; |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | private InitialLdapContext createLocalContext() throws NamingException |
| | | { |
| | | String ldapUrl = "ldap://"+getUserData().getHostName()+":"+ |
| | | String ldapUrl = "ldap://"+ |
| | | Utils.getHostNameForLdapUrl(getUserData().getHostName())+":"+ |
| | | getUserData().getServerPort(); |
| | | String dn = getUserData().getDirectoryManagerDn(); |
| | | String pwd = getUserData().getDirectoryManagerPwd(); |
| | | return Utils.createLdapContext(ldapUrl, dn, pwd, |
| | | Utils.getDefaultLDAPTimeout(), null); |
| | | } |
| | | private void createLocalAds(InitialLdapContext ctx) |
| | | private void createLocalAds(InitialLdapContext ctx, boolean addData) |
| | | throws ApplicationException, ADSContextException |
| | | { |
| | | try |
| | | { |
| | | ADSContext adsContext = new ADSContext(ctx); |
| | | adsContext.createAdminData(null); |
| | | adsContext.registerServer(getNewServerAdsProperties()); |
| | | if (getUserData().mustCreateAdministrator()) |
| | | if (addData) |
| | | { |
| | | adsContext.createAdministrator(getAdministratorProperties()); |
| | | adsContext.createAdminData(null); |
| | | adsContext.registerServer(getNewServerAdsProperties()); |
| | | if (getUserData().mustCreateAdministrator()) |
| | | { |
| | | adsContext.createAdministrator(getAdministratorProperties()); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | adsContext.createAdministrationSuffix(null); |
| | | } |
| | | } |
| | | catch (ADSContextException ace) |
| | |
| | | } |
| | | catch (NamingException ne) |
| | | { |
| | | System.out.println("dn: "+auth.getDn()); |
| | | System.out.println("dn: "+auth.getDn()); |
| | | |
| | | String errorMessage = getMsg("cannot-connect-to-remote-generic", |
| | | server.getHostPort(true), ne.toString(true)); |
| | | throw new ApplicationException( |
| | |
| | | minRefreshPeriod = 10000; |
| | | } |
| | | if (!msg.equals(lastDisplayedMsg) && |
| | | ((currentTime - minRefreshPeriod) > lastTimeMsgDisplayed)) |
| | | ((currentTime - minRefreshPeriod) > lastTimeMsgDisplayed)) |
| | | if (!msg.equals(lastDisplayedMsg)) |
| | | { |
| | | notifyListeners(getFormattedProgress(msg)); |
| | | lastDisplayedMsg = msg; |