1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
package mill.eval
import java.io.IOException
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.{FileVisitResult, FileVisitor}
import java.nio.{file => jnio}
import java.security.{DigestOutputStream, MessageDigest}
import upickle.default.{ReadWriter => RW}
import ammonite.ops.Path
import mill.util.{DummyOutputStream, IO, JsonFormatters}
/**
* A wrapper around `ammonite.ops.Path` that calculates it's hashcode based
* on the contents of the filesystem underneath it. Used to ensure filesystem
* changes can bust caches which are keyed off hashcodes.
*/
case class PathRef(path: ammonite.ops.Path, quick: Boolean, sig: Int){
override def hashCode() = sig
}
object PathRef{
def apply(path: ammonite.ops.Path, quick: Boolean = false) = {
val sig = {
val digest = MessageDigest.getInstance("MD5")
val digestOut = new DigestOutputStream(DummyOutputStream, digest)
jnio.Files.walkFileTree(
path.toNIO,
java.util.EnumSet.of(jnio.FileVisitOption.FOLLOW_LINKS),
Integer.MAX_VALUE,
new FileVisitor[jnio.Path] {
def preVisitDirectory(dir: jnio.Path, attrs: BasicFileAttributes) = {
digest.update(dir.toAbsolutePath.toString.getBytes)
FileVisitResult.CONTINUE
}
def visitFile(file: jnio.Path, attrs: BasicFileAttributes) = {
digest.update(file.toAbsolutePath.toString.getBytes)
if (quick){
val value = (path.mtime.toMillis, path.size).hashCode()
digest.update((value >>> 24).toByte)
digest.update((value >>> 16).toByte)
digest.update((value >>> 8).toByte)
digest.update(value.toByte)
} else if (jnio.Files.isReadable(file)) {
val is = jnio.Files.newInputStream(file)
IO.stream(is, digestOut)
is.close()
}
FileVisitResult.CONTINUE
}
def visitFileFailed(file: jnio.Path, exc: IOException) = FileVisitResult.CONTINUE
def postVisitDirectory(dir: jnio.Path, exc: IOException) = FileVisitResult.CONTINUE
}
)
java.util.Arrays.hashCode(digest.digest())
}
new PathRef(path, quick, sig)
}
implicit def jsonFormatter: RW[PathRef] = upickle.default.readwriter[String].bimap[PathRef](
p => {
(if (p.quick) "qref" else "ref") + ":" +
String.format("%08x", p.sig: Integer) + ":" +
p.path.toString()
},
s => {
val Array(prefix, hex, path) = s.split(":", 3)
PathRef(
Path(path),
prefix match{ case "qref" => true case "ref" => false},
// Parsing to a long and casting to an int is the only way to make
// round-trip handling of negative numbers work =(
java.lang.Long.parseLong(hex, 16).toInt
)
}
)
}
|