Skip to content

Commit

Permalink
izfiles cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
pshirshov committed Aug 30, 2023
1 parent 21eddab commit a697c37
Show file tree
Hide file tree
Showing 11 changed files with 227 additions and 149 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package izumi.fundamentals.platform.files

import izumi.fundamentals.platform.os.{IzOs, OsType}

import java.nio.file.{Path, Paths}

trait ExecutableSearch {
def haveExecutables(names: String*): Boolean = {
names.forall(which(_).nonEmpty)
}

def which(name: String, morePaths: Seq[String] = Seq.empty): Option[Path] = {
find(binaryNameCandidates(name), IzOs.path ++ morePaths)
}

def whichAll(name: String, morePaths: Seq[String] = Seq.empty): Iterable[Path] = {
findAll(binaryNameCandidates(name), IzOs.path ++ morePaths)
}

private def find(candidates: Seq[String], paths: Seq[String]): Option[Path] = {
paths.view
.flatMap {
p =>
candidates.map(ext => Paths.get(p).resolve(ext))
}
.find {
p =>
p.toFile.exists()
}
}

private def findAll(candidates: Seq[String], paths: Seq[String]): Iterable[Path] = {
paths.view
.flatMap {
p =>
candidates.map(ext => Paths.get(p).resolve(ext))
}
.filter {
p =>
p.toFile.exists()
}
}

private def binaryNameCandidates(name: String): Seq[String] = {
IzOs.osType match {
case OsType.Windows =>
Seq("exe", "com", "bat").map(ext => s"$name.$ext")
case _ =>
Seq(name)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package izumi.fundamentals.platform.files

import izumi.fundamentals.platform.time.IzTime

import java.io.File
import java.time.LocalDateTime

trait FileAttributes { this: FileSearch =>
/** Unsafe, will recursively iterate the whole directory
*/
def getLastModifiedRecursively(directory: File): Option[LocalDateTime] = {
import IzTime.*

if (!directory.exists()) {
return None
}

if (directory.isDirectory) {
val dmt = directory.lastModified().asEpochMillisLocal

val fmt = walk(directory).map(_.toFile.lastModified().asEpochMillisLocal).toSeq

Some((dmt +: fmt).max)
} else {
Some(directory.lastModified().asEpochMillisLocal)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package izumi.fundamentals.platform.files

import java.io.File
import java.nio.charset.StandardCharsets
import java.nio.file.Path

trait FileReads {
def readString(path: Path): String = {
import java.nio.file.Files
new String(Files.readAllBytes(path), StandardCharsets.UTF_8)
}

def readString(file: File): String = {
readString(file.toPath)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package izumi.fundamentals.platform.files

import java.io.File
import java.nio.file.{Files, Path}
import scala.jdk.CollectionConverters.*

trait FileSearch {
def walk(directory: Path): Iterator[Path] = {
Files.walk(directory).iterator().asScala
}

def walk(directory: File): Iterator[Path] = {
walk(directory.toPath)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package izumi.fundamentals.platform.files

import izumi.fundamentals.platform.language.Quirks

import java.nio.file.{Files, Path}

trait FsRefresh { this: RecursiveFileRemovals =>
def recreateDirs(paths: Path*): Unit = {
paths.foreach(recreateDir)
}

def recreateDir(path: Path): Unit = {
val asFile = path.toFile

if (asFile.exists()) {
erase(path)
}

Quirks.discard(asFile.mkdirs())
}

def refreshSymlink(symlink: Path, target: Path): Unit = {
Quirks.discard(symlink.toFile.delete())
Quirks.discard(Files.createSymbolicLink(symlink, target.toFile.getCanonicalFile.toPath))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package izumi.fundamentals.platform.files

import java.nio.file.{Path, Paths}

trait Homedir {
def home(): Path = {
Paths.get(System.getProperty("user.home"))
}

def homedir(): String = {
home().toFile.getCanonicalPath
}
}
Original file line number Diff line number Diff line change
@@ -1,144 +1,19 @@
package izumi.fundamentals.platform.files

import java.io.{File, IOException}
import java.net.URI
import java.nio.charset.StandardCharsets
import java.nio.file._
import java.nio.file.attribute.BasicFileAttributes
import java.time.LocalDateTime
import java.util.stream.Collectors

import izumi.fundamentals.platform.language.Quirks
import izumi.fundamentals.platform.os.{IzOs, OsType}
import izumi.fundamentals.platform.time.IzTime

import scala.jdk.CollectionConverters._
import java.nio.file.*
import scala.annotation.nowarn
import scala.jdk.CollectionConverters.*
import scala.util.Try

object IzFiles {
def homedir(): String = {
Paths.get(System.getProperty("user.home")).toFile.getCanonicalPath
}

def getFs(uri: URI): Try[FileSystem] = synchronized {
@nowarn("msg=Unused import")
object IzFiles extends RecursiveFileRemovals with FileSearch with FsRefresh with FileReads with ExecutableSearch with Homedir with FileAttributes {
def getFs(uri: URI, loader: ClassLoader): Try[FileSystem] = {
// this is like DCL, there might be races but we have no tool to prevent them
// so first we try to get a filesystem, if it's not there we try to create it, there might be a race so it can fail, so we try to get again
Try(FileSystems.getFileSystem(uri))
.recover {
case _ =>
FileSystems.newFileSystem(uri, Map.empty[String, Any].asJava)
}
}

def getLastModified(directory: File): Option[LocalDateTime] = {
import IzTime._

if (!directory.exists()) {
return None
}

if (directory.isDirectory) {
val dmt = directory.lastModified().asEpochMillisLocal

val fmt = walk(directory).map(_.toFile.lastModified().asEpochMillisLocal)

Some((dmt +: fmt).max)
} else {
Some(directory.lastModified().asEpochMillisLocal)
}
}

def walk(directory: File): Seq[Path] = {
Files.walk(directory.toPath).collect(Collectors.toList()).asScala.toSeq
}

def recreateDirs(paths: Path*): Unit = {
paths.foreach(recreateDir)
}

def readString(path: Path): String = {
import java.nio.file.Files
new String(Files.readAllBytes(path), StandardCharsets.UTF_8)
}

def readString(file: File): String = {
readString(file.toPath)
}

def recreateDir(path: Path): Unit = {
val asFile = path.toFile

if (asFile.exists()) {
removeDir(path)
}

Quirks.discard(asFile.mkdirs())
}

def removeDir(root: Path): Unit = {
val _ = Files.walkFileTree(
root,
new SimpleFileVisitor[Path] {
override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = {
Files.delete(file)
FileVisitResult.CONTINUE
}

override def postVisitDirectory(dir: Path, exc: IOException): FileVisitResult = {
Files.delete(dir)
FileVisitResult.CONTINUE
}

},
)
}

def refreshSymlink(symlink: Path, target: Path): Unit = {
Quirks.discard(symlink.toFile.delete())
Quirks.discard(Files.createSymbolicLink(symlink, target.toFile.getCanonicalFile.toPath))
}

def haveExecutables(names: String*): Boolean = {
names.forall(which(_).nonEmpty)
}

def which(name: String, morePaths: Seq[String] = Seq.empty): Option[Path] = {
find(binaryNameCandidates(name), IzOs.path ++ morePaths)
}

def whichAll(name: String, morePaths: Seq[String] = Seq.empty): Iterable[Path] = {
findAll(binaryNameCandidates(name), IzOs.path ++ morePaths)
}

private def binaryNameCandidates(name: String): Seq[String] = {
IzOs.osType match {
case OsType.Windows =>
Seq("exe", "com", "bat").map(ext => s"$name.$ext")
case _ =>
Seq(name)
}
}

def find(candidates: Seq[String], paths: Seq[String]): Option[Path] = {
paths.view
.flatMap {
p =>
candidates.map(ext => Paths.get(p).resolve(ext))
}
.find {
p =>
p.toFile.exists()
}
}

def findAll(candidates: Seq[String], paths: Seq[String]): Iterable[Path] = {
paths.view
.flatMap {
p =>
candidates.map(ext => Paths.get(p).resolve(ext))
}
.filter {
p =>
p.toFile.exists()
}
.orElse(Try(FileSystems.newFileSystem(uri, Map.empty[String, Any].asJava, loader)))
.orElse(Try(FileSystems.getFileSystem(uri)))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ object IzZip {
}

// zip filesystem isn't thread safe
def findInZips(zips: Seq[File], predicate: Path => Boolean): Iterable[(Path, String)] = synchronized {
def findInZips(zips: Seq[File], predicate: Path => Boolean): Iterable[(Path, String)] = {
zips
.filter(f => f.exists() && f.isFile && (f.getName.endsWith(".jar") || f.getName.endsWith(".zip")))
.flatMap {
f =>
val uri = f.toURI
val jarUri = URI.create(s"jar:${uri.toString}")
val fs = IzFiles.getFs(jarUri).get
val fs = IzFiles.getFs(jarUri, Thread.currentThread().getContextClassLoader).get

try {
enumerate(predicate, fs)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package izumi.fundamentals.platform.files

import java.io.{File, IOException}
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.{FileVisitResult, Files, Path, SimpleFileVisitor}

trait RecursiveFileRemovals {
def erase(root: Path): Unit = {
val _ = Files.walkFileTree(
root,
new SimpleFileVisitor[Path] {
override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = {
Files.delete(file)
FileVisitResult.CONTINUE
}

override def postVisitDirectory(dir: Path, exc: IOException): FileVisitResult = {
Files.delete(dir)
FileVisitResult.CONTINUE
}

},
)
}

def erase(root: File): Unit = {
erase(root.toPath)
}

@deprecated("use IzFiles.erase")
def removeDir(root: Path): Unit = {
erase(root)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,12 @@ final class IzResources(private val classLoader: ClassLoader) extends AnyVal {
Some(LoadablePathReference(Paths.get(u.toURI), null))
} catch {
case _: FileSystemNotFoundException =>
IzFiles.getFs(u.toURI) match {
IzFiles.getFs(u.toURI, classLoader) match {
case Failure(_) =>
Some(UnloadablePathReference(u.toURI))
// throw exception

case Success(fs) =>
fs.synchronized {
Some(LoadablePathReference(fs.provider().getPath(u.toURI), fs))
}
Some(LoadablePathReference(fs.provider().getPath(u.toURI), fs))
}

}
Expand Down
Loading

0 comments on commit a697c37

Please sign in to comment.