From f366462dfaa401c7f81b5e44bc3567f2e417a2aa Mon Sep 17 00:00:00 2001 From: azerr Date: Sat, 29 Jul 2023 19:20:12 +0200 Subject: [PATCH] Show with progress bar the load of long process Fixes #471 Signed-off-by: azerr --- .../maven/MavenLemminxExtension.java | 98 ++++++++++++++++--- .../searcher/LocalRepositorySearcher.java | 68 +++++++++++-- 2 files changed, 146 insertions(+), 20 deletions(-) diff --git a/lemminx-maven/src/main/java/org/eclipse/lemminx/extensions/maven/MavenLemminxExtension.java b/lemminx-maven/src/main/java/org/eclipse/lemminx/extensions/maven/MavenLemminxExtension.java index 96bddf2e..7484f05a 100644 --- a/lemminx-maven/src/main/java/org/eclipse/lemminx/extensions/maven/MavenLemminxExtension.java +++ b/lemminx-maven/src/main/java/org/eclipse/lemminx/extensions/maven/MavenLemminxExtension.java @@ -34,7 +34,6 @@ import java.util.Optional; import java.util.Properties; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -74,6 +73,8 @@ import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.WorkspaceReader; import org.eclipse.lemminx.commons.TextDocument; +import org.eclipse.lemminx.commons.progress.ProgressMonitor; +import org.eclipse.lemminx.commons.progress.ProgressSupport; import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.dom.DOMParser; import org.eclipse.lemminx.extensions.maven.participants.codeaction.ExtractPropertyCodeAction; @@ -147,6 +148,8 @@ public class MavenLemminxExtension implements IXMLExtension { // Thread which loads Maven component (plexus container, maven session, etc) which can take some time. private CompletableFuture mavenInitializer; + private ProgressSupport progressSupport; + @Override public void doSave(ISaveContext context) { if (context.getType() == SaveContextType.SETTINGS) { @@ -177,6 +180,7 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) { } this.currentRegistry = registry; this.resolverExtensionManager = registry.getResolverExtensionManager(); + this.progressSupport = registry.getProgressSupport(); try { // Do not invoke getters the MavenLemminxExtension in participant constructors, // or that will trigger loading of plexus, Maven and so on even for non pom files @@ -207,7 +211,7 @@ private void initialize() throws MavenInitializationException { } private CompletableFuture getMavenInitializer() { - if (mavenInitializer != null) { + if (mavenInitializer != null && !mavenInitializer.isCompletedExceptionally()) { return mavenInitializer; } return getOrCreateMavenInitializer(); @@ -236,49 +240,117 @@ private synchronized CompletableFuture getOrCreateMavenInitializer() { } private void doInitialize(CancelChecker cancelChecker) { + Exception error = null; + ProgressMonitor progressMonitor = progressSupport != null ? progressSupport.createProgressMonitor() : null; try { + if (progressMonitor != null) { + progressMonitor.begin("Loading Maven components...", "", 100, null); + } + boolean skipCentralRepository = settings.getCentral().isSkip(); + int nbSteps = 7 - (skipCentralRepository ? 1 : 0); + int currentStep = 1; + int percentage = 15; + // Step1 : initialize Plexus container cancelChecker.checkCanceled(); + if (progressMonitor != null) { + progressMonitor.report("Initializing Plexus container" + getStepMessage(currentStep, nbSteps) + "...", + percentage, null); + } this.container = newPlexusContainer(); - + // Step2 : initialize Maven request cancelChecker.checkCanceled(); + if (progressMonitor != null) { + currentStep++; + percentage += 15; + progressMonitor.report("Initializing Maven request" + getStepMessage(currentStep, nbSteps) + "...", + percentage, null); + } mavenRequest = initMavenRequest(container, settings); - + // Step3 : initialize Repository system session cancelChecker.checkCanceled(); - DefaultRepositorySystemSessionFactory repositorySessionFactory = container.lookup(DefaultRepositorySystemSessionFactory.class); - RepositorySystemSession repositorySystemSession = repositorySessionFactory.newRepositorySession(mavenRequest); - + if (progressMonitor != null) { + currentStep++; + percentage += 15; + progressMonitor.report( + "Initializing Repository system session" + getStepMessage(currentStep, nbSteps) + "...", + percentage, null); + } + DefaultRepositorySystemSessionFactory repositorySessionFactory = container + .lookup(DefaultRepositorySystemSessionFactory.class); + RepositorySystemSession repositorySystemSession = repositorySessionFactory + .newRepositorySession(mavenRequest); + // Step4 : initialize Maven session cancelChecker.checkCanceled(); + if (progressMonitor != null) { + currentStep++; + percentage += 15; + progressMonitor.report("Initializing Maven session" + getStepMessage(currentStep, nbSteps) + "...", + percentage, null); + } MavenExecutionResult mavenResult = new DefaultMavenExecutionResult(); // TODO: MavenSession is deprecated. Investigate for alternative mavenSession = new MavenSession(container, repositorySystemSession, mavenRequest, mavenResult); cache = new MavenProjectCache(mavenSession); - + // Step5 : create local repository searcher cancelChecker.checkCanceled(); - localRepositorySearcher = new LocalRepositorySearcher(mavenSession.getRepositorySession().getLocalRepository().getBasedir()); - - if (!settings.getCentral().isSkip()) { + if (progressMonitor != null) { + currentStep++; + percentage += 15; + progressMonitor.report( + "Creating local repository searcher" + getStepMessage(currentStep, nbSteps) + "...", percentage, + null); + } + localRepositorySearcher = new LocalRepositorySearcher( + mavenSession.getRepositorySession().getLocalRepository().getBasedir(), progressSupport); + + if (!skipCentralRepository) { // Step6 : create central repository searcher cancelChecker.checkCanceled(); + if (progressMonitor != null) { + currentStep++; + percentage += 15; + progressMonitor.report( + "Creating central repository searcher" + getStepMessage(currentStep, nbSteps) + "...", + percentage, null); + } centralSearcher = new RemoteCentralRepositorySearcher(); } buildPluginManager = null; mavenPluginManager = container.lookup(MavenPluginManager.class); buildPluginManager = container.lookup(BuildPluginManager.class); - + // Step7 : initializing Workspace readers cancelChecker.checkCanceled(); - internalDidChangeWorkspaceFolders(this.initialWorkspaceFolders.stream().map(WorkspaceFolder::getUri).map(URI::create).toArray(URI[]::new), null); + if (progressMonitor != null) { + currentStep++; + percentage += 15; + progressMonitor.report("Initializing Workspace readers" + getStepMessage(currentStep, nbSteps) + "...", + percentage, null); + } + internalDidChangeWorkspaceFolders(this.initialWorkspaceFolders.stream().map(WorkspaceFolder::getUri) + .map(URI::create).toArray(URI[]::new), null); } catch (Exception e) { + error = e; LOGGER.log(Level.SEVERE, e.getMessage(), e); stop(currentRegistry); + } finally { + if (progressMonitor != null) { + String message = error != null ? "Maven initialization terminated with error " + error.getMessage() + : "Maven initialization done"; + progressMonitor.end(message); + } } } + private static String getStepMessage(int currentStep, int nbSteps) { + return " (" + currentStep + "/" + nbSteps + ")"; + } + private MavenExecutionRequest initMavenRequest(PlexusContainer container, XMLMavenSettings options) throws Exception { MavenExecutionRequest mavenRequest = new DefaultMavenExecutionRequest(); Properties systemProperties = getSystemProperties(); diff --git a/lemminx-maven/src/main/java/org/eclipse/lemminx/extensions/maven/searcher/LocalRepositorySearcher.java b/lemminx-maven/src/main/java/org/eclipse/lemminx/extensions/maven/searcher/LocalRepositorySearcher.java index 1298abbf..fb8d5365 100644 --- a/lemminx-maven/src/main/java/org/eclipse/lemminx/extensions/maven/searcher/LocalRepositorySearcher.java +++ b/lemminx-maven/src/main/java/org/eclipse/lemminx/extensions/maven/searcher/LocalRepositorySearcher.java @@ -13,6 +13,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.ClosedWatchServiceException; +import java.nio.file.DirectoryStream; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; @@ -21,14 +22,15 @@ import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -38,24 +40,33 @@ import org.apache.maven.model.Dependency; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.lemminx.commons.progress.ProgressMonitor; +import org.eclipse.lemminx.commons.progress.ProgressSupport; import org.eclipse.lemminx.extensions.maven.MavenLemminxExtension; import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.eclipse.lsp4j.jsonrpc.CompletableFutures; +/** + * Search for collecting artifacts from the local repository /.m2 + * + */ public class LocalRepositorySearcher { private static final Logger LOGGER = Logger.getLogger(LocalRepositorySearcher.class.getName()); private final File localRepository; - public LocalRepositorySearcher(File localRepository) { - this.localRepository = localRepository; - } + private final ProgressSupport progressSupport; private Map>> cache = new HashMap<>(); private WatchKey watchKey; private WatchService watchService; + public LocalRepositorySearcher(File localRepository, ProgressSupport progressSupport) { + this.localRepository = localRepository; + this.progressSupport = progressSupport; + } + public Set searchGroupIds() throws IOException { return getLocalArtifactsLastVersion().stream().map(Artifact::getGroupId).distinct().collect(Collectors.toSet()); } @@ -134,8 +145,38 @@ private synchronized CompletableFuture> getOrCreateLocalArt private Collection computeLocalArtifacts(CancelChecker cancelChecker) throws IOException { final Path repoPath = localRepository.toPath(); + ProgressMonitor progressMonitor = progressSupport != null ? progressSupport.createProgressMonitor() : null; + if (progressMonitor != null) { + progressMonitor.begin("Loading local artifacts from '" + repoPath, null, 100, null); + } Map groupIdArtifactIdToVersion = new HashMap<>(); - Files.walkFileTree(repoPath, Collections.emptySet(), 10, new SimpleFileVisitor() { + try { + List subPaths = getSubDirectories(repoPath); + int increment = Math.round(100f / subPaths.size()); + int i = 0; + for (Path path : subPaths) { + cancelChecker.checkCanceled(); + if (progressMonitor != null) { + progressMonitor.report("scanning folder' " + path.getFileName().getName(0) + "' (" + i++ + "/" + subPaths.size() + ")...", increment++, null); + } + try { + collect(path, repoPath, groupIdArtifactIdToVersion, cancelChecker); + } + catch(IOException e) { + LOGGER.log(Level.SEVERE, "Error while scanning local repo folder " + path, e); + } + } + } finally { + if (progressMonitor != null) { + progressMonitor.end(null); + } + } + return groupIdArtifactIdToVersion.values(); + } + + private void collect(final Path currentPath, final Path repoPath, Map groupIdArtifactIdToVersion, + CancelChecker cancelChecker) throws IOException { + Files.walkFileTree(currentPath, Collections.emptySet(), 10, new SimpleFileVisitor() { @Override public FileVisitResult preVisitDirectory(Path file, BasicFileAttributes attrs) throws IOException { if (file.getFileName().toString().charAt(0) == '.') { @@ -174,7 +215,20 @@ public FileVisitResult preVisitDirectory(Path file, BasicFileAttributes attrs) t return FileVisitResult.SKIP_SUBTREE; } }); - return groupIdArtifactIdToVersion.values(); + } + + private static List getSubDirectories(Path directoryPath) { + List subDirectories = new ArrayList<>(); + try (DirectoryStream directoryStream = Files.newDirectoryStream(directoryPath)) { + for (Path entry : directoryStream) { + if (Files.isDirectory(entry)) { + subDirectories.add(entry); + } + } + } catch (IOException e) { + // Do nothing + } + return subDirectories; } // TODO consider using directly ArtifactRepository for those 2 methods @@ -195,7 +249,7 @@ public void stop() { cache .values() .forEach(f -> f.cancel(true)); - // Close the watch service which tracks the local repository. + // Close the watch service which tracks the local repository. if (watchService != null && watchKey != null) { watchKey.cancel(); try {