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 <laszlo.kisha...@gmail.com> 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: commits-unsubscr...@netbeans.apache.org For additional commands, e-mail: commits-h...@netbeans.apache.org For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists