|
Play Framework/Scala example source code file (FileWatcher.scala)
The FileWatcher.scala Play Framework example source code
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package play.sbtplugin.run
import java.io.{ IOException, File }
import sbt.{ WatchState, SourceModificationWatch, IO }
import sbt.Path._
import scala.util.control.NonFatal
/**
* A service that can watch files
*/
trait PlayWatchService {
/**
* Watch the given sequence of files or directories.
*
* @param filesToWatch The files to watch.
* @param onChange A callback that is executed whenever something changes.
* @return A watcher
*/
def watch(filesToWatch: Seq[File], onChange: () => Unit): PlayWatcher
}
/**
* A watcher, that watches files.
*/
trait PlayWatcher {
/**
* Stop watching the files.
*/
def stop(): Unit
}
object PlayWatchService {
def apply(targetDirectory: File): PlayWatchService = {
JNotifyPlayWatchService(targetDirectory).getOrElse(new SbtPlayWatchService(500))
}
}
/**
* A polling Play watch service. Polls in the background.
*/
private class SbtPlayWatchService(pollDelayMillis: Int) extends PlayWatchService {
def watch(filesToWatch: Seq[File], onChange: () => Unit) = {
@volatile var stopped = false
val thread = new Thread(new Runnable {
def run() = {
var state = WatchState.empty
while (!stopped) {
val (triggered, newState) = SourceModificationWatch.watch(filesToWatch.***.distinct, pollDelayMillis,
state)(stopped)
if (triggered) onChange()
state = newState
}
}
}, "sbt-play-watch-service")
thread.setDaemon(true)
thread.start()
new PlayWatcher {
def stop() = stopped = true
}
}
}
private class JNotifyPlayWatchService(delegate: JNotifyPlayWatchService.JNotifyDelegate) extends PlayWatchService {
def watch(filesToWatch: Seq[File], onChange: () => Unit) = {
val listener = delegate.newListener(onChange)
val registeredIds = filesToWatch.map { file =>
delegate.addWatch(file.getAbsolutePath, listener)
}
new PlayWatcher {
def stop() = registeredIds.foreach(delegate.removeWatch)
}
}
}
private object JNotifyPlayWatchService {
import java.lang.reflect.{ Method, InvocationHandler, Proxy }
/**
* Captures all the reflection invocations in one place.
*/
class JNotifyDelegate(classLoader: ClassLoader, listenerClass: Class[_], addWatchMethod: Method, removeWatchMethod: Method) {
def addWatch(fileOrDirectory: String, listener: AnyRef): Int = {
addWatchMethod.invoke(null,
fileOrDirectory, // The file or directory to watch
15: java.lang.Integer, // flags to say watch for all events
true: java.lang.Boolean, // Watch subtree
listener).asInstanceOf[Int]
}
def removeWatch(id: Int): Unit = {
try {
removeWatchMethod.invoke(null, id.asInstanceOf[AnyRef])
} catch {
case _: Throwable =>
// Ignore, if we fail to remove a watch it's not the end of the world.
// http://sourceforge.net/p/jnotify/bugs/12/
// We match on Throwable because matching on an IOException didn't work.
// http://sourceforge.net/p/jnotify/bugs/5/
}
}
def newListener(onChange: () => Unit): AnyRef = {
Proxy.newProxyInstance(classLoader, Seq(listenerClass).toArray, new InvocationHandler {
def invoke(proxy: AnyRef, m: Method, args: Array[AnyRef]): AnyRef = {
onChange()
null
}
})
}
}
// Try state - null means no attempt to load yet, None means failed load, Some means successful load
@volatile var watchService: Option[JNotifyPlayWatchService] = null
def apply(targetDirectory: File): Option[PlayWatchService] = {
try {
watchService match {
case null =>
watchService = None
val jnotifyJarFile = this.getClass.getClassLoader.asInstanceOf[java.net.URLClassLoader].getURLs
.map(_.getFile)
.find(_.contains("/jnotify"))
.map(new File(_))
.getOrElse(sys.error("Missing JNotify?"))
val sbtLoader = this.getClass.getClassLoader.getParent.asInstanceOf[java.net.URLClassLoader]
val method = classOf[java.net.URLClassLoader].getDeclaredMethod("addURL", classOf[java.net.URL])
method.setAccessible(true)
method.invoke(sbtLoader, jnotifyJarFile.toURI.toURL)
val nativeLibrariesDirectory = new File(targetDirectory, "native_libraries")
if (!nativeLibrariesDirectory.exists) {
// Unzip native libraries from the jnotify jar to target/native_libraries
IO.unzip(jnotifyJarFile, targetDirectory, (name: String) => name.startsWith("native_libraries"))
}
val libs = new File(nativeLibrariesDirectory, System.getProperty("sun.arch.data.model") + "bits").getAbsolutePath
// Hack to set java.library.path
System.setProperty("java.library.path", {
Option(System.getProperty("java.library.path")).map { existing =>
existing + java.io.File.pathSeparator + libs
}.getOrElse(libs)
})
val fieldSysPath = classOf[ClassLoader].getDeclaredField("sys_paths")
fieldSysPath.setAccessible(true)
fieldSysPath.set(null, null)
val jnotifyClass = sbtLoader.loadClass("net.contentobjects.jnotify.JNotify")
val jnotifyListenerClass = sbtLoader.loadClass("net.contentobjects.jnotify.JNotifyListener")
val addWatchMethod = jnotifyClass.getMethod("addWatch", classOf[String], classOf[Int], classOf[Boolean], jnotifyListenerClass)
val removeWatchMethod = jnotifyClass.getMethod("removeWatch", classOf[Int])
val d = new JNotifyDelegate(sbtLoader, jnotifyListenerClass, addWatchMethod, removeWatchMethod)
// Try it
d.removeWatch(0)
watchService = Some(new JNotifyPlayWatchService(d))
case other => other
}
watchService
} catch {
case NonFatal(e) =>
reportError(e)
None
// JNotify failure on FreeBSD
case e: ExceptionInInitializerError =>
reportError(e)
None
case e: NoClassDefFoundError =>
reportError(e)
None
// JNotify failure on Linux
case e: UnsatisfiedLinkError =>
reportError(e)
None
}
}
private def reportError(e: Throwable) = {
println(play.sbtplugin.Colors.red(
"""|
|Cannot load the JNotify native library (%s)
|Play will poll for file changes, so expect increased system load.
|""".format(e.getMessage).stripMargin
))
}
}
Other Play Framework source code examplesHere is a short list of links related to this Play Framework FileWatcher.scala source code file: |
| ... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.