├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle ├── buildViaTravis.sh └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main └── java │ ├── com │ └── barbarysoftware │ │ ├── jna │ │ ├── CFAllocatorRef.java │ │ ├── CFArrayRef.java │ │ ├── CFIndex.java │ │ ├── CFRunLoopRef.java │ │ ├── CFStringRef.java │ │ ├── CarbonAPI.java │ │ └── FSEventStreamRef.java │ │ └── watchservice │ │ ├── AbstractWatchKey.java │ │ ├── AbstractWatchService.java │ │ ├── MacOSXListeningWatchService.java │ │ ├── MacOSXWatchKey.java │ │ ├── MacOSXWatchServiceFactory.java │ │ └── WatchableFile.java │ └── rx │ └── fileutils │ ├── FileSystemEvent.java │ ├── FileSystemEventKind.java │ └── FileSystemWatcher.java └── test └── java └── rx └── fileutils └── FileSystemEventOnSubscribeTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | 27 | # OS generated files # 28 | ###################### 29 | .DS_Store* 30 | ehthumbs.db 31 | Icon? 32 | Thumbs.db 33 | 34 | # Editor Files # 35 | ################ 36 | *~ 37 | *.swp 38 | 39 | # Gradle Files # 40 | ################ 41 | .gradle 42 | .gradletasknamecache 43 | .m2 44 | 45 | # Build output directies 46 | target/ 47 | build/ 48 | 49 | # IntelliJ specific files/directories 50 | out 51 | .idea 52 | *.ipr 53 | *.iws 54 | *.iml 55 | atlassian-ide-plugin.xml 56 | 57 | # Eclipse specific files/directories 58 | .classpath 59 | .project 60 | .settings 61 | .metadata 62 | bin/ 63 | 64 | # NetBeans specific files/directories 65 | .nbattrs 66 | /.nb-gradle/profiles/private/ 67 | .nb-gradle-properties 68 | 69 | # Scala build 70 | *.cache 71 | /.nb-gradle/private/ 72 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | 5 | sudo: false 6 | # as per http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ 7 | 8 | # script for build and release via Travis to Bintray 9 | script: gradle/buildViaTravis.sh 10 | 11 | # cache between builds 12 | cache: 13 | directories: 14 | - $HOME/.m2 15 | - $HOME/.gradle 16 | 17 | env: 18 | global: 19 | - secure: 0tbIZRqFHChc+aQHWGPzmLUto/cHo/EZ3KHt+tRd+8Xfp+4zJw3Qu4qVrMgcE8HwGy3WedcL2lsMrZtEdZknA58HtuB8mmUMAISOeTtVZ2PxTgslCfPAO148nnWKAnkh3m6JT5Jm1hy1FmKwvf0LQOqKn+BNLya7ysuMaxBGRLd6TO8aRYksq/jO7YvHXYUX8nPlCcvG9L8RjzcbjKWy0XrwqfC+1xovV48QGVAJyDUWY27Q7BqLU6x2VBo24FZmtF/nlSHeVkf9IF7iWaNupRyfFnA8qawL1qH3w1VD+dzAEDgZHertdbgMGPg1Z2DWrL/wgILq0h0g86P0J+Thc0AikevaBxirzSaVRdMF+RI/J2fNPC5ysfOAdGSKc6jelx2TMdJZ0DU5/PyO90Sct5F1BY7xg9ooKs0CzCjo+U1OSprP4bMvxydk8hRwu1Cbrgww40gKqpS5Nk7WP2+y5TX//ECjs8u4jfAZbjf195lbGjgazUQwThtGh32OfEOtfrgLe9CmOD99WuYiG/1oy7S99Z4Rrcz+WWnQYIsiB8k/YcfNFj7C/RlK/pdZsFdmnBnHvELvxo6CYYMqrQjCK5OOJUGBJGkquDaUCiFj9cc6sLVgfeuLRUTOMkqqA4bUgdERipbr8tYy18xuul4PsvwKvxwdxK+hBThvAtzN7EA= 20 | - secure: mVQPulb4SjgTyYWqI6DHo/ptFk0zZ0a7C/6wPtgXq5DTgQUA/1wRi8iD/TAGiaxt1MVe4rtrFNXwxMVuLDlGgh+QdPExsP6VmOwKlwRAqxgPRRXaRIr0Gg3kEmbeh+S1JMBRs2wAXU7A0BluxsiQi3aLNO2YIZ88+Xv1ej4ypaGJYhZHxOSuTp+Hk9pRrKMBdewiQY5xXW9LFD6AhVozCYf73drQerN8sW0QHNSB/f1xw1ffGifpu8j7ljFJwTF0pEldO8z73WuTYuL/b5MlcpTxPbPFPbYHok2xRdyf9sFfmWW5UduVAK3St2J+JgoIEZF8Rzf3Iafd8Ly8SO3LMfr3IxGAhpwzWMmCs7ZENfap3Fj1Q3YUMOVDbEsz94eoLhwSK8By5iK7K2l2OFnzU+LkevRC+uYxzuu7qe5VIPR+B5VZnRFfnZGNS9oImC+htR/yhfYyseRDnzCW9V2hiWfFRk38b6jh1YcDVLw0KsH2xJ3Me9v9Ez2QQ2K7fj+yai67krRpkYgaSZIlwqgZZOlhTRKp0+i6Nzd2RmjAsgqjwAyKkAiu6uTwBPS28YomMptoy2di18VuoweOkn6p6A8et3mZdhoXNJAAzga8JAfkx+hijGTlyLnTg7UiYw6BhG4A9pq5zLhdfP9ohqD+F7JCOz2+9DBYd+6QwxiHQfE= 21 | - secure: BaJYAYQQfe2jB44HEspnWdcZ+BWJYnpD++AWoLpyFtSUxqKUQJ/jErmRSZsfRaX3203EwPqpcqDQnDWj8god1zOfuTBliJTuNHrKi2a+Ji5c+A6aBSUjJ4MWrytriTpafbZGhm+AOq82gV1egI5EIc4yHXlSzj61owBULpkIdAupxqL9LAcsj3lO5GnmARARrE7GdZyWwMz0w0K5s/8gPpagJj0lj/VASrOAsG/GOeKOx3EPyX/tMldo/ssUiflNsrwcVw+TjUZfk8D44mG2f8xINcn1MenMxeKPVrqRftDOEuWZnXs051RY8aAUxcbg/9AhIfRuTfXT+73aOQKWh+nT/2g66+CRvAQX2AGRMfiftW88JHwD37lW1klb5c4PSw8a/z/Udfb1zwXICg3lgXPhPbt6F67Y+vEdXm4WOFVq7NpNZ2i6n1+4CDjkninpQ73IZqNfqFWnrdNtXVBuY0PCN9IbDJAJEJIgNvfytcE5u78rs7Au94lYTffU7icjTZw90rBPmUWVRAXMmMSpZ2RJj4BkmjNI7awkXhAFJPO5Cf0dgg+cSoiDFhb0TS15eY7dD6DM/drOhocPNeYrgC+3NFeSkkOsdzoA7+Uzjv55qRL4bQ0AsaYRFhB5dj/91wAV4mQKpspomfIT8PqttMx37IfEK2XFAwW98EU+QpI= 22 | - secure: tvDRlGYwuXHjJV/AOOpR8BOIFXoicdr1DnXqtvAFLaUMHnnlgoiSne8ud6hxvoajUr00XdGakj9feDweRZoLVLj5HJznIWEQHMdb3eaoS8Cr063GGvPGrWqospOmLDQL2ZhMdlc2oeFzYNNdRNPGSkorC9m0iBq46+A4ouH/GxLjS0XeqrH6t63Vt6TzMZ5FnnI/rFWAVP0GF38l7oJGkjwxKvOvByWQ7+mjUs5coYfVZuHl65t/TODNy2X2Dv0s37Tz/8mxmGb4PwIoU2beScYXo3UHOm9R2v8XVAT4N6k3oKm4Iunr1BHBlnuqLosxZYz3FhdKNO6xj/PXQJfq+xapFArl2WmBcpmlStltBQEshNuFLMMyud+87T2oOV/umgPsRjbbSAxt9rpwz8RmQi7vkFSwTZSQazplRCZUkBdAlM6yNxSL4mJ0zfUR8A0ANqWXMYPniewYvPDOhc+BYS6eEYsPzBbPQ407LVH4/4cPuC7oe8OOZcW0i9k4j8YIMfqWhb0n/mUTO5+vYM2ZzjLxZO9xCHEqCIF8vIVLaevxERwuIWHK5skYOI+w9Y8TfIyfsFJs54H1A+dEjmFE657EIeUDlNzyYrZ6v58e3FPmvtsN1udigYDjWH03+FogoJll7duxHnErHe3tFeHKKFWQqMBmoKX+zB/Ej+jb/7o= 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to RxJava 2 | 3 | If you would like to contribute code you can do so through GitHub by forking the repository and sending a pull request (on a branch other than `master` or `gh-pages`). 4 | 5 | When submitting code, please make every effort to follow existing conventions and style in order to keep the code as readable as possible. 6 | 7 | ## License 8 | 9 | By contributing your code, you agree to license your contribution under the terms of the APLv2: https://github.com/ReactiveX/RxJava/blob/master/LICENSE 10 | 11 | All files are released with the Apache 2.0 license. 12 | 13 | If you are adding a new file it should have a header like this: 14 | 15 | ``` 16 | /** 17 | * Copyright 2015 Netflix, Inc. 18 | * 19 | * Licensed under the Apache License, Version 2.0 (the "License"); 20 | * you may not use this file except in compliance with the License. 21 | * You may obtain a copy of the License at 22 | * 23 | * http://www.apache.org/licenses/LICENSE-2.0 24 | * 25 | * Unless required by applicable law or agreed to in writing, software 26 | * distributed under the License is distributed on an "AS IS" BASIS, 27 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28 | * See the License for the specific language governing permissions and 29 | * limitations under the License. 30 | */ 31 | ``` 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2012 Netflix, Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RxJava FileUtils 2 | 3 | File utility operators for [RxJava](https://github.com/ReactiveX/RxJava). 4 | 5 | ## Master Build Status 6 | 7 | 8 | 9 | ## Communication 10 | 11 | - Google Group: [RxJava](http://groups.google.com/d/forum/rxjava) 12 | - Twitter: [@RxJava](http://twitter.com/RxJava) 13 | - [GitHub Issues](https://github.com/ReactiveX/RxJavaFileUtils/issues) 14 | 15 | ## Binaries 16 | 17 | Binaries and dependency information for Maven, Ivy, Gradle and others can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Cio.reactivex.rxjava-file-utils). 18 | 19 | Example for Gradle: 20 | 21 | ```groovy 22 | compile 'io.reactivex:rxjava-file-utils:x.y.z' 23 | ``` 24 | 25 | and for Maven: 26 | 27 | ```xml 28 | 29 | io.reactivex 30 | rxjava-file-utils 31 | x.y.z 32 | 33 | ``` 34 | and for Ivy: 35 | 36 | ```xml 37 | 38 | ``` 39 | 40 | Snapshots are available via [JFrog](https://oss.jfrog.org/webapp/search/artifact/?5&q=rxjava-file-utils): 41 | 42 | ```groovy 43 | repositories { 44 | maven { url 'https://oss.jfrog.org/libs-snapshot' } 45 | } 46 | 47 | dependencies { 48 | compile 'io.reactivex:rxjava-file-utils:x.y.z-SNAPSHOT' 49 | } 50 | ``` 51 | 52 | ## Build 53 | 54 | To build: 55 | 56 | ``` 57 | $ git clone git@github.com:ReactiveX/RxJavaFileUtils.git 58 | $ cd RxJavaFileUtils/ 59 | $ ./gradlew build 60 | ``` 61 | 62 | ## Bugs and Feedback 63 | 64 | For bugs, questions and discussions please use the [Github Issues](https://github.com/ReactiveX/RxJavaFileUtils/issues). 65 | 66 | 67 | ## LICENSE 68 | 69 | Licensed under the Apache License, Version 2.0 (the "License"); 70 | you may not use this file except in compliance with the License. 71 | You may obtain a copy of the License at 72 | 73 | 74 | 75 | Unless required by applicable law or agreed to in writing, software 76 | distributed under the License is distributed on an "AS IS" BASIS, 77 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 78 | See the License for the specific language governing permissions and 79 | limitations under the License. 80 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { jcenter() } 3 | dependencies { classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:4.0.0' } 4 | } 5 | 6 | description = 'File utilities using RxJava' 7 | 8 | apply plugin: 'nebula.rxjava-project' 9 | apply plugin: 'java' 10 | 11 | sourceCompatibility = JavaVersion.VERSION_1_8 12 | targetCompatibility = JavaVersion.VERSION_1_8 13 | 14 | dependencies { 15 | compile 'io.reactivex:rxjava:1.0.12' 16 | compile 'net.java.dev.jna:jna:3.2.2' 17 | 18 | testCompile 'junit:junit-dep:4.10' 19 | testCompile 'org.mockito:mockito-core:1.8.5' 20 | } 21 | 22 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | release.scope=patch 2 | -------------------------------------------------------------------------------- /gradle/buildViaTravis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script will build the project. 3 | 4 | if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then 5 | echo -e "Build Pull Request #$TRAVIS_PULL_REQUEST => Branch [$TRAVIS_BRANCH]" 6 | ./gradlew -Prelease.useLastTag=true build 7 | elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" == "" ]; then 8 | echo -e 'Build Branch with Snapshot => Branch ['$TRAVIS_BRANCH']' 9 | ./gradlew -Prelease.travisci=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" build snapshot --stacktrace 10 | elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then 11 | echo -e 'Build Branch for Release => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG']' 12 | ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" final --stacktrace 13 | else 14 | echo -e 'WARN: Should not be here => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG'] Pull Request ['$TRAVIS_PULL_REQUEST']' 15 | ./gradlew -Prelease.useLastTag=true build 16 | fi 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReactiveX/RxJavaFileUtils/3ee8d12fc2947eb5d7e65cfb13bfd6ea13be884e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Mar 02 10:17:40 PST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='rxjava-file-utils' 2 | -------------------------------------------------------------------------------- /src/main/java/com/barbarysoftware/jna/CFAllocatorRef.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from: https://github.com/gjoseph/BarbaryWatchService 3 | * Waiting to see what the license is - https://github.com/gjoseph/BarbaryWatchService/issues/6 4 | */ 5 | package com.barbarysoftware.jna; 6 | 7 | import com.sun.jna.ptr.PointerByReference; 8 | 9 | public class CFAllocatorRef extends PointerByReference { 10 | 11 | } -------------------------------------------------------------------------------- /src/main/java/com/barbarysoftware/jna/CFArrayRef.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from: https://github.com/gjoseph/BarbaryWatchService 3 | * Waiting to see what the license is - https://github.com/gjoseph/BarbaryWatchService/issues/6 4 | */ 5 | package com.barbarysoftware.jna; 6 | 7 | import com.sun.jna.ptr.PointerByReference; 8 | 9 | public class CFArrayRef extends PointerByReference { 10 | 11 | } -------------------------------------------------------------------------------- /src/main/java/com/barbarysoftware/jna/CFIndex.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from: https://github.com/gjoseph/BarbaryWatchService 3 | * Waiting to see what the license is - https://github.com/gjoseph/BarbaryWatchService/issues/6 4 | */ 5 | package com.barbarysoftware.jna; 6 | 7 | import com.sun.jna.NativeLong; 8 | 9 | public class CFIndex extends NativeLong { 10 | private static final long serialVersionUID = 0; 11 | 12 | public static CFIndex valueOf(int i) { 13 | CFIndex idx = new CFIndex(); 14 | idx.setValue(i); 15 | return idx; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/barbarysoftware/jna/CFRunLoopRef.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from: https://github.com/gjoseph/BarbaryWatchService 3 | * Waiting to see what the license is - https://github.com/gjoseph/BarbaryWatchService/issues/6 4 | */ 5 | package com.barbarysoftware.jna; 6 | 7 | import com.sun.jna.ptr.PointerByReference; 8 | 9 | public class CFRunLoopRef extends PointerByReference { 10 | 11 | } -------------------------------------------------------------------------------- /src/main/java/com/barbarysoftware/jna/CFStringRef.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from: https://github.com/gjoseph/BarbaryWatchService 3 | * Waiting to see what the license is - https://github.com/gjoseph/BarbaryWatchService/issues/6 4 | */ 5 | package com.barbarysoftware.jna; 6 | 7 | import com.sun.jna.ptr.PointerByReference; 8 | 9 | public class CFStringRef extends PointerByReference { 10 | 11 | public static CFStringRef toCFString(String s) { 12 | final char[] chars = s.toCharArray(); 13 | int length = chars.length; 14 | return CarbonAPI.INSTANCE.CFStringCreateWithCharacters(null, chars, CFIndex.valueOf(length)); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/barbarysoftware/jna/CarbonAPI.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from: https://github.com/gjoseph/BarbaryWatchService 3 | * Waiting to see what the license is - https://github.com/gjoseph/BarbaryWatchService/issues/6 4 | */ 5 | package com.barbarysoftware.jna; 6 | 7 | import com.sun.jna.*; 8 | 9 | public interface CarbonAPI extends Library { 10 | CarbonAPI INSTANCE = (CarbonAPI) Native.loadLibrary("Carbon", CarbonAPI.class); 11 | 12 | CFArrayRef CFArrayCreate( 13 | CFAllocatorRef allocator, // always set to Pointer.NULL 14 | Pointer[] values, 15 | CFIndex numValues, 16 | Void callBacks // always set to Pointer.NULL 17 | ); 18 | 19 | CFStringRef CFStringCreateWithCharacters( 20 | Void alloc, // always pass NULL 21 | char[] chars, 22 | CFIndex numChars 23 | ); 24 | 25 | public FSEventStreamRef FSEventStreamCreate( 26 | Pointer v, // always use Pointer.NULL 27 | FSEventStreamCallback callback, 28 | Pointer context, // always use Pointer.NULL 29 | CFArrayRef pathsToWatch, 30 | long sinceWhen, // use -1 for events since now 31 | double latency, // in seconds 32 | int flags // 0 is good for now 33 | 34 | ); 35 | 36 | boolean FSEventStreamStart(FSEventStreamRef streamRef); 37 | 38 | void FSEventStreamStop(FSEventStreamRef streamRef); 39 | 40 | void FSEventStreamScheduleWithRunLoop(FSEventStreamRef streamRef, CFRunLoopRef runLoop, CFStringRef runLoopMode); 41 | 42 | CFRunLoopRef CFRunLoopGetCurrent(); 43 | 44 | void CFRunLoopRun(); 45 | 46 | void CFRunLoopStop(CFRunLoopRef rl); 47 | 48 | public interface FSEventStreamCallback extends Callback { 49 | @SuppressWarnings({"UnusedDeclaration"}) 50 | void invoke(FSEventStreamRef streamRef, Pointer clientCallBackInfo, NativeLong numEvents, Pointer eventPaths, Pointer eventFlags, Pointer eventIds); 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/barbarysoftware/jna/FSEventStreamRef.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from: https://github.com/gjoseph/BarbaryWatchService 3 | * Waiting to see what the license is - https://github.com/gjoseph/BarbaryWatchService/issues/6 4 | */ 5 | package com.barbarysoftware.jna; 6 | 7 | import com.sun.jna.ptr.PointerByReference; 8 | 9 | public class FSEventStreamRef extends PointerByReference { 10 | 11 | } -------------------------------------------------------------------------------- /src/main/java/com/barbarysoftware/watchservice/AbstractWatchKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2009 Sun Microsystems, Inc. All Rights Reserved. 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 | * 5 | * This code is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License version 2 only, as 7 | * published by the Free Software Foundation. Sun designates this 8 | * particular file as subject to the "Classpath" exception as provided 9 | * by Sun in the LICENSE file that accompanied this code. 10 | * 11 | * This code is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 | * version 2 for more details (a copy is included in the LICENSE file that 15 | * accompanied this code). 16 | * 17 | * You should have received a copy of the GNU General Public License version 18 | * 2 along with this work; if not, write to the Free Software Foundation, 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 | * 21 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 22 | * CA 95054 USA or visit www.sun.com if you need additional information or 23 | * have any questions. 24 | */ 25 | 26 | package com.barbarysoftware.watchservice; 27 | 28 | import java.nio.file.WatchEvent; 29 | import java.nio.file.WatchKey; 30 | import java.util.ArrayList; 31 | import java.util.List; 32 | 33 | import static java.nio.file.StandardWatchEventKinds.OVERFLOW; 34 | 35 | /** 36 | * Base implementation class for watch keys. 37 | */ 38 | 39 | abstract class AbstractWatchKey implements WatchKey { 40 | 41 | /** 42 | * Maximum size of event list (in the future this may be tunable) 43 | */ 44 | static final int MAX_EVENT_LIST_SIZE = 512; 45 | 46 | /** 47 | * Special event to signal overflow 48 | */ 49 | static final Event OVERFLOW_EVENT = 50 | new Event<>(OVERFLOW, null); 51 | 52 | /** 53 | * Possible key states 54 | */ 55 | private static enum State { 56 | READY, SIGNALLED 57 | } 58 | 59 | // reference to watcher 60 | private final AbstractWatchService watcher; 61 | 62 | // key state 63 | private State state; 64 | 65 | // pending events 66 | private List> events; 67 | 68 | protected AbstractWatchKey(AbstractWatchService watcher) { 69 | this.watcher = watcher; 70 | this.state = State.READY; 71 | this.events = new ArrayList>(); 72 | } 73 | 74 | final AbstractWatchService watcher() { 75 | return watcher; 76 | } 77 | 78 | /** 79 | * Enqueues this key to the watch service 80 | */ 81 | final void signal() { 82 | synchronized (this) { 83 | if (state == State.READY) { 84 | state = State.SIGNALLED; 85 | watcher.enqueueKey(this); 86 | } 87 | } 88 | } 89 | 90 | /** 91 | * Adds the event to this key and signals it. 92 | * @param kind event kind 93 | * @param context context 94 | */ 95 | @SuppressWarnings("unchecked") 96 | final void signalEvent(WatchEvent.Kind kind, Object context) { 97 | synchronized (this) { 98 | int size = events.size(); 99 | if (size > 1) { 100 | // don't let list get too big 101 | if (size >= MAX_EVENT_LIST_SIZE) { 102 | kind = OVERFLOW; 103 | context = null; 104 | } 105 | 106 | // repeated event 107 | WatchEvent prev = events.get(size - 1); 108 | if (kind == prev.kind()) { 109 | boolean isRepeat; 110 | if (context == null) { 111 | isRepeat = (prev.context() == null); 112 | } else { 113 | isRepeat = context.equals(prev.context()); 114 | } 115 | if (isRepeat) { 116 | ((Event) prev).increment(); 117 | return; 118 | } 119 | } 120 | } 121 | 122 | // non-repeated event 123 | events.add(new Event((WatchEvent.Kind) kind, context)); 124 | signal(); 125 | } 126 | } 127 | 128 | @Override 129 | public final List> pollEvents() { 130 | synchronized (this) { 131 | List> result = events; 132 | events = new ArrayList>(); 133 | return result; 134 | } 135 | } 136 | 137 | @Override 138 | public final boolean reset() { 139 | synchronized (this) { 140 | if (state == State.SIGNALLED && isValid()) { 141 | if (events.isEmpty()) { 142 | state = State.READY; 143 | } else { 144 | // pending events so re-queue key 145 | watcher.enqueueKey(this); 146 | } 147 | } 148 | return isValid(); 149 | } 150 | } 151 | 152 | /** 153 | * WatchEvent implementation 154 | */ 155 | private static class Event implements WatchEvent { 156 | private final WatchEvent.Kind kind; 157 | private final T context; 158 | 159 | // synchronize on watch key to access/increment count 160 | private int count; 161 | 162 | Event(WatchEvent.Kind type, T context) { 163 | this.kind = type; 164 | this.context = context; 165 | this.count = 1; 166 | } 167 | 168 | @Override 169 | public WatchEvent.Kind kind() { 170 | return kind; 171 | } 172 | 173 | @Override 174 | public T context() { 175 | return context; 176 | } 177 | 178 | @Override 179 | public int count() { 180 | return count; 181 | } 182 | 183 | // for repeated events 184 | void increment() { 185 | count++; 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/com/barbarysoftware/watchservice/AbstractWatchService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2008-2009 Sun Microsystems, Inc. All Rights Reserved. 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 | * 5 | * This code is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License version 2 only, as 7 | * published by the Free Software Foundation. Sun designates this 8 | * particular file as subject to the "Classpath" exception as provided 9 | * by Sun in the LICENSE file that accompanied this code. 10 | * 11 | * This code is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 | * version 2 for more details (a copy is included in the LICENSE file that 15 | * accompanied this code). 16 | * 17 | * You should have received a copy of the GNU General Public License version 18 | * 2 along with this work; if not, write to the Free Software Foundation, 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 | * 21 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 22 | * CA 95054 USA or visit www.sun.com if you need additional information or 23 | * have any questions. 24 | */ 25 | 26 | package com.barbarysoftware.watchservice; 27 | 28 | import java.io.IOException; 29 | import java.nio.file.ClosedWatchServiceException; 30 | import java.nio.file.WatchEvent; 31 | import java.nio.file.WatchKey; 32 | import java.nio.file.WatchService; 33 | import java.nio.file.Watchable; 34 | import java.util.concurrent.LinkedBlockingDeque; 35 | import java.util.concurrent.TimeUnit; 36 | 37 | /** 38 | * Base implementation class for watch services. 39 | */ 40 | 41 | abstract class AbstractWatchService implements WatchService { 42 | 43 | // signaled keys waiting to be dequeued 44 | private final LinkedBlockingDeque pendingKeys = 45 | new LinkedBlockingDeque(); 46 | 47 | // special key to indicate that watch service is closed 48 | private final WatchKey CLOSE_KEY = new AbstractWatchKey(null) { 49 | @Override 50 | public boolean isValid() { 51 | return true; 52 | } 53 | 54 | @Override 55 | public void cancel() { 56 | } 57 | 58 | @Override 59 | public Watchable watchable() { 60 | return null; 61 | } 62 | }; 63 | 64 | // used when closing watch service 65 | private volatile boolean closed; 66 | private final Object closeLock = new Object(); 67 | 68 | protected AbstractWatchService() { 69 | } 70 | 71 | /** 72 | * Register the given object with this watch service 73 | */ 74 | abstract WatchKey register(WatchableFile watchableFile, 75 | WatchEvent.Kind[] events, 76 | WatchEvent.Modifier... modifers) 77 | throws IOException; 78 | 79 | // used by AbstractWatchKey to enqueue key 80 | final void enqueueKey(WatchKey key) { 81 | pendingKeys.offer(key); 82 | } 83 | 84 | /** 85 | * Throws ClosedWatchServiceException if watch service is closed 86 | */ 87 | private void checkOpen() { 88 | if (closed) 89 | throw new ClosedWatchServiceException(); 90 | } 91 | 92 | /** 93 | * Checks the key isn't the special CLOSE_KEY used to unblock threads when 94 | * the watch service is closed. 95 | */ 96 | private void checkKey(WatchKey key) { 97 | if (key == CLOSE_KEY) { 98 | // re-queue in case there are other threads blocked in take/poll 99 | enqueueKey(key); 100 | } 101 | checkOpen(); 102 | } 103 | 104 | @Override 105 | public final WatchKey poll() { 106 | checkOpen(); 107 | WatchKey key = pendingKeys.poll(); 108 | checkKey(key); 109 | return key; 110 | } 111 | 112 | @Override 113 | public final WatchKey poll(long timeout, TimeUnit unit) 114 | throws InterruptedException { 115 | checkOpen(); 116 | WatchKey key = pendingKeys.poll(timeout, unit); 117 | checkKey(key); 118 | return key; 119 | } 120 | 121 | @Override 122 | public final WatchKey take() 123 | throws InterruptedException { 124 | checkOpen(); 125 | WatchKey key = pendingKeys.take(); 126 | checkKey(key); 127 | return key; 128 | } 129 | 130 | /** 131 | * Tells whether or not this watch service is open. 132 | */ 133 | final boolean isOpen() { 134 | return !closed; 135 | } 136 | 137 | /** 138 | * Retrieves the object upon which the close method synchronizes. 139 | */ 140 | final Object closeLock() { 141 | return closeLock; 142 | } 143 | 144 | /** 145 | * Closes this watch service. This method is invoked by the close 146 | * method to perform the actual work of closing the watch service. 147 | */ 148 | abstract void implClose() throws IOException; 149 | 150 | @Override 151 | public final void close() 152 | throws IOException { 153 | synchronized (closeLock) { 154 | // nothing to do if already closed 155 | if (closed) 156 | return; 157 | closed = true; 158 | 159 | implClose(); 160 | 161 | // clear pending keys and queue special key to ensure that any 162 | // threads blocked in take/poll wakeup 163 | pendingKeys.clear(); 164 | pendingKeys.offer(CLOSE_KEY); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/com/barbarysoftware/watchservice/MacOSXListeningWatchService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from: https://github.com/gjoseph/BarbaryWatchService 3 | * Waiting to see what the license is - https://github.com/gjoseph/BarbaryWatchService/issues/6 4 | */ 5 | package com.barbarysoftware.watchservice; 6 | 7 | import com.barbarysoftware.jna.CFArrayRef; 8 | import com.barbarysoftware.jna.CFIndex; 9 | import com.barbarysoftware.jna.CFRunLoopRef; 10 | import com.barbarysoftware.jna.CFStringRef; 11 | import com.barbarysoftware.jna.CarbonAPI; 12 | import com.barbarysoftware.jna.FSEventStreamRef; 13 | import com.sun.jna.NativeLong; 14 | import com.sun.jna.Pointer; 15 | 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.nio.file.WatchEvent; 19 | import java.nio.file.WatchKey; 20 | import java.util.ArrayList; 21 | import java.util.HashSet; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Set; 25 | import java.util.concurrent.ConcurrentHashMap; 26 | 27 | import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; 28 | import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; 29 | import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; 30 | 31 | /** 32 | * This class contains the bulk of my implementation of the Watch Service API. It hooks into Carbon's 33 | * File System Events API. 34 | * 35 | * @author Steve McLeod 36 | */ 37 | class MacOSXListeningWatchService extends AbstractWatchService { 38 | 39 | // need to keep reference to callbacks to prevent garbage collection 40 | @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"}) 41 | private final List callbackList = new ArrayList(); 42 | private final List threadList = new ArrayList(); 43 | 44 | @Override 45 | WatchKey register(WatchableFile watchableFile, WatchEvent.Kind[] events, WatchEvent.Modifier... modifers) throws IOException { 46 | final File file = watchableFile.toFile(); 47 | final Map lastModifiedMap = createLastModifiedMap(file); 48 | final String s = file.getAbsolutePath(); 49 | final Pointer[] values = {CFStringRef.toCFString(s).getPointer()}; 50 | final CFArrayRef pathsToWatch = CarbonAPI.INSTANCE.CFArrayCreate(null, values, CFIndex.valueOf(1), null); 51 | final MacOSXWatchKey watchKey = new MacOSXWatchKey(watchableFile, this, events); 52 | 53 | final double latency = 1.0; /* Latency in seconds */ 54 | 55 | final long kFSEventStreamEventIdSinceNow = -1; // this is 0xFFFFFFFFFFFFFFFF 56 | final int kFSEventStreamCreateFlagNoDefer = 0x00000002; 57 | final CarbonAPI.FSEventStreamCallback callback = new MacOSXListeningCallback(watchKey, lastModifiedMap); 58 | callbackList.add(callback); 59 | final FSEventStreamRef stream = CarbonAPI.INSTANCE.FSEventStreamCreate( 60 | Pointer.NULL, 61 | callback, 62 | Pointer.NULL, 63 | pathsToWatch, 64 | kFSEventStreamEventIdSinceNow, 65 | latency, 66 | kFSEventStreamCreateFlagNoDefer); 67 | 68 | final CFRunLoopThread thread = new CFRunLoopThread(stream, file); 69 | thread.setDaemon(true); 70 | thread.start(); 71 | threadList.add(thread); 72 | return watchKey; 73 | } 74 | 75 | public static class CFRunLoopThread extends Thread { 76 | 77 | private final FSEventStreamRef streamRef; 78 | private CFRunLoopRef runLoop; 79 | 80 | public CFRunLoopThread(FSEventStreamRef streamRef, File file) { 81 | super("WatchService for " + file); 82 | this.streamRef = streamRef; 83 | } 84 | 85 | @Override 86 | public void run() { 87 | runLoop = CarbonAPI.INSTANCE.CFRunLoopGetCurrent(); 88 | final CFStringRef runLoopMode = CFStringRef.toCFString("kCFRunLoopDefaultMode"); 89 | CarbonAPI.INSTANCE.FSEventStreamScheduleWithRunLoop(streamRef, runLoop, runLoopMode); 90 | CarbonAPI.INSTANCE.FSEventStreamStart(streamRef); 91 | CarbonAPI.INSTANCE.CFRunLoopRun(); 92 | } 93 | 94 | public CFRunLoopRef getRunLoop() { 95 | return runLoop; 96 | } 97 | 98 | public FSEventStreamRef getStreamRef() { 99 | return streamRef; 100 | } 101 | } 102 | 103 | private Map createLastModifiedMap(File file) { 104 | Map lastModifiedMap = new ConcurrentHashMap(); 105 | for (File child : recursiveListFiles(file)) { 106 | lastModifiedMap.put(child, child.lastModified()); 107 | } 108 | return lastModifiedMap; 109 | } 110 | 111 | private static Set recursiveListFiles(File file) { 112 | Set files = new HashSet(); 113 | if (file != null) { 114 | files.add(file); 115 | if (file.isDirectory()) { 116 | for (File child : file.listFiles()) { 117 | files.addAll(recursiveListFiles(child)); 118 | } 119 | } 120 | } 121 | return files; 122 | } 123 | 124 | @Override 125 | void implClose() throws IOException { 126 | for (CFRunLoopThread thread : threadList) { 127 | CarbonAPI.INSTANCE.CFRunLoopStop(thread.getRunLoop()); 128 | CarbonAPI.INSTANCE.FSEventStreamStop(thread.getStreamRef()); 129 | } 130 | threadList.clear(); 131 | callbackList.clear(); 132 | } 133 | 134 | 135 | private static class MacOSXListeningCallback implements CarbonAPI.FSEventStreamCallback { 136 | private final MacOSXWatchKey watchKey; 137 | private final Map lastModifiedMap; 138 | 139 | private MacOSXListeningCallback(MacOSXWatchKey watchKey, Map lastModifiedMap) { 140 | this.watchKey = watchKey; 141 | this.lastModifiedMap = lastModifiedMap; 142 | } 143 | 144 | public void invoke(FSEventStreamRef streamRef, Pointer clientCallBackInfo, NativeLong numEvents, Pointer eventPaths, Pointer /* array of unsigned int */ eventFlags, /* array of unsigned long */ Pointer eventIds) { 145 | final int length = numEvents.intValue(); 146 | 147 | for (String folderName : eventPaths.getStringArray(0, length)) { 148 | final Set filesOnDisk = recursiveListFiles(new File(folderName)); 149 | 150 | final List createdFiles = findCreatedFiles(filesOnDisk); 151 | final List modifiedFiles = findModifiedFiles(filesOnDisk); 152 | final List deletedFiles = findDeletedFiles(folderName, filesOnDisk); 153 | 154 | for (File file : createdFiles) { 155 | if (watchKey.isReportCreateEvents()) { 156 | watchKey.signalEvent(ENTRY_CREATE, file.toPath()); 157 | } 158 | lastModifiedMap.put(file, file.lastModified()); 159 | } 160 | 161 | for (File file : modifiedFiles) { 162 | if (watchKey.isReportModifyEvents()) { 163 | watchKey.signalEvent(ENTRY_MODIFY, file.toPath()); 164 | } 165 | lastModifiedMap.put(file, file.lastModified()); 166 | } 167 | 168 | for (File file : deletedFiles) { 169 | if (watchKey.isReportDeleteEvents()) { 170 | watchKey.signalEvent(ENTRY_DELETE, file.toPath()); 171 | } 172 | lastModifiedMap.remove(file); 173 | } 174 | } 175 | } 176 | 177 | private List findModifiedFiles(Set filesOnDisk) { 178 | List modifiedFileList = new ArrayList(); 179 | for (File file : filesOnDisk) { 180 | final Long lastModified = lastModifiedMap.get(file); 181 | if (lastModified != null && lastModified != file.lastModified()) { 182 | modifiedFileList.add(file); 183 | } 184 | } 185 | return modifiedFileList; 186 | } 187 | 188 | private List findCreatedFiles(Set filesOnDisk) { 189 | List createdFileList = new ArrayList(); 190 | for (File file : filesOnDisk) { 191 | if (!lastModifiedMap.containsKey(file)) { 192 | createdFileList.add(file); 193 | } 194 | } 195 | return createdFileList; 196 | } 197 | 198 | private List findDeletedFiles(String folderName, Set filesOnDisk) { 199 | List deletedFileList = new ArrayList(); 200 | for (File file : lastModifiedMap.keySet()) { 201 | if (file.getAbsolutePath().startsWith(folderName) && !filesOnDisk.contains(file)) { 202 | deletedFileList.add(file); 203 | } 204 | } 205 | return deletedFileList; 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/main/java/com/barbarysoftware/watchservice/MacOSXWatchKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from: https://github.com/gjoseph/BarbaryWatchService 3 | * Waiting to see what the license is - https://github.com/gjoseph/BarbaryWatchService/issues/6 4 | */ 5 | package com.barbarysoftware.watchservice; 6 | 7 | import java.nio.file.Path; 8 | import java.nio.file.StandardWatchEventKinds; 9 | import java.nio.file.WatchEvent; 10 | import java.nio.file.Watchable; 11 | import java.util.concurrent.atomic.AtomicBoolean; 12 | 13 | class MacOSXWatchKey extends AbstractWatchKey { 14 | private final AtomicBoolean cancelled = new AtomicBoolean(false); 15 | private final boolean reportCreateEvents; 16 | private final boolean reportModifyEvents; 17 | private final boolean reportDeleteEvents; 18 | private final Path path; 19 | 20 | public MacOSXWatchKey(Path path, AbstractWatchService macOSXWatchService, WatchEvent.Kind[] events) { 21 | super(macOSXWatchService); 22 | this.path = path; 23 | boolean reportCreateEvents = false; 24 | boolean reportModifyEvents = false; 25 | boolean reportDeleteEvents = false; 26 | 27 | for (WatchEvent.Kind event : events) { 28 | if (event == StandardWatchEventKinds.ENTRY_CREATE) { 29 | reportCreateEvents = true; 30 | } else if (event == StandardWatchEventKinds.ENTRY_MODIFY) { 31 | reportModifyEvents = true; 32 | } else if (event == StandardWatchEventKinds.ENTRY_DELETE) { 33 | reportDeleteEvents = true; 34 | } 35 | } 36 | this.reportCreateEvents = reportCreateEvents; 37 | this.reportDeleteEvents = reportDeleteEvents; 38 | this.reportModifyEvents = reportModifyEvents; 39 | } 40 | 41 | public boolean isValid() { 42 | return !cancelled.get() && watcher().isOpen(); 43 | } 44 | 45 | public void cancel() { 46 | cancelled.set(true); 47 | } 48 | 49 | @Override 50 | public Watchable watchable() { 51 | return path; 52 | } 53 | 54 | public boolean isReportCreateEvents() { 55 | return reportCreateEvents; 56 | } 57 | 58 | public boolean isReportModifyEvents() { 59 | return reportModifyEvents; 60 | } 61 | 62 | public boolean isReportDeleteEvents() { 63 | return reportDeleteEvents; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/barbarysoftware/watchservice/MacOSXWatchServiceFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from: https://github.com/gjoseph/BarbaryWatchService 3 | * Waiting to see what the license is - https://github.com/gjoseph/BarbaryWatchService/issues/6 4 | */ 5 | package com.barbarysoftware.watchservice; 6 | 7 | import java.nio.file.WatchService; 8 | 9 | public final class MacOSXWatchServiceFactory { 10 | private MacOSXWatchServiceFactory() {} 11 | 12 | public static WatchService newWatchService() { 13 | return new MacOSXListeningWatchService(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/barbarysoftware/watchservice/WatchableFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Forked from: https://github.com/gjoseph/BarbaryWatchService 3 | * Waiting to see what the license is - https://github.com/gjoseph/BarbaryWatchService/issues/6 4 | */ 5 | package com.barbarysoftware.watchservice; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.net.URI; 10 | import java.nio.file.FileSystem; 11 | import java.nio.file.LinkOption; 12 | import java.nio.file.Path; 13 | import java.nio.file.ProviderMismatchException; 14 | import java.nio.file.WatchEvent; 15 | import java.nio.file.WatchKey; 16 | import java.nio.file.WatchService; 17 | import java.util.Iterator; 18 | import java.util.Spliterator; 19 | import java.util.function.Consumer; 20 | 21 | public class WatchableFile implements Path { 22 | 23 | private final Path file; 24 | 25 | public WatchableFile(Path file) { 26 | if (file == null) { 27 | throw new NullPointerException("file must not be null"); 28 | } 29 | this.file = file; 30 | } 31 | 32 | @Override 33 | public WatchKey register(WatchService watcher, 34 | WatchEvent.Kind[] events, 35 | WatchEvent.Modifier... modifiers) 36 | throws IOException { 37 | if (watcher == null) 38 | throw new NullPointerException(); 39 | if (!(watcher instanceof AbstractWatchService)) 40 | throw new ProviderMismatchException(); 41 | return ((AbstractWatchService) watcher).register(this, events, modifiers); 42 | } 43 | 44 | private static final WatchEvent.Modifier[] NO_MODIFIERS = new WatchEvent.Modifier[0]; 45 | 46 | @Override 47 | public final WatchKey register(WatchService watcher, 48 | WatchEvent.Kind... events) 49 | throws IOException { 50 | return register(watcher, events, NO_MODIFIERS); 51 | } 52 | 53 | @Override 54 | public Iterator iterator() { 55 | return file.iterator(); 56 | } 57 | 58 | @Override 59 | public int compareTo(Path other) { 60 | return file.compareTo(other); 61 | } 62 | 63 | @Override 64 | public boolean equals(Object other) { 65 | return file.equals(other); 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | return file.hashCode(); 71 | } 72 | 73 | @Override 74 | public void forEach(Consumer action) { 75 | file.forEach(action); 76 | } 77 | 78 | @Override 79 | public Spliterator spliterator() { 80 | return file.spliterator(); 81 | } 82 | 83 | @Override 84 | public FileSystem getFileSystem() { 85 | return file.getFileSystem(); 86 | } 87 | 88 | @Override 89 | public boolean isAbsolute() { 90 | return file.isAbsolute(); 91 | } 92 | 93 | @Override 94 | public Path getRoot() { 95 | return file.getRoot(); 96 | } 97 | 98 | @Override 99 | public Path getFileName() { 100 | return file.getFileName(); 101 | } 102 | 103 | @Override 104 | public Path getParent() { 105 | return file.getParent(); 106 | } 107 | 108 | @Override 109 | public int getNameCount() { 110 | return file.getNameCount(); 111 | } 112 | 113 | @Override 114 | public Path getName(int index) { 115 | return file.getName(index); 116 | } 117 | 118 | @Override 119 | public Path subpath(int beginIndex, int endIndex) { 120 | return file.subpath(beginIndex, endIndex); 121 | } 122 | 123 | @Override 124 | public boolean startsWith(Path other) { 125 | return file.startsWith(other); 126 | } 127 | 128 | @Override 129 | public boolean startsWith(String other) { 130 | return file.startsWith(other); 131 | } 132 | 133 | @Override 134 | public boolean endsWith(Path other) { 135 | return file.endsWith(other); 136 | } 137 | 138 | @Override 139 | public boolean endsWith(String other) { 140 | return file.endsWith(other); 141 | } 142 | 143 | @Override 144 | public Path normalize() { 145 | return file.normalize(); 146 | } 147 | 148 | @Override 149 | public Path resolve(Path other) { 150 | return file.resolve(other); 151 | } 152 | 153 | @Override 154 | public Path resolve(String other) { 155 | return file.resolve(other); 156 | } 157 | 158 | @Override 159 | public Path resolveSibling(Path other) { 160 | return file.resolveSibling(other); 161 | } 162 | 163 | @Override 164 | public Path resolveSibling(String other) { 165 | return file.resolveSibling(other); 166 | } 167 | 168 | @Override 169 | public Path relativize(Path other) { 170 | return file.relativize(other); 171 | } 172 | 173 | @Override 174 | public URI toUri() { 175 | return file.toUri(); 176 | } 177 | 178 | @Override 179 | public Path toAbsolutePath() { 180 | return file.toAbsolutePath(); 181 | } 182 | 183 | @Override 184 | public Path toRealPath(LinkOption... options) throws IOException { 185 | return file.toRealPath(options); 186 | } 187 | 188 | @Override 189 | public File toFile() { 190 | return file.toFile(); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/rx/fileutils/FileSystemEvent.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in 5 | * compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See 11 | * the License for the specific language governing permissions and limitations under the License. 12 | */ 13 | package rx.fileutils; 14 | 15 | import java.nio.file.Path; 16 | import java.nio.file.StandardWatchEventKinds; 17 | import java.nio.file.WatchEvent; 18 | 19 | public class FileSystemEvent { 20 | private FileSystemEventKind fileSystemEventKind; 21 | 22 | private Path path; 23 | 24 | FileSystemEvent(WatchEvent event) { 25 | WatchEvent.Kind kind = event.kind(); 26 | if (StandardWatchEventKinds.OVERFLOW != kind) { 27 | this.path = (Path) event.context(); 28 | } 29 | 30 | this.fileSystemEventKind = FileSystemEventKind.toFileSystemEventKind(kind); 31 | } 32 | 33 | public FileSystemEventKind getFileSystemEventKind() { 34 | return fileSystemEventKind; 35 | } 36 | 37 | public Path getPath() { 38 | return path; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/rx/fileutils/FileSystemEventKind.java: -------------------------------------------------------------------------------- 1 | package rx.fileutils; 2 | 3 | import java.nio.file.StandardWatchEventKinds; 4 | import java.nio.file.WatchEvent; 5 | import java.util.Arrays; 6 | import java.util.stream.Collectors; 7 | 8 | /** 9 | * Created by rroeser on 7/17/15. 10 | */ 11 | public enum FileSystemEventKind { 12 | OVERFLOW, 13 | ENTRY_CREATE, 14 | ENTRY_DELETE, 15 | ENTRY_MODIFY; 16 | 17 | static WatchEvent.Kind toWatchEventKind(FileSystemEventKind fileSystemEventKind) { 18 | WatchEvent.Kind kind = null; 19 | 20 | switch (fileSystemEventKind) { 21 | case OVERFLOW: 22 | kind = StandardWatchEventKinds.OVERFLOW; 23 | break; 24 | case ENTRY_CREATE: 25 | kind = StandardWatchEventKinds.ENTRY_CREATE; 26 | break; 27 | case ENTRY_DELETE: 28 | kind = StandardWatchEventKinds.ENTRY_DELETE; 29 | break; 30 | case ENTRY_MODIFY: 31 | kind = StandardWatchEventKinds.ENTRY_MODIFY; 32 | break; 33 | } 34 | 35 | return kind; 36 | } 37 | 38 | static WatchEvent.Kind[] toWatchEventKinds(FileSystemEventKind... fileSystemEventKinds) { 39 | return Arrays 40 | .asList(fileSystemEventKinds) 41 | .stream() 42 | .map(FileSystemEventKind::toWatchEventKind) 43 | .collect(Collectors.toList()) 44 | .toArray(new WatchEvent.Kind[fileSystemEventKinds.length]); 45 | } 46 | 47 | static FileSystemEventKind toFileSystemEventKind(WatchEvent.Kind watchEventKind) { 48 | FileSystemEventKind kind = null; 49 | 50 | if (watchEventKind == StandardWatchEventKinds.OVERFLOW) { 51 | kind = OVERFLOW; 52 | } else if (watchEventKind == StandardWatchEventKinds.ENTRY_CREATE) { 53 | kind = ENTRY_CREATE; 54 | } else if (watchEventKind == StandardWatchEventKinds.ENTRY_DELETE) { 55 | kind = ENTRY_DELETE; 56 | } else if (watchEventKind == StandardWatchEventKinds.ENTRY_MODIFY) { 57 | kind = ENTRY_MODIFY; 58 | } 59 | 60 | return kind; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/rx/fileutils/FileSystemWatcher.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in 5 | * compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See 11 | * the License for the specific language governing permissions and limitations under the License. 12 | */ 13 | package rx.fileutils; 14 | 15 | import com.barbarysoftware.watchservice.MacOSXWatchServiceFactory; 16 | import com.barbarysoftware.watchservice.WatchableFile; 17 | import com.sun.nio.file.SensitivityWatchEventModifier; 18 | import rx.Observable; 19 | import rx.Scheduler; 20 | import rx.Subscriber; 21 | import rx.schedulers.Schedulers; 22 | 23 | import java.io.IOException; 24 | import java.nio.file.*; 25 | import java.nio.file.attribute.BasicFileAttributes; 26 | import java.util.*; 27 | 28 | import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; 29 | 30 | public final class FileSystemWatcher { 31 | 32 | static final boolean IS_MAC; 33 | 34 | static { 35 | String os = System.getProperty("os.name").toLowerCase(); 36 | IS_MAC = os.contains("mac"); 37 | } 38 | 39 | private FileSystemWatcher() {} 40 | 41 | private static class FileSystemEventOnSubscribe implements Observable.OnSubscribe { 42 | private WatchService watcher; 43 | private Scheduler scheduler; 44 | private volatile boolean close = false; 45 | private final Set watchedPaths; 46 | 47 | public FileSystemEventOnSubscribe( 48 | Map paths, 49 | Scheduler scheduler, 50 | boolean scanCurrentFs 51 | ) { 52 | watchedPaths = new HashSet(); 53 | if (scanCurrentFs) { 54 | paths.forEach((path, kind) -> { 55 | if (Arrays.asList(kind).contains(FileSystemEventKind.ENTRY_CREATE)) { 56 | watchedPaths.add(path); 57 | } 58 | }); 59 | } 60 | 61 | try { 62 | if (IS_MAC) { 63 | this.watcher = MacOSXWatchServiceFactory.newWatchService(); 64 | 65 | for (Path path : paths.keySet()) { 66 | final WatchableFile watchableFile = new WatchableFile(path); 67 | FileSystemEventKind[] kinds = paths.get(path); 68 | watchableFile.register(watcher, FileSystemEventKind.toWatchEventKinds(kinds)); 69 | } 70 | } 71 | else { 72 | this.watcher = FileSystems.getDefault().newWatchService(); 73 | 74 | for (Path path : paths.keySet()) { 75 | FileSystemEventKind[] kinds = paths.get(path); 76 | path.register(watcher, FileSystemEventKind.toWatchEventKinds(kinds), SensitivityWatchEventModifier.HIGH); 77 | } 78 | } 79 | this.scheduler = scheduler; 80 | } catch (IOException e) { 81 | throw new RuntimeException(e); 82 | } 83 | } 84 | 85 | public FileSystemEventOnSubscribe(Map paths, Scheduler scheduler) { 86 | this(paths, scheduler, false); 87 | } 88 | 89 | @Override 90 | public void call(Subscriber subscriber) { 91 | // scan the watchedPaths and trigger fake ENTRY_CREATE events 92 | watchedPaths.forEach(path -> { 93 | getEventsForCurrentFiles(path).forEach(event -> { 94 | FileSystemEvent fileSystemEvent = new FileSystemEvent(event); 95 | subscriber.onNext(fileSystemEvent); 96 | }); 97 | }); 98 | 99 | Scheduler.Worker worker = scheduler.createWorker(); 100 | subscriber.add(worker); 101 | 102 | worker.schedule(() -> { 103 | do { 104 | try { 105 | WatchKey key = watcher.take(); 106 | if (key == null) { 107 | continue; 108 | } 109 | 110 | for (WatchEvent event : key.pollEvents()) { 111 | FileSystemEvent fileSystemEvent = new FileSystemEvent(event); 112 | subscriber.onNext(fileSystemEvent); 113 | } 114 | 115 | if (!key.reset()) { 116 | close(); 117 | } 118 | } catch (Throwable t) { 119 | subscriber.onError(t); 120 | } 121 | } while (!close); 122 | 123 | subscriber.onCompleted(); 124 | }); 125 | } 126 | 127 | public void close() { 128 | this.close = true; 129 | 130 | try { 131 | watcher.close(); 132 | } catch (Exception e) {} 133 | } 134 | 135 | /** 136 | * Return fake ENTRY_CREATE events for the current files. 137 | * This simplify the code by treating current files the same way as new files. 138 | */ 139 | private List> getEventsForCurrentFiles(Path directory) { 140 | final List> events = new ArrayList<>(); 141 | try { 142 | Files.walkFileTree(directory, new SimpleFileVisitor() { 143 | @Override 144 | public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { 145 | events.add(pathToWatchEvent(path)); 146 | return FileVisitResult.CONTINUE; 147 | } 148 | 149 | @Override 150 | public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) { 151 | events.add(pathToWatchEvent(path)); 152 | return FileVisitResult.CONTINUE; 153 | } 154 | }); 155 | } catch (IOException e) { 156 | e.printStackTrace(); 157 | } 158 | 159 | return events; 160 | } 161 | 162 | private WatchEvent pathToWatchEvent(Path path) { 163 | return new WatchEvent() { 164 | @Override 165 | public Kind kind() { 166 | return ENTRY_CREATE; 167 | } 168 | 169 | @Override 170 | public int count() { 171 | return 1; 172 | } 173 | 174 | @Override 175 | public Path context() { 176 | return path; 177 | } 178 | }; 179 | } 180 | } 181 | 182 | public static class Builder { 183 | private Map paths = new HashMap<>(); 184 | 185 | private Scheduler scheduler = Schedulers.newThread(); 186 | 187 | private boolean scanCurrentFS = false; 188 | 189 | Builder() {} 190 | 191 | public Builder addPath(Path path, FileSystemEventKind... kinds) { 192 | paths.put(path, kinds); 193 | 194 | return this; 195 | } 196 | 197 | public Builder addPaths(Map paths) { 198 | this.paths.putAll(paths); 199 | 200 | return this; 201 | } 202 | 203 | public Builder withScheduler(Scheduler scheduler) { 204 | this.scheduler = scheduler; 205 | 206 | return this; 207 | } 208 | 209 | public Builder withCurrentFsScanning(boolean enable) { 210 | this.scanCurrentFS = enable; 211 | 212 | return this; 213 | } 214 | 215 | public Observable build() { 216 | try { 217 | FileSystemEventOnSubscribe fileSystemEventOnSubscribe 218 | = new FileSystemEventOnSubscribe(paths, scheduler, scanCurrentFS); 219 | 220 | Observable fileSystemEventObservable 221 | = Observable.create(fileSystemEventOnSubscribe); 222 | 223 | fileSystemEventObservable 224 | .doOnUnsubscribe(fileSystemEventOnSubscribe::close); 225 | 226 | return fileSystemEventObservable; 227 | } catch (Exception e) { 228 | throw new RuntimeException(e); 229 | } 230 | } 231 | 232 | } 233 | 234 | public static Builder newBuilder() { 235 | return new Builder(); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/test/java/rx/fileutils/FileSystemEventOnSubscribeTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in 5 | * compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is 10 | * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See 11 | * the License for the specific language governing permissions and limitations under the License. 12 | */ 13 | package rx.fileutils; 14 | 15 | import org.junit.After; 16 | import org.junit.Assert; 17 | import org.junit.Before; 18 | import org.junit.Test; 19 | import rx.Observable; 20 | import rx.Subscription; 21 | import rx.observers.TestSubscriber; 22 | import rx.schedulers.Schedulers; 23 | 24 | import java.io.File; 25 | import java.io.FileWriter; 26 | import java.nio.file.Path; 27 | import java.util.HashMap; 28 | import java.util.Map; 29 | import java.util.UUID; 30 | import java.util.concurrent.CountDownLatch; 31 | 32 | public class FileSystemEventOnSubscribeTest { 33 | private static final String testDirPath = "/tmp/testDirPath-" + UUID.randomUUID().toString(); 34 | 35 | private static File dir; 36 | 37 | @Before 38 | public void setup() { 39 | dir = new File(testDirPath); 40 | dir.mkdirs(); 41 | } 42 | 43 | @After 44 | public void teardown() throws Exception { 45 | deleteRecursive(dir); 46 | } 47 | 48 | public static void deleteRecursive(File path){ 49 | File[] c = path.listFiles(); 50 | System.out.println("Cleaning out folder:" + path.toString()); 51 | for (File file : c){ 52 | if (file.isDirectory()){ 53 | System.out.println("Deleting file:" + file.toString()); 54 | deleteRecursive(file); 55 | file.delete(); 56 | } else { 57 | file.delete(); 58 | } 59 | } 60 | 61 | path.delete(); 62 | } 63 | 64 | @Test(timeout = 15_000) 65 | public void testWatchForFileCreateAndModify() throws Exception { 66 | Map paths = new HashMap<>(); 67 | 68 | paths.put(dir.toPath(), new FileSystemEventKind[] { 69 | FileSystemEventKind.ENTRY_CREATE, 70 | FileSystemEventKind.ENTRY_MODIFY } ); 71 | 72 | Observable fileSystemWatcher = 73 | FileSystemWatcher 74 | .newBuilder() 75 | .addPaths(paths) 76 | .withScheduler(Schedulers.io()) 77 | .build(); 78 | 79 | TestSubscriber subscriber = new TestSubscriber(); 80 | 81 | CountDownLatch latch = new CountDownLatch(FileSystemWatcher.IS_MAC ? 3 : 2); 82 | Subscription subscribe = fileSystemWatcher 83 | .doOnNext(a -> { 84 | latch.countDown(); 85 | FileSystemEventKind kind = a.getFileSystemEventKind(); 86 | 87 | System.out.println("Got an event for " + kind.name()); 88 | 89 | }) 90 | .subscribe(subscriber); 91 | 92 | boolean closed = subscribe.isUnsubscribed(); 93 | 94 | Assert.assertFalse(closed); 95 | 96 | File file = new File(dir, "testFile" + System.currentTimeMillis()); 97 | file.createNewFile(); 98 | 99 | Thread.sleep(3000); 100 | 101 | FileWriter writer = new FileWriter(file, true); 102 | writer.write(1); 103 | writer.flush(); 104 | writer.close(); 105 | 106 | latch.await(); 107 | 108 | subscribe.unsubscribe(); 109 | 110 | closed = subscribe.isUnsubscribed(); 111 | 112 | Assert.assertTrue(closed); 113 | 114 | if (FileSystemWatcher.IS_MAC) { 115 | subscriber.assertValueCount(3); 116 | } else { 117 | subscriber.assertValueCount(2); 118 | } 119 | 120 | } 121 | } --------------------------------------------------------------------------------