From 051ee1a9f7b20fa88c7d5600089d098a5c0111a0 Mon Sep 17 00:00:00 2001 From: Mark Hiner Date: Fri, 2 May 2025 15:59:03 -0500 Subject: [PATCH] Archive the Fiji.app folder if any file changes Because the app folder is signed as a whole, if any file changes the signing will have to change. This also means that if any file in Fiji.app is updated, we do not want to leave a .old version in the app folder as that will invalidate the signing. Instead, if we now detect any file in the .app folder is updated, we archive the complete state of the app folder to a single .old.app, and flag all files in the .app folder for installation/update. --- .../java/net/imagej/updater/Installer.java | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/imagej/updater/Installer.java b/src/main/java/net/imagej/updater/Installer.java index f1a0d09..cc436d0 100644 --- a/src/main/java/net/imagej/updater/Installer.java +++ b/src/main/java/net/imagej/updater/Installer.java @@ -33,6 +33,8 @@ import java.io.File; import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -139,6 +141,35 @@ public synchronized void start() throws IOException { } final List list = new ArrayList<>(); + + // Special care must be taken when updating files in the Mac bundle (Fiji.app) + // These files are all signed together as one unit, so individual files can not be modified. + // First we scan the update list to see if there's anything in Fiji.app to update + boolean updateMacApp = false; + for (final FileObject file : files.toInstallOrUpdate()) { + if (file.filename.contains("Fiji.app")) { + updateMacApp = true; + break; + } + } + // If there is a Mac app update, we need to flag ALL installed files in Fiji.app for update + if (updateMacApp) { + for (FileObject installed : files.installed()) { + if (installed.filename.contains("Fiji.app")) { + installed.stageForUpdate(files, true); + } + } + Path macBundlePath = files.prefix("Fiji.app").toPath(); + File backupAppFile = files.prefix("Fiji.old.app"); + Path backupAppPath = backupAppFile.toPath(); + // Remove the existing Fiji.old.app if it exists + if (backupAppFile.exists()) { + deleteDirectory(backupAppPath); + } + // Create a new Fiji.old.app backup + copyDirectory(macBundlePath, backupAppPath); + } + for (final FileObject file : files.toInstallOrUpdate()) { final String name = file.filename; File saveTo = files.prefixUpdate(name); @@ -150,7 +181,12 @@ public synchronized void start() throws IOException { // put them in the update subdir to migrate over as they must be in place // for the launch itself. So we rename the old file to a backup (.old) and // set up the download of the new file directly to replace it. - if (file.executable || + // + // However - in the case of Fiji.app files, we already handled the backup process atomically above, + // so all we need to do is update the saveTo to download directly and not to the update directory + if (name.contains("Fiji.app")) { + saveTo = files.prefix(name); + } else if (file.executable || saveTo.getAbsolutePath().contains("config" + File.separator + "jaunch")) { saveTo = files.prefix(name); String oldName = saveTo.getAbsolutePath() + ".old"; @@ -416,4 +452,39 @@ protected static boolean moveOutOfTheWay(final File file) { return file.renameTo(backup); } + private static void deleteDirectory(Path directory) throws IOException { + Files.walkFileTree(directory, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); // Delete each file + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); // Delete directory after contents + return FileVisitResult.CONTINUE; + } + }); + } + + private static void copyDirectory(Path sourceDir, Path targetDir) throws IOException { + Files.walkFileTree(sourceDir, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + Path targetPath = targetDir.resolve(sourceDir.relativize(dir)); + if (!Files.exists(targetPath)) { + Files.createDirectories(targetPath); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Path targetPath = targetDir.resolve(sourceDir.relativize(file)); + Files.copy(file, targetPath, StandardCopyOption.REPLACE_EXISTING); + return FileVisitResult.CONTINUE; + } + }); + } }