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