1+ /*******************************************************************************
2+ * Copyright (c) 2025 Dieter Mai and others.
3+ *
4+ * This program and the accompanying materials
5+ * are made available under the terms of the Eclipse Public License 2.0
6+ * which accompanies this distribution, and is available at
7+ * https://www.eclipse.org/legal/epl-2.0/
8+ *
9+ * SPDX-License-Identifier: EPL-2.0
10+ *
11+ * Contributors:
12+ * Dieter Mai - initial API and implementation
13+ *******************************************************************************/
14+ package org .eclipse .pde .internal .core .util ;
15+
16+ import java .io .IOException ;
17+ import java .nio .file .FileVisitResult ;
18+ import java .nio .file .FileVisitor ;
19+ import java .nio .file .Files ;
20+ import java .nio .file .Path ;
21+ import java .nio .file .attribute .BasicFileAttributes ;
22+ import java .util .Objects ;
23+
24+ import org .eclipse .core .runtime .IProgressMonitor ;
25+ import org .eclipse .core .runtime .NullProgressMonitor ;
26+ import org .eclipse .core .runtime .Platform ;
27+ import org .eclipse .core .runtime .SubMonitor ;
28+
29+ /**
30+ * Deletes content of a directory recursively.
31+ */
32+ public class DeleteContentWalker implements FileVisitor <Path > {
33+
34+ /**
35+ * Deletes the given root directory and its content. If the deletion fails
36+ * or is canceled by the user, the method returns silently.
37+ *
38+ * @param root
39+ * The root directory to delete.
40+ * @param monitor
41+ * The monitor to report progress to. Can be null.
42+ */
43+ public static void deleteDirectory (Path root , IProgressMonitor monitor ) {
44+ // if there is no file, no need to proceed
45+ if (root == null || !Files .exists (root )) {
46+ return ;
47+ }
48+
49+ IProgressMonitor submonitor = createSubMonitor (root , monitor );
50+
51+ try {
52+ Files .walkFileTree (root , new DeleteContentWalker (root , submonitor ));
53+ } catch (IOException e ) {
54+ // noting to do
55+ } finally {
56+ submonitor .done ();
57+ }
58+ }
59+
60+ private static IProgressMonitor createSubMonitor (Path root , IProgressMonitor monitor ) {
61+ IProgressMonitor submonitor ;
62+ if (monitor == null || monitor instanceof NullProgressMonitor ) {
63+ submonitor = SubMonitor .convert (monitor );
64+ } else {
65+ try (var stream = Files .list (root )) {
66+ // only use the root content for progress. anything else would
67+ // be overkill.
68+ long count = stream .count ();
69+ submonitor = SubMonitor .convert (monitor , (int ) count );
70+ } catch (IOException e ) {
71+ // In case of error, just ignore the monitor;
72+ submonitor = SubMonitor .convert (monitor );
73+ }
74+ }
75+ return submonitor ;
76+ }
77+
78+ private final Path root ;
79+ private final IProgressMonitor monitor ;
80+
81+ private DeleteContentWalker (Path root , IProgressMonitor monitor ) {
82+ this .root = root ;
83+ this .monitor = monitor ;
84+ }
85+
86+ @ Override
87+ public FileVisitResult preVisitDirectory (Path path , BasicFileAttributes attrs ) throws IOException {
88+ if (Platform .OS .isWindows ()) {
89+ Files .setAttribute (path , "dos:readonly" , false ); //$NON-NLS-1$
90+ }
91+ return resultIfNotCanceled (FileVisitResult .CONTINUE );
92+ }
93+
94+ @ Override
95+ public FileVisitResult visitFile (Path path , BasicFileAttributes attrs ) throws IOException {
96+ if (Platform .OS .isWindows ()) {
97+ Files .setAttribute (path , "dos:readonly" , false ); //$NON-NLS-1$
98+ }
99+ Files .deleteIfExists (path );
100+
101+ if (Objects .equals (path .getParent (), root )) {
102+ monitor .worked (1 );
103+ }
104+ return resultIfNotCanceled (FileVisitResult .CONTINUE );
105+ }
106+
107+ @ Override
108+ public FileVisitResult visitFileFailed (Path file , IOException exc ) throws IOException {
109+ if (exc != null ) {
110+ throw exc ;
111+ }
112+ return resultIfNotCanceled (FileVisitResult .CONTINUE );
113+ }
114+
115+ @ Override
116+ public FileVisitResult postVisitDirectory (Path path , IOException exc ) throws IOException {
117+ Files .deleteIfExists (path );
118+
119+ if (Objects .equals (path .getParent (), root )) {
120+ monitor .worked (1 );
121+ }
122+ return resultIfNotCanceled (FileVisitResult .CONTINUE );
123+ }
124+
125+ /**
126+ * Returns the given result if not canceled. If canceled
127+ * {@link FileVisitResult#TERMINATE} is returned.
128+ */
129+ private FileVisitResult resultIfNotCanceled (FileVisitResult result ) {
130+ if (monitor .isCanceled ()) {
131+ return FileVisitResult .TERMINATE ;
132+ }
133+ return result ;
134+ }
135+ }
0 commit comments