2023-12-19 14:35:24 +00:00
/ *
* Copyright ( C ) 2023 Laurent CLOUET
* Author Laurent CLOUET < laurent . clouet @nopnop.net >
*
* 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 .
* /
2024-12-14 13:54:56 +00:00
package com.sheepit.client.config ;
2023-12-19 14:35:24 +00:00
2024-12-14 13:54:56 +00:00
import com.sheepit.client.logger.Log ;
import com.sheepit.client.datamodel.server.Chunk ;
import com.sheepit.client.utils.Utils ;
2023-12-19 14:35:24 +00:00
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 ;
2024-11-20 22:51:06 +00:00
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.LinkedList ;
import java.util.List ;
import java.util.ListIterator ;
2023-12-19 14:35:24 +00:00
@AllArgsConstructor
public class DirectoryManager {
private Configuration configuration ;
public String getActualStoragePathFor ( Chunk chunk ) {
return isSharedEnabled ( ) ? getSharedPathFor ( chunk ) : getCachePathFor ( chunk ) ;
}
public String getCachePathFor ( Chunk chunk ) {
2024-10-31 13:30:37 +00:00
return configuration . getWoolCacheDirectory ( ) . getAbsolutePath ( ) + File . separator + chunk . getId ( ) + " .wool " ;
2023-12-19 14:35:24 +00:00
}
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 {
2024-07-13 12:11:00 +00:00
Files . deleteIfExists ( targetArchivePath ) ; // createLink only works if the target does not exist
2023-12-19 14:35:24 +00:00
try {
Files . createLink ( targetArchivePath , existingArchivePath ) ;
2024-11-20 23:13:00 +00:00
Log . getInstance ( ) . debug ( " Created hardlink from " + targetArchivePath + " to " + existingArchivePath ) ;
2023-12-19 14:35:24 +00:00
}
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
2024-11-20 23:13:00 +00:00
Log . getInstance ( ) . debug ( " Failed to create hardlink, falling back to copying file to " + targetArchivePath ) ;
2023-12-19 14:35:24 +00:00
Files . copy ( existingArchivePath , targetArchivePath , StandardCopyOption . REPLACE_EXISTING ) ;
}
}
catch ( IOException e ) {
2024-11-20 23:13:00 +00:00
Log . getInstance ( ) . error ( " Error while copying " + source + " from shared downloads directory to working dir " ) ;
2023-12-19 14:35:24 +00:00
return false ;
}
return true ;
}
2024-11-20 22:51:06 +00:00
/ * *
* 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 < File > getLocalCacheFiles ( ) {
List < File > files_local = new LinkedList < File > ( ) ;
List < File > files = new LinkedList < File > ( ) ;
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 < String > filesystemHealthCheck ( ) {
List < String > logs = new ArrayList < > ( ) ;
String f = " FSHealth: " ;
logs . add ( f + " FilesystemHealthCheck started " ) ;
List < File > dirsToCheck = new ArrayList < > ( ) ;
List < File > 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 < File > 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 ;
}
2023-12-19 14:35:24 +00:00
}