
.10.dp.source-code.file.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core_2.10 Show documentation
Show all versions of core_2.10 Show documentation
Core drx utilities that have no other dependencies other than the core scala lib
The newest version!
/*
Copyright 2010 Aaron J. Radke
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cc.drx
//java package names renamed
import java.io.{File=>JFile}
import java.nio.file.{Path => JPath, Paths => JPaths, Files => JFiles}
import java.io.{InputStream => JIS}
import java.io.{OutputStream => JOS}
import java.nio.file.{WatchService,WatchKey,StandardWatchEventKinds=>WEK}
import scala.collection.JavaConverters._
import scala.concurrent.blocking
//java packages
import java.nio.file.attribute.PosixFilePermission
//--Object
object File{
//--alias to common stream generator formats
val GZ = StreamGenerator.GZ
//TODO need to make a smart convert that looks at file extensions and does the right thing the typeclass for Covnert is already there as a nice possible solution...
//--file helper objects
implicit object ParsableFile extends Parsable[File]{ def apply(v:String):File = File(v) }
case class Loc(file:File, line:Int)
final class FileStringContext(val sc:StringContext) extends AnyVal{
def file(args:Any*):File = File(sc.s(args:_*))
}
//--constructors
def apply(path:JPath):File = new File(path.toFile)
def apply(file:JFile):File = new File(file)
def apply(path:String="."):File = new File(new JFile(path))
//--specific constructions
def roots = java.io.File.listRoots.toList map apply
def root = File("/") //multiple meanings for windows but the list function will list the available mounts in windows
def home = File(System.getProperty("user.home")) //cross platform method to generate
def cwd = File(".") //curent working directory, pwd is the absolute version
def pwd = File(".").canon //current working directory, pwd is the absolute canonical version
/**Open a dialog box to choose a file **Blocking** */
def chooseBlocking(dialogTitle:String="Choose a file or directory",
startingDirectory:File=File(".")
):Option[File] = {
var block = true
var choice:Option[File] = None
chooseOption(dialogTitle, startingDirectory){ f => choice = f ; block = false}
@tailrec def waitForSignal:Option[File] = if(block){blocking{Thread.sleep(100)}; waitForSignal}
else choice
waitForSignal
}
/**Open a dialog box to choose a file **Non-Blocking** */
def choose(dialogTitle:String="Choose a file or directory",
startingDirectory:File=File(".")
)(callback: File => Unit):Unit = chooseOption(dialogTitle, startingDirectory){
case Some(f) => callback(f)
case _ =>
}
private def chooseOption(dialogTitle:String="Choose a file or directory",
startingDirectory:File=File(".")
)(callback: Option[File] => Unit):Unit = {
//Setup the chooser
import javax.swing.JFileChooser
import javax.swing.UIManager
val previousLF = UIManager.getLookAndFeel
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName)
val chooser = new JFileChooser
UIManager.setLookAndFeel(previousLF)
chooser setCurrentDirectory startingDirectory.file
chooser setDialogTitle dialogTitle
chooser setFileSelectionMode JFileChooser.FILES_AND_DIRECTORIES
chooser setAcceptAllFileFilterUsed false
//Setup the frame panel with an embedded chooser
import javax.swing.{JFrame,JPanel}
val frame = new JFrame
val panel = new JPanel
//Setup the chooser action commands
chooser.addActionListener(
new java.awt.event.ActionListener{
override def actionPerformed(e: java.awt.event.ActionEvent) = {
val cmd = e.getActionCommand match {
case JFileChooser.APPROVE_SELECTION =>
frame.setVisible(false)
frame.dispose
callback(Option(chooser.getSelectedFile) map File.apply)
case _ =>
frame.setVisible(false)
frame.dispose
callback(None)
}
}
}
)
//frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
frame.getContentPane.add(java.awt.BorderLayout.CENTER, panel)
panel.add(chooser)
val loc = {
val d = Display.insets()
d.loc + d.size/2 - Vec(200,200)
}
frame.setLocation(loc.x.toInt,loc.y.toInt)
frame.pack
frame.toFront
frame.setAlwaysOnTop(true)
frame.setVisible(true)
}
//--directory listings
private class DirectoryListingIterator(startingDir:File) extends Iterator[File]{
private val stream = JFiles.newDirectoryStream(startingDir.file.toPath)
private val it = stream.iterator
private var opened = true
private var autoClose = true
def keepOpen:DirectoryListingIterator = {autoClose = false; this} // turn off autoClose
def hasNext:Boolean = {
val res = it.hasNext
if(opened && autoClose && !it.hasNext){opened=false; stream.close}
res
}
def next:File = File(it.next)
}
}
//-- case class
class File(val file:JFile) extends AnyVal{
def toJava:java.io.File = file
//--construction
//-child path
def /(that:File):File = File(path + "/" + that.path)
def /(filename:String):File = if(filename == "" || filename == "/") this else File(new JFile(file,filename))
def /(filename:Symbol):File = this / filename.name
def /(glob:Glob):Iterator[File] = this/(glob, 999)
def /(glob:Glob, maxDepth:Int=0):Iterator[File] = walk(maxDepth) filter {f =>
glob matches f.relativeTo(this).unixPath
}
// def *(glob:String):Iterator[File] TODO add glob by list or by walk depth
def +(postfix:String):File = File(path + postfix)
//-use current file as relative absolute
def apply(path:String) = {
val f = File(path)
if(f.isAbs) f else this / path
}
/**default to string should be a cross platform representation of path separators
* Note: the system path form is still returned by _.path */
override def toString = unixPath// vs file.toString which is the java default
//--operations
def rename(that:File):Option[File] = if(file renameTo that.file) Some(that) else None
def delete = file.delete
//--properties
def size = Bytes(file.length)
def driveSize = {
val f:java.io.File = root.file //get the root file object
OS.Memory(free=Bytes(f.getFreeSpace),total=Bytes(f.getTotalSpace),max=Bytes(f.getUsableSpace))
}
def name:String = file.getName
def path:String = file.getPath
def unixPath = path.replaceAllLiterally("\\","/")
def basename = {
val s = name
val i = s.lastIndexOf(".")
if(i < 1 || i == s.size-1) s //-1 => no .; 0 => .hidden file; N-1 => dangling.
else s.substring(0,i)
}
def ext = {
val s = name
val i = s.lastIndexOf(".")
if(i < 1 || i == s.size-1) "" //-1 => no .; 0 => .hidden file; N-1 => dangling.
else s.substring(i+1)
}
def isDir:Boolean = file.isDirectory
def isFile:Boolean = file.isFile
def isAbs:Boolean = file.isAbsolute
def isHidden:Boolean = file.isHidden
def canRead:Boolean = file.canRead
/**provide a monadic expression for the existence of a file*/
def fileOption:Option[File] = if(isFile) Some(this) else None
//TODO add | syntax for orElse
/**switch to a file or directory that exists else keep the last filename*/
def orElse(that: => File):File = if(isFile || isDir) this else that
/**if a file exists use it as an input else use the input. This is nice to roll over to resources: File("lost.txt") or IO.resource("found.txt")*/
def orElse(that: => Input):Input = if(isFile) this.in else that
/**if the file doesn't exist download it*/
def orElse(url:URL)(implicit ec:ExecutionContext):Future[File] = if(isFile) Future.successful(this) else url.connect map {_ get this}
def modified:Date = Date(file.lastModified)
/**a setter way to set the last modified time, i.e a copy should sometimes do this*/
def modified_=(date:Date) = file.setLastModified(date.ms)
def url:URL = new URL(canon.toURI.toURL)
def abs:File = File(file.getAbsoluteFile)
def canon:File = if(path(0) == '~') (File.home/path.drop(1)).canon else File(file.getCanonicalFile)
//FIXME test this because the getAbsoluteFile was automatic before, but now f.abs.parent is required and it uses the unix path expasion instead of File parent finding
//def parent:Option[File] = Option(file.getAbsoluteFile.getParentFile) map File.apply
def parent:Option[File] = {
val p = unixPath
val n = name
if(p.endsWith("/"+n)) Some(File(p.dropRight(n.size+1))) else None
}
def companion(altExt:String):File = {
val n = this.ext.size
File( (if(n == 0) path + "." else path dropRight n) + altExt )
}
/**clean the file name to be a safe (without spaces and problematic characters)*/
def safe:File = {
//TODO what about parent directories in the path that may not be safe
val safeName = name
.replaceAllLiterally(" ","_") //messes with quotes
.replaceAllLiterally("/","_") //bad for unix
.replaceAllLiterally("\\","_") //bad for windows
.replaceAllLiterally(":","") //bad for macos
parent.map{_ / safeName} getOrElse File(safeName)
}
def relativeTo(that:File):File = {
val path = JPaths.get(that.file.toURI) relativize JPaths.get(this.file.toURI)
File(path.toFile)
}
final def ==(that:File):Boolean = this.file.getCanonicalFile == that.file.getCanonicalFile
//final def ==(that:File) = this.canon.path == that.canon.path
//final def ==(that:File) = this.file == that.file
/**ensures current file parent directories exist by checking or making them, returns Success[File] if successful
* This can be used as a sort of monadic wrapper around succesful parents
* In most cases this removes the need for an explicit mkdir, assuming you should only need a directory if you have a file
* Note: this assumes a file inside the directory is specified. It only makes parent files, not the file itself
* Note: we need the canonical in system version of the file to make sure the parent directory is really possible on the system
*/
def mkParents:Try[File] = canon.parent match {
case None => Fail((s"$this does not define a feasible parent on this system"))
case Some(p) => {
if(p.isFile) Fail((s"parent of $this alreay exists and is not a directory"))
else if(p.isDir) Success(this) //the parent already exists and is a file
else Try{p.file.mkdirs} match { //use java.io.File mkdirs to make the parents
case Success(true) => Success(this)
case Success(false) => Fail(s"could not create parent dir $p")
case Failure(ex) => Failure(ex)
}
}
}
/**ensures current file parent directories exist by checking or making them, returns Some(File) if successful*/
@deprecated("use `mkParents` for a more accurate name and rich return type","v0.2.15")
def mkdirs:Option[File] = mkParents.toOption
//TODO make this fail utility function a broader drx utility function, maybe at the package level
private def Fail[A](msg:String):Failure[A] = Failure[A](new java.lang.Throwable(msg))
//local copy helper functions
private def cp(that:File):Unit = this.in copy that.out
private def cpTry(that:File):Try[File] = Try{cp(that); that.modified = this.modified; that}
/**smart copy, make sure the destination parent directories exist and preserve the src file modification time*/
def copyTo(that:File):Try[File] = that.mkParents flatMap cpTry
def chmod(perm:Int):Unit = {
def rwx(perm:Int):(Boolean,Boolean,Boolean) = ((perm & 4) > 0, (perm & 2) > 0, (perm & 1) > 0)
val (ro,wo,xo) = rwx(perm / 100)
val (rg,wg,xg) = rwx(perm % 100 / 10)
val (ra,wa,xa) = rwx(perm % 100 % 10)
//> java 7
Try{
val perms = Seq(
ro -> PosixFilePermission.OWNER_READ,
wo -> PosixFilePermission.OWNER_WRITE,
xo -> PosixFilePermission.OWNER_EXECUTE,
rg -> PosixFilePermission.GROUP_READ,
wg -> PosixFilePermission.GROUP_WRITE,
xg -> PosixFilePermission.GROUP_EXECUTE,
ra -> PosixFilePermission.OTHERS_READ,
wa -> PosixFilePermission.OTHERS_WRITE,
xa -> PosixFilePermission.OTHERS_EXECUTE
).filter(_._1).map(_._2).toSet.asJava
JFiles.setPosixFilePermissions(file.toPath,perms)
} getOrElse {
//< java 7
val r = ro | rg | ra
val w = wo | wg | wa
val x = xo | xg | xa
val rGlobal = rg | ra
val wGlobal = wg | wa
val xGlobal = xg | xa
file.setReadable( r, !rGlobal) // set if any are writable, set owner only
file.setWritable( w, !wGlobal)
file.setExecutable(x, !xGlobal)
}
{}
}
/**Smart listing files or directories or cross platform root capabilities*/
def list:Iterator[File] = {
if(path == "/" || path == "\\"){ //root path case works even for Windows by listing the available drives
val roots = File.roots
if(roots.size == 1) roots(0).list //if only one mount list its directories (this is the unix case and if only a C: drive)
else roots.iterator
}
else if(this.isDir)
Try{new File.DirectoryListingIterator(this)} getOrElse {println(s"warning could not list file $this"); Iterator()} //list the files in the directory if no exception
else if(this.isFile)
Iterator(this) //if this is a file return an iterator over a single file
else if(path startsWith "~") (OS.home / path.drop(1)).list
else Iterator[File]()
}
def root:File = {
val p = this.canon.path
File.roots.find(p startsWith _.path) getOrElse File.root.canon
}
/**walk is equivalent to walk(maxDepth=0,=>false,0) is equivalent to list */
def walk:Iterator[File] = list.flatMap{f =>
if(f.isDir) Iterator(f) ++ f.walk
else Iterator(f)
}
/**walk(maxDepth=0) is equivalent to list */
def walk(maxDepth:Int):Iterator[File] = walkAtDepth(maxDepth=Some(maxDepth), skipDir = (_) => false) //don't skip any file (directory)
def walkSkipIf(skipDir:File=>Boolean):Iterator[File] = walkAtDepth(maxDepth=None, skipDir=skipDir)
private def walkAtDepth(
maxDepth:Option[Int],
skipDir:File=>Boolean = (_) => false, //don't skip directories by default
currentDepth:Int = 0 //accumulator should be set
):Iterator[File] = list.flatMap{f =>
val notAtMaxDepth = maxDepth map {currentDepth < _} getOrElse true
if(notAtMaxDepth && f.isDir && f.canRead && !skipDir(f))
Iterator(f) ++ f.walkAtDepth(maxDepth,skipDir,currentDepth+1)
else Iterator(f)
}
//--stream generators
/*
private var specifiedStreamGenerator:Option[IO.StreamGenerator] = None
private def findStreamGenerator:IO.StreamGenerator = {
if(specifiedStreamGenerator.isDefined) specifiedStreamGenerator.get
else streamGeneratorLibrary.find(_.exts contains ext) getOrElse IO.Normal
}
def as(sg:IO.StreamGenerator):File = {specifiedStreamGenerator = Some(sg); this}
*/
def in:Input = Input(new java.io.BufferedInputStream(new java.io.FileInputStream(file),8*1024))
private def mkOutput(append:Boolean):Try[Output] = mkParents.map{f =>
Output(new java.io.BufferedOutputStream(new java.io.FileOutputStream(f.file,append),8*1024))
}
def out:Output = mkOutput(append=false).get
def outAppend:Output = mkOutput(append=true).get
//--file watch functions
def watch(func:File => Unit)(implicit ec: ExecutionContext):WatchService =
if(isDir) watch("CMD",recursive=true)(func)
else watch("CM")(func)
def watch(setup:String,recursive:Boolean=false,debounce:Time=Time(1))(func:File => Unit)(implicit ec:ExecutionContext):WatchService = {
val wek = Map(
'C' -> WEK.ENTRY_CREATE,
'M' -> WEK.ENTRY_MODIFY,
'D' -> WEK.ENTRY_DELETE
)
val weks = setup.toUpperCase map wek
val dir:File = if(isDir) this else parent getOrElse this
val dirPath:JPath = dir.file.toPath
val watcher = dirPath.getFileSystem.newWatchService
val registeredPaths = TrieMap.empty[WatchKey,JPath] //crazy map is required to back out the absolute file path from multiple registered paths
def register(dir:File):Unit = {
//println(s"Register watching $dir")
val path = dir.file.toPath
val wk = path.register(watcher, weks:_*)
registeredPaths(wk) = path
}
//--register
register(dir) //parent
if(recursive && isDir) for(f <- walk; if f.isDir) register(f) //recursively
//--map for file modifications
val lastModified = TrieMap.empty[File,Date]
@tailrec def watch:Unit = {
val key = watcher.take
//Thread.sleep(1000)//1sec
key.pollEvents.asScala.foreach{e =>
/*
e.kind match {
case WEK.ENTRY_MODIFY => println(s"Modified $file")
case WEK.ENTRY_CREATE => println(s"Created $file")
case WEK.ENTRY_DELETE => println(s"Deleted $file")
}
*/
val eventPath = e.context.asInstanceOf[JPath]
val dir = File(registeredPaths.getOrElse(key,dirPath).toFile)
val eventFile = dir / eventPath.toFile.getName
if(isDir || (isFile && this == eventFile)){
val bounced = (lastModified contains eventFile) && (eventFile.modified - lastModified(eventFile) < debounce)
if(!bounced) func(eventFile)
}
lastModified(eventFile) = eventFile.modified
}
if(key.reset) watch
else { key.cancel; watcher.close}
}
Future{watch} onComplete {
case Success(_) => println(s"Done watching $this")
case Failure(e:java.io.FileNotFoundException) => watch //keep watching
case Failure(e) => println(s"Stopped watching $this: $e")
}
watcher
}
//--conversion utilities
def convert[A<:FileKind, B<:FileKind](tgt:File)(implicit fc:FileConverter[A,B], ec:EC):Future[File] = fc.convert(this, tgt)
def convert[A<:FileKind, B<:FileKind](implicit fc:FileConverter[A,B], ec:EC):Future[File] = fc.convert(this)
//auto detect file types
def convertTo(tgt:File)(implicit fcs:FileConverters, ec:EC):Future[File] = fcs.convert(this,tgt)
}