Skip to content

Commit 025f2ca

Browse files
committed
NotifyWatcher
1 parent 604f328 commit 025f2ca

14 files changed

+712
-4
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ target/
66
.project
77
.cache
88
.sbtserver
9+
.bsp/
910
project/.sbtserver
1011
tags
1112
nohup.out
1213
out
1314
lowered.hnir
15+
random_data/

build.sc

+8-3
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ object os extends Module {
5050
def platformSegment = "jvm"
5151
def moduleDeps = super.moduleDeps :+ os.jvm()
5252
def ivyDeps = Agg(
53-
ivy"net.java.dev.jna:jna:5.0.0"
53+
ivy"net.java.dev.jna:jna:5.0.0",
54+
ivy"com.lihaoyi::sourcecode::0.2.5",
5455
)
5556
object test extends Tests with OsLibTestModule {
5657
def platformSegment = "jvm"
@@ -107,7 +108,11 @@ trait OsLibModule extends CrossScalaModule with PublishModule{
107108
trait OsLibTestModule extends ScalaModule with TestModule{
108109
def ivyDeps = Agg(
109110
ivy"com.lihaoyi::utest::0.7.8",
110-
ivy"com.lihaoyi::sourcecode::0.2.5"
111+
ivy"com.lihaoyi::sourcecode::0.2.5",
112+
if (scalaVersion().startsWith("2.11"))
113+
ivy"org.scalacheck::scalacheck::1.15.2"
114+
else
115+
ivy"org.scalacheck::scalacheck::1.15.3"
111116
)
112117

113118
def platformSegment: String
@@ -116,7 +121,7 @@ trait OsLibTestModule extends ScalaModule with TestModule{
116121
millSourcePath / s"src-$platformSegment"
117122
)
118123

119-
def testFrameworks = Seq("utest.runner.Framework")
124+
def testFrameworks = Seq("utest.runner.Framework", "org.scalacheck.ScalaCheckFramework")
120125
// we check the textual output of system commands and expect it in english
121126
override def forkEnv: Target[Map[String, String]] = super.forkEnv() ++ Map("LC_ALL" -> "C")
122127
}

os/src/Internals.scala

+4
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@ object Internals{
2222
src,
2323
dest.write(_, 0, _)
2424
)
25+
26+
def linux() = {
27+
System.getProperty("os.name") == "Linux"
28+
}
2529
}

os/watch/src/inotify/Errno.scala

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package os.watch.inotify
2+
3+
object Errno {
4+
5+
val EAGAIN: Int = 11
6+
val ENOTDIR: Int = 20
7+
8+
}

os/watch/src/inotify/Event.scala

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package os.watch.inotify
2+
3+
import java.nio.ByteBuffer
4+
5+
case class Event(val wd: Int, val mask: Mask, val cookie: Int, val name: String) {
6+
7+
8+
9+
}
10+
11+
object Event {
12+
def apply(buf: ByteBuffer): Event = {
13+
val wd = buf.getInt
14+
val mask = buf.getInt
15+
val cookie = buf.getInt
16+
val len = buf.getInt
17+
val sb = new StringBuilder()
18+
19+
var i = 0
20+
21+
while (i < len) {
22+
val b = buf.get()
23+
if (b != 0) {
24+
sb.append(b.toChar)
25+
}
26+
i += 1
27+
}
28+
29+
Event(wd, Mask(mask), cookie, sb.toString)
30+
}
31+
}

os/watch/src/inotify/Mask.scala

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package os.watch.inotify
2+
3+
import scala.collection.mutable
4+
5+
case class Mask(mask: Int) {
6+
def |(rhs: Mask): Mask = Mask(mask|rhs.mask)
7+
def contains(rhs: Mask) : Boolean = (mask & rhs.mask) == rhs.mask
8+
9+
override def toString: String = {
10+
11+
val things = Mask.named_masks.toList.sortBy(_._1).flatMap { case(name,m) =>
12+
if (this.contains(m)) Seq(name) else Seq()
13+
}.mkString("+")
14+
15+
f"Mask($mask%08x = $things)"
16+
}
17+
}
18+
19+
object Mask {
20+
val named_masks : mutable.Map[String,Mask] = mutable.Map()
21+
22+
private def named(bit: Int)(implicit name: sourcecode.Name): Mask = {
23+
val a = Mask(1 << bit)
24+
named_masks.put(name.value,a)
25+
a
26+
}
27+
28+
val access: Mask = named(0)
29+
val modify: Mask = named(1)
30+
val attrib: Mask = named(2)
31+
val close_write: Mask = named(3)
32+
val close_nowrite: Mask = named(4)
33+
val open: Mask = named(5)
34+
val move_from: Mask = named(6)
35+
val move_to: Mask = named(7)
36+
val create: Mask = named(8)
37+
val delete: Mask = named(9)
38+
val delete_self: Mask = named(10)
39+
40+
val unmount: Mask = named(13)
41+
val overflow: Mask = named(14)
42+
val ignored: Mask = named(15)
43+
44+
val only_dir: Mask = named(24)
45+
val do_not_follow: Mask = named(25)
46+
val exclude_unlink: Mask = named(26)
47+
48+
val mask_create: Mask = named(28)
49+
val mask_add: Mask = named(29)
50+
val is_dir: Mask = named(30)
51+
val one_shot: Mask = named(31)
52+
53+
54+
val close : Mask = close_write | close_nowrite
55+
val move : Mask = move_from | move_to
56+
57+
val all_events = access | modify | attrib |
58+
close | open | move | create |
59+
delete | delete_self | unmount
60+
}

os/watch/src/inotify/Notify.scala

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package os.watch.inotify
2+
3+
import com.sun.jna.{LastErrorException, Library, Native}
4+
import geny.Generator
5+
6+
import java.nio.{ByteBuffer, ByteOrder}
7+
import java.util.concurrent.atomic.AtomicReference
8+
import scala.annotation.tailrec
9+
import scala.collection.mutable
10+
import scala.util.Try
11+
12+
trait Notify extends Library {
13+
//@throws[LastErrorException]
14+
def inotify_init() : Int;
15+
16+
//@throws[LastErrorException]
17+
def inotify_init1(flags: Int) : Int
18+
19+
//@throws[LastErrorException]
20+
def inotify_add_watch(fd: Int, path: String, mask: Int): Int
21+
22+
//@throws[LastErrorException]
23+
def inotify_rm_watch(fd: Int, wd: Int): Int
24+
25+
//@throws[LastErrorException]
26+
def read(fd: Int, buf: Array[Byte], count: Long): Long
27+
28+
//@throws[LastErrorException]
29+
def close(fd: Int): Int
30+
}
31+
32+
object Notify {
33+
val it : Notify = Native.load("c",classOf[Notify])
34+
35+
val O_NONBLOCK: Int = 1 << 11
36+
37+
38+
import Generator._
39+
40+
// convenience
41+
def add_watch(fd: Int, path : os.Path, actions: Mask) : Int = {
42+
it.inotify_add_watch(fd,path.toString,actions.mask)
43+
}
44+
45+
def events(buf: ByteBuffer): Generator[Event] = new Generator[Event]() {
46+
override def generate(handleItem: Event => Action): Action = {
47+
while (buf.hasRemaining) {
48+
if (handleItem(Event(buf)) == End) return End
49+
}
50+
Continue
51+
}
52+
}
53+
54+
def buffers(fd: => AtomicReference[Option[Int]]): Generator[ByteBuffer] = {
55+
new Generator[ByteBuffer] {
56+
override def generate(handleItem: ByteBuffer => Action): Action = {
57+
58+
@tailrec
59+
def loop(arr: Array[Byte]): Action = fd.get match {
60+
case Some(fd) =>
61+
it.read(fd, arr, arr.length) match {
62+
case 0 =>
63+
Continue
64+
case n if n < 0 =>
65+
val errno = Native.getLastError()
66+
if (errno == Errno.EAGAIN) {
67+
Thread.sleep(10)
68+
loop(arr)
69+
} else {
70+
throw new NotifyException(s"read error ${Native.getLastError()}, fd = $fd")
71+
}
72+
//throw new Exception (s"n = $n")
73+
case n =>
74+
val buf = ByteBuffer.wrap(arr, 0, n.toInt).order(ByteOrder.nativeOrder())
75+
if (handleItem(buf) == End) {
76+
End
77+
} else {
78+
loop(arr)
79+
}
80+
}
81+
case None =>
82+
End
83+
}
84+
85+
86+
loop(Array.fill[Byte](1000)(0))
87+
}
88+
}
89+
}
90+
91+
def events(fd: AtomicReference[Option[Int]]): Generator[Event] = for {
92+
b <- buffers(fd)
93+
e <- events(b)
94+
} yield e
95+
96+
97+
98+
99+
100+
}
101+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package os.watch.inotify
2+
3+
class NotifyException(msg: String) extends Exception(msg) {
4+
5+
}

0 commit comments

Comments
 (0)