001package co.codewizards.cloudstore.core.oio.nio; 002 003import static co.codewizards.cloudstore.core.util.AssertUtil.*; 004 005import co.codewizards.cloudstore.core.io.ByteArrayOutputStream; 006import java.io.FileFilter; 007import java.io.FilenameFilter; 008import java.io.IOException; 009import java.net.URI; 010import java.nio.file.Files; 011import java.nio.file.LinkOption; 012import java.nio.file.NoSuchFileException; 013import java.nio.file.Path; 014import java.nio.file.Paths; 015import java.nio.file.StandardCopyOption; 016import java.nio.file.attribute.BasicFileAttributeView; 017import java.nio.file.attribute.BasicFileAttributes; 018import java.nio.file.attribute.FileTime; 019import java.text.SimpleDateFormat; 020import java.util.ArrayList; 021import java.util.Date; 022import java.util.List; 023 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026 027import co.codewizards.cloudstore.core.oio.File; 028import co.codewizards.cloudstore.core.oio.IoFile; 029import co.codewizards.cloudstore.core.util.childprocess.DumpStreamThread; 030 031/** 032 * File object with allowed imports to the java Java 1.7 NIO2 classes and packages. 033 * 034 * @author Sebastian Schefczyk 035 */ 036public class NioFile extends IoFile implements File { 037 private static final long serialVersionUID = 1L; 038 039 private static final Logger logger = LoggerFactory.getLogger(NioFile.class); 040 041 042 protected NioFile(final String pathname) { 043 super(pathname); 044 } 045 046 protected NioFile(final File parent, final String child) { 047 super(parent, child); 048 } 049 050 protected NioFile(final String parent, final String child) { 051 super(parent, child); 052 } 053 054 protected NioFile(final URI uri) { 055 super(uri); 056 } 057 058 protected NioFile(final java.io.File ioFile) { 059 super(ioFile); 060 } 061 062 063 @Override 064 public File getParentFile() { 065 final java.io.File parentFile = this.ioFile.getParentFile(); 066 return parentFile != null ? new NioFile(parentFile) : null; 067 } 068 069 @Override 070 public File[] listFiles() { 071 final java.io.File[] ioFilesListFiles = this.ioFile.listFiles(); 072 return NioFileUtil.convert(ioFilesListFiles); 073 } 074 075 @Override 076 public File[] listFiles(final FileFilter fileFilter) { 077 final java.io.File[] ioFilesListFiles = this.ioFile.listFiles(fileFilter); 078 return NioFileUtil.convert(ioFilesListFiles); 079 } 080 081 @Override 082 public File[] listFiles(final FilenameFilter fileFilter) { 083 final java.io.File[] ioFilesListFiles = this.ioFile.listFiles(fileFilter); 084 return NioFileUtil.convert(ioFilesListFiles); 085 } 086 087 @Override 088 public File getAbsoluteFile() { 089 return new NioFile(ioFile.getAbsoluteFile()); 090 } 091 092 @Override 093 public boolean existsNoFollow() { 094 return Files.exists(ioFile.toPath(), LinkOption.NOFOLLOW_LINKS); 095 } 096 097 @Override 098 public int compareTo(final File otherFile) { 099 return ioFile.compareTo(otherFile.getIoFile()); 100 } 101 102 @Override 103 public File getCanonicalFile() throws IOException { 104 return new NioFile(ioFile.getCanonicalFile()); 105 } 106 107 @Override 108 public boolean isRegularFileNoFollowLinks() { 109 return Files.isRegularFile(ioFile.toPath(), LinkOption.NOFOLLOW_LINKS); 110 } 111 112 @Override 113 public boolean isRegularFileFollowLinks() { 114 return Files.isRegularFile(ioFile.toPath()); 115 } 116 117 @Override 118 public boolean isDirectoryNoFollowSymLinks() { 119 return Files.isDirectory(ioFile.toPath(), LinkOption.NOFOLLOW_LINKS); 120 } 121 122 @Override 123 public boolean isDirectoryFollowSymLinks() { 124 return Files.isDirectory(ioFile.toPath()); 125 } 126 127 @Override 128 public boolean isSymbolicLink() { 129 return Files.isSymbolicLink(ioFile.toPath()); 130 } 131 132 @Override 133 public String readSymbolicLinkToPathString() throws IOException { 134 final Path symlinkPath = ioFile.toPath(); 135 final Path currentTargetPath = Files.readSymbolicLink(symlinkPath); 136 final String currentTarget = toPathString(currentTargetPath); 137 return currentTarget; 138 } 139 140 @Override 141 public long lastModified() { 142 try { 143 final BasicFileAttributes attributes = Files.readAttributes( 144 ioFile.toPath(), BasicFileAttributes.class); 145 return attributes.lastModifiedTime().toMillis(); 146 } catch (final NoSuchFileException x) { 147 return 0; // be compatible with old, classic java.io.File. 148 } catch (final IOException e) { 149 throw new RuntimeException(e); 150 } 151 } 152 153 @Override 154 public long getLastModifiedNoFollow() { 155 try { 156 final BasicFileAttributes attributes = Files.readAttributes( 157 ioFile.toPath(), BasicFileAttributes.class, 158 LinkOption.NOFOLLOW_LINKS); 159 return attributes.lastModifiedTime().toMillis(); 160 } catch (final IOException e) { 161 throw new RuntimeException(e); 162 } 163 } 164 165 private static String toPathString(final Path path) { 166 assertNotNull(path, "path"); 167 return path.toString().replace(java.io.File.separatorChar, '/'); 168 } 169 170 @Override 171 public boolean renameTo(final File dest) { 172 return ioFile.renameTo(dest.getIoFile()); 173 } 174 175 @Override 176 public void createSymbolicLink(final String targetPath) throws IOException { 177 Files.createSymbolicLink(ioFile.toPath(), Paths.get(targetPath)).toString(); 178 } 179 180 @Override 181 public void move(final File toFile) throws IOException { 182 Files.move(ioFile.toPath(), toFile.getIoFile().toPath()); 183 } 184 185 @Override 186 public void copyToCopyAttributes(final File toFile) throws IOException { 187 Files.copy(ioFile.toPath(), toFile.getIoFile().toPath(), StandardCopyOption.COPY_ATTRIBUTES); 188 } 189 190 @Override 191 public boolean setLastModified(long lastModified) { 192 final FileTime lastModifiedTime = FileTime.fromMillis(lastModified); 193 try { 194 Files.getFileAttributeView(ioFile.toPath(), BasicFileAttributeView.class).setTimes(lastModifiedTime, null, null); 195 } catch (final IOException e) { 196 logger.error("Setting the lastModified timestamp of '"+ ioFile +"' failed with the following error: " + e, e); 197 return false; 198 } 199 return true; 200 } 201 202 @Override 203 public void setLastModifiedNoFollow(final long lastModified) { 204 final Path path = ioFile.toPath().toAbsolutePath(); 205 final List<Throwable> errors = new ArrayList<>(); 206 207 final FileTime lastModifiedTime = FileTime.fromMillis(lastModified); 208 try { 209 Files.getFileAttributeView(path, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS) 210 .setTimes(lastModifiedTime, null, null); 211 212 return; 213 } catch (final IOException e) { 214 errors.add(e); 215 } 216 217 // It's currently impossible to modify the 'lastModified' timestamp of a symlink :-( 218 // http://stackoverflow.com/questions/17308363/symlink-lastmodifiedtime-in-java-1-7 219 // Therefore, we fall back to the touch command, if the above code failed. 220 221 final String timestamp = new SimpleDateFormat("YYYYMMddHHmm.ss").format(new Date(lastModified)); 222 final ProcessBuilder processBuilder = new ProcessBuilder("touch", "-c", "-h", "-m", "-t", timestamp, path.toString()); 223 processBuilder.redirectErrorStream(true); 224 try { 225 final Process process = processBuilder.start(); 226 final ByteArrayOutputStream stdOut = new ByteArrayOutputStream(); 227 final int processExitCode; 228 final DumpStreamThread dumpInputStreamThread = new DumpStreamThread(process.getInputStream(), stdOut, logger); 229 try { 230 dumpInputStreamThread.start(); 231 processExitCode = process.waitFor(); 232 } finally { 233 dumpInputStreamThread.flushBuffer(); 234 dumpInputStreamThread.interrupt(); 235 } 236 237 if (processExitCode != 0) { 238 final String stdOutString = new String(stdOut.toByteArray()); 239 throw new IOException(String.format( 240 "Command 'touch' failed with exitCode=%s and the following message: %s", 241 processExitCode, stdOutString)); 242 } 243 244 return; 245 } catch (IOException | InterruptedException e) { 246 errors.add(e); 247 } 248 249 if (!errors.isEmpty()) { 250 logger.error("Setting the lastModified timestamp of '{}' failed with the following errors:", path); 251 for (final Throwable error : errors) { 252 logger.error("" + error, error); 253 } 254 } 255 } 256 257 @Override 258 public String relativize(final File target) { 259 return ioFile.getAbsoluteFile().toPath().relativize(target.getIoFile().getAbsoluteFile().toPath()).toString(); 260 } 261 262}