/* * Copyright (C) 2023 Laurent CLOUET * Author Laurent CLOUET * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; version 2 * of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.sheepit.client.config; import com.sheepit.client.logger.Log; import com.sheepit.client.datamodel.server.Chunk; import com.sheepit.client.utils.Utils; import lombok.AllArgsConstructor; import java.io.File; import java.io.IOException; import java.nio.file.FileSystemException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; @AllArgsConstructor public class DirectoryManager { private Configuration configuration; public String getActualStoragePathFor(Chunk chunk) { return isSharedEnabled() ? getSharedPathFor(chunk) : getCachePathFor(chunk); } public String getCachePathFor(Chunk chunk) { return configuration.getWoolCacheDirectory().getAbsolutePath() + File.separator + chunk.getId() + ".wool"; } public String getSharedPathFor(Chunk chunk) { return configuration.getSharedDownloadsDirectory().getAbsolutePath() + File.separator + chunk.getId() + ".wool"; } public boolean isSharedEnabled() { return configuration.getSharedDownloadsDirectory() != null && configuration.getSharedDownloadsDirectory().exists(); } public boolean copyChunkFromSharedToCache(Chunk chunk) { return copyFileFromSharedToCache(getSharedPathFor(chunk), getCachePathFor(chunk)); } private boolean copyFileFromSharedToCache(String source, String target) { Path existingArchivePath = Paths.get(source); Path targetArchivePath = Paths.get(target); if (existingArchivePath.equals(targetArchivePath)) { // target are the same, do nothing return true; } try { Files.deleteIfExists(targetArchivePath); // createLink only works if the target does not exist try { Files.createLink(targetArchivePath, existingArchivePath); Log.getInstance().debug("Created hardlink from " + targetArchivePath + " to " + existingArchivePath); } catch (UnsupportedOperationException // underlying file system does not support hard-linking | FileSystemException // cache-dir and shared-zip are on separate file systems, even though hard-linking is supported | SecurityException // user is not allowed to create hard-links ignore) { // Creating hardlinks might not be supported on some filesystems Log.getInstance().debug("Failed to create hardlink, falling back to copying file to " + targetArchivePath); Files.copy(existingArchivePath, targetArchivePath, StandardCopyOption.REPLACE_EXISTING); } } catch (IOException e) { Log.getInstance().error("Error while copying " + source + " from shared downloads directory to working dir"); return false; } return true; } /** * Creates cache directory */ public void createCacheDir() { this.removeWorkingDirectory(); this.configuration.getWorkingDirectory().mkdirs(); this.configuration.getWoolCacheDirectory().mkdirs(); if (this.configuration.getSharedDownloadsDirectory() != null) { this.configuration.getSharedDownloadsDirectory().mkdirs(); if (this.configuration.getSharedDownloadsDirectory().exists() == false) { System.err.println("DirectoryManager::createCacheDir Unable to create common directory " + this.configuration.getSharedDownloadsDirectory().getAbsolutePath()); } } } /** * Cleans working directory and also deletes it if the user hasn't specified a cache directory */ public void removeWorkingDirectory() { if (this.configuration.isUserHasSpecifiedACacheDir()) { this.cleanWorkingDirectory(); } else { Utils.delete(this.configuration.getWorkingDirectory()); } } /** * Deletes the working and storage directories */ public void cleanWorkingDirectory() { this.cleanDirectory(this.configuration.getWorkingDirectory()); this.cleanDirectory(this.configuration.getWoolCacheDirectory()); } /** * Cleans a directory and removes files in it from the md5 cache * @param dir representing the directory to be cleaned * @return false if the dir null, true otherwise */ private boolean cleanDirectory(File dir) { if (dir == null) { return false; } File[] files = dir.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory()) { Utils.delete(file); } else { try { String extension = file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase(); String name = file.getName().substring(0, file.getName().length() - 1 * extension.length()); if (".wool".equals(extension)) { // check if the md5 of the file is ok String md5_local = Utils.md5(file.getAbsolutePath()); if (md5_local.equals(name) == false) { file.delete(); } // TODO: remove old one } else { file.delete(); } } catch (StringIndexOutOfBoundsException e) { // because the file does not have an . in his path file.delete(); } } } } return true; } /** * @return a list of archives (files with extensions .zip or .wool) in the * working, storage, and shared downloads directories as long as they are not null */ public List getLocalCacheFiles() { List files_local = new LinkedList(); List files = new LinkedList(); if (this.configuration.getWorkingDirectory() != null) { File[] filesInDirectory = this.configuration.getWorkingDirectory().listFiles(); if (filesInDirectory != null) { files.addAll(Arrays.asList(filesInDirectory)); } } if (this.configuration.getWoolCacheDirectory() != null) { File[] filesInDirectory = this.configuration.getWoolCacheDirectory().listFiles(); if (filesInDirectory != null) { files.addAll(Arrays.asList(filesInDirectory)); } } if (this.configuration.getSharedDownloadsDirectory() != null) { File[] filesInDirectory = this.configuration.getSharedDownloadsDirectory().listFiles(); if (filesInDirectory != null) { files.addAll(Arrays.asList(filesInDirectory)); } } for (File file : files) { if (file.isFile()) { try { String extension = file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase(); String name = file.getName().substring(0, file.getName().length() - 1 * extension.length()); if (".wool".equals(extension)) { // check if the md5 of the file is ok String md5_local = Utils.md5(file.getAbsolutePath()); if (md5_local.equals(name)) { files_local.add(file); } } } catch (StringIndexOutOfBoundsException e) { // because the file does not have an . his path } } } return files_local; } /** * Runs through all SheepIt related directories and checks if files and folders are all readable, writeable * and in case of directories, checks if the contents can be listed and if usable space is enough. * Only logs instances where something was detected, otherwise is it will only print "FilesystemHealthCheck started" * @return an ArrayList of Strings containing all logs of the FSHealth check */ public List filesystemHealthCheck() { List logs = new ArrayList<>(); String f = "FSHealth: "; logs.add(f + "FilesystemHealthCheck started"); List dirsToCheck = new ArrayList<>(); List dirsChecked = new ArrayList<>(); dirsToCheck.add(configuration.getWorkingDirectory().getAbsoluteFile()); if (configuration.getSharedDownloadsDirectory() != null && dirsToCheck.contains(configuration.getSharedDownloadsDirectory().getAbsoluteFile()) == false) { dirsToCheck.add(configuration.getSharedDownloadsDirectory().getAbsoluteFile()); } if (configuration.getWoolCacheDirectory() != null && dirsToCheck.contains(configuration.getWoolCacheDirectory().getAbsoluteFile()) == false) { dirsToCheck.add(configuration.getWoolCacheDirectory().getAbsoluteFile()); } ListIterator dirs = dirsToCheck.listIterator(); while (dirs.hasNext()) { File dir = dirs.next(); dirs.remove(); dirsChecked.add(dir); File[] fileList = dir.listFiles(); if (fileList == null) { logs.add(f + "File list of " + dir + " is null"); } else { for (File file : fileList) { file = file.getAbsoluteFile(); //logs.add(f + file); boolean canRead = file.canRead(); boolean canWrite = file.canWrite(); boolean isDir = file.isDirectory(); if (canRead == false) { logs.add(f + "Can't read from " + file); } if (canWrite == false) { logs.add(f + "Can't write to " + file); } if (canRead && canWrite && isDir) { if (dirsChecked.contains(file)) { logs.add(f + "Dir " + file + " already checked. Loop detected"); } else { dirs.add(file); } long usableSpace = file.getUsableSpace(); if (usableSpace < 512 * 1024) { logs.add(f + "Usable space is " + usableSpace + " for " + file); } } } } } return logs; } }