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 if (! exists()) // https://github.com/cloudstore/cloudstore/issues/68 143 return 0; 144 145 try { 146 final BasicFileAttributes attributes = Files.readAttributes( 147 ioFile.toPath(), BasicFileAttributes.class); 148 return attributes.lastModifiedTime().toMillis(); 149 } catch (final IOException e) { 150 // The file might have been deleted between the check above and the attempt to read the 151 // attributes => check again and just log + exit, if it does not exist (anymore). 152 if (! exists()) { 153 logger.warn("lastModified: Seems, the file '{}' was deleted while we accessed it: {}", getAbsolutePath(), e); 154 return 0; 155 } 156 throw new RuntimeException(e); 157 } 158 } 159 160 @Override 161 public long getLastModifiedNoFollow() { 162 if (! existsNoFollow()) // for symmetry reasons with lastModified() -- see also https://github.com/cloudstore/cloudstore/issues/68 163 return 0; 164 165 try { 166 final BasicFileAttributes attributes = Files.readAttributes( 167 ioFile.toPath(), BasicFileAttributes.class, 168 LinkOption.NOFOLLOW_LINKS); 169 return attributes.lastModifiedTime().toMillis(); 170 } catch (final IOException e) { 171 // The file might have been deleted between the check above and the attempt to read the 172 // attributes => check again and just log + exit, if it does not exist (anymore). 173 if (! existsNoFollow()) { 174 logger.warn("getLastModifiedNoFollow: Seems, the file '{}' was deleted while we accessed it: {}", getAbsolutePath(), e); 175 return 0; 176 } 177 throw new RuntimeException(e); 178 } 179 } 180 181 private static String toPathString(final Path path) { 182 assertNotNull(path, "path"); 183 return path.toString().replace(java.io.File.separatorChar, '/'); 184 } 185 186 @Override 187 public boolean renameTo(final File dest) { 188 return ioFile.renameTo(dest.getIoFile()); 189 } 190 191 @Override 192 public void createSymbolicLink(final String targetPath) throws IOException { 193 Files.createSymbolicLink(ioFile.toPath(), Paths.get(targetPath)).toString(); 194 } 195 196 @Override 197 public void move(final File toFile) throws IOException { 198 Files.move(ioFile.toPath(), toFile.getIoFile().toPath()); 199 } 200 201 @Override 202 public void copyToCopyAttributes(final File toFile) throws IOException { 203 Files.copy(ioFile.toPath(), toFile.getIoFile().toPath(), StandardCopyOption.COPY_ATTRIBUTES); 204 } 205 206 @Override 207 public boolean setLastModified(long lastModified) { 208 final FileTime lastModifiedTime = FileTime.fromMillis(lastModified); 209 try { 210 Files.getFileAttributeView(ioFile.toPath(), BasicFileAttributeView.class).setTimes(lastModifiedTime, null, null); 211 } catch (final Exception e) { 212 logger.error("Setting the lastModified timestamp of '"+ ioFile +"' failed with the following error: " + e, e); 213 return false; 214 } 215 return true; 216 } 217 218 @Override 219 public boolean setLastModifiedNoFollow(final long lastModified) { 220 final Path path = ioFile.toPath().toAbsolutePath(); 221 final List<Throwable> errors = new ArrayList<>(); 222 223 final FileTime lastModifiedTime = FileTime.fromMillis(lastModified); 224 try { 225 Files.getFileAttributeView(path, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS) 226 .setTimes(lastModifiedTime, null, null); 227 228 return true; 229 } catch (final IOException e) { 230 errors.add(e); 231 } 232 233 // It's currently impossible to modify the 'lastModified' timestamp of a symlink :-( 234 // http://stackoverflow.com/questions/17308363/symlink-lastmodifiedtime-in-java-1-7 235 // Therefore, we fall back to the touch command, if the above code failed. 236 237 final String timestamp = new SimpleDateFormat("YYYYMMddHHmm.ss").format(new Date(lastModified)); 238 final ProcessBuilder processBuilder = new ProcessBuilder("touch", "-c", "-h", "-m", "-t", timestamp, path.toString()); 239 processBuilder.redirectErrorStream(true); 240 try { 241 final Process process = processBuilder.start(); 242 final ByteArrayOutputStream stdOut = new ByteArrayOutputStream(); 243 final int processExitCode; 244 final DumpStreamThread dumpInputStreamThread = new DumpStreamThread(process.getInputStream(), stdOut, logger); 245 try { 246 dumpInputStreamThread.start(); 247 processExitCode = process.waitFor(); 248 } finally { 249 dumpInputStreamThread.flushBuffer(); 250 dumpInputStreamThread.interrupt(); 251 } 252 253 if (processExitCode != 0) { 254 final String stdOutString = new String(stdOut.toByteArray()); 255 throw new IOException(String.format( 256 "Command 'touch' failed with exitCode=%s and the following message: %s", 257 processExitCode, stdOutString)); 258 } 259 260 return true; 261 } catch (IOException | InterruptedException e) { 262 errors.add(e); 263 } 264 265 if (!errors.isEmpty()) { 266 logger.error("Setting the lastModified timestamp of '{}' failed with the following errors:", path); 267 for (final Throwable error : errors) { 268 logger.error("" + error, error); 269 } 270 } 271 return false; 272 } 273 274 @Override 275 public String relativize(final File target) { 276 return ioFile.getAbsoluteFile().toPath().relativize(target.getIoFile().getAbsoluteFile().toPath()).toString(); 277 } 278 279}