This is an automated email from the ASF dual-hosted git repository.
lkishalmi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push:
new fcab7a1923 Rework Janitor, try to use real version numbers, in the
future
fcab7a1923 is described below
commit fcab7a1923361bc2db40ef1a6f85bcef183b2311
Author: Laszlo Kishalmi <[email protected]>
AuthorDate: Mon Jul 3 18:19:51 2023 -0700
Rework Janitor, try to use real version numbers, in the future
---
.../src/org/netbeans/modules/janitor/Janitor.java | 392 +++++++++++++--------
.../modules/janitor/JanitorOptionsPanel.java | 6 +-
2 files changed, 254 insertions(+), 144 deletions(-)
diff --git a/platform/janitor/src/org/netbeans/modules/janitor/Janitor.java
b/platform/janitor/src/org/netbeans/modules/janitor/Janitor.java
index 0fa2e56456..91763d194a 100644
--- a/platform/janitor/src/org/netbeans/modules/janitor/Janitor.java
+++ b/platform/janitor/src/org/netbeans/modules/janitor/Janitor.java
@@ -20,7 +20,9 @@ package org.netbeans.modules.janitor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
@@ -29,7 +31,6 @@ import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Duration;
import java.time.Instant;
-import java.time.temporal.ChronoUnit;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@@ -37,6 +38,8 @@ import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import java.util.prefs.Preferences;
import javax.swing.Icon;
import org.netbeans.api.annotations.common.StaticResource;
@@ -58,36 +61,31 @@ import org.openide.windows.OnShowing;
"# {1} - the days of abandonement",
"# {2} - the disk space can be reclaimed (in megabytes)",
"TIT_ABANDONED_USERDIR=NetBeans {0} was last used {1} days ago.",
-
"# {0} - the name of the abandoned cache dir.",
"# {1} - the disk space can be reclaimed (in megabytes)",
"TIT_ABANDONED_CACHEDIR=NetBeans cache directory {0} seems to be
abandoned.",
-
"# {0} - is the user directory name",
"# {1} - the days of abandonement",
"# {2} - the disk space can be reclaimed (in megabytes)",
"DESC_ABANDONED_USERDIR=Remove unused data and cache directories of
NetBeans {0}. "
- + "Free up {2} MB of disk space.",
-
+ + "Free up {2} MB of disk space.",
"# {0} - is the cache directory name",
"# {1} - the disk space can be reclaimed (in megabytes)",
"DESC_ABANDONED_CACHEDIR=NetBeans could not find a user dir for cache dir
{0}, so it is probably abandoned. "
- + "Remove abandoned cache dir, "
- + "free up {1} MB of disk space.",
-
+ + "Remove abandoned cache dir, "
+ + "free up {1} MB of disk space.",
"TIT_CONFIRM_CLEANUP=Confirm Cleanup",
-
"# {0} - the dirname to be cleaned up",
"TXT_CONFIRM_CLEANUP=Remove user and cache data for NetBeans {0}?",
-
- "# {0} - the dirname to be cleaned up",
+ "# {0} - the dirname to be cleaned up",
"TXT_CONFIRM_CACHE_CLEANUP=Remove abandoned cache dir?",
-
"# {0} - the dirname to be cleaned up",
"LBL_CLEANUP=Removing unused/abandoned user and/or cache dirs."
})
public class Janitor {
+ private static final Logger LOG =
Logger.getLogger(Janitor.class.getName());
+
private static final int UNUSED_DAYS = 30;
public static final String PROP_JANITOR_ENABLED = "janitorEnabled";
//NOI18N
@@ -96,39 +94,60 @@ public class Janitor {
private static final String LOGFILE_NAME = "var/log/messages.log"; //NOI18N
private static final String ALL_CHECKSUM_NAME =
"lastModified/all-checksum.txt"; //NOI18N
+ private static final String LAST_VERSION_NAME = ".lastUsedVersion";
//NOI18N
+
+ private static final String NB_VERSION;
+
@StaticResource
private static final String CLEAN_ICON =
"org/netbeans/modules/janitor/resources/clean.gif"; //NOI18N
static final RequestProcessor JANITOR_RP = new RequestProcessor("janitor",
1); //NOI18N
static final Map<ActionListener, Notification> CLEANUP_TASKS = new
WeakHashMap<>();
+ static {
+ String version = System.getProperty("netbeans.buildnumber"); //NOI18N
+ if (version != null) {
+ // remove git hash from the build number
+ int dash = version.lastIndexOf('-');
+ if (dash + 41 == version.length()) { // 40 chars for git SHA sum,
1 for the dash
+ version = version.substring(0, dash);
+ }
+ }
+ NB_VERSION = version;
+ }
+
static void scanForJunk() {
// Remove previously opened notifications
CLEANUP_TASKS.values().forEach((nf) -> nf.clear());
CLEANUP_TASKS.clear();
Icon clean = ImageUtilities.loadImageIcon(CLEAN_ICON, false);
- List<Pair<String, Integer>> otherVersions = getOtherVersions();
-
- for (Pair<String, Integer> ver : otherVersions) {
- String name = ver.first();
- Integer age = ver.second();
- long toFree = size(getUserDir(name)) + size(getCacheDir(name));
- toFree = toFree / (1_000_000) + 1;
- if (getUserDir(name) != null) {
- ActionListener cleanupListener = cleanupAction(name,
Bundle.TXT_CONFIRM_CLEANUP(name));
- Notification nf = NotificationDisplayer.getDefault().notify(
- Bundle.TIT_ABANDONED_USERDIR(name, age, toFree),
- clean,
- Bundle.DESC_ABANDONED_USERDIR(name, age, toFree),
- cleanupListener);
-
- CLEANUP_TASKS.put(cleanupListener, nf);
+ List<CleanupPair> candidates = getCandidates();
+
+ Instant now = Instant.now();
+ int maxUnused = getUnusedDays();
+
+ for (CleanupPair candidate : candidates) {
+ int age = candidate.age(now);
+ int toFree = candidate.size();
+ String name = candidate.getName();
+ if (candidate.userdir != null) {
+ if (age > maxUnused) {
+ ActionListener cleanupListener = cleanupAction(candidate,
Bundle.TXT_CONFIRM_CLEANUP(name));
+ Notification nf =
NotificationDisplayer.getDefault().notify(
+ Bundle.TIT_ABANDONED_USERDIR(name, age, toFree),
+ clean,
+ Bundle.DESC_ABANDONED_USERDIR(name, age, toFree),
+ cleanupListener);
+
+ CLEANUP_TASKS.put(cleanupListener, nf);
+ }
} else {
- if (isAutoRemoveAbanconedCache()) {
- JANITOR_RP.post(() -> cleanup(name));
+ if (isAutoRemoveAbandonedCache()) {
+ LOG.log(Level.INFO, "Janitor autoremove abandoned cache: "
+ candidate.cachedir.dir);
+ JANITOR_RP.post(() -> cleanup(candidate));
} else {
- ActionListener cleanupListener = cleanupAction(name,
Bundle.TXT_CONFIRM_CACHE_CLEANUP(name));
+ ActionListener cleanupListener = cleanupAction(candidate,
Bundle.TXT_CONFIRM_CACHE_CLEANUP(name));
Notification nf =
NotificationDisplayer.getDefault().notify(
Bundle.TIT_ABANDONED_CACHEDIR(name, toFree),
clean,
@@ -137,10 +156,10 @@ public class Janitor {
CLEANUP_TASKS.put(cleanupListener, nf);
}
}
- }
+ }
}
-
- static ActionListener cleanupAction(String name, String label) {
+
+ static ActionListener cleanupAction(CleanupPair cp, String label) {
return new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
@@ -154,7 +173,7 @@ public class Janitor {
null
);
if (DialogDescriptor.YES_OPTION ==
DialogDisplayer.getDefault().notify(descriptor)) {
- JANITOR_RP.post(() -> cleanup(name));
+ JANITOR_RP.post(() -> cleanup(cp));
}
Janitor.setEnabled(panel.isEnabledOnStartup());
Notification nf = CLEANUP_TASKS.get(this);
@@ -165,14 +184,13 @@ public class Janitor {
};
}
- static void cleanup(String name) {
- try (ProgressHandle handle =
ProgressHandle.createHandle(Bundle.LBL_CLEANUP(name))){
+ static void cleanup(CleanupPair cp) {
+ try (ProgressHandle handle =
ProgressHandle.createHandle(Bundle.LBL_CLEANUP(cp.getName()))) {
handle.start();
- deleteDir(getUserDir(name));
- deleteDir(getCacheDir(name));
- }
+ cp.delete();
+ }
}
-
+
public static final Preferences getPreferences() {
return NbPreferences.forModule(Janitor.class);
}
@@ -182,6 +200,7 @@ public class Janitor {
@Override
public void run() {
+ JANITOR_RP.post(Janitor::markUserCacheDirs, 5_000);
if (isEnabled()) {
// Starting delayed, not to interfere with other startup IO
operations
JANITOR_RP.post(Janitor::scanForJunk, 60_000);
@@ -190,127 +209,59 @@ public class Janitor {
}
- static void runNow() {
- JANITOR_RP.post(Janitor::scanForJunk);
- }
-
- static File getUserDir(String version) {
- File ret = null;
- File userDir = Places.getUserDirectory();
- if (userDir != null) {
- ret = new File(userDir.getParentFile(), version);
- ret = ret.isDirectory() ? ret : null;
- }
-
- return ret;
- }
-
- static File getCacheDir(String version) {
- File ret = null;
- File cacheDir = Places.getCacheDirectory();
- if (cacheDir != null) {
- ret = new File(cacheDir.getParentFile(), version);
- ret = ret.isDirectory() ? ret : null;
- }
- return ret;
+ static void markUserCacheDirs() {
+ writeVersion(Places.getCacheDirectory());
+ writeVersion(Places.getUserDirectory());
}
- static void deleteDir(File dir) {
- if ((dir == null) || !dir.exists()) return;
- Path path = dir.toPath();
- try {
- Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
- @Override
- public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs) throws IOException {
- Files.delete(file);
- return FileVisitResult.CONTINUE;
- }
-
- @Override
- public FileVisitResult postVisitDirectory(Path dir,
IOException exc) throws IOException {
- Files.delete(dir);
- return FileVisitResult.CONTINUE;
- }
- });
- } catch(IOException ex) {
- // Well we've tried
- }
+ static void runNow() {
+ JANITOR_RP.post(Janitor::scanForJunk);
}
- public static long size(File f) {
- if (f == null) {
- return 0;
- }
- final Path path = f.toPath();
- final AtomicLong size = new AtomicLong(0);
-
- try {
- Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
- @Override
- public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs) {
- size.addAndGet(attrs.size());
- return FileVisitResult.CONTINUE;
- }
-
- @Override
- public FileVisitResult visitFileFailed(Path file, IOException
exc) {
- return FileVisitResult.CONTINUE;
- }
-
- @Override
- public FileVisitResult postVisitDirectory(Path dir,
IOException exc) {
- return FileVisitResult.CONTINUE;
- }
- });
- } catch (IOException e) {
+ static void writeVersion(File baseDir) {
+ File lastUsedVersion = new File(baseDir, LAST_VERSION_NAME);
+ if (NB_VERSION != null) {
+ try (FileWriter fw = new FileWriter(lastUsedVersion)) {
+ fw.write(NB_VERSION);
+ } catch (IOException ex) {
+ // do nothing we've tried...
+ LOG.log(Level.FINE, "Could not write version info." , ex);
//NOI18N
+ }
}
-
- return size.get();
}
- static List<Pair<String, Integer>> getOtherVersions() {
+ static List<CleanupPair> getCandidates() {
+ Set<String> names = new HashSet<>();
File userDir = Places.getUserDirectory();
- List<Pair<String, Integer>> ret = new LinkedList<>();
- Set<String> availableUserDirs = new HashSet<>();
- Instant now = Instant.now();
if (userDir != null) {
File userParent = userDir.getParentFile();
for (File f : userParent.listFiles()) {
- availableUserDirs.add(f.getName());
- Path logFile = new File(f, LOGFILE_NAME).toPath();
- if (!f.equals(userDir) && Files.isRegularFile(logFile)) {
- try {
- Instant lastModified =
Files.getLastModifiedTime(logFile).toInstant();
- Integer age = (int) Duration.between(lastModified,
now).toDays();
- if (lastModified.plus(getUnusedDays(),
ChronoUnit.DAYS).isBefore(now)) {
- ret.add(Pair.of(f.getName(), age));
- }
- } catch (IOException ex) {
- //Just ignore what we can't process
- }
+ if (f.isDirectory() && !f.equals(userDir)) {
+ names.add(f.getName());
}
}
}
- //Search for abandoned cache dirs (cache dirs with no user dir)
File cacheDir = Places.getCacheDirectory();
if (cacheDir != null) {
File cacheParent = cacheDir.getParentFile();
for (File f : cacheParent.listFiles()) {
- if (f.isDirectory() &&
!availableUserDirs.contains(f.getName())) {
- if (new File(f, ALL_CHECKSUM_NAME).exists() &&
!cacheDir.equals(f)) {
- try {
- Instant lastModified =
Files.getLastModifiedTime(f.toPath()).toInstant();
- Integer age = (int) Duration.between(lastModified,
now).toDays();
- ret.add(Pair.of(f.getName(), age));
- } catch (IOException ex) {
- //Just ignore what we can't process
- }
- }
+ if (f.isDirectory() && !f.equals(userDir)) {
+ names.add(f.getName());
}
}
}
+
+ List<CleanupPair> ret = new LinkedList<>();
+ for (String name : names) {
+ CleanupDir user = CleanupDir.get(CleanupDir.Kind.USERDIR, name);
+ CleanupDir cache = CleanupDir.get(CleanupDir.Kind.CACHEDIR, name);
+ if (user != null || cache != null) {
+
+ ret.add(new CleanupPair(user, cache));
+ }
+ }
return ret;
}
@@ -330,12 +281,171 @@ public class Janitor {
return getPreferences().getInt(PROP_UNUSED_DAYS, UNUSED_DAYS);
}
- static boolean isAutoRemoveAbanconedCache() {
+ static boolean isAutoRemoveAbandonedCache() {
return getPreferences().getBoolean(PROP_AUTO_REMOVE_ABANDONED_CACHE,
true);
}
- static void setAutoRemoveAbanconedCache(boolean b) {
+ static void setAutoRemoveAbandonedCache(boolean b) {
getPreferences().putBoolean(PROP_AUTO_REMOVE_ABANDONED_CACHE, b);
}
+ private static class CleanupDir {
+
+ enum Kind {USERDIR, CACHEDIR};
+
+ private final Path dir;
+ private final Kind kind;
+
+ private CleanupDir(Path dir, Kind kind) {
+ this.dir = dir;
+ this.kind = kind;
+ }
+
+ static CleanupDir get(CleanupDir.Kind kind, String version) {
+ Path dir = kind == CleanupDir.Kind.USERDIR ?
Places.getUserDirectory().toPath() : Places.getCacheDirectory().toPath();
+ Path f = dir.getParent().resolve(version);
+ Path test = f.resolve((kind == CleanupDir.Kind.USERDIR) ?
LOGFILE_NAME : ALL_CHECKSUM_NAME);
+ return !f.equals(dir) && Files.isDirectory(f) &&
Files.isRegularFile(test) ? new CleanupDir(f, kind) : null;
+ }
+
+ public long size() {
+
+ if (dir == null) {
+ return 0;
+ }
+ final AtomicLong size = new AtomicLong(0);
+
+ try {
+ Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs) {
+ size.addAndGet(attrs.size());
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFileFailed(Path file,
IOException exc) {
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir,
IOException exc) {
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ } catch (IOException e) {
+ LOG.log(Level.FINE, "Something went wrong calculating the size
of " + dir, e); //NOI18N
+ }
+
+ return size.get();
+ }
+
+ public String getName() {
+ String name = dir.getFileName().toString();
+ Path f = dir.resolve(LAST_VERSION_NAME);
+ if (Files.isRegularFile(f)) {
+ try {
+ if (Files.size(f) < 100) {
+ try (BufferedReader br = Files.newBufferedReader(f)) {
+ name = br.readLine();
+ }
+ } else{
+ LOG.log(Level.WARNING, "Skipped version file " + f + "
as it is suspiciously large."); //NOI18N
+ }
+ } catch (IOException ex) {
+ // Could not read the file, stick to the dirname
+ }
+ } else {
+ LOG.log(Level.INFO, f.toString() + " is missing fallback to
dirname: " + name); //NOI18N
+ switch (name) { // Map a few elder Snap release revision to
IDE version number, these could be removed in NetBeans 21/22
+ case "80":
+ return "18";
+ case "76":
+ return "17";
+ case "74":
+ return "16";
+ case "69":
+ return "15";
+ }
+ }
+ return name;
+ }
+
+ public int age(Instant now) {
+ int ret = -1;
+ Path f;
+ switch (kind) {
+ case CACHEDIR:
+ f = dir.resolve(ALL_CHECKSUM_NAME);
+ break;
+ default:
+ f = dir.resolve(LOGFILE_NAME);
+ }
+ if (Files.isRegularFile(f)) {
+ try {
+ Instant lastModified =
Files.getLastModifiedTime(f).toInstant();
+ ret = (int) Duration.between(lastModified,
now).toDays();
+ } catch (IOException ex) {
+ //Just ignore what we can't process
+ }
+ }
+ return ret;
+ }
+
+ public void delete() {
+ try {
+ Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file,
BasicFileAttributes attrs) throws IOException {
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir,
IOException exc) throws IOException {
+ Files.delete(dir);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ } catch (IOException ex) {
+ // Well we've tried
+ LOG.log(Level.INFO, "Janitor couldn't remove " +
dir.toString(), ex); //NOI18N
+ }
+ }
+ }
+
+ private static class CleanupPair {
+ final CleanupDir userdir;
+ final CleanupDir cachedir;
+
+ public CleanupPair(CleanupDir userdir, CleanupDir cachedir) {
+ this.userdir = userdir;
+ this.cachedir = cachedir;
+ if ((userdir == null) && (cachedir == null)) {
+ throw new IllegalArgumentException("Both user and cache dirs
cannot be null!"); //NOI18N
+ }
+ }
+
+ public String getName() {
+ return userdir != null ? userdir.getName() : cachedir.getName();
+ }
+
+ public int age(Instant now) {
+ return userdir != null ? userdir.age(now) : cachedir.age(now);
+ }
+
+ public int size() {
+ int sum = 0;
+ sum += userdir != null ? userdir.size() : 0;
+ sum += cachedir != null ? cachedir.size() : 0;
+ return sum / (1_000_000) + 1;
+ }
+
+ public void delete() {
+ if (userdir != null) userdir.delete();
+ if (cachedir != null) cachedir.delete();
+ }
+
+ }
+
}
diff --git
a/platform/janitor/src/org/netbeans/modules/janitor/JanitorOptionsPanel.java
b/platform/janitor/src/org/netbeans/modules/janitor/JanitorOptionsPanel.java
index 78f8091a14..504fab8fce 100644
--- a/platform/janitor/src/org/netbeans/modules/janitor/JanitorOptionsPanel.java
+++ b/platform/janitor/src/org/netbeans/modules/janitor/JanitorOptionsPanel.java
@@ -39,14 +39,14 @@ public class JanitorOptionsPanel extends javax.swing.JPanel
{
void loadValues() {
cbEnabled.setSelected(Janitor.isEnabled());
- cbAutoRemove.setSelected(Janitor.isAutoRemoveAbanconedCache());
+ cbAutoRemove.setSelected(Janitor.isAutoRemoveAbandonedCache());
spinnerModel.setValue(Janitor.getUnusedDays());
resetRunNow();
}
void saveValues() {
Janitor.setEnabled(cbEnabled.isSelected());
- Janitor.setAutoRemoveAbanconedCache(cbAutoRemove.isSelected());
+ Janitor.setAutoRemoveAbandonedCache(cbAutoRemove.isSelected());
Janitor.setUnusedDays(spinnerModel.getNumber().intValue());
resetRunNow();
}
@@ -54,7 +54,7 @@ public class JanitorOptionsPanel extends javax.swing.JPanel {
boolean isChanged() {
boolean changed = false;
changed |= cbEnabled.isSelected() != Janitor.isEnabled();
- changed |= cbAutoRemove.isSelected() !=
Janitor.isAutoRemoveAbanconedCache();
+ changed |= cbAutoRemove.isSelected() !=
Janitor.isAutoRemoveAbandonedCache();
changed |= spinnerModel.getNumber().intValue() !=
Janitor.getUnusedDays();
changed |= !btRunNow.isEnabled();
return changed;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists