package co.codewizards.cloudstore.core.oio.nio;
import static co.codewizards.cloudstore.core.util.AssertUtil.*;
import co.codewizards.cloudstore.core.io.ByteArrayOutputStream;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import co.codewizards.cloudstore.core.oio.File;
import co.codewizards.cloudstore.core.oio.IoFile;
import co.codewizards.cloudstore.core.util.childprocess.DumpStreamThread;
/**
* File object with allowed imports to the java Java 1.7 NIO2 classes and packages.
*
* @author Sebastian Schefczyk
*/
public class NioFile extends IoFile implements File {
private static final long serialVersionUID = 1L;
private static final Logger logger = LoggerFactory.getLogger(NioFile.class);
protected NioFile(final String pathname) {
super(pathname);
}
protected NioFile(final File parent, final String child) {
super(parent, child);
}
protected NioFile(final String parent, final String child) {
super(parent, child);
}
protected NioFile(final URI uri) {
super(uri);
}
protected NioFile(final java.io.File ioFile) {
super(ioFile);
}
@Override
public File getParentFile() {
final java.io.File parentFile = this.ioFile.getParentFile();
return parentFile != null ? new NioFile(parentFile) : null;
}
@Override
public File[] listFiles() {
final java.io.File[] ioFilesListFiles = this.ioFile.listFiles();
return NioFileUtil.convert(ioFilesListFiles);
}
@Override
public File[] listFiles(final FileFilter fileFilter) {
final java.io.File[] ioFilesListFiles = this.ioFile.listFiles(fileFilter);
return NioFileUtil.convert(ioFilesListFiles);
}
@Override
public File[] listFiles(final FilenameFilter fileFilter) {
final java.io.File[] ioFilesListFiles = this.ioFile.listFiles(fileFilter);
return NioFileUtil.convert(ioFilesListFiles);
}
@Override
public File getAbsoluteFile() {
return new NioFile(ioFile.getAbsoluteFile());
}
@Override
public boolean existsNoFollow() {
return Files.exists(ioFile.toPath(), LinkOption.NOFOLLOW_LINKS);
}
@Override
public int compareTo(final File otherFile) {
return ioFile.compareTo(otherFile.getIoFile());
}
@Override
public File getCanonicalFile() throws IOException {
return new NioFile(ioFile.getCanonicalFile());
}
@Override
public boolean isRegularFileNoFollowLinks() {
return Files.isRegularFile(ioFile.toPath(), LinkOption.NOFOLLOW_LINKS);
}
@Override
public boolean isRegularFileFollowLinks() {
return Files.isRegularFile(ioFile.toPath());
}
@Override
public boolean isDirectoryNoFollowSymLinks() {
return Files.isDirectory(ioFile.toPath(), LinkOption.NOFOLLOW_LINKS);
}
@Override
public boolean isDirectoryFollowSymLinks() {
return Files.isDirectory(ioFile.toPath());
}
@Override
public boolean isSymbolicLink() {
return Files.isSymbolicLink(ioFile.toPath());
}
@Override
public String readSymbolicLinkToPathString() throws IOException {
final Path symlinkPath = ioFile.toPath();
final Path currentTargetPath = Files.readSymbolicLink(symlinkPath);
final String currentTarget = toPathString(currentTargetPath);
return currentTarget;
}
@Override
public long lastModified() {
try {
final BasicFileAttributes attributes = Files.readAttributes(
ioFile.toPath(), BasicFileAttributes.class);
return attributes.lastModifiedTime().toMillis();
} catch (final NoSuchFileException x) {
return 0; // be compatible with old, classic java.io.File.
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
@Override
public long getLastModifiedNoFollow() {
try {
final BasicFileAttributes attributes = Files.readAttributes(
ioFile.toPath(), BasicFileAttributes.class,
LinkOption.NOFOLLOW_LINKS);
return attributes.lastModifiedTime().toMillis();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
private static String toPathString(final Path path) {
assertNotNull(path, "path");
return path.toString().replace(java.io.File.separatorChar, '/');
}
@Override
public boolean renameTo(final File dest) {
return ioFile.renameTo(dest.getIoFile());
}
@Override
public void createSymbolicLink(final String targetPath) throws IOException {
Files.createSymbolicLink(ioFile.toPath(), Paths.get(targetPath)).toString();
}
@Override
public void move(final File toFile) throws IOException {
Files.move(ioFile.toPath(), toFile.getIoFile().toPath());
}
@Override
public void copyToCopyAttributes(final File toFile) throws IOException {
Files.copy(ioFile.toPath(), toFile.getIoFile().toPath(), StandardCopyOption.COPY_ATTRIBUTES);
}
@Override
public boolean setLastModified(long lastModified) {
final FileTime lastModifiedTime = FileTime.fromMillis(lastModified);
try {
Files.getFileAttributeView(ioFile.toPath(), BasicFileAttributeView.class).setTimes(lastModifiedTime, null, null);
} catch (final IOException e) {
logger.error("Setting the lastModified timestamp of '"+ ioFile +"' failed with the following error: " + e, e);
return false;
}
return true;
}
@Override
public void setLastModifiedNoFollow(final long lastModified) {
final Path path = ioFile.toPath().toAbsolutePath();
final List<Throwable> errors = new ArrayList<>();
final FileTime lastModifiedTime = FileTime.fromMillis(lastModified);
try {
Files.getFileAttributeView(path, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS)
.setTimes(lastModifiedTime, null, null);
return;
} catch (final IOException e) {
errors.add(e);
}
// It's currently impossible to modify the 'lastModified' timestamp of a symlink :-(
// http://stackoverflow.com/questions/17308363/symlink-lastmodifiedtime-in-java-1-7
// Therefore, we fall back to the touch command, if the above code failed.
final String timestamp = new SimpleDateFormat("YYYYMMddHHmm.ss").format(new Date(lastModified));
final ProcessBuilder processBuilder = new ProcessBuilder("touch", "-c", "-h", "-m", "-t", timestamp, path.toString());
processBuilder.redirectErrorStream(true);
try {
final Process process = processBuilder.start();
final ByteArrayOutputStream stdOut = new ByteArrayOutputStream();
final int processExitCode;
final DumpStreamThread dumpInputStreamThread = new DumpStreamThread(process.getInputStream(), stdOut, logger);
try {
dumpInputStreamThread.start();
processExitCode = process.waitFor();
} finally {
dumpInputStreamThread.flushBuffer();
dumpInputStreamThread.interrupt();
}
if (processExitCode != 0) {
final String stdOutString = new String(stdOut.toByteArray());
throw new IOException(String.format(
"Command 'touch' failed with exitCode=%s and the following message: %s",
processExitCode, stdOutString));
}
return;
} catch (IOException | InterruptedException e) {
errors.add(e);
}
if (!errors.isEmpty()) {
logger.error("Setting the lastModified timestamp of '{}' failed with the following errors:", path);
for (final Throwable error : errors) {
logger.error("" + error, error);
}
}
}
@Override
public String relativize(final File target) {
return ioFile.getAbsoluteFile().toPath().relativize(target.getIoFile().getAbsoluteFile().toPath()).toString();
}
}