From 890c7594b2c2da2dd8eb18939696fdf8fc0ea274 Mon Sep 17 00:00:00 2001 From: HÃ¥kon Hallingstad Date: Fri, 27 Mar 2020 19:51:39 +0100 Subject: UnixPath support for atomicWrite, getFilename, getParent, setLastModifiedTime --- .../hosted/node/admin/task/util/file/UnixPath.java | 48 +++++++++++++++++----- .../node/admin/task/util/file/UnixPathTest.java | 42 +++++++++++++++++++ 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java index 268e0a5ccfd..f6eb7190db3 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java @@ -11,6 +11,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileTime; import java.nio.file.attribute.GroupPrincipal; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFileAttributes; @@ -37,22 +38,33 @@ import static com.yahoo.yolean.Exceptions.uncheck; public class UnixPath { private final Path path; - public UnixPath(Path path) { - this.path = path; - } + public UnixPath(Path path) { this.path = path; } + public UnixPath(String path) { this(Paths.get(path)); } - public UnixPath(String path) { - this(Paths.get(path)); - } + public Path toPath() { return path; } + public UnixPath resolve(String relativeOrAbsolutePath) { return new UnixPath(path.resolve(relativeOrAbsolutePath)); } + + public UnixPath getParent() { + Path parentPath = path.getParent(); + if (parentPath == null) { + throw new IllegalStateException("Path has no parent directory: '" + path + "'"); + } - public Path toPath() { - return path; + return new UnixPath(parentPath); } - public boolean exists() { - return Files.exists(path); + public String getFilename() { + Path filename = path.getFileName(); + if (filename == null) { + // E.g. "/". + throw new IllegalStateException("Path has no filename: '" + path.toString() + "'"); + } + + return filename.toString(); } + public boolean exists() { return Files.exists(path); } + public String readUtf8File() { return new String(readBytes(), StandardCharsets.UTF_8); } @@ -91,6 +103,18 @@ public class UnixPath { return this; } + public void atomicWriteUt8(String content) { + atomicWriteBytes(content.getBytes(StandardCharsets.UTF_8)); + } + + /** Write a file to the same dir as this, and then atomically move it to this' path. */ + public void atomicWriteBytes(byte[] content) { + Path tempPath = path.getParent().resolve(path.getFileName() + ".10Ia2f4N5"); + UnixPath temporaryPath = getParent().resolve(getFilename() + ".10Ia2f4N5"); + temporaryPath.writeBytes(content); + temporaryPath.atomicMove(path); + } + public String getPermissions() { return getAttributes().permissions(); } @@ -135,6 +159,10 @@ public class UnixPath { return getAttributes().lastModifiedTime(); } + public void setLastModifiedTime(Instant instant) { + uncheck(() -> Files.setLastModifiedTime(path, FileTime.from(Instant.now()))); + } + public FileAttributes getAttributes() { PosixFileAttributes attributes = uncheck(() -> Files.getFileAttributeView(path, PosixFileAttributeView.class).readAttributes()); diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPathTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPathTest.java index 3b839f7f446..3159689c22e 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPathTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPathTest.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.node.admin.task.util.file; import com.yahoo.vespa.test.file.TestFileSystem; +import org.junit.ComparisonFailure; import org.junit.Test; import java.nio.file.FileSystem; @@ -13,6 +14,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @author hakonhall @@ -124,4 +126,44 @@ public class UnixPathTest { assertFalse(dir1 + " deleted recursively", Files.exists(dir1)); } + @Test + public void atomicWrite() { + var path = new UnixPath(fs.getPath("/dir/foo")); + path.createParents(); + path.writeUtf8File("bar"); + path.atomicWriteUt8("bar v2"); + assertEquals("bar v2", path.readUtf8File()); + } + + @Test + public void testParentAndFilename() { + var absolutePath = new UnixPath("/foo/bar"); + assertEquals("/foo", absolutePath.getParent().toString()); + assertEquals("bar", absolutePath.getFilename()); + + var pathWithoutSlash = new UnixPath("foo"); + assertRuntimeException(IllegalStateException.class, "Path has no parent directory: 'foo'", () -> pathWithoutSlash.getParent()); + assertEquals("foo", pathWithoutSlash.getFilename()); + + var pathWithSlash = new UnixPath("/foo"); + assertEquals("/", pathWithSlash.getParent().toString()); + assertEquals("foo", pathWithSlash.getFilename()); + + assertRuntimeException(IllegalStateException.class, "Path has no parent directory: '/'", () -> new UnixPath("/").getParent()); + assertRuntimeException(IllegalStateException.class, "Path has no filename: '/'", () -> new UnixPath("/").getFilename()); + } + + private void assertRuntimeException(Class baseClass, String message, Runnable runnable) { + try { + runnable.run(); + fail("No exception was thrown"); + } catch (RuntimeException e) { + if (!baseClass.isInstance(e)) { + throw new ComparisonFailure("Exception class mismatch", baseClass.getName(), e.getClass().getName()); + } + + assertEquals(message, e.getMessage()); + } + } + } -- cgit v1.2.3