Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTodor Boev2021-05-31 15:13:01 +0000
committerTodor Boev2021-06-30 19:16:47 +0000
commita57a0ce4a6b964c35a6172f4c043d7bf6057c6dd (patch)
tree1892133ebb581fb3a546eb0f435e0b9f44df602c
parent297545ddd241eca93f6e6423a9cfa800131b5483 (diff)
downloadrt.equinox.p2-a57a0ce4a6b964c35a6172f4c043d7bf6057c6dd.tar.gz
rt.equinox.p2-a57a0ce4a6b964c35a6172f4c043d7bf6057c6dd.tar.xz
rt.equinox.p2-a57a0ce4a6b964c35a6172f4c043d7bf6057c6dd.zip
Bug 537757 - Convert BackupStore to java.nio.fileI20210630-1800
- Cleanup to the path conversion methods - Cleaned up the backup store root handling - Using UUID for the unique part of the backup dir name - Convert to Java 7 file API using only File.move() for the most critical operation. - Improved logging - Changed BackupStore to SimpleBackupStore - Fixed the backup tests to use Java 7 Signed-off-by: Todor Boev <rinsvind@gmail.com> Change-Id: I16f3496bf9accacdb2932b0a2a14a96d3581d37b Reviewed-on: https://git.eclipse.org/r/c/equinox/rt.equinox.p2/+/182585 Tested-by: Equinox Bot <equinox-bot@eclipse.org>
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/META-INF/MANIFEST.MF2
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/pom.xml2
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/core/BackupTest.java384
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/natives/AllTests.java2
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/natives/BackupStoreTest.java189
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/natives/SimpleBackupStoreTest.java161
-rw-r--r--bundles/org.eclipse.equinox.p2.touchpoint.natives/META-INF/MANIFEST.MF9
-rw-r--r--bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/BackupStore.java896
-rw-r--r--bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/LazyBackupStore.java4
-rw-r--r--bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/Messages.java10
-rw-r--r--bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/SimpleBackupStore.java894
-rw-r--r--bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/Util.java72
-rw-r--r--bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/actions/UnzipAction.java24
-rw-r--r--bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/messages.properties8
14 files changed, 1283 insertions, 1374 deletions
diff --git a/bundles/org.eclipse.equinox.p2.tests/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.tests/META-INF/MANIFEST.MF
index ff61d2971..fc73bbdd1 100644
--- a/bundles/org.eclipse.equinox.p2.tests/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.tests/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.equinox.p2.tests;singleton:=true
-Bundle-Version: 1.8.300.qualifier
+Bundle-Version: 1.8.400.qualifier
Bundle-ClassPath: .
Bundle-Activator: org.eclipse.equinox.p2.tests.TestActivator
Bundle-Vendor: %providerName
diff --git a/bundles/org.eclipse.equinox.p2.tests/pom.xml b/bundles/org.eclipse.equinox.p2.tests/pom.xml
index dbd85361d..85f1ee5b0 100644
--- a/bundles/org.eclipse.equinox.p2.tests/pom.xml
+++ b/bundles/org.eclipse.equinox.p2.tests/pom.xml
@@ -16,7 +16,7 @@
<groupId>org.eclipse.equinox</groupId>
<artifactId>org.eclipse.equinox.p2.tests</artifactId>
- <version>1.8.300-SNAPSHOT</version>
+ <version>1.8.400-SNAPSHOT</version>
<packaging>eclipse-test-plugin</packaging>
<properties>
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/core/BackupTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/core/BackupTest.java
index daf3a5020..30faf7e96 100644
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/core/BackupTest.java
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/core/BackupTest.java
@@ -13,297 +13,189 @@
*******************************************************************************/
package org.eclipse.equinox.p2.tests.core;
-import java.io.*;
-import org.eclipse.equinox.internal.p2.touchpoint.natives.BackupStore;
+import static java.nio.file.FileVisitResult.CONTINUE;
+import static java.nio.file.StandardOpenOption.CREATE_NEW;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import org.eclipse.equinox.internal.p2.touchpoint.natives.SimpleBackupStore;
import org.eclipse.equinox.p2.tests.AbstractProvisioningTest;
public class BackupTest extends AbstractProvisioningTest {
- private static final String BUPREFIX = "BackupTest";
- private File sourceDir;
- private File aDir;
- private File aaDir;
- private File bDir;
- private File aTxt;
- private File bTxt;
- private File abDir;
- private File cTxt;
- private File cTxtRelative;
+ private static final String BUPREFIX = "backup-test";
+
+ private Path sourceDir;
+
+ private Path aDir;
+ private Path aaDir;
+ private Path aTxt;
+
+ private Path bDir;
+ private Path bTxt;
+
+ private Path abDir;
+
+ private Path cTxt;
+
+ private SimpleBackupStore store;
/**
- * Sets up directories and files under user.home
- * <ul><li>P2BUTEST/</li>
- * <ul><li>A/</li>
- * <ul><li>AA/</li>
- * <ul><li>a.txt</li>
- * <li>b.txt</li>
- * </ul>
- * </ul>
- * <li>B/</li>
- * </ul>
- * </ul>
+ * <pre>
+ * /p2-backup-test
+ * /a
+ * /aa
+ * /a.txt
+ * /b.txt
+ * /ab
+ * /c.txt
+ * /b
+ * </pre>
*/
@Override
- public void setUp() {
- // create some test files under user.home
- // do not want them under /tmp as it may be on its own file system (and even
- // be an in-memory file system).
- //
+ public void setUp() throws IOException {
+ // Don't want to backup under /tmp since it may be it's own file system or an
+ // in-memory file system
String userHome = System.getProperty("user.home");
- sourceDir = new File(new File(userHome), "P2BUTEST");
- aDir = new File(sourceDir, "A");
- aDir.mkdirs();
- aaDir = new File(aDir, "AA");
- aaDir.mkdir();
- abDir = new File(aDir, "AB");
- abDir.mkdir();
-
- bDir = new File(sourceDir, "B");
- bDir.mkdirs();
- aTxt = new File(aaDir, "a.txt");
- bTxt = new File(aaDir, "b.txt");
- cTxt = new File(abDir, "c.txt");
- cTxtRelative = new File(aaDir, "../AB/c.txt");
- try {
- writeToFile(aTxt, "A\nA file with an A");
- writeToFile(bTxt, "B\nA file with a B");
- writeToFile(cTxt, "C\nA file with a C");
- } catch (IOException e) {
- fail();
- }
- }
+ sourceDir = Path.of(userHome, "p2-backup-test");
+ deleteAll(sourceDir);
- private void writeToFile(File file, String content) throws IOException {
- file.getParentFile().mkdirs();
- file.createNewFile();
+ aDir = sourceDir.resolve("a");
+ Files.createDirectories(aDir);
- try (Writer writer = new BufferedWriter(new FileWriter(file))) {
- writer.write(content);
- }
+ aaDir = aDir.resolve("aa");
+ Files.createDirectories(aaDir);
+
+ aTxt = aaDir.resolve("a.txt");
+ Files.write(aTxt, "A\nA file with an A".getBytes());
+
+ bTxt = aaDir.resolve("b.txt");
+ Files.write(bTxt, "B\nA file with a B".getBytes());
+
+ abDir = aDir.resolve("ab");
+ Files.createDirectories(abDir);
+
+ cTxt = abDir.resolve("c.txt");
+ Files.write(cTxt, "C\nA file with a C".getBytes());
+
+ bDir = sourceDir.resolve("b");
+ Files.createDirectories(bDir);
+
+ store = new SimpleBackupStore(null, BUPREFIX);
}
@Override
- public void tearDown() {
- fullyDelete(sourceDir);
+ public void tearDown() throws IOException {
+ deleteAll(sourceDir);
}
- /**
- * Deletes a file, or a directory with all of it's children.
- * @param file the file or directory to fully delete
- * @return true if, and only if the file is deleted
- */
- private boolean fullyDelete(File file) {
- if (!file.exists())
- return true;
- if (file.isDirectory()) {
- File[] children = file.listFiles();
- for (File child : children) {
- if (!fullyDelete(new File(file, child.getName()))) {
- return false;
+ private static void deleteAll(Path path) throws IOException {
+ if (!Files.exists(path)) {
+ return;
+ }
+
+ Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ Files.delete(file);
+ return CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+ if (exc != null) {
+ throw exc;
}
+ Files.delete(dir);
+ return CONTINUE;
}
- }
- return file.delete();
+ });
}
/**
* Test that a path containing ".." can be backed up and restored.
*/
- public void testBackupRelative() {
- BackupStore store = new BackupStore(null, BUPREFIX);
- // backup and overwrite a.txt
- try {
- store.backup(cTxtRelative);
- } catch (IOException e) {
- e.printStackTrace();
- fail("IO Exception when backing up cTxtRelative");
- }
- if (cTxt.exists())
- fail("File not moved to backup - still exists");
- try {
- writeToFile(cTxt, "XXXX\n- This file should be restored with C");
- } catch (IOException e) {
- e.printStackTrace();
- fail("Could not write a file for testing purposes.");
- }
+ public void testBackupRelative() throws IOException {
+ Path cTxtRelative = aaDir.resolve(aaDir.relativize(cTxt));
- // restore
- try {
- store.restore();
- } catch (IOException e) {
- e.printStackTrace();
- fail("Restore operation failed with IOException");
- }
- // assert restore
- assertFileContent("Restore of C failed - not original content", cTxt, "C");
+ store.backup(cTxtRelative.toFile());
+ assertFalse(Files.exists(cTxt));
+
+ Files.write(cTxt, "XXXX\n- This file should be restored with C".getBytes());
+
+ store.restore();
+ assertFileContent("Restore of C failed - not original content", cTxt.toFile(), "C");
assertNoGarbage(store);
}
- public void testBackupRestore() {
- BackupStore store = new BackupStore(null, BUPREFIX);
- // backup and overwrite a.txt
- try {
- store.backup(aTxt);
- } catch (IOException e) {
- e.printStackTrace();
- fail("IO Exception when backing up aTxt");
- }
- if (aTxt.exists())
- fail("File not moved to backup - still exists");
- try {
- writeToFile(aTxt, "XXXX\n- This file should be restored with A");
- } catch (IOException e) {
- e.printStackTrace();
- fail("Could not write a file for testing purposes.");
- }
+ public void testBackupRestore() throws IOException {
+ store.backup(aTxt.toFile());
+ assertFalse("File not moved to backup - still exists", Files.exists(aTxt));
- // backup the empty B directory
- try {
- store.backup(bDir);
- } catch (IOException e) {
- e.printStackTrace();
- fail("IO Exception when backing up bDir");
- }
- if (bDir.exists())
- fail("Backed up directory was not moved");
-
- // backup b as a copy
- try {
- store.backupCopy(bTxt);
- assertFileContent("File should have been copied", bTxt, "B");
- } catch (IOException e) {
- fail("Could not backupCopy bTxt");
- }
+ Files.write(aTxt, "XXXX\n- This file should be restored with A".getBytes());
- // restore
- try {
- store.restore();
- } catch (IOException e) {
- e.printStackTrace();
- fail("Restore operation failed with IOException");
- }
+ store.backup(bDir.toFile());
+ assertFalse("Backed up directory was not moved", Files.isDirectory(bDir));
- // assert restore
- assertFileContent("Restore of A failed - not original content", aTxt, "A");
- if (!bDir.isDirectory() && bDir.listFiles().length != 0)
- fail("Empty directory not restored ok");
+ store.backupCopy(bTxt.toFile());
+ assertFileContent("File should have been copied", bTxt.toFile(), "B");
+ store.restore();
+ assertFileContent("Restore of A failed - not original content", aTxt.toFile(), "A");
+ assertTrue("Empty directory not restored ok", Files.isDirectory(bDir) && Files.list(bDir).count() == 0);
assertNoGarbage(store);
}
- public void testBackupDiscard() {
- BackupStore store = new BackupStore(null, BUPREFIX);
- // backup and overwrite a.txt
- try {
- store.backup(aTxt);
- } catch (IOException e) {
- e.printStackTrace();
- fail("IO Exception when backing up aTxt");
- }
- if (aTxt.exists())
- fail("File not moved to backup - still exists");
- try {
- writeToFile(aTxt, "XXXX\n- This file should be restored with A");
- } catch (IOException e) {
- e.printStackTrace();
- fail("Could not write a file for testing purposes.");
- }
-
- // backup the empty B directory
- try {
- store.backup(bDir);
- } catch (IOException e) {
- e.printStackTrace();
- fail("IO Exception when backing up bDir");
- }
- if (bDir.exists())
- fail("Backed up directory was not moved");
+ public void testBackupDiscard() throws IOException {
+ store.backup(aTxt.toFile());
+ assertFalse("File not moved to backup - still exists", Files.exists(aTxt));
- // restore
- store.discard();
+ Files.write(aTxt, "XXXX\n- This file should be restored with A".getBytes());
- // assert discard
- assertFileContent("Discard of A failed - not new content", aTxt, "XXXX");
- if (bDir.isDirectory())
- fail("Remove of empty directory not discarded ok");
+ store.backup(bDir.toFile());
+ assertFalse("Backed up directory was not moved", Files.exists(bDir));
+ store.discard();
+ assertFileContent("Discard of a.txt failed - not new content", aTxt.toFile(), "XXXX");
+ assertFalse("Empty directory not discarded - still exists", Files.isDirectory(bDir));
assertNoGarbage(store);
}
- public void testBackupAll() {
- BackupStore store = new BackupStore(null, BUPREFIX);
- // backup and overwrite a.txt
- try {
- store.backupAll(aDir);
- } catch (IOException e) {
- e.printStackTrace();
- fail("IO Exception when backing up aDir");
- }
- if (aTxt.exists())
- fail("File not moved to backup - still exists");
- if (bTxt.exists())
- fail("File bTxt not moved to backup - still exists");
-
- try {
- writeToFile(aTxt, "XXXX\n- This file should be restored with A");
- } catch (IOException e) {
- e.printStackTrace();
- fail("Could not write a file for testing purposes.");
- }
- try {
- store.restore();
- } catch (IOException e) {
- fail("Restore failed");
- }
- assertFileContent("A not restored", aTxt, "A");
- assertFileContent("B not restored", bTxt, "B");
+ public void testBackupAll() throws IOException {
+ store.backupAll(aDir.toFile());
+ assertFalse("File not moved to backup - still exists", Files.exists(aTxt));
+ assertFalse("File bTxt not moved to backup - still exists", Files.exists(bTxt));
+
+ Files.createDirectories(aTxt.getParent());
+ Files.write(aTxt, "XXXX\n- This file should be restored with A".getBytes(), CREATE_NEW);
+
+ store.restore();
+ assertFileContent("A not restored", aTxt.toFile(), "A");
+ assertFileContent("B not restored", bTxt.toFile(), "B");
assertNoGarbage(store);
}
- public void testBackupCopyAll() {
- BackupStore store = new BackupStore(null, BUPREFIX);
- // backup and overwrite a.txt
- try {
- store.backupCopyAll(aDir);
- } catch (IOException e) {
- e.printStackTrace();
- fail("IO Exception when backing up aDir");
- }
- if (!aTxt.exists())
- fail("File not copied to backup - does not exist");
- if (!bTxt.exists())
- fail("File bTxt not copied to backup - does not exists");
-
- try {
- writeToFile(aTxt, "XXXX\n- This file should be restored with A");
- writeToFile(bTxt, "XXXX\n- This file should be restored with B");
- } catch (IOException e) {
- e.printStackTrace();
- fail("Could not write a file for testing purposes.");
- }
- try {
- store.restore();
- } catch (IOException e) {
- fail("Restore failed");
- }
- assertFileContent("A not restored", aTxt, "A");
- assertFileContent("B not restored", bTxt, "B");
+ public void testBackupCopyAll() throws IOException {
+ store.backupCopyAll(aDir.toFile());
+ assertTrue("File not copied to backup - does not exist", Files.exists(aTxt));
+ assertTrue("File bTxt not copied to backup - does not exists", Files.exists(bTxt));
+
+ Files.write(aTxt, "XXXX\n- This file should be restored with A".getBytes());
+ Files.write(bTxt, "XXXX\n- This file should be restored with B".getBytes());
+
+ store.restore();
+ assertFileContent("A not restored", aTxt.toFile(), "A");
+ assertFileContent("B not restored", bTxt.toFile(), "B");
assertNoGarbage(store);
}
- private void assertNoGarbage(BackupStore store) {
- File buDir = new File(store.getBackupRoot(), BUPREFIX);
- if (buDir.exists())
- fail("Backup directory not cleaned up");
-
- // Set roots = store.getBackupRoots();
- // if (roots.size() == 0)
- // assertTrue("Root set is empty", true);
- // for (Iterator itor = roots.iterator(); itor.hasNext();) {
- // File root = (File) itor.next();
- // File buDir = new File(root, BUPREFIX);
- // if (buDir.exists())
- // fail("Backup directory not cleaned up");
- // }
+ private static void assertNoGarbage(SimpleBackupStore store) {
+ File buDir = store.getBackupRoot();
+ assertFalse("Backup directory not cleaned up", buDir.exists());
}
}
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/natives/AllTests.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/natives/AllTests.java
index 08ffbaf7c..27f0c1618 100644
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/natives/AllTests.java
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/natives/AllTests.java
@@ -23,7 +23,7 @@ import org.junit.runners.Suite;
@Suite.SuiteClasses({
ChmodActionTest.class, CleanupzipActionTest.class, CollectActionTest.class, LinkActionTest.class,
MkdirActionTest.class, NativeTouchpointTest.class, RmdirActionTest.class, UnzipActionTest.class,
- CopyActionTest.class, RemoveActionTest.class, BackupStoreTest.class,
+ CopyActionTest.class, RemoveActionTest.class, SimpleBackupStoreTest.class,
CheckAndPromptNativePackageWindowsRegistryTest.class
})
public class AllTests {
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/natives/BackupStoreTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/natives/BackupStoreTest.java
deleted file mode 100644
index 011c4d3cd..000000000
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/natives/BackupStoreTest.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2014, 2017 EclipseSource and others.
- *
- * This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- * EclipseSource - initial API and implementation
- *******************************************************************************/
-package org.eclipse.equinox.p2.tests.touchpoint.natives;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.Writer;
-import org.eclipse.equinox.internal.p2.touchpoint.natives.BackupStore;
-import org.eclipse.equinox.p2.tests.AbstractProvisioningTest;
-
-public class BackupStoreTest extends AbstractProvisioningTest {
-
- private static final String BUPREFIX = "BackupTest";
- private File sourceDir;
- private File aDir;
- private File aaDir;
- private File aTxt;
- private File bDir;
- private File bTxt;
-
- /**
- * Sets up directories and files under user.home
- * <ul><li>P2BUTEST/</li>
- * <ul><li>A/</li>
- * <ul><li>AA/</li>
- * <ul><li>a.txt</li>
- * </ul>
- * </ul>
- * </ul>
- * </ul>
- */
- @Override
- public void setUp() {
- // create some test files under user.home
- // do not want them under /tmp as it may be on its own file system (and even
- // be an in-memory file system).
- //
- String userHome = System.getProperty("user.home");
- sourceDir = new File(new File(userHome), "P2BUTEST");
- fullyDelete(sourceDir);
- aDir = new File(sourceDir, "A");
- aDir.mkdirs();
- aaDir = new File(aDir, "AA");
- aaDir.mkdir();
- aTxt = new File(aaDir, "eclipse.exe");
- bDir = new File(sourceDir, "B");
- bTxt = new File(bDir, "b.txt");
- try {
- writeToFile(aTxt, "A\nA file with an A");
- } catch (IOException e) {
- fail();
- }
- }
-
- private void writeToFile(File file, String content) throws IOException {
- file.getParentFile().mkdirs();
- file.createNewFile();
-
- try (Writer writer = new BufferedWriter(new FileWriter(file))) {
- writer.write(content);
- }
- }
-
- @Override
- public void tearDown() {
- fullyDelete(sourceDir);
- }
-
- /**
- * Deletes a file, or a directory with all of it's children.
- * @param file the file or directory to fully delete
- * @return true if, and only if the file is deleted
- */
- private boolean fullyDelete(File file) {
- if (!file.exists())
- return true;
- if (file.isDirectory()) {
- File[] children = file.listFiles();
- for (File child : children) {
- if (!fullyDelete(new File(file, child.getName()))) {
- return false;
- }
- }
- }
- return file.delete();
- }
-
- public void testBackupByRenamingFile() {
- String filePath = aTxt.getAbsolutePath();
- class TestBackupByRenamingFileBackupStore extends BackupStore {
- public TestBackupByRenamingFileBackupStore() {
- super(null, BUPREFIX);
- }
-
- @Override
- public void renameInPlace(File file) {
- super.renameInPlace(file);
- }
-
- @Override
- protected String getTimeStamp() {
- return "-123";
- }
- }
- TestBackupByRenamingFileBackupStore backupStore = new TestBackupByRenamingFileBackupStore();
- backupStore.renameInPlace(aTxt);
-
- assertFalse(aTxt.exists());
- assertTrue(new File(filePath + "-123.p2bu").exists());
-
- backupStore.discard();
- assertFalse(new File(filePath + "-123.p2bu").exists());
- }
-
- public void testRenameIfMoveToBackupFails() throws IOException {
- String filePath = aTxt.getAbsolutePath();
- class TestRenameIfMoveToBackupFailsBackupStore extends BackupStore {
- public TestRenameIfMoveToBackupFailsBackupStore() {
- super(null, BUPREFIX);
- }
-
- @Override
- public void renameInPlace(File file) {
- super.renameInPlace(file);
- }
-
- @Override
- public boolean moveToBackupStore(File a, File b) {
- return false;
- }
-
- @Override
- public void moveToBackup(File a, File b) throws IOException {
- super.moveToBackup(a, b);
- }
-
- @Override
- protected String getTimeStamp() {
- return "-123";
- }
- }
- TestRenameIfMoveToBackupFailsBackupStore backupStore = new TestRenameIfMoveToBackupFailsBackupStore();
- backupStore.moveToBackup(aTxt, bTxt);
-
- assertFalse(aTxt.exists());
- assertTrue(new File(filePath + "-123.p2bu").exists());
- assertFalse(bTxt.exists());
-
- backupStore.discard();
- assertFalse(new File(filePath + "-123.p2bu").exists());
- }
-
- public void testDoNotRenameIfMoveToBackupWorks() throws IOException {
- String filePath = aTxt.getAbsolutePath();
- new BackupStore(null, BUPREFIX) {
- @Override
- public void renameInPlace(File file) {
- super.renameInPlace(file);
- }
-
- @Override
- public boolean moveToBackupStore(File a, File b) {
- return super.moveToBackupStore(a, b);
- }
-
- @Override
- public void moveToBackup(File a, File b) throws IOException {
- super.moveToBackup(a, b);
- }
- }.moveToBackup(aTxt, bTxt);
-
- assertFalse(aTxt.exists());
- assertFalse(new File(filePath + ".p2bu").exists());
- assertTrue(bTxt.exists());
- }
-}
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/natives/SimpleBackupStoreTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/natives/SimpleBackupStoreTest.java
new file mode 100644
index 000000000..0fa1cda76
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/natives/SimpleBackupStoreTest.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2014, 2021 EclipseSource and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * EclipseSource - initial API and implementation
+ * Todor Boev - refactor to the java 7 file api
+ *******************************************************************************/
+package org.eclipse.equinox.p2.tests.touchpoint.natives;
+
+import static java.nio.file.FileVisitResult.CONTINUE;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import org.eclipse.equinox.internal.p2.touchpoint.natives.SimpleBackupStore;
+import org.eclipse.equinox.p2.tests.AbstractProvisioningTest;
+
+public class SimpleBackupStoreTest extends AbstractProvisioningTest {
+ private static final String BACKUP_PREFIX = "backup-test";
+
+ private Path sourceDir;
+
+ private Path aDir;
+ private Path aaDir;
+ private Path aFile;
+
+ /**
+ * <pre>
+ * /p2-backup-test
+ * /a
+ * /aa
+ * /eclipse.exe
+ * </pre>
+ */
+ @Override
+ public void setUp() throws IOException {
+ String userHome = System.getProperty("user.home");
+
+ sourceDir = Path.of(userHome, "p2-backup-test");
+ deleteAll(sourceDir);
+
+ aDir = sourceDir.resolve("a");
+ Files.createDirectories(aDir);
+
+ aaDir = aDir.resolve("aa");
+ Files.createDirectories(aaDir);
+
+ // The eclipse.exe is the only one eligible for backup-in-place
+ aFile = aaDir.resolve("eclipse.exe");
+ Files.createFile(aFile);
+ }
+
+ @Override
+ public void tearDown() throws IOException {
+ deleteAll(sourceDir);
+ }
+
+ private static void deleteAll(Path path) throws IOException {
+ if (!Files.exists(path)) {
+ return;
+ }
+
+ Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ Files.delete(file);
+ return CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+ if (exc != null) {
+ throw exc;
+ }
+ Files.delete(dir);
+ return CONTINUE;
+ }
+ });
+ }
+
+ public void testBackupInPlace() throws IOException {
+ class TestMoveInPlaceStore extends SimpleBackupStore {
+ public TestMoveInPlaceStore() {
+ super(null, BACKUP_PREFIX);
+ }
+
+ @Override
+ public Path toInPlaceBackupPath(Path path) {
+ return super.toInPlaceBackupPath(path);
+ }
+
+ @Override
+ protected void move(Path a, Path b) throws IOException {
+ // In place backup - allow
+ if (b.equals(toInPlaceBackupPath(a))) {
+ super.move(a, b);
+ }
+ // In place restore - allow
+ else if (a.equals(toInPlaceBackupPath(b))) {
+ super.move(a, b);
+ }
+ // Everything else - fail
+ else {
+ throw new IOException("Test fail move: " + a + " -> " + b);
+ }
+ }
+ }
+
+ TestMoveInPlaceStore buStore = new TestMoveInPlaceStore();
+
+ final Path path = aFile;
+ final Path inPlaceBuPath = buStore.toInPlaceBackupPath(aFile);
+
+ buStore.backup(path.toFile());
+
+ assertFalse(Files.exists(path));
+ assertTrue(Files.exists(inPlaceBuPath));
+
+ buStore.restore();
+
+ assertTrue(Files.exists(path));
+ assertFalse(Files.exists(inPlaceBuPath));
+ }
+
+ public void testNoBackupInPlace() throws IOException {
+ class TestNoBackupInPlaceStore extends SimpleBackupStore {
+ public TestNoBackupInPlaceStore() {
+ super(null, BACKUP_PREFIX);
+ }
+
+ @Override
+ public Path toInPlaceBackupPath(Path path) {
+ return super.toInPlaceBackupPath(path);
+ }
+ }
+
+ TestNoBackupInPlaceStore buStore = new TestNoBackupInPlaceStore();
+
+ final Path path = aFile;
+ final Path inPlaceBuPath = buStore.toInPlaceBackupPath(aFile);
+
+ buStore.backup(path.toFile());
+
+ assertFalse(Files.exists(path));
+ assertFalse(Files.exists(inPlaceBuPath));
+
+ buStore.restore();
+
+ assertTrue(Files.exists(path));
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.natives/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.touchpoint.natives/META-INF/MANIFEST.MF
index 21663c044..d5c588998 100644
--- a/bundles/org.eclipse.equinox.p2.touchpoint.natives/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.natives/META-INF/MANIFEST.MF
@@ -6,13 +6,16 @@ Bundle-Version: 1.4.100.qualifier
Bundle-Activator: org.eclipse.equinox.internal.p2.touchpoint.natives.Activator
Bundle-Vendor: %providerName
Bundle-Localization: plugin
-Export-Package: org.eclipse.equinox.internal.p2.touchpoint.natives;x-internal:=true,
+Export-Package:
+ org.eclipse.equinox.internal.p2.touchpoint.natives;x-internal:=true,
org.eclipse.equinox.internal.p2.touchpoint.natives.actions;x-internal:=true
-Require-Bundle: org.eclipse.equinox.common,
+Require-Bundle:
+ org.eclipse.equinox.common,
org.eclipse.equinox.app;bundle-version="1.3.0";resolution:=optional
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
-Import-Package: org.eclipse.equinox.internal.p2.core.helpers,
+Import-Package:
+ org.eclipse.equinox.internal.p2.core.helpers,
org.eclipse.equinox.internal.p2.engine,
org.eclipse.equinox.p2.core;version="[2.0.0,3.0.0)",
org.eclipse.equinox.p2.engine;version="[2.0.0,3.0.0)",
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/BackupStore.java b/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/BackupStore.java
deleted file mode 100644
index 9206eab61..000000000
--- a/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/BackupStore.java
+++ /dev/null
@@ -1,896 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2009, 2018 Cloudsmith Inc. and others.
- *
- * This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- * Cloudsmith Inc. - initial API and implementation
- * SAP AG - Ongoing development
- *******************************************************************************/
-
-package org.eclipse.equinox.internal.p2.touchpoint.natives;
-
-import java.io.*;
-import java.net.*;
-import java.util.*;
-import java.util.Map.Entry;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
-import org.eclipse.osgi.util.NLS;
-
-/**
- * Stores files by copying them to a uniquely named temporary directory.
- * The BackupStore remembers filenames and can recreate them in their original location.
- *
- * <h3>Usage</h3>
- * The user of this class should instantiate the BackupStore with some prefix that is
- * meaningful to a human. Uniqueness is obtained without the prefix - the prefix is used to
- * be able to differentiate between different backup directories by a human (in case of crashes etc).
- *
- * If instantiated with a directory this directory will be used to store the backup root directory. If
- * this directory is null, the users home directory is used by default.
- *
- * Once instantiated, use the {@link #backup(File)} and {@link #backupDirectory(File)} methods
- * to move files to backup instead of deleting them. A file that
- * is backed up should not be deleted - it is simply moved out of the way.
- * Use {@link #backupCopy(File)} to
- * move the file out of harms way, but keep a copy of it in the original location.
- * The methods {@link #backupAll(File)} and {@link #backupCopyAll(File)} backs up an entire structure.
- *
- * When backup is finished - the user should either call {@link #restore()} to put all
- * of the files back, or call {@link #discard()} to remove all of the backed up "copies".
- *
- * If {@link #restore()} or {@link #discard()} is not called the backup files will never be deleted.
- *
- * The backup store does not synchronize directories - actions that write new files are
- * responsible for removing them. Overwriting existing files should be done by first backing
- * up the file, and then creating a new file. Modifying a file, should be done by
- * using {@link #backupCopy(File)} or
- * first making a copy, then backing up the original, and then renaming the copy.
- *
- * <h3>Read Only and Permissions</h3>
- * Directories that are read only (to current user) can not be backed up.
- * Backup is performed using {@link File#renameTo(File)} and handling of permissions
- * is operating system dependent. It is expected that a Un*x type system retains the
- * permissions as a file is moved to the backup store and later gets restored.
- * Backup directories are created as they are needed and will (at least on Un*x) inherit the
- * permissions from its parent directory.
- *
- * If a rename can not be performed, the backup store will make a copy and delete the original
- * file. This makes it possible to backup and restore across volume boundaries.
- *
- * When restoring directories they
- * will be created with permissions in a platform specific way (on UN*IX they will inherit the permissions
- * of the parent directory).
- *
- * <h3>Checkpointing</h3>
- * Checkpointing (i.e. to be able to rollback to a particular point) can be implemented by using
- * multiple instances of BackupStore. The client code will need to remember the individual order
- * among the backup stores.
- *
- * <h3>Restartability</h3>
- * Not implemented - it is possible to obtain the name of the backup directories,
- * so manual restore is possible after a crash. An idea is to add persistence to a file, and
- * be able to read it back in again.
- *
- * <h3>A note about exceptions</h3>
- * In general {@link IllegalArgumentException} is thrown when attempting an operation
- * that is considered "wrong use", and an {@link IllegalStateException} or subclass thereof is thrown on an overall
- * wrong use of BackupStore (i.e. attempt to backup when store has been restored). Some cases of
- * "wrong use" can not be differentiated from I/O errors (like a "file not found" as this could
- * be caused by an entire disk disappearing - in these case an {@link IOException} is thrown.
- *
- * <h3>Implementation Note</h3>
- * The backup root directory will contain folders that reflects file system roots. These are encoded using
- * "_" for the UNI*X root directory, "__" for a Windows network mounted directory, and single "drive letter" folders
- * corresponding to Windows drive letters. Typically, on UN*X there will only be a "_" directory in the backup root,
- * and on windows there will typically be a single directory called "C".
- *
- *
- */
-public class BackupStore implements IBackupStore {
-
- private static final String BACKUP_FILE_EXTENSION = ".p2bu"; //$NON-NLS-1$
-
- /**
- * The name to use for a directory that represents leading separator (i.e. "/" or "\").
- */
- private static final String ROOTCHAR = "_"; //$NON-NLS-1$
-
- /**
- * Map of directory File to backup root (File) - the backup root has
- * a directory named {@link #backupName} where the backup is found.
- */
- // private Map backups = new HashMap();
- private final File backupRoot;
-
- /**
- * The name of the backup directory (no path - relative to the backup root).
- */
- private final String backupName;
-
- /**
- * The name of a dummy file used to backup empty directories
- */
- private final String dummyName;
-
- /**
- * A server socket that is used to obtain a port (a shared resource on this machine)
- * and thus create a unique number. Used as part of the unique id of backup directories
- * and probe files.
- */
- private ServerSocket socket = null;
-
- /**
- * Counter of how many files where backed up. Used as a simple check mechanism if
- * everything was restored (a guard against manual/external tampering with the backup directories).
- */
- private long backupCounter;
-
- /**
- * Counter of how many files where restored. See {@link #backupCounter}.
- */
- private long restoreCounter;
-
- /**
- * Flag indicating if this BackupStore has been restored or canceled.
- */
- private boolean closed;
-
- private final Map<String, String> renamedInPlace = new HashMap<>();
-
- /**
- * Generates a BackupStore with a default prefix of ".p2bu" for backup directory and
- * probe file.
- * The full id of the store is on the format "prefix_hextime_hexIPport"
- * - see {@link #genUnique()} for more info.
- */
- public BackupStore() {
- this(null, BACKUP_FILE_EXTENSION);
- }
-
- /**
- * Generates a BackupStore with a specified prefix for backup directories and
- * probe file.
- * The full id of the store is on the format "prefix_hextime_hexipport"
- * - see {@link #genUnique()} for more info.
- *
- * @param buParentDirectory - name of directory where the backup directory should be created - if null,
- * java.io.tmpdir is used
- * @param prefix - prefix used for human identification of backup directories
- */
- public BackupStore(File buParentDirectory, String prefix) {
- if (buParentDirectory == null) {
- buParentDirectory = new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$
- }
- backupRoot = buParentDirectory;
-
- // generate a name for the backup store and the dummy file used for empty directories
- String unique = genUnique();
- dummyName = prefix + "d_" + unique; //$NON-NLS-1$
- backupName = prefix + "_" + unique; //$NON-NLS-1$
- backupCounter = 0;
- restoreCounter = 0;
- closed = false;
- }
-
- /**
- * Since a socket port is used to create a unique number, the socket
- * must be closed if this instance is garbage collected and the user
- * of the instance has not either restored or discarded.
- */
- @Override
- protected void finalize() throws Throwable {
- try {
- if (socket != null && !socket.isClosed()) {
- socket.close();
- }
- } finally {
- super.finalize();
- }
- }
-
- /**
- * Returns the unique backup name (this is the name of generated backup directories).
- *
- * @return the backup name.
- */
- @Override
- public String getBackupName() {
- return backupName;
- }
-
- public File getBackupRoot() {
- return backupRoot;
- }
-
- /**
- * Backup the file by moving it to the backup store (for later (optional) restore).
- * Calling this method with a file that represents a directory is equivalent to calling
- * {@link #backupDirectory(File)}.
- *
- * A file (path) can only be backed up once per BackupStore instance.
- * When the file is backed up, it is moved to a directory under this BackupStore instance's directory
- * with a relative path corresponding to the original relative path from the backup root e.g.
- * the file /A/B/C/foo.txt could be moved to /A/.p2bu_ffffff_ffffff/B/C/foo.txt when /A is the
- * backup root.
- *
- * If a directory is first backed up, and later replaced by a regular file, and this file
- * is backed up (or vice versa) - an {@link IllegalArgumentException} is thrown
- *
- * A backup can not be performed on a closed BackupStore.
- *
- * @param file - the file (or directory) to backup
- * @return true if the file was backed up, false if this file (path) has already been backed up (the file is not
- * moved to the store).
- * @throws IOException - if the backup operation fails, or the file does not exist
- * @throws ClosedBackupStoreException - if the BackupStore has been closed
- * @throws IllegalArgumentException - on type mismatch (file vs. directory) of earlier backup, or if file does not
- * exist
- */
- @Override
- public boolean backup(File file) throws IOException {
- if (closed) {
- throw new ClosedBackupStoreException("Can not perform backup()"); //$NON-NLS-1$
- }
- if (!file.exists()) {
- throw new IOException(NLS.bind(Messages.BackupStore_file_not_found, file.getAbsolutePath()));
- }
- if (file.isDirectory()) {
- return backupDirectory(file);
- }
- file = makeParentCanonical(file);
- File buFile = getBackupFile(file);
- // already backed up, but was a directory = wrong usage
- if (buFile.isDirectory()) {
- throw new IllegalArgumentException(
- NLS.bind(Messages.BackupStore_directory_file_mismatch, buFile.getAbsolutePath()));
- }
- // has already been backed up - can only be done once with one BackupStore
- if (buFile.exists()) {
- // although backed up, the file can be still on the file system when, for example,
- // two IUs are unzipping their contents to the same location and share a few common file,
- // which have to be removed twice
- if (file.exists() && !file.delete()) {
- throw new IOException(NLS.bind(Messages.BackupStore_can_not_remove, file.getAbsolutePath()));
- }
- return false;
- }
-
- moveToBackup(file, buFile);
-
- return true;
- }
-
- /**
- * Move/rename file to a backup file. Callers of the method must have ensured that the source file exists and the
- * backup file has not been created yet.
- *
- * @param file source file to move; should already exist and must not be directory
- * @param buFile destination backup file to move to; should not exist and must be a directory
- * @throws IOException if the backup operation fails
- */
- protected void moveToBackup(File file, File buFile) throws IOException {
- // make sure all of the directories exist / gets created
- buFile.getParentFile().mkdirs();
- if (buFile.getParentFile().exists() && !buFile.getParentFile().isDirectory()) {
- throw new IllegalArgumentException(
- NLS.bind(Messages.BackupStore_file_directory_mismatch, buFile.getParentFile().getAbsolutePath()));
- }
- if (moveToBackupStore(file, buFile)) {
- backupCounter++;
- return;
- }
- // could not move - this can happen because source and target are on different volumes, or
- // that source is locked "in use" on a windows machine. The copy will work across volumes,
- // but the locked file will fail on the subsequent delete.
- //
- // Rename in place
- if (isEclipseExe(file)) {
- renameInPlace(file);
- } else {
- Util.copyStream(new FileInputStream(file), true, new FileOutputStream(buFile), true);
- backupCounter++;
- }
-
- // File.exists() is not reliable so always attempt to delete first and check why it may have failed second.
- if (!file.delete() && file.exists()) {
- throw new IOException(NLS.bind(Messages.BackupStore_can_not_delete_after_copy_0, file));
- }
- }
-
- private boolean isEclipseExe(File file) {
- String launcher = System.getProperty("eclipse.launcher"); //$NON-NLS-1$
- if (launcher != null) {
- String base = new File(launcher).getName();
- if (file.getName().equalsIgnoreCase(base)) {
- return true;
- }
- }
- return file.getName().equalsIgnoreCase("eclipse.exe") || file.getName().equalsIgnoreCase("eclipsec.exe"); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- protected boolean moveToBackupStore(File file, File buFile) {
- if (file.renameTo(buFile)) {
- // if the original file still exists, we have a problem.
- if (!file.exists()) {
- return true;
- }
- // If the renamed work, but the file still exists, remove the backup
- // and return false
- if (buFile.exists()) {
- buFile.delete();
- }
- }
- return false;
- }
-
- protected void renameInPlace(File file) {
- String newName = file.getAbsolutePath() + getTimeStamp() + BACKUP_FILE_EXTENSION;
- renamedInPlace.put(file.getAbsolutePath(), newName);
- file.renameTo(new File(newName));
- }
-
- protected String getTimeStamp() {
- return "-" + new Date().getTime(); //$NON-NLS-1$
- }
-
- private File getBackupFile(File file) {
- File buRoot = backupRoot;
- File buDir = new File(buRoot, backupName);
- // create the relative path from root and use that in buDir
- File buFile = new File(buDir, makeRelativeFromRoot(file).getPath());
- return buFile;
- }
-
- /**
- * Backs up a file, or everything under a directory.
- *
- * @param file - file to backup or directory
- * @throws IOException if backup operation failed
- */
- @Override
- public void backupAll(File file) throws IOException {
- if (!file.exists()) {
- return;
- }
- file = makeParentCanonical(file);
- if (file.isDirectory()) {
- File[] files = file.listFiles();
- if (files != null) {
- for (File f : files) {
- backupAll(f);
- }
- }
- }
- backup(file);
- }
-
- /**
- * Backs up a file, or everything under a directory.
- * A copy of the backup is left in the original place.
- *
- * @param file
- * @throws IOException
- */
- @Override
- public void backupCopyAll(File file) throws IOException {
- if (!file.exists()) {
- return;
- }
- file = makeParentCanonical(file);
- if (file.isDirectory()) {
- File[] files = file.listFiles();
- if (files != null) {
- for (File f : files) {
- backupCopyAll(f);
- }
- }
- // if directory was empty, it needs to be backed up and then recreated
- //
- if (files == null || files.length == 0) {
- backupDirectory(file);
- file.mkdir();
- }
- } else {
- backupCopy(file);
- }
- }
-
- /**
- * Backup the file by moving it to the backup store (for later (optional) restore) but leaving
- * a copy of the contents in the original location.
- * Calling this method with a file that represents a directory throws an {@link IllegalArgumentException}.
- *
- * A file (path) can only be backed up once per BackupStore instance.
- * When the file is backed up, it is moved to a directory under this BackupStore instance's directory
- * with a relative path corresponding to the original relative path from the backup root e.g.
- * the file /A/B/C/foo.txt could be moved to /A/.p2bu_ffffff_ffffff/B/C/foo.txt when /A is the
- * backup root.
- *
- * If a directory is first backed up, and later replaced by a regular file, and this file
- * is backed up (or vice versa) - an {@link IllegalArgumentException} is thrown
- *
- * A backup can not be performed on a closed BackupStore.
- *
- * @param file - the file (or directory) to backup
- * @return true if the file was backed up, false if this file (path) has already been backed up (the file is not
- * moved to the store).
- * @throws IOException - if the backup operation fails, or the file does not exist
- * @throws ClosedBackupStoreException - if the BackupStore has been closed
- * @throws IllegalArgumentException - on type mismatch (file vs. directory) of earlier backup, or if file is a
- * Directory
- */
- @Override
- public boolean backupCopy(File file) throws IOException {
- if (closed) {
- throw new ClosedBackupStoreException(Messages.BackupStore_backupCopy_closed_store);
- }
- if (!file.exists()) {
- throw new IOException(NLS.bind(Messages.BackupStore_file_not_found, file.getAbsolutePath()));
- }
- if (file.isDirectory()) {
- throw new IllegalArgumentException(
- NLS.bind(Messages.BackupStore_can_not_copy_directory, file.getAbsolutePath()));
- }
- file = makeParentCanonical(file);
- // File buRoot = backupRoot;
- // File buRoot = findBackupRoot(file);
- File buDir = new File(backupRoot, backupName);
- // move the file
- // create the relative path from root and use that in buDir
- File buFile = new File(buDir, makeRelativeFromRoot(file).getPath());
- // already backed up, but was a directory = wrong usage
- if (buFile.isDirectory()) {
- throw new IllegalArgumentException(
- NLS.bind(Messages.BackupStore_directory_file_mismatch, buFile.getAbsolutePath()));
- }
- // has already been backed up - can only be done once with one BackupStore
- if (buFile.exists()) {
- return false;
- }
-
- // make sure all of the directories exist / gets created
- buFile.getParentFile().getCanonicalFile().mkdirs();
- if (buFile.getParentFile().exists() && !buFile.getParentFile().isDirectory()) {
- throw new IllegalArgumentException(
- NLS.bind(Messages.BackupStore_file_directory_mismatch, buFile.getParentFile().getAbsolutePath()));
- }
-
- // just make a copy - one has to be made in one direction anyway
- // A renameTo followed by a copy is preferred as it preserves file permissions on the moved file
- // but it is easier to just copy and keep original.
- Util.copyStream(new FileInputStream(file), true, new FileOutputStream(buFile), true);
- backupCounter++;
- return true;
- }
-
- /**
- * Performs backup of an empty directory. The directory must be empty before it can be backed up (i.e.
- * similar to a delete of a directory). Backup the files of the directory first.
- * A call to backup a directory is really only needed for empty directories as a restore
- * of a file will also restore all of its parent directories.
- *
- * @param file - the (empty) directory to back up
- * @return true if the directory was moved to backup. false if the directory was already backed up
- * @throws IllegalArgumentException if file is not a directory, or is not empty.
- * @throws IOException if directory can not be moved to the backup store, or if the directory is not
- * writeable
- */
- @Override
- public boolean backupDirectory(File file) throws IOException {
- if (!file.isDirectory()) {
- throw new IllegalArgumentException(NLS.bind(Messages.BackupStore_not_a_directory, file.getAbsolutePath()));
- }
- file = makeParentCanonical(file);
- if (file.list().length != 0) {
- throw new IllegalArgumentException(
- NLS.bind(Messages.BackupStore_directory_not_empty, file.getAbsolutePath()));
- }
- // the easiest way is to create a dummy file and back that up (the dummy is simply ignored when restoring).
- File dummy = new File(file, dummyName);
- dummy = makeParentCanonical(dummy);
- File buFile = getBackupFile(dummy);
- boolean backedUp = buFile.exists();
- // backup only if the folder has not been already backed up;
- // this can happen if, for example, two IUs unzip to the same folder and then want to delete it
- if (!backedUp) {
- if (closed) {
- throw new ClosedBackupStoreException("Can not perform backup()"); //$NON-NLS-1$
- }
- if (!dummy.createNewFile()) {
- throw new IOException(NLS.bind(Messages.BackupStore_can_not_create_dummy, dummy.getAbsolutePath()));
- }
- moveToBackup(dummy, buFile);
- }
- // previous checks have verified that the directory exists
- if (!file.delete()) {
- throw new IOException(NLS.bind(Messages.BackupStore_can_not_remove, dummy.getAbsolutePath()));
- }
- // will return true if the directory was already backed up at the beginning of the operation and false otherwise
- return !backedUp;
- }
-
- /**
- * Restores all backup files from backup store.
- * Note that restore of a (non directory) file deletes an existing file or directory found
- * in the restore location.
- * When the backup has been restored this BackupStore instance is closed and can not be
- * used for further backup or restore.
- *
- * If there are unrestorable items (non writable directories, or general IO exceptions) these items
- * are written to the log, and the backup copies remain in the file system and can be manually restored
- * (using a simple zip of the backup directory, and an unzip to the buRoot once the problem has been corrected).
- *
- * @throws IOException if the backup was not fully restored - unrestored items have been logged.
- * @throws ClosedBackupStoreException if the backup is already closed.
- */
- @Override
- public void restore() throws IOException {
- if (closed) {
- throw new ClosedBackupStoreException(Messages.BackupStore_restore_closed_store);
- }
- // put back all files
- // collect things that could not be restored (so final status can be reported)
- Set<File> unrestorable = new HashSet<>();
- boolean restored = true;
- if (!backupRoot.exists()) {
- logError(NLS.bind(Messages.BackupStore_missing_backup_directory, backupRoot.getAbsolutePath()));
- restored = false;
- } else {
- restoreRoots(new File(backupRoot, backupName), unrestorable);
- }
-
- logUnrestorables(unrestorable);
- if (unrestorable.size() > 0) {
- restored = false;
- }
- close(restored);
- closed = true;
- }
-
- private void logUnrestorables(Set<File> unrestorable) {
- // if there are unrestorable units log them
- //
- if (unrestorable != null && unrestorable.size() > 0) {
- for (File file : unrestorable) {
- logError(NLS.bind(Messages.BackupStore_manual_restore_needed, file.getAbsolutePath()));
- }
- }
- }
-
- /**
- * Discards and closes this BackupStore. Does nothing if this store is already
- * restored or discarded.
- */
- @Override
- public void discard() {
- if (closed) {
- return;
- }
- closeSocket();
- removeBackups();
- closed = true;
- }
-
- private void close(boolean fullyRestored) throws IOException {
- closeSocket();
- // check external tampering with backup store
- if (backupCounter != restoreCounter) {
- if (!fullyRestored) {
- logError(NLS.bind(Messages.BackupStore_0_of_1_items_restored, Long.valueOf(restoreCounter),
- Long.valueOf(backupCounter)));
- } else {
- logError(NLS.bind(Messages.BackupStore_externally_modified_0_of_1_restored,
- Long.valueOf(restoreCounter), Long.valueOf(backupCounter)));
- fullyRestored = false;
- }
- }
- if (!fullyRestored) {
- throw new IOException(Messages.BackupStore_errors_while_restoring_see_log);
- }
- // everything has been restored - the backup can now be removed
- removeBackups();
- }
-
- private void closeSocket() {
- if (socket != null && !socket.isClosed()) {
- try {
- socket.close();
- } catch (IOException e) { /* ignored */
- logWarning(
- NLS.bind(Messages.BackupStore_can_not_close_tcp_port, Integer.valueOf(socket.getLocalPort())));
- }
- }
- }
-
- private void removeBackups() {
- File buRoot = new File(backupRoot, backupName);
- if (!fullyDelete(buRoot)) {
- logWarning(NLS.bind(Messages.BackupStore_can_not_remove_bu_directory, buRoot.getAbsolutePath()));
- }
- for (String newName : renamedInPlace.values()) {
- File buFile = new File(newName);
- if (!fullyDelete(buFile)) {
- logWarning(NLS.bind(Messages.BackupStore_can_not_remove_bu_file, buRoot.getAbsolutePath()));
- }
- }
- }
-
- private static void logWarning(String message) {
- LogHelper.log(createWarning(message));
- }
-
- private static IStatus createWarning(String message) {
- return new Status(IStatus.WARNING, Activator.ID, message);
- }
-
- private static void logError(String message) {
- LogHelper.log(createError(message));
- }
-
- private static IStatus createError(String message) {
- return new Status(IStatus.ERROR, Activator.ID, message);
- }
-
- /**
- * Deletes a file, or a directory with all of it's children.
- *
- * @param file the file or directory to fully delete
- * @return true if, and only if the file is deleted without errors
- */
- private boolean fullyDelete(File file) {
- if (file.isDirectory()) {
- File[] children = file.listFiles();
- if (children != null) {
- for (File child : children) {
- // we will not stop even if some deletion failed
- fullyDelete(new File(file, child.getName()));
- }
- }
- }
- // will attempt to delete before exists check to get rid of dead links
- if (file.delete()) {
- return true;
- }
- // will return true if files does not actually exist even delete fails
- return !file.exists();
- }
-
- private void restore(File root, File buRoot, Set<File> unrestorable) {
- File[] children = buRoot.listFiles();
- if (children == null) { // error - can't read the backup directory
- unrestorable.add(buRoot);
- return;
- }
- for (File child : children) {
- File bu = new File(buRoot, child.getName());
- File target = new File(root, bu.getName());
- if (bu.isDirectory()) {
- if (!target.exists() && !target.mkdir()) {
- unrestorable.add(bu);
- continue; // give up on this branch
- }
- if (target.exists() && !target.isDirectory()) {
- // ouch, there is a file where we need a directory
- // that must be deleted.
- target.delete();
- if (!target.mkdir()) {
- unrestorable.add(bu);
- continue; // give up on branch
- }
- }
- restore(target, bu, unrestorable);
- } else {
- // do not restore the dummies (as they are used to trigger creation of
- // empty directories and are not wanted in the restored location.
- if (bu.getName().equals(dummyName)) {
- restoreCounter++; // count of the restored directory in this case.
- continue;
- }
- // if the original was overwritten by something and this file was not
- // removed, it needs to be deleted now. If it can't be deleted, the
- // renameTo will fail, and the bu is reported as not restorable.
- // fullyDelete will remove a directory completely - we are restoring a file so it can
- // not be kept.
- if (target.exists()) {
- fullyDelete(target);
- }
-
- // rename if possible, but must copy if not possible to just rename
- if (!bu.renameTo(target)) {
- // did not work to rename, probably because of volume boundaries. Try to copy instead,
- try {
- Util.copyStream(new FileInputStream(bu), true, new FileOutputStream(target), true);
- restoreCounter++; // consider it restored
- } catch (FileNotFoundException e) {
- unrestorable.add(bu);
- continue;
- } catch (IOException e) {
- unrestorable.add(bu);
- continue;
- }
- if (!bu.delete()) { // cleanup
- // could not remove the backup after copy - log, safe to remove manually
- logWarning(NLS.bind(Messages.BackupStore_can_not_delete_tmp_file, bu.getAbsolutePath()));
- }
- } else {
- restoreCounter++;
- }
- }
- }
- }
-
- /**
- * Restores everything backed up in the buRoot. Responsible for decoding the specially named root
- * target directories (i.e. _/, __/, C/, etc.) into the real system names.
- *
- * @param buRoot
- * @param unrestorable
- */
- private void restoreRoots(File buRoot, Set<File> unrestorable) {
- File[] children = buRoot.listFiles();
- if (children == null) { // error - can't read the backup directory
- unrestorable.add(buRoot);
- return;
- }
- for (File child : children) {
- // Names are root-chars, or drive letters in the root bu directory
- String name = child.getName();
- String rName = name;
- String prefix = ""; //$NON-NLS-1$
- while (rName.startsWith(ROOTCHAR)) {
- prefix += File.separator;
- rName = rName.substring(1);
- }
- if (prefix.length() < 1) {
- // The name is a drive name
- rName = rName + ":" + File.separator; //$NON-NLS-1$
- } else {
- rName = prefix + rName;
- }
- // File root = new File(rName);
- File bu = new File(buRoot, name);
- File target = new File(rName);
- if (!bu.isDirectory()) {
- // the roots should all be directories - so this can only happen if someone manually
- // stored files in the backup root - mark them as unrestorable and continue.
- unrestorable.add(bu);
- continue;
- }
- // the backup roots are system roots, and can not be created - but check root is directory and exists.
- // (Network drives could have gone away etc).
- //
- if (!(target.exists() && target.isDirectory())) {
- unrestorable.add(bu);
- continue; // give up on this branch
- }
- // then perform a recursive restore
- restore(target, bu, unrestorable);
- }
- restoreRenamedFiles(unrestorable);
- }
-
- private void restoreRenamedFiles(Set<File> unrestorable) {
- for (Entry<String, String> entry : renamedInPlace.entrySet()) {
- File bu = new File(entry.getValue());
- if (!bu.renameTo(new File(entry.getKey()))) {
- unrestorable.add(bu);
- }
- }
- }
-
- private static long msCounter = 0;
-
- /**
- * Generates a unique hex string by taking currentTimeMillis + sequence
- * number at the end allowing for 32 numbers to be generated per ms.
- * This is sufficient uniqueness in the same VM. (And is still just a fallback solution
- * if there is no access to a TCP port)
- *
- * To make number unique over multiple VMs - the PID of the process would be enough, but
- * it is complicated to get hold of - a separate program must be launched and its PPID
- * investigated. There is no standard API in Java to get the PID. Instead, a socket port is bound
- * to ensure local uniqueness.
- *
- * To make number unique across multiple hosts (we may be provisioning over NFS), the
- * 48 LS bits of the IP address is used (this is more than enough for an IPv4 address).
- * (If there is no IP address, the machine is not on a
- * network) - unfortunately the MAC address can not be used as this requires Java 6 (where
- * there also is a UUID that should be used instead of this method).
- *
- * This method needs to be modified when IPv6 addressing is the norm - at that time, the
- * restriction on Java 1.4 has hopefully been lifted, and it is possible to use the MAC address,
- * or the UUID provided since java 1.6
- *
- * @return a unique string
- */
- private String genUnique() {
- // use 5 LSB bits for counter within ms - i.e. 32 instances can be created
- // per millisecond.
- long timePart = (System.currentTimeMillis() << 5) | (msCounter++ & 31);
- // can't use the MAC address - but take IP address if provisioning across NFS
- long ipPart = 0;
- try {
- // the returned address can be 32 bits IPv4, or 128 bits IPv6 (?)
- // In any case use the LSB bits (as many as will fit
- byte[] address = InetAddress.getLocalHost().getAddress();
- for (byte element : address) {
- ipPart = ((ipPart << 8) | (element & 0xff));
- }
- } catch (UnknownHostException e) {
- // there is no IP address, and there and hence no concurrency from other machines.
- // use the default ip part 0
- }
- int port = 0;
- try {
- // TODO: this should be replaced by InetAddress.getLoopbackAddress() when 1.7 compatibility is OK
- // on a system where solely IPv6 is available this address resolution will fail:
- socket = new ServerSocket(0, 1, InetAddress.getByName("127.0.0.1")); //$NON-NLS-1$
- port = socket.getLocalPort();
- } catch (IOException e) {
- try {
- if (socket != null) {
- socket.close();
- }
- } catch (IOException e1) { // ignore failure to close -
- }
- // use a random number as port in this case
- port = new Random().nextInt() & 0xffff;
- }
- // port is never > 0xffff
- long aPart = (ipPart << 16) | (port & 0xffff);
- return Long.toHexString(timePart) + "_" + Long.toHexString(aPart); //$NON-NLS-1$
-
- }
-
- /**
- * Turns a file into a "relativized" absolute file.
- * A leading "root" is transformed to the ROOTCHAR character. On Windows, network mapped drives starts
- * with two separators - and are encoded as two ROOTCHAR.
- * e.g.
- * \\Host\C$\File becomes __\Host\C$\File
- * /users/test/file becomes _/users/test/file
- * C:/somewhere/file becomes C/somewhere/file
- *
- * @param file
- * @return a relativized absolute abstract file
- */
- private File makeRelativeFromRoot(File file) {
- File absolute = file.getAbsoluteFile();
- String path = absolute.getPath();
- String prefix = ""; //$NON-NLS-1$
- while (path.startsWith(File.separator)) {
- prefix += ROOTCHAR;
- path = path.substring(1);
- }
- if (prefix.length() > 0) {
- path = prefix + File.separator + path;
- return new File(path);
- }
- // it is a windows drive letter first.
- // Transform C:/foo to C/foo
- //
- int idx = path.indexOf(":"); //$NON-NLS-1$
- if (idx < 1) {
- throw new InternalError("File is neither absolute nor has a drive name: " + path); //$NON-NLS-1$
- }
- path = path.substring(0, idx) + path.substring(idx + 1);
-
- return new File(path);
- }
-
- /**
- * The parent path may include ".." as a directory name - this must be made canonical. But if the file itself is
- * a symbolic link, it should not be resolved.
- */
- private File makeParentCanonical(File file) throws IOException {
- return new File(file.getParentFile().getCanonicalFile(), file.getName());
- }
-}
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/LazyBackupStore.java b/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/LazyBackupStore.java
index 9ea5d5f56..f0aae9319 100644
--- a/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/LazyBackupStore.java
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/LazyBackupStore.java
@@ -21,7 +21,7 @@ import java.io.IOException;
* when needed.
*/
public class LazyBackupStore implements IBackupStore {
- private BackupStore delegate;
+ private SimpleBackupStore delegate;
private final String prefix;
/**
@@ -61,7 +61,7 @@ public class LazyBackupStore implements IBackupStore {
private void loadDelegate() {
if (delegate != null)
return;
- delegate = new BackupStore(null, prefix);
+ delegate = new SimpleBackupStore(null, prefix);
}
@Override
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/Messages.java b/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/Messages.java
index 87e0a837e..9a3bd9c03 100644
--- a/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/Messages.java
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/Messages.java
@@ -26,10 +26,10 @@ public class Messages extends NLS {
}
public static String BackupStore_0_of_1_items_restored;
- public static String BackupStore_backupCopy_closed_store;
- public static String BackupStore_can_not_close_tcp_port;
+ public static String BackupStore_externally_modified_0_of_1_restored;
+ public static String BackupStore_errors_while_restoring_see_log;
public static String BackupStore_can_not_copy_directory;
- public static String BackupStore_can_not_create_dummy;
+ public static String BackupStore_can_not_create_placeholder;
public static String BackupStore_can_not_delete_after_copy_0;
public static String BackupStore_can_not_delete_tmp_file;
public static String BackupStore_can_not_remove;
@@ -37,14 +37,12 @@ public class Messages extends NLS {
public static String BackupStore_can_not_remove_bu_file;
public static String BackupStore_directory_file_mismatch;
public static String BackupStore_directory_not_empty;
- public static String BackupStore_errors_while_restoring_see_log;
- public static String BackupStore_externally_modified_0_of_1_restored;
public static String BackupStore_file_directory_mismatch;
public static String BackupStore_file_not_found;
public static String BackupStore_manual_restore_needed;
public static String BackupStore_missing_backup_directory;
public static String BackupStore_not_a_directory;
- public static String BackupStore_restore_closed_store;
+ public static String BackupStore_closed_store;
public static String BlockMacUpdate_0;
public static String BlockMacUpdate_1;
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/SimpleBackupStore.java b/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/SimpleBackupStore.java
new file mode 100644
index 000000000..07bf4f9c5
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/SimpleBackupStore.java
@@ -0,0 +1,894 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2018 Cloudsmith Inc. and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Cloudsmith Inc. - initial API and implementation
+ * SAP AG - Ongoing development
+ *******************************************************************************/
+
+package org.eclipse.equinox.internal.p2.touchpoint.natives;
+
+import static java.nio.file.FileVisitResult.CONTINUE;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static java.util.stream.Collectors.joining;
+import static org.eclipse.equinox.internal.p2.touchpoint.natives.Util.logError;
+import static org.eclipse.equinox.internal.p2.touchpoint.natives.Util.logWarning;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.*;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Stores files by moving them to a uniquely named temporary directory.
+ *
+ * TheBackupStore remembers filenames and can recreate them in their original
+ * location.
+ *
+ * <h3>Usage</h3> The user of this class should instantiate the BackupStore with
+ * some prefix that is meaningful to a human. Uniqueness is obtained without the
+ * prefix - the prefix is used to be able to differentiate between different
+ * backup directories by a human (in case of crashes etc).
+ *
+ * If instantiated with a directory this directory will be used to store the
+ * backup root directory. If this directory is null, the users home directory is
+ * used by default.
+ *
+ * Once instantiated, use the {@link #backup(File)} and
+ * {@link #backupDirectory(File)} methods to move files to backup instead of
+ * deleting them. A file that is backed up should not be deleted - it is simply
+ * moved out of the way. Use {@link #backupCopy(File)} to move the file out of
+ * harms way, but keep a copy of it in the original location. The methods
+ * {@link #backupAll(File)} and {@link #backupCopyAll(File)} backs up an entire
+ * structure.
+ *
+ * When backup is finished - the user should either call {@link #restore()} to
+ * put all of the files back, or call {@link #discard()} to remove all of the
+ * backed up "copies".
+ *
+ * If {@link #restore()} or {@link #discard()} is not called the backup files
+ * will never be deleted.
+ *
+ * The backup store does not synchronize directories - actions that write new
+ * files are responsible for removing them. Overwriting existing files should be
+ * done by first backing up the file, and then creating a new file. Modifying a
+ * file, should be done by using {@link #backupCopy(File)} or first making a
+ * copy, then backing up the original, and then renaming the copy.
+ *
+ * <h3>Read Only and Permissions</h3> Directories that are read only (to current
+ * user) can not be backed up. Backup is performed using
+ * {@link File#renameTo(File)} and handling of permissions is operating system
+ * dependent. It is expected that a Un*x type system retains the permissions as
+ * a file is moved to the backup store and later gets restored. Backup
+ * directories are created as they are needed and will (at least on Un*x)
+ * inherit the permissions from its parent directory.
+ *
+ * If a rename can not be performed, the backup store will make a copy and
+ * delete the original file. This makes it possible to backup and restore across
+ * volume boundaries.
+ *
+ * When restoring directories they will be created with permissions in a
+ * platform specific way (on UN*IX they will inherit the permissions of the
+ * parent directory).
+ *
+ * <h3>Checkpointing</h3> Checkpointing (i.e. to be able to rollback to a
+ * particular point) can be implemented by using multiple instances of
+ * BackupStore. The client code will need to remember the individual order among
+ * the backup stores.
+ *
+ * <h3>Restartability</h3> Not implemented - it is possible to obtain the name
+ * of the backup directories, so manual restore is possible after a crash. An
+ * idea is to add persistence to a file, and be able to read it back in again.
+ *
+ * <h3>A note about exceptions</h3> In general {@link IllegalArgumentException}
+ * is thrown when attempting an operation that is considered "wrong use", and an
+ * {@link IllegalStateException} or subclass thereof is thrown on an overall
+ * wrong use of BackupStore (i.e. attempt to backup when store has been
+ * restored). Some cases of "wrong use" can not be differentiated from I/O
+ * errors (like a "file not found" as this could be caused by an entire disk
+ * disappearing - in these case an {@link IOException} is thrown.
+ *
+ * <h3>Implementation Note</h3> The backup root directory will contain folders
+ * that reflects file system roots. These are encoded using "_" for the UNI*X
+ * root directory, "__" for a Windows network mounted directory, and single
+ * "drive letter" folders corresponding to Windows drive letters. Typically, on
+ * UN*X there will only be a "_" directory in the backup root, and on windows
+ * there will typically be a single directory called "C".
+ */
+public class SimpleBackupStore implements IBackupStore {
+ public static final String BACKUP_FILE_EXTENSION = "p2bu"; //$NON-NLS-1$
+
+ public static final String DIR_PLACEHOLDER = "emptydir"; //$NON-NLS-1$
+
+ /**
+ * The name to use for a directory that represents leading separator (i.e. "/"
+ * or "\").
+ */
+ private static final String ROOTCHAR = "_"; //$NON-NLS-1$
+
+ /**
+ * Map of directory File to backup root (File) - the backup root has a directory
+ * named {@link #buStoreName} where the backup is found.
+ */
+ private final Path buStoreRoot;
+
+ private String buInPlaceSuffix;
+
+ /**
+ * Backup files that sit next to the original rather than in the backup store.
+ */
+ private List<Path> buInPlace;
+
+ /**
+ * Counter of how many files where backed up. Used as a simple check mechanism
+ * if everything was restored (a guard against manual/external tampering with
+ * the backup directories).
+ */
+ private long backupCounter;
+
+ /**
+ * Counter of how many files where restored. See {@link #backupCounter}.
+ */
+ private long restoreCounter;
+
+ /**
+ * Flag indicating if this BackupStore has been restored or canceled.
+ */
+ private boolean closed;
+
+ /**
+ * Generates a BackupStore with a default prefix of ".p2bu" for backup directory
+ * and probe file.
+ */
+ public SimpleBackupStore() {
+ this(null, "." + BACKUP_FILE_EXTENSION); //$NON-NLS-1$
+ }
+
+ /**
+ * Generates a BackupStore with a specified prefix for backup directories and
+ * probe file.
+ *
+ * @param buStoreParent Parent under which the backup store will be created. If
+ * null, java.io.tmpdir is used
+ * @param prefix Prefix used for human identification of backup stores.
+ */
+ public SimpleBackupStore(File buStoreParent, String prefix) {
+ String unique = UUID.randomUUID().toString();
+
+ String buStoreName = prefix + "_" + unique; //$NON-NLS-1$
+ this.buStoreRoot = (buStoreParent != null) ? buStoreParent.toPath().resolve(buStoreName)
+ : Paths.get(System.getProperty("java.io.tmpdir")).resolve(buStoreName); //$NON-NLS-1$
+
+ this.buInPlaceSuffix = String.format("-%s.%s", unique, BACKUP_FILE_EXTENSION); //$NON-NLS-1$
+ this.buInPlace = new ArrayList<>();
+ }
+
+ /**
+ * Returns the unique backup name (this is the name of generated backup
+ * directories).
+ *
+ * @return the backup name.
+ */
+ @Override
+ public String getBackupName() {
+ return buStoreRoot.getFileName().toString();
+ }
+
+ /**
+ * @return the parent dire under which backups are created
+ */
+ public File getBackupRoot() {
+ return buStoreRoot.toFile();
+ }
+
+ /**
+ * Backup the file by moving it to the backup store (for later (optional)
+ * restore). Calling this method with a file that represents a directory is
+ * equivalent to calling {@link #backupDirectory(File)}.
+ *
+ * A file (path) can only be backed up once per BackupStore instance. When the
+ * file is backed up, it is moved to a directory under this BackupStore
+ * instance's directory with a relative path corresponding to the original
+ * relative path from the backup root e.g. the file /A/B/C/foo.txt could be
+ * moved to /A/.p2bu_ffffff_ffffff/B/C/foo.txt when /A is the backup root.
+ *
+ * If a directory is first backed up, and later replaced by a regular file, and
+ * this file is backed up (or vice versa) - an {@link IllegalArgumentException}
+ * is thrown
+ *
+ * A backup can not be performed on a closed BackupStore.
+ *
+ * @param file - the file (or directory) to backup
+ *
+ * @return true if the file was backed up, false if this file (path) has already
+ * been backed up (the file is not moved to the store).
+ *
+ * @throws IOException - if the backup operation fails, or the
+ * file does not exist
+ * @throws ClosedBackupStoreException - if the BackupStore has been closed
+ * @throws IllegalArgumentException - on type mismatch (file vs. directory) of
+ * earlier backup, or if file does not exist
+ */
+ @Override
+ public boolean backup(File file) throws IOException {
+ assertOpen();
+
+ Path path = file.toPath();
+
+ if (Files.isDirectory(path)) {
+ return backupDirectory(path.toFile());
+ }
+
+ if (!Files.exists(path)) {
+ throw new IOException(NLS.bind(Messages.BackupStore_file_not_found, path.toAbsolutePath()));
+ }
+
+ Path buPath = toBackupPath(path);
+
+ // Already backed up, but was a directory - wrong usage
+ if (Files.isDirectory(buPath)) {
+ throw new IllegalArgumentException(
+ NLS.bind(Messages.BackupStore_directory_file_mismatch, buPath.toAbsolutePath()));
+ }
+
+ return moveToBackup(path, buPath);
+ }
+
+ /**
+ * Performs backup of an empty directory.
+ *
+ * The directory must be empty before it can be backed up (i.e. similar to a
+ * delete of a directory). The called must backup the files of the directory
+ * first. A call to backup a directory is really only needed for empty
+ * directories as a restore of a file will also restore all of its parent
+ * directories.
+ *
+ * @param file - the (empty) directory to back up
+ *
+ * @return true if the directory was moved to backup. false if the directory was
+ * already backed up.
+ *
+ * @throws IllegalArgumentException if file is not a directory, or is not empty.
+ * @throws IOException if directory can not be moved to the backup
+ * store, or if the directory is not writeable
+ */
+ @Override
+ public boolean backupDirectory(File file) throws IOException {
+ assertOpen();
+
+ Path path = file.toPath();
+
+ if (!Files.isDirectory(path)) {
+ throw new IllegalArgumentException(NLS.bind(Messages.BackupStore_not_a_directory, file.getAbsolutePath()));
+ }
+
+ if (Files.list(path).count() > 0) {
+ throw new IllegalArgumentException(
+ NLS.bind(Messages.BackupStore_directory_not_empty, file.getAbsolutePath()));
+ }
+
+ return moveDirToBackup(path);
+ }
+
+ /**
+ * Backs up a file, or everything under a directory.
+ *
+ * @param file - file to backup or directory
+ *
+ * @throws IOException if backup operation failed
+ */
+ @Override
+ public void backupAll(File file) throws IOException {
+ assertOpen();
+
+ Path path = file.toPath().normalize();
+
+ Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path f, BasicFileAttributes attrs) throws IOException {
+ backup(f.toFile());
+ return CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+ if (exc != null) {
+ throw exc;
+ }
+ moveDirToBackup(dir);
+ return CONTINUE;
+ }
+ });
+ }
+
+ /**
+ * Backup the file by leaving a copy of the contents in the original location.
+ *
+ * Calling this method with a file that represents a directory throws an
+ * {@link IllegalArgumentException}.
+ *
+ * A file (path) can only be backed up once per BackupStore instance. When the
+ * file is backed up, it is moved to a directory under this BackupStore
+ * instance's directory with a relative path corresponding to the original
+ * relative path from the backup root e.g. the file /A/B/C/foo.txt could be
+ * moved to /A/.p2bu_ffffff_ffffff/B/C/foo.txt when /A is the backup root.
+ *
+ * If a directory is first backed up, and later replaced by a regular file, and
+ * this file is backed up (or vice versa) - an {@link IllegalArgumentException}
+ * is thrown
+ *
+ * A backup can not be performed on a closed BackupStore.
+ *
+ * @param file - the file (or directory) to backup
+ *
+ * @return true if the file was backed up, false if this file (path) has already
+ * been backed up (the file is not moved to the store).
+ *
+ * @throws IOException if the backup operation fails, or the file
+ * does not exist
+ * @throws ClosedBackupStoreException if the BackupStore has been closed
+ * @throws IllegalArgumentException on type mismatch (file vs. directory) of
+ * earlier backup, or if file is a Directory
+ */
+ @Override
+ public boolean backupCopy(File file) throws IOException {
+ assertOpen();
+
+ Path path = file.toPath();
+
+ if (!Files.exists(path)) {
+ throw new IOException(NLS.bind(Messages.BackupStore_file_not_found, file.getAbsolutePath()));
+ }
+
+ if (Files.isDirectory(path)) {
+ throw new IllegalArgumentException(
+ NLS.bind(Messages.BackupStore_can_not_copy_directory, file.getAbsolutePath()));
+ }
+
+ Path buPath = toBackupPath(path);
+
+ // Already backed up, but was a directory = wrong usage
+ if (Files.isDirectory(buPath)) {
+ throw new IllegalArgumentException(
+ NLS.bind(Messages.BackupStore_directory_file_mismatch, buPath.toAbsolutePath()));
+ }
+
+ // Already backed up, can only be done once with one BackupStore
+ if (Files.exists(buPath)) {
+ return false;
+ }
+
+ Files.createDirectories(buPath.getParent());
+ Files.copy(path, buPath, REPLACE_EXISTING);
+
+ backupCounter++;
+ return true;
+ }
+
+ /**
+ * Backs up a file, or everything under a directory.
+ *
+ * A copy of the backup is left in the original place.
+ *
+ * @param file
+ *
+ * @throws IOException
+ */
+ @Override
+ public void backupCopyAll(File file) throws IOException {
+ assertOpen();
+
+ Path path = file.toPath();
+ if (!Files.exists(path)) {
+ return;
+ }
+
+ path = path.normalize();
+
+ if (Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)) {
+ backupCopy(file);
+ } else if (Files.isDirectory(path)) {
+ Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path f, BasicFileAttributes attrs) throws IOException {
+ backupCopy(f.toFile());
+ return CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+ if (exc != null) {
+ throw exc;
+ }
+ copyDirToBackup(dir);
+ return CONTINUE;
+ }
+ });
+ }
+ }
+
+ /**
+ * Restores all backup files from backup store. Note that restore of a (non
+ * directory) file deletes an existing file or directory found in the restore
+ * location. When the backup has been restored this BackupStore instance is
+ * closed and can not be used for further backup or restore.
+ *
+ * If there are unrestorable items (non writable directories, or general IO
+ * exceptions) these items are written to the log, and the backup copies remain
+ * in the file system and can be manually restored (using a simple zip of the
+ * backup directory, and an unzip to the buRoot once the problem has been
+ * corrected).
+ *
+ * @throws IOException if the backup was not fully restored -
+ * unrestored items have been logged.
+ * @throws ClosedBackupStoreException if the backup is already closed.
+ */
+ @Override
+ public void restore() throws IOException {
+ assertOpen();
+ closed = true;
+
+ // Put back all files.
+ // Collect things that could not be restored
+ Map<Path, Throwable> unrestorable = new HashMap<>();
+
+ restoreBackups(unrestorable);
+ restoreInPlaceBackups(unrestorable);
+
+ boolean restored = true;
+
+ // Checked failed attempts to restore
+ if (!unrestorable.isEmpty()) {
+ restored = false;
+
+ unrestorable.forEach((p, err) -> {
+ logError(NLS.bind(Messages.BackupStore_manual_restore_needed, err, p.toAbsolutePath()));
+ });
+ }
+
+ // Check external tampering with backup store
+ if (backupCounter != restoreCounter) {
+ restored = false;
+
+ if (!unrestorable.isEmpty()) {
+ logError(NLS.bind(Messages.BackupStore_0_of_1_items_restored, restoreCounter, backupCounter));
+ } else {
+ logError(NLS.bind(Messages.BackupStore_externally_modified_0_of_1_restored, restoreCounter,
+ backupCounter));
+ }
+ }
+
+ if (!restored) {
+ throw new IOException(Messages.BackupStore_errors_while_restoring_see_log);
+ }
+ }
+
+ /**
+ * Discards and closes this BackupStore. Does nothing if this store is already
+ * restored or discarded.
+ */
+ @Override
+ public void discard() {
+ if (closed) {
+ return;
+ }
+ closed = true;
+
+ try {
+ deleteAll(buStoreRoot);
+ } catch (IOException e) {
+ logWarning(NLS.bind(Messages.BackupStore_can_not_remove_bu_directory, buStoreRoot.toAbsolutePath()));
+ }
+
+ for (Path buFile : buInPlace) {
+ try {
+ deleteAll(buFile);
+ } catch (IOException e) {
+ logWarning(NLS.bind(Messages.BackupStore_can_not_remove_bu_file, buFile.toAbsolutePath()));
+ }
+ }
+ }
+
+ private void assertOpen() {
+ if (closed) {
+ throw new ClosedBackupStoreException(Messages.BackupStore_closed_store);
+ }
+ }
+
+ /**
+ * Makes sure a directory exists in the backup store without touching the original directory content
+ *
+ * @param path
+ *
+ * @return false if the directory is already created in the backup store, false if a placeholder had
+ * to be created and backed up.
+ *
+ * @throws IOException
+ */
+ private boolean copyDirToBackup(Path path) throws IOException {
+ Path buPath = toBackupPath(path);
+
+ if (Files.exists(buPath)) {
+ return false;
+ }
+
+ Path placeholderPath = path.resolve(DIR_PLACEHOLDER);
+ try {
+ Files.createFile(placeholderPath);
+ } catch (IOException e) {
+ throw new IOException(
+ NLS.bind(Messages.BackupStore_can_not_create_placeholder, placeholderPath.toAbsolutePath()), e);
+ }
+
+ Path buPlaceholderPath = buPath.resolve(DIR_PLACEHOLDER);
+ moveToBackup(placeholderPath, buPlaceholderPath);
+ return true;
+ }
+
+ private boolean moveDirToBackup(Path dir) throws IOException {
+ boolean copied = copyDirToBackup(dir);
+
+ try {
+ Files.delete(dir);
+ } catch (IOException e) {
+ throw new IOException(NLS.bind(Messages.BackupStore_can_not_remove, dir.toAbsolutePath()));
+ }
+
+ return copied;
+ }
+
+ /**
+ * Move/rename file to a backup file.
+ *
+ * Exposed for testing purposes.
+ *
+ * Callers of the method must have ensured that the source file exists and the
+ * backup file does not exist.
+ *
+ * @param file source file to move; should already exist and must not be
+ * directory
+ * @param buFile destination backup file to move to; should not exist and must
+ * be a directory
+ *
+ * @throws IOException if the backup operation fails
+ */
+ private boolean moveToBackup(Path path, Path buPath) throws IOException {
+ // Already backed up. Can only be done once with one BackupStore.
+ if (Files.exists(buPath)) {
+ /*
+ * Although backed up, the file can be still on the file system. For example,
+ * two IUs may be unzipping their contents to the same location and share a few
+ * common files, which have to be removed twice.
+ */
+ try {
+ Files.delete(path);
+ } catch (IOException e) {
+ throw new IOException(NLS.bind(Messages.BackupStore_can_not_remove, path.toAbsolutePath()), e);
+ }
+
+ return false;
+ }
+
+ // make sure all of the directories exist / gets created
+ Path buPathDir = buPath.getParent();
+ try {
+ Files.createDirectories(buPathDir);
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ NLS.bind(Messages.BackupStore_file_directory_mismatch, buPathDir.toAbsolutePath()), e);
+ }
+
+ try {
+ move(path, buPath);
+ } catch (IOException e) {
+ // TODO Log exception?
+ if (!isEclipseExe(path)) {
+ throw e;
+ }
+
+ Path inPlaceBuPath = toInPlaceBackupPath(path);
+ move(path, inPlaceBuPath);
+ buInPlace.add(inPlaceBuPath);
+ }
+
+ backupCounter++;
+ return true;
+ }
+
+ /**
+ * Restores everything stored in the backup root
+ *
+ * Responsible for converting the root prefix of the path from backup format
+ * back to the original real OS names. I.e. "_/" to "//", "__/" to "///", "C/"
+ * to "C:", etc.
+ *
+ * @param unrestorable accumulate unrestorable paths (including the entire
+ * backup store).
+ *
+ * @throws IOException
+ */
+ private void restoreBackups(Map<Path, Throwable> unrestorable) throws IOException {
+ if (!Files.exists(buStoreRoot)) {
+ unrestorable.put(buStoreRoot, new IOException(
+ NLS.bind(Messages.BackupStore_missing_backup_directory, buStoreRoot.toAbsolutePath())));
+ return;
+ }
+
+ Files.walkFileTree(buStoreRoot, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult preVisitDirectory(Path buDir, BasicFileAttributes attrs) {
+ try {
+ if (Files.isSameFile(buStoreRoot, buDir)) {
+ return CONTINUE;
+ }
+
+ Path dir = toSourcePath(buDir);
+
+ // There is a file where we the original directory used to be - delete it
+ if (Files.isRegularFile(dir)) {
+ Files.delete(dir);
+ }
+
+ // Make the original directory if needed
+ Files.createDirectories(dir);
+ } catch (IOException e) {
+ unrestorable.put(buDir, e);
+ }
+ return CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path buFile, BasicFileAttributes attrs) {
+ Path file = toSourcePath(buFile);
+ try {
+ // The first level children of buStoreRoot are always directories since they
+ // model file system roots
+ if (Files.isSameFile(buFile.getParent(), buStoreRoot)) {
+ unrestorable.put(buFile, new IOException("Not a directory")); //$NON-NLS-1$
+ } else {
+ /*
+ * Do not restore the place-holders as they are used to trigger creation of
+ * empty directories and are not wanted in the restored location.
+ *
+ * They are counted as restored non the less.
+ */
+ if (!DIR_PLACEHOLDER.equals(buFile.getFileName().toString())) {
+ // Clean up the site where the original used to be.
+ // It may be that a file or a directory now occupies it.
+ deleteAll(file);
+
+ // Move the backup to the original location
+ move(buFile, file);
+ } else {
+ Files.delete(buFile);
+ }
+
+ restoreCounter++;
+ }
+ } catch (IOException e) {
+ unrestorable.put(buFile, e);
+ }
+ return CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
+ unrestorable.put(file, exc);
+ throw exc;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path buDir, IOException exc) throws IOException {
+ if (exc != null) {
+ unrestorable.put(buDir, exc);
+ throw exc;
+ }
+ try {
+ Files.delete(buDir);
+ } catch (DirectoryNotEmptyException e) {
+ String children = Files.list(buDir)
+ .map(p -> p.relativize(buDir))
+ .map(Path::toString)
+ .collect(joining(",")); //$NON-NLS-1$
+ unrestorable.put(buDir, new IOException(String.format(
+ "Directory %s not empty: %s", buDir, children, e))); //$NON-NLS-1$
+ } catch (IOException e) {
+ unrestorable.put(buDir, e);
+ }
+ return CONTINUE;
+ }
+ });
+ }
+
+ private void restoreInPlaceBackups(Map<Path, Throwable> unrestorable) {
+ for (Path buPath : buInPlace) {
+ Path path = toInPlaceSourcePath(buPath);
+
+ try {
+ move(buPath, path);
+ restoreCounter++;
+ } catch (IOException e) {
+ unrestorable.put(buPath, e);
+ }
+ }
+ }
+
+ /**
+ * Converts a source path to a backup path.
+ *
+ * Exposed for testing purposes.
+ *
+ * A leading "root" is transformed to the ROOTCHAR character. On Windows,
+ * network mapped drives starts with two separators - and are encoded as two
+ * ROOTCHAR.
+ *
+ * E.g. \\Host\C$\file becomes __\Host\C$\file /users/test/file becomes
+ * _/users/test/file C:/file becomes C/file
+ *
+ * @param file a source file that needs to be backed up
+ *
+ * @return a file to which the original content can be backed up
+ *
+ * @throws IOException
+ */
+ protected Path toBackupPath(Path path) throws IOException {
+ Path pathNormal = path.normalize();
+
+ String buPath = pathNormal.toAbsolutePath().toString();
+
+ String buPrefix = ""; //$NON-NLS-1$
+ while (buPath.startsWith(File.separator)) {
+ buPrefix += ROOTCHAR;
+ buPath = buPath.substring(1);
+ }
+
+ // Linux or Windows net mount
+ if (!buPrefix.isEmpty()) {
+ buPath = Paths.get(buPrefix, buPath).toString();
+ }
+ // Windows
+ else {
+ // It is a windows drive letter first.
+ // Transform C:/foo to C/foo
+ int idx = buPath.indexOf(":"); //$NON-NLS-1$
+ if (idx < 1) {
+ throw new IllegalArgumentException("File is neither absolute nor has a drive name: " + buPath); //$NON-NLS-1$
+ }
+ buPath = buPath.substring(0, idx) + buPath.substring(idx + 1);
+ }
+
+ Path buFile = buStoreRoot.resolve(buPath);
+ return buFile;
+ }
+
+ /**
+ * Converts a backup file to the original source file.
+ *
+ * ///x/y/z -> ___x/y/z \\x\y\z c:\x\y\z -> c\x\y\z
+ *
+ * @param buPath an absolute file under {@link #buStoreRoot} to which some
+ * content is backed up.
+ *
+ * @return the original source file to which the content can be restored.
+ */
+ protected Path toSourcePath(Path buPath) {
+ Path buPathRel = buStoreRoot.relativize(buPath);
+
+ String pathName = buPathRel.toString();
+
+ String prefix = ""; //$NON-NLS-1$
+ while (pathName.startsWith(ROOTCHAR)) {
+ prefix += File.separator;
+ pathName = pathName.substring(1);
+ }
+
+ if (prefix.isEmpty()) {
+ // The first char is a windows drive name
+ pathName = pathName.charAt(0) + ":" + pathName.substring(1); //$NON-NLS-1$
+ } else {
+ pathName = prefix + pathName;
+ }
+
+ return Paths.get(pathName);
+ }
+
+ /**
+ * Converts a path to an in-place backup path.
+ *
+ * Exposed for testing purposes.
+ *
+ * @param path
+ *
+ * @return a path next to the original where the original will be moved, rather
+ * than will be moved
+ */
+ protected Path toInPlaceBackupPath(Path path) {
+ String buPathName = path.getFileName() + buInPlaceSuffix;
+ Path buPath = path.toAbsolutePath().resolveSibling(buPathName);
+ return buPath;
+ }
+
+ /**
+ * Converts a in-place backup path to the original source path.
+ *
+ * Exposed for testing purposes.
+ *
+ * @param path
+ *
+ * @return a source path
+ */
+ protected Path toInPlaceSourcePath(Path buPath) {
+ String buPathName = buPath.getFileName().toString();
+
+ int suffixIdx = buPathName.indexOf(buInPlaceSuffix);
+ if (suffixIdx <= 0) {
+ throw new IllegalArgumentException();
+ }
+
+ String pathName = buPathName.substring(0, suffixIdx);
+ Path path = buPath.resolveSibling(pathName);
+ return path;
+ }
+
+ /**
+ * A generic file operation that attempts to move a file.
+ *
+ * Exposed in a separate method for testing purposes.
+ */
+ protected void move(Path source, Path target) throws IOException {
+ Files.move(source, target, REPLACE_EXISTING);
+ }
+
+ private static boolean isEclipseExe(Path file) {
+ String name = file.getFileName().toString();
+
+ String launcher = System.getProperty("eclipse.launcher"); //$NON-NLS-1$
+ if (launcher != null) {
+ String launcherName = Paths.get(launcher).getFileName().toString();
+ if (name.equalsIgnoreCase(launcherName)) {
+ return true;
+ }
+ }
+
+ return name.equalsIgnoreCase("eclipse.exe") || name.equalsIgnoreCase("eclipsec.exe"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Deletes a file, or a directory with all of it's children.
+ *
+ * @param path
+ *
+ * @throws IOException
+ */
+ private static void deleteAll(Path path) throws IOException {
+ if (!Files.exists(path)) {
+ return;
+ }
+
+ Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ Files.delete(file);
+ return CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+ if (exc != null) {
+ throw exc;
+ }
+ Files.delete(dir);
+ return CONTINUE;
+ }
+ });
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/Util.java b/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/Util.java
index 3edabc078..afde945b3 100644
--- a/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/Util.java
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/Util.java
@@ -31,10 +31,9 @@ import org.eclipse.equinox.p2.repository.artifact.*;
import org.eclipse.osgi.util.NLS;
public class Util {
-
- public static void log(String message) {
- LogHelper.log(createError(message));
- }
+ /*
+ * Logging
+ */
public static IStatus createError(String message) {
return new Status(IStatus.ERROR, Activator.ID, message);
@@ -44,6 +43,34 @@ public class Util {
return new Status(IStatus.ERROR, Activator.ID, message, exception);
}
+ public static void logError(String message, Throwable exception) {
+ LogHelper.log(createError(message, exception));
+ }
+
+ public static void logError(String message) {
+ LogHelper.log(createError(message));
+ }
+
+ public static IStatus createWarning(String message) {
+ return new Status(IStatus.WARNING, Activator.ID, message);
+ }
+
+ public static IStatus createWarning(String message, Throwable exception) {
+ return new Status(IStatus.WARNING, Activator.ID, message, exception);
+ }
+
+ public static void logWarning(String message, Throwable exception) {
+ LogHelper.log(createWarning(message, exception));
+ }
+
+ public static void logWarning(String message) {
+ LogHelper.log(createWarning(message));
+ }
+
+ /*
+ * Locations and caches
+ */
+
public static String getInstallFolder(IProfile profile) {
return profile.getProperty(IProfile.PROP_INSTALL_FOLDER);
}
@@ -58,11 +85,13 @@ public class Util {
public static IFileArtifactRepository getDownloadCacheRepo(IProvisioningAgent agent) throws ProvisionException {
URI location = getDownloadCacheLocation(agent);
- if (location == null)
+ if (location == null) {
throw new IllegalStateException(Messages.could_not_obtain_download_cache);
+ }
IArtifactRepositoryManager manager = getArtifactRepositoryManager(agent);
- if (manager == null)
+ if (manager == null) {
throw new IllegalStateException(Messages.artifact_repo_not_found);
+ }
IArtifactRepository repository;
try {
repository = manager.loadRepository(location, null);
@@ -76,18 +105,24 @@ public class Util {
}
IFileArtifactRepository downloadCache = repository.getAdapter(IFileArtifactRepository.class);
- if (downloadCache == null)
+ if (downloadCache == null) {
throw new ProvisionException(NLS.bind(Messages.download_cache_not_writeable, location));
+ }
return downloadCache;
}
- static private URI getDownloadCacheLocation(IProvisioningAgent agent) {
+ private static URI getDownloadCacheLocation(IProvisioningAgent agent) {
IAgentLocation location = getAgentLocation(agent);
- if (location == null)
+ if (location == null) {
return null;
+ }
return URIUtil.append(location.getDataArea("org.eclipse.equinox.p2.core"), "cache/"); //$NON-NLS-1$ //$NON-NLS-2$
}
+ /*
+ * File operations
+ */
+
/**
* Unzip from a File to an output directory, with progress indication and
* backup. monitor and backup store may be null.
@@ -113,9 +148,8 @@ public class Util {
monitor);
} catch (IOException e) {
// add the file name to the message
- IOException ioException = new IOException(NLS.bind(Messages.Util_Error_Unzipping, zipFile, e.getMessage()));
- ioException.initCause(e);
- throw ioException;
+ IOException ioExc = new IOException(NLS.bind(Messages.Util_Error_Unzipping, zipFile, e.getMessage()), e);
+ throw ioExc;
}
}
@@ -150,8 +184,9 @@ public class Util {
throw new IOException(Messages.Util_Invalid_Zip_File_Format);
}
- if (path != null && path.trim().length() == 0)
+ if (path != null && path.trim().length() == 0) {
path = null;
+ }
Pattern pathRegex = path == null ? null : createAntStylePattern("(" + path + ")(*)"); //$NON-NLS-1$ //$NON-NLS-2$
Collection<Pattern> includeRegexp = new ArrayList<>();
@@ -177,8 +212,9 @@ public class Util {
boolean unzip = includeRegexp.isEmpty();
for (Pattern pattern : includeRegexp) {
unzip = pattern.matcher(name).matches();
- if (unzip)
+ if (unzip) {
break;
+ }
}
if (unzip && !excludeRegexp.isEmpty()) {
for (Pattern pattern : excludeRegexp) {
@@ -193,8 +229,9 @@ public class Util {
Matcher matcher = pathRegex.matcher(name);
if (matcher.matches()) {
name = matcher.group(2);
- if (name.startsWith("/")) //$NON-NLS-1$
+ if (name.startsWith("/")) { //$NON-NLS-1$
name = name.substring(1);
+ }
}
}
File outFile = createSubPathFile(outputDir, name);
@@ -203,10 +240,11 @@ public class Util {
outFile.mkdirs();
} else {
if (outFile.exists()) {
- if (store != null)
+ if (store != null) {
store.backup(outFile);
- else
+ } else {
outFile.delete();
+ }
} else {
outFile.getParentFile().mkdirs();
}
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/actions/UnzipAction.java b/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/actions/UnzipAction.java
index 77961095b..6dc4cf962 100644
--- a/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/actions/UnzipAction.java
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/actions/UnzipAction.java
@@ -44,19 +44,22 @@ public class UnzipAction extends ProvisioningAction {
/**
* Unzip as directed by parameters.
* Record what was zipped in the profile.
+ *
* @param parameters
* @param restoreable - if the unzip should be backed up
* @return status
*/
public IStatus unzip(Map<String, Object> parameters, boolean restoreable) {
String source = (String) parameters.get(ActionConstants.PARM_SOURCE);
- if (source == null)
+ if (source == null) {
return Util.createError(NLS.bind(Messages.param_not_set, ActionConstants.PARM_SOURCE, ACTION_UNZIP));
+ }
String originalSource = source;
String target = (String) parameters.get(ActionConstants.PARM_TARGET);
- if (target == null)
+ if (target == null) {
return Util.createError(NLS.bind(Messages.param_not_set, ActionConstants.PARM_TARGET, ACTION_UNZIP));
+ }
IInstallableUnit iu = (IInstallableUnit) parameters.get(ActionConstants.PARM_IU);
Profile profile = (Profile) parameters.get(ActionConstants.PARM_PROFILE);
@@ -87,7 +90,9 @@ public class UnzipAction extends ProvisioningAction {
unzippedFileNameBuffer.append(unzippedFile.getAbsolutePath()).append(ActionConstants.PIPE);
}
- profile.setInstallableUnitProperty(iu, "unzipped" + ActionConstants.PIPE + originalSource + ActionConstants.PIPE + target, unzippedFileNameBuffer.toString()); //$NON-NLS-1$
+ profile.setInstallableUnitProperty(iu,
+ "unzipped" + ActionConstants.PIPE + originalSource + ActionConstants.PIPE + target, //$NON-NLS-1$
+ unzippedFileNameBuffer.toString());
return Status.OK_STATUS;
}
@@ -96,18 +101,23 @@ public class UnzipAction extends ProvisioningAction {
* Unzips a source zip into the given destination. Any existing contents in the destination
* are backed up in the provided backup store.
*/
- private static File[] unzip(String source, String destination, String path, String includePattern, String excludePattern, IBackupStore store) {
+ private static File[] unzip(String source, String destination, String path, String includePattern,
+ String excludePattern, IBackupStore store) {
File zipFile = new File(source);
if (zipFile == null || !zipFile.exists()) {
- Util.log(UnzipAction.class.getName() + " the files to be unzipped is not here"); //$NON-NLS-1$
+ Util.logError(UnzipAction.class.getName() + " the files to be unzipped is not here", null); //$NON-NLS-1$
}
try {
String taskName = NLS.bind(Messages.unzipping, source);
String[] includes = includePattern == null ? null : includePattern.split("\\s+"); //$NON-NLS-1$
String[] excludes = excludePattern == null ? null : excludePattern.split("\\s+"); //$NON-NLS-1$
- return Util.unzipFile(zipFile, new File(destination), path, includes, excludes, store, taskName, new NullProgressMonitor());
+ return Util.unzipFile(zipFile, new File(destination), path, includes, excludes, store, taskName,
+ new NullProgressMonitor());
} catch (IOException e) {
- Util.log(UnzipAction.class.getName() + " error unzipping zipfile: " + zipFile.getAbsolutePath() + "destination: " + destination); //$NON-NLS-1$ //$NON-NLS-2$
+ Util.logError(
+ UnzipAction.class.getName() + " error unzipping zipfile: " + zipFile.getAbsolutePath() //$NON-NLS-1$
+ + "destination: " + destination, //$NON-NLS-1$
+ null);
}
return new File[0];
}
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/messages.properties b/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/messages.properties
index b5885bc36..a7204930b 100644
--- a/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/messages.properties
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.natives/src/org/eclipse/equinox/internal/p2/touchpoint/natives/messages.properties
@@ -14,11 +14,10 @@
# SAP SE - bug 465602
###############################################################################
-BackupStore_backupCopy_closed_store=Can not perform backup on closed backup store.
+BackupStore_closed_store=Can not perform operation on closed backup store.
BackupStore_0_of_1_items_restored={0} items out of {1} items restored.
-BackupStore_can_not_close_tcp_port=Could not close tcp socket for port: {0}
BackupStore_can_not_copy_directory=Can not copy a directory: {0}
-BackupStore_can_not_create_dummy=Can not create dummy file: {0}
+BackupStore_can_not_create_placeholder=Can not create placeholder file: {0}
BackupStore_can_not_delete_after_copy_0=File that was copied to backup could not be deleted: {0}
BackupStore_can_not_delete_tmp_file=Can not delete temporary file - it is safe to delete it manually: {0}
BackupStore_can_not_remove=Can not remove : {0}
@@ -30,10 +29,9 @@ BackupStore_errors_while_restoring_see_log=Errors while restoring - see earlier
BackupStore_externally_modified_0_of_1_restored=Backup store modified externally\! {0} items out of {1} items restored. Remaining items can not be found.
BackupStore_file_directory_mismatch=File already backed up as a file - and is now required as a directory: {0}
BackupStore_file_not_found=File does not exist: {0}.
-BackupStore_manual_restore_needed=Manual restore of backup needed for: {0}
+BackupStore_manual_restore_needed=Restore failed: {0}. Manual restore of backup needed for: {1}
BackupStore_missing_backup_directory=Missing backup directory - can not restore: {0}
BackupStore_not_a_directory=File is not a directory: {0}
-BackupStore_restore_closed_store=Can not perform restore on closed backup store
BlockMacUpdate_0=Installation impossible
BlockMacUpdate_1=The installation/update you are trying to perform can not be completed because of structural changes to Eclipse. The installation/update will stop and will leave your existing Eclipse installation intact. You need to retrieve a new version of Eclipse from http://download.eclipse.org/.

Back to the top