
.10.dp.source-code.sync.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
/** Rsync is not available on windows and does not provide a generic transformation and skip api*/
object Sync{
/**default with ansi progress display*/
def apply(transform:File => Action):Sync = Sync(transform, _.print)
/**default direct mapping with ansi progress display*/
def apply(srcDir:File, dstDir:File, dryRun:Boolean=false):Summary =
Sync(f => Copy(f), _.print).apply(srcDir, dstDir, dryRun)
//--support types
abstract sealed trait Action{
def dst:File
def reason:Option[Symbol]
def sync(src:File, dryRun:Boolean):Unit //TODO return the success or failure to be reported...
}
//Action classes
case class Skip(dst:File, reason:Option[Symbol]=None) extends Action {
def sync(src:File, dryRun:Boolean) = {} //sync operations for Skip are no-ops
}
case class Copy(dst:File, reason:Option[Symbol]=None) extends Action {
def sync(src:File, dryRun:Boolean) = if(!dryRun){
val res = src.copyTo(dst) //use smart copy and create parent directories if they don't exists
if(res.isFailure) println(Red(s"could not copy $src to $dst; $res"))
}
}
//Alternative constructors
object Skip{def apply(dst:File, reason:Symbol):Skip = Skip(dst, Some(reason))}
object Copy{def apply(dst:File, reason:Symbol):Copy = Copy(dst, Some(reason))}
case class ReasonCount(reasons:Map[Option[Symbol], Int]=Map.empty){
def +(reason:Option[Symbol]) = {
val count:Int = reasons.getOrElse(reason, 0)+1
ReasonCount( reasons + (reason -> count) )
}
lazy val total:Int = reasons.values.sum
override def toString = reasons.map{case (k,v) => (k getOrElse 'Unspecified).name.replaceAllLiterally(" ","_") + ":" + v}.mkString(" ")
}
case class Summary(copyCount:ReasonCount, skipCount:ReasonCount, action:Action){
import Color._
def ansi:String = Ansi.kson("Sync", "copy" -> s"{$copyCount}", "skip" -> s"{$skipCount}")
override def toString = Ansi.strip(ansi)
/**nice ansi progress print*/
def print:Unit = print(false)
def printVerbose:Unit = print(true)
def print(verbose:Boolean):Unit = {
val reasonString = action.reason.map{s=>s" (${s.name})"}.getOrElse("")
action match {
case _:Skip =>
if(verbose) println(s"Sync skip "+ action.dst + reasonString)
else Ansi.printtmp(ansi) //don't print all the cases
case Copy(dst,reason) => println(s"Sync copy to "+ action.dst + reasonString)
}
}
def +(action:Action):Summary = action match {
case _:Copy => Summary(copyCount+action.reason, skipCount, action)
case _:Skip => Summary(copyCount, skipCount+action.reason, action)
}
def total = copyCount.total + skipCount.total
}
}
import Sync._
/**Simple file sync with generic transformation definition*/
case class Sync(transform:File => Action, progress:Summary => Unit){
/**use the default verbose file printing progress*/
def verbose:Sync = Sync(transform, _.printVerbose)
def apply(srcDir:File, dstDir:File, dryRun:Boolean=false):Summary = {
//TODO make this also work if the src is a File instead of a directory
//immediately find the canonical version of the src and dst, (for windows machines this is especially important to expand things like ~/ for the home directory)
val srcDirCanon = srcDir.canon //now the user passed in variables can be masked
val dstDirCanon = dstDir.canon
var summary = Summary(ReasonCount(),ReasonCount(),Copy(dstDir)) //initial sync summary zero and remote root dir
// Macro.pp(srcDir, srcDirCanon, srcDirCanon.isFile, srcDirCanon.isDir)
Timer.withHeader(Ansi.kson("Sync"+(if(dryRun)"(dry)" else ""), "src" -> srcDir, "dst" -> dstDir)){
for(src <- srcDirCanon.walk if !src.isDir){ //TODO add walkSkipIf to skip entire dirs
val srcRelative = src relativeTo srcDirCanon
//-- determine the action type
val action = transform(srcRelative) match {
case Copy(dstRelative,reason) => {
val dst = dstDirCanon / dstRelative
val reasonString = reason.map{"."+_.name}.getOrElse("") //add .subcategory reason
def copy(reason:Symbol):Copy = Copy(dst, Symbol(reason.name + reasonString))
// -- needs update tests on the absolute file locations
// Essentially skip copy unless files are possibly different then force overwrite
// Note: doing a diff(or sha1) is on the order of time to do a copy so just do a copy
if (!dst.isFile) copy('Missing)
else if(src.size != dst.size) copy('DifferentSize)
else if(src.modified > dst.modified) copy('Older)
else Skip(dst, Symbol("Exists" + reasonString))
}
case x => x //this skip reason from the relative transform should just pass through
}
progress(summary) //call the updated summary with the progress callback (report before action)
action.sync(src,dryRun) // apply the sync action (skips are essentially null ops)
summary += action // update the summary with a new count of the action types
}
println(summary.ansi)
summary
}
}
}