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}