package mirror; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.AccessDeniedException; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Charsets; import com.google.protobuf.ByteString; public class NativeFileAccess implements FileAccess { private static final Logger log = LoggerFactory.getLogger(NativeFileAccess.class); public static void main(String[] args) throws Exception { Path root = Paths.get("/home/stephen/dir1"); NativeFileAccess f = new NativeFileAccess(root); Path bar = Paths.get("bar.txt"); ByteString b = f.read(bar); String s = Charsets.US_ASCII.newDecoder().decode(ByteBuffer.wrap(b.toByteArray())).toString(); System.out.println(s); f.write(bar, ByteBuffer.wrap((s + "2").getBytes())); f.setModifiedTime(bar, System.currentTimeMillis()); System.out.println(root.resolve(bar).toFile().lastModified()); } private final Path rootDirectory; public NativeFileAccess(Path rootDirectory) { this.rootDirectory = rootDirectory; } @Override public void write(Path relative, ByteBuffer data) throws IOException { Path path = rootDirectory.resolve(relative); mkdir(path.getParent().toAbsolutePath()); try { doWrite(data, path); } catch (AccessDeniedException ade) { // sometimes code generators mark files as read-only; for now just assume // our "newer always wins" logic is correct, and try to write it anyway NativeFileAccessUtils.setWritable(path); doWrite(data, path); } } @Override public ByteString read(Path relative) throws IOException { try (FileInputStream fis = new FileInputStream(resolve(relative).toFile())) { return ByteString.readFrom(fis); } } @Override public long getModifiedTime(Path relative) throws IOException { return Files.getLastModifiedTime(resolve(relative), LinkOption.NOFOLLOW_LINKS).toMillis(); } @Override public void setModifiedTime(Path relative, long millis) throws IOException { NativeFileAccessUtils.setModifiedTimeForSymlink(resolve(relative).toAbsolutePath(), millis); } @Override public void delete(Path relative) throws IOException { File file = resolve(relative).toFile(); if (file.isDirectory()) { FileUtils.deleteDirectory(file); } else { file.delete(); } } @Override public void createSymlink(Path relative, Path target) throws IOException { Path path = resolve(relative); path.getParent().toFile().mkdirs(); if (Files.exists(path, LinkOption.NOFOLLOW_LINKS)) { Files.delete(path); } Files.createSymbolicLink(path, target); } @Override public boolean isSymlink(Path relativePath) { return Files.isSymbolicLink(resolve(relativePath)); } @Override public Path readSymlink(Path relativePath) throws IOException { // symlink semantics is that the path is relative to the location of the link // path (relativePath), so we don't want to return it relative to the rootDirectory Path path = resolve(relativePath); Path parent = path.getParent(); Path symlink = Files.readSymbolicLink(path); if (symlink.isAbsolute()) { Path p = parent.toAbsolutePath().relativize(symlink); log.debug("Read absolute symlink {} as {}, returning {}", relativePath, symlink, p); return p; } else { Path target = parent.resolve(symlink); Path p = parent.relativize(target); log.debug("Read relative symlink {} as {}, returning {}", relativePath, symlink, p); return p; } } @Override public boolean exists(Path relativePath) { return resolve(relativePath).toFile().exists(); } private Path resolve(Path relativePath) { return rootDirectory.resolve(relativePath); } @Override public long getFileSize(Path relativePath) throws IOException { return resolve(relativePath).toFile().length(); } @Override public void mkdir(Path relativePath) throws IOException { mkdirImpl(resolve(relativePath).toAbsolutePath()); } @Override public boolean isDirectory(Path relativePath) { return resolve(relativePath).toFile().isDirectory(); } private static void doWrite(ByteBuffer data, Path path) throws IOException { FileChannel c = FileChannel.open(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); try { c.write(data); } finally { c.close(); } } /** @param path the absolute path of the directory to create */ private static void mkdirImpl(Path path) throws IOException { path.toFile().mkdirs(); if (!path.toFile().exists()) { // it could be that relative has a parent that used to be a symlink, but now is not anymore... boolean foundOldSymlink = false; Path current = path; while (current != null) { if (Files.isSymbolicLink(current)) { current.toFile().delete(); path.toFile().mkdirs(); foundOldSymlink = true; } current = current.getParent(); } if (!foundOldSymlink) { throw new IOException("Could not create directory " + path + " (" + path.toFile() + " does not exist)"); } } } @Override public boolean isExecutable(Path relativePath) throws IOException { return NativeFileAccessUtils.isExecutable(resolve(relativePath)); } @Override public void setExecutable(Path relativePath) throws IOException { NativeFileAccessUtils.setExecutable(resolve(relativePath)); } }