summaryrefslogtreecommitdiff
path: root/project/ShaResolve.scala
blob: cea2b2d6ccfacb274bc9db2602d3e14e9d998486 (plain) (blame)
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import sbt._

import Build._
import Keys._
import Project.Initialize
import scala.collection.{ mutable, immutable }
import scala.collection.parallel.CompositeThrowable
import java.security.MessageDigest

case class Credentials(user: String, pw: String)

/** Helpers to resolve SHA artifacts from typesafe repo. */
object ShaResolve {
  import dispatch.{Http,url}
  val remote_urlbase="http://typesafe.artifactoryonline.com/typesafe/scala-sha-bootstrap/org/scala-lang/bootstrap"  
  
  val pullBinaryLibs = TaskKey[Unit]("pull-binary-libs", "Pulls binary libs by the SHA key.")
  val pushBinaryLibs = TaskKey[Unit]("push-binary-libs", "Pushes binary libs whose SHA has changed.")
  val binaryLibCache = SettingKey[File]("binary-lib-cache", "Location of the cache of binary libs for this scala build.")

  def settings: Seq[Setting[_]] = Seq(
    binaryLibCache in ThisBuild := file(System.getProperty("user.home")) / ".sbt" / "cache" / "scala",
    pullBinaryLibs in ThisBuild <<= (baseDirectory, binaryLibCache, streams) map resolveLibs,
    pushBinaryLibs in ThisBuild <<= (baseDirectory, streams) map getCredentialsAndPushFiles
  )

  def resolveLibs(dir: File, cacheDir: File, s: TaskStreams): Unit = loggingParallelExceptions(s) {
     val files = (dir / "test" / "files" ** "*.desired.sha1") +++ (dir / "lib" ** "*.desired.sha1")
     for {
       (file, name) <- (files x relativeTo(dir)).par
       uri = name.dropRight(13).replace('\\', '/')       
       jar = dir / uri
       if !jar.exists || !isValidSha(file)
       sha = getShaFromShafile(file)
     } pullFile(jar, sha + "/" + uri, cacheDir, sha, s)
  }

  /** This method removes all SHA1 files that don't match their corresponding JAR. */
  def removeInvalidShaFiles(dir: File): Unit = {
    val files = (dir / "test" / "files" ** "*.desired.sha1") +++ (dir / "lib" ** "*.desired.sha1")
    for {
      (file, name) <- (files x relativeTo(dir)).par
      uri = name.dropRight(13).replace('\\', '/')       
      jar = dir / uri
      if !jar.exists || !isValidSha(file)
    } IO.delete(jar)
  }
  def getCredentials: Credentials = System.out.synchronized {
    val user = (SimpleReader.readLine("Please enter your STARR username> ") getOrElse error("No username provided."))
    val password = (SimpleReader.readLine("Please enter your STARR password> ", Some('*')) getOrElse error("No password provided."))
    Credentials(user, password)
  }

  def getCredentialsAndPushFiles(dir: File, s: TaskStreams): Unit =
    pushFiles(dir, getCredentials, s)

  def pushFiles(dir: File, cred: Credentials, s: TaskStreams): Unit = loggingParallelExceptions(s) {
    val files = (dir / "test" / "files" ** "*.jar") +++ (dir / "lib" ** "*.jar")
    for {
      (jar, name) <- (files x relativeTo(dir)).par
      shafile = dir / (name + ".desired.sha1")
      if !shafile.exists || !isValidSha(shafile)
    } pushFile(jar, name, cred, s)
  }

  @inline final def loggingParallelExceptions[U](s: TaskStreams)(f: => U): U = try f catch {
    case t: CompositeThrowable =>
      s.log.error("Error during parallel execution, GET READY FOR STACK TRACES!!")
      t.throwables foreach (t2 => s.log.trace(t2))
      throw t
  }

  // TODO - Finish this publishing aspect.

  def getShaFromShafile(file: File): String = parseShaFile(file)._2

  // This should calculate the SHA sum of a file the same as the linux process.
  def calculateSha(file: File): String = {
    val digest = MessageDigest.getInstance("SHA1")
    val in = new java.io.FileInputStream(file);
    val buffer = new Array[Byte](8192)
    try {
       def read(): Unit = in.read(buffer) match {
         case x if x <= 0 => ()
         case size => digest.update(buffer, 0, size); read()
       }
       read()
    } finally in.close()
    val sha = convertToHex(digest.digest())
    sha
  }

  def convertToHex(data: Array[Byte]): String = {
    def byteToHex(b: Int) =
      if ((0 <= b) && (b <= 9)) ('0' + b).toChar
      else ('a' + (b-10)).toChar
    val buf = new StringBuffer
    for (i <- 0 until data.length) {
      buf append byteToHex((data(i) >>> 4) & 0x0F)
      buf append byteToHex(data(i) & 0x0F)
    }
    buf.toString
  }
  // Parses a sha file into a file and a sha.
  def parseShaFile(file: File): (File, String) =
    IO.read(file).split("\\s") match {
       case Array(sha, filename) if filename.startsWith("?") => (new File(file.getParentFile, filename.drop(1)), sha)
       case Array(sha, filename)                             => (new File(file.getParentFile, filename), sha)
       case _                                                => error(file.getAbsolutePath + " is an invalid sha file")
    }
  

  def isValidSha(file: File): Boolean =
    try {
      val (jar, sha) = parseShaFile(file)
      jar.exists && calculateSha(jar) == sha
    } catch {
      case t: Exception => false
    }
     

  def pullFile(file: File, uri: String, cacheDir: File, sha: String, s: TaskStreams): Unit = {
    val cachedFile = cacheDir / uri
    if (!cachedFile.exists || calculateSha(cachedFile) != sha) {
      // Ensure the directory for the cache exists.
      cachedFile.getParentFile.mkdirs()
      val url = remote_urlbase + "/" + uri
      val fous = new java.io.FileOutputStream(cachedFile)
      s.log.info("Pulling [" + cachedFile + "] to cache")
      try Http(dispatch.url(url) >>> fous) finally fous.close()
    }
    s.log.info("Pulling [" + file + "] from local cache")
    IO.copyFile(cachedFile, file)
  }
  
  // Pushes a file and writes the new .desired.sha1 for git.
  def pushFile(file: File, uri: String, cred: Credentials, s: TaskStreams): Unit = {
    val sha = calculateSha(file)
    val url = remote_urlbase + "/" + sha + "/" + uri
    val sender = dispatch.url(url).PUT.as(cred.user,cred.pw) <<< (file, "application/java-archive")
    // TODO - output to logger.
    Http(sender >>> System.out)
    val shafile = file.getParentFile / (file.getName + ".desired.sha1")
    IO.touch(shafile)
    IO.write(shafile, sha + " ?" + file.getName)
  }
}