├── .clang-format ├── .gitignore ├── DEV-NOTES.md ├── LICENSE ├── README.md ├── include ├── FileWatcher.h └── filemonitor │ ├── BasicFileMonitor.h │ ├── BasicFileMonitorService.h │ ├── FileMonitor.h │ ├── FileMonitorEvent.h │ ├── fsevents │ └── FileMonitorImpl.h │ ├── kqueue │ ├── basic_file_monitor_service.hpp │ └── file_monitor_impl.hpp │ ├── polling │ ├── basic_file_monitor_service.hpp │ └── file_monitor_impl.hpp │ └── windows │ ├── basic_file_monitor_service.hpp │ └── file_monitor_impl.hpp ├── src ├── FileWatcher.cpp └── filemonitor │ └── fsevents │ └── FileMonitorImpl.cpp └── tests └── UnitTests ├── src ├── AssignmentTest.cpp ├── BasicTest.cpp ├── ContainerTest.cpp ├── DeleteTest.cpp ├── DoubleTest.cpp ├── OverWriteTest.cpp ├── PerformanceTest.cpp ├── RegexTest.cpp ├── RenameTest.cpp ├── ScopedTest.cpp ├── TestMain.cpp ├── catch.hpp └── utils.h ├── vc2013 ├── unit.sln ├── unit.vcxproj └── unit.vcxproj.filters └── xcode ├── Info.plist ├── UnitTests.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── xcshareddata │ └── UnitTests.xcscmblueprint └── UnitTests_Prefix.pch /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -2 4 | AlignAfterOpenBracket: false 5 | AlignConsecutiveAssignments: false 6 | AlignEscapedNewlinesLeft: false 7 | AlignOperands: false 8 | AlignTrailingComments: false 9 | AllowAllParametersOfDeclarationOnNextLine: true 10 | AllowShortBlocksOnASingleLine: false 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: Inline 13 | AllowShortIfStatementsOnASingleLine: true 14 | AllowShortLoopsOnASingleLine: false 15 | AlwaysBreakAfterDefinitionReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: false 17 | AlwaysBreakTemplateDeclarations: true 18 | BinPackArguments: false 19 | BinPackParameters: false 20 | BreakBeforeBinaryOperators: All 21 | BreakBeforeBraces: Stroustrup 22 | BreakBeforeTernaryOperators: false 23 | BreakConstructorInitializersBeforeComma: false 24 | ColumnLimit: 0 25 | CommentPragmas: '^ IWYU pragma:' 26 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 27 | ConstructorInitializerIndentWidth: 4 28 | ContinuationIndentWidth: 4 29 | Cpp11BracedListStyle: false 30 | DerivePointerAlignment: false 31 | DisableFormat: false 32 | ExperimentalAutoDetectBinPacking: false 33 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 34 | IndentCaseLabels: false 35 | IndentWidth: 4 36 | IndentWrappedFunctionNames: false 37 | KeepEmptyLinesAtTheStartOfBlocks: false 38 | MacroBlockBegin: '' 39 | MacroBlockEnd: '' 40 | MaxEmptyLinesToKeep: 1 41 | NamespaceIndentation: None 42 | ObjCBlockIndentWidth: 4 43 | ObjCSpaceAfterProperty: true 44 | ObjCSpaceBeforeProtocolList: true 45 | PenaltyBreakBeforeFirstCallParameter: 19 46 | PenaltyBreakComment: 300 47 | PenaltyBreakFirstLessLess: 120 48 | PenaltyBreakString: 1000 49 | PenaltyExcessCharacter: 1000000 50 | PenaltyReturnTypeOnItsOwnLine: 60 51 | PointerAlignment: Right 52 | SpaceAfterCStyleCast: false 53 | SpaceBeforeAssignmentOperators: true 54 | SpaceBeforeParens: Never 55 | SpaceInEmptyParentheses: false 56 | SpacesBeforeTrailingComments: 1 57 | SpacesInAngles: false 58 | SpacesInContainerLiterals: true 59 | SpacesInCStyleCastParentheses: false 60 | SpacesInParentheses: true 61 | SpacesInSquareBrackets: false 62 | Standard: Cpp11 63 | TabWidth: 4 64 | UseTab: Always 65 | ... 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | reated by https://www.gitignore.io 2 | 3 | .DS_Store 4 | 5 | ### Xcode ### 6 | build/ 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata 16 | *.xccheckout 17 | *.moved-aside 18 | DerivedData 19 | *.xcuserstate 20 | *.xcworkspacedata 21 | 22 | ### C++ ### 23 | # Compiled Object files 24 | *.slo 25 | *.lo 26 | *.o 27 | *.obj 28 | 29 | # Precompiled Headers 30 | *.gch 31 | 32 | # Compiled Dynamic libraries 33 | *.so 34 | *.dylib 35 | *.dll 36 | 37 | # Fortran module files 38 | *.mod 39 | 40 | # Compiled Static libraries 41 | *.lai 42 | *.la 43 | *.a 44 | *.lib 45 | 46 | # Executables 47 | *.exe 48 | *.out 49 | *.app 50 | 51 | 52 | ### VisualStudio ### 53 | ## Ignore Visual Studio temporary files, build results, and 54 | ## files generated by popular Visual Studio add-ons. 55 | 56 | # User-specific files 57 | *.suo 58 | *.user 59 | *.userosscache 60 | *.sln.docstates 61 | 62 | # User-specific files (MonoDevelop/Xamarin Studio) 63 | *.userprefs 64 | 65 | # Build results 66 | [Dd]ebug/ 67 | [Dd]ebugPublic/ 68 | [Rr]elease/ 69 | [Rr]eleases/ 70 | x64/ 71 | x86/ 72 | build/ 73 | bld/ 74 | [Bb]in/ 75 | [Oo]bj/ 76 | 77 | # Visual Studo 2015 cache/options directory 78 | .vs/ 79 | 80 | # MSTest test Results 81 | [Tt]est[Rr]esult*/ 82 | [Bb]uild[Ll]og.* 83 | 84 | # NUNIT 85 | *.VisualState.xml 86 | TestResult.xml 87 | 88 | # Build Results of an ATL Project 89 | [Dd]ebugPS/ 90 | [Rr]eleasePS/ 91 | dlldata.c 92 | 93 | *_i.c 94 | *_p.c 95 | *_i.h 96 | *.ilk 97 | *.meta 98 | *.obj 99 | *.pdb 100 | *.pgc 101 | *.pgd 102 | *.rsp 103 | *.sbr 104 | *.tlb 105 | *.tli 106 | *.tlh 107 | *.tmp 108 | *.tmp_proj 109 | *.log 110 | *.vspscc 111 | *.vssscc 112 | .builds 113 | *.pidb 114 | *.svclog 115 | *.scc 116 | 117 | # Chutzpah Test files 118 | _Chutzpah* 119 | 120 | # Visual C++ cache files 121 | ipch/ 122 | *.aps 123 | *.ncb 124 | *.opensdf 125 | *.sdf 126 | *.cachefile 127 | 128 | # Visual Studio profiler 129 | *.psess 130 | *.vsp 131 | *.vspx 132 | 133 | # TFS 2012 Local Workspace 134 | $tf/ 135 | 136 | # Guidance Automation Toolkit 137 | *.gpState 138 | 139 | # ReSharper is a .NET coding add-in 140 | _ReSharper*/ 141 | *.[Rr]e[Ss]harper 142 | *.DotSettings.user 143 | 144 | # JustCode is a .NET coding addin-in 145 | .JustCode 146 | 147 | # TeamCity is a build add-in 148 | _TeamCity* 149 | 150 | # DotCover is a Code Coverage Tool 151 | *.dotCover 152 | 153 | # NCrunch 154 | _NCrunch_* 155 | .*crunch*.local.xml 156 | 157 | # MightyMoose 158 | *.mm.* 159 | AutoTest.Net/ 160 | 161 | # Web workbench (sass) 162 | .sass-cache/ 163 | 164 | # Installshield output folder 165 | [Ee]xpress/ 166 | 167 | # DocProject is a documentation generator add-in 168 | DocProject/buildhelp/ 169 | DocProject/Help/*.HxT 170 | DocProject/Help/*.HxC 171 | DocProject/Help/*.hhc 172 | DocProject/Help/*.hhk 173 | DocProject/Help/*.hhp 174 | DocProject/Help/Html2 175 | DocProject/Help/html 176 | 177 | # Click-Once directory 178 | publish/ 179 | 180 | # Publish Web Output 181 | *.[Pp]ublish.xml 182 | *.azurePubxml 183 | # TODO: Comment the next line if you want to checkin your web deploy settings 184 | # but database connection strings (with potential passwords) will be unencrypted 185 | *.pubxml 186 | *.publishproj 187 | 188 | # NuGet Packages 189 | *.nupkg 190 | # The packages folder can be ignored because of Package Restore 191 | **/packages/* 192 | # except build/, which is used as an MSBuild target. 193 | !**/packages/build/ 194 | # Uncomment if necessary however generally it will be regenerated when needed 195 | #!**/packages/repositories.config 196 | 197 | # Windows Azure Build Output 198 | csx/ 199 | *.build.csdef 200 | 201 | # Windows Store app package directory 202 | AppPackages/ 203 | 204 | # Others 205 | *.[Cc]ache 206 | ClientBin/ 207 | [Ss]tyle[Cc]op.* 208 | ~$* 209 | *~ 210 | *.dbmdl 211 | *.dbproj.schemaview 212 | *.pfx 213 | *.publishsettings 214 | node_modules/ 215 | bower_components/ 216 | 217 | # RIA/Silverlight projects 218 | Generated_Code/ 219 | 220 | # Backup & report files from converting an old project file 221 | # to a newer Visual Studio version. Backup files are not needed, 222 | # because we have git ;-) 223 | _UpgradeReport_Files/ 224 | Backup*/ 225 | UpgradeLog*.XML 226 | UpgradeLog*.htm 227 | 228 | # SQL Server files 229 | *.mdf 230 | *.ldf 231 | 232 | # Business Intelligence projects 233 | *.rdl.data 234 | *.bim.layout 235 | *.bim_*.settings 236 | 237 | # Microsoft Fakes 238 | FakesAssemblies/ 239 | 240 | # Node.js Tools for Visual Studio 241 | .ntvs_analysis.dat 242 | 243 | # Visual Studio 6 build log 244 | *.plg 245 | 246 | # Visual Studio 6 workspace options file 247 | *.opt 248 | 249 | -------------------------------------------------------------------------------- /DEV-NOTES.md: -------------------------------------------------------------------------------- 1 | # Developer Notes 2 | Initial work was done to support polling and kqueue's. kqueues support direct file changes. After conversations, it was realized we needed support for both files and folders. 3 | 4 | KQueues: These work by opening up direct handle ids that are associated with files. We then poll to see if any of them change. This is very streamlined regarding file monitoring, but this can't monitor folders or folder sub-content. 5 | 6 | FSevents: Takes a list of paths, either folders or files. If a file, callback will occur on changes to that specific file. If a folder, callbacks will occur on changes to all sub-folders and files. The challenge is that you do not have knowledge of which watched folder triggered a callback. 7 | 8 | Windows: Very similar to FSevents except you can not target an individual file, only a directory. This means you will always need to filter out the triggered events. 9 | 10 | Putting it all together: It seems like we can easily have cross platform live file monitoring, but we can't guarantee consistent callback filtering. i.e. watching a single file on OS X will only trigger callbacks upon modification of that file. Watching a single file on Windows will trigger callbacks upon modification of anything in that same folder. 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Lucas Vickers - All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that 4 | the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and 7 | the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 9 | the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 12 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 13 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 14 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 15 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 16 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 17 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 18 | POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cinder-FileMonitor 2 | ASIO file monitor for cinder 3 | 4 | # Description 5 | 6 | This tool allows for cross platform (eventually os x, windows, and linux) monitoring of both files and directories. The interface allows for the monitoring of a single file, as well as the monitoring of a folder via a regex search string. 7 | 8 | All callbacks occur in lambda functions. Eventually this could get tied into the core of Cinder allowing for a concept of 'live resources' 9 | 10 | # Details 11 | 12 | Currently we only make a callback on ADDED, REMOVED, MODIFIED, and RENAMED events. When an application modifies a file, multiple things can happen such as a timestamp changes before a file is modified. I tried to only select the most important events to avoid spurious callbacks. 13 | 14 | # Examples 15 | 16 | See _tets/UnitTests_ -------------------------------------------------------------------------------- /include/FileWatcher.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016, Lucas Vickers - All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that 5 | the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and 8 | the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 10 | the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 13 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 14 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 15 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 16 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 17 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 18 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 19 | POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "cinder/Cinder.h" 25 | #include "cinder/Exception.h" 26 | #include "cinder/Filesystem.h" 27 | #include "cinder/Noncopyable.h" 28 | 29 | #include "FileMonitor.h" 30 | 31 | namespace filewatcher { 32 | 33 | // expose internal event types 34 | typedef filemonitor::FileMonitorEvent::EventType EventType; 35 | 36 | typedef std::function WatchCallback; 37 | 38 | class WatchedTarget; 39 | 40 | //! Object for managing live-asset monitoring. Handles the asio service 41 | //! and passes along updates as needed 42 | class FileWatcher { 43 | 44 | //! allow WatchedObjects access to removeWatch and updateCallback 45 | friend class WatchedTarget; 46 | 47 | public: 48 | static FileWatcher *instance(); 49 | 50 | //! Creates a watch of a single file 51 | static WatchedTarget watchFile( const ci::fs::path &file, 52 | const WatchCallback &callback ); 53 | 54 | //! Creates a watch of a directory and subdirectories given a regex match 55 | static WatchedTarget watchPath( const ci::fs::path &path, 56 | const std::string ®ex, 57 | const WatchCallback &callback ); 58 | 59 | ~FileWatcher() { mAsioWork.reset(); } 60 | 61 | //! Registers update routine with current cinder app 62 | void registerUpdate(); 63 | 64 | //! Polls the asio service to check for updates 65 | void poll(); 66 | 67 | 68 | private: 69 | 70 | FileWatcher(); 71 | 72 | //! WatchedObject deconstructors will call this 73 | void removeWatch( uint64_t wid ); 74 | 75 | void updateCallback( uint64_t wid, const WatchCallback &callback ); 76 | 77 | //! Updates routine can be synced with cinder if desired 78 | void update(); 79 | 80 | // TODO migrate away from boost error_codes? 81 | void fileEventHandler( const boost::system::error_code &ec, 82 | const filemonitor::FileMonitorEvent &ev ); 83 | 84 | std::map mRegisteredCallbacks; 85 | 86 | boost::asio::io_service mIoService; 87 | std::unique_ptr mFileMonitor; 88 | std::unique_ptr mAsioWork; 89 | }; 90 | 91 | //! Used to watch a single file 92 | class WatchedTarget : private ci::Noncopyable { 93 | 94 | //! Allow FileWatcher access to constructors 95 | friend class FileWatcher; 96 | 97 | public: 98 | //! Creates a dead object (non watching target) 99 | WatchedTarget() 100 | : mWatchId( 0 ) { } 101 | 102 | //! Valid objects will be de-registered from the FileMonitor service 103 | ~WatchedTarget(); 104 | 105 | WatchedTarget( WatchedTarget &&other ); 106 | WatchedTarget& operator=( WatchedTarget &&rhs ); 107 | 108 | // TODO allow default constructor? 109 | //WatchedTarget() { } 110 | 111 | uint64_t getId() const { return mWatchId; } 112 | 113 | //! Check if we're watching a path 114 | bool isPath() const { return ! mRegexMatch.empty(); } 115 | //! Check if we're watching a specific file 116 | bool isFile() const { return mRegexMatch.empty(); } 117 | 118 | //! Get the path that is being watched (file or general path) 119 | ci::fs::path getPath() const { return mPath; } 120 | //! Get the regex that is being applied to the watch 121 | std::string getRegex() const { return mRegexMatch; } 122 | 123 | //! Updates the callback that will be triggered when a change is registered 124 | void updateCallback( const WatchCallback &callback ); 125 | 126 | 127 | protected: 128 | //! Constructor for watching a file 129 | WatchedTarget( uint64_t wid, 130 | const ci::fs::path &path, 131 | const WatchCallback &callback ) 132 | : mWatchId( wid ), 133 | mPath( path ), 134 | mCallback( callback ) 135 | { } 136 | 137 | //! Constructor for watching a path 138 | WatchedTarget( uint64_t wid, 139 | const ci::fs::path &path, 140 | const std::string ®exMatch, 141 | const WatchCallback &callback ) 142 | : mWatchId( wid ), 143 | mPath( path ), 144 | mCallback( callback ), 145 | mRegexMatch( regexMatch ) 146 | { } 147 | 148 | //! when utilized the ID will always be > 0 149 | uint64_t mWatchId; 150 | ci::fs::path mPath; 151 | WatchCallback mCallback; 152 | std::string mRegexMatch; 153 | 154 | }; 155 | 156 | template 157 | class WatchedTargetMap : public std::map { 158 | public: 159 | 160 | void addWatch( KeyT key, WatchedTarget&& target ) { 161 | this->insert( std::make_pair( key, std::move( target ) ) ); 162 | } 163 | 164 | }; 165 | 166 | } // namespace filewatcher 167 | -------------------------------------------------------------------------------- /include/filemonitor/BasicFileMonitor.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016, Lucas Vickers - All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that 5 | the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and 8 | the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 10 | the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 13 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 14 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 15 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 16 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 17 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 18 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 19 | POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | #pragma once 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | namespace filemonitor { 29 | 30 | template 31 | class BasicFileMonitor 32 | : public boost::asio::basic_io_object 33 | { 34 | public: 35 | explicit BasicFileMonitor( boost::asio::io_service &io_service ) 36 | : boost::asio::basic_io_object( io_service ) 37 | { 38 | } 39 | 40 | uint64_t addFile( const boost::filesystem::path &file ) 41 | { 42 | return this->service.addFile( this->implementation, file ); 43 | } 44 | 45 | uint64_t addPath( const boost::filesystem::path &path, const std::string ®exMatch ) 46 | { 47 | return this->service.addPath( this->implementation, path, regexMatch ); 48 | } 49 | 50 | void remove( uint64_t id ) 51 | { 52 | this->service.remove( this->implementation, id ); 53 | } 54 | 55 | FileMonitorEvent monitor() 56 | { 57 | boost::system::error_code ec; 58 | // TODO throw error from low level service avoid boost error 59 | FileMonitorEvent ev = this->service.monitor( this->implementation, ec ); 60 | boost::asio::detail::throw_error( ec ); 61 | return ev; 62 | } 63 | 64 | FileMonitorEvent monitor( boost::system::error_code &ec ) 65 | { 66 | return this->service.monitor( this->implementation, ec ); 67 | } 68 | 69 | template 70 | void asyncMonitor( Handler handler ) 71 | { 72 | this->service.asyncMonitor( this->implementation, handler ); 73 | } 74 | }; 75 | 76 | } // namespace filemonitor 77 | -------------------------------------------------------------------------------- /include/filemonitor/BasicFileMonitorService.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016, Lucas Vickers - All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that 5 | the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and 8 | the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 10 | the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 13 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 14 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 15 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 16 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 17 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 18 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 19 | POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | #pragma once 23 | 24 | #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) 25 | # include "windows/FileMonitorImpl.h" 26 | #elif defined(__APPLE__) && defined(__MACH__) 27 | # include "fsevents/FileMonitorImpl.h" 28 | #else 29 | // fallback method 30 | # include "polling/FileMonitorImpl.h" 31 | #endif 32 | 33 | 34 | #include 35 | #include 36 | #include 37 | 38 | // TODO move to cinder's asio 39 | //#include "asio/asio.hpp" 40 | 41 | namespace filemonitor { 42 | 43 | template 44 | class BasicFileMonitorService 45 | : public boost::asio::io_service::service 46 | { 47 | public: 48 | static boost::asio::io_service::id id; 49 | 50 | explicit BasicFileMonitorService( boost::asio::io_service &io_service ) 51 | : boost::asio::io_service::service( io_service ), 52 | mAsyncMonitorWork( new boost::asio::io_service::work( mAsyncMonitorIoService ) ), 53 | mAsyncMonitorThread( boost::bind( &boost::asio::io_service::run, &mAsyncMonitorIoService ) ) 54 | { 55 | } 56 | 57 | ~BasicFileMonitorService() 58 | { 59 | // The asyncMonitor thread will finish when mAsyncMonitorWork is reset as all asynchronous 60 | // operations have been aborted and were discarded before (in destroy). 61 | mAsyncMonitorWork.reset(); 62 | 63 | // Event processing is stopped to discard queued operations. 64 | mAsyncMonitorIoService.stop(); 65 | 66 | // The asyncMonitor thread is joined to make sure the file monitor service is 67 | // destroyed _after_ the thread is finished (not that the thread tries to access 68 | // instance properties which don't exist anymore). 69 | mAsyncMonitorThread.join(); 70 | } 71 | 72 | // TODO move to std 73 | typedef boost::shared_ptr implementation_type; 74 | 75 | void construct( implementation_type &impl ) 76 | { 77 | impl.reset( new FileMonitorImplementation() ); 78 | } 79 | 80 | void destroy( implementation_type &impl ) 81 | { 82 | // If an asynchronous call is currently waiting for an event 83 | // we must interrupt the blocked call to make sure it returns. 84 | impl->destroy(); 85 | 86 | impl.reset(); 87 | } 88 | 89 | uint64_t addPath( implementation_type &impl, const boost::filesystem::path &path, const std::string& regexMatch ) 90 | { 91 | if ( ! boost::filesystem::is_directory( path ) ) { 92 | // TODO migrate to a different exception 93 | throw std::invalid_argument("boost::asio::BasicFileMonitorService::addFile: \"" + 94 | path.string() + "\" is not a valid file or directory entry"); 95 | } 96 | 97 | return impl->addPath( path, regexMatch ); 98 | } 99 | 100 | uint64_t addFile( implementation_type &impl, const boost::filesystem::path &path ) 101 | { 102 | if ( ! boost::filesystem::is_regular_file( path ) ) { 103 | // TODO migrate to a different exception 104 | throw std::invalid_argument("boost::asio::BasicFileMonitorService::addFile: \"" + 105 | path.string() + "\" is not a valid file or directory entry"); 106 | } 107 | if ( boost::filesystem::is_symlink( path ) && boost::filesystem::read_symlink( path ) != path ) { 108 | // TODO migrate to a different exception 109 | throw std::invalid_argument("boost::asio::BasicFileMonitorService::addFile: \"" + 110 | path.string() + "\" this path is a symlink and must be resolved"); 111 | } 112 | return impl->addFile( path ); 113 | } 114 | 115 | void remove( implementation_type &impl, uint64_t id ) 116 | { 117 | impl->remove( id ); 118 | } 119 | 120 | /** 121 | * Blocking event monitor. 122 | */ 123 | FileMonitorEvent monitor( implementation_type &impl, boost::system::error_code &ec ) 124 | { 125 | return impl->popFrontEvent( ec ); 126 | } 127 | 128 | template 129 | class MonitorOperation 130 | { 131 | public: 132 | MonitorOperation( implementation_type &impl, boost::asio::io_service &ioService, Handler handler ) 133 | : mImpl( impl ), mIoService( ioService ), mWork( ioService ), mHandler( handler ) 134 | { 135 | } 136 | 137 | void operator()() const 138 | { 139 | implementation_type impl = mImpl.lock(); 140 | if( impl ) { 141 | boost::system::error_code ec; 142 | FileMonitorEvent ev = impl->popFrontEvent( ec ); 143 | this->mIoService.post( boost::asio::detail::bind_handler( mHandler, ec, ev ) ); 144 | } 145 | else { 146 | this->mIoService.post( boost::asio::detail::bind_handler( mHandler, 147 | boost::asio::error::operation_aborted, 148 | FileMonitorEvent() ) ); 149 | } 150 | } 151 | 152 | private: 153 | boost::weak_ptr mImpl; 154 | boost::asio::io_service &mIoService; 155 | boost::asio::io_service::work mWork; 156 | Handler mHandler; 157 | }; 158 | 159 | /** 160 | * Non-blocking event monitor. 161 | */ 162 | template 163 | void asyncMonitor( implementation_type &impl, Handler handler ) 164 | { 165 | this->mAsyncMonitorIoService.post( MonitorOperation( impl, this->get_io_service(), handler ) ); 166 | } 167 | 168 | private: 169 | void shutdown_service() 170 | { 171 | //TODO need anything? 172 | } 173 | 174 | boost::asio::io_service mAsyncMonitorIoService; 175 | //! note: migrated from scoped_ptr. remove comment if this works 176 | std::unique_ptr mAsyncMonitorWork; 177 | std::thread mAsyncMonitorThread; 178 | }; 179 | 180 | template 181 | boost::asio::io_service::id BasicFileMonitorService::id; 182 | 183 | } // filemonitor namespace 184 | 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /include/filemonitor/FileMonitor.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016, Lucas Vickers - All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that 5 | the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and 8 | the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 10 | the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 13 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 14 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 15 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 16 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 17 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 18 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 19 | POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "FileMonitorEvent.h" 25 | #include "BasicFileMonitor.h" 26 | #include "BasicFileMonitorService.h" 27 | 28 | namespace filemonitor { 29 | 30 | typedef BasicFileMonitor< BasicFileMonitorService <> > FileMonitor; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /include/filemonitor/FileMonitorEvent.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016, Lucas Vickers - All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that 5 | the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and 8 | the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 10 | the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 13 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 14 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 15 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 16 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 17 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 18 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 19 | POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | #pragma once 23 | 24 | #include 25 | #include 26 | 27 | namespace filemonitor { 28 | 29 | struct FileMonitorEvent 30 | { 31 | enum EventType 32 | { 33 | NONE, 34 | REMOVED, // file removed 35 | ADDED, // file added 36 | MODIFIED, // file changed 37 | RENAMED_OLD, // file renamed, old name 38 | RENAMED_NEW // file renamed, new name 39 | }; 40 | 41 | FileMonitorEvent() 42 | : type( NONE ), id( 0 ) 43 | { } 44 | 45 | FileMonitorEvent( const boost::filesystem::path &p, EventType t, uint64_t id ) 46 | : path( p ), type( t ), id( id ) 47 | { } 48 | 49 | boost::filesystem::path path; 50 | EventType type; 51 | uint64_t id; 52 | }; 53 | 54 | inline std::ostream& operator << ( std::ostream& os, const FileMonitorEvent &ev ) 55 | { 56 | os << "FileMonitorEvent " 57 | << []( int type ) { 58 | switch( type ) { 59 | case FileMonitorEvent::REMOVED: return "REMOVED"; 60 | case FileMonitorEvent::ADDED: return "ADDED"; 61 | case FileMonitorEvent::MODIFIED: return "MODIFIED"; 62 | case FileMonitorEvent::RENAMED_OLD: return "RENAMED_OLD"; 63 | case FileMonitorEvent::RENAMED_NEW: return "RENAMED_NEW"; 64 | default: return "UNKNOWN"; 65 | } 66 | } ( ev.type ) << " " << ev.path; 67 | return os; 68 | } 69 | 70 | } // namespace filemonitor -------------------------------------------------------------------------------- /include/filemonitor/fsevents/FileMonitorImpl.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016, Lucas Vickers - All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that 5 | the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and 8 | the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 10 | the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 13 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 14 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 15 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 16 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 17 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 18 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 19 | POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | #pragma once 23 | 24 | #include // TODO migrate to standard C++ / cinder if possible 25 | #include // TODO migrate to standard C++ / cinder if possible 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "FileMonitorEvent.h" 33 | 34 | namespace filemonitor { 35 | 36 | class FileMonitorImpl : 37 | public std::enable_shared_from_this 38 | { 39 | public: 40 | FileMonitorImpl() 41 | : mRun(true), mWorkThread( &FileMonitorImpl::workThread, this ), mFsevents( nullptr ) 42 | {} 43 | 44 | ~FileMonitorImpl(); 45 | 46 | uint64_t addPath( const boost::filesystem::path &path, const std::string ®exMatch ); 47 | 48 | uint64_t addFile( const boost::filesystem::path &file ); 49 | 50 | void remove( uint64_t id ); 51 | 52 | void destroy(); 53 | 54 | FileMonitorEvent popFrontEvent( boost::system::error_code &ec ); 55 | 56 | void verifyEvent( const boost::filesystem::path &path, FileMonitorEvent::EventType type ); 57 | 58 | void pushBackEvent( const FileMonitorEvent &ev ); 59 | 60 | private: 61 | 62 | void startFsevents(); 63 | 64 | void stopFsevents(); 65 | 66 | static void fseventsCallback( ConstFSEventStreamRef streamRef, 67 | void *clientCallBackInfo, 68 | size_t numEvents, 69 | void *eventPaths, 70 | const FSEventStreamEventFlags eventFlags[], 71 | const FSEventStreamEventId eventIds[] ); 72 | 73 | void workThread(); 74 | 75 | bool running(); 76 | 77 | void stopWorkThread(); 78 | 79 | //! Templated to make it easier to swap out map types 80 | //! takes the ID and returns the path that was associated / removed 81 | template 82 | boost::filesystem::path removeEntry( uint64_t id, mapType &idMap ) 83 | { 84 | boost::filesystem::path path; 85 | 86 | auto iter = idMap.find( id ); 87 | assert( iter != idMap.end() ); 88 | 89 | path = iter->second.path; 90 | // Removes the entry based on the ID 91 | idMap.erase( iter ); 92 | 93 | return path; 94 | } 95 | 96 | //! Templated to make it easier to swap out map types 97 | //! takes the ID and returns the path that was associated / removed 98 | template 99 | boost::filesystem::path removeEntry( uint64_t id, mapType &idMap, mmapType &pathMmap ) 100 | { 101 | boost::filesystem::path path; 102 | 103 | auto iter = idMap.find( id ); 104 | assert( iter != idMap.end() ); 105 | 106 | auto range = pathMmap.equal_range( iter->second.path ); 107 | assert( range.first != pathMmap.end() ); 108 | 109 | auto rangeIter = range.first; 110 | while( rangeIter != range.second ) { 111 | // check id values for match 112 | if( rangeIter->second->entryID == iter->second.entryID ) { 113 | break; 114 | } 115 | ++rangeIter; 116 | } 117 | assert( rangeIter != range.second ); 118 | 119 | path = iter->second.path; 120 | // Removes the entry based on the ID 121 | idMap.erase( iter ); 122 | // Removes the path that was based on path and ID 123 | pathMmap.erase( rangeIter ); 124 | 125 | assert( mFilesMmap.size() == mFiles.size() ); 126 | 127 | return path; 128 | } 129 | 130 | void incrementTarget( const boost::filesystem::path &path ); 131 | 132 | void decrementTarget( const boost::filesystem::path &path ); 133 | 134 | class PathEntry 135 | { 136 | public: 137 | 138 | PathEntry( const boost::filesystem::path &path, 139 | const std::string ®exMatch, 140 | uint64_t entryID ) 141 | : path( path ), regexMatch( regexMatch ), entryID( entryID ) 142 | {} 143 | 144 | uint64_t entryID; 145 | boost::filesystem::path path; 146 | std::regex regexMatch; 147 | }; 148 | 149 | class FileEntry 150 | { 151 | public: 152 | 153 | FileEntry( const boost::filesystem::path &path, 154 | uint64_t entryID ) 155 | : path( path ), entryID( entryID ) 156 | { } 157 | 158 | uint64_t entryID; 159 | boost::filesystem::path path; 160 | }; 161 | 162 | std::mutex mPathsMutex; 163 | 164 | // ids, always > 0 165 | uint64_t mNextFileId{2}; 166 | uint64_t mNextPathId{1}; 167 | 168 | // TODO explore the use of hashmaps 169 | 170 | //! Owns entries data 171 | std::unordered_map mPaths; 172 | std::unordered_map mFiles; 173 | 174 | // references entries 175 | struct pathHash { 176 | size_t operator()( const boost::filesystem::path &p ) const { 177 | 178 | return std::hash()( p.string() ); 179 | 180 | // TODO resolve fs::hash_value once namespace has been converted 181 | //return boost::filesystem::hash_value( p ); 182 | } 183 | }; 184 | 185 | // TODO explore maps vs sets performance 186 | 187 | //! Used for quick lookup of file specific activity via a path 188 | //! Multimap to support multiple watches on a single file 189 | //! References entries data 190 | std::unordered_multimap mFilesMmap; 191 | 192 | //! Used to keep track of all watched targets, both file and paths 193 | //! This is used for creating the watch list as it contains both files and paths 194 | std::unordered_map mAllTargetsMap; 195 | 196 | bool mRun{false}; 197 | CFRunLoopRef mRunloop; 198 | std::mutex mRunloopMutex; 199 | std::condition_variable mRunloopCond; 200 | 201 | std::mutex mWorkThreadMutex; 202 | std::thread mWorkThread; 203 | 204 | FSEventStreamRef mFsevents; 205 | std::mutex mEventsMutex; 206 | std::condition_variable mEventsCond; 207 | std::deque mEvents; 208 | }; 209 | 210 | } // filemonitor namespace 211 | -------------------------------------------------------------------------------- /include/filemonitor/kqueue/basic_file_monitor_service.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "file_monitor_impl.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace filemonitor { 11 | 12 | template 13 | class basic_file_monitor_service 14 | : public boost::asio::io_service::service 15 | { 16 | public: 17 | static boost::asio::io_service::id id; 18 | 19 | explicit basic_file_monitor_service( boost::asio::io_service &io_service ) 20 | : boost::asio::io_service::service( io_service ), 21 | async_monitor_work_( new boost::asio::io_service::work( async_monitor_io_service_ ) ), 22 | async_monitor_thread_( boost::bind( &boost::asio::io_service::run, &async_monitor_io_service_ ) ) 23 | { 24 | } 25 | 26 | ~basic_file_monitor_service() 27 | { 28 | // The async_monitor thread will finish when mAsync_monitor_work is reset as all asynchronous 29 | // operations have been aborted and were discarded before (in destroy). 30 | async_monitor_work_.reset(); 31 | 32 | // Event processing is stopped to discard queued operations. 33 | async_monitor_io_service_.stop(); 34 | 35 | // The async_monitor thread is joined to make sure the file monitor service is 36 | // destroyed _after_ the thread is finished (not that the thread tries to access 37 | // instance properties which don't exist anymore). 38 | async_monitor_thread_.join(); 39 | } 40 | 41 | typedef boost::shared_ptr implementation_type; 42 | 43 | void construct( implementation_type &impl ) 44 | { 45 | impl.reset( new FileMonitorImplementation() ); 46 | } 47 | 48 | void destroy( implementation_type &impl ) 49 | { 50 | // If an asynchronous call is currently waiting for an event 51 | // we must interrupt the blocked call to make sure it returns. 52 | impl->destroy(); 53 | 54 | impl.reset(); 55 | } 56 | 57 | void add_file( implementation_type &impl, const std::string &filename ) 58 | { 59 | if ( ! boost::filesystem::is_regular_file( filename ) ) { 60 | throw std::invalid_argument("boost::asio::basic_file_monitor_service::add_file: " + 61 | filename + " is not a valid file entry"); 62 | } 63 | 64 | int event_fd = ::open( filename.c_str(), O_EVTONLY ); 65 | if( event_fd < 0 ) { 66 | boost::system::system_error e( boost::system::error_code( errno, boost::system::get_system_category() ), 67 | "boost::asio::file_monitor_impl::add_file: open failed" ); 68 | boost::throw_exception( e ); 69 | } 70 | 71 | impl->add_file( filename, event_fd ); 72 | } 73 | 74 | void remove_file( implementation_type &impl, const std::string &filename ) 75 | { 76 | // Removing the file from the implementation will close the associated file handle. 77 | // Closing the file handle will make kevent() clear corresponding events. 78 | impl->remove_file( filename ); 79 | } 80 | 81 | /** 82 | * Blocking event monitor. 83 | */ 84 | file_monitor_event monitor( implementation_type &impl, boost::system::error_code &ec ) 85 | { 86 | return impl->popfront_event( ec ); 87 | } 88 | 89 | template 90 | class monitor_operation 91 | { 92 | public: 93 | monitor_operation( implementation_type &impl, boost::asio::io_service &io_service, Handler handler ) 94 | : impl_( impl ), 95 | io_service_( io_service ), 96 | work_( io_service ), 97 | handler_( handler ) 98 | { 99 | } 100 | 101 | void operator()() const 102 | { 103 | implementation_type impl = impl_.lock(); 104 | if( impl ) { 105 | boost::system::error_code ec; 106 | file_monitor_event ev = impl->popfront_event( ec ); 107 | this->io_service_.post( boost::asio::detail::bind_handler( handler_, ec, ev ) ); 108 | } 109 | else { 110 | this->io_service_.post( boost::asio::detail::bind_handler( handler_, 111 | boost::asio::error::operation_aborted, 112 | file_monitor_event() ) ); 113 | } 114 | } 115 | 116 | private: 117 | boost::weak_ptr impl_; 118 | boost::asio::io_service &io_service_; 119 | boost::asio::io_service::work work_; 120 | Handler handler_; 121 | }; 122 | 123 | /** 124 | * Non-blocking event monitor. 125 | */ 126 | template 127 | void async_monitor( implementation_type &impl, Handler handler ) 128 | { 129 | this->async_monitor_io_service_.post( monitor_operation( impl, this->get_io_service(), handler ) ); 130 | } 131 | 132 | private: 133 | void shutdown_service() 134 | { 135 | } 136 | 137 | boost::asio::io_service async_monitor_io_service_; 138 | boost::scoped_ptr async_monitor_work_; 139 | std::thread async_monitor_thread_; 140 | }; 141 | 142 | template 143 | boost::asio::io_service::id basic_file_monitor_service::id; 144 | 145 | } // filemonitor namespace 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /include/filemonitor/kqueue/file_monitor_impl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace filemonitor { 15 | 16 | class file_monitor_impl : 17 | public boost::enable_shared_from_this 18 | { 19 | 20 | public: 21 | file_monitor_impl() 22 | : kqueue_( init_kqueue() ), 23 | run_(true), 24 | work_thread_( &file_monitor_impl::work_thread, this ) 25 | {} 26 | 27 | ~file_monitor_impl() 28 | { 29 | // The work thread is stopped and joined. 30 | stop_work_thread(); 31 | work_thread_.join(); 32 | ::close( kqueue_ ); 33 | } 34 | 35 | void add_file( const boost::filesystem::path &file, int event_fd ) 36 | { 37 | std::lock_guard lock( add_remove_mutex_ ); 38 | add_queue_.push_back( std::pair( file, event_fd ) ); 39 | } 40 | 41 | void remove_file( const boost::filesystem::path &file ) 42 | { 43 | std::lock_guard lock( add_remove_mutex_ ); 44 | remove_queue_.push_back( file ); 45 | } 46 | 47 | void destroy() 48 | { 49 | std::lock_guard lock( events_mutex_ ); 50 | run_ = false; 51 | events_cond_.notify_all(); 52 | } 53 | 54 | file_monitor_event popfront_event( boost::system::error_code &ec ) 55 | { 56 | std::unique_lock lock( events_mutex_ ); 57 | while( run_ && events_.empty() ) { 58 | events_cond_.wait( lock ); 59 | } 60 | file_monitor_event ev; 61 | if( ! events_.empty() ) { 62 | ec = boost::system::error_code(); 63 | ev = events_.front(); 64 | events_.pop_front(); 65 | } else { 66 | ec = boost::asio::error::operation_aborted; 67 | } 68 | return ev; 69 | } 70 | 71 | void pushback_event( const file_monitor_event &ev ) 72 | { 73 | std::lock_guard lock( events_mutex_ ); 74 | if( run_ ) { 75 | events_.push_back( ev ); 76 | events_cond_.notify_all(); 77 | } 78 | } 79 | 80 | private: 81 | int init_kqueue() 82 | { 83 | int fd = kqueue(); 84 | if( fd == -1 ) { 85 | boost::system::system_error e(boost::system::error_code(errno, boost::system::get_system_category()), "boost::asio::file_monitor_impl::init_kqueue: kqueue failed"); 86 | boost::throw_exception(e); 87 | } 88 | return fd; 89 | } 90 | 91 | void work_thread() 92 | { 93 | while( running() ) { 94 | 95 | // deal with removes 96 | { 97 | std::lock_guard lock( add_remove_mutex_ ); 98 | for( const auto& name : remove_queue_ ) { 99 | 100 | auto it = files_bimap_.left.find( name ); 101 | if( it != files_bimap_.left.end() ) { 102 | ::close( it->second ); 103 | files_bimap_.left.erase( name ); 104 | } 105 | } 106 | remove_queue_.clear(); 107 | } 108 | 109 | // deal with adds 110 | int add_index = 0; 111 | { 112 | std::lock_guard lock( add_remove_mutex_ ); 113 | 114 | while( ! add_queue_.empty() && add_index < event_list_size ) { 115 | 116 | int fd = add_queue_.begin()->second; 117 | const boost::filesystem::path& path = add_queue_.begin()->first; 118 | 119 | unsigned eventFilter = NOTE_WRITE | NOTE_DELETE | NOTE_RENAME; 120 | EV_SET( &event_list_[add_index++], fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, eventFilter, 0, 0 ); 121 | 122 | // if user is re-adding a file, close the old handle and use the new handle. 123 | // combinations of rename/delete/etc could mean our filename->fd is out of date, 124 | // so use the new one 125 | auto it = files_bimap_.left.find( path ); 126 | if( it != files_bimap_.left.end() ) { 127 | ::close( it->second ); 128 | bool success = files_bimap_.left.replace_data( it, fd ); 129 | assert( success ); 130 | } else { 131 | // otherwise just add 132 | files_bimap_.insert( watched_file( path, fd ) ); 133 | } 134 | 135 | add_queue_.pop_front(); 136 | } 137 | } 138 | 139 | // TODO BUG: There's an obvious bug here. We need to add items that are registered but not added 140 | 141 | struct timespec timeout; 142 | timeout.tv_sec = 0; 143 | timeout.tv_nsec = 200000000; 144 | 145 | // Wait for changes or for timeout 146 | int nEvents = kevent( kqueue_, event_list_, add_index, event_list_, event_list_size, &timeout ); 147 | 148 | if( nEvents < 0 or event_list_[0].flags == EV_ERROR ) 149 | { 150 | boost::system::system_error e(boost::system::error_code( errno, boost::system::get_system_category() ), "boost::asio::file_monitor_impl::work_thread: kevent failed"); 151 | boost::throw_exception(e); 152 | } 153 | 154 | // Cycle through the number of events that occured. The ident is the file handle. 155 | if( nEvents > 0 ) { 156 | for( int i=0; i lock( work_thread_mutex_ ); 178 | return run_; 179 | } 180 | 181 | void stop_work_thread() 182 | { 183 | // Access to run_ is sychronized with running(). 184 | std::lock_guard lock( work_thread_mutex_ ); 185 | run_ = false; 186 | } 187 | 188 | int kqueue_; 189 | bool run_; 190 | std::mutex work_thread_mutex_; 191 | std::thread work_thread_; 192 | 193 | // need to go from unix_handle.id -> path and path -> unix_handle 194 | typedef boost::bimap< boost::filesystem::path, int > files_bimap; 195 | typedef files_bimap::value_type watched_file; 196 | files_bimap files_bimap_; 197 | 198 | // for adding and removing outside of the worker thread 199 | std::mutex add_remove_mutex_; 200 | std::deque< std::pair< boost::filesystem::path, int > > add_queue_; 201 | std::deque< boost::filesystem::path > remove_queue_; 202 | 203 | static const int event_list_size = 256; 204 | struct kevent event_list_[event_list_size]; 205 | 206 | std::mutex events_mutex_; 207 | std::condition_variable events_cond_; 208 | std::deque< file_monitor_event > events_; 209 | }; 210 | 211 | } // asio namespace 212 | 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /include/filemonitor/polling/basic_file_monitor_service.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "file_monitor_impl.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace filemonitor { 11 | 12 | template 13 | class basic_file_monitor_service 14 | : public boost::asio::io_service::service 15 | { 16 | public: 17 | static boost::asio::io_service::id id; 18 | 19 | explicit basic_file_monitor_service( boost::asio::io_service &io_service ) 20 | : boost::asio::io_service::service( io_service ), 21 | async_monitor_work_( new boost::asio::io_service::work( async_monitor_io_service_ ) ), 22 | async_monitor_thread_( boost::bind( &boost::asio::io_service::run, &async_monitor_io_service_ ) ) 23 | { 24 | } 25 | 26 | ~basic_file_monitor_service() 27 | { 28 | // The async_monitor thread will finish when mAsync_monitor_work is reset as all asynchronous 29 | // operations have been aborted and were discarded before (in destroy). 30 | async_monitor_work_.reset(); 31 | 32 | // Event processing is stopped to discard queued operations. 33 | async_monitor_io_service_.stop(); 34 | 35 | // The async_monitor thread is joined to make sure the file monitor service is 36 | // destroyed _after_ the thread is finished (not that the thread tries to access 37 | // instance properties which don't exist anymore). 38 | async_monitor_thread_.join(); 39 | } 40 | 41 | typedef boost::shared_ptr implementation_type; 42 | 43 | void construct( implementation_type &impl ) 44 | { 45 | impl.reset( new FileMonitorImplementation() ); 46 | } 47 | 48 | void destroy( implementation_type &impl ) 49 | { 50 | // If an asynchronous call is currently waiting for an event 51 | // we must interrupt the blocked call to make sure it returns. 52 | impl->destroy(); 53 | 54 | impl.reset(); 55 | } 56 | 57 | void add_file( implementation_type &impl, const boost::filesystem::path &path ) 58 | { 59 | if ( ! boost::filesystem::is_regular_file( path ) ) { 60 | throw std::invalid_argument( "boost::asio::basic_file_monitor_service::add_file: " + 61 | path.string() + " is not a valid file entry"); 62 | } 63 | impl->add_file( path ); 64 | } 65 | 66 | void remove_file( implementation_type &impl, const boost::filesystem::path &path ) 67 | { 68 | impl->remove_file( path ); 69 | } 70 | 71 | /** 72 | * Blocking event monitor. 73 | */ 74 | file_monitor_event monitor( implementation_type &impl, boost::system::error_code &ec ) 75 | { 76 | return impl->popfront_event( ec ); 77 | } 78 | 79 | template 80 | class monitor_operation 81 | { 82 | public: 83 | monitor_operation( implementation_type &impl, boost::asio::io_service &io_service, Handler handler ) 84 | : impl_( impl ), 85 | io_service_( io_service ), 86 | work_( io_service ), 87 | handler_( handler ) 88 | { 89 | } 90 | 91 | void operator()() const 92 | { 93 | implementation_type impl = impl_.lock(); 94 | if( impl ) { 95 | boost::system::error_code ec; 96 | file_monitor_event ev = impl->popfront_event( ec ); 97 | this->io_service_.post( boost::asio::detail::bind_handler( handler_, ec, ev ) ); 98 | } 99 | else { 100 | this->io_service_.post( boost::asio::detail::bind_handler( handler_, 101 | boost::asio::error::operation_aborted, 102 | file_monitor_event() ) ); 103 | } 104 | } 105 | 106 | private: 107 | boost::weak_ptr impl_; 108 | boost::asio::io_service &io_service_; 109 | boost::asio::io_service::work work_; 110 | Handler handler_; 111 | }; 112 | 113 | /** 114 | * Non-blocking event monitor. 115 | */ 116 | template 117 | void async_monitor( implementation_type &impl, Handler handler ) 118 | { 119 | this->async_monitor_io_service_.post( monitor_operation( impl, this->get_io_service(), handler ) ); 120 | } 121 | 122 | private: 123 | void shutdown_service() 124 | { 125 | } 126 | 127 | boost::asio::io_service async_monitor_io_service_; 128 | boost::scoped_ptr async_monitor_work_; 129 | std::thread async_monitor_thread_; 130 | }; 131 | 132 | template 133 | boost::asio::io_service::id basic_file_monitor_service::id; 134 | 135 | } // filemonitor namespace 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /include/filemonitor/polling/file_monitor_impl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace filemonitor { 11 | 12 | static const uint16_t POLLING_DELAY = 200; 13 | 14 | class file_monitor_impl : 15 | public boost::enable_shared_from_this 16 | { 17 | 18 | public: 19 | file_monitor_impl() 20 | : run_(true), 21 | work_thread_( &file_monitor_impl::work_thread, this ) 22 | {} 23 | 24 | ~file_monitor_impl() 25 | { 26 | // The work thread is stopped and joined. 27 | stop_work_thread(); 28 | work_thread_.join(); 29 | } 30 | 31 | void add_file( const boost::filesystem::path &path ) 32 | { 33 | std::lock_guard lock( add_remove_mutex_ ); 34 | add_queue_.push_back( path ); 35 | } 36 | 37 | void remove_file( const boost::filesystem::path &path ) 38 | { 39 | std::lock_guard lock( add_remove_mutex_ ); 40 | remove_queue_.push_back( path ); 41 | } 42 | 43 | void destroy() 44 | { 45 | std::lock_guard lock( events_mutex_ ); 46 | run_ = false; 47 | events_cond_.notify_all(); 48 | } 49 | 50 | file_monitor_event popfront_event( boost::system::error_code &ec ) 51 | { 52 | std::unique_lock lock( events_mutex_ ); 53 | while( run_ && events_.empty() ) { 54 | events_cond_.wait( lock ); 55 | } 56 | file_monitor_event ev; 57 | if( ! events_.empty() ) { 58 | ec = boost::system::error_code(); 59 | ev = events_.front(); 60 | events_.pop_front(); 61 | } else { 62 | ec = boost::asio::error::operation_aborted; 63 | } 64 | return ev; 65 | } 66 | 67 | void pushback_event( const file_monitor_event &ev ) 68 | { 69 | std::lock_guard lock( events_mutex_ ); 70 | if( run_ ) { 71 | events_.push_back( ev ); 72 | events_cond_.notify_all(); 73 | } 74 | } 75 | 76 | private: 77 | 78 | void work_thread() 79 | { 80 | while( running() ) { 81 | 82 | // deal with removes 83 | { 84 | std::lock_guard lock( add_remove_mutex_ ); 85 | for( const auto& name : remove_queue_ ) { 86 | file_timestamps_.erase( name ); 87 | } 88 | remove_queue_.clear(); 89 | } 90 | 91 | // deal with adds 92 | { 93 | std::lock_guard lock( add_remove_mutex_ ); 94 | for( const auto& file : add_queue_ ) { 95 | // overwrite existing items to re-fresh the timestamp 96 | file_timestamps_[file] = boost::filesystem::last_write_time( file ); 97 | } 98 | add_queue_.clear(); 99 | } 100 | 101 | // process timestamps 102 | for( const auto& it : file_timestamps_ ) { 103 | // check if it still exists 104 | if( ! boost::filesystem::exists( it.first ) ) { 105 | // renamed or deleted, assume deleted 106 | pushback_event( file_monitor_event( it.first, file_monitor_event::remove ) ); 107 | { 108 | std::lock_guard lock( add_remove_mutex_ ); 109 | remove_queue_.push_back( it.first ); 110 | } 111 | } else if ( boost::filesystem::last_write_time( it.first ) != it.second ) { 112 | // file modified/written 113 | pushback_event( file_monitor_event( it.first, file_monitor_event::write ) ); 114 | file_timestamps_[it.first] = boost::filesystem::last_write_time( it.first ); 115 | } 116 | } 117 | 118 | std::this_thread::sleep_for( std::chrono::milliseconds( POLLING_DELAY ) ); 119 | } 120 | } 121 | 122 | bool running() 123 | { 124 | // Access to run_ is sychronized with stop_work_thread(). 125 | std::lock_guard lock( work_thread_mutex_ ); 126 | return run_; 127 | } 128 | 129 | void stop_work_thread() 130 | { 131 | // Access to run_ is sychronized with running(). 132 | std::lock_guard lock( work_thread_mutex_ ); 133 | run_ = false; 134 | } 135 | 136 | bool run_; 137 | std::mutex work_thread_mutex_; 138 | std::thread work_thread_; 139 | 140 | // filepath -> timestamp 141 | std::map file_timestamps_; 142 | 143 | // for adding and removing outside of the worker thread 144 | std::mutex add_remove_mutex_; 145 | std::deque add_queue_; 146 | std::deque remove_queue_; 147 | 148 | std::mutex events_mutex_; 149 | std::condition_variable events_cond_; 150 | std::deque< file_monitor_event > events_; 151 | }; 152 | 153 | } // asio namespace 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /include/filemonitor/windows/basic_file_monitor_service.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "file_monitor_impl.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace filemonitor { 11 | 12 | template 13 | class basic_file_monitor_service 14 | : public boost::asio::io_service::service 15 | { 16 | public: 17 | static boost::asio::io_service::id id; 18 | 19 | explicit basic_file_monitor_service( boost::asio::io_service &io_service ) 20 | : boost::asio::io_service::service( io_service ), 21 | async_monitor_work_( new boost::asio::io_service::work( async_monitor_io_service_ ) ), 22 | async_monitor_thread_( boost::bind( &boost::asio::io_service::run, &async_monitor_io_service_ ) ) 23 | { 24 | } 25 | 26 | ~basic_file_monitor_service() 27 | { 28 | // The async_monitor thread will finish when mAsync_monitor_work is reset as all asynchronous 29 | // operations have been aborted and were discarded before (in destroy). 30 | async_monitor_work_.reset(); 31 | 32 | // Event processing is stopped to discard queued operations. 33 | async_monitor_io_service_.stop(); 34 | 35 | // The async_monitor thread is joined to make sure the file monitor service is 36 | // destroyed _after_ the thread is finished (not that the thread tries to access 37 | // instance properties which don't exist anymore). 38 | async_monitor_thread_.join(); 39 | } 40 | 41 | typedef boost::shared_ptr implementation_type; 42 | 43 | void construct( implementation_type &impl ) 44 | { 45 | impl.reset( new FileMonitorImplementation() ); 46 | } 47 | 48 | void destroy( implementation_type &impl ) 49 | { 50 | // If an asynchronous call is currently waiting for an event 51 | // we must interrupt the blocked call to make sure it returns. 52 | impl->destroy(); 53 | 54 | impl.reset(); 55 | } 56 | 57 | void add_file( implementation_type &impl, const boost::filesystem::path &path ) 58 | { 59 | if ( ! boost::filesystem::is_regular_file( path ) && ! boost::filesystem::is_directory( path ) ) { 60 | throw std::invalid_argument("boost::asio::basic_file_monitor_service::add_file: \"" + 61 | path.string() + "\" is not a valid file or directory entry"); 62 | } 63 | impl->add_file( path ); 64 | } 65 | 66 | void remove_file( implementation_type &impl, const boost::filesystem::path &path ) 67 | { 68 | impl->remove_file( path ); 69 | } 70 | 71 | /** 72 | * Blocking event monitor. 73 | */ 74 | file_monitor_event monitor( implementation_type &impl, boost::system::error_code &ec ) 75 | { 76 | return impl->popfront_event( ec ); 77 | } 78 | 79 | template 80 | class monitor_operation 81 | { 82 | public: 83 | monitor_operation( implementation_type &impl, boost::asio::io_service &io_service, Handler handler ) 84 | : impl_( impl ), 85 | io_service_( io_service ), 86 | work_( io_service ), 87 | handler_( handler ) 88 | { 89 | } 90 | 91 | void operator()() const 92 | { 93 | implementation_type impl = impl_.lock(); 94 | if( impl ) { 95 | boost::system::error_code ec; 96 | file_monitor_event ev = impl->popfront_event( ec ); 97 | this->io_service_.post( boost::asio::detail::bind_handler( handler_, ec, ev ) ); 98 | } 99 | else { 100 | this->io_service_.post( boost::asio::detail::bind_handler( handler_, 101 | boost::asio::error::operation_aborted, 102 | file_monitor_event() ) ); 103 | } 104 | } 105 | 106 | private: 107 | boost::weak_ptr impl_; 108 | boost::asio::io_service &io_service_; 109 | boost::asio::io_service::work work_; 110 | Handler handler_; 111 | }; 112 | 113 | /** 114 | * Non-blocking event monitor. 115 | */ 116 | template 117 | void async_monitor( implementation_type &impl, Handler handler ) 118 | { 119 | this->async_monitor_io_service_.post( monitor_operation( impl, this->get_io_service(), handler ) ); 120 | } 121 | 122 | private: 123 | void shutdown_service() 124 | { 125 | } 126 | 127 | boost::asio::io_service async_monitor_io_service_; 128 | boost::scoped_ptr async_monitor_work_; 129 | std::thread async_monitor_thread_; 130 | }; 131 | 132 | template 133 | boost::asio::io_service::id basic_file_monitor_service::id; 134 | 135 | } // filemonitor namespace 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /include/filemonitor/windows/file_monitor_impl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // TODO migrate to standard C++ 4 | #include // TODO migrate to standard C++ 5 | 6 | #include // TODO migrate to standard C++ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | namespace filemonitor { 17 | 18 | class file_monitor_impl : 19 | public boost::enable_shared_from_this 20 | { 21 | 22 | public: 23 | file_monitor_impl() 24 | : run_(true), 25 | iocp_(init_iocp()), 26 | work_thread_( &file_monitor_impl::work_thread, this ) 27 | {} 28 | 29 | ~file_monitor_impl() 30 | { 31 | // The work thread is stopped and joined. 32 | stop_work_thread(); 33 | work_thread_.join(); 34 | } 35 | 36 | void add_file( const boost::filesystem::path &file ) 37 | { 38 | // TODO update the flags appropriately. May not need write. Also the open permissions may need to be changed. 39 | HANDLE handle = CreateFileA( file.string().c_str(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL ); 40 | if( handle == INVALID_HANDLE_VALUE ) { 41 | DWORD last_error = GetLastError(); 42 | // TODO move this out of boost namespace 43 | boost::system::system_error e(boost::system::error_code(last_error, boost::system::get_system_category()), "boost::asio::basic_dir_monitor_service::add_directory: CreateFile failed"); 44 | boost::throw_exception(e); 45 | } 46 | 47 | // No smart pointer can be used as the pointer must travel as a completion key 48 | // through the I/O completion port module. 49 | completion_key *ck = new completion_key( handle, file ); 50 | iocp_ = CreateIoCompletionPort( ck->handle, iocp_, reinterpret_cast(ck), 0 ); 51 | if( iocp_ == NULL ) { 52 | delete ck; 53 | DWORD last_error = GetLastError(); 54 | // TODO move this out of the boost namespace 55 | boost::system::system_error e(boost::system::error_code(last_error, boost::system::get_system_category()), "boost::asio::basic_dir_monitor_service::add_directory: CreateIoCompletionPort failed"); 56 | boost::throw_exception(e); 57 | } 58 | 59 | DWORD bytes_transferred; // ignored 60 | // TODO change filter to something readable 61 | DWORD filter = 0x1FF; 62 | BOOL res = ReadDirectoryChangesW(ck->handle, ck->buffer, sizeof(ck->buffer), FALSE, filter, &bytes_transferred, &ck->overlapped, NULL); 63 | if( !res ) { 64 | delete ck; 65 | DWORD last_error = GetLastError(); 66 | // TODO move this out of the boost namespace 67 | boost::system::system_error e(boost::system::error_code(last_error, boost::system::get_system_category()), "boost::asio::basic_dir_monitor_service::add_directory: ReadDirectoryChangesW failed"); 68 | boost::throw_exception(e); 69 | } 70 | 71 | std::lock_guard lock( paths_mutex_ ); 72 | paths_.insert( file ); 73 | } 74 | 75 | void remove_file( const boost::filesystem::path &file ) 76 | { 77 | std::lock_guard lock( paths_mutex_ ); 78 | paths_.erase( file ); 79 | } 80 | 81 | void destroy() 82 | { 83 | std::lock_guard lock( events_mutex_ ); 84 | run_ = false; 85 | events_cond_.notify_all(); 86 | } 87 | 88 | file_monitor_event popfront_event( boost::system::error_code &ec ) 89 | { 90 | std::unique_lock lock( events_mutex_ ); 91 | while( run_ && events_.empty() ) { 92 | events_cond_.wait( lock ); 93 | } 94 | file_monitor_event ev; 95 | if( ! events_.empty() ) { 96 | ec = boost::system::error_code(); 97 | ev = events_.front(); 98 | events_.pop_front(); 99 | } else { 100 | ec = boost::asio::error::operation_aborted; 101 | } 102 | return ev; 103 | } 104 | 105 | void pushback_event( const file_monitor_event &ev ) 106 | { 107 | std::lock_guard lock( events_mutex_ ); 108 | if( run_ ) { 109 | events_.push_back( ev ); 110 | events_cond_.notify_all(); 111 | } 112 | } 113 | 114 | private: 115 | 116 | struct completion_key 117 | { 118 | completion_key( HANDLE h, const boost::filesystem::path& p ) 119 | : handle(h), 120 | path(p) 121 | { 122 | ZeroMemory(&overlapped, sizeof(overlapped)); 123 | } 124 | 125 | HANDLE handle; 126 | boost::filesystem::path path; 127 | char buffer[1024]; 128 | OVERLAPPED overlapped; 129 | }; 130 | 131 | HANDLE init_iocp() 132 | { 133 | HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); 134 | if (iocp == NULL) 135 | { 136 | DWORD last_error = GetLastError(); 137 | // TODO move this out of the boost namespace 138 | boost::system::system_error e(boost::system::error_code(last_error, boost::system::get_system_category()), "boost::asio::basic_dir_monitor_service::init_iocp: CreateIoCompletionPort failed"); 139 | boost::throw_exception(e); 140 | } 141 | return iocp; 142 | } 143 | 144 | void work_thread() 145 | { 146 | while( running() ) { 147 | DWORD bytes_transferred; 148 | completion_key *ck; 149 | OVERLAPPED *overlapped; 150 | BOOL res = GetQueuedCompletionStatus( iocp_, &bytes_transferred, reinterpret_cast(&ck), &overlapped, INFINITE ); 151 | if( !res ) 152 | { 153 | DWORD last_error = GetLastError(); 154 | // TODO move this out of the boost namespace 155 | boost::system::system_error e(boost::system::error_code(last_error, boost::system::get_system_category()), "boost::asio::basic_dir_monitor_service::work_thread: GetQueuedCompletionStatus failed"); 156 | boost::throw_exception(e); 157 | } 158 | 159 | // TODO we may leak completion_keys if we exit while watching multiple files. Need to check this 160 | 161 | if( ck ) 162 | { 163 | // If a file handle is closed GetQueuedCompletionStatus() returns and bytes_transferred will be set to 0. 164 | // The completion key must be deleted then as it won't be used anymore. 165 | if( !bytes_transferred ) { 166 | delete ck; 167 | continue; 168 | } 169 | 170 | DWORD offset = 0; 171 | PFILE_NOTIFY_INFORMATION fni; 172 | do 173 | { 174 | fni = reinterpret_cast( ck->buffer + offset ); 175 | file_monitor_event::event_type type = file_monitor_event::null; 176 | switch( fni->Action ) { 177 | case FILE_ACTION_ADDED: type = file_monitor_event::added; break; 178 | case FILE_ACTION_REMOVED: type = file_monitor_event::removed; break; 179 | case FILE_ACTION_MODIFIED: type = file_monitor_event::modified; break; 180 | case FILE_ACTION_RENAMED_OLD_NAME: type = file_monitor_event::renamed_old; break; 181 | case FILE_ACTION_RENAMED_NEW_NAME: type = file_monitor_event::renamed_new; break; 182 | } 183 | pushback_event( file_monitor_event( boost::filesystem::path( ck->path ) / to_utf8( fni->FileName, fni->FileNameLength / sizeof( WCHAR ) ), type ) ); 184 | offset += fni->NextEntryOffset; 185 | } 186 | while( fni->NextEntryOffset ); 187 | 188 | ZeroMemory( &ck->overlapped, sizeof( ck->overlapped ) ); 189 | // TODO change 0x1FF to a map that makes actual sense 190 | DWORD filter = 0x1FF; 191 | BOOL res = ReadDirectoryChangesW( ck->handle, ck->buffer, sizeof( ck->buffer ), FALSE, filter, &bytes_transferred, &ck->overlapped, NULL ); 192 | if( !res ) 193 | { 194 | delete ck; 195 | DWORD last_error = GetLastError(); 196 | // TOOD move out of boost and into something more cinder specific 197 | boost::system::system_error e( boost::system::error_code( last_error, boost::system::get_system_category() ), "boost::asio::basic_dir_monitor_service::work_thread: ReadDirectoryChangesW failed" ); 198 | boost::throw_exception( e ); 199 | } 200 | } 201 | } 202 | } 203 | 204 | bool running() 205 | { 206 | // Access to run_ is sychronized with stop_work_thread(). 207 | std::lock_guard lock( work_thread_mutex_ ); 208 | return run_; 209 | } 210 | 211 | void stop_work_thread() 212 | { 213 | // Access to run_ is sychronized with running(). 214 | std::lock_guard lock( work_thread_mutex_ ); 215 | run_ = false; 216 | } 217 | 218 | std::string to_utf8(WCHAR *filename, DWORD length) 219 | { 220 | // TODO make this more cinder-like 221 | int size = WideCharToMultiByte(CP_UTF8, 0, filename, length, NULL, 0, NULL, NULL); 222 | if (!size) 223 | { 224 | DWORD last_error = GetLastError(); 225 | boost::system::system_error e(boost::system::error_code(last_error, boost::system::get_system_category()), "boost::asio::basic_dir_monitor_service::to_utf8: WideCharToMultiByte failed"); 226 | boost::throw_exception(e); 227 | } 228 | 229 | char buffer[1024]; 230 | std::unique_ptr dynbuffer; 231 | if( size > sizeof(buffer) ) 232 | { 233 | dynbuffer.reset( new char[size] ); 234 | size = WideCharToMultiByte( CP_UTF8, 0, filename, length, dynbuffer.get(), size, NULL, NULL ); 235 | } 236 | else 237 | { 238 | size = WideCharToMultiByte( CP_UTF8, 0, filename, length, buffer, sizeof(buffer), NULL, NULL ); 239 | } 240 | 241 | if( !size ) 242 | { 243 | DWORD last_error = GetLastError(); 244 | boost::system::system_error e(boost::system::error_code(last_error, boost::system::get_system_category()), "boost::asio::basic_dir_monitor_service::to_utf8: WideCharToMultiByte failed"); 245 | boost::throw_exception(e); 246 | } 247 | 248 | return dynbuffer.get() ? std::string( dynbuffer.get(), size ) : std::string( buffer, size ); 249 | } 250 | 251 | HANDLE iocp_; 252 | 253 | bool run_; 254 | std::mutex work_thread_mutex_; 255 | std::thread work_thread_; 256 | 257 | std::mutex paths_mutex_; 258 | boost::unordered_set paths_; 259 | 260 | std::mutex events_mutex_; 261 | std::condition_variable events_cond_; 262 | std::deque events_; 263 | 264 | }; 265 | 266 | } // filemonitor namespace 267 | -------------------------------------------------------------------------------- /src/FileWatcher.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016, Lucas Vickers - All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that 5 | the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and 8 | the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 10 | the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 13 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 14 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 15 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 16 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 17 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 18 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 19 | POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | #include "FileWatcher.h" 23 | 24 | #include "cinder/app/App.h" 25 | 26 | using namespace ci; 27 | using namespace std; 28 | 29 | namespace filewatcher { 30 | 31 | 32 | // ---------------------------------------------------------------------------------------------------- 33 | // MARK: - FileWatcher 34 | // ---------------------------------------------------------------------------------------------------- 35 | FileWatcher::FileWatcher() 36 | { 37 | // TODO move this into cinder's asio 38 | 39 | // setup our filemonitor asio service and link it to the internal cinder io_service 40 | mFileMonitor = unique_ptr( new filemonitor::FileMonitor( mIoService ) ); 41 | 42 | mAsioWork = auto_ptr( new boost::asio::io_service::work( mIoService ) ); 43 | 44 | // prime the first handler 45 | mFileMonitor->asyncMonitor( std::bind( &FileWatcher::fileEventHandler, this, std::placeholders::_1, std::placeholders::_2 ) ); 46 | } 47 | 48 | FileWatcher *FileWatcher::instance() 49 | { 50 | static FileWatcher sInstance; 51 | return &sInstance; 52 | } 53 | 54 | void FileWatcher::registerUpdate() 55 | { 56 | app::App::get()->getSignalUpdate().connect( bind( &FileWatcher::update, this ) ); 57 | } 58 | 59 | void FileWatcher::poll() 60 | { 61 | mIoService.poll(); 62 | } 63 | 64 | 65 | WatchedTarget FileWatcher::watchFile( const fs::path &file, 66 | const WatchCallback &callback ) 67 | { 68 | uint64_t wid = instance()->mFileMonitor->addFile( file ); 69 | WatchedTarget obj = WatchedTarget( wid , file, callback ); 70 | // register the callback 71 | auto it = instance()->mRegisteredCallbacks.insert( std::pair( wid, obj.mCallback ) ); 72 | 73 | // double check item didn't exist, should be impossible 74 | CI_ASSERT( it.second ); 75 | return obj; 76 | } 77 | 78 | WatchedTarget FileWatcher::watchPath( const fs::path &path, 79 | const std::string ®ex, 80 | const WatchCallback &callback ) 81 | { 82 | uint64_t wid = instance()->mFileMonitor->addPath( path, regex ); 83 | WatchedTarget obj = WatchedTarget( wid , path, regex, callback ); 84 | auto it = instance()->mRegisteredCallbacks.insert( std::pair( wid, obj.mCallback ) ); 85 | 86 | // double check item didn't exist, should be impossible 87 | CI_ASSERT( it.second ); 88 | return obj; 89 | } 90 | 91 | void FileWatcher::removeWatch( uint64_t wid ) 92 | { 93 | 94 | int r = instance()->mRegisteredCallbacks.erase( wid ); 95 | 96 | CI_ASSERT( r == 1 ); 97 | mFileMonitor->remove( wid ); 98 | } 99 | 100 | void FileWatcher::updateCallback( uint64_t wid, const WatchCallback &callback ) 101 | { 102 | int r = instance()->mRegisteredCallbacks.erase( wid ); 103 | CI_ASSERT( r == 1 ); 104 | instance()->mRegisteredCallbacks.insert( std::pair( wid, callback ) ); 105 | } 106 | 107 | 108 | void FileWatcher::update() 109 | { 110 | mIoService.poll(); 111 | } 112 | 113 | void FileWatcher::fileEventHandler( const boost::system::error_code &ec, 114 | const filemonitor::FileMonitorEvent &ev ) 115 | { 116 | // TODO do stuff 117 | cout << "EC: " << ec << endl; 118 | cout << "EV: " << ev << endl << endl; 119 | 120 | //! re-process if no error 121 | if( ! ec && ev.type != filemonitor::FileMonitorEvent::NONE ) 122 | { 123 | auto it = mRegisteredCallbacks.find( ev.id ); 124 | 125 | //! it's possible that we can remove a watch before the callback triggered, 126 | //! so don't treat this as an error. 127 | if( it != mRegisteredCallbacks.end() ) { 128 | it->second( ev.path, ev.type ); 129 | } 130 | } else { 131 | //! TODO some error handling 132 | } 133 | 134 | 135 | //! add the next handler 136 | mFileMonitor->asyncMonitor( std::bind( &FileWatcher::fileEventHandler, this, std::placeholders::_1, std::placeholders::_2 ) ); 137 | } 138 | 139 | // ---------------------------------------------------------------------------------------------------- 140 | // MARK: - WatchedTarget 141 | // ---------------------------------------------------------------------------------------------------- 142 | WatchedTarget::~WatchedTarget() 143 | { 144 | cout << "Destructor called"; 145 | //! mWatchID of 0 means we're a dead object who transfered ownership 146 | if( mWatchId > 0 ) { 147 | FileWatcher::instance()->removeWatch( mWatchId ); 148 | cout <<", and removed a watch"; 149 | } 150 | cout << "." << endl; 151 | } 152 | 153 | WatchedTarget::WatchedTarget( WatchedTarget &&other ) 154 | { 155 | mWatchId = other.mWatchId; 156 | mPath = other.mPath; 157 | mCallback = other.mCallback; 158 | mRegexMatch = other.mRegexMatch; 159 | 160 | //! Setting id to 0 tells us it's a dead object 161 | other.mWatchId = 0; 162 | other.mPath = ""; 163 | other.mCallback = nullptr; 164 | other.mRegexMatch = ""; 165 | } 166 | 167 | WatchedTarget& WatchedTarget::operator=( WatchedTarget &&rhs ) 168 | { 169 | mWatchId = rhs.mWatchId; 170 | mPath = rhs.mPath; 171 | mCallback = rhs.mCallback; 172 | mRegexMatch = rhs.mRegexMatch; 173 | 174 | //! Setting id to 0 tells us it's a dead object 175 | rhs.mWatchId = 0; 176 | rhs.mPath = ""; 177 | rhs.mCallback = nullptr; 178 | rhs.mRegexMatch = ""; 179 | 180 | return *this; 181 | } 182 | 183 | } // namespace filewatcher 184 | -------------------------------------------------------------------------------- /src/filemonitor/fsevents/FileMonitorImpl.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016, Lucas Vickers - All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that 5 | the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and 8 | the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 10 | the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 13 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 14 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 15 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 16 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 17 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 18 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 19 | POSSIBILITY OF SUCH DAMAGE. 20 | */ 21 | 22 | #include "FileMonitorImpl.h" 23 | #include 24 | 25 | #include 26 | 27 | namespace filemonitor { 28 | 29 | FileMonitorImpl::~FileMonitorImpl() 30 | { 31 | // The work thread is stopped and joined. 32 | stopWorkThread(); 33 | mWorkThread.join(); 34 | stopFsevents(); 35 | } 36 | 37 | uint64_t FileMonitorImpl::addPath( const boost::filesystem::path &path, const std::string ®exMatch ) 38 | { 39 | std::lock_guard lock( mPathsMutex ); 40 | 41 | uint64_t id = mNextPathId; 42 | mNextPathId += 2; 43 | 44 | auto iter = mPaths.emplace( id, PathEntry( path, regexMatch, id ) ); 45 | assert( iter.second ); 46 | 47 | incrementTarget( path ); 48 | 49 | stopFsevents(); 50 | startFsevents(); 51 | 52 | return id; 53 | } 54 | 55 | uint64_t FileMonitorImpl::addFile( const boost::filesystem::path &file ) 56 | { 57 | std::lock_guard lock( mPathsMutex ); 58 | 59 | uint64_t id = mNextFileId; 60 | mNextFileId += 2; 61 | 62 | auto iter = mFiles.emplace( id, FileEntry( file, id ) ); 63 | assert( iter.second ); 64 | // iter.first is the multimap iter. iter is filesystem::path, pointer to the FileEntry 65 | mFilesMmap.insert( std::make_pair( file, &(iter.first->second) ) ); 66 | 67 | assert( mFilesMmap.size() == mFiles.size() ); 68 | 69 | // increment the file target (can be multiple watches on a directory) 70 | // fsevents wants the path not the file, so pass the parent_path 71 | incrementTarget( file.parent_path() ); 72 | 73 | stopFsevents(); 74 | startFsevents(); 75 | 76 | return id; 77 | } 78 | 79 | void FileMonitorImpl::remove( uint64_t id ) 80 | { 81 | std::lock_guard lock( mPathsMutex ); 82 | 83 | boost::filesystem::path path; 84 | 85 | // remove from containers 86 | if( id % 2 == 0 ) { 87 | // even is file 88 | path = removeEntry( id, mFiles, mFilesMmap ); 89 | decrementTarget( path.parent_path() ); 90 | } else { 91 | // odd is path 92 | path = removeEntry( id, mPaths ); 93 | decrementTarget( path ); 94 | } 95 | 96 | stopFsevents(); 97 | startFsevents(); 98 | } 99 | 100 | void FileMonitorImpl::destroy() 101 | { 102 | std::lock_guard lock( mEventsMutex ); 103 | mRun = false; 104 | mEventsCond.notify_all(); 105 | } 106 | 107 | FileMonitorEvent FileMonitorImpl::popFrontEvent( boost::system::error_code &ec ) 108 | { 109 | std::unique_lock lock( mEventsMutex ); 110 | while( mRun && mEvents.empty() ) { 111 | mEventsCond.wait( lock ); 112 | } 113 | FileMonitorEvent ev; 114 | if( ! mEvents.empty() ) { 115 | ec = boost::system::error_code(); 116 | ev = mEvents.front(); 117 | mEvents.pop_front(); 118 | } else { 119 | ec = boost::asio::error::operation_aborted; 120 | } 121 | return ev; 122 | } 123 | 124 | void FileMonitorImpl::verifyEvent( const boost::filesystem::path &path, FileMonitorEvent::EventType type ) 125 | { 126 | if( ! mRun ) { 127 | return; 128 | } 129 | 130 | // TODO confirm this winds up in proper worker thread 131 | 132 | //! check for exact file matches, streamlined map search to keep complexity minimal 133 | auto range = mFilesMmap.equal_range( path ); 134 | for( auto it = range.first; it != range.second; ++it ) { 135 | pushBackEvent( FileMonitorEvent( path, type, it->second->entryID ) ); 136 | } 137 | 138 | //! check every regex possibility, which is computationally more expensive 139 | for( const auto &it : mPaths ) { 140 | if( std::regex_match( path.string(), it.second.regexMatch ) ) { 141 | pushBackEvent( FileMonitorEvent( path, type, it.second.entryID ) ); 142 | } 143 | } 144 | } 145 | 146 | void FileMonitorImpl::pushBackEvent( const FileMonitorEvent &ev ) 147 | { 148 | std::lock_guard lock( mEventsMutex ); 149 | mEvents.push_back( ev ); 150 | mEventsCond.notify_all(); 151 | } 152 | 153 | void FileMonitorImpl::startFsevents() 154 | { 155 | if ( mPaths.size() == 0 && mFiles.size() == 0 ) { 156 | mFsevents = nullptr; 157 | return; 158 | } 159 | 160 | // Need to pass FSEvents an array of unique paths, both files and folders. 161 | // We assume that there are no duplicates between pathsMmap and filesMmap 162 | // since one is only files and one only folders 163 | 164 | CFMutableArrayRef allPaths = CFArrayCreateMutable( kCFAllocatorDefault, mAllTargetsMap.size(), &kCFTypeArrayCallBacks ); 165 | 166 | for( const auto &path : mAllTargetsMap ) { 167 | 168 | CFStringRef cfstr = CFStringCreateWithCString( kCFAllocatorDefault, path.first.string().c_str(), kCFStringEncodingUTF8 ); 169 | CFArrayAppendValue( allPaths, cfstr ); 170 | CFRelease(cfstr); 171 | } 172 | 173 | FSEventStreamContext context = {0, this, NULL, NULL, NULL}; 174 | mFsevents = FSEventStreamCreate( kCFAllocatorDefault, 175 | &filemonitor::FileMonitorImpl::fseventsCallback, 176 | &context, 177 | allPaths, 178 | //todo determine when we need to support historical events (if ever, I hope never) 179 | kFSEventStreamEventIdSinceNow, // only new modifications 180 | (CFTimeInterval) 1.0, // 1 second latency interval 181 | kFSEventStreamCreateFlagFileEvents ); 182 | FSEventStreamRetain( mFsevents ); 183 | CFRelease( allPaths ); 184 | 185 | if( ! mFsevents ) 186 | { 187 | // TODO move this out of boost namespace 188 | boost::system::system_error e( boost::system::error_code( errno, boost::system::get_system_category() ), 189 | "filemonitor::FileMonitorImpl::init_fsevents: fsevents failed" ); 190 | boost::throw_exception(e); 191 | } 192 | 193 | while( !mRunloop ) { 194 | std::this_thread::yield(); 195 | } 196 | 197 | FSEventStreamScheduleWithRunLoop( mFsevents, mRunloop, kCFRunLoopDefaultMode ); 198 | FSEventStreamStart( mFsevents ); 199 | mRunloopCond.notify_all(); 200 | FSEventStreamFlushAsync( mFsevents ); 201 | } 202 | 203 | void FileMonitorImpl::stopFsevents() 204 | { 205 | if (mFsevents) 206 | { 207 | FSEventStreamStop( mFsevents ); 208 | // TODO do we need to unschedule this? 209 | // FSEventStreamUnscheduleFromRunLoop(mFsevents, mRunloop, kCFRunLoopDefaultMode); 210 | FSEventStreamInvalidate( mFsevents ); 211 | FSEventStreamRelease( mFsevents ); 212 | } 213 | mFsevents = nullptr; 214 | } 215 | 216 | void FileMonitorImpl::fseventsCallback( ConstFSEventStreamRef streamRef, 217 | void *clientCallBackInfo, 218 | size_t numEvents, 219 | void *eventPaths, 220 | const FSEventStreamEventFlags eventFlags[], 221 | const FSEventStreamEventId eventIds[] ) 222 | { 223 | size_t i; 224 | char **paths = (char**)eventPaths; 225 | FileMonitorImpl* impl = (FileMonitorImpl*)clientCallBackInfo; 226 | 227 | for( i = 0; i < numEvents; ++i ) 228 | { 229 | // TODO keep track of these, because we don't necessarily want to return folders as events 230 | // kFSEventStreamEventFlagItemIsDir 231 | // kFSEventStreamEventFlagItemIsFile 232 | 233 | boost::filesystem::path path( paths[i] ); 234 | if( eventFlags[i] & kFSEventStreamEventFlagNone ) { 235 | // TODO log this 236 | } 237 | if( eventFlags[i] & kFSEventStreamEventFlagMustScanSubDirs ) { 238 | // Events coalesced into a single event. Docs recommend a directory scan to figure out what 239 | // changed. I should log errors and see if this ever actually happens. 240 | } 241 | if( eventFlags[i] & kFSEventStreamEventFlagItemCreated ) { 242 | impl->verifyEvent( path, FileMonitorEvent::ADDED ); 243 | } 244 | if( eventFlags[i] & kFSEventStreamEventFlagItemRemoved ) { 245 | impl->verifyEvent( path, FileMonitorEvent::REMOVED ); 246 | } 247 | if( eventFlags[i] & kFSEventStreamEventFlagItemModified ) { 248 | impl->verifyEvent( path, FileMonitorEvent::MODIFIED ); 249 | } 250 | if( eventFlags[i] & kFSEventStreamEventFlagItemRenamed ) 251 | { 252 | if( ! boost::filesystem::exists( path ) ) 253 | { 254 | impl->verifyEvent( path, FileMonitorEvent::RENAMED_OLD ); 255 | } 256 | else 257 | { 258 | impl->verifyEvent( path, FileMonitorEvent::RENAMED_NEW ); 259 | } 260 | } 261 | } 262 | } 263 | 264 | void FileMonitorImpl::workThread() 265 | { 266 | mRunloop = CFRunLoopGetCurrent(); 267 | 268 | while( running() ) 269 | { 270 | std::unique_lock lock( mRunloopMutex ); 271 | mRunloopCond.wait( lock ); 272 | CFRunLoopRun(); 273 | } 274 | } 275 | 276 | bool FileMonitorImpl::running() 277 | { 278 | std::lock_guard lock( mWorkThreadMutex ); 279 | return mRun; 280 | } 281 | 282 | void FileMonitorImpl::stopWorkThread() 283 | { 284 | std::lock_guard lock( mWorkThreadMutex ); 285 | mRun = false; 286 | CFRunLoopStop( mRunloop ); // exits the thread 287 | mRunloopCond.notify_all(); 288 | } 289 | 290 | void FileMonitorImpl::incrementTarget( const boost::filesystem::path &path ) 291 | { 292 | auto it = mAllTargetsMap.find( path ); 293 | if( it != mAllTargetsMap.end() ) { 294 | it->second += 1; 295 | } else { 296 | auto it = mAllTargetsMap.insert( std::make_pair( path, 1 ) ); 297 | assert( it.second ); 298 | } 299 | } 300 | 301 | void FileMonitorImpl::decrementTarget( const boost::filesystem::path &path ) 302 | { 303 | auto it = mAllTargetsMap.find( path ); 304 | assert( it != mAllTargetsMap.end() ); 305 | 306 | if( it->second == 1 ) { 307 | mAllTargetsMap.erase( it ); 308 | } else { 309 | it->second -= 1; 310 | } 311 | } 312 | 313 | } // filemonitor namespace 314 | -------------------------------------------------------------------------------- /tests/UnitTests/src/AssignmentTest.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvickers/Cinder-FileWatcher/0686a7fa3bc0ee1e084041d995dae688bf8d8651/tests/UnitTests/src/AssignmentTest.cpp -------------------------------------------------------------------------------- /tests/UnitTests/src/BasicTest.cpp: -------------------------------------------------------------------------------- 1 | #include "cinder/app/Platform.h" 2 | #include "cinder/app/App.h" 3 | #include "cinder/Utilities.h" 4 | #include "catch.hpp" 5 | 6 | #include 7 | 8 | #include "utils.h" 9 | #include "FileWatcher.h" 10 | 11 | using namespace ci; 12 | using namespace std; 13 | using namespace ci::app; 14 | 15 | 16 | TEST_CASE( "BasicFileTest" ) 17 | { 18 | SECTION( "Single file is watched, modified, and change detected within 2 seconds." ) 19 | { 20 | fs::remove_all( getTestingPath() ); 21 | CI_ASSERT( createTestingDir( getTestingPath() ) ); 22 | 23 | fs::path target = getTestingPath() / "basictest.txt"; 24 | fs::path dummy = getTestingPath() / "dummy.txt"; 25 | fs::path dummy2 = getTestingPath() / "dummy2.txt"; 26 | writeToFile( target, "start" ); 27 | writeToFile( dummy, "dummy" ); 28 | 29 | filewatcher::WatchedTarget watchedFile; 30 | 31 | ActionMap actions; 32 | watchedFile = filewatcher::FileWatcher::watchFile( target, 33 | [ &actions ]( const ci::fs::path& file, filewatcher::EventType type ) { 34 | actions[file].process( type ); 35 | } ); 36 | 37 | CI_ASSERT( watchedFile.isFile() ); 38 | 39 | // modify the temp file 40 | writeToFile( target, "finish" ); 41 | writeToFile( dummy, "fake" ); 42 | writeToFile( dummy2, "fake" ); 43 | 44 | // wait 2 seconds 45 | std::chrono::time_point waitTime = 46 | std::chrono::system_clock::now() + std::chrono::seconds( 2 ); 47 | 48 | // poll the service to force a check before app updates 49 | while( std::chrono::system_clock::now() < waitTime ) { 50 | filewatcher::FileWatcher::instance()->poll(); 51 | ci::sleep( 1000 / 30 ); 52 | } 53 | 54 | CI_ASSERT( actions[target].modified >= 1 ); 55 | CI_ASSERT( actions[dummy].modified == 0 ); 56 | CI_ASSERT( actions[dummy2].modified == 0 && actions[dummy2].added == 0 ); 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/UnitTests/src/ContainerTest.cpp: -------------------------------------------------------------------------------- 1 | #include "cinder/app/Platform.h" 2 | #include "cinder/app/App.h" 3 | #include "cinder/Utilities.h" 4 | #include "catch.hpp" 5 | 6 | #include 7 | 8 | #include "utils.h" 9 | #include "FileWatcher.h" 10 | 11 | using namespace ci; 12 | using namespace std; 13 | using namespace ci::app; 14 | 15 | void prepPath( fs::path path ) 16 | { 17 | fs::remove_all( path ); 18 | CI_ASSERT( createTestingDir( path ) ); 19 | } 20 | 21 | TEST_CASE( "BasicContainerTest" ) 22 | { 23 | SECTION( "A container is used to watch a combination of files and paths" ) 24 | { 25 | fs::path root = getTestingPath(); 26 | fs::remove_all( root ); 27 | CI_ASSERT( createTestingDir( root ) ); 28 | 29 | ActionMap actions; 30 | auto func = [ &actions ]( const ci::fs::path& file, filewatcher::EventType type ) { 31 | actions[file].process( type ); 32 | }; 33 | 34 | filewatcher::WatchedTargetMap fileMap; 35 | 36 | // watch varied files and paths via container 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/UnitTests/src/DeleteTest.cpp: -------------------------------------------------------------------------------- 1 | #include "cinder/app/Platform.h" 2 | #include "cinder/app/App.h" 3 | #include "cinder/Utilities.h" 4 | #include "catch.hpp" 5 | 6 | #include 7 | 8 | #include "utils.h" 9 | #include "FileWatcher.h" 10 | 11 | using namespace ci; 12 | using namespace std; 13 | using namespace ci::app; 14 | 15 | 16 | TEST_CASE( "FileDeletionTest" ) 17 | { 18 | SECTION( "Single file is deleted and delete detected within 2 seconds." ) 19 | { 20 | fs::remove_all( getTestingPath() ); 21 | CI_ASSERT( createTestingDir( getTestingPath() ) ); 22 | 23 | fs::path target = getTestingPath() / "todelete.txt"; 24 | writeToFile( target, "start" ); 25 | 26 | filewatcher::WatchedTarget watchedFile; 27 | 28 | ActionMap actions; 29 | watchedFile = filewatcher::FileWatcher::watchFile( target, 30 | [ &actions ]( const ci::fs::path& file, filewatcher::EventType type ) { 31 | actions[file].process( type ); 32 | } ); 33 | 34 | // delete it 35 | fs::remove( target ); 36 | 37 | // wait 2 seconds 38 | std::chrono::time_point waitTime = 39 | std::chrono::system_clock::now() + std::chrono::seconds( 2 ); 40 | 41 | // poll the service to force a check before app updates 42 | while( std::chrono::system_clock::now() < waitTime ) { 43 | filewatcher::FileWatcher::instance()->poll(); 44 | ci::sleep( 1000 / 30 ); 45 | } 46 | 47 | CI_ASSERT( actions[target].removed == 1 ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/UnitTests/src/DoubleTest.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvickers/Cinder-FileWatcher/0686a7fa3bc0ee1e084041d995dae688bf8d8651/tests/UnitTests/src/DoubleTest.cpp -------------------------------------------------------------------------------- /tests/UnitTests/src/OverWriteTest.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvickers/Cinder-FileWatcher/0686a7fa3bc0ee1e084041d995dae688bf8d8651/tests/UnitTests/src/OverWriteTest.cpp -------------------------------------------------------------------------------- /tests/UnitTests/src/PerformanceTest.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvickers/Cinder-FileWatcher/0686a7fa3bc0ee1e084041d995dae688bf8d8651/tests/UnitTests/src/PerformanceTest.cpp -------------------------------------------------------------------------------- /tests/UnitTests/src/RegexTest.cpp: -------------------------------------------------------------------------------- 1 | #include "cinder/app/Platform.h" 2 | #include "cinder/app/App.h" 3 | #include "cinder/Utilities.h" 4 | #include "catch.hpp" 5 | 6 | #include 7 | #include 8 | 9 | #include "utils.h" 10 | #include "FileWatcher.h" 11 | 12 | using namespace ci; 13 | using namespace std; 14 | using namespace ci::app; 15 | 16 | 17 | TEST_CASE( "RegexTest" ) 18 | { 19 | SECTION( "Directory is watched with regex expression" ) 20 | { 21 | fs::remove_all( getTestingPath() ); 22 | CI_ASSERT( createTestingDir( getTestingPath() ) ); 23 | 24 | std::string regex = ".*\\.jpg$"; 25 | fs::path path = getTestingPath(); 26 | 27 | fs::path target = path / "exists.jpg"; 28 | fs::path dummy = path / "dummy.png"; 29 | fs::path remove = path / "remove.jpg"; 30 | fs::path awesomejpg = path / "awesome.jpg"; 31 | fs::path awesomepng = path / "awesome.png"; 32 | 33 | writeToFile( target, "start" ); 34 | writeToFile( dummy, "start" ); 35 | writeToFile( remove, "delete" ); 36 | 37 | ActionMap actions; 38 | filewatcher::WatchedTarget watchedPath; 39 | 40 | watchedPath = filewatcher::FileWatcher::watchPath( path, regex, 41 | [ &actions ]( const ci::fs::path& file, filewatcher::EventType type ) { 42 | actions[file].process( type ); 43 | } ); 44 | 45 | CI_ASSERT( watchedPath.isPath() ); 46 | 47 | // modify the files 48 | writeToFile( target, "finish" ); 49 | writeToFile( dummy, "fake" ); 50 | writeToFile( awesomejpg, "cool" ); 51 | writeToFile( awesomepng, "cool2" ); 52 | fs::remove( remove ); 53 | 54 | // wait 2 seconds 55 | std::chrono::time_point waitTime = 56 | std::chrono::system_clock::now() + std::chrono::seconds( 2 ); 57 | 58 | // poll the service to force a check before app updates 59 | while( std::chrono::system_clock::now() < waitTime ) { 60 | filewatcher::FileWatcher::instance()->poll(); 61 | ci::sleep( 1000 / 30 ); 62 | } 63 | 64 | CI_ASSERT( actions[target].modified >= 1 ); 65 | CI_ASSERT( actions[dummy].modified == 0 ); 66 | CI_ASSERT( actions[remove].removed == 1 ); 67 | CI_ASSERT( actions[awesomejpg].modified >= 1 ); 68 | CI_ASSERT( actions[awesomepng].modified == 0 ); 69 | } 70 | 71 | SECTION( "Directory is watched with regex expression on subfolder and subfolder triggers watch." ) 72 | { 73 | fs::path root = getTestingPath(); 74 | fs::remove_all( root ); 75 | CI_ASSERT( createTestingDir( root ) ); 76 | 77 | fs::path subdir = root / "subdir"; 78 | CI_ASSERT( createTestingDir( subdir) ); 79 | 80 | std::string regex = ".*\\/subdir\\/.*\\.jpg$"; 81 | 82 | std::vector hits; 83 | std::vector misses; 84 | std::vector deleteHits; 85 | std::vector deleteMisses; 86 | std::vector createHits; 87 | std::vector createMisses; 88 | 89 | int testSize = 5; 90 | 91 | // hits 92 | for(int i=0; i waitTime = 175 | std::chrono::system_clock::now() + std::chrono::seconds( 5 ); 176 | 177 | // poll the service to force a check before app updates 178 | while( std::chrono::system_clock::now() < waitTime ) { 179 | filewatcher::FileWatcher::instance()->poll(); 180 | ci::sleep( 1000 / 30 ); 181 | } 182 | 183 | // verify 184 | 185 | // hits 186 | for( auto file : hits ) { 187 | CI_ASSERT( actions[file].modified >= 1 ); 188 | } 189 | 190 | // misses 191 | for( auto file : misses ) { 192 | CI_ASSERT( actions[file].modified == 0 ); 193 | } 194 | 195 | // delete hits 196 | for( auto file : deleteHits ) { 197 | CI_ASSERT( actions[file].removed == 1 ); 198 | } 199 | 200 | // delete misses 201 | for( auto file : deleteMisses ) { 202 | CI_ASSERT( actions[file].removed == 0 ); 203 | } 204 | 205 | // create hits 206 | for( auto file : createHits ) { 207 | auto hit = actions[file]; 208 | CI_ASSERT( actions[file].added == 1 ); 209 | } 210 | 211 | // create misses 212 | for( auto file : createMisses ) { 213 | CI_ASSERT( actions[file].added == 0 ); 214 | } 215 | 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /tests/UnitTests/src/RenameTest.cpp: -------------------------------------------------------------------------------- 1 | #include "cinder/app/Platform.h" 2 | #include "cinder/app/App.h" 3 | #include "cinder/Utilities.h" 4 | #include "catch.hpp" 5 | 6 | #include 7 | 8 | #include "utils.h" 9 | #include "FileWatcher.h" 10 | 11 | using namespace ci; 12 | using namespace std; 13 | using namespace ci::app; 14 | 15 | 16 | TEST_CASE( "RenameTest" ) 17 | { 18 | SECTION( "Single file is renamed and change detected within 2 seconds." ) 19 | { 20 | fs::remove_all( getTestingPath() ); 21 | CI_ASSERT( createTestingDir( getTestingPath() ) ); 22 | 23 | fs::path original = getTestingPath() / "original.txt"; 24 | fs::path renamed = getTestingPath() / "renamed.txt"; 25 | writeToFile( original, "start" ); 26 | 27 | filewatcher::WatchedTarget watchedFile; 28 | 29 | ActionMap actions; 30 | watchedFile = filewatcher::FileWatcher::watchFile( original, 31 | [ &actions ]( const ci::fs::path& file, filewatcher::EventType type ) { 32 | std::cout << "Action on file " << file << std::endl; 33 | actions[file].process( type ); 34 | } ); 35 | 36 | CI_ASSERT( watchedFile.isFile() ); 37 | 38 | // rename file 39 | fs::rename( original, renamed ); 40 | 41 | // wait 2 seconds 42 | std::chrono::time_point waitTime = 43 | std::chrono::system_clock::now() + std::chrono::seconds( 2 ); 44 | 45 | // poll the service to force a check before app updates 46 | while( std::chrono::system_clock::now() < waitTime ) { 47 | filewatcher::FileWatcher::instance()->poll(); 48 | ci::sleep( 1000 / 30 ); 49 | } 50 | 51 | CI_ASSERT( actions[original].renamedOld == 1 ); 52 | 53 | // Currently we can't watch for the new file name. we wouldn't know which callback 54 | // to assign it to. Perhaps there is some info that lets us know this, but tbd. 55 | //CI_ASSERT( actions[renamed].renamedNew == 1 ); 56 | //CI_ASSERT( actions[renamed].added == 1 ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/UnitTests/src/ScopedTest.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucasvickers/Cinder-FileWatcher/0686a7fa3bc0ee1e084041d995dae688bf8d8651/tests/UnitTests/src/ScopedTest.cpp -------------------------------------------------------------------------------- /tests/UnitTests/src/TestMain.cpp: -------------------------------------------------------------------------------- 1 | 2 | /// 3 | /// @file Main test file compiles Catch unit test framework. 4 | /// Write your own tests in separate source files and build 5 | /// them and link them with the main project target. 6 | /// 7 | /// These unit tests are useful for non-visual testing of Cinder. 8 | /// 9 | 10 | #define CATCH_CONFIG_MAIN 11 | #include "catch.hpp" 12 | -------------------------------------------------------------------------------- /tests/UnitTests/src/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cinder/app/App.h" 4 | 5 | #include 6 | #include 7 | 8 | #include "FileWatcher.h" 9 | 10 | struct Action { 11 | int added; 12 | int modified; 13 | int removed; 14 | int renamedOld; 15 | int renamedNew; 16 | 17 | Action() 18 | : added( 0 ), modified( 0 ), removed( 0 ), renamedOld( 0 ), renamedNew( 0 ) 19 | { } 20 | 21 | void process( filewatcher::EventType type ) { 22 | switch( type ) { 23 | case filewatcher::EventType::ADDED: 24 | ++added; 25 | break; 26 | case filewatcher::EventType::MODIFIED: 27 | ++modified; 28 | break; 29 | case filewatcher::EventType::REMOVED: 30 | ++removed; 31 | break; 32 | case filewatcher::EventType::RENAMED_OLD: 33 | ++renamedOld; 34 | break; 35 | case filewatcher::EventType::RENAMED_NEW: 36 | ++renamedNew; 37 | break; 38 | default: 39 | break; 40 | } 41 | } 42 | }; 43 | 44 | typedef std::map ActionMap; 45 | 46 | inline cinder::fs::path getTestingPath() 47 | { 48 | return cinder::app::getAppPath() / "logtest"; 49 | } 50 | 51 | inline bool createTestingDir( const cinder::fs::path& path ) 52 | { 53 | if( ! cinder::fs::is_directory( path ) ) { 54 | return cinder::fs::create_directory( path ); 55 | } 56 | return true; 57 | } 58 | 59 | inline void writeToFile( const cinder::fs::path &file, const std::string &text ) 60 | { 61 | std::ofstream ofile; 62 | ofile.open( file.string() ); 63 | ofile << text; 64 | ofile.flush(); 65 | ofile.close(); 66 | } 67 | 68 | -------------------------------------------------------------------------------- /tests/UnitTests/vc2013/unit.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests", "unit.vcxproj", "{D3DC7E53-261C-49E1-835A-81084DE4B3BB}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Win32 = Debug|Win32 9 | Release|Win32 = Release|Win32 10 | Debug|x64 = Debug|x64 11 | Release|x64 = Release|x64 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {D3DC7E53-261C-49E1-835A-81084DE4B3BB}.Debug|Win32.ActiveCfg = Debug|Win32 15 | {D3DC7E53-261C-49E1-835A-81084DE4B3BB}.Debug|Win32.Build.0 = Debug|Win32 16 | {D3DC7E53-261C-49E1-835A-81084DE4B3BB}.Release|Win32.ActiveCfg = Release|Win32 17 | {D3DC7E53-261C-49E1-835A-81084DE4B3BB}.Release|Win32.Build.0 = Release|Win32 18 | {D3DC7E53-261C-49E1-835A-81084DE4B3BB}.Debug|x64.ActiveCfg = Debug|x64 19 | {D3DC7E53-261C-49E1-835A-81084DE4B3BB}.Debug|x64.Build.0 = Debug|x64 20 | {D3DC7E53-261C-49E1-835A-81084DE4B3BB}.Release|x64.ActiveCfg = Release|x64 21 | {D3DC7E53-261C-49E1-835A-81084DE4B3BB}.Release|x64.Build.0 = Release|x64 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /tests/UnitTests/vc2013/unit.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {D3DC7E53-261C-49E1-835A-81084DE4B3BB} 23 | UnitTests 24 | Win32Proj 25 | UnitTests 26 | 27 | 28 | 29 | Application 30 | false 31 | v120 32 | Unicode 33 | true 34 | 35 | 36 | Application 37 | false 38 | v120 39 | Unicode 40 | true 41 | 42 | 43 | Application 44 | true 45 | v120 46 | Unicode 47 | 48 | 49 | Application 50 | true 51 | v120 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | <_ProjectFileVersion>10.0.30319.1 71 | $(ProjectDir)build\$(PlatformToolset)\$(Configuration)\$(PlatformTarget)\ 72 | $(ProjectDir)build\$(PlatformToolset)\$(Configuration)\$(PlatformTarget)\obj\ 73 | true 74 | true 75 | $(ProjectDir)build\$(PlatformToolset)\$(Configuration)\$(PlatformTarget)\ 76 | $(ProjectDir)build\$(PlatformToolset)\$(Configuration)\$(PlatformTarget)\obj\ 77 | false 78 | false 79 | 80 | 81 | CinderUnitTests 82 | 83 | 84 | CinderUnitTests 85 | 86 | 87 | CinderUnitTests 88 | $(ProjectDir)build\$(PlatformToolset)\$(Configuration)\$(PlatformTarget)\ 89 | $(ProjectDir)build\$(PlatformToolset)\$(Configuration)\$(PlatformTarget)\obj\ 90 | 91 | 92 | CinderUnitTests 93 | $(ProjectDir)build\$(PlatformToolset)\$(Configuration)\$(PlatformTarget)\ 94 | $(ProjectDir)build\$(PlatformToolset)\$(Configuration)\$(PlatformTarget)\obj\ 95 | 96 | 97 | 98 | Disabled 99 | ..\include;..\src;..\..\..\include 100 | WIN32;_WIN32_WINNT=0x0601;_WINDOWS;NOMINMAX;_DEBUG;%(PreprocessorDefinitions) 101 | true 102 | EnableFastChecks 103 | MultiThreadedDebug 104 | 105 | Level3 106 | EditAndContinue 107 | true 108 | 109 | 110 | "..\..\..\include";..\include 111 | 112 | 113 | cinder-$(PlatformToolset)_d.lib;OpenGL32.lib;%(AdditionalDependencies) 114 | "..\..\..\lib\msw\$(PlatformTarget)" 115 | true 116 | Console 117 | false 118 | 119 | MachineX86 120 | LIBCMT;LIBCPMT 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Disabled 142 | ..\include;..\src;..\..\..\include 143 | WIN32;_WIN32_WINNT=0x0601;_WINDOWS;NOMINMAX;_DEBUG;%(PreprocessorDefinitions) 144 | EnableFastChecks 145 | MultiThreadedDebug 146 | 147 | Level3 148 | ProgramDatabase 149 | true 150 | 151 | 152 | "..\..\..\include";..\include 153 | 154 | 155 | cinder-$(PlatformToolset)_d.lib;OpenGL32.lib;%(AdditionalDependencies) 156 | "..\..\..\lib\msw\$(PlatformTarget)" 157 | true 158 | Console 159 | false 160 | 161 | LIBCMT;LIBCPMT 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | ..\include;..\src;..\..\..\include 183 | WIN32;_WIN32_WINNT=0x0601;_WINDOWS;NOMINMAX;NDEBUG;%(PreprocessorDefinitions) 184 | MultiThreaded 185 | 186 | Level3 187 | ProgramDatabase 188 | true 189 | 190 | 191 | true 192 | 193 | 194 | "..\..\..\include";..\include 195 | 196 | 197 | cinder-$(PlatformToolset).lib;OpenGL32.lib;%(AdditionalDependencies) 198 | "..\..\..\lib\msw\$(PlatformTarget)" 199 | false 200 | true 201 | Console 202 | true 203 | 204 | false 205 | 206 | MachineX86 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | ..\include;..\src;..\..\..\include 224 | WIN32;_WIN32_WINNT=0x0601;_WINDOWS;NOMINMAX;NDEBUG;%(PreprocessorDefinitions) 225 | MultiThreaded 226 | 227 | Level3 228 | ProgramDatabase 229 | true 230 | 231 | 232 | true 233 | 234 | 235 | "..\..\..\include";..\include 236 | 237 | 238 | cinder-$(PlatformToolset).lib;OpenGL32.lib;%(AdditionalDependencies) 239 | "..\..\..\lib\msw\$(PlatformTarget)" 240 | false 241 | true 242 | Console 243 | true 244 | 245 | false 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | -------------------------------------------------------------------------------- /tests/UnitTests/vc2013/unit.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav 15 | 16 | 17 | {1827bea0-7c6d-42c9-b5e0-c605fffdeb77} 18 | 19 | 20 | {d86862cb-6666-42c3-b358-aaa143508507} 21 | 22 | 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | Source Files 38 | 39 | 40 | Source Files 41 | 42 | 43 | Source Files 44 | 45 | 46 | Source Files\signals 47 | 48 | 49 | Source Files\audio 50 | 51 | 52 | Source Files\audio 53 | 54 | 55 | Source Files\audio 56 | 57 | 58 | 59 | 60 | Header Files 61 | 62 | 63 | Source Files\audio 64 | 65 | 66 | -------------------------------------------------------------------------------- /tests/UnitTests/xcode/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | CinderApp.icns 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | ${MACOSX_DEPLOYMENT_TARGET} 27 | NSHumanReadableCopyright 28 | Copyright © 2015 __MyCompanyName__. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/UnitTests/xcode/UnitTests.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5F24058A1D1A31CF0056637B /* RegexTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5F76DA481D0F9718001E3E4B /* RegexTest.cpp */; }; 11 | 5F24058B1D1A32F60056637B /* DeleteTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5F24057E1D1A1E060056637B /* DeleteTest.cpp */; }; 12 | 5F24058C1D1A32F90056637B /* RenameTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5F24057F1D1A1E060056637B /* RenameTest.cpp */; }; 13 | 5F24058D1D1A32FE0056637B /* BasicTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5F76DA311D0E3DB7001E3E4B /* BasicTest.cpp */; }; 14 | 5F24058F1D1A33420056637B /* ContainerTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5F24058E1D1A33420056637B /* ContainerTest.cpp */; }; 15 | 5F76DA341D0E3DB7001E3E4B /* TestMain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5F76DA321D0E3DB7001E3E4B /* TestMain.cpp */; }; 16 | 5F76DA3B1D0E42E5001E3E4B /* FileMonitorImpl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5F76DA3A1D0E42E5001E3E4B /* FileMonitorImpl.cpp */; }; 17 | 5F76DA451D0E43D2001E3E4B /* FileWatcher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5F76DA431D0E43D2001E3E4B /* FileWatcher.cpp */; }; 18 | 9CC02E8F1BDE75EF00B5058A /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 006D720219952D00008149E2 /* AVFoundation.framework */; }; 19 | 9CC02E901BDE75F600B5058A /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 006D720319952D00008149E2 /* CoreMedia.framework */; }; 20 | 9CC02E921BDE75FE00B5058A /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9CC02E911BDE75FE00B5058A /* Cocoa.framework */; }; 21 | 9CC02E941BDE760600B5058A /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9CC02E931BDE760600B5058A /* OpenGL.framework */; }; 22 | 9CC02E961BDE760B00B5058A /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9CC02E951BDE760B00B5058A /* CoreVideo.framework */; }; 23 | 9CC02E971BDE761400B5058A /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B784AF0FF439BC000DE1D7 /* Accelerate.framework */; }; 24 | 9CC02E981BDE761A00B5058A /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B784B10FF439BC000DE1D7 /* AudioUnit.framework */; }; 25 | 9CC02E991BDE762800B5058A /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B784B20FF439BC000DE1D7 /* CoreAudio.framework */; }; 26 | 9CC02E9A1BDE763000B5058A /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B995581B128DF400A5C623 /* IOKit.framework */; }; 27 | 9CC02E9B1BDE763600B5058A /* IOSurface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B995591B128DF400A5C623 /* IOSurface.framework */; }; 28 | 9CC02E9C1BDE764400B5058A /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B784B00FF439BC000DE1D7 /* AudioToolbox.framework */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXCopyFilesBuildPhase section */ 32 | 9CC02E7E1BDE74AA00B5058A /* CopyFiles */ = { 33 | isa = PBXCopyFilesBuildPhase; 34 | buildActionMask = 2147483647; 35 | dstPath = /usr/share/man/man1/; 36 | dstSubfolderSpec = 0; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 1; 40 | }; 41 | /* End PBXCopyFilesBuildPhase section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | 006D720219952D00008149E2 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 45 | 006D720319952D00008149E2 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; 46 | 0091D8F80E81B9330029341E /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = /System/Library/Frameworks/OpenGL.framework; sourceTree = ""; }; 47 | 00B784AF0FF439BC000DE1D7 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; 48 | 00B784B00FF439BC000DE1D7 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 49 | 00B784B10FF439BC000DE1D7 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; 50 | 00B784B20FF439BC000DE1D7 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; }; 51 | 00B995581B128DF400A5C623 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; 52 | 00B995591B128DF400A5C623 /* IOSurface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOSurface.framework; path = System/Library/Frameworks/IOSurface.framework; sourceTree = SDKROOT; }; 53 | 0BFCCD63DF794B1EAE655DCC /* CinderApp.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = CinderApp.icns; path = ../../../samples/data/CinderApp.icns; sourceTree = ""; }; 54 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 55 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 56 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 57 | 5323E6B10EAFCA74003A9687 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = /System/Library/Frameworks/CoreVideo.framework; sourceTree = ""; }; 58 | 5F24057E1D1A1E060056637B /* DeleteTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DeleteTest.cpp; sourceTree = ""; }; 59 | 5F24057F1D1A1E060056637B /* RenameTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RenameTest.cpp; sourceTree = ""; }; 60 | 5F24058E1D1A33420056637B /* ContainerTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ContainerTest.cpp; sourceTree = ""; }; 61 | 5F76DA311D0E3DB7001E3E4B /* BasicTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BasicTest.cpp; sourceTree = ""; }; 62 | 5F76DA321D0E3DB7001E3E4B /* TestMain.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TestMain.cpp; sourceTree = ""; }; 63 | 5F76DA351D0E3FE0001E3E4B /* utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = utils.h; sourceTree = ""; }; 64 | 5F76DA3A1D0E42E5001E3E4B /* FileMonitorImpl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = FileMonitorImpl.cpp; path = ../../../src/filemonitor/fsevents/FileMonitorImpl.cpp; sourceTree = ""; }; 65 | 5F76DA3C1D0E42F4001E3E4B /* BasicFileMonitorService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BasicFileMonitorService.h; path = ../../../include/filemonitor/BasicFileMonitorService.h; sourceTree = ""; }; 66 | 5F76DA3D1D0E42F4001E3E4B /* BasicFileMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BasicFileMonitor.h; path = ../../../include/filemonitor/BasicFileMonitor.h; sourceTree = ""; }; 67 | 5F76DA3E1D0E42F4001E3E4B /* FileMonitorEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FileMonitorEvent.h; path = ../../../include/filemonitor/FileMonitorEvent.h; sourceTree = ""; }; 68 | 5F76DA3F1D0E42F4001E3E4B /* FileMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FileMonitor.h; path = ../../../include/filemonitor/FileMonitor.h; sourceTree = ""; }; 69 | 5F76DA411D0E4303001E3E4B /* FileMonitorImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FileMonitorImpl.h; path = ../../../include/filemonitor/fsevents/FileMonitorImpl.h; sourceTree = ""; }; 70 | 5F76DA431D0E43D2001E3E4B /* FileWatcher.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = FileWatcher.cpp; path = ../../../src/FileWatcher.cpp; sourceTree = ""; }; 71 | 5F76DA441D0E43D2001E3E4B /* FileWatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FileWatcher.h; path = ../../../include/FileWatcher.h; sourceTree = ""; }; 72 | 5F76DA481D0F9718001E3E4B /* RegexTest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RegexTest.cpp; sourceTree = ""; }; 73 | 6E8118130C2B4ADCA23B5B2B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 74 | 9CA851B71C1F74000049358B /* catch.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = catch.hpp; sourceTree = ""; }; 75 | 9CC02E801BDE74AA00B5058A /* CinderUnitTests */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = CinderUnitTests; sourceTree = BUILT_PRODUCTS_DIR; }; 76 | 9CC02E911BDE75FE00B5058A /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 77 | 9CC02E931BDE760600B5058A /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; 78 | 9CC02E951BDE760B00B5058A /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; }; 79 | C0A5E928B33D45F6A388E366 /* UnitTests_Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = "\"\""; path = UnitTests_Prefix.pch; sourceTree = ""; }; 80 | /* End PBXFileReference section */ 81 | 82 | /* Begin PBXFrameworksBuildPhase section */ 83 | 9CC02E7D1BDE74AA00B5058A /* Frameworks */ = { 84 | isa = PBXFrameworksBuildPhase; 85 | buildActionMask = 2147483647; 86 | files = ( 87 | 9CC02E9B1BDE763600B5058A /* IOSurface.framework in Frameworks */, 88 | 9CC02E9A1BDE763000B5058A /* IOKit.framework in Frameworks */, 89 | 9CC02E991BDE762800B5058A /* CoreAudio.framework in Frameworks */, 90 | 9CC02E981BDE761A00B5058A /* AudioUnit.framework in Frameworks */, 91 | 9CC02E9C1BDE764400B5058A /* AudioToolbox.framework in Frameworks */, 92 | 9CC02E971BDE761400B5058A /* Accelerate.framework in Frameworks */, 93 | 9CC02E961BDE760B00B5058A /* CoreVideo.framework in Frameworks */, 94 | 9CC02E941BDE760600B5058A /* OpenGL.framework in Frameworks */, 95 | 9CC02E921BDE75FE00B5058A /* Cocoa.framework in Frameworks */, 96 | 9CC02E901BDE75F600B5058A /* CoreMedia.framework in Frameworks */, 97 | 9CC02E8F1BDE75EF00B5058A /* AVFoundation.framework in Frameworks */, 98 | ); 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | /* End PBXFrameworksBuildPhase section */ 102 | 103 | /* Begin PBXGroup section */ 104 | 01B97315FEAEA392516A2CEA /* Blocks */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 5F76DA421D0E43CA001E3E4B /* FileWatcher */, 108 | 5F76DA361D0E42AB001E3E4B /* filemonitor */, 109 | ); 110 | name = Blocks; 111 | sourceTree = ""; 112 | }; 113 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 006D720219952D00008149E2 /* AVFoundation.framework */, 117 | 006D720319952D00008149E2 /* CoreMedia.framework */, 118 | 00B784AF0FF439BC000DE1D7 /* Accelerate.framework */, 119 | 00B784B00FF439BC000DE1D7 /* AudioToolbox.framework */, 120 | 00B784B10FF439BC000DE1D7 /* AudioUnit.framework */, 121 | 00B784B20FF439BC000DE1D7 /* CoreAudio.framework */, 122 | 5323E6B10EAFCA74003A9687 /* CoreVideo.framework */, 123 | 0091D8F80E81B9330029341E /* OpenGL.framework */, 124 | 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 125 | 00B995581B128DF400A5C623 /* IOKit.framework */, 126 | 00B995591B128DF400A5C623 /* IOSurface.framework */, 127 | ); 128 | name = "Linked Frameworks"; 129 | sourceTree = ""; 130 | }; 131 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 135 | 29B97325FDCFA39411CA2CEA /* Foundation.framework */, 136 | ); 137 | name = "Other Frameworks"; 138 | sourceTree = ""; 139 | }; 140 | 19C28FACFE9D520D11CA2CBB /* Products */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 9CC02E801BDE74AA00B5058A /* CinderUnitTests */, 144 | ); 145 | name = Products; 146 | sourceTree = ""; 147 | }; 148 | 29B97314FDCFA39411CA2CEA /* UnitTests */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | 01B97315FEAEA392516A2CEA /* Blocks */, 152 | 29B97315FDCFA39411CA2CEA /* Headers */, 153 | 29B97317FDCFA39411CA2CEA /* Resources */, 154 | 29B97323FDCFA39411CA2CEA /* Frameworks */, 155 | 19C28FACFE9D520D11CA2CBB /* Products */, 156 | 9CA851B51C1F74000049358B /* Source */, 157 | ); 158 | indentWidth = 4; 159 | name = UnitTests; 160 | sourceTree = ""; 161 | tabWidth = 4; 162 | }; 163 | 29B97315FDCFA39411CA2CEA /* Headers */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | C0A5E928B33D45F6A388E366 /* UnitTests_Prefix.pch */, 167 | ); 168 | name = Headers; 169 | sourceTree = ""; 170 | }; 171 | 29B97317FDCFA39411CA2CEA /* Resources */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | 0BFCCD63DF794B1EAE655DCC /* CinderApp.icns */, 175 | 6E8118130C2B4ADCA23B5B2B /* Info.plist */, 176 | ); 177 | name = Resources; 178 | sourceTree = ""; 179 | }; 180 | 29B97323FDCFA39411CA2CEA /* Frameworks */ = { 181 | isa = PBXGroup; 182 | children = ( 183 | 9CC02E951BDE760B00B5058A /* CoreVideo.framework */, 184 | 9CC02E931BDE760600B5058A /* OpenGL.framework */, 185 | 9CC02E911BDE75FE00B5058A /* Cocoa.framework */, 186 | 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 187 | 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, 188 | ); 189 | name = Frameworks; 190 | sourceTree = ""; 191 | }; 192 | 5F76DA361D0E42AB001E3E4B /* filemonitor */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | 5F76DA381D0E42C3001E3E4B /* source */, 196 | 5F76DA371D0E42B3001E3E4B /* include */, 197 | ); 198 | name = filemonitor; 199 | sourceTree = ""; 200 | }; 201 | 5F76DA371D0E42B3001E3E4B /* include */ = { 202 | isa = PBXGroup; 203 | children = ( 204 | 5F76DA3C1D0E42F4001E3E4B /* BasicFileMonitorService.h */, 205 | 5F76DA3D1D0E42F4001E3E4B /* BasicFileMonitor.h */, 206 | 5F76DA3E1D0E42F4001E3E4B /* FileMonitorEvent.h */, 207 | 5F76DA3F1D0E42F4001E3E4B /* FileMonitor.h */, 208 | 5F76DA401D0E42F7001E3E4B /* fsevents */, 209 | ); 210 | name = include; 211 | sourceTree = ""; 212 | }; 213 | 5F76DA381D0E42C3001E3E4B /* source */ = { 214 | isa = PBXGroup; 215 | children = ( 216 | 5F76DA391D0E42CA001E3E4B /* fsevents */, 217 | ); 218 | name = source; 219 | sourceTree = ""; 220 | }; 221 | 5F76DA391D0E42CA001E3E4B /* fsevents */ = { 222 | isa = PBXGroup; 223 | children = ( 224 | 5F76DA3A1D0E42E5001E3E4B /* FileMonitorImpl.cpp */, 225 | ); 226 | name = fsevents; 227 | sourceTree = ""; 228 | }; 229 | 5F76DA401D0E42F7001E3E4B /* fsevents */ = { 230 | isa = PBXGroup; 231 | children = ( 232 | 5F76DA411D0E4303001E3E4B /* FileMonitorImpl.h */, 233 | ); 234 | name = fsevents; 235 | sourceTree = ""; 236 | }; 237 | 5F76DA421D0E43CA001E3E4B /* FileWatcher */ = { 238 | isa = PBXGroup; 239 | children = ( 240 | 5F76DA431D0E43D2001E3E4B /* FileWatcher.cpp */, 241 | 5F76DA441D0E43D2001E3E4B /* FileWatcher.h */, 242 | ); 243 | name = FileWatcher; 244 | sourceTree = ""; 245 | }; 246 | 9CA851B51C1F74000049358B /* Source */ = { 247 | isa = PBXGroup; 248 | children = ( 249 | 5F24058E1D1A33420056637B /* ContainerTest.cpp */, 250 | 5F24057E1D1A1E060056637B /* DeleteTest.cpp */, 251 | 5F24057F1D1A1E060056637B /* RenameTest.cpp */, 252 | 5F76DA481D0F9718001E3E4B /* RegexTest.cpp */, 253 | 5F76DA351D0E3FE0001E3E4B /* utils.h */, 254 | 5F76DA311D0E3DB7001E3E4B /* BasicTest.cpp */, 255 | 5F76DA321D0E3DB7001E3E4B /* TestMain.cpp */, 256 | 9CA851B71C1F74000049358B /* catch.hpp */, 257 | ); 258 | name = Source; 259 | path = ../src; 260 | sourceTree = ""; 261 | }; 262 | /* End PBXGroup section */ 263 | 264 | /* Begin PBXNativeTarget section */ 265 | 9CC02E7F1BDE74AA00B5058A /* CinderUnitTests */ = { 266 | isa = PBXNativeTarget; 267 | buildConfigurationList = 9CC02E841BDE74AA00B5058A /* Build configuration list for PBXNativeTarget "CinderUnitTests" */; 268 | buildPhases = ( 269 | 9CC02E7C1BDE74AA00B5058A /* Sources */, 270 | 9CC02E7D1BDE74AA00B5058A /* Frameworks */, 271 | 9CC02E7E1BDE74AA00B5058A /* CopyFiles */, 272 | ); 273 | buildRules = ( 274 | ); 275 | dependencies = ( 276 | ); 277 | name = CinderUnitTests; 278 | productName = CinderUnitTests; 279 | productReference = 9CC02E801BDE74AA00B5058A /* CinderUnitTests */; 280 | productType = "com.apple.product-type.tool"; 281 | }; 282 | /* End PBXNativeTarget section */ 283 | 284 | /* Begin PBXProject section */ 285 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 286 | isa = PBXProject; 287 | attributes = { 288 | TargetAttributes = { 289 | 9CC02E7F1BDE74AA00B5058A = { 290 | CreatedOnToolsVersion = 7.1; 291 | }; 292 | }; 293 | }; 294 | buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "UnitTests" */; 295 | compatibilityVersion = "Xcode 3.2"; 296 | developmentRegion = English; 297 | hasScannedForEncodings = 1; 298 | knownRegions = ( 299 | English, 300 | Japanese, 301 | French, 302 | German, 303 | ); 304 | mainGroup = 29B97314FDCFA39411CA2CEA /* UnitTests */; 305 | projectDirPath = ""; 306 | projectRoot = ""; 307 | targets = ( 308 | 9CC02E7F1BDE74AA00B5058A /* CinderUnitTests */, 309 | ); 310 | }; 311 | /* End PBXProject section */ 312 | 313 | /* Begin PBXSourcesBuildPhase section */ 314 | 9CC02E7C1BDE74AA00B5058A /* Sources */ = { 315 | isa = PBXSourcesBuildPhase; 316 | buildActionMask = 2147483647; 317 | files = ( 318 | 5F76DA3B1D0E42E5001E3E4B /* FileMonitorImpl.cpp in Sources */, 319 | 5F24058A1D1A31CF0056637B /* RegexTest.cpp in Sources */, 320 | 5F24058B1D1A32F60056637B /* DeleteTest.cpp in Sources */, 321 | 5F24058D1D1A32FE0056637B /* BasicTest.cpp in Sources */, 322 | 5F76DA451D0E43D2001E3E4B /* FileWatcher.cpp in Sources */, 323 | 5F24058F1D1A33420056637B /* ContainerTest.cpp in Sources */, 324 | 5F76DA341D0E3DB7001E3E4B /* TestMain.cpp in Sources */, 325 | 5F24058C1D1A32F90056637B /* RenameTest.cpp in Sources */, 326 | ); 327 | runOnlyForDeploymentPostprocessing = 0; 328 | }; 329 | /* End PBXSourcesBuildPhase section */ 330 | 331 | /* Begin XCBuildConfiguration section */ 332 | 9CC02E851BDE74AA00B5058A /* Debug */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | CINDER_PATH = ../../../../..; 336 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 337 | CLANG_ENABLE_MODULES = YES; 338 | CLANG_ENABLE_OBJC_ARC = YES; 339 | CLANG_WARN_BOOL_CONVERSION = YES; 340 | CLANG_WARN_CONSTANT_CONVERSION = YES; 341 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 342 | CLANG_WARN_EMPTY_BODY = YES; 343 | CLANG_WARN_ENUM_CONVERSION = YES; 344 | CLANG_WARN_INT_CONVERSION = YES; 345 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 346 | CLANG_WARN_UNREACHABLE_CODE = YES; 347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 348 | CODE_SIGN_IDENTITY = "-"; 349 | COPY_PHASE_STRIP = NO; 350 | DEBUG_INFORMATION_FORMAT = dwarf; 351 | ENABLE_STRICT_OBJC_MSGSEND = YES; 352 | GCC_C_LANGUAGE_STANDARD = gnu99; 353 | GCC_DYNAMIC_NO_PIC = NO; 354 | GCC_NO_COMMON_BLOCKS = YES; 355 | GCC_OPTIMIZATION_LEVEL = 0; 356 | GCC_PREPROCESSOR_DEFINITIONS = ( 357 | "DEBUG=1", 358 | "$(inherited)", 359 | ); 360 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 361 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 362 | GCC_WARN_UNDECLARED_SELECTOR = YES; 363 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 364 | GCC_WARN_UNUSED_FUNCTION = YES; 365 | MACOSX_DEPLOYMENT_TARGET = 10.10; 366 | MTL_ENABLE_DEBUG_INFO = YES; 367 | OTHER_LDFLAGS = ( 368 | "$(CINDER_PATH)/lib/libcinder_d.a", 369 | "-lcurl", 370 | ); 371 | PRODUCT_NAME = "$(TARGET_NAME)"; 372 | SYMROOT = ./build; 373 | USER_HEADER_SEARCH_PATHS = "\"$(CINDER_PATH)/include\" ../"; 374 | }; 375 | name = Debug; 376 | }; 377 | 9CC02E861BDE74AA00B5058A /* Release */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | CINDER_PATH = ../../../../..; 381 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 382 | CLANG_ENABLE_MODULES = YES; 383 | CLANG_ENABLE_OBJC_ARC = YES; 384 | CLANG_WARN_BOOL_CONVERSION = YES; 385 | CLANG_WARN_CONSTANT_CONVERSION = YES; 386 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 387 | CLANG_WARN_EMPTY_BODY = YES; 388 | CLANG_WARN_ENUM_CONVERSION = YES; 389 | CLANG_WARN_INT_CONVERSION = YES; 390 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 391 | CLANG_WARN_UNREACHABLE_CODE = YES; 392 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 393 | CODE_SIGN_IDENTITY = "-"; 394 | COPY_PHASE_STRIP = NO; 395 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 396 | ENABLE_NS_ASSERTIONS = NO; 397 | ENABLE_STRICT_OBJC_MSGSEND = YES; 398 | GCC_C_LANGUAGE_STANDARD = gnu99; 399 | GCC_NO_COMMON_BLOCKS = YES; 400 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 401 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 402 | GCC_WARN_UNDECLARED_SELECTOR = YES; 403 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 404 | GCC_WARN_UNUSED_FUNCTION = YES; 405 | MACOSX_DEPLOYMENT_TARGET = 10.10; 406 | MTL_ENABLE_DEBUG_INFO = NO; 407 | OTHER_LDFLAGS = ( 408 | "$(CINDER_PATH)/lib/libcinder.a", 409 | "-lcurl", 410 | ); 411 | PRODUCT_NAME = "$(TARGET_NAME)"; 412 | SYMROOT = ./build; 413 | USER_HEADER_SEARCH_PATHS = "\"$(CINDER_PATH)/include\" ../"; 414 | }; 415 | name = Release; 416 | }; 417 | C01FCF4F08A954540054247B /* Debug */ = { 418 | isa = XCBuildConfiguration; 419 | buildSettings = { 420 | ALWAYS_SEARCH_USER_PATHS = NO; 421 | CINDER_PATH = ../../..; 422 | CLANG_CXX_LANGUAGE_STANDARD = "c++11"; 423 | CLANG_CXX_LIBRARY = "libc++"; 424 | ENABLE_TESTABILITY = YES; 425 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 426 | GCC_WARN_UNUSED_VARIABLE = YES; 427 | HEADER_SEARCH_PATHS = "\"$(CINDER_PATH)/include\""; 428 | MACOSX_DEPLOYMENT_TARGET = 10.8; 429 | ONLY_ACTIVE_ARCH = YES; 430 | SDKROOT = macosx; 431 | USER_HEADER_SEARCH_PATHS = "\"$(CINDER_PATH)/include\" ../include"; 432 | }; 433 | name = Debug; 434 | }; 435 | C01FCF5008A954540054247B /* Release */ = { 436 | isa = XCBuildConfiguration; 437 | buildSettings = { 438 | ALWAYS_SEARCH_USER_PATHS = NO; 439 | CINDER_PATH = ../../..; 440 | CLANG_CXX_LANGUAGE_STANDARD = "c++11"; 441 | CLANG_CXX_LIBRARY = "libc++"; 442 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 443 | GCC_WARN_UNUSED_VARIABLE = YES; 444 | HEADER_SEARCH_PATHS = "\"$(CINDER_PATH)/include\""; 445 | MACOSX_DEPLOYMENT_TARGET = 10.8; 446 | SDKROOT = macosx; 447 | USER_HEADER_SEARCH_PATHS = "\"$(CINDER_PATH)/include\" ../include"; 448 | }; 449 | name = Release; 450 | }; 451 | /* End XCBuildConfiguration section */ 452 | 453 | /* Begin XCConfigurationList section */ 454 | 9CC02E841BDE74AA00B5058A /* Build configuration list for PBXNativeTarget "CinderUnitTests" */ = { 455 | isa = XCConfigurationList; 456 | buildConfigurations = ( 457 | 9CC02E851BDE74AA00B5058A /* Debug */, 458 | 9CC02E861BDE74AA00B5058A /* Release */, 459 | ); 460 | defaultConfigurationIsVisible = 0; 461 | defaultConfigurationName = Release; 462 | }; 463 | C01FCF4E08A954540054247B /* Build configuration list for PBXProject "UnitTests" */ = { 464 | isa = XCConfigurationList; 465 | buildConfigurations = ( 466 | C01FCF4F08A954540054247B /* Debug */, 467 | C01FCF5008A954540054247B /* Release */, 468 | ); 469 | defaultConfigurationIsVisible = 0; 470 | defaultConfigurationName = Release; 471 | }; 472 | /* End XCConfigurationList section */ 473 | }; 474 | rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; 475 | } 476 | -------------------------------------------------------------------------------- /tests/UnitTests/xcode/UnitTests.xcodeproj/project.xcworkspace/xcshareddata/UnitTests.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "C603A9047E2A8DF758164A077AEE60B90C346CAB", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "C603A9047E2A8DF758164A077AEE60B90C346CAB" : 0, 8 | "AD4B458D0D225ED63FAE0155615993B7D3ECE020" : 0 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "93B0EFA1-540D-463B-8797-B6E8AE811092", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "C603A9047E2A8DF758164A077AEE60B90C346CAB" : "Cinder-FileMonitor\/", 13 | "AD4B458D0D225ED63FAE0155615993B7D3ECE020" : ".." 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "UnitTests", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "tests\/UnitTests\/xcode\/UnitTests.xcodeproj", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/cinder\/Cinder.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "AD4B458D0D225ED63FAE0155615993B7D3ECE020" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:lucasvickers\/Cinder-FileMonitor.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "C603A9047E2A8DF758164A077AEE60B90C346CAB" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /tests/UnitTests/xcode/UnitTests_Prefix.pch: -------------------------------------------------------------------------------- 1 | #if defined( __cplusplus ) 2 | #include "cinder/Cinder.h" 3 | 4 | #include "cinder/app/App.h" 5 | 6 | #include "cinder/gl/gl.h" 7 | 8 | #include "cinder/CinderMath.h" 9 | #include "cinder/Matrix.h" 10 | #include "cinder/Vector.h" 11 | #include "cinder/Quaternion.h" 12 | #endif 13 | --------------------------------------------------------------------------------