package jdrivesync.fs; import jdrivesync.cli.Options; import jdrivesync.exception.JDriveSyncException; import jdrivesync.logging.LoggerFactory; import jdrivesync.model.SyncDirectory; import jdrivesync.model.SyncItem; import jdrivesync.report.ReportEntry; import jdrivesync.report.ReportFactory; import jdrivesync.sync.Synchronization; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import static jdrivesync.util.FileUtil.toRelativePath; public class FileSystemAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(); public static final String TRASH = ".trash"; private final Options options; public FileSystemAdapter(Options options) { this.options = options; } public File[] listFiles(File directory) { File[] files = directory.listFiles(); if (files == null) { files = new File[0]; } if (LOGGER.isLoggable(Level.FINE)) { if (files.length > 0) { for (File file : files) { LOGGER.log(Level.FINE, "Directory '" + directory.getAbsolutePath() + "' contains file '" + file.getAbsolutePath() + "'."); } } else { LOGGER.log(Level.FINE, "Directory '" + directory.getAbsolutePath() + "' does not contain any files."); } } Arrays.sort(files, (f1, f2) -> f1.getName().compareTo(f2.getName())); return files; } public boolean isDirectory(File directory) { return directory.isDirectory(); } public boolean exists(File file) { return file.exists(); } public boolean canRead(File file) { return file.canRead(); } public void deleteDirectorySubtree(Path path) throws IOException { if (Files.exists(path)) { LOGGER.log(Level.FINE, "Deleting subtree '" + path + "'."); if (!options.isDryRun()) { Files.walkFileTree(path, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { LOGGER.log(Level.FINE, "Deleting file '" + file + "'."); delete(file.toFile()); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { LOGGER.log(Level.FINE, "Deleting directory '" + dir + "'."); delete(dir.toFile()); return FileVisitResult.CONTINUE; } }); } } } public boolean delete(File file) { boolean deleted = false; if (TRASH.equals(file.getName()) && file.getParentFile() != null && file.getParentFile().equals(options.getLocalRootDir().get())) { LOGGER.log(Level.FINE, "Not deleting file '" + file.getAbsolutePath() + "' because it is our trash bin."); } else { if (options.isNoDelete()) { LOGGER.log(Level.FINE, "Not deleting file '" + file.getAbsolutePath() + "' because option --no-delete is set."); deleted = true; } else { if (options.isDeleteFiles()) { LOGGER.log(Level.FINE, "Deleting file '" + file.getAbsolutePath() + "'."); if (!options.isDryRun()) { deleted = file.delete(); } } else { try { if (!options.isDryRun()) { Path trashDir = createTrashDir(); Path relativePath = options.getLocalRootDir().get().toPath().relativize(file.toPath()); Path target = Paths.get(trashDir.toString(), relativePath.toString()); Path targetParent = target.getParent(); if (targetParent != null) { Files.createDirectories(targetParent); } LOGGER.log(Level.FINE, "Moving file '" + file.getAbsolutePath() + "' to trash bin ('" + target + "')."); Files.move(file.toPath(), target); deleted = true; } } catch (Exception e) { LOGGER.log(Level.WARNING, "Could not move file to .trash directory: " + e.getMessage(), e); } } } } return deleted; } private Path createTrashDir() throws IOException { File localRootDir = options.getLocalRootDir().get(); Path trashDirPath = Paths.get(localRootDir.getAbsolutePath(), TRASH); if (!Files.exists(trashDirPath)) { trashDirPath = Files.createDirectory(trashDirPath); } return trashDirPath; } public BasicFileAttributes readAttributes(File file) throws IOException { return Files.readAttributes(file.toPath(), BasicFileAttributes.class); } public Path createDirectory(File file) throws IOException { LOGGER.log(Level.FINE, "Creating directory '" + file.getAbsolutePath() + "'."); if (!options.isDryRun()) { return Files.createDirectory(file.toPath()); } return file.toPath(); } public void setLastModifiedTime(File file, long millis) throws IOException { Date date = new Date(millis); String dateFormat = Synchronization.DATE_FORMAT.format(date); LOGGER.log(Level.FINE, "Setting last modified time on file '" + file.getAbsolutePath() + "' to " + dateFormat + "."); if (!options.isDryRun()) { Files.setLastModifiedTime(file.toPath(), FileTime.fromMillis(millis)); } } public void storeFile(InputStream inputStream, SyncItem syncItem) { try { Optional<com.google.api.services.drive.model.File> remoteFileOptional = syncItem.getRemoteFile(); if (remoteFileOptional.isPresent()) { com.google.api.services.drive.model.File remoteFile = remoteFileOptional.get(); if (syncItem.getParent().isPresent()) { SyncDirectory parentSyncDir = syncItem.getParent().get(); if (parentSyncDir.getLocalFile().isPresent()) { java.io.File parentFile = parentSyncDir.getLocalFile().get(); java.io.File newFile = new java.io.File(parentFile, remoteFileOptional.get().getTitle()); copyStreamToFile(inputStream, newFile); syncItem.setLocalFile(Optional.of(newFile)); setLastModifiedTime(newFile, remoteFile.getModifiedDate().getValue()); } else { LOGGER.log(Level.WARNING, "Cannot create file '" + syncItem.getPath() + "' because local file is not present."); } } else { java.io.File parentFile = options.getLocalRootDir().get(); java.io.File newFile = new java.io.File(parentFile, remoteFileOptional.get().getTitle()); copyStreamToFile(inputStream, newFile); syncItem.setLocalFile(Optional.of(newFile)); setLastModifiedTime(newFile, remoteFile.getModifiedDate().getValue()); } } else { LOGGER.log(Level.WARNING, "Cannot create file '" + syncItem.getPath() + "' because remote file is not present."); } } catch (IOException e) { throw new JDriveSyncException(JDriveSyncException.Reason.IOException, "Failed to store local file: " + e.getMessage(), e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { } } } } private void copyStreamToFile(InputStream inputStream, java.io.File file) throws IOException { if (!options.isDryRun()) { byte[] buffer = new byte[1024]; try (FileOutputStream fos = new FileOutputStream(file)) { int read = inputStream.read(buffer, 0, buffer.length); while (read >= 0) { fos.write(buffer, 0, read); read = inputStream.read(buffer, 0, buffer.length); } } } } }