watchdog-0.9.0/0000755000175000017500000000000013341331653014060 5ustar danilodanilo00000000000000watchdog-0.9.0/AUTHORS0000644000175000017500000000521013341114150015115 0ustar danilodanilo00000000000000Project Lead: ------------- Yesudeep Mangalapilly Contributors in alphabetical order: ----------------------------------- Adrian Tejn Kern Andrew Schaaf Danilo de Jesus da Silva Bellini David LaPalomento Filip Noetzel Gary van der Merwe Gora Khargosh Hannu Valtonen Jesse Printz Luke McCarthy Lukáš Lalinský Malthe Borch Martin Kreichgauer Martin Kreichgauer Mike Lundy Raymond Hettinger Roman Ovchinnikov Rotem Yaari Ryan Kelly Senko Rasic Senko Rašić Shane Hathaway Simon Pantzare Simon Pantzare Steven Samuel Cole Stéphane Klein Thomas Guest Thomas Heller Tim Cuthbertson Todd Whiteman Will McGugan Yesudeep Mangalapilly Yesudeep Mangalapilly dvogel gfxmonk We would like to thank these individuals for ideas: --------------------------------------------------- Tim Golden Sebastien Martini Initially we used the flask theme for the documentation which was written by ---------------------------------------------------------------------------- Armin Ronacher Watchdog also includes open source libraries or adapted code from the following projects: - MacFSEvents - http://github.com/malthe/macfsevents - watch_directory.py - http://timgolden.me.uk/python/downloads/watch_directory.py - pyinotify - http://github.com/seb-m/pyinotify - fsmonitor - http://github.com/shaurz/fsmonitor - echo - http://wordaligned.org/articles/echo - Lukáš Lalinský's ordered set queue implementation: http://stackoverflow.com/questions/1581895/how-check-if-a-task-is-already-in-python-queue - Armin Ronacher's flask-sphinx-themes for the documentation: https://github.com/mitsuhiko/flask-sphinx-themes - pyfilesystem - http://code.google.com/p/pyfilesystem - get_FILE_NOTIFY_INFORMATION - http://blog.gmane.org/gmane.comp.python.ctypes/month=20070901 watchdog-0.9.0/COPYING0000644000175000017500000000114213341070440015103 0ustar danilodanilo00000000000000Copyright 2011 Yesudeep Mangalapilly Copyright 2012 Google, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. watchdog-0.9.0/LICENSE0000644000175000017500000002613613341070440015067 0ustar danilodanilo00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. watchdog-0.9.0/MANIFEST.in0000644000175000017500000000063213341070440015611 0ustar danilodanilo00000000000000include README.rst include changelog.rst include LICENSE include COPYING include AUTHORS recursive-include src *.py *.h *.c include docs/*.txt include docs/*.xml include docs/Makefile include docs/make.bat recursive-include docs/source * recursive-include tests *.py #global-exclude .DS_Store #global-exclude Thumbs.db #global-exclude Desktop.ini #global-exclude *.swp #global-exclude *~ #global-exclude *.bak watchdog-0.9.0/PKG-INFO0000644000175000017500000003123513341331653015161 0ustar danilodanilo00000000000000Metadata-Version: 1.1 Name: watchdog Version: 0.9.0 Summary: Filesystem events monitoring Home-page: http://github.com/gorakhargosh/watchdog Author: Yesudeep Mangalapilly Author-email: yesudeep@gmail.com License: Apache License 2.0 Description: Watchdog ======== Python API and shell utilities to monitor file system events. Example API Usage ----------------- A simple program that uses watchdog to monitor directories specified as command-line arguments and logs events generated: .. code-block:: python import sys import time import logging from watchdog.observers import Observer from watchdog.events import LoggingEventHandler if __name__ == "__main__": logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S') path = sys.argv[1] if len(sys.argv) > 1 else '.' event_handler = LoggingEventHandler() observer = Observer() observer.schedule(event_handler, path, recursive=True) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join() Shell Utilities --------------- Watchdog comes with a utility script called ``watchmedo``. Please type ``watchmedo --help`` at the shell prompt to know more about this tool. Here is how you can log the current directory recursively for events related only to ``*.py`` and ``*.txt`` files while ignoring all directory events: .. code-block:: bash watchmedo log \ --patterns="*.py;*.txt" \ --ignore-directories \ --recursive \ . You can use the ``shell-command`` subcommand to execute shell commands in response to events: .. code-block:: bash watchmedo shell-command \ --patterns="*.py;*.txt" \ --recursive \ --command='echo "${watch_src_path}"' \ . Please see the help information for these commands by typing: .. code-block:: bash watchmedo [command] --help About ``watchmedo`` Tricks ~~~~~~~~~~~~~~~~~~~~~~~~~~ ``watchmedo`` can read ``tricks.yaml`` files and execute tricks within them in response to file system events. Tricks are actually event handlers that subclass ``watchdog.tricks.Trick`` and are written by plugin authors. Trick classes are augmented with a few additional features that regular event handlers don't need. An example ``tricks.yaml`` file: .. code-block:: yaml tricks: - watchdog.tricks.LoggerTrick: patterns: ["*.py", "*.js"] - watchmedo_webtricks.GoogleClosureTrick: patterns: ['*.js'] hash_names: true mappings_format: json # json|yaml|python mappings_module: app/javascript_mappings suffix: .min.js compilation_level: advanced # simple|advanced source_directory: app/static/js/ destination_directory: app/public/js/ files: index-page: - app/static/js/vendor/jquery*.js - app/static/js/base.js - app/static/js/index-page.js about-page: - app/static/js/vendor/jquery*.js - app/static/js/base.js - app/static/js/about-page/**/*.js The directory containing the ``tricks.yaml`` file will be monitored. Each trick class is initialized with its corresponding keys in the ``tricks.yaml`` file as arguments and events are fed to an instance of this class as they arrive. Tricks will be included in the 0.5.0 release. I need community input about them. Please file enhancement requests at the `issue tracker`_. Installation ------------ Installing from PyPI using ``pip``: .. code-block:: bash $ pip install watchdog Installing from PyPI using ``easy_install``: .. code-block:: bash $ easy_install watchdog Installing from source: .. code-block:: bash $ python setup.py install Installation Caveats ~~~~~~~~~~~~~~~~~~~~ The ``watchmedo`` script depends on PyYAML_ which links with LibYAML_, which brings a performance boost to the PyYAML parser. However, installing LibYAML_ is optional but recommended. On Mac OS X, you can use homebrew_ to install LibYAML: .. code-block:: bash $ brew install libyaml On Linux, use your favorite package manager to install LibYAML. Here's how you do it on Ubuntu: .. code-block:: bash $ sudo aptitude install libyaml-dev On Windows, please install PyYAML_ using the binaries they provide. Documentation ------------- You can browse the latest release documentation_ online. Contribute ---------- Fork the `repository`_ on GitHub and send a pull request, or file an issue ticket at the `issue tracker`_. For general help and questions use the official `mailing list`_ or ask on `stackoverflow`_ with tag `python-watchdog`. Create and activate your virtual environment, then:: pip install pytest pip install -e . py.test tests Supported Platforms ------------------- * Linux 2.6 (inotify) * Mac OS X (FSEvents, kqueue) * FreeBSD/BSD (kqueue) * Windows (ReadDirectoryChangesW with I/O completion ports; ReadDirectoryChangesW worker threads) * OS-independent (polling the disk for directory snapshots and comparing them periodically; slow and not recommended) Note that when using watchdog with kqueue, you need the number of file descriptors allowed to be opened by programs running on your system to be increased to more than the number of files that you will be monitoring. The easiest way to do that is to edit your ``~/.profile`` file and add a line similar to:: ulimit -n 1024 This is an inherent problem with kqueue because it uses file descriptors to monitor files. That plus the enormous amount of bookkeeping that watchdog needs to do in order to monitor file descriptors just makes this a painful way to monitor files and directories. In essence, kqueue is not a very scalable way to monitor a deeply nested directory of files and directories with a large number of files. About using watchdog with editors like Vim ------------------------------------------ Vim does not modify files unless directed to do so. It creates backup files and then swaps them in to replace the files you are editing on the disk. This means that if you use Vim to edit your files, the on-modified events for those files will not be triggered by watchdog. You may need to configure Vim to appropriately to disable this feature. Dependencies ------------ 1. Python 2.6 or above. 2. pathtools_ 3. select_backport_ (select.kqueue replacement for 2.6 on BSD/Mac OS X) 4. XCode_ (only on Mac OS X) 5. PyYAML_ (only for ``watchmedo`` script) 6. argh_ (only for ``watchmedo`` script) Licensing --------- Watchdog is licensed under the terms of the `Apache License, version 2.0`_. Copyright 2011 `Yesudeep Mangalapilly`_. Copyright 2012 Google, Inc. Project `source code`_ is available at Github. Please report bugs and file enhancement requests at the `issue tracker`_. Why Watchdog? ------------- Too many people tried to do the same thing and none did what I needed Python to do: * pnotify_ * `unison fsmonitor`_ * fsmonitor_ * guard_ * pyinotify_ * `inotify-tools`_ * jnotify_ * treewalker_ * `file.monitor`_ * pyfilesystem_ .. links: .. _Yesudeep Mangalapilly: yesudeep@gmail.com .. _source code: http://github.com/gorakhargosh/watchdog .. _issue tracker: http://github.com/gorakhargosh/watchdog/issues .. _Apache License, version 2.0: http://www.apache.org/licenses/LICENSE-2.0 .. _documentation: http://packages.python.org/watchdog/ .. _stackoverflow: http://stackoverflow.com/questions/tagged/python-watchdog .. _mailing list: http://groups.google.com/group/watchdog-python .. _repository: http://github.com/gorakhargosh/watchdog .. _issue tracker: http://github.com/gorakhargosh/watchdog/issues .. _homebrew: http://mxcl.github.com/homebrew/ .. _select_backport: http://pypi.python.org/pypi/select_backport .. _argh: http://pypi.python.org/pypi/argh .. _PyYAML: http://www.pyyaml.org/ .. _XCode: http://developer.apple.com/technologies/tools/xcode.html .. _LibYAML: http://pyyaml.org/wiki/LibYAML .. _pathtools: http://github.com/gorakhargosh/pathtools .. _pnotify: http://mark.heily.com/pnotify .. _unison fsmonitor: https://webdav.seas.upenn.edu/viewvc/unison/trunk/src/fsmonitor.py?view=markup&pathrev=471 .. _fsmonitor: http://github.com/shaurz/fsmonitor .. _guard: http://github.com/guard/guard .. _pyinotify: http://github.com/seb-m/pyinotify .. _inotify-tools: http://github.com/rvoicilas/inotify-tools .. _jnotify: http://jnotify.sourceforge.net/ .. _treewalker: http://github.com/jbd/treewatcher .. _file.monitor: http://github.com/pke/file.monitor .. _pyfilesystem: http://code.google.com/p/pyfilesystem .. :changelog: API changes ----------- 0.8.2 ~~~~~ - Event emitters are no longer started on schedule if ``Observer`` is not already running. 0.8.0 ~~~~~ - ``DirectorySnapshot``: methods returning internal stat info replaced by ``mtime``, ``inode`` and ``path`` methods. - ``DirectorySnapshot``: ``walker_callback`` parameter deprecated. Keywords: python filesystem monitoring monitor FSEvents kqueue inotify ReadDirectoryChangesW polling DirectorySnapshot Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Natural Language :: English Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: POSIX :: BSD Classifier: Operating System :: Microsoft :: Windows :: Windows NT/2000 Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Programming Language :: C Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: System :: Monitoring Classifier: Topic :: System :: Filesystems Classifier: Topic :: Utilities watchdog-0.9.0/README.rst0000755000175000017500000002054213341070440015547 0ustar danilodanilo00000000000000Watchdog ======== Python API and shell utilities to monitor file system events. Example API Usage ----------------- A simple program that uses watchdog to monitor directories specified as command-line arguments and logs events generated: .. code-block:: python import sys import time import logging from watchdog.observers import Observer from watchdog.events import LoggingEventHandler if __name__ == "__main__": logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S') path = sys.argv[1] if len(sys.argv) > 1 else '.' event_handler = LoggingEventHandler() observer = Observer() observer.schedule(event_handler, path, recursive=True) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join() Shell Utilities --------------- Watchdog comes with a utility script called ``watchmedo``. Please type ``watchmedo --help`` at the shell prompt to know more about this tool. Here is how you can log the current directory recursively for events related only to ``*.py`` and ``*.txt`` files while ignoring all directory events: .. code-block:: bash watchmedo log \ --patterns="*.py;*.txt" \ --ignore-directories \ --recursive \ . You can use the ``shell-command`` subcommand to execute shell commands in response to events: .. code-block:: bash watchmedo shell-command \ --patterns="*.py;*.txt" \ --recursive \ --command='echo "${watch_src_path}"' \ . Please see the help information for these commands by typing: .. code-block:: bash watchmedo [command] --help About ``watchmedo`` Tricks ~~~~~~~~~~~~~~~~~~~~~~~~~~ ``watchmedo`` can read ``tricks.yaml`` files and execute tricks within them in response to file system events. Tricks are actually event handlers that subclass ``watchdog.tricks.Trick`` and are written by plugin authors. Trick classes are augmented with a few additional features that regular event handlers don't need. An example ``tricks.yaml`` file: .. code-block:: yaml tricks: - watchdog.tricks.LoggerTrick: patterns: ["*.py", "*.js"] - watchmedo_webtricks.GoogleClosureTrick: patterns: ['*.js'] hash_names: true mappings_format: json # json|yaml|python mappings_module: app/javascript_mappings suffix: .min.js compilation_level: advanced # simple|advanced source_directory: app/static/js/ destination_directory: app/public/js/ files: index-page: - app/static/js/vendor/jquery*.js - app/static/js/base.js - app/static/js/index-page.js about-page: - app/static/js/vendor/jquery*.js - app/static/js/base.js - app/static/js/about-page/**/*.js The directory containing the ``tricks.yaml`` file will be monitored. Each trick class is initialized with its corresponding keys in the ``tricks.yaml`` file as arguments and events are fed to an instance of this class as they arrive. Tricks will be included in the 0.5.0 release. I need community input about them. Please file enhancement requests at the `issue tracker`_. Installation ------------ Installing from PyPI using ``pip``: .. code-block:: bash $ pip install watchdog Installing from PyPI using ``easy_install``: .. code-block:: bash $ easy_install watchdog Installing from source: .. code-block:: bash $ python setup.py install Installation Caveats ~~~~~~~~~~~~~~~~~~~~ The ``watchmedo`` script depends on PyYAML_ which links with LibYAML_, which brings a performance boost to the PyYAML parser. However, installing LibYAML_ is optional but recommended. On Mac OS X, you can use homebrew_ to install LibYAML: .. code-block:: bash $ brew install libyaml On Linux, use your favorite package manager to install LibYAML. Here's how you do it on Ubuntu: .. code-block:: bash $ sudo aptitude install libyaml-dev On Windows, please install PyYAML_ using the binaries they provide. Documentation ------------- You can browse the latest release documentation_ online. Contribute ---------- Fork the `repository`_ on GitHub and send a pull request, or file an issue ticket at the `issue tracker`_. For general help and questions use the official `mailing list`_ or ask on `stackoverflow`_ with tag `python-watchdog`. Create and activate your virtual environment, then:: pip install pytest pip install -e . py.test tests Supported Platforms ------------------- * Linux 2.6 (inotify) * Mac OS X (FSEvents, kqueue) * FreeBSD/BSD (kqueue) * Windows (ReadDirectoryChangesW with I/O completion ports; ReadDirectoryChangesW worker threads) * OS-independent (polling the disk for directory snapshots and comparing them periodically; slow and not recommended) Note that when using watchdog with kqueue, you need the number of file descriptors allowed to be opened by programs running on your system to be increased to more than the number of files that you will be monitoring. The easiest way to do that is to edit your ``~/.profile`` file and add a line similar to:: ulimit -n 1024 This is an inherent problem with kqueue because it uses file descriptors to monitor files. That plus the enormous amount of bookkeeping that watchdog needs to do in order to monitor file descriptors just makes this a painful way to monitor files and directories. In essence, kqueue is not a very scalable way to monitor a deeply nested directory of files and directories with a large number of files. About using watchdog with editors like Vim ------------------------------------------ Vim does not modify files unless directed to do so. It creates backup files and then swaps them in to replace the files you are editing on the disk. This means that if you use Vim to edit your files, the on-modified events for those files will not be triggered by watchdog. You may need to configure Vim to appropriately to disable this feature. Dependencies ------------ 1. Python 2.6 or above. 2. pathtools_ 3. select_backport_ (select.kqueue replacement for 2.6 on BSD/Mac OS X) 4. XCode_ (only on Mac OS X) 5. PyYAML_ (only for ``watchmedo`` script) 6. argh_ (only for ``watchmedo`` script) Licensing --------- Watchdog is licensed under the terms of the `Apache License, version 2.0`_. Copyright 2011 `Yesudeep Mangalapilly`_. Copyright 2012 Google, Inc. Project `source code`_ is available at Github. Please report bugs and file enhancement requests at the `issue tracker`_. Why Watchdog? ------------- Too many people tried to do the same thing and none did what I needed Python to do: * pnotify_ * `unison fsmonitor`_ * fsmonitor_ * guard_ * pyinotify_ * `inotify-tools`_ * jnotify_ * treewalker_ * `file.monitor`_ * pyfilesystem_ .. links: .. _Yesudeep Mangalapilly: yesudeep@gmail.com .. _source code: http://github.com/gorakhargosh/watchdog .. _issue tracker: http://github.com/gorakhargosh/watchdog/issues .. _Apache License, version 2.0: http://www.apache.org/licenses/LICENSE-2.0 .. _documentation: http://packages.python.org/watchdog/ .. _stackoverflow: http://stackoverflow.com/questions/tagged/python-watchdog .. _mailing list: http://groups.google.com/group/watchdog-python .. _repository: http://github.com/gorakhargosh/watchdog .. _issue tracker: http://github.com/gorakhargosh/watchdog/issues .. _homebrew: http://mxcl.github.com/homebrew/ .. _select_backport: http://pypi.python.org/pypi/select_backport .. _argh: http://pypi.python.org/pypi/argh .. _PyYAML: http://www.pyyaml.org/ .. _XCode: http://developer.apple.com/technologies/tools/xcode.html .. _LibYAML: http://pyyaml.org/wiki/LibYAML .. _pathtools: http://github.com/gorakhargosh/pathtools .. _pnotify: http://mark.heily.com/pnotify .. _unison fsmonitor: https://webdav.seas.upenn.edu/viewvc/unison/trunk/src/fsmonitor.py?view=markup&pathrev=471 .. _fsmonitor: http://github.com/shaurz/fsmonitor .. _guard: http://github.com/guard/guard .. _pyinotify: http://github.com/seb-m/pyinotify .. _inotify-tools: http://github.com/rvoicilas/inotify-tools .. _jnotify: http://jnotify.sourceforge.net/ .. _treewalker: http://github.com/jbd/treewatcher .. _file.monitor: http://github.com/pke/file.monitor .. _pyfilesystem: http://code.google.com/p/pyfilesystem watchdog-0.9.0/changelog.rst0000644000175000017500000000053413341070440016535 0ustar danilodanilo00000000000000.. :changelog: API changes ----------- 0.8.2 ~~~~~ - Event emitters are no longer started on schedule if ``Observer`` is not already running. 0.8.0 ~~~~~ - ``DirectorySnapshot``: methods returning internal stat info replaced by ``mtime``, ``inode`` and ``path`` methods. - ``DirectorySnapshot``: ``walker_callback`` parameter deprecated. watchdog-0.9.0/docs/0000755000175000017500000000000013341331653015010 5ustar danilodanilo00000000000000watchdog-0.9.0/docs/Makefile0000644000175000017500000001077213341070440016451 0ustar danilodanilo00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/watchdog.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/watchdog.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/watchdog" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/watchdog" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." watchdog-0.9.0/docs/dependencies.txt0000644000175000017500000000002013341070440020161 0ustar danilodanilo00000000000000pathtools>=0.1.2watchdog-0.9.0/docs/echo.py.txt0000644000175000017500000000706713341070440017122 0ustar danilodanilo00000000000000The echo module "echoes" calls to functions by tracing the arguments they're called with. It also supports echoing classes and modules by tracing all function calls made to them. Here's how. >>> import echo Let's define a function and echo calls to it: >>> def f(x, y, z): ... pass >>> f = echo.echo(f) >>> f(1, 2, 'three') f(x=1, y=2, z='three') We can also use decorator syntax: >>> @echo.echo ... def f(x, y, z): ... pass ... >>> f(1, 2, 'three') f(x=1, y=2, z='three') The decorated function maintain the undecorated function's attributes as far as possible: >>> def f(): ... " I'm f, don't change my docstring " ... pass ... >>> echo.echo(f).__doc__ " I'm f, don't change my docstring " Default arguments are echoed: >>> @echo.echo ... def fn_with_defaults(x=1, y=None): ... pass ... >>> fn_with_defaults() fn_with_defaults(x=1, y=None) Arbitrary nameless arguments are echoed: >>> @echo.echo ... def fn_with_nameless_args(*args): ... pass ... >>> fn_with_nameless_args(1, 2, 3) fn_with_nameless_args(1, 2, 3) >>> fn_with_nameless_args("abc", (1, 2, 3)) fn_with_nameless_args('abc', (1, 2, 3)) And so are keyword arguments (though note that the order of the output "arg=value" pairs is undefined). >>> @echo.echo ... def fn_with_keyword_args(**k): ... pass ... >>> fn_with_keyword_args(breakfast="spam") fn_with_keyword_args(breakfast='spam') >>> fn_with_keyword_args(breakfast='spam', lunch='spam', dinner='spam') fn_with_keyword_args(...) You can mix default positional, arbitrary and keyword arguments: >>> @echo.echo ... def full_monty(x, y, z='muesli', *v, **k): ... pass ... >>> full_monty('spam', 'eggs', extra='more spam') full_monty(x='spam', y='eggs', z='muesli', extra='more spam') >>> full_monty('spam', 'eggs', 'more spam', extra='even more spam') full_monty(x='spam', y='eggs', z='more spam', extra='even more spam') You can echo functions in a class by decorating them. >>> class Example(object): ... @echo.echo ... def __init__(self): pass ... @echo.echo ... def m(self): pass ... >>> ex = Example() __init__(self=) >>> ex.m() m(self=) This works equally well on classmethods and staticmethods, as well as on classic classes >>> class AnotherExample: ... @classmethod ... @echo.echo ... def cm(klass): pass ... @staticmethod ... @echo.echo ... def sm(): pass ... >>> AnotherExample.cm() cm(klass=) >>> AnotherExample.sm() sm() >>> another_ex = AnotherExample() >>> another_ex.cm() cm(klass=) >>> another_ex.sm() sm() Alternatively, don't decorate the methods you want to echo up front, retrospectively decorate the whole class. >>> class YetAnotherExample(object): ... def __init__(self): pass ... def m(self, x, y): pass ... @classmethod ... def cm(klass, x, y): pass ... @staticmethod ... def sm(x, y): pass ... >>> echo.echo_class(YetAnotherExample) >>> y = YetAnotherExample() __init__(self=) >>> y.m('echo', 'echo') m(self=, x='echo', y='echo') >>> y.cm('echo', 'echo') cm(klass=, x='echo', y='echo') >>> y.sm('echo', 'echo') sm(x='echo', y='echo') Private methods are echoed as well. >>> class Privates(object): ... def __myob(self): pass ... def do_something(self): self.__myob() ... >>> Privates().do_something() >>> echo.echo_class(Privates) >>> Privates().do_something() do_something(self=) __myob(self=) watchdog-0.9.0/docs/eclipse_cdt_style.xml0000644000175000017500000004003213341070440021221 0ustar danilodanilo00000000000000 watchdog-0.9.0/docs/make.bat0000644000175000017500000001064713341070440016417 0ustar danilodanilo00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\watchdog.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\watchdog.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end watchdog-0.9.0/docs/source/0000755000175000017500000000000013341331653016310 5ustar danilodanilo00000000000000watchdog-0.9.0/docs/source/api.rst0000644000175000017500000000221513341070440017605 0ustar danilodanilo00000000000000.. include:: global.rst.inc .. api_reference: ============= API Reference ============= `watchdog.events` ================= .. automodule:: watchdog.events `watchdog.observers.api` ======================== .. automodule:: watchdog.observers.api :synopsis: Classes useful to observer implementers. Immutables ---------- .. autoclass:: ObservedWatch :members: :show-inheritance: Collections ----------- .. autoclass:: EventQueue :members: :show-inheritance: Classes ------- .. autoclass:: EventEmitter :members: :show-inheritance: .. autoclass:: EventDispatcher :members: :show-inheritance: .. autoclass:: BaseObserver :members: :show-inheritance: `watchdog.observers` ==================== .. automodule:: watchdog.observers `watchdog.observers.polling` ============================ .. automodule:: watchdog.observers.polling `watchdog.utils` ================ .. automodule:: watchdog.utils `watchdog.utils.dirsnapshot` ============================ .. automodule:: watchdog.utils.dirsnapshot .. toctree:: :maxdepth: 2 watchdog-0.9.0/docs/source/conf.py0000644000175000017500000002045613341070440017610 0ustar danilodanilo00000000000000# -*- coding: utf-8 -*- # # watchdog documentation build configuration file, created by # sphinx-quickstart on Tue Nov 30 00:43:58 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os.path # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. TOP_DIR_PATH = os.path.abspath('../../') SRC_DIR_PATH = os.path.join(TOP_DIR_PATH, 'src') sys.path.insert(0, SRC_DIR_PATH) import watchdog.version PROJECT_NAME = 'watchdog' AUTHOR_NAME = 'Yesudeep Mangalapilly' COPYRIGHT = '2010, Yesudeep Mangalapilly' # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = PROJECT_NAME copyright = COPYRIGHT # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = watchdog.version.VERSION_STRING # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. #html_theme = 'default' html_theme = 'pyramid' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = '%sdoc' % PROJECT_NAME # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', '%s.tex' % PROJECT_NAME, '%s Documentation' % PROJECT_NAME, AUTHOR_NAME, 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', PROJECT_NAME, '%s Documentation' % PROJECT_NAME, [AUTHOR_NAME], 1) ] # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. epub_title = PROJECT_NAME epub_author = AUTHOR_NAME epub_publisher = AUTHOR_NAME epub_copyright = COPYRIGHT # The language of the text. It defaults to the language option # or en if the language is not set. #epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. #epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. #epub_identifier = '' # A unique identification for the text. #epub_uid = '' # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_post_files = [] # A list of files that should not be packed into the epub file. #epub_exclude_files = [] # The depth of the table of contents in toc.ncx. #epub_tocdepth = 3 # Allow duplicate toc entries. #epub_tocdup = True watchdog-0.9.0/docs/source/examples/0000755000175000017500000000000013341331653020126 5ustar danilodanilo00000000000000watchdog-0.9.0/docs/source/examples/logger.py0000644000175000017500000000060513341070440021752 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import time from watchdog.observers import Observer from watchdog.tricks import LoggerTrick event_handler = LoggerTrick() observer = Observer() observer.schedule(event_handler, sys.argv[1], recursive=True) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join() watchdog-0.9.0/docs/source/examples/patterns.py0000644000175000017500000000133013341070440022327 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import time from watchdog.events import PatternMatchingEventHandler from watchdog.observers import Observer import logging logging.basicConfig(level=logging.DEBUG) class MyEventHandler(PatternMatchingEventHandler): def on_any_event(self, event): logging.debug(event) event_handler = MyEventHandler(patterns=['*.py', '*.pyc'], ignore_patterns=['version.py'], ignore_directories=True) observer = Observer() observer.schedule(event_handler, sys.argv[1], recursive=True) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join() watchdog-0.9.0/docs/source/examples/simple.py0000644000175000017500000000155313341070440021767 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import logging import sys import time from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer logging.basicConfig(level=logging.DEBUG) class MyEventHandler(FileSystemEventHandler): def catch_all_handler(self, event): logging.debug(event) def on_moved(self, event): self.catch_all_handler(event) def on_created(self, event): self.catch_all_handler(event) def on_deleted(self, event): self.catch_all_handler(event) def on_modified(self, event): self.catch_all_handler(event) path = sys.argv[1] event_handler = MyEventHandler() observer = Observer() observer.schedule(event_handler, path, recursive=True) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join() watchdog-0.9.0/docs/source/examples/tricks.json0000644000175000017500000000166113341070440022316 0ustar danilodanilo00000000000000[ { "watchdog.tricks.LoggerTrick": { "patterns": [ "*.py", "*.js" ] } }, { "watchmedo_webtricks.GoogleClosureTrick": { "scripts": { "index-page": [ "app/static/js/vendor/jquery.js", "app/static/js/base.js", "app/static/js/index-page.js"], "about-page": [ "app/static/js/vendor/jquery.js", "app/static/js/base.js", "app/static/js/about-page.js"] }, "suffix": ".min.js", "source_directory": "app/static/js/", "hash_names": true, "patterns": ["*.js"], "destination_directory": "app/public/js/", "compilation_level": "advanced", "mappings_module": "app/javascript_mappings.json" } } ] watchdog-0.9.0/docs/source/examples/tricks.yaml0000644000175000017500000000125013341070440022301 0ustar danilodanilo00000000000000tricks: - watchdog.tricks.LoggerTrick: patterns: ["*.py", "*.js"] - watchmedo_webtricks.GoogleClosureTrick: patterns: ['*.js'] hash_names: true mappings_format: json # json|yaml|python mappings_module: app/javascript_mappings suffix: .min.js compilation_level: advanced # simple|advanced source_directory: app/static/js/ destination_directory: app/public/js/ files: index-page: - app/static/js/vendor/jquery.js - app/static/js/base.js - app/static/js/index-page.js about-page: - app/static/js/vendor/jquery.js - app/static/js/base.js - app/static/js/about-page.js watchdog-0.9.0/docs/source/global.rst.inc0000644000175000017500000000472713341327704021066 0ustar danilodanilo00000000000000.. Global includes, substitutions, and common links. .. |author_name| replace:: Yesudeep Mangalapilly .. |author_email| replace:: yesudeep@gmail.com .. |copyright| replace:: Copyright 2012 Google, Inc. .. |project_name| replace:: ``watchdog`` .. |project_version| replace:: 0.9.0 .. _issue tracker: http://github.com/gorakhargosh/watchdog/issues .. _code repository: http://github.com/gorakhargosh/watchdog .. _mailing list: http://groups.google.com/group/watchdog-python .. _kqueue: http://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2 .. _FSEvents: http://developer.apple.com/library/mac/#documentation/Darwin/Conceptual/FSEvents_ProgGuide/Introduction/Introduction.html .. _inotify: http://linux.die.net/man/7/inotify .. _Mac OS X File System Monitoring Performance Guidelines: http://developer.apple.com/library/ios/#documentation/Performance/Conceptual/FileSystem/Articles/TrackingChanges.html .. _ReadDirectoryChangesW: http://msdn.microsoft.com/en-us/library/aa365465(VS.85).aspx .. _GetQueuedCompletionStatus: http://msdn.microsoft.com/en-us/library/aa364986(v=VS.85).aspx .. _CreateIoCompletionPort: http://msdn.microsoft.com/en-us/library/aa363862(v=VS.85).aspx .. _argh: http://pypi.python.org/pypi/argh .. _argparse: http://pypi.python.org/pypi/argparse .. _coverage: http://nedbatchelder.com/code/coverage/ .. _file.monitor: http://github.com/pke/file.monitor .. _fsmonitor: http://github.com/shaurz/fsmonitor .. _git: http://git-scm.org/ .. _github: http://github.com/ .. _guard: http://github.com/guard/guard .. _homebrew: http://mxcl.github.com/homebrew/ .. _inotify-tools: http://github.com/rvoicilas/inotify-tools .. _jnotify: http://jnotify.sourceforge.net/ .. _LibYAML: http://pyyaml.org/wiki/LibYAML .. _nose: http://nose.readthedocs.org/en/latest/ .. _pip: http://pypi.python.org/pypi/pip .. _pnotify: http://mark.heily.com/pnotify .. _pyfilesystem: http://code.google.com/p/pyfilesystem .. _pyinotify: http://github.com/seb-m/pyinotify .. _Python: http://python.org .. _pywin32: http://sourceforge.net/projects/pywin32/ .. _PyYAML: http://www.pyyaml.org/ .. _select_backport: http://pypi.python.org/pypi/select_backport/ .. _sphinx: http://sphinx.pocoo.org/ .. _treewalker: http://github.com/jbd/treewatcher .. _unison fsmonitor: https://webdav.seas.upenn.edu/viewvc/unison/trunk/src/fsmonitor.py?view=markup&pathrev=471 .. _XCode: http://developer.apple.com/technologies/tools/xcode.html .. _zc.buildout: http://www.buildout.org/ .. _pathtools: http://github.com/gorakhargosh/pathtools watchdog-0.9.0/docs/source/hacking.rst0000644000175000017500000000370413341070440020444 0ustar danilodanilo00000000000000.. include:: global.rst.inc .. _hacking: Contributing ============ Welcome hacker! So you have got something you would like to see in |project_name|? Whee. This document will help you get started. Important URLs -------------- |project_name| uses git_ to track code history and hosts its `code repository`_ at github_. The `issue tracker`_ is where you can file bug reports and request features or enhancements to |project_name|. Before you start ---------------- Ensure your system has the following programs and libraries installed before beginning to hack: 1. Python_ 2. git_ 3. ssh 4. XCode_ (on Mac OS X) 5. select_backport_ (on BSD/Mac OS X if you're using Python 2.6) Setting up the Work Environment ------------------------------- |project_name| makes extensive use of zc.buildout_ to set up its work environment. You should get familiar with it. Steps to setting up a clean environment: 1. Fork the `code repository`_ into your github_ account. Let us call you ``hackeratti`` for the sake of this example. Replace ``hackeratti`` with your own username below. 2. Clone your fork and setup your environment:: $ git clone --recursive git@github.com:hackeratti/watchdog.git $ cd watchdog $ python tools/bootstrap.py --distribute $ bin/buildout .. IMPORTANT:: Re-run ``bin/buildout`` every time you make a change to the ``buildout.cfg`` file. That's it with the setup. Now you're ready to hack on |project_name|. Enabling Continuous Integration ------------------------------- The repository checkout contains a script called ``autobuild.sh`` which you must run prior to making changes. It will detect changes to Python source code or restructuredText documentation files anywhere in the directory tree and rebuild sphinx_ documentation, run all tests using nose_, and generate coverage_ reports. Start it by issuing this command in the ``watchdog`` directory checked out earlier:: $ tools/autobuild.sh ... Happy hacking! watchdog-0.9.0/docs/source/index.rst0000644000175000017500000000243713341070440020151 0ustar danilodanilo00000000000000.. watchdog documentation master file, created by sphinx-quickstart on Tue Nov 30 00:43:58 2010. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. .. include:: global.rst.inc Watchdog ======== Python API library and shell utilities to monitor file system events. Directory monitoring made easy with ----------------------------------- * A cross-platform API. * A shell tool to run commands in response to directory changes. Get started quickly with a simple example in :ref:`quickstart`. Easy installation ----------------- You can use pip_ to install |project_name| quickly and easily:: $ pip install watchdog Need more help with installing? See :ref:`installation`. User's Guide ============ .. toctree:: :maxdepth: 2 installation quickstart api hacking Contribute ========== Found a bug in or want a feature added to |project_name|? You can fork the official `code repository`_ or file an issue ticket at the `issue tracker`_. You can also ask questions at the official `mailing list`_. You may also want to refer to :ref:`hacking` for information about contributing code or documentation to |project_name|. Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` watchdog-0.9.0/docs/source/installation.rst0000644000175000017500000001513013341070440021535 0ustar danilodanilo00000000000000.. include:: global.rst.inc .. _installation: Installation ============ |project_name| requires Python 2.6 or above to work. If you are using a Linux/FreeBSD/Mac OS X system, you already have Python installed. However, you may wish to upgrade your system to Python 2.7 at least, because this version comes with updates that can reduce compatibility problems. See a list of :ref:`installation-dependencies`. Installing from PyPI using pip ------------------------------ .. parsed-literal:: $ pip install |project_name| Installing from source tarballs ------------------------------- .. parsed-literal:: $ wget -c http://pypi.python.org/packages/source/w/watchdog/watchdog-|project_version|.tar.gz $ tar zxvf |project_name|-|project_version|.tar.gz $ cd |project_name|-|project_version| $ python setup.py install Installing from the code repository ----------------------------------- :: $ git clone --recursive git://github.com/gorakhargosh/watchdog.git $ cd watchdog $ python setup.py install .. _installation-dependencies: Dependencies ------------ |project_name| depends on many libraries to do its job. The following is a list of dependencies you need based on the operating system you are using. +---------------------+-------------+-------------+-------------+-------------+ | Operating system | Windows | Linux 2.6 | Mac OS X/ | BSD | | Dependency (row) | | | Darwin | | +=====================+=============+=============+=============+=============+ | XCode_ | | | Yes | | +---------------------+-------------+-------------+-------------+-------------+ | PyYAML_ | Yes | Yes | Yes | Yes | +---------------------+-------------+-------------+-------------+-------------+ | argh_ | Yes | Yes | Yes | Yes | +---------------------+-------------+-------------+-------------+-------------+ | argparse_ | Yes | Yes | Yes | Yes | +---------------------+-------------+-------------+-------------+-------------+ | select_backport_ | | | Yes | Yes | | (Python 2.6) | | | | | +---------------------+-------------+-------------+-------------+-------------+ | pathtools_ | Yes | Yes | Yes | Yes | +---------------------+-------------+-------------+-------------+-------------+ Installing Dependencies ~~~~~~~~~~~~~~~~~~~~~~~ The ``watchmedo`` script depends on PyYAML_ which links with LibYAML_. On Mac OS X, you can use homebrew_ to install LibYAML:: brew install libyaml On Linux, use your favorite package manager to install LibYAML. Here's how you do it on Ubuntu:: sudo aptitude install libyaml-dev On Windows, please install PyYAML_ using the binaries they provide. Supported Platforms (and Caveats) --------------------------------- |project_name| uses native APIs as much as possible falling back to polling the disk periodically to compare directory snapshots only when it cannot use an API natively-provided by the underlying operating system. The following operating systems are currently supported: .. WARNING:: Differences between behaviors of these native API are noted below. Linux 2.6+ Linux kernel version 2.6 and later come with an API called inotify_ that programs can use to monitor file system events. .. NOTE:: On most systems the maximum number of watches that can be created per user is limited to ``8192``. |project_name| needs one per directory to monitor. To change this limit, edit ``/etc/sysctl.conf`` and add:: fs.inotify.max_user_watches=16384 Mac OS X The Darwin kernel/OS X API maintains two ways to monitor directories for file system events: * kqueue_ * FSEvents_ |project_name| can use whichever one is available, preferring FSEvents over ``kqueue(2)``. ``kqueue(2)`` uses open file descriptors for monitoring and the current implementation uses `Mac OS X File System Monitoring Performance Guidelines`_ to open these file descriptors only to monitor events, thus allowing OS X to unmount volumes that are being watched without locking them. .. NOTE:: More information about how |project_name| uses ``kqueue(2)`` is noted in `BSD Unix variants`_. Much of this information applies to Mac OS X as well. _`BSD Unix variants` BSD variants come with kqueue_ which programs can use to monitor changes to open file descriptors. Because of the way ``kqueue(2)`` works, |project_name| needs to open these files and directories in read-only non-blocking mode and keep books about them. |project_name| will automatically open file descriptors for all new files/directories created and close those for which are deleted. .. NOTE:: The maximum number of open file descriptor per process limit on your operating system can hinder |project_name|'s ability to monitor files. You should ensure this limit is set to at least **1024** (or a value suitable to your usage). The following command appended to your ``~/.profile`` configuration file does this for you:: ulimit -n 1024 Windows Vista and later The Windows API provides the ReadDirectoryChangesW_. |project_name| currently contains implementation for a synchronous approach requiring additional API functionality only available in Windows Vista and later. .. NOTE:: Since renaming is not the same operation as movement on Windows, |project_name| tries hard to convert renames to movement events. Also, because the ReadDirectoryChangesW_ API function returns rename/movement events for directories even before the underlying I/O is complete, |project_name| may not be able to completely scan the moved directory in order to successfully queue movement events for files and directories within it. .. NOTE:: Since the Windows API does not provide information about whether an object is a file or a directory, delete events for directories may be reported as a file deleted event. OS Independent Polling |project_name| also includes a fallback-implementation that polls watched directories for changes by periodically comparing snapshots of the directory tree. watchdog-0.9.0/docs/source/quickstart.rst0000644000175000017500000000355013341070440021231 0ustar danilodanilo00000000000000.. include:: global.rst.inc .. _quickstart: Quickstart ========== Below we present a simple example that monitors the current directory recursively (which means, it will traverse any sub-directories) to detect changes. Here is what we will do with the API: 1. Create an instance of the :class:`watchdog.observers.Observer` thread class. 2. Implement a subclass of :class:`watchdog.events.FileSystemEventHandler` (or as in our case, we will use the built-in :class:`watchdog.events.LoggingEventHandler`, which already does). 3. Schedule monitoring a few paths with the observer instance attaching the event handler. 4. Start the observer thread and wait for it generate events without blocking our main thread. By default, an :class:`watchdog.observers.Observer` instance will not monitor sub-directories. By passing ``recursive=True`` in the call to :meth:`watchdog.observers.Observer.schedule` monitoring entire directory trees is ensured. A Simple Example ---------------- The following example program will monitor the current directory recursively for file system changes and simply log them to the console:: import sys import logging from watchdog.observers import Observer from watchdog.events import LoggingEventHandler if __name__ == "__main__": logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S') path = sys.argv[1] if len(sys.argv) > 1 else '.' event_handler = LoggingEventHandler() observer = Observer() observer.schedule(event_handler, path, recursive=True) observer.start() try: while observer.isAlive(): observer.join(1) except KeyboardInterrupt: observer.stop() observer.join() To stop the program, press Control-C. watchdog-0.9.0/setup.cfg0000644000175000017500000000024213341331653015677 0ustar danilodanilo00000000000000[build_sphinx] source-dir = docs/source build-dir = docs/build all_files = 1 [upload_sphinx] upload-dir = docs/build/html [egg_info] tag_build = tag_date = 0 watchdog-0.9.0/setup.py0000644000175000017500000001332313341321132015563 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys import imp import os.path from setuptools import setup, find_packages from setuptools.extension import Extension from setuptools.command.build_ext import build_ext from setuptools.command.test import test as TestCommand from distutils.util import get_platform SRC_DIR = 'src' WATCHDOG_PKG_DIR = os.path.join(SRC_DIR, 'watchdog') version = imp.load_source('version', os.path.join(WATCHDOG_PKG_DIR, 'version.py')) ext_modules = [] if get_platform().startswith('macosx'): ext_modules = [ Extension( name='_watchdog_fsevents', sources=[ 'src/watchdog_fsevents.c', ], libraries=['m'], define_macros=[ ('WATCHDOG_VERSION_STRING', '"' + version.VERSION_STRING + '"'), ('WATCHDOG_VERSION_MAJOR', version.VERSION_MAJOR), ('WATCHDOG_VERSION_MINOR', version.VERSION_MINOR), ('WATCHDOG_VERSION_BUILD', version.VERSION_BUILD), ], extra_link_args=[ '-framework', 'CoreFoundation', '-framework', 'CoreServices', ], extra_compile_args=[ '-std=c99', '-pedantic', '-Wall', '-Wextra', '-fPIC', # required w/Xcode 5.1+ and above because of '-mno-fused-madd' '-Wno-error=unused-command-line-argument-hard-error-in-future' ] ), ] class PyTest(TestCommand): def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [ '--cov=' + SRC_DIR, '--cov-report=term-missing', 'tests'] self.test_suite = True def run_tests(self): import pytest errno = pytest.main(self.test_args) sys.exit(errno) tests_require=['pytest', 'pytest-cov', 'pytest-timeout >=0.3'] if sys.version_info < (2, 7, 0): tests_require.append('unittest2') install_requires = [ "PyYAML<3.13" if sys.version_info[:2] == (3, 2) else "PyYAML>=3.10", "argh>=0.24.1", "pathtools>=0.1.1", ] if sys.version_info < (2, 7, 0): # argparse is merged into Python 2.7 in the Python 2x series # and Python 3.2 in the Python 3x series. install_requires.append('argparse >=1.1') if any([key in sys.platform for key in ['bsd', 'darwin']]): # Python 2.6 and below have the broken/non-existent kqueue implementations # in the select module. This backported patch adds one from Python 2.7, # which works. install_requires.append('select_backport >=0.2') with open('README.rst') as f: readme = f.read() with open('changelog.rst') as f: changelog = f.read() setup(name="watchdog", version=version.VERSION_STRING, description="Filesystem events monitoring", long_description=readme + '\n\n' + changelog, author="Yesudeep Mangalapilly", author_email="yesudeep@gmail.com", license="Apache License 2.0", url="http://github.com/gorakhargosh/watchdog", keywords=' '.join([ 'python', 'filesystem', 'monitoring', 'monitor', 'FSEvents', 'kqueue', 'inotify', 'ReadDirectoryChangesW', 'polling', 'DirectorySnapshot', ]), classifiers=[ 'Development Status :: 3 - Alpha', 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', 'Natural Language :: English', 'Operating System :: POSIX :: Linux', 'Operating System :: MacOS :: MacOS X', 'Operating System :: POSIX :: BSD', 'Operating System :: Microsoft :: Windows :: Windows NT/2000', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: PyPy', 'Programming Language :: C', 'Topic :: Software Development :: Libraries', 'Topic :: System :: Monitoring', 'Topic :: System :: Filesystems', 'Topic :: Utilities', ], package_dir={'': SRC_DIR}, packages=find_packages(SRC_DIR), include_package_data=True, install_requires=install_requires, tests_require=tests_require, cmdclass={ 'build_ext': build_ext, 'test': PyTest, }, ext_modules=ext_modules, entry_points={'console_scripts': [ 'watchmedo = watchdog.watchmedo:main', ]}, zip_safe=False ) watchdog-0.9.0/src/0000755000175000017500000000000013341331653014647 5ustar danilodanilo00000000000000watchdog-0.9.0/src/watchdog/0000755000175000017500000000000013341331653016447 5ustar danilodanilo00000000000000watchdog-0.9.0/src/watchdog/__init__.py0000644000175000017500000000125413341070440020554 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. watchdog-0.9.0/src/watchdog/events.py0000644000175000017500000004427413341070440020332 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ :module: watchdog.events :synopsis: File system events and event handlers. :author: yesudeep@google.com (Yesudeep Mangalapilly) Event Classes ------------- .. autoclass:: FileSystemEvent :members: :show-inheritance: :inherited-members: .. autoclass:: FileSystemMovedEvent :members: :show-inheritance: .. autoclass:: FileMovedEvent :members: :show-inheritance: .. autoclass:: DirMovedEvent :members: :show-inheritance: .. autoclass:: FileModifiedEvent :members: :show-inheritance: .. autoclass:: DirModifiedEvent :members: :show-inheritance: .. autoclass:: FileCreatedEvent :members: :show-inheritance: .. autoclass:: DirCreatedEvent :members: :show-inheritance: .. autoclass:: FileDeletedEvent :members: :show-inheritance: .. autoclass:: DirDeletedEvent :members: :show-inheritance: Event Handler Classes --------------------- .. autoclass:: FileSystemEventHandler :members: :show-inheritance: .. autoclass:: PatternMatchingEventHandler :members: :show-inheritance: .. autoclass:: RegexMatchingEventHandler :members: :show-inheritance: .. autoclass:: LoggingEventHandler :members: :show-inheritance: """ import os.path import logging import re from pathtools.patterns import match_any_paths from watchdog.utils import has_attribute from watchdog.utils import unicode_paths EVENT_TYPE_MOVED = 'moved' EVENT_TYPE_DELETED = 'deleted' EVENT_TYPE_CREATED = 'created' EVENT_TYPE_MODIFIED = 'modified' class FileSystemEvent(object): """ Immutable type that represents a file system event that is triggered when a change occurs on the monitored file system. All FileSystemEvent objects are required to be immutable and hence can be used as keys in dictionaries or be added to sets. """ event_type = None """The type of the event as a string.""" is_directory = False """True if event was emitted for a directory; False otherwise.""" def __init__(self, src_path): self._src_path = src_path @property def src_path(self): """Source path of the file system object that triggered this event.""" return self._src_path def __str__(self): return self.__repr__() def __repr__(self): return ("<%(class_name)s: event_type=%(event_type)s, " "src_path=%(src_path)r, " "is_directory=%(is_directory)s>" ) % (dict( class_name=self.__class__.__name__, event_type=self.event_type, src_path=self.src_path, is_directory=self.is_directory)) # Used for comparison of events. @property def key(self): return (self.event_type, self.src_path, self.is_directory) def __eq__(self, event): return self.key == event.key def __ne__(self, event): return self.key != event.key def __hash__(self): return hash(self.key) class FileSystemMovedEvent(FileSystemEvent): """ File system event representing any kind of file system movement. """ event_type = EVENT_TYPE_MOVED def __init__(self, src_path, dest_path): super(FileSystemMovedEvent, self).__init__(src_path) self._dest_path = dest_path @property def dest_path(self): """The destination path of the move event.""" return self._dest_path # Used for hashing this as an immutable object. @property def key(self): return (self.event_type, self.src_path, self.dest_path, self.is_directory) def __repr__(self): return ("<%(class_name)s: src_path=%(src_path)r, " "dest_path=%(dest_path)r, " "is_directory=%(is_directory)s>" ) % (dict(class_name=self.__class__.__name__, src_path=self.src_path, dest_path=self.dest_path, is_directory=self.is_directory)) # File events. class FileDeletedEvent(FileSystemEvent): """File system event representing file deletion on the file system.""" event_type = EVENT_TYPE_DELETED def __init__(self, src_path): super(FileDeletedEvent, self).__init__(src_path) def __repr__(self): return "<%(class_name)s: src_path=%(src_path)r>" %\ dict(class_name=self.__class__.__name__, src_path=self.src_path) class FileModifiedEvent(FileSystemEvent): """File system event representing file modification on the file system.""" event_type = EVENT_TYPE_MODIFIED def __init__(self, src_path): super(FileModifiedEvent, self).__init__(src_path) def __repr__(self): return ("<%(class_name)s: src_path=%(src_path)r>" ) % (dict(class_name=self.__class__.__name__, src_path=self.src_path)) class FileCreatedEvent(FileSystemEvent): """File system event representing file creation on the file system.""" event_type = EVENT_TYPE_CREATED def __init__(self, src_path): super(FileCreatedEvent, self).__init__(src_path) def __repr__(self): return ("<%(class_name)s: src_path=%(src_path)r>" ) % (dict(class_name=self.__class__.__name__, src_path=self.src_path)) class FileMovedEvent(FileSystemMovedEvent): """File system event representing file movement on the file system.""" def __init__(self, src_path, dest_path): super(FileMovedEvent, self).__init__(src_path, dest_path) def __repr__(self): return ("<%(class_name)s: src_path=%(src_path)r, " "dest_path=%(dest_path)r>" ) % (dict(class_name=self.__class__.__name__, src_path=self.src_path, dest_path=self.dest_path)) # Directory events. class DirDeletedEvent(FileSystemEvent): """File system event representing directory deletion on the file system.""" event_type = EVENT_TYPE_DELETED is_directory = True def __init__(self, src_path): super(DirDeletedEvent, self).__init__(src_path) def __repr__(self): return ("<%(class_name)s: src_path=%(src_path)r>" ) % (dict(class_name=self.__class__.__name__, src_path=self.src_path)) class DirModifiedEvent(FileSystemEvent): """ File system event representing directory modification on the file system. """ event_type = EVENT_TYPE_MODIFIED is_directory = True def __init__(self, src_path): super(DirModifiedEvent, self).__init__(src_path) def __repr__(self): return ("<%(class_name)s: src_path=%(src_path)r>" ) % (dict(class_name=self.__class__.__name__, src_path=self.src_path)) class DirCreatedEvent(FileSystemEvent): """File system event representing directory creation on the file system.""" event_type = EVENT_TYPE_CREATED is_directory = True def __init__(self, src_path): super(DirCreatedEvent, self).__init__(src_path) def __repr__(self): return ("<%(class_name)s: src_path=%(src_path)r>" ) % (dict(class_name=self.__class__.__name__, src_path=self.src_path)) class DirMovedEvent(FileSystemMovedEvent): """File system event representing directory movement on the file system.""" is_directory = True def __init__(self, src_path, dest_path): super(DirMovedEvent, self).__init__(src_path, dest_path) def __repr__(self): return ("<%(class_name)s: src_path=%(src_path)r, " "dest_path=%(dest_path)r>" ) % (dict(class_name=self.__class__.__name__, src_path=self.src_path, dest_path=self.dest_path)) class FileSystemEventHandler(object): """ Base file system event handler that you can override methods from. """ def dispatch(self, event): """Dispatches events to the appropriate methods. :param event: The event object representing the file system event. :type event: :class:`FileSystemEvent` """ self.on_any_event(event) _method_map = { EVENT_TYPE_MODIFIED: self.on_modified, EVENT_TYPE_MOVED: self.on_moved, EVENT_TYPE_CREATED: self.on_created, EVENT_TYPE_DELETED: self.on_deleted, } event_type = event.event_type _method_map[event_type](event) def on_any_event(self, event): """Catch-all event handler. :param event: The event object representing the file system event. :type event: :class:`FileSystemEvent` """ def on_moved(self, event): """Called when a file or a directory is moved or renamed. :param event: Event representing file/directory movement. :type event: :class:`DirMovedEvent` or :class:`FileMovedEvent` """ def on_created(self, event): """Called when a file or directory is created. :param event: Event representing file/directory creation. :type event: :class:`DirCreatedEvent` or :class:`FileCreatedEvent` """ def on_deleted(self, event): """Called when a file or directory is deleted. :param event: Event representing file/directory deletion. :type event: :class:`DirDeletedEvent` or :class:`FileDeletedEvent` """ def on_modified(self, event): """Called when a file or directory is modified. :param event: Event representing file/directory modification. :type event: :class:`DirModifiedEvent` or :class:`FileModifiedEvent` """ class PatternMatchingEventHandler(FileSystemEventHandler): """ Matches given patterns with file paths associated with occurring events. """ def __init__(self, patterns=None, ignore_patterns=None, ignore_directories=False, case_sensitive=False): super(PatternMatchingEventHandler, self).__init__() self._patterns = patterns self._ignore_patterns = ignore_patterns self._ignore_directories = ignore_directories self._case_sensitive = case_sensitive @property def patterns(self): """ (Read-only) Patterns to allow matching event paths. """ return self._patterns @property def ignore_patterns(self): """ (Read-only) Patterns to ignore matching event paths. """ return self._ignore_patterns @property def ignore_directories(self): """ (Read-only) ``True`` if directories should be ignored; ``False`` otherwise. """ return self._ignore_directories @property def case_sensitive(self): """ (Read-only) ``True`` if path names should be matched sensitive to case; ``False`` otherwise. """ return self._case_sensitive def dispatch(self, event): """Dispatches events to the appropriate methods. :param event: The event object representing the file system event. :type event: :class:`FileSystemEvent` """ if self.ignore_directories and event.is_directory: return paths = [] if has_attribute(event, 'dest_path'): paths.append(unicode_paths.decode(event.dest_path)) if event.src_path: paths.append(unicode_paths.decode(event.src_path)) if match_any_paths(paths, included_patterns=self.patterns, excluded_patterns=self.ignore_patterns, case_sensitive=self.case_sensitive): self.on_any_event(event) _method_map = { EVENT_TYPE_MODIFIED: self.on_modified, EVENT_TYPE_MOVED: self.on_moved, EVENT_TYPE_CREATED: self.on_created, EVENT_TYPE_DELETED: self.on_deleted, } event_type = event.event_type _method_map[event_type](event) class RegexMatchingEventHandler(FileSystemEventHandler): """ Matches given regexes with file paths associated with occurring events. """ def __init__(self, regexes=[r".*"], ignore_regexes=[], ignore_directories=False, case_sensitive=False): super(RegexMatchingEventHandler, self).__init__() if case_sensitive: self._regexes = [re.compile(r) for r in regexes] self._ignore_regexes = [re.compile(r) for r in ignore_regexes] else: self._regexes = [re.compile(r, re.I) for r in regexes] self._ignore_regexes = [re.compile(r, re.I) for r in ignore_regexes] self._ignore_directories = ignore_directories self._case_sensitive = case_sensitive @property def regexes(self): """ (Read-only) Regexes to allow matching event paths. """ return self._regexes @property def ignore_regexes(self): """ (Read-only) Regexes to ignore matching event paths. """ return self._ignore_regexes @property def ignore_directories(self): """ (Read-only) ``True`` if directories should be ignored; ``False`` otherwise. """ return self._ignore_directories @property def case_sensitive(self): """ (Read-only) ``True`` if path names should be matched sensitive to case; ``False`` otherwise. """ return self._case_sensitive def dispatch(self, event): """Dispatches events to the appropriate methods. :param event: The event object representing the file system event. :type event: :class:`FileSystemEvent` """ if self.ignore_directories and event.is_directory: return paths = [] if has_attribute(event, 'dest_path'): paths.append(unicode_paths.decode(event.dest_path)) if event.src_path: paths.append(unicode_paths.decode(event.src_path)) if any(r.match(p) for r in self.ignore_regexes for p in paths): return if any(r.match(p) for r in self.regexes for p in paths): self.on_any_event(event) _method_map = { EVENT_TYPE_MODIFIED: self.on_modified, EVENT_TYPE_MOVED: self.on_moved, EVENT_TYPE_CREATED: self.on_created, EVENT_TYPE_DELETED: self.on_deleted, } event_type = event.event_type _method_map[event_type](event) class LoggingEventHandler(FileSystemEventHandler): """Logs all the events captured.""" def on_moved(self, event): super(LoggingEventHandler, self).on_moved(event) what = 'directory' if event.is_directory else 'file' logging.info("Moved %s: from %s to %s", what, event.src_path, event.dest_path) def on_created(self, event): super(LoggingEventHandler, self).on_created(event) what = 'directory' if event.is_directory else 'file' logging.info("Created %s: %s", what, event.src_path) def on_deleted(self, event): super(LoggingEventHandler, self).on_deleted(event) what = 'directory' if event.is_directory else 'file' logging.info("Deleted %s: %s", what, event.src_path) def on_modified(self, event): super(LoggingEventHandler, self).on_modified(event) what = 'directory' if event.is_directory else 'file' logging.info("Modified %s: %s", what, event.src_path) class LoggingFileSystemEventHandler(LoggingEventHandler): """ For backwards-compatibility. Please use :class:`LoggingEventHandler` instead. """ def generate_sub_moved_events(src_dir_path, dest_dir_path): """Generates an event list of :class:`DirMovedEvent` and :class:`FileMovedEvent` objects for all the files and directories within the given moved directory that were moved along with the directory. :param src_dir_path: The source path of the moved directory. :param dest_dir_path: The destination path of the moved directory. :returns: An iterable of file system events of type :class:`DirMovedEvent` and :class:`FileMovedEvent`. """ for root, directories, filenames in os.walk(dest_dir_path): for directory in directories: full_path = os.path.join(root, directory) renamed_path = full_path.replace(dest_dir_path, src_dir_path) if src_dir_path else None yield DirMovedEvent(renamed_path, full_path) for filename in filenames: full_path = os.path.join(root, filename) renamed_path = full_path.replace(dest_dir_path, src_dir_path) if src_dir_path else None yield FileMovedEvent(renamed_path, full_path) def generate_sub_created_events(src_dir_path): """Generates an event list of :class:`DirCreatedEvent` and :class:`FileCreatedEvent` objects for all the files and directories within the given moved directory that were moved along with the directory. :param src_dir_path: The source path of the created directory. :returns: An iterable of file system events of type :class:`DirCreatedEvent` and :class:`FileCreatedEvent`. """ for root, directories, filenames in os.walk(src_dir_path): for directory in directories: yield DirCreatedEvent(os.path.join(root, directory)) for filename in filenames: yield FileCreatedEvent(os.path.join(root, filename))watchdog-0.9.0/src/watchdog/observers/0000755000175000017500000000000013341331653020461 5ustar danilodanilo00000000000000watchdog-0.9.0/src/watchdog/observers/__init__.py0000644000175000017500000000673213341121055022573 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ :module: watchdog.observers :synopsis: Observer that picks a native implementation if available. :author: yesudeep@google.com (Yesudeep Mangalapilly) Classes ======= .. autoclass:: Observer :members: :show-inheritance: :inherited-members: Observer thread that schedules watching directories and dispatches calls to event handlers. You can also import platform specific classes directly and use it instead of :class:`Observer`. Here is a list of implemented observer classes.: ============== ================================ ============================== Class Platforms Note ============== ================================ ============================== |Inotify| Linux 2.6.13+ ``inotify(7)`` based observer |FSEvents| Mac OS X FSEvents based observer |Kqueue| Mac OS X and BSD with kqueue(2) ``kqueue(2)`` based observer |WinApi| MS Windows Windows API-based observer |Polling| Any fallback implementation ============== ================================ ============================== .. |Inotify| replace:: :class:`.inotify.InotifyObserver` .. |FSEvents| replace:: :class:`.fsevents.FSEventsObserver` .. |Kqueue| replace:: :class:`.kqueue.KqueueObserver` .. |WinApi| replace:: :class:`.read_directory_changes.WindowsApiObserver` .. |WinApiAsync| replace:: :class:`.read_directory_changes_async.WindowsApiAsyncObserver` .. |Polling| replace:: :class:`.polling.PollingObserver` """ import warnings from watchdog.utils import platform from watchdog.utils import UnsupportedLibc if platform.is_linux(): try: from .inotify import InotifyObserver as Observer except UnsupportedLibc: from .polling import PollingObserver as Observer elif platform.is_darwin(): # FIXME: catching too broad. Error prone try: from .fsevents import FSEventsObserver as Observer except: try: from .kqueue import KqueueObserver as Observer warnings.warn("Failed to import fsevents. Fall back to kqueue") except: from .polling import PollingObserver as Observer warnings.warn("Failed to import fsevents and kqueue. Fall back to polling.") elif platform.is_bsd(): from .kqueue import KqueueObserver as Observer elif platform.is_windows(): # TODO: find a reliable way of checking Windows version and import # polling explicitly for Windows XP try: from .read_directory_changes import WindowsApiObserver as Observer except: from .polling import PollingObserver as Observer warnings.warn("Failed to import read_directory_changes. Fall back to polling.") else: from .polling import PollingObserver as Observer __all__ = ["Observer"] watchdog-0.9.0/src/watchdog/observers/api.py0000644000175000017500000002671013341070440021604 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import with_statement import threading from watchdog.utils import BaseThread from watchdog.utils.compat import queue from watchdog.utils.bricks import SkipRepeatsQueue DEFAULT_EMITTER_TIMEOUT = 1 # in seconds. DEFAULT_OBSERVER_TIMEOUT = 1 # in seconds. # Collection classes class EventQueue(SkipRepeatsQueue): """Thread-safe event queue based on a special queue that skips adding the same event (:class:`FileSystemEvent`) multiple times consecutively. Thus avoiding dispatching multiple event handling calls when multiple identical events are produced quicker than an observer can consume them. """ class ObservedWatch(object): """An scheduled watch. :param path: Path string. :param recursive: ``True`` if watch is recursive; ``False`` otherwise. """ def __init__(self, path, recursive): self._path = path self._is_recursive = recursive @property def path(self): """The path that this watch monitors.""" return self._path @property def is_recursive(self): """Determines whether subdirectories are watched for the path.""" return self._is_recursive @property def key(self): return self.path, self.is_recursive def __eq__(self, watch): return self.key == watch.key def __ne__(self, watch): return self.key != watch.key def __hash__(self): return hash(self.key) def __repr__(self): return "" % ( self.path, self.is_recursive) # Observer classes class EventEmitter(BaseThread): """ Producer thread base class subclassed by event emitters that generate events and populate a queue with them. :param event_queue: The event queue to populate with generated events. :type event_queue: :class:`watchdog.events.EventQueue` :param watch: The watch to observe and produce events for. :type watch: :class:`ObservedWatch` :param timeout: Timeout (in seconds) between successive attempts at reading events. :type timeout: ``float`` """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT): BaseThread.__init__(self) self._event_queue = event_queue self._watch = watch self._timeout = timeout @property def timeout(self): """ Blocking timeout for reading events. """ return self._timeout @property def watch(self): """ The watch associated with this emitter. """ return self._watch def queue_event(self, event): """ Queues a single event. :param event: Event to be queued. :type event: An instance of :class:`watchdog.events.FileSystemEvent` or a subclass. """ self._event_queue.put((event, self.watch)) def queue_events(self, timeout): """Override this method to populate the event queue with events per interval period. :param timeout: Timeout (in seconds) between successive attempts at reading events. :type timeout: ``float`` """ def run(self): try: while self.should_keep_running(): self.queue_events(self.timeout) finally: pass class EventDispatcher(BaseThread): """ Consumer thread base class subclassed by event observer threads that dispatch events from an event queue to appropriate event handlers. :param timeout: Event queue blocking timeout (in seconds). :type timeout: ``float`` """ def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT): BaseThread.__init__(self) self._event_queue = EventQueue() self._timeout = timeout @property def timeout(self): """Event queue block timeout.""" return self._timeout @property def event_queue(self): """The event queue which is populated with file system events by emitters and from which events are dispatched by a dispatcher thread.""" return self._event_queue def dispatch_events(self, event_queue, timeout): """Override this method to consume events from an event queue, blocking on the queue for the specified timeout before raising :class:`queue.Empty`. :param event_queue: Event queue to populate with one set of events. :type event_queue: :class:`EventQueue` :param timeout: Interval period (in seconds) to wait before timing out on the event queue. :type timeout: ``float`` :raises: :class:`queue.Empty` """ def run(self): while self.should_keep_running(): try: self.dispatch_events(self.event_queue, self.timeout) except queue.Empty: continue class BaseObserver(EventDispatcher): """Base observer.""" def __init__(self, emitter_class, timeout=DEFAULT_OBSERVER_TIMEOUT): EventDispatcher.__init__(self, timeout) self._emitter_class = emitter_class self._lock = threading.RLock() self._watches = set() self._handlers = dict() self._emitters = set() self._emitter_for_watch = dict() def _add_emitter(self, emitter): self._emitter_for_watch[emitter.watch] = emitter self._emitters.add(emitter) def _remove_emitter(self, emitter): del self._emitter_for_watch[emitter.watch] self._emitters.remove(emitter) emitter.stop() try: emitter.join() except RuntimeError: pass def _clear_emitters(self): for emitter in self._emitters: emitter.stop() for emitter in self._emitters: try: emitter.join() except RuntimeError: pass self._emitters.clear() self._emitter_for_watch.clear() def _add_handler_for_watch(self, event_handler, watch): if watch not in self._handlers: self._handlers[watch] = set() self._handlers[watch].add(event_handler) def _remove_handlers_for_watch(self, watch): del self._handlers[watch] @property def emitters(self): """Returns event emitter created by this observer.""" return self._emitters def start(self): for emitter in self._emitters: emitter.start() super(BaseObserver, self).start() def schedule(self, event_handler, path, recursive=False): """ Schedules watching a path and calls appropriate methods specified in the given event handler in response to file system events. :param event_handler: An event handler instance that has appropriate event handling methods which will be called by the observer in response to file system events. :type event_handler: :class:`watchdog.events.FileSystemEventHandler` or a subclass :param path: Directory path that will be monitored. :type path: ``str`` :param recursive: ``True`` if events will be emitted for sub-directories traversed recursively; ``False`` otherwise. :type recursive: ``bool`` :return: An :class:`ObservedWatch` object instance representing a watch. """ with self._lock: watch = ObservedWatch(path, recursive) self._add_handler_for_watch(event_handler, watch) # If we don't have an emitter for this watch already, create it. if self._emitter_for_watch.get(watch) is None: emitter = self._emitter_class(event_queue=self.event_queue, watch=watch, timeout=self.timeout) self._add_emitter(emitter) if self.is_alive(): emitter.start() self._watches.add(watch) return watch def add_handler_for_watch(self, event_handler, watch): """Adds a handler for the given watch. :param event_handler: An event handler instance that has appropriate event handling methods which will be called by the observer in response to file system events. :type event_handler: :class:`watchdog.events.FileSystemEventHandler` or a subclass :param watch: The watch to add a handler for. :type watch: An instance of :class:`ObservedWatch` or a subclass of :class:`ObservedWatch` """ with self._lock: self._add_handler_for_watch(event_handler, watch) def remove_handler_for_watch(self, event_handler, watch): """Removes a handler for the given watch. :param event_handler: An event handler instance that has appropriate event handling methods which will be called by the observer in response to file system events. :type event_handler: :class:`watchdog.events.FileSystemEventHandler` or a subclass :param watch: The watch to remove a handler for. :type watch: An instance of :class:`ObservedWatch` or a subclass of :class:`ObservedWatch` """ with self._lock: self._handlers[watch].remove(event_handler) def unschedule(self, watch): """Unschedules a watch. :param watch: The watch to unschedule. :type watch: An instance of :class:`ObservedWatch` or a subclass of :class:`ObservedWatch` """ with self._lock: emitter = self._emitter_for_watch[watch] del self._handlers[watch] self._remove_emitter(emitter) self._watches.remove(watch) def unschedule_all(self): """Unschedules all watches and detaches all associated event handlers.""" with self._lock: self._handlers.clear() self._clear_emitters() self._watches.clear() def on_thread_stop(self): self.unschedule_all() def dispatch_events(self, event_queue, timeout): event, watch = event_queue.get(block=True, timeout=timeout) with self._lock: # To allow unschedule/stop and safe removal of event handlers # within event handlers itself, check if the handler is still # registered after every dispatch. for handler in list(self._handlers.get(watch, [])): if handler in self._handlers.get(watch, []): handler.dispatch(event) event_queue.task_done() watchdog-0.9.0/src/watchdog/observers/fsevents.py0000644000175000017500000001460013341122136022663 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ :module: watchdog.observers.fsevents :synopsis: FSEvents based emitter implementation. :author: yesudeep@google.com (Yesudeep Mangalapilly) :platforms: Mac OS X """ from __future__ import with_statement import sys import threading import unicodedata import _watchdog_fsevents as _fsevents from watchdog.events import ( FileDeletedEvent, FileModifiedEvent, FileCreatedEvent, FileMovedEvent, DirDeletedEvent, DirModifiedEvent, DirCreatedEvent, DirMovedEvent ) from watchdog.utils.dirsnapshot import DirectorySnapshot from watchdog.observers.api import ( BaseObserver, EventEmitter, DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT ) class FSEventsEmitter(EventEmitter): """ Mac OS X FSEvents Emitter class. :param event_queue: The event queue to fill with events. :param watch: A watch object representing the directory to monitor. :type watch: :class:`watchdog.observers.api.ObservedWatch` :param timeout: Read events blocking timeout (in seconds). :type timeout: ``float`` """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT): EventEmitter.__init__(self, event_queue, watch, timeout) self._lock = threading.Lock() self.snapshot = DirectorySnapshot(watch.path, watch.is_recursive) def on_thread_stop(self): _fsevents.remove_watch(self.watch) _fsevents.stop(self) def queue_events(self, timeout): with self._lock: if not self.watch.is_recursive\ and self.watch.path not in self.pathnames: return new_snapshot = DirectorySnapshot(self.watch.path, self.watch.is_recursive) events = new_snapshot - self.snapshot self.snapshot = new_snapshot # Files. for src_path in events.files_deleted: self.queue_event(FileDeletedEvent(src_path)) for src_path in events.files_modified: self.queue_event(FileModifiedEvent(src_path)) for src_path in events.files_created: self.queue_event(FileCreatedEvent(src_path)) for src_path, dest_path in events.files_moved: self.queue_event(FileMovedEvent(src_path, dest_path)) # Directories. for src_path in events.dirs_deleted: self.queue_event(DirDeletedEvent(src_path)) for src_path in events.dirs_modified: self.queue_event(DirModifiedEvent(src_path)) for src_path in events.dirs_created: self.queue_event(DirCreatedEvent(src_path)) for src_path, dest_path in events.dirs_moved: self.queue_event(DirMovedEvent(src_path, dest_path)) def run(self): try: def callback(pathnames, flags, emitter=self): emitter.queue_events(emitter.timeout) # for pathname, flag in zip(pathnames, flags): # if emitter.watch.is_recursive: # and pathname != emitter.watch.path: # new_sub_snapshot = DirectorySnapshot(pathname, True) # old_sub_snapshot = self.snapshot.copy(pathname) # diff = new_sub_snapshot - old_sub_snapshot # self.snapshot += new_subsnapshot # else: # new_snapshot = DirectorySnapshot(emitter.watch.path, False) # diff = new_snapshot - emitter.snapshot # emitter.snapshot = new_snapshot # INFO: FSEvents reports directory notifications recursively # by default, so we do not need to add subdirectory paths. #pathnames = set([self.watch.path]) # if self.watch.is_recursive: # for root, directory_names, _ in os.walk(self.watch.path): # for directory_name in directory_names: # full_path = absolute_path( # os.path.join(root, directory_name)) # pathnames.add(full_path) self.pathnames = [self.watch.path] _fsevents.add_watch(self, self.watch, callback, self.pathnames) _fsevents.read_events(self) except: pass class FSEventsObserver(BaseObserver): def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT): BaseObserver.__init__(self, emitter_class=FSEventsEmitter, timeout=timeout) def schedule(self, event_handler, path, recursive=False): # Python 2/3 compat try: str_class = unicode except NameError: str_class = str # Fix for issue #26: Trace/BPT error when given a unicode path # string. https://github.com/gorakhargosh/watchdog/issues#issue/26 if isinstance(path, str_class): #path = unicode(path, 'utf-8') path = unicodedata.normalize('NFC', path) # We only encode the path in Python 2 for backwards compatibility. # On Python 3 we want the path to stay as unicode if possible for # the sake of path matching not having to be rewritten to use the # bytes API instead of strings. The _watchdog_fsevent.so code for # Python 3 can handle both str and bytes paths, which is why we # do not HAVE to encode it with Python 3. The Python 2 code in # _watchdog_fsevents.so was not changed for the sake of backwards # compatibility. if sys.version_info < (3,): path = path.encode('utf-8') return BaseObserver.schedule(self, event_handler, path, recursive) watchdog-0.9.0/src/watchdog/observers/fsevents2.py0000644000175000017500000002150113341116766022757 0ustar danilodanilo00000000000000# -*- coding: utf-8 -*- # # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ :module: watchdog.observers.fsevents2 :synopsis: FSEvents based emitter implementation. :platforms: Mac OS X """ import os import logging import unicodedata from threading import Thread from watchdog.utils.compat import queue from watchdog.events import ( FileDeletedEvent, FileModifiedEvent, FileCreatedEvent, FileMovedEvent, DirDeletedEvent, DirModifiedEvent, DirCreatedEvent, DirMovedEvent ) from watchdog.observers.api import ( BaseObserver, EventEmitter, DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT, ) # pyobjc import AppKit from FSEvents import ( FSEventStreamCreate, CFRunLoopGetCurrent, FSEventStreamScheduleWithRunLoop, FSEventStreamStart, CFRunLoopRun, CFRunLoopStop, FSEventStreamStop, FSEventStreamInvalidate, FSEventStreamRelease, ) from FSEvents import ( kCFAllocatorDefault, kCFRunLoopDefaultMode, kFSEventStreamEventIdSinceNow, kFSEventStreamCreateFlagNoDefer, kFSEventStreamCreateFlagFileEvents, kFSEventStreamEventFlagItemCreated, kFSEventStreamEventFlagItemRemoved, kFSEventStreamEventFlagItemInodeMetaMod, kFSEventStreamEventFlagItemRenamed, kFSEventStreamEventFlagItemModified, kFSEventStreamEventFlagItemFinderInfoMod, kFSEventStreamEventFlagItemChangeOwner, kFSEventStreamEventFlagItemXattrMod, kFSEventStreamEventFlagItemIsDir, kFSEventStreamEventFlagItemIsSymlink, ) logger = logging.getLogger(__name__) class FSEventsQueue(Thread): """ Low level FSEvents client. """ def __init__(self, path): Thread.__init__(self) self._queue = queue.Queue() self._run_loop = None if isinstance(path, bytes): path = path.decode('utf-8') self._path = unicodedata.normalize('NFC', path) context = None latency = 1.0 self._stream_ref = FSEventStreamCreate( kCFAllocatorDefault, self._callback, context, [self._path], kFSEventStreamEventIdSinceNow, latency, kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents) if self._stream_ref is None: raise IOError("FSEvents. Could not create stream.") def run(self): pool = AppKit.NSAutoreleasePool.alloc().init() self._run_loop = CFRunLoopGetCurrent() FSEventStreamScheduleWithRunLoop( self._stream_ref, self._run_loop, kCFRunLoopDefaultMode) if not FSEventStreamStart(self._stream_ref): FSEventStreamInvalidate(self._stream_ref) FSEventStreamRelease(self._stream_ref) raise IOError("FSEvents. Could not start stream.") CFRunLoopRun() FSEventStreamStop(self._stream_ref) FSEventStreamInvalidate(self._stream_ref) FSEventStreamRelease(self._stream_ref) del pool # Make sure waiting thread is notified self._queue.put(None) def stop(self): if self._run_loop is not None: CFRunLoopStop(self._run_loop) def _callback(self, streamRef, clientCallBackInfo, numEvents, eventPaths, eventFlags, eventIDs): events = [NativeEvent(path, flags, _id) for path, flags, _id in zip(eventPaths, eventFlags, eventIDs)] logger.debug("FSEvents callback. Got %d events:" % numEvents) for e in events: logger.debug(e) self._queue.put(events) def read_events(self): """ Returns a list or one or more events, or None if there are no more events to be read. """ if not self.is_alive(): return None return self._queue.get() class NativeEvent(object): def __init__(self, path, flags, event_id): self.path = path self.flags = flags self.event_id = event_id self.is_created = bool(flags & kFSEventStreamEventFlagItemCreated) self.is_removed = bool(flags & kFSEventStreamEventFlagItemRemoved) self.is_renamed = bool(flags & kFSEventStreamEventFlagItemRenamed) self.is_modified = bool(flags & kFSEventStreamEventFlagItemModified) self.is_change_owner = bool(flags & kFSEventStreamEventFlagItemChangeOwner) self.is_inode_meta_mod = bool(flags & kFSEventStreamEventFlagItemInodeMetaMod) self.is_finder_info_mod = bool(flags & kFSEventStreamEventFlagItemFinderInfoMod) self.is_xattr_mod = bool(flags & kFSEventStreamEventFlagItemXattrMod) self.is_symlink = bool(flags & kFSEventStreamEventFlagItemIsSymlink) self.is_directory = bool(flags & kFSEventStreamEventFlagItemIsDir) @property def _event_type(self): if self.is_created: return "Created" if self.is_removed: return "Removed" if self.is_renamed: return "Renamed" if self.is_modified: return "Modified" if self.is_inode_meta_mod: return "InodeMetaMod" if self.is_xattr_mod: return "XattrMod" return "Unknown" def __repr__(self): s ="" return s % (repr(self.path), self._event_type, self.is_directory, hex(self.flags), self.event_id) class FSEventsEmitter(EventEmitter): """ FSEvents based event emitter. Handles conversion of native events. """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT): EventEmitter.__init__(self, event_queue, watch, timeout) self._fsevents = FSEventsQueue(watch.path) self._fsevents.start() def on_thread_stop(self): self._fsevents.stop() def queue_events(self, timeout): events = self._fsevents.read_events() if events is None: return i = 0 while i < len(events): event = events[i] # For some reason the create and remove flags are sometimes also # set for rename and modify type events, so let those take # precedence. if event.is_renamed: # Internal moves appears to always be consecutive in the same # buffer and have IDs differ by exactly one (while others # don't) making it possible to pair up the two events coming # from a singe move operation. (None of this is documented!) # Otherwise, guess whether file was moved in or out. #TODO: handle id wrapping if (i+1 < len(events) and events[i+1].is_renamed and events[i+1].event_id == event.event_id + 1): cls = DirMovedEvent if event.is_directory else FileMovedEvent self.queue_event(cls(event.path, events[i+1].path)) self.queue_event(DirModifiedEvent(os.path.dirname(event.path))) self.queue_event(DirModifiedEvent(os.path.dirname(events[i+1].path))) i += 1 elif os.path.exists(event.path): cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(event.path)) self.queue_event(DirModifiedEvent(os.path.dirname(event.path))) else: cls = DirDeletedEvent if event.is_directory else FileDeletedEvent self.queue_event(cls(event.path)) self.queue_event(DirModifiedEvent(os.path.dirname(event.path))) #TODO: generate events for tree elif event.is_modified or event.is_inode_meta_mod or event.is_xattr_mod : cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(event.path)) elif event.is_created: cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(event.path)) self.queue_event(DirModifiedEvent(os.path.dirname(event.path))) elif event.is_removed: cls = DirDeletedEvent if event.is_directory else FileDeletedEvent self.queue_event(cls(event.path)) self.queue_event(DirModifiedEvent(os.path.dirname(event.path))) i += 1 class FSEventsObserver2(BaseObserver): def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT): BaseObserver.__init__(self, emitter_class=FSEventsEmitter, timeout=timeout) watchdog-0.9.0/src/watchdog/observers/inotify.py0000644000175000017500000002052013341115772022515 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ :module: watchdog.observers.inotify :synopsis: ``inotify(7)`` based emitter implementation. :author: Sebastien Martini :author: Luke McCarthy :author: yesudeep@google.com (Yesudeep Mangalapilly) :author: Tim Cuthbertson :platforms: Linux 2.6.13+. .. ADMONITION:: About system requirements Recommended minimum kernel version: 2.6.25. Quote from the inotify(7) man page: "Inotify was merged into the 2.6.13 Linux kernel. The required library interfaces were added to glibc in version 2.4. (IN_DONT_FOLLOW, IN_MASK_ADD, and IN_ONLYDIR were only added in version 2.5.)" Therefore, you must ensure the system is running at least these versions appropriate libraries and the kernel. .. ADMONITION:: About recursiveness, event order, and event coalescing Quote from the inotify(7) man page: If successive output inotify events produced on the inotify file descriptor are identical (same wd, mask, cookie, and name) then they are coalesced into a single event if the older event has not yet been read (but see BUGS). The events returned by reading from an inotify file descriptor form an ordered queue. Thus, for example, it is guaranteed that when renaming from one directory to another, events will be produced in the correct order on the inotify file descriptor. ... Inotify monitoring of directories is not recursive: to monitor subdirectories under a directory, additional watches must be created. This emitter implementation therefore automatically adds watches for sub-directories if running in recursive mode. Some extremely useful articles and documentation: .. _inotify FAQ: http://inotify.aiken.cz/?section=inotify&page=faq&lang=en .. _intro to inotify: http://www.linuxjournal.com/article/8478 """ from __future__ import with_statement import os import threading from .inotify_buffer import InotifyBuffer from watchdog.observers.api import ( EventEmitter, BaseObserver, DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT ) from watchdog.events import ( DirDeletedEvent, DirModifiedEvent, DirMovedEvent, DirCreatedEvent, FileDeletedEvent, FileModifiedEvent, FileMovedEvent, FileCreatedEvent, generate_sub_moved_events, generate_sub_created_events, ) from watchdog.utils import unicode_paths class InotifyEmitter(EventEmitter): """ inotify(7)-based event emitter. :param event_queue: The event queue to fill with events. :param watch: A watch object representing the directory to monitor. :type watch: :class:`watchdog.observers.api.ObservedWatch` :param timeout: Read events blocking timeout (in seconds). :type timeout: ``float`` """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT): EventEmitter.__init__(self, event_queue, watch, timeout) self._lock = threading.Lock() self._inotify = None def on_thread_start(self): path = unicode_paths.encode(self.watch.path) self._inotify = InotifyBuffer(path, self.watch.is_recursive) def on_thread_stop(self): if self._inotify: self._inotify.close() def queue_events(self, timeout, full_events=False): #If "full_events" is true, then the method will report unmatched move events as seperate events #This behavior is by default only called by a InotifyFullEmitter with self._lock: event = self._inotify.read_event() if event is None: return if isinstance(event, tuple): move_from, move_to = event src_path = self._decode_path(move_from.src_path) dest_path = self._decode_path(move_to.src_path) cls = DirMovedEvent if move_from.is_directory else FileMovedEvent self.queue_event(cls(src_path, dest_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) self.queue_event(DirModifiedEvent(os.path.dirname(dest_path))) if move_from.is_directory and self.watch.is_recursive: for sub_event in generate_sub_moved_events(src_path, dest_path): self.queue_event(sub_event) return src_path = self._decode_path(event.src_path) if event.is_moved_to: if (full_events): cls = DirMovedEvent if event.is_directory else FileMovedEvent self.queue_event(cls(None, src_path)) else: cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) if event.is_directory and self.watch.is_recursive: for sub_event in generate_sub_created_events(src_path): self.queue_event(sub_event) elif event.is_attrib: cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(src_path)) elif event.is_modify: cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(src_path)) elif event.is_delete or (event.is_moved_from and not full_events): cls = DirDeletedEvent if event.is_directory else FileDeletedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) elif event.is_moved_from and full_events: cls = DirMovedEvent if event.is_directory else FileMovedEvent self.queue_event(cls(src_path, None)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) elif event.is_create: cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) def _decode_path(self, path): """ Decode path only if unicode string was passed to this emitter. """ if isinstance(self.watch.path, bytes): return path return unicode_paths.decode(path) class InotifyFullEmitter(InotifyEmitter): """ inotify(7)-based event emitter. By default this class produces move events even if they are not matched Such move events will have a ``None`` value for the unmatched part. :param event_queue: The event queue to fill with events. :param watch: A watch object representing the directory to monitor. :type watch: :class:`watchdog.observers.api.ObservedWatch` :param timeout: Read events blocking timeout (in seconds). :type timeout: ``float`` """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT): InotifyEmitter.__init__(self, event_queue, watch, timeout) def queue_events(self, timeout, events=True): InotifyEmitter.queue_events(self, timeout, full_events=events) class InotifyObserver(BaseObserver): """ Observer thread that schedules watching directories and dispatches calls to event handlers. """ def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT, generate_full_events=False): if (generate_full_events): BaseObserver.__init__(self, emitter_class=InotifyFullEmitter, timeout=timeout) else: BaseObserver.__init__(self, emitter_class=InotifyEmitter, timeout=timeout) watchdog-0.9.0/src/watchdog/observers/inotify_buffer.py0000644000175000017500000000573513341070440024051 0ustar danilodanilo00000000000000# -*- coding: utf-8 -*- # # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging from watchdog.utils import BaseThread from watchdog.utils.delayed_queue import DelayedQueue from watchdog.observers.inotify_c import Inotify logger = logging.getLogger(__name__) class InotifyBuffer(BaseThread): """A wrapper for `Inotify` that holds events for `delay` seconds. During this time, IN_MOVED_FROM and IN_MOVED_TO events are paired. """ delay = 0.5 def __init__(self, path, recursive=False): BaseThread.__init__(self) self._queue = DelayedQueue(self.delay) self._inotify = Inotify(path, recursive) self.start() def read_event(self): """Returns a single event or a tuple of from/to events in case of a paired move event. If this buffer has been closed, immediately return None. """ return self._queue.get() def on_thread_stop(self): self._inotify.close() self._queue.close() def close(self): self.stop() self.join() def run(self): """Read event from `inotify` and add them to `queue`. When reading a IN_MOVE_TO event, remove the previous added matching IN_MOVE_FROM event and add them back to the queue as a tuple. """ deleted_self = False while self.should_keep_running() and not deleted_self: inotify_events = self._inotify.read_events() for inotify_event in inotify_events: logger.debug("in-event %s", inotify_event) if inotify_event.is_moved_to: def matching_from_event(event): return (not isinstance(event, tuple) and event.is_moved_from and event.cookie == inotify_event.cookie) from_event = self._queue.remove(matching_from_event) if from_event is not None: self._queue.put((from_event, inotify_event)) else: logger.debug("could not find matching move_from event") self._queue.put(inotify_event) else: self._queue.put(inotify_event) if inotify_event.is_delete_self and \ inotify_event.src_path == self._inotify.path: # Deleted the watched directory, stop watching for events deleted_self = True watchdog-0.9.0/src/watchdog/observers/inotify_c.py0000644000175000017500000004472313341105741023025 0ustar danilodanilo00000000000000# -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import with_statement import os import errno import struct import threading import ctypes import ctypes.util from functools import reduce from ctypes import c_int, c_char_p, c_uint32 from watchdog.utils import has_attribute from watchdog.utils import UnsupportedLibc def _load_libc(): libc_path = None try: libc_path = ctypes.util.find_library('c') except (OSError, IOError, RuntimeError): # Note: find_library will on some platforms raise these undocumented # errors, e.g.on android IOError "No usable temporary directory found" # will be raised. pass if libc_path is not None: return ctypes.CDLL(libc_path) # Fallbacks try: return ctypes.CDLL('libc.so') except (OSError, IOError): pass try: return ctypes.CDLL('libc.so.6') except (OSError, IOError): pass # uClibc try: return ctypes.CDLL('libc.so.0') except (OSError, IOError) as err: raise err libc = _load_libc() if not has_attribute(libc, 'inotify_init') or \ not has_attribute(libc, 'inotify_add_watch') or \ not has_attribute(libc, 'inotify_rm_watch'): raise UnsupportedLibc("Unsupported libc version found: %s" % libc._name) inotify_add_watch = ctypes.CFUNCTYPE(c_int, c_int, c_char_p, c_uint32, use_errno=True)( ("inotify_add_watch", libc)) inotify_rm_watch = ctypes.CFUNCTYPE(c_int, c_int, c_uint32, use_errno=True)( ("inotify_rm_watch", libc)) inotify_init = ctypes.CFUNCTYPE(c_int, use_errno=True)( ("inotify_init", libc)) class InotifyConstants(object): # User-space events IN_ACCESS = 0x00000001 # File was accessed. IN_MODIFY = 0x00000002 # File was modified. IN_ATTRIB = 0x00000004 # Meta-data changed. IN_CLOSE_WRITE = 0x00000008 # Writable file was closed. IN_CLOSE_NOWRITE = 0x00000010 # Unwritable file closed. IN_OPEN = 0x00000020 # File was opened. IN_MOVED_FROM = 0x00000040 # File was moved from X. IN_MOVED_TO = 0x00000080 # File was moved to Y. IN_CREATE = 0x00000100 # Subfile was created. IN_DELETE = 0x00000200 # Subfile was deleted. IN_DELETE_SELF = 0x00000400 # Self was deleted. IN_MOVE_SELF = 0x00000800 # Self was moved. # Helper user-space events. IN_CLOSE = IN_CLOSE_WRITE | IN_CLOSE_NOWRITE # Close. IN_MOVE = IN_MOVED_FROM | IN_MOVED_TO # Moves. # Events sent by the kernel to a watch. IN_UNMOUNT = 0x00002000 # Backing file system was unmounted. IN_Q_OVERFLOW = 0x00004000 # Event queued overflowed. IN_IGNORED = 0x00008000 # File was ignored. # Special flags. IN_ONLYDIR = 0x01000000 # Only watch the path if it's a directory. IN_DONT_FOLLOW = 0x02000000 # Do not follow a symbolic link. IN_EXCL_UNLINK = 0x04000000 # Exclude events on unlinked objects IN_MASK_ADD = 0x20000000 # Add to the mask of an existing watch. IN_ISDIR = 0x40000000 # Event occurred against directory. IN_ONESHOT = 0x80000000 # Only send event once. # All user-space events. IN_ALL_EVENTS = reduce( lambda x, y: x | y, [ IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE, IN_CLOSE_NOWRITE, IN_OPEN, IN_MOVED_FROM, IN_MOVED_TO, IN_DELETE, IN_CREATE, IN_DELETE_SELF, IN_MOVE_SELF, ]) # Flags for ``inotify_init1`` IN_CLOEXEC = 0x02000000 IN_NONBLOCK = 0x00004000 # Watchdog's API cares only about these events. WATCHDOG_ALL_EVENTS = reduce( lambda x, y: x | y, [ InotifyConstants.IN_MODIFY, InotifyConstants.IN_ATTRIB, InotifyConstants.IN_MOVED_FROM, InotifyConstants.IN_MOVED_TO, InotifyConstants.IN_CREATE, InotifyConstants.IN_DELETE, InotifyConstants.IN_DELETE_SELF, InotifyConstants.IN_DONT_FOLLOW, ]) class inotify_event_struct(ctypes.Structure): """ Structure representation of the inotify_event structure (used in buffer size calculations):: struct inotify_event { __s32 wd; /* watch descriptor */ __u32 mask; /* watch mask */ __u32 cookie; /* cookie to synchronize two events */ __u32 len; /* length (including nulls) of name */ char name[0]; /* stub for possible name */ }; """ _fields_ = [('wd', c_int), ('mask', c_uint32), ('cookie', c_uint32), ('len', c_uint32), ('name', c_char_p)] EVENT_SIZE = ctypes.sizeof(inotify_event_struct) DEFAULT_NUM_EVENTS = 2048 DEFAULT_EVENT_BUFFER_SIZE = DEFAULT_NUM_EVENTS * (EVENT_SIZE + 16) class Inotify(object): """ Linux inotify(7) API wrapper class. :param path: The directory path for which we want an inotify object. :type path: :class:`bytes` :param recursive: ``True`` if subdirectories should be monitored; ``False`` otherwise. """ def __init__(self, path, recursive=False, event_mask=WATCHDOG_ALL_EVENTS): # The file descriptor associated with the inotify instance. inotify_fd = inotify_init() if inotify_fd == -1: Inotify._raise_error() self._inotify_fd = inotify_fd self._lock = threading.Lock() # Stores the watch descriptor for a given path. self._wd_for_path = dict() self._path_for_wd = dict() self._path = path self._event_mask = event_mask self._is_recursive = recursive self._add_dir_watch(path, recursive, event_mask) self._moved_from_events = dict() @property def event_mask(self): """The event mask for this inotify instance.""" return self._event_mask @property def path(self): """The path associated with the inotify instance.""" return self._path @property def is_recursive(self): """Whether we are watching directories recursively.""" return self._is_recursive @property def fd(self): """The file descriptor associated with the inotify instance.""" return self._inotify_fd def clear_move_records(self): """Clear cached records of MOVED_FROM events""" self._moved_from_events = dict() def source_for_move(self, destination_event): """ The source path corresponding to the given MOVED_TO event. If the source path is outside the monitored directories, None is returned instead. """ if destination_event.cookie in self._moved_from_events: return self._moved_from_events[destination_event.cookie].src_path else: return None def remember_move_from_event(self, event): """ Save this event as the source event for future MOVED_TO events to reference. """ self._moved_from_events[event.cookie] = event def add_watch(self, path): """ Adds a watch for the given path. :param path: Path to begin monitoring. """ with self._lock: self._add_watch(path, self._event_mask) def remove_watch(self, path): """ Removes a watch for the given path. :param path: Path string for which the watch will be removed. """ with self._lock: wd = self._wd_for_path.pop(path) del self._path_for_wd[wd] if inotify_rm_watch(self._inotify_fd, wd) == -1: Inotify._raise_error() def close(self): """ Closes the inotify instance and removes all associated watches. """ with self._lock: if self._path in self._wd_for_path: wd = self._wd_for_path[self._path] inotify_rm_watch(self._inotify_fd, wd) os.close(self._inotify_fd) def read_events(self, event_buffer_size=DEFAULT_EVENT_BUFFER_SIZE): """ Reads events from inotify and yields them. """ # HACK: We need to traverse the directory path # recursively and simulate events for newly # created subdirectories/files. This will handle # mkdir -p foobar/blah/bar; touch foobar/afile def _recursive_simulate(src_path): events = [] for root, dirnames, filenames in os.walk(src_path): for dirname in dirnames: try: full_path = os.path.join(root, dirname) wd_dir = self._add_watch(full_path, self._event_mask) e = InotifyEvent( wd_dir, InotifyConstants.IN_CREATE | InotifyConstants.IN_ISDIR, 0, dirname, full_path) events.append(e) except OSError: pass for filename in filenames: full_path = os.path.join(root, filename) wd_parent_dir = self._wd_for_path[os.path.dirname(full_path)] e = InotifyEvent( wd_parent_dir, InotifyConstants.IN_CREATE, 0, filename, full_path) events.append(e) return events event_buffer = None while True: try: event_buffer = os.read(self._inotify_fd, event_buffer_size) except OSError as e: if e.errno == errno.EINTR: continue break with self._lock: event_list = [] for wd, mask, cookie, name in Inotify._parse_event_buffer(event_buffer): if wd == -1: continue wd_path = self._path_for_wd[wd] src_path = os.path.join(wd_path, name) if name else wd_path #avoid trailing slash inotify_event = InotifyEvent(wd, mask, cookie, name, src_path) if inotify_event.is_moved_from: self.remember_move_from_event(inotify_event) elif inotify_event.is_moved_to: move_src_path = self.source_for_move(inotify_event) if move_src_path in self._wd_for_path: moved_wd = self._wd_for_path[move_src_path] del self._wd_for_path[move_src_path] self._wd_for_path[inotify_event.src_path] = moved_wd self._path_for_wd[moved_wd] = inotify_event.src_path src_path = os.path.join(wd_path, name) inotify_event = InotifyEvent(wd, mask, cookie, name, src_path) if inotify_event.is_ignored: # Clean up book-keeping for deleted watches. path = self._path_for_wd.pop(wd) if self._wd_for_path[path] == wd: del self._wd_for_path[path] continue event_list.append(inotify_event) if (self.is_recursive and inotify_event.is_directory and inotify_event.is_create): # TODO: When a directory from another part of the # filesystem is moved into a watched directory, this # will not generate events for the directory tree. # We need to coalesce IN_MOVED_TO events and those # IN_MOVED_TO events which don't pair up with # IN_MOVED_FROM events should be marked IN_CREATE # instead relative to this directory. try: self._add_watch(src_path, self._event_mask) except OSError: continue event_list.extend(_recursive_simulate(src_path)) return event_list # Non-synchronized methods. def _add_dir_watch(self, path, recursive, mask): """ Adds a watch (optionally recursively) for the given directory path to monitor events specified by the mask. :param path: Path to monitor :param recursive: ``True`` to monitor recursively. :param mask: Event bit mask. """ if not os.path.isdir(path): raise OSError('Path is not a directory') self._add_watch(path, mask) if recursive: for root, dirnames, _ in os.walk(path): for dirname in dirnames: full_path = os.path.join(root, dirname) if os.path.islink(full_path): continue self._add_watch(full_path, mask) def _add_watch(self, path, mask): """ Adds a watch for the given path to monitor events specified by the mask. :param path: Path to monitor :param mask: Event bit mask. """ wd = inotify_add_watch(self._inotify_fd, path, mask) if wd == -1: Inotify._raise_error() self._wd_for_path[path] = wd self._path_for_wd[wd] = path return wd @staticmethod def _raise_error(): """ Raises errors for inotify failures. """ err = ctypes.get_errno() if err == errno.ENOSPC: raise OSError("inotify watch limit reached") elif err == errno.EMFILE: raise OSError("inotify instance limit reached") else: raise OSError(os.strerror(err)) @staticmethod def _parse_event_buffer(event_buffer): """ Parses an event buffer of ``inotify_event`` structs returned by inotify:: struct inotify_event { __s32 wd; /* watch descriptor */ __u32 mask; /* watch mask */ __u32 cookie; /* cookie to synchronize two events */ __u32 len; /* length (including nulls) of name */ char name[0]; /* stub for possible name */ }; The ``cookie`` member of this struct is used to pair two related events, for example, it pairs an IN_MOVED_FROM event with an IN_MOVED_TO event. """ i = 0 while i + 16 <= len(event_buffer): wd, mask, cookie, length = struct.unpack_from('iIII', event_buffer, i) name = event_buffer[i + 16:i + 16 + length].rstrip(b'\0') i += 16 + length yield wd, mask, cookie, name class InotifyEvent(object): """ Inotify event struct wrapper. :param wd: Watch descriptor :param mask: Event mask :param cookie: Event cookie :param name: Event name. :param src_path: Event source path """ def __init__(self, wd, mask, cookie, name, src_path): self._wd = wd self._mask = mask self._cookie = cookie self._name = name self._src_path = src_path @property def src_path(self): return self._src_path @property def wd(self): return self._wd @property def mask(self): return self._mask @property def cookie(self): return self._cookie @property def name(self): return self._name @property def is_modify(self): return self._mask & InotifyConstants.IN_MODIFY > 0 @property def is_close_write(self): return self._mask & InotifyConstants.IN_CLOSE_WRITE > 0 @property def is_close_nowrite(self): return self._mask & InotifyConstants.IN_CLOSE_NOWRITE > 0 @property def is_access(self): return self._mask & InotifyConstants.IN_ACCESS > 0 @property def is_delete(self): return self._mask & InotifyConstants.IN_DELETE > 0 @property def is_delete_self(self): return self._mask & InotifyConstants.IN_DELETE_SELF > 0 @property def is_create(self): return self._mask & InotifyConstants.IN_CREATE > 0 @property def is_moved_from(self): return self._mask & InotifyConstants.IN_MOVED_FROM > 0 @property def is_moved_to(self): return self._mask & InotifyConstants.IN_MOVED_TO > 0 @property def is_move(self): return self._mask & InotifyConstants.IN_MOVE > 0 @property def is_move_self(self): return self._mask & InotifyConstants.IN_MOVE_SELF > 0 @property def is_attrib(self): return self._mask & InotifyConstants.IN_ATTRIB > 0 @property def is_ignored(self): return self._mask & InotifyConstants.IN_IGNORED > 0 @property def is_directory(self): # It looks like the kernel does not provide this information for # IN_DELETE_SELF and IN_MOVE_SELF. In this case, assume it's a dir. # See also: https://github.com/seb-m/pyinotify/blob/2c7e8f8/python2/pyinotify.py#L897 return (self.is_delete_self or self.is_move_self or self._mask & InotifyConstants.IN_ISDIR > 0) @property def key(self): return self._src_path, self._wd, self._mask, self._cookie, self._name def __eq__(self, inotify_event): return self.key == inotify_event.key def __ne__(self, inotify_event): return self.key == inotify_event.key def __hash__(self): return hash(self.key) @staticmethod def _get_mask_string(mask): masks = [] for c in dir(InotifyConstants): if c.startswith('IN_') and c not in ['IN_ALL_EVENTS', 'IN_CLOSE', 'IN_MOVE']: c_val = getattr(InotifyConstants, c) if mask & c_val: masks.append(c) mask_string = '|'.join(masks) return mask_string def __repr__(self): mask_string = self._get_mask_string(self.mask) s = "" return s % (self.src_path, self.wd, mask_string, self.cookie, self.name) watchdog-0.9.0/src/watchdog/observers/kqueue.py0000644000175000017500000006132413341070440022332 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ :module: watchdog.observers.kqueue :synopsis: ``kqueue(2)`` based emitter implementation. :author: yesudeep@google.com (Yesudeep Mangalapilly) :platforms: Mac OS X and BSD with kqueue(2). .. WARNING:: kqueue is a very heavyweight way to monitor file systems. Each kqueue-detected directory modification triggers a full directory scan. Traversing the entire directory tree and opening file descriptors for all files will create performance problems. We need to find a way to re-scan only those directories which report changes and do a diff between two sub-DirectorySnapshots perhaps. .. ADMONITION:: About ``select.kqueue`` and Python versions * Python 2.5 does not ship with ``select.kqueue`` * Python 2.6 ships with a broken ``select.kqueue`` that cannot take multiple events in the event list passed to ``kqueue.control``. * Python 2.7 ships with a working ``select.kqueue`` implementation. I have backported the Python 2.7 implementation to Python 2.5 and 2.6 in the ``select_backport`` package available on PyPI. .. ADMONITION:: About OS X performance guidelines Quote from the `Mac OS X File System Performance Guidelines`_: "When you only want to track changes on a file or directory, be sure to open it using the ``O_EVTONLY`` flag. This flag prevents the file or directory from being marked as open or in use. This is important if you are tracking files on a removable volume and the user tries to unmount the volume. With this flag in place, the system knows it can dismiss the volume. If you had opened the files or directories without this flag, the volume would be marked as busy and would not be unmounted." ``O_EVTONLY`` is defined as ``0x8000`` in the OS X header files. More information here: http://www.mlsite.net/blog/?p=2312 Classes ------- .. autoclass:: KqueueEmitter :members: :show-inheritance: Collections and Utility Classes ------------------------------- .. autoclass:: KeventDescriptor :members: :show-inheritance: .. autoclass:: KeventDescriptorSet :members: :show-inheritance: .. _Mac OS X File System Performance Guidelines: http://developer.apple.com/library/ios/#documentation/Performance/Conceptual/FileSystem/Articles/TrackingChanges.html#//apple_ref/doc/uid/20001993-CJBJFIDD """ from __future__ import with_statement from watchdog.utils import platform import threading import errno import sys import stat import os # See the notes for this module in the documentation above ^. #import select # if not has_attribute(select, 'kqueue') or sys.version_info < (2, 7, 0): if sys.version_info < (2, 7, 0): import select_backport as select else: import select from pathtools.path import absolute_path from watchdog.observers.api import ( BaseObserver, EventEmitter, DEFAULT_OBSERVER_TIMEOUT, DEFAULT_EMITTER_TIMEOUT ) from watchdog.utils.dirsnapshot import DirectorySnapshot from watchdog.events import ( DirMovedEvent, DirDeletedEvent, DirCreatedEvent, DirModifiedEvent, FileMovedEvent, FileDeletedEvent, FileCreatedEvent, FileModifiedEvent, EVENT_TYPE_MOVED, EVENT_TYPE_DELETED, EVENT_TYPE_CREATED ) # Maximum number of events to process. MAX_EVENTS = 4096 # O_EVTONLY value from the header files for OS X only. O_EVTONLY = 0x8000 # Pre-calculated values for the kevent filter, flags, and fflags attributes. if platform.is_darwin(): WATCHDOG_OS_OPEN_FLAGS = O_EVTONLY else: WATCHDOG_OS_OPEN_FLAGS = os.O_RDONLY | os.O_NONBLOCK WATCHDOG_KQ_FILTER = select.KQ_FILTER_VNODE WATCHDOG_KQ_EV_FLAGS = select.KQ_EV_ADD | select.KQ_EV_ENABLE | select.KQ_EV_CLEAR WATCHDOG_KQ_FFLAGS = ( select.KQ_NOTE_DELETE | select.KQ_NOTE_WRITE | select.KQ_NOTE_EXTEND | select.KQ_NOTE_ATTRIB | select.KQ_NOTE_LINK | select.KQ_NOTE_RENAME | select.KQ_NOTE_REVOKE ) # Flag tests. def is_deleted(kev): """Determines whether the given kevent represents deletion.""" return kev.fflags & select.KQ_NOTE_DELETE def is_modified(kev): """Determines whether the given kevent represents modification.""" fflags = kev.fflags return (fflags & select.KQ_NOTE_EXTEND) or (fflags & select.KQ_NOTE_WRITE) def is_attrib_modified(kev): """Determines whether the given kevent represents attribute modification.""" return kev.fflags & select.KQ_NOTE_ATTRIB def is_renamed(kev): """Determines whether the given kevent represents movement.""" return kev.fflags & select.KQ_NOTE_RENAME class KeventDescriptorSet(object): """ Thread-safe kevent descriptor collection. """ def __init__(self): # Set of KeventDescriptor self._descriptors = set() # Descriptor for a given path. self._descriptor_for_path = dict() # Descriptor for a given fd. self._descriptor_for_fd = dict() # List of kevent objects. self._kevents = list() self._lock = threading.Lock() @property def kevents(self): """ List of kevents monitored. """ with self._lock: return self._kevents @property def paths(self): """ List of paths for which kevents have been created. """ with self._lock: return list(self._descriptor_for_path.keys()) def get_for_fd(self, fd): """ Given a file descriptor, returns the kevent descriptor object for it. :param fd: OS file descriptor. :type fd: ``int`` :returns: A :class:`KeventDescriptor` object. """ with self._lock: return self._descriptor_for_fd[fd] def get(self, path): """ Obtains a :class:`KeventDescriptor` object for the specified path. :param path: Path for which the descriptor will be obtained. """ with self._lock: path = absolute_path(path) return self._get(path) def __contains__(self, path): """ Determines whether a :class:`KeventDescriptor has been registered for the specified path. :param path: Path for which the descriptor will be obtained. """ with self._lock: path = absolute_path(path) return self._has_path(path) def add(self, path, is_directory): """ Adds a :class:`KeventDescriptor` to the collection for the given path. :param path: The path for which a :class:`KeventDescriptor` object will be added. :param is_directory: ``True`` if the path refers to a directory; ``False`` otherwise. :type is_directory: ``bool`` """ with self._lock: path = absolute_path(path) if not self._has_path(path): self._add_descriptor(KeventDescriptor(path, is_directory)) def remove(self, path): """ Removes the :class:`KeventDescriptor` object for the given path if it already exists. :param path: Path for which the :class:`KeventDescriptor` object will be removed. """ with self._lock: path = absolute_path(path) if self._has_path(path): self._remove_descriptor(self._get(path)) def clear(self): """ Clears the collection and closes all open descriptors. """ with self._lock: for descriptor in self._descriptors: descriptor.close() self._descriptors.clear() self._descriptor_for_fd.clear() self._descriptor_for_path.clear() self._kevents = [] # Thread-unsafe methods. Locking is provided at a higher level. def _get(self, path): """Returns a kevent descriptor for a given path.""" return self._descriptor_for_path[path] def _has_path(self, path): """Determines whether a :class:`KeventDescriptor` for the specified path exists already in the collection.""" return path in self._descriptor_for_path def _add_descriptor(self, descriptor): """ Adds a descriptor to the collection. :param descriptor: An instance of :class:`KeventDescriptor` to be added. """ self._descriptors.add(descriptor) self._kevents.append(descriptor.kevent) self._descriptor_for_path[descriptor.path] = descriptor self._descriptor_for_fd[descriptor.fd] = descriptor def _remove_descriptor(self, descriptor): """ Removes a descriptor from the collection. :param descriptor: An instance of :class:`KeventDescriptor` to be removed. """ self._descriptors.remove(descriptor) del self._descriptor_for_fd[descriptor.fd] del self._descriptor_for_path[descriptor.path] self._kevents.remove(descriptor.kevent) descriptor.close() class KeventDescriptor(object): """ A kevent descriptor convenience data structure to keep together: * kevent * directory status * path * file descriptor :param path: Path string for which a kevent descriptor will be created. :param is_directory: ``True`` if the path refers to a directory; ``False`` otherwise. :type is_directory: ``bool`` """ def __init__(self, path, is_directory): self._path = absolute_path(path) self._is_directory = is_directory self._fd = os.open(path, WATCHDOG_OS_OPEN_FLAGS) self._kev = select.kevent(self._fd, filter=WATCHDOG_KQ_FILTER, flags=WATCHDOG_KQ_EV_FLAGS, fflags=WATCHDOG_KQ_FFLAGS) @property def fd(self): """OS file descriptor for the kevent descriptor.""" return self._fd @property def path(self): """The path associated with the kevent descriptor.""" return self._path @property def kevent(self): """The kevent object associated with the kevent descriptor.""" return self._kev @property def is_directory(self): """Determines whether the kevent descriptor refers to a directory. :returns: ``True`` or ``False`` """ return self._is_directory def close(self): """ Closes the file descriptor associated with a kevent descriptor. """ try: os.close(self.fd) except OSError: pass @property def key(self): return (self.path, self.is_directory) def __eq__(self, descriptor): return self.key == descriptor.key def __ne__(self, descriptor): return self.key != descriptor.key def __hash__(self): return hash(self.key) def __repr__(self): return ""\ % (self.path, self.is_directory) class KqueueEmitter(EventEmitter): """ kqueue(2)-based event emitter. .. ADMONITION:: About ``kqueue(2)`` behavior and this implementation ``kqueue(2)`` monitors file system events only for open descriptors, which means, this emitter does a lot of book-keeping behind the scenes to keep track of open descriptors for every entry in the monitored directory tree. This also means the number of maximum open file descriptors on your system must be increased **manually**. Usually, issuing a call to ``ulimit`` should suffice:: ulimit -n 1024 Ensure that you pick a number that is larger than the number of files you expect to be monitored. ``kqueue(2)`` does not provide enough information about the following things: * The destination path of a file or directory that is renamed. * Creation of a file or directory within a directory; in this case, ``kqueue(2)`` only indicates a modified event on the parent directory. Therefore, this emitter takes a snapshot of the directory tree when ``kqueue(2)`` detects a change on the file system to be able to determine the above information. :param event_queue: The event queue to fill with events. :param watch: A watch object representing the directory to monitor. :type watch: :class:`watchdog.observers.api.ObservedWatch` :param timeout: Read events blocking timeout (in seconds). :type timeout: ``float`` """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT): EventEmitter.__init__(self, event_queue, watch, timeout) self._kq = select.kqueue() self._lock = threading.RLock() # A collection of KeventDescriptor. self._descriptors = KeventDescriptorSet() def walker_callback(path, stat_info, self=self): self._register_kevent(path, stat.S_ISDIR(stat_info.st_mode)) self._snapshot = DirectorySnapshot(watch.path, watch.is_recursive, walker_callback) def _register_kevent(self, path, is_directory): """ Registers a kevent descriptor for the given path. :param path: Path for which a kevent descriptor will be created. :param is_directory: ``True`` if the path refers to a directory; ``False`` otherwise. :type is_directory: ``bool`` """ try: self._descriptors.add(path, is_directory) except OSError as e: if e.errno == errno.ENOENT: # Probably dealing with a temporary file that was created # and then quickly deleted before we could open # a descriptor for it. Therefore, simply queue a sequence # of created and deleted events for the path. #path = absolute_path(path) # if is_directory: # self.queue_event(DirCreatedEvent(path)) # self.queue_event(DirDeletedEvent(path)) # else: # self.queue_event(FileCreatedEvent(path)) # self.queue_event(FileDeletedEvent(path)) # TODO: We could simply ignore these files. # Locked files cause the python process to die with # a bus error when we handle temporary files. # eg. .git/index.lock when running tig operations. # I don't fully understand this at the moment. pass else: # All other errors are propagated. raise def _unregister_kevent(self, path): """ Convenience function to close the kevent descriptor for a specified kqueue-monitored path. :param path: Path for which the kevent descriptor will be closed. """ self._descriptors.remove(path) def queue_event(self, event): """ Handles queueing a single event object. :param event: An instance of :class:`watchdog.events.FileSystemEvent` or a subclass. """ # Handles all the book keeping for queued events. # We do not need to fire moved/deleted events for all subitems in # a directory tree here, because this function is called by kqueue # for all those events anyway. EventEmitter.queue_event(self, event) if event.event_type == EVENT_TYPE_CREATED: self._register_kevent(event.src_path, event.is_directory) elif event.event_type == EVENT_TYPE_MOVED: self._unregister_kevent(event.src_path) self._register_kevent(event.dest_path, event.is_directory) elif event.event_type == EVENT_TYPE_DELETED: self._unregister_kevent(event.src_path) def _queue_dirs_modified(self, dirs_modified, ref_snapshot, new_snapshot): """ Queues events for directory modifications by scanning the directory for changes. A scan is a comparison between two snapshots of the same directory taken at two different times. This also determines whether files or directories were created, which updated the modified timestamp for the directory. """ if dirs_modified: for dir_modified in dirs_modified: self.queue_event(DirModifiedEvent(dir_modified)) diff_events = new_snapshot - ref_snapshot for file_created in diff_events.files_created: self.queue_event(FileCreatedEvent(file_created)) for directory_created in diff_events.dirs_created: self.queue_event(DirCreatedEvent(directory_created)) def _queue_events_except_renames_and_dir_modifications(self, event_list): """ Queues events from the kevent list returned from the call to :meth:`select.kqueue.control`. .. NOTE:: Queues only the deletions, file modifications, attribute modifications. The other events, namely, file creation, directory modification, file rename, directory rename, directory creation, etc. are determined by comparing directory snapshots. """ files_renamed = set() dirs_renamed = set() dirs_modified = set() for kev in event_list: descriptor = self._descriptors.get_for_fd(kev.ident) src_path = descriptor.path if is_deleted(kev): if descriptor.is_directory: self.queue_event(DirDeletedEvent(src_path)) else: self.queue_event(FileDeletedEvent(src_path)) elif is_attrib_modified(kev): if descriptor.is_directory: self.queue_event(DirModifiedEvent(src_path)) else: self.queue_event(FileModifiedEvent(src_path)) elif is_modified(kev): if descriptor.is_directory: # When a directory is modified, it may be due to # sub-file/directory renames or new file/directory # creation. We determine all this by comparing # snapshots later. dirs_modified.add(src_path) else: self.queue_event(FileModifiedEvent(src_path)) elif is_renamed(kev): # Kqueue does not specify the destination names for renames # to, so we have to process these after taking a snapshot # of the directory. if descriptor.is_directory: dirs_renamed.add(src_path) else: files_renamed.add(src_path) return files_renamed, dirs_renamed, dirs_modified def _queue_renamed(self, src_path, is_directory, ref_snapshot, new_snapshot): """ Compares information from two directory snapshots (one taken before the rename operation and another taken right after) to determine the destination path of the file system object renamed, and adds appropriate events to the event queue. """ try: ref_stat_info = ref_snapshot.stat_info(src_path) except KeyError: # Probably caught a temporary file/directory that was renamed # and deleted. Fires a sequence of created and deleted events # for the path. if is_directory: self.queue_event(DirCreatedEvent(src_path)) self.queue_event(DirDeletedEvent(src_path)) else: self.queue_event(FileCreatedEvent(src_path)) self.queue_event(FileDeletedEvent(src_path)) # We don't process any further and bail out assuming # the event represents deletion/creation instead of movement. return try: dest_path = absolute_path( new_snapshot.path_for_inode(ref_stat_info.st_ino)) if is_directory: event = DirMovedEvent(src_path, dest_path) # TODO: Do we need to fire moved events for the items # inside the directory tree? Does kqueue does this # all by itself? Check this and then enable this code # only if it doesn't already. # A: It doesn't. So I've enabled this block. if self.watch.is_recursive: for sub_event in event.sub_moved_events(): self.queue_event(sub_event) self.queue_event(event) else: self.queue_event(FileMovedEvent(src_path, dest_path)) except KeyError: # If the new snapshot does not have an inode for the # old path, we haven't found the new name. Therefore, # we mark it as deleted and remove unregister the path. if is_directory: self.queue_event(DirDeletedEvent(src_path)) else: self.queue_event(FileDeletedEvent(src_path)) def _read_events(self, timeout=None): """ Reads events from a call to the blocking :meth:`select.kqueue.control()` method. :param timeout: Blocking timeout for reading events. :type timeout: ``float`` (seconds) """ return self._kq.control(self._descriptors.kevents, MAX_EVENTS, timeout) def queue_events(self, timeout): """ Queues events by reading them from a call to the blocking :meth:`select.kqueue.control()` method. :param timeout: Blocking timeout for reading events. :type timeout: ``float`` (seconds) """ with self._lock: try: event_list = self._read_events(timeout) files_renamed, dirs_renamed, dirs_modified = ( self._queue_events_except_renames_and_dir_modifications(event_list)) # Take a fresh snapshot of the directory and update the # saved snapshot. new_snapshot = DirectorySnapshot(self.watch.path, self.watch.is_recursive) ref_snapshot = self._snapshot self._snapshot = new_snapshot if files_renamed or dirs_renamed or dirs_modified: for src_path in files_renamed: self._queue_renamed(src_path, False, ref_snapshot, new_snapshot) for src_path in dirs_renamed: self._queue_renamed(src_path, True, ref_snapshot, new_snapshot) self._queue_dirs_modified(dirs_modified, ref_snapshot, new_snapshot) except OSError as e: if e.errno == errno.EBADF: # logging.debug(e) pass else: raise def on_thread_stop(self): # Clean up. with self._lock: self._descriptors.clear() self._kq.close() class KqueueObserver(BaseObserver): """ Observer thread that schedules watching directories and dispatches calls to event handlers. """ def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT): BaseObserver.__init__(self, emitter_class=KqueueEmitter, timeout=timeout) watchdog-0.9.0/src/watchdog/observers/polling.py0000644000175000017500000001136313341122226022475 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ :module: watchdog.observers.polling :synopsis: Polling emitter implementation. :author: yesudeep@google.com (Yesudeep Mangalapilly) Classes ------- .. autoclass:: PollingObserver :members: :show-inheritance: .. autoclass:: PollingObserverVFS :members: :show-inheritance: :special-members: """ from __future__ import with_statement import os import threading from functools import partial from watchdog.utils import stat as default_stat from watchdog.utils.dirsnapshot import DirectorySnapshot, DirectorySnapshotDiff from watchdog.observers.api import ( EventEmitter, BaseObserver, DEFAULT_OBSERVER_TIMEOUT, DEFAULT_EMITTER_TIMEOUT ) from watchdog.events import ( DirMovedEvent, DirDeletedEvent, DirCreatedEvent, DirModifiedEvent, FileMovedEvent, FileDeletedEvent, FileCreatedEvent, FileModifiedEvent ) class PollingEmitter(EventEmitter): """ Platform-independent emitter that polls a directory to detect file system changes. """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT, stat=default_stat, listdir=os.listdir): EventEmitter.__init__(self, event_queue, watch, timeout) self._snapshot = None self._lock = threading.Lock() self._take_snapshot = lambda: DirectorySnapshot( self.watch.path, self.watch.is_recursive, stat=stat, listdir=listdir) def on_thread_start(self): self._snapshot = self._take_snapshot() def queue_events(self, timeout): # We don't want to hit the disk continuously. # timeout behaves like an interval for polling emitters. if self.stopped_event.wait(timeout): return with self._lock: if not self.should_keep_running(): return # Get event diff between fresh snapshot and previous snapshot. # Update snapshot. try: new_snapshot = self._take_snapshot() except OSError: self.queue_event(DirDeletedEvent(self.watch.path)) self.stop() return events = DirectorySnapshotDiff(self._snapshot, new_snapshot) self._snapshot = new_snapshot # Files. for src_path in events.files_deleted: self.queue_event(FileDeletedEvent(src_path)) for src_path in events.files_modified: self.queue_event(FileModifiedEvent(src_path)) for src_path in events.files_created: self.queue_event(FileCreatedEvent(src_path)) for src_path, dest_path in events.files_moved: self.queue_event(FileMovedEvent(src_path, dest_path)) # Directories. for src_path in events.dirs_deleted: self.queue_event(DirDeletedEvent(src_path)) for src_path in events.dirs_modified: self.queue_event(DirModifiedEvent(src_path)) for src_path in events.dirs_created: self.queue_event(DirCreatedEvent(src_path)) for src_path, dest_path in events.dirs_moved: self.queue_event(DirMovedEvent(src_path, dest_path)) class PollingObserver(BaseObserver): """ Platform-independent observer that polls a directory to detect file system changes. """ def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT): BaseObserver.__init__(self, emitter_class=PollingEmitter, timeout=timeout) class PollingObserverVFS(BaseObserver): """ File system independent observer that polls a directory to detect changes. """ def __init__(self, stat, listdir, polling_interval=1): """ :param stat: stat function. See ``os.stat`` for details. :param listdir: listdir function. See ``os.listdir`` for details. :type polling_interval: float :param polling_interval: interval in seconds between polling the file system. """ emitter_cls = partial(PollingEmitter, stat=stat, listdir=listdir) BaseObserver.__init__(self, emitter_class=emitter_cls, timeout=polling_interval) watchdog-0.9.0/src/watchdog/observers/read_directory_changes.py0000644000175000017500000001217413341121057025522 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import with_statement import threading import os.path import time from watchdog.events import ( DirCreatedEvent, DirMovedEvent, DirModifiedEvent, FileCreatedEvent, FileDeletedEvent, FileMovedEvent, FileModifiedEvent, generate_sub_moved_events, generate_sub_created_events, ) from watchdog.observers.api import ( EventEmitter, BaseObserver, DEFAULT_OBSERVER_TIMEOUT, DEFAULT_EMITTER_TIMEOUT ) from watchdog.observers.winapi import ( read_events, get_directory_handle, close_directory_handle, ) # HACK: WATCHDOG_TRAVERSE_MOVED_DIR_DELAY = 1 # seconds class WindowsApiEmitter(EventEmitter): """ Windows API-based emitter that uses ReadDirectoryChangesW to detect file system changes for a watch. """ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT): EventEmitter.__init__(self, event_queue, watch, timeout) self._lock = threading.Lock() self._handle = None def on_thread_start(self): self._handle = get_directory_handle(self.watch.path) def on_thread_stop(self): if self._handle: close_directory_handle(self._handle) def queue_events(self, timeout): winapi_events = read_events(self._handle, self.watch.is_recursive) with self._lock: last_renamed_src_path = "" for winapi_event in winapi_events: src_path = os.path.join(self.watch.path, winapi_event.src_path) if winapi_event.is_renamed_old: last_renamed_src_path = src_path elif winapi_event.is_renamed_new: dest_path = src_path src_path = last_renamed_src_path if os.path.isdir(dest_path): event = DirMovedEvent(src_path, dest_path) if self.watch.is_recursive: # HACK: We introduce a forced delay before # traversing the moved directory. This will read # only file movement that finishes within this # delay time. time.sleep(WATCHDOG_TRAVERSE_MOVED_DIR_DELAY) # The following block of code may not # obtain moved events for the entire tree if # the I/O is not completed within the above # delay time. So, it's not guaranteed to work. # TODO: Come up with a better solution, possibly # a way to wait for I/O to complete before # queuing events. for sub_moved_event in generate_sub_moved_events(src_path, dest_path): self.queue_event(sub_moved_event) self.queue_event(event) else: self.queue_event(FileMovedEvent(src_path, dest_path)) elif winapi_event.is_modified: cls = DirModifiedEvent if os.path.isdir(src_path) else FileModifiedEvent self.queue_event(cls(src_path)) elif winapi_event.is_added: isdir = os.path.isdir(src_path) cls = DirCreatedEvent if isdir else FileCreatedEvent self.queue_event(cls(src_path)) if isdir: # If a directory is moved from outside the watched folder to inside it # we only get a created directory event out of it, not any events for its children # so use the same hack as for file moves to get the child events time.sleep(WATCHDOG_TRAVERSE_MOVED_DIR_DELAY) sub_events = generate_sub_created_events(src_path) for sub_created_event in sub_events: self.queue_event(sub_created_event) elif winapi_event.is_removed: self.queue_event(FileDeletedEvent(src_path)) class WindowsApiObserver(BaseObserver): """ Observer thread that schedules watching directories and dispatches calls to event handlers. """ def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT): BaseObserver.__init__(self, emitter_class=WindowsApiEmitter, timeout=timeout) watchdog-0.9.0/src/watchdog/observers/winapi.py0000644000175000017500000002665513341121061022326 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # winapi.py: Windows API-Python interface (removes dependency on pywin32) # # Copyright (C) 2007 Thomas Heller # Copyright (C) 2010 Will McGugan # Copyright (C) 2010 Ryan Kelly # Copyright (C) 2010 Yesudeep Mangalapilly # Copyright (C) 2014 Thomas Amland # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and / or other materials provided with the distribution. # * Neither the name of the organization nor the names of its contributors may # be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # Portions of this code were taken from pyfilesystem, which uses the above # new BSD license. from __future__ import with_statement import ctypes.wintypes from functools import reduce try: LPVOID = ctypes.wintypes.LPVOID except AttributeError: # LPVOID wasn't defined in Py2.5, guess it was introduced in Py2.6 LPVOID = ctypes.c_void_p # Invalid handle value. INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value # File notification contants. FILE_NOTIFY_CHANGE_FILE_NAME = 0x01 FILE_NOTIFY_CHANGE_DIR_NAME = 0x02 FILE_NOTIFY_CHANGE_ATTRIBUTES = 0x04 FILE_NOTIFY_CHANGE_SIZE = 0x08 FILE_NOTIFY_CHANGE_LAST_WRITE = 0x010 FILE_NOTIFY_CHANGE_LAST_ACCESS = 0x020 FILE_NOTIFY_CHANGE_CREATION = 0x040 FILE_NOTIFY_CHANGE_SECURITY = 0x0100 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 FILE_FLAG_OVERLAPPED = 0x40000000 FILE_LIST_DIRECTORY = 1 FILE_SHARE_READ = 0x01 FILE_SHARE_WRITE = 0x02 FILE_SHARE_DELETE = 0x04 OPEN_EXISTING = 3 # File action constants. FILE_ACTION_CREATED = 1 FILE_ACTION_DELETED = 2 FILE_ACTION_MODIFIED = 3 FILE_ACTION_RENAMED_OLD_NAME = 4 FILE_ACTION_RENAMED_NEW_NAME = 5 FILE_ACTION_OVERFLOW = 0xFFFF # Aliases FILE_ACTION_ADDED = FILE_ACTION_CREATED FILE_ACTION_REMOVED = FILE_ACTION_DELETED THREAD_TERMINATE = 0x0001 # IO waiting constants. WAIT_ABANDONED = 0x00000080 WAIT_IO_COMPLETION = 0x000000C0 WAIT_OBJECT_0 = 0x00000000 WAIT_TIMEOUT = 0x00000102 # Error codes ERROR_OPERATION_ABORTED = 995 class OVERLAPPED(ctypes.Structure): _fields_ = [('Internal', LPVOID), ('InternalHigh', LPVOID), ('Offset', ctypes.wintypes.DWORD), ('OffsetHigh', ctypes.wintypes.DWORD), ('Pointer', LPVOID), ('hEvent', ctypes.wintypes.HANDLE), ] def _errcheck_bool(value, func, args): if not value: raise ctypes.WinError() return args def _errcheck_handle(value, func, args): if not value: raise ctypes.WinError() if value == INVALID_HANDLE_VALUE: raise ctypes.WinError() return args def _errcheck_dword(value, func, args): if value == 0xFFFFFFFF: raise ctypes.WinError() return args ReadDirectoryChangesW = ctypes.windll.kernel32.ReadDirectoryChangesW ReadDirectoryChangesW.restype = ctypes.wintypes.BOOL ReadDirectoryChangesW.errcheck = _errcheck_bool ReadDirectoryChangesW.argtypes = ( ctypes.wintypes.HANDLE, # hDirectory LPVOID, # lpBuffer ctypes.wintypes.DWORD, # nBufferLength ctypes.wintypes.BOOL, # bWatchSubtree ctypes.wintypes.DWORD, # dwNotifyFilter ctypes.POINTER(ctypes.wintypes.DWORD), # lpBytesReturned ctypes.POINTER(OVERLAPPED), # lpOverlapped LPVOID # FileIOCompletionRoutine # lpCompletionRoutine ) CreateFileW = ctypes.windll.kernel32.CreateFileW CreateFileW.restype = ctypes.wintypes.HANDLE CreateFileW.errcheck = _errcheck_handle CreateFileW.argtypes = ( ctypes.wintypes.LPCWSTR, # lpFileName ctypes.wintypes.DWORD, # dwDesiredAccess ctypes.wintypes.DWORD, # dwShareMode LPVOID, # lpSecurityAttributes ctypes.wintypes.DWORD, # dwCreationDisposition ctypes.wintypes.DWORD, # dwFlagsAndAttributes ctypes.wintypes.HANDLE # hTemplateFile ) CloseHandle = ctypes.windll.kernel32.CloseHandle CloseHandle.restype = ctypes.wintypes.BOOL CloseHandle.argtypes = ( ctypes.wintypes.HANDLE, # hObject ) CancelIoEx = ctypes.windll.kernel32.CancelIoEx CancelIoEx.restype = ctypes.wintypes.BOOL CancelIoEx.errcheck = _errcheck_bool CancelIoEx.argtypes = ( ctypes.wintypes.HANDLE, # hObject ctypes.POINTER(OVERLAPPED) # lpOverlapped ) CreateEvent = ctypes.windll.kernel32.CreateEventW CreateEvent.restype = ctypes.wintypes.HANDLE CreateEvent.errcheck = _errcheck_handle CreateEvent.argtypes = ( LPVOID, # lpEventAttributes ctypes.wintypes.BOOL, # bManualReset ctypes.wintypes.BOOL, # bInitialState ctypes.wintypes.LPCWSTR, # lpName ) SetEvent = ctypes.windll.kernel32.SetEvent SetEvent.restype = ctypes.wintypes.BOOL SetEvent.errcheck = _errcheck_bool SetEvent.argtypes = ( ctypes.wintypes.HANDLE, # hEvent ) WaitForSingleObjectEx = ctypes.windll.kernel32.WaitForSingleObjectEx WaitForSingleObjectEx.restype = ctypes.wintypes.DWORD WaitForSingleObjectEx.errcheck = _errcheck_dword WaitForSingleObjectEx.argtypes = ( ctypes.wintypes.HANDLE, # hObject ctypes.wintypes.DWORD, # dwMilliseconds ctypes.wintypes.BOOL, # bAlertable ) CreateIoCompletionPort = ctypes.windll.kernel32.CreateIoCompletionPort CreateIoCompletionPort.restype = ctypes.wintypes.HANDLE CreateIoCompletionPort.errcheck = _errcheck_handle CreateIoCompletionPort.argtypes = ( ctypes.wintypes.HANDLE, # FileHandle ctypes.wintypes.HANDLE, # ExistingCompletionPort LPVOID, # CompletionKey ctypes.wintypes.DWORD, # NumberOfConcurrentThreads ) GetQueuedCompletionStatus = ctypes.windll.kernel32.GetQueuedCompletionStatus GetQueuedCompletionStatus.restype = ctypes.wintypes.BOOL GetQueuedCompletionStatus.errcheck = _errcheck_bool GetQueuedCompletionStatus.argtypes = ( ctypes.wintypes.HANDLE, # CompletionPort LPVOID, # lpNumberOfBytesTransferred LPVOID, # lpCompletionKey ctypes.POINTER(OVERLAPPED), # lpOverlapped ctypes.wintypes.DWORD, # dwMilliseconds ) PostQueuedCompletionStatus = ctypes.windll.kernel32.PostQueuedCompletionStatus PostQueuedCompletionStatus.restype = ctypes.wintypes.BOOL PostQueuedCompletionStatus.errcheck = _errcheck_bool PostQueuedCompletionStatus.argtypes = ( ctypes.wintypes.HANDLE, # CompletionPort ctypes.wintypes.DWORD, # lpNumberOfBytesTransferred ctypes.wintypes.DWORD, # lpCompletionKey ctypes.POINTER(OVERLAPPED), # lpOverlapped ) class FILE_NOTIFY_INFORMATION(ctypes.Structure): _fields_ = [("NextEntryOffset", ctypes.wintypes.DWORD), ("Action", ctypes.wintypes.DWORD), ("FileNameLength", ctypes.wintypes.DWORD), #("FileName", (ctypes.wintypes.WCHAR * 1))] ("FileName", (ctypes.c_char * 1))] LPFNI = ctypes.POINTER(FILE_NOTIFY_INFORMATION) # We don't need to recalculate these flags every time a call is made to # the win32 API functions. WATCHDOG_FILE_FLAGS = FILE_FLAG_BACKUP_SEMANTICS WATCHDOG_FILE_SHARE_FLAGS = reduce( lambda x, y: x | y, [ FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_DELETE, ]) WATCHDOG_FILE_NOTIFY_FLAGS = reduce( lambda x, y: x | y, [ FILE_NOTIFY_CHANGE_FILE_NAME, FILE_NOTIFY_CHANGE_DIR_NAME, FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_NOTIFY_CHANGE_SIZE, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_NOTIFY_CHANGE_SECURITY, FILE_NOTIFY_CHANGE_LAST_ACCESS, FILE_NOTIFY_CHANGE_CREATION, ]) BUFFER_SIZE = 2048 def _parse_event_buffer(readBuffer, nBytes): results = [] while nBytes > 0: fni = ctypes.cast(readBuffer, LPFNI)[0] ptr = ctypes.addressof(fni) + FILE_NOTIFY_INFORMATION.FileName.offset #filename = ctypes.wstring_at(ptr, fni.FileNameLength) filename = ctypes.string_at(ptr, fni.FileNameLength) results.append((fni.Action, filename.decode('utf-16'))) numToSkip = fni.NextEntryOffset if numToSkip <= 0: break readBuffer = readBuffer[numToSkip:] nBytes -= numToSkip # numToSkip is long. nBytes should be long too. return results def get_directory_handle(path): """Returns a Windows handle to the specified directory path.""" return CreateFileW(path, FILE_LIST_DIRECTORY, WATCHDOG_FILE_SHARE_FLAGS, None, OPEN_EXISTING, WATCHDOG_FILE_FLAGS, None) def close_directory_handle(handle): try: CancelIoEx(handle, None) # force ReadDirectoryChangesW to return CloseHandle(handle) # close directory handle except WindowsError: try: CloseHandle(handle) # close directory handle except: return def read_directory_changes(handle, recursive): """Read changes to the directory using the specified directory handle. http://timgolden.me.uk/pywin32-docs/win32file__ReadDirectoryChangesW_meth.html """ event_buffer = ctypes.create_string_buffer(BUFFER_SIZE) nbytes = ctypes.wintypes.DWORD() try: ReadDirectoryChangesW(handle, ctypes.byref(event_buffer), len(event_buffer), recursive, WATCHDOG_FILE_NOTIFY_FLAGS, ctypes.byref(nbytes), None, None) except WindowsError as e: if e.winerror == ERROR_OPERATION_ABORTED: return [], 0 raise e # Python 2/3 compat try: int_class = long except NameError: int_class = int return event_buffer.raw, int_class(nbytes.value) class WinAPINativeEvent(object): def __init__(self, action, src_path): self.action = action self.src_path = src_path @property def is_added(self): return self.action == FILE_ACTION_CREATED @property def is_removed(self): return self.action == FILE_ACTION_REMOVED @property def is_modified(self): return self.action == FILE_ACTION_MODIFIED @property def is_renamed_old(self): return self.action == FILE_ACTION_RENAMED_OLD_NAME @property def is_renamed_new(self): return self.action == FILE_ACTION_RENAMED_NEW_NAME def __repr__(self): return ("" % (self.action, self.src_path)) def read_events(handle, recursive): buf, nbytes = read_directory_changes(handle, recursive) events = _parse_event_buffer(buf, nbytes) return [WinAPINativeEvent(action, path) for action, path in events] watchdog-0.9.0/src/watchdog/tricks/0000755000175000017500000000000013341331653017746 5ustar danilodanilo00000000000000watchdog-0.9.0/src/watchdog/tricks/__init__.py0000644000175000017500000001210213341070440022045 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import signal import subprocess import time from watchdog.utils import echo, has_attribute from watchdog.events import PatternMatchingEventHandler class Trick(PatternMatchingEventHandler): """Your tricks should subclass this class.""" @classmethod def generate_yaml(cls): context = dict(module_name=cls.__module__, klass_name=cls.__name__) template_yaml = """- %(module_name)s.%(klass_name)s: args: - argument1 - argument2 kwargs: patterns: - "*.py" - "*.js" ignore_patterns: - "version.py" ignore_directories: false """ return template_yaml % context class LoggerTrick(Trick): """A simple trick that does only logs events.""" def on_any_event(self, event): pass @echo.echo def on_modified(self, event): pass @echo.echo def on_deleted(self, event): pass @echo.echo def on_created(self, event): pass @echo.echo def on_moved(self, event): pass class ShellCommandTrick(Trick): """Executes shell commands in response to matched events.""" def __init__(self, shell_command=None, patterns=None, ignore_patterns=None, ignore_directories=False, wait_for_process=False, drop_during_process=False): super(ShellCommandTrick, self).__init__(patterns, ignore_patterns, ignore_directories) self.shell_command = shell_command self.wait_for_process = wait_for_process self.drop_during_process = drop_during_process self.process = None def on_any_event(self, event): from string import Template if self.drop_during_process and self.process and self.process.poll() is None: return if event.is_directory: object_type = 'directory' else: object_type = 'file' context = { 'watch_src_path': event.src_path, 'watch_dest_path': '', 'watch_event_type': event.event_type, 'watch_object': object_type, } if self.shell_command is None: if has_attribute(event, 'dest_path'): context.update({'dest_path': event.dest_path}) command = 'echo "${watch_event_type} ${watch_object} from ${watch_src_path} to ${watch_dest_path}"' else: command = 'echo "${watch_event_type} ${watch_object} ${watch_src_path}"' else: if has_attribute(event, 'dest_path'): context.update({'watch_dest_path': event.dest_path}) command = self.shell_command command = Template(command).safe_substitute(**context) self.process = subprocess.Popen(command, shell=True) if self.wait_for_process: self.process.wait() class AutoRestartTrick(Trick): """Starts a long-running subprocess and restarts it on matched events. The command parameter is a list of command arguments, such as ['bin/myserver', '-c', 'etc/myconfig.ini']. Call start() after creating the Trick. Call stop() when stopping the process. """ def __init__(self, command, patterns=None, ignore_patterns=None, ignore_directories=False, stop_signal=signal.SIGINT, kill_after=10): super(AutoRestartTrick, self).__init__( patterns, ignore_patterns, ignore_directories) self.command = command self.stop_signal = stop_signal self.kill_after = kill_after self.process = None def start(self): self.process = subprocess.Popen(self.command, preexec_fn=os.setsid) def stop(self): if self.process is None: return try: os.killpg(os.getpgid(self.process.pid), self.stop_signal) except OSError: # Process is already gone pass else: kill_time = time.time() + self.kill_after while time.time() < kill_time: if self.process.poll() is not None: break time.sleep(0.25) else: try: os.killpg(os.getpgid(self.process.pid), 9) except OSError: # Process is already gone pass self.process = None @echo.echo def on_any_event(self, event): self.stop() self.start() watchdog-0.9.0/src/watchdog/utils/0000755000175000017500000000000013341331653017607 5ustar danilodanilo00000000000000watchdog-0.9.0/src/watchdog/utils/__init__.py0000644000175000017500000001111213341105533021710 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ :module: watchdog.utils :synopsis: Utility classes and functions. :author: yesudeep@google.com (Yesudeep Mangalapilly) Classes ------- .. autoclass:: BaseThread :members: :show-inheritance: :inherited-members: """ import os import sys import threading from watchdog.utils import platform from watchdog.utils.compat import Event if sys.version_info[0] == 2 and platform.is_windows(): # st_ino is not implemented in os.stat on this platform import win32stat stat = win32stat.stat else: stat = os.stat def has_attribute(ob, attribute): """ :func:`hasattr` swallows exceptions. :func:`has_attribute` tests a Python object for the presence of an attribute. :param ob: object to inspect :param attribute: ``str`` for the name of the attribute. """ return getattr(ob, attribute, None) is not None class UnsupportedLibc(Exception): pass class BaseThread(threading.Thread): """ Convenience class for creating stoppable threads. """ def __init__(self): threading.Thread.__init__(self) if has_attribute(self, 'daemon'): self.daemon = True else: self.setDaemon(True) self._stopped_event = Event() if not has_attribute(self._stopped_event, 'is_set'): self._stopped_event.is_set = self._stopped_event.isSet @property def stopped_event(self): return self._stopped_event def should_keep_running(self): """Determines whether the thread should continue running.""" return not self._stopped_event.is_set() def on_thread_stop(self): """Override this method instead of :meth:`stop()`. :meth:`stop()` calls this method. This method is called immediately after the thread is signaled to stop. """ pass def stop(self): """Signals the thread to stop.""" self._stopped_event.set() self.on_thread_stop() def on_thread_start(self): """Override this method instead of :meth:`start()`. :meth:`start()` calls this method. This method is called right before this thread is started and this object’s run() method is invoked. """ pass def start(self): self.on_thread_start() threading.Thread.start(self) def load_module(module_name): """Imports a module given its name and returns a handle to it.""" try: __import__(module_name) except ImportError: raise ImportError('No module named %s' % module_name) return sys.modules[module_name] def load_class(dotted_path): """Loads and returns a class definition provided a dotted path specification the last part of the dotted path is the class name and there is at least one module name preceding the class name. Notes: You will need to ensure that the module you are trying to load exists in the Python path. Examples: - module.name.ClassName # Provided module.name is in the Python path. - module.ClassName # Provided module is in the Python path. What won't work: - ClassName - modle.name.ClassName # Typo in module name. - module.name.ClasNam # Typo in classname. """ dotted_path_split = dotted_path.split('.') if len(dotted_path_split) > 1: klass_name = dotted_path_split[-1] module_name = '.'.join(dotted_path_split[:-1]) module = load_module(module_name) if has_attribute(module, klass_name): klass = getattr(module, klass_name) return klass # Finally create and return an instance of the class # return klass(*args, **kwargs) else: raise AttributeError('Module %s does not have class attribute %s' % ( module_name, klass_name)) else: raise ValueError( 'Dotted module path %s must contain a module name and a classname' % dotted_path) watchdog-0.9.0/src/watchdog/utils/bricks.py0000644000175000017500000001656413341070440021444 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Utility collections or "bricks". :module: watchdog.utils.bricks :author: yesudeep@google.com (Yesudeep Mangalapilly) :author: lalinsky@gmail.com (Lukáš Lalinský) :author: python@rcn.com (Raymond Hettinger) Classes ======= .. autoclass:: OrderedSetQueue :members: :show-inheritance: :inherited-members: .. autoclass:: OrderedSet """ import sys import collections from .compat import queue class SkipRepeatsQueue(queue.Queue): """Thread-safe implementation of an special queue where a put of the last-item put'd will be dropped. The implementation leverages locking already implemented in the base class redefining only the primitives. Queued items must be immutable and hashable so that they can be used as dictionary keys. You must implement **only read-only properties** and the :meth:`Item.__hash__()`, :meth:`Item.__eq__()`, and :meth:`Item.__ne__()` methods for items to be hashable. An example implementation follows:: class Item(object): def __init__(self, a, b): self._a = a self._b = b @property def a(self): return self._a @property def b(self): return self._b def _key(self): return (self._a, self._b) def __eq__(self, item): return self._key() == item._key() def __ne__(self, item): return self._key() != item._key() def __hash__(self): return hash(self._key()) based on the OrderedSetQueue below """ def _init(self, maxsize): queue.Queue._init(self, maxsize) self._last_item = None def _put(self, item): if item != self._last_item: queue.Queue._put(self, item) self._last_item = item else: # `put` increments `unfinished_tasks` even if we did not put # anything into the queue here self.unfinished_tasks -= 1 def _get(self): item = queue.Queue._get(self) if item is self._last_item: self._last_item = None return item class OrderedSetQueue(queue.Queue): """Thread-safe implementation of an ordered set queue. Disallows adding a duplicate item while maintaining the order of items in the queue. The implementation leverages locking already implemented in the base class redefining only the primitives. Since the internal queue is not replaced, the order is maintained. The set is used merely to check for the existence of an item. Queued items must be immutable and hashable so that they can be used as dictionary keys. You must implement **only read-only properties** and the :meth:`Item.__hash__()`, :meth:`Item.__eq__()`, and :meth:`Item.__ne__()` methods for items to be hashable. An example implementation follows:: class Item(object): def __init__(self, a, b): self._a = a self._b = b @property def a(self): return self._a @property def b(self): return self._b def _key(self): return (self._a, self._b) def __eq__(self, item): return self._key() == item._key() def __ne__(self, item): return self._key() != item._key() def __hash__(self): return hash(self._key()) :author: lalinsky@gmail.com (Lukáš Lalinský) :url: http://stackoverflow.com/questions/1581895/how-check-if-a-task-is-already-in-python-queue """ def _init(self, maxsize): queue.Queue._init(self, maxsize) self._set_of_items = set() def _put(self, item): if item not in self._set_of_items: queue.Queue._put(self, item) self._set_of_items.add(item) else: # `put` increments `unfinished_tasks` even if we did not put # anything into the queue here self.unfinished_tasks -= 1 def _get(self): item = queue.Queue._get(self) self._set_of_items.remove(item) return item if sys.version_info >= (2, 6, 0): KEY, PREV, NEXT = list(range(3)) class OrderedSet(collections.MutableSet): """ Implementation based on a doubly-linked link and an internal dictionary. This design gives :class:`OrderedSet` the same big-Oh running times as regular sets including O(1) adds, removes, and lookups as well as O(n) iteration. .. ADMONITION:: Implementation notes Runs on Python 2.6 or later (and runs on Python 3.0 or later without any modifications). :author: python@rcn.com (Raymond Hettinger) :url: http://code.activestate.com/recipes/576694/ """ def __init__(self, iterable=None): self.end = end = [] end += [None, end, end] # sentinel node for doubly linked list self.map = {} # key --> [key, prev, next] if iterable is not None: self |= iterable def __len__(self): return len(self.map) def __contains__(self, key): return key in self.map def add(self, key): if key not in self.map: end = self.end curr = end[PREV] curr[NEXT] = end[PREV] = self.map[key] = [key, curr, end] def discard(self, key): if key in self.map: key, prev, _next = self.map.pop(key) prev[NEXT] = _next _next[PREV] = prev def __iter__(self): end = self.end curr = end[NEXT] while curr is not end: yield curr[KEY] curr = curr[NEXT] def __reversed__(self): end = self.end curr = end[PREV] while curr is not end: yield curr[KEY] curr = curr[PREV] def pop(self, last=True): if not self: raise KeyError('set is empty') key = next(reversed(self)) if last else next(iter(self)) self.discard(key) return key def __repr__(self): if not self: return '%s()' % (self.__class__.__name__,) return '%s(%r)' % (self.__class__.__name__, list(self)) def __eq__(self, other): if isinstance(other, OrderedSet): return len(self) == len(other) and list(self) == list(other) return set(self) == set(other) def __del__(self): self.clear() # remove circular references watchdog-0.9.0/src/watchdog/utils/compat.py0000644000175000017500000000153413341070440021441 0ustar danilodanilo00000000000000# -*- coding: utf-8 -*- # # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys __all__ = ['queue', 'Event'] try: import queue except ImportError: import Queue as queue if sys.version_info < (2, 7): from watchdog.utils.event_backport import Event else: from threading import Eventwatchdog-0.9.0/src/watchdog/utils/decorators.py0000644000175000017500000001054313341070440022323 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Most of this code was obtained from the Python documentation online. """Decorator utility functions. decorators: - synchronized - propertyx - accepts - returns - singleton - attrs - deprecated """ import functools import warnings import threading import sys def synchronized(lock=None): """Decorator that synchronizes a method or a function with a mutex lock. Example usage: @synchronized() def operation(self, a, b): ... """ if lock is None: lock = threading.Lock() def wrapper(function): def new_function(*args, **kwargs): lock.acquire() try: return function(*args, **kwargs) finally: lock.release() return new_function return wrapper def propertyx(function): """Decorator to easily create properties in classes. Example: class Angle(object): def __init__(self, rad): self._rad = rad @property def rad(): def fget(self): return self._rad def fset(self, angle): if isinstance(angle, Angle): angle = angle.rad self._rad = float(angle) Arguments: - `function`: The function to be decorated. """ keys = ('fget', 'fset', 'fdel') func_locals = {'doc': function.__doc__} def probe_func(frame, event, arg): if event == 'return': locals = frame.f_locals func_locals.update(dict((k, locals.get(k)) for k in keys)) sys.settrace(None) return probe_func sys.settrace(probe_func) function() return property(**func_locals) def accepts(*types): """Decorator to ensure that the decorated function accepts the given types as arguments. Example: @accepts(int, (int,float)) @returns((int,float)) def func(arg1, arg2): return arg1 * arg2 """ def check_accepts(f): assert len(types) == f.__code__.co_argcount def new_f(*args, **kwds): for (a, t) in zip(args, types): assert isinstance(a, t),\ "arg %r does not match %s" % (a, t) return f(*args, **kwds) new_f.__name__ = f.__name__ return new_f return check_accepts def returns(rtype): """Decorator to ensure that the decorated function returns the given type as argument. Example: @accepts(int, (int,float)) @returns((int,float)) def func(arg1, arg2): return arg1 * arg2 """ def check_returns(f): def new_f(*args, **kwds): result = f(*args, **kwds) assert isinstance(result, rtype),\ "return value %r does not match %s" % (result, rtype) return result new_f.__name__ = f.__name__ return new_f return check_returns def singleton(cls): """Decorator to ensures a class follows the singleton pattern. Example: @singleton class MyClass: ... """ instances = {} def getinstance(): if cls not in instances: instances[cls] = cls() return instances[cls] return getinstance def attrs(**kwds): """Decorator to add attributes to a function. Example: @attrs(versionadded="2.2", author="Guido van Rossum") def mymethod(f): ... """ def decorate(f): for k in kwds: setattr(f, k, kwds[k]) return f return decorate def deprecated(func): """This is a decorator which can be used to mark functions as deprecated. It will result in a warning being emitted when the function is used. ## Usage examples ## @deprecated def my_func(): pass @other_decorators_must_be_upper @deprecated def my_func(): pass """ @functools.wraps(func) def new_func(*args, **kwargs): warnings.warn_explicit( "Call to deprecated function %(funcname)s." % { 'funcname': func.__name__, }, category=DeprecationWarning, filename=func.__code__.co_filename, lineno=func.__code__.co_firstlineno + 1 ) return func(*args, **kwargs) return new_func watchdog-0.9.0/src/watchdog/utils/delayed_queue.py0000644000175000017500000000555613341070440023001 0ustar danilodanilo00000000000000# -*- coding: utf-8 -*- # # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import time import threading from collections import deque class DelayedQueue(object): def __init__(self, delay): self.delay = delay self._lock = threading.Lock() self._not_empty = threading.Condition(self._lock) self._queue = deque() self._closed = False def put(self, element): """Add element to queue.""" self._lock.acquire() self._queue.append((element, time.time())) self._not_empty.notify() self._lock.release() def close(self): """Close queue, indicating no more items will be added.""" self._closed = True # Interrupt the blocking _not_empty.wait() call in get self._not_empty.acquire() self._not_empty.notify() self._not_empty.release() def get(self): """Remove and return an element from the queue, or this queue has been closed raise the Closed exception. """ while True: # wait for element to be added to queue self._not_empty.acquire() while len(self._queue) == 0 and not self._closed: self._not_empty.wait() if self._closed: self._not_empty.release() return None head, insert_time = self._queue[0] self._not_empty.release() # wait for delay time_left = insert_time + self.delay - time.time() while time_left > 0: time.sleep(time_left) time_left = insert_time + self.delay - time.time() # return element if it's still in the queue self._lock.acquire() try: if len(self._queue) > 0 and self._queue[0][0] is head: self._queue.popleft() return head finally: self._lock.release() def remove(self, predicate): """Remove and return the first items for which predicate is True, ignoring delay.""" try: self._lock.acquire() for i, (elem, t) in enumerate(self._queue): if predicate(elem): del self._queue[i] return elem finally: self._lock.release() return None watchdog-0.9.0/src/watchdog/utils/dirsnapshot.py0000644000175000017500000002214113341116002022504 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ :module: watchdog.utils.dirsnapshot :synopsis: Directory snapshots and comparison. :author: yesudeep@google.com (Yesudeep Mangalapilly) .. ADMONITION:: Where are the moved events? They "disappeared" This implementation does not take partition boundaries into consideration. It will only work when the directory tree is entirely on the same file system. More specifically, any part of the code that depends on inode numbers can break if partition boundaries are crossed. In these cases, the snapshot diff will represent file/directory movement as created and deleted events. Classes ------- .. autoclass:: DirectorySnapshot :members: :show-inheritance: .. autoclass:: DirectorySnapshotDiff :members: :show-inheritance: """ import errno import os from stat import S_ISDIR from watchdog.utils import stat as default_stat class DirectorySnapshotDiff(object): """ Compares two directory snapshots and creates an object that represents the difference between the two snapshots. :param ref: The reference directory snapshot. :type ref: :class:`DirectorySnapshot` :param snapshot: The directory snapshot which will be compared with the reference snapshot. :type snapshot: :class:`DirectorySnapshot` """ def __init__(self, ref, snapshot): created = snapshot.paths - ref.paths deleted = ref.paths - snapshot.paths # check that all unchanged paths have the same inode for path in ref.paths & snapshot.paths: if ref.inode(path) != snapshot.inode(path): created.add(path) deleted.add(path) # find moved paths moved = set() for path in set(deleted): inode = ref.inode(path) new_path = snapshot.path(inode) if new_path: # file is not deleted but moved deleted.remove(path) moved.add((path, new_path)) for path in set(created): inode = snapshot.inode(path) old_path = ref.path(inode) if old_path: created.remove(path) moved.add((old_path, path)) # find modified paths # first check paths that have not moved modified = set() for path in ref.paths & snapshot.paths: if ref.inode(path) == snapshot.inode(path): if ref.mtime(path) != snapshot.mtime(path): modified.add(path) for (old_path, new_path) in moved: if ref.mtime(old_path) != snapshot.mtime(new_path): modified.add(old_path) self._dirs_created = [path for path in created if snapshot.isdir(path)] self._dirs_deleted = [path for path in deleted if ref.isdir(path)] self._dirs_modified = [path for path in modified if ref.isdir(path)] self._dirs_moved = [(frm, to) for (frm, to) in moved if ref.isdir(frm)] self._files_created = list(created - set(self._dirs_created)) self._files_deleted = list(deleted - set(self._dirs_deleted)) self._files_modified = list(modified - set(self._dirs_modified)) self._files_moved = list(moved - set(self._dirs_moved)) @property def files_created(self): """List of files that were created.""" return self._files_created @property def files_deleted(self): """List of files that were deleted.""" return self._files_deleted @property def files_modified(self): """List of files that were modified.""" return self._files_modified @property def files_moved(self): """ List of files that were moved. Each event is a two-tuple the first item of which is the path that has been renamed to the second item in the tuple. """ return self._files_moved @property def dirs_modified(self): """ List of directories that were modified. """ return self._dirs_modified @property def dirs_moved(self): """ List of directories that were moved. Each event is a two-tuple the first item of which is the path that has been renamed to the second item in the tuple. """ return self._dirs_moved @property def dirs_deleted(self): """ List of directories that were deleted. """ return self._dirs_deleted @property def dirs_created(self): """ List of directories that were created. """ return self._dirs_created class DirectorySnapshot(object): """ A snapshot of stat information of files in a directory. :param path: The directory path for which a snapshot should be taken. :type path: ``str`` :param recursive: ``True`` if the entire directory tree should be included in the snapshot; ``False`` otherwise. :type recursive: ``bool`` :param walker_callback: .. deprecated:: 0.7.2 :param stat: Use custom stat function that returns a stat structure for path. Currently only st_dev, st_ino, st_mode and st_mtime are needed. A function with the signature ``walker_callback(path, stat_info)`` which will be called for every entry in the directory tree. :param listdir: Use custom listdir function. See ``os.listdir`` for details. """ def __init__(self, path, recursive=True, walker_callback=(lambda p, s: None), stat=default_stat, listdir=os.listdir): self._stat_info = {} self._inode_to_path = {} st = stat(path) self._stat_info[path] = st self._inode_to_path[(st.st_ino, st.st_dev)] = path def walk(root): try: paths = [os.path.join(root, name) for name in listdir(root)] except OSError as e: # Directory may have been deleted between finding it in the directory # list of its parent and trying to delete its contents. If this # happens we treat it as empty. if e.errno == errno.ENOENT: return else: raise entries = [] for p in paths: try: entries.append((p, stat(p))) except OSError: continue for _ in entries: yield _ if recursive: for path, st in entries: if S_ISDIR(st.st_mode): for _ in walk(path): yield _ for p, st in walk(path): i = (st.st_ino, st.st_dev) self._inode_to_path[i] = p self._stat_info[p] = st walker_callback(p, st) @property def paths(self): """ Set of file/directory paths in the snapshot. """ return set(self._stat_info.keys()) def path(self, id): """ Returns path for id. None if id is unknown to this snapshot. """ return self._inode_to_path.get(id) def inode(self, path): """ Returns an id for path. """ st = self._stat_info[path] return (st.st_ino, st.st_dev) def isdir(self, path): return S_ISDIR(self._stat_info[path].st_mode) def mtime(self, path): return self._stat_info[path].st_mtime def stat_info(self, path): """ Returns a stat information object for the specified path from the snapshot. Attached information is subject to change. Do not use unless you specify `stat` in constructor. Use :func:`inode`, :func:`mtime`, :func:`isdir` instead. :param path: The path for which stat information should be obtained from a snapshot. """ return self._stat_info[path] def __sub__(self, previous_dirsnap): """Allow subtracting a DirectorySnapshot object instance from another. :returns: A :class:`DirectorySnapshotDiff` object. """ return DirectorySnapshotDiff(previous_dirsnap, self) def __str__(self): return self.__repr__() def __repr__(self): return str(self._stat_info) watchdog-0.9.0/src/watchdog/utils/echo.py0000644000175000017500000001227113341070440021074 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # echo.py: Tracing function calls using Python decorators. # # Written by Thomas Guest # Please see http://wordaligned.org/articles/echo # # Place into the public domain. """ Echo calls made to functions and methods in a module. "Echoing" a function call means printing out the name of the function and the values of its arguments before making the call (which is more commonly referred to as "tracing", but Python already has a trace module). Example: to echo calls made to functions in "my_module" do: import echo import my_module echo.echo_module(my_module) Example: to echo calls made to functions in "my_module.my_class" do: echo.echo_class(my_module.my_class) Alternatively, echo.echo can be used to decorate functions. Calls to the decorated function will be echoed. Example: @echo.echo def my_function(args): pass """ import inspect import sys def name(item): " Return an item's name. " return item.__name__ def is_classmethod(instancemethod, klass): " Determine if an instancemethod is a classmethod. " return inspect.ismethod(instancemethod) and instancemethod.__self__ is klass def is_static_method(method, klass): """Returns True if method is an instance method of klass.""" for c in klass.mro(): if name(method) in c.__dict__: return isinstance(c.__dict__[name(method)], staticmethod) else: return False def is_class_private_name(name): " Determine if a name is a class private name. " # Exclude system defined names such as __init__, __add__ etc return name.startswith("__") and not name.endswith("__") def method_name(method): """ Return a method's name. This function returns the name the method is accessed by from outside the class (i.e. it prefixes "private" methods appropriately). """ mname = name(method) if is_class_private_name(mname): mname = "_%s%s" % (name(method.__self__.__class__), mname) return mname def format_arg_value(arg_val): """ Return a string representing a (name, value) pair. >>> format_arg_value(('x', (1, 2, 3))) 'x=(1, 2, 3)' """ arg, val = arg_val return "%s=%r" % (arg, val) def echo(fn, write=sys.stdout.write): """ Echo calls to a function. Returns a decorated version of the input function which "echoes" calls made to it by writing out the function's name and the arguments it was called with. """ import functools # Unpack function's arg count, arg names, arg defaults code = fn.__code__ argcount = code.co_argcount argnames = code.co_varnames[:argcount] fn_defaults = fn.__defaults__ or list() argdefs = dict(list(zip(argnames[-len(fn_defaults):], fn_defaults))) @functools.wraps(fn) def wrapped(*v, **k): # Collect function arguments by chaining together positional, # defaulted, extra positional and keyword arguments. positional = list(map(format_arg_value, list(zip(argnames, v)))) defaulted = [format_arg_value((a, argdefs[a])) for a in argnames[len(v):] if a not in k] nameless = list(map(repr, v[argcount:])) keyword = list(map(format_arg_value, list(k.items()))) args = positional + defaulted + nameless + keyword write("%s(%s)\n" % (name(fn), ", ".join(args))) return fn(*v, **k) return wrapped def echo_instancemethod(klass, method, write=sys.stdout.write): """ Change an instancemethod so that calls to it are echoed. Replacing a classmethod is a little more tricky. See: http://www.python.org/doc/current/ref/types.html """ mname = method_name(method) never_echo = "__str__", "__repr__", # Avoid recursion printing method calls if mname in never_echo: pass elif is_classmethod(method, klass): setattr(klass, mname, classmethod(echo(method.__func__, write))) else: setattr(klass, mname, echo(method, write)) def echo_class(klass, write=sys.stdout.write): """ Echo calls to class methods and static functions """ for _, method in inspect.getmembers(klass, inspect.ismethod): #In python 3 only class methods are returned here, but in python2 instance methods are too. echo_instancemethod(klass, method, write) for _, fn in inspect.getmembers(klass, inspect.isfunction): if is_static_method(fn, klass): setattr(klass, name(fn), staticmethod(echo(fn, write))) else: #It's not a class or a static method, so it must be an instance method. #This should only be called in python 3, because in python 3 instance methods are considered functions. echo_instancemethod(klass, fn, write) def echo_module(mod, write=sys.stdout.write): """ Echo calls to functions and methods in a module. """ for fname, fn in inspect.getmembers(mod, inspect.isfunction): setattr(mod, fname, echo(fn, write)) for _, klass in inspect.getmembers(mod, inspect.isclass): echo_class(klass, write) if __name__ == "__main__": import doctest optionflags = doctest.ELLIPSIS doctest.testfile('echoexample.txt', optionflags=optionflags) doctest.testmod(optionflags=optionflags) watchdog-0.9.0/src/watchdog/utils/event_backport.py0000644000175000017500000000160613341070440023164 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Backport of Event from py2.7 (method wait in py2.6 returns None) from threading import Condition, Lock class Event(object): def __init__(self,): self.__cond = Condition(Lock()) self.__flag = False def isSet(self): return self.__flag is_set = isSet def set(self): self.__cond.acquire() try: self.__flag = True self.__cond.notify_all() finally: self.__cond.release() def clear(self): self.__cond.acquire() try: self.__flag = False finally: self.__cond.release() def wait(self, timeout=None): self.__cond.acquire() try: if not self.__flag: self.__cond.wait(timeout) return self.__flag finally: self.__cond.release() watchdog-0.9.0/src/watchdog/utils/importlib2.py0000644000175000017500000000346013341070440022241 0ustar danilodanilo00000000000000# The MIT License (MIT) # Copyright (c) 2013 Peter M. Elias # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE def import_module(target, relative_to=None): target_parts = target.split('.') target_depth = target_parts.count('') target_path = target_parts[target_depth:] target = target[target_depth:] fromlist = [target] if target_depth and relative_to: relative_parts = relative_to.split('.') relative_to = '.'.join(relative_parts[:-(target_depth - 1) or None]) if len(target_path) > 1: relative_to = '.'.join(filter(None, [relative_to]) + target_path[:-1]) fromlist = target_path[-1:] target = fromlist[0] elif not relative_to: fromlist = [] mod = __import__(relative_to or target, globals(), locals(), fromlist) return getattr(mod, target, mod) watchdog-0.9.0/src/watchdog/utils/platform.py0000644000175000017500000000274213341070440022004 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys PLATFORM_WINDOWS = 'windows' PLATFORM_LINUX = 'linux' PLATFORM_BSD = 'bsd' PLATFORM_DARWIN = 'darwin' PLATFORM_UNKNOWN = 'unknown' def get_platform_name(): if sys.platform.startswith("win"): return PLATFORM_WINDOWS elif sys.platform.startswith('darwin'): return PLATFORM_DARWIN elif sys.platform.startswith('linux'): return PLATFORM_LINUX elif sys.platform.startswith(('dragonfly', 'freebsd', 'netbsd', 'openbsd', )): return PLATFORM_BSD else: return PLATFORM_UNKNOWN __platform__ = get_platform_name() def is_linux(): return __platform__ == PLATFORM_LINUX def is_bsd(): return __platform__ == PLATFORM_BSD def is_darwin(): return __platform__ == PLATFORM_DARWIN def is_windows(): return __platform__ == PLATFORM_WINDOWS watchdog-0.9.0/src/watchdog/utils/unicode_paths.py0000644000175000017500000000421013341070440022775 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (c) 2013 Will Bond # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import sys from watchdog.utils import platform try: # Python 2 str_cls = unicode bytes_cls = str except NameError: # Python 3 str_cls = str bytes_cls = bytes # This is used by Linux when the locale seems to be improperly set. UTF-8 tends # to be the encoding used by all distros, so this is a good fallback. fs_fallback_encoding = 'utf-8' fs_encoding = sys.getfilesystemencoding() or fs_fallback_encoding def encode(path): if isinstance(path, str_cls): try: path = path.encode(fs_encoding, 'strict') except UnicodeEncodeError: if not platform.is_linux(): raise path = path.encode(fs_fallback_encoding, 'strict') return path def decode(path): if isinstance(path, bytes_cls): try: path = path.decode(fs_encoding, 'strict') except UnicodeDecodeError: if not platform.is_linux(): raise path = path.decode(fs_fallback_encoding, 'strict') return path watchdog-0.9.0/src/watchdog/utils/win32stat.py0000644000175000017500000000737013341070440022020 0ustar danilodanilo00000000000000# -*- coding: utf-8 -*- # # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ :module: watchdog.utils.win32stat :synopsis: Implementation of stat with st_ino and st_dev support. Functions --------- .. autofunction:: stat """ import ctypes import ctypes.wintypes import stat as stdstat from collections import namedtuple INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value OPEN_EXISTING = 3 FILE_READ_ATTRIBUTES = 0x80 FILE_ATTRIBUTE_NORMAL = 0x80 FILE_ATTRIBUTE_READONLY = 0x1 FILE_ATTRIBUTE_DIRECTORY = 0x10 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 class FILETIME(ctypes.Structure): _fields_ = [("dwLowDateTime", ctypes.wintypes.DWORD), ("dwHighDateTime", ctypes.wintypes.DWORD)] class BY_HANDLE_FILE_INFORMATION(ctypes.Structure): _fields_ = [('dwFileAttributes', ctypes.wintypes.DWORD), ('ftCreationTime', FILETIME), ('ftLastAccessTime', FILETIME), ('ftLastWriteTime', FILETIME), ('dwVolumeSerialNumber', ctypes.wintypes.DWORD), ('nFileSizeHigh', ctypes.wintypes.DWORD), ('nFileSizeLow', ctypes.wintypes.DWORD), ('nNumberOfLinks', ctypes.wintypes.DWORD), ('nFileIndexHigh', ctypes.wintypes.DWORD), ('nFileIndexLow', ctypes.wintypes.DWORD)] CreateFile = ctypes.windll.kernel32.CreateFileW CreateFile.restype = ctypes.wintypes.HANDLE CreateFile.argtypes = ( ctypes.c_wchar_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.c_void_p, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD, ctypes.wintypes.HANDLE, ) GetFileInformationByHandle = ctypes.windll.kernel32.GetFileInformationByHandle GetFileInformationByHandle.restype = ctypes.wintypes.BOOL GetFileInformationByHandle.argtypes = ( ctypes.wintypes.HANDLE, ctypes.wintypes.POINTER(BY_HANDLE_FILE_INFORMATION), ) CloseHandle = ctypes.windll.kernel32.CloseHandle CloseHandle.restype = ctypes.wintypes.BOOL CloseHandle.argtypes = (ctypes.wintypes.HANDLE,) StatResult = namedtuple('StatResult', 'st_dev st_ino st_mode st_mtime') def _to_mode(attr): m = 0 if (attr & FILE_ATTRIBUTE_DIRECTORY): m |= stdstat.S_IFDIR | 0o111 else: m |= stdstat.S_IFREG if (attr & FILE_ATTRIBUTE_READONLY): m |= 0o444 else: m |= 0o666 return m def _to_unix_time(ft): t = (ft.dwHighDateTime) << 32 | ft.dwLowDateTime return (t / 10000000) - 11644473600 def stat(path): hfile = CreateFile(path, FILE_READ_ATTRIBUTES, 0, None, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, None) if hfile == INVALID_HANDLE_VALUE: raise ctypes.WinError() info = BY_HANDLE_FILE_INFORMATION() r = GetFileInformationByHandle(hfile, info) CloseHandle(hfile) if not r: raise ctypes.WinError() return StatResult(st_dev=info.dwVolumeSerialNumber, st_ino=(info.nFileIndexHigh << 32) + info.nFileIndexLow, st_mode=_to_mode(info.dwFileAttributes), st_mtime=_to_unix_time(info.ftLastWriteTime) ) watchdog-0.9.0/src/watchdog/version.py0000644000175000017500000000171613341327704020515 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # When updating this version number, please update the # ``docs/source/global.rst.inc`` file as well. VERSION_MAJOR = 0 VERSION_MINOR = 9 VERSION_BUILD = 0 VERSION_INFO = (VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD) VERSION_STRING = "%d.%d.%d" % VERSION_INFO __version__ = VERSION_INFO watchdog-0.9.0/src/watchdog/watchmedo.py0000755000175000017500000004204113341070440020772 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ :module: watchdog.watchmedo :author: yesudeep@google.com (Yesudeep Mangalapilly) :synopsis: ``watchmedo`` shell script utility. """ import os.path import sys import yaml import time import logging try: from cStringIO import StringIO except ImportError: try: from StringIO import StringIO except ImportError: from io import StringIO from argh import arg, aliases, ArghParser, expects_obj from watchdog.version import VERSION_STRING from watchdog.utils import load_class logging.basicConfig(level=logging.INFO) CONFIG_KEY_TRICKS = 'tricks' CONFIG_KEY_PYTHON_PATH = 'python-path' def path_split(pathname_spec, separator=os.path.sep): """ Splits a pathname specification separated by an OS-dependent separator. :param pathname_spec: The pathname specification. :param separator: (OS Dependent) `:` on Unix and `;` on Windows or user-specified. """ return list(pathname_spec.split(separator)) def add_to_sys_path(pathnames, index=0): """ Adds specified paths at specified index into the sys.path list. :param paths: A list of paths to add to the sys.path :param index: (Default 0) The index in the sys.path list where the paths will be added. """ for pathname in pathnames[::-1]: sys.path.insert(index, pathname) def load_config(tricks_file_pathname): """ Loads the YAML configuration from the specified file. :param tricks_file_path: The path to the tricks configuration file. :returns: A dictionary of configuration information. """ f = open(tricks_file_pathname, 'rb') content = f.read() f.close() config = yaml.load(content) return config def parse_patterns(patterns_spec, ignore_patterns_spec, separator=';'): """ Parses pattern argument specs and returns a two-tuple of (patterns, ignore_patterns). """ patterns = patterns_spec.split(separator) ignore_patterns = ignore_patterns_spec.split(separator) if ignore_patterns == ['']: ignore_patterns = [] return (patterns, ignore_patterns) def observe_with(observer, event_handler, pathnames, recursive): """ Single observer thread with a scheduled path and event handler. :param observer: The observer thread. :param event_handler: Event handler which will be called in response to file system events. :param pathnames: A list of pathnames to monitor. :param recursive: ``True`` if recursive; ``False`` otherwise. """ for pathname in set(pathnames): observer.schedule(event_handler, pathname, recursive) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join() def schedule_tricks(observer, tricks, pathname, recursive): """ Schedules tricks with the specified observer and for the given watch path. :param observer: The observer thread into which to schedule the trick and watch. :param tricks: A list of tricks. :param pathname: A path name which should be watched. :param recursive: ``True`` if recursive; ``False`` otherwise. """ for trick in tricks: for name, value in list(trick.items()): TrickClass = load_class(name) handler = TrickClass(**value) trick_pathname = getattr(handler, 'source_directory', None) or pathname observer.schedule(handler, trick_pathname, recursive) @aliases('tricks') @arg('files', nargs='*', help='perform tricks from given file') @arg('--python-path', default='.', help='paths separated by %s to add to the python path' % os.path.sep) @arg('--interval', '--timeout', dest='timeout', default=1.0, help='use this as the polling interval/blocking timeout') @arg('--recursive', default=True, help='recursively monitor paths') @expects_obj def tricks_from(args): """ Subcommand to execute tricks from a tricks configuration file. :param args: Command line argument options. """ from watchdog.observers import Observer add_to_sys_path(path_split(args.python_path)) observers = [] for tricks_file in args.files: observer = Observer(timeout=args.timeout) if not os.path.exists(tricks_file): raise IOError("cannot find tricks file: %s" % tricks_file) config = load_config(tricks_file) try: tricks = config[CONFIG_KEY_TRICKS] except KeyError: raise KeyError("No `%s' key specified in %s." % ( CONFIG_KEY_TRICKS, tricks_file)) if CONFIG_KEY_PYTHON_PATH in config: add_to_sys_path(config[CONFIG_KEY_PYTHON_PATH]) dir_path = os.path.dirname(tricks_file) if not dir_path: dir_path = os.path.relpath(os.getcwd()) schedule_tricks(observer, tricks, dir_path, args.recursive) observer.start() observers.append(observer) try: while True: time.sleep(1) except KeyboardInterrupt: for o in observers: o.unschedule_all() o.stop() for o in observers: o.join() @aliases('generate-tricks-yaml') @arg('trick_paths', nargs='*', help='Dotted paths for all the tricks you want to generate') @arg('--python-path', default='.', help='paths separated by %s to add to the python path' % os.path.sep) @arg('--append-to-file', default=None, help='appends the generated tricks YAML to a file; \ if not specified, prints to standard output') @arg('-a', '--append-only', dest='append_only', default=False, help='if --append-to-file is not specified, produces output for \ appending instead of a complete tricks yaml file.') @expects_obj def tricks_generate_yaml(args): """ Subcommand to generate Yaml configuration for tricks named on the command line. :param args: Command line argument options. """ python_paths = path_split(args.python_path) add_to_sys_path(python_paths) output = StringIO() for trick_path in args.trick_paths: TrickClass = load_class(trick_path) output.write(TrickClass.generate_yaml()) content = output.getvalue() output.close() header = yaml.dump({CONFIG_KEY_PYTHON_PATH: python_paths}) header += "%s:\n" % CONFIG_KEY_TRICKS if args.append_to_file is None: # Output to standard output. if not args.append_only: content = header + content sys.stdout.write(content) else: if not os.path.exists(args.append_to_file): content = header + content output = open(args.append_to_file, 'ab') output.write(content) output.close() @arg('directories', nargs='*', default='.', help='directories to watch.') @arg('-p', '--pattern', '--patterns', dest='patterns', default='*', help='matches event paths with these patterns (separated by ;).') @arg('-i', '--ignore-pattern', '--ignore-patterns', dest='ignore_patterns', default='', help='ignores event paths with these patterns (separated by ;).') @arg('-D', '--ignore-directories', dest='ignore_directories', default=False, help='ignores events for directories') @arg('-R', '--recursive', dest='recursive', default=False, help='monitors the directories recursively') @arg('--interval', '--timeout', dest='timeout', default=1.0, help='use this as the polling interval/blocking timeout') @arg('--trace', default=False, help='dumps complete dispatching trace') @arg('--debug-force-polling', default=False, help='[debug] forces polling') @arg('--debug-force-kqueue', default=False, help='[debug] forces BSD kqueue(2)') @arg('--debug-force-winapi', default=False, help='[debug] forces Windows API') @arg('--debug-force-winapi-async', default=False, help='[debug] forces Windows API + I/O completion') @arg('--debug-force-fsevents', default=False, help='[debug] forces Mac OS X FSEvents') @arg('--debug-force-inotify', default=False, help='[debug] forces Linux inotify(7)') @expects_obj def log(args): """ Subcommand to log file system events to the console. :param args: Command line argument options. """ from watchdog.utils import echo from watchdog.tricks import LoggerTrick if args.trace: echo.echo_class(LoggerTrick) patterns, ignore_patterns =\ parse_patterns(args.patterns, args.ignore_patterns) handler = LoggerTrick(patterns=patterns, ignore_patterns=ignore_patterns, ignore_directories=args.ignore_directories) if args.debug_force_polling: from watchdog.observers.polling import PollingObserver as Observer elif args.debug_force_kqueue: from watchdog.observers.kqueue import KqueueObserver as Observer elif args.debug_force_winapi_async: from watchdog.observers.read_directory_changes_async import\ WindowsApiAsyncObserver as Observer elif args.debug_force_winapi: from watchdog.observers.read_directory_changes import\ WindowsApiObserver as Observer elif args.debug_force_inotify: from watchdog.observers.inotify import InotifyObserver as Observer elif args.debug_force_fsevents: from watchdog.observers.fsevents import FSEventsObserver as Observer else: # Automatically picks the most appropriate observer for the platform # on which it is running. from watchdog.observers import Observer observer = Observer(timeout=args.timeout) observe_with(observer, handler, args.directories, args.recursive) @arg('directories', nargs='*', default='.', help='directories to watch') @arg('-c', '--command', dest='command', default=None, help='''shell command executed in response to matching events. These interpolation variables are available to your command string:: ${watch_src_path} - event source path; ${watch_dest_path} - event destination path (for moved events); ${watch_event_type} - event type; ${watch_object} - ``file`` or ``directory`` Note:: Please ensure you do not use double quotes (") to quote your command string. That will force your shell to interpolate before the command is processed by this subcommand. Example option usage:: --command='echo "${watch_src_path}"' ''') @arg('-p', '--pattern', '--patterns', dest='patterns', default='*', help='matches event paths with these patterns (separated by ;).') @arg('-i', '--ignore-pattern', '--ignore-patterns', dest='ignore_patterns', default='', help='ignores event paths with these patterns (separated by ;).') @arg('-D', '--ignore-directories', dest='ignore_directories', default=False, help='ignores events for directories') @arg('-R', '--recursive', dest='recursive', default=False, help='monitors the directories recursively') @arg('--interval', '--timeout', dest='timeout', default=1.0, help='use this as the polling interval/blocking timeout') @arg('-w', '--wait', dest='wait_for_process', action='store_true', default=False, help="wait for process to finish to avoid multiple simultaneous instances") @arg('-W', '--drop', dest='drop_during_process', action='store_true', default=False, help="Ignore events that occur while command is still being executed " \ "to avoid multiple simultaneous instances") @arg('--debug-force-polling', default=False, help='[debug] forces polling') @expects_obj def shell_command(args): """ Subcommand to execute shell commands in response to file system events. :param args: Command line argument options. """ from watchdog.tricks import ShellCommandTrick if not args.command: args.command = None if args.debug_force_polling: from watchdog.observers.polling import PollingObserver as Observer else: from watchdog.observers import Observer patterns, ignore_patterns = parse_patterns(args.patterns, args.ignore_patterns) handler = ShellCommandTrick(shell_command=args.command, patterns=patterns, ignore_patterns=ignore_patterns, ignore_directories=args.ignore_directories, wait_for_process=args.wait_for_process, drop_during_process=args.drop_during_process) observer = Observer(timeout=args.timeout) observe_with(observer, handler, args.directories, args.recursive) @arg('command', help='''Long-running command to run in a subprocess. ''') @arg('command_args', metavar='arg', nargs='*', help='''Command arguments. Note: Use -- before the command arguments, otherwise watchmedo will try to interpret them. ''') @arg('-d', '--directory', dest='directories', metavar='directory', action='append', help='Directory to watch. Use another -d or --directory option ' 'for each directory.') @arg('-p', '--pattern', '--patterns', dest='patterns', default='*', help='matches event paths with these patterns (separated by ;).') @arg('-i', '--ignore-pattern', '--ignore-patterns', dest='ignore_patterns', default='', help='ignores event paths with these patterns (separated by ;).') @arg('-D', '--ignore-directories', dest='ignore_directories', default=False, help='ignores events for directories') @arg('-R', '--recursive', dest='recursive', default=False, help='monitors the directories recursively') @arg('--interval', '--timeout', dest='timeout', default=1.0, help='use this as the polling interval/blocking timeout') @arg('--signal', dest='signal', default='SIGINT', help='stop the subprocess with this signal (default SIGINT)') @arg('--kill-after', dest='kill_after', default=10.0, help='when stopping, kill the subprocess after the specified timeout ' '(default 10)') @expects_obj def auto_restart(args): """ Subcommand to start a long-running subprocess and restart it on matched events. :param args: Command line argument options. """ from watchdog.observers import Observer from watchdog.tricks import AutoRestartTrick import signal import re if not args.directories: args.directories = ['.'] # Allow either signal name or number. if re.match('^SIG[A-Z]+$', args.signal): stop_signal = getattr(signal, args.signal) else: stop_signal = int(args.signal) # Handle SIGTERM in the same manner as SIGINT so that # this program has a chance to stop the child process. def handle_sigterm(_signum, _frame): raise KeyboardInterrupt() signal.signal(signal.SIGTERM, handle_sigterm) patterns, ignore_patterns = parse_patterns(args.patterns, args.ignore_patterns) command = [args.command] command.extend(args.command_args) handler = AutoRestartTrick(command=command, patterns=patterns, ignore_patterns=ignore_patterns, ignore_directories=args.ignore_directories, stop_signal=stop_signal, kill_after=args.kill_after) handler.start() observer = Observer(timeout=args.timeout) observe_with(observer, handler, args.directories, args.recursive) handler.stop() epilog = """Copyright 2011 Yesudeep Mangalapilly . Copyright 2012 Google, Inc. Licensed under the terms of the Apache license, version 2.0. Please see LICENSE in the source code for more information.""" parser = ArghParser(epilog=epilog) parser.add_commands([tricks_from, tricks_generate_yaml, log, shell_command, auto_restart]) parser.add_argument('--version', action='version', version='%(prog)s ' + VERSION_STRING) def main(): """Entry-point function.""" parser.dispatch() if __name__ == '__main__': main() watchdog-0.9.0/src/watchdog.egg-info/0000755000175000017500000000000013341331653020141 5ustar danilodanilo00000000000000watchdog-0.9.0/src/watchdog.egg-info/PKG-INFO0000644000175000017500000003123513341331653021242 0ustar danilodanilo00000000000000Metadata-Version: 1.1 Name: watchdog Version: 0.9.0 Summary: Filesystem events monitoring Home-page: http://github.com/gorakhargosh/watchdog Author: Yesudeep Mangalapilly Author-email: yesudeep@gmail.com License: Apache License 2.0 Description: Watchdog ======== Python API and shell utilities to monitor file system events. Example API Usage ----------------- A simple program that uses watchdog to monitor directories specified as command-line arguments and logs events generated: .. code-block:: python import sys import time import logging from watchdog.observers import Observer from watchdog.events import LoggingEventHandler if __name__ == "__main__": logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S') path = sys.argv[1] if len(sys.argv) > 1 else '.' event_handler = LoggingEventHandler() observer = Observer() observer.schedule(event_handler, path, recursive=True) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join() Shell Utilities --------------- Watchdog comes with a utility script called ``watchmedo``. Please type ``watchmedo --help`` at the shell prompt to know more about this tool. Here is how you can log the current directory recursively for events related only to ``*.py`` and ``*.txt`` files while ignoring all directory events: .. code-block:: bash watchmedo log \ --patterns="*.py;*.txt" \ --ignore-directories \ --recursive \ . You can use the ``shell-command`` subcommand to execute shell commands in response to events: .. code-block:: bash watchmedo shell-command \ --patterns="*.py;*.txt" \ --recursive \ --command='echo "${watch_src_path}"' \ . Please see the help information for these commands by typing: .. code-block:: bash watchmedo [command] --help About ``watchmedo`` Tricks ~~~~~~~~~~~~~~~~~~~~~~~~~~ ``watchmedo`` can read ``tricks.yaml`` files and execute tricks within them in response to file system events. Tricks are actually event handlers that subclass ``watchdog.tricks.Trick`` and are written by plugin authors. Trick classes are augmented with a few additional features that regular event handlers don't need. An example ``tricks.yaml`` file: .. code-block:: yaml tricks: - watchdog.tricks.LoggerTrick: patterns: ["*.py", "*.js"] - watchmedo_webtricks.GoogleClosureTrick: patterns: ['*.js'] hash_names: true mappings_format: json # json|yaml|python mappings_module: app/javascript_mappings suffix: .min.js compilation_level: advanced # simple|advanced source_directory: app/static/js/ destination_directory: app/public/js/ files: index-page: - app/static/js/vendor/jquery*.js - app/static/js/base.js - app/static/js/index-page.js about-page: - app/static/js/vendor/jquery*.js - app/static/js/base.js - app/static/js/about-page/**/*.js The directory containing the ``tricks.yaml`` file will be monitored. Each trick class is initialized with its corresponding keys in the ``tricks.yaml`` file as arguments and events are fed to an instance of this class as they arrive. Tricks will be included in the 0.5.0 release. I need community input about them. Please file enhancement requests at the `issue tracker`_. Installation ------------ Installing from PyPI using ``pip``: .. code-block:: bash $ pip install watchdog Installing from PyPI using ``easy_install``: .. code-block:: bash $ easy_install watchdog Installing from source: .. code-block:: bash $ python setup.py install Installation Caveats ~~~~~~~~~~~~~~~~~~~~ The ``watchmedo`` script depends on PyYAML_ which links with LibYAML_, which brings a performance boost to the PyYAML parser. However, installing LibYAML_ is optional but recommended. On Mac OS X, you can use homebrew_ to install LibYAML: .. code-block:: bash $ brew install libyaml On Linux, use your favorite package manager to install LibYAML. Here's how you do it on Ubuntu: .. code-block:: bash $ sudo aptitude install libyaml-dev On Windows, please install PyYAML_ using the binaries they provide. Documentation ------------- You can browse the latest release documentation_ online. Contribute ---------- Fork the `repository`_ on GitHub and send a pull request, or file an issue ticket at the `issue tracker`_. For general help and questions use the official `mailing list`_ or ask on `stackoverflow`_ with tag `python-watchdog`. Create and activate your virtual environment, then:: pip install pytest pip install -e . py.test tests Supported Platforms ------------------- * Linux 2.6 (inotify) * Mac OS X (FSEvents, kqueue) * FreeBSD/BSD (kqueue) * Windows (ReadDirectoryChangesW with I/O completion ports; ReadDirectoryChangesW worker threads) * OS-independent (polling the disk for directory snapshots and comparing them periodically; slow and not recommended) Note that when using watchdog with kqueue, you need the number of file descriptors allowed to be opened by programs running on your system to be increased to more than the number of files that you will be monitoring. The easiest way to do that is to edit your ``~/.profile`` file and add a line similar to:: ulimit -n 1024 This is an inherent problem with kqueue because it uses file descriptors to monitor files. That plus the enormous amount of bookkeeping that watchdog needs to do in order to monitor file descriptors just makes this a painful way to monitor files and directories. In essence, kqueue is not a very scalable way to monitor a deeply nested directory of files and directories with a large number of files. About using watchdog with editors like Vim ------------------------------------------ Vim does not modify files unless directed to do so. It creates backup files and then swaps them in to replace the files you are editing on the disk. This means that if you use Vim to edit your files, the on-modified events for those files will not be triggered by watchdog. You may need to configure Vim to appropriately to disable this feature. Dependencies ------------ 1. Python 2.6 or above. 2. pathtools_ 3. select_backport_ (select.kqueue replacement for 2.6 on BSD/Mac OS X) 4. XCode_ (only on Mac OS X) 5. PyYAML_ (only for ``watchmedo`` script) 6. argh_ (only for ``watchmedo`` script) Licensing --------- Watchdog is licensed under the terms of the `Apache License, version 2.0`_. Copyright 2011 `Yesudeep Mangalapilly`_. Copyright 2012 Google, Inc. Project `source code`_ is available at Github. Please report bugs and file enhancement requests at the `issue tracker`_. Why Watchdog? ------------- Too many people tried to do the same thing and none did what I needed Python to do: * pnotify_ * `unison fsmonitor`_ * fsmonitor_ * guard_ * pyinotify_ * `inotify-tools`_ * jnotify_ * treewalker_ * `file.monitor`_ * pyfilesystem_ .. links: .. _Yesudeep Mangalapilly: yesudeep@gmail.com .. _source code: http://github.com/gorakhargosh/watchdog .. _issue tracker: http://github.com/gorakhargosh/watchdog/issues .. _Apache License, version 2.0: http://www.apache.org/licenses/LICENSE-2.0 .. _documentation: http://packages.python.org/watchdog/ .. _stackoverflow: http://stackoverflow.com/questions/tagged/python-watchdog .. _mailing list: http://groups.google.com/group/watchdog-python .. _repository: http://github.com/gorakhargosh/watchdog .. _issue tracker: http://github.com/gorakhargosh/watchdog/issues .. _homebrew: http://mxcl.github.com/homebrew/ .. _select_backport: http://pypi.python.org/pypi/select_backport .. _argh: http://pypi.python.org/pypi/argh .. _PyYAML: http://www.pyyaml.org/ .. _XCode: http://developer.apple.com/technologies/tools/xcode.html .. _LibYAML: http://pyyaml.org/wiki/LibYAML .. _pathtools: http://github.com/gorakhargosh/pathtools .. _pnotify: http://mark.heily.com/pnotify .. _unison fsmonitor: https://webdav.seas.upenn.edu/viewvc/unison/trunk/src/fsmonitor.py?view=markup&pathrev=471 .. _fsmonitor: http://github.com/shaurz/fsmonitor .. _guard: http://github.com/guard/guard .. _pyinotify: http://github.com/seb-m/pyinotify .. _inotify-tools: http://github.com/rvoicilas/inotify-tools .. _jnotify: http://jnotify.sourceforge.net/ .. _treewalker: http://github.com/jbd/treewatcher .. _file.monitor: http://github.com/pke/file.monitor .. _pyfilesystem: http://code.google.com/p/pyfilesystem .. :changelog: API changes ----------- 0.8.2 ~~~~~ - Event emitters are no longer started on schedule if ``Observer`` is not already running. 0.8.0 ~~~~~ - ``DirectorySnapshot``: methods returning internal stat info replaced by ``mtime``, ``inode`` and ``path`` methods. - ``DirectorySnapshot``: ``walker_callback`` parameter deprecated. Keywords: python filesystem monitoring monitor FSEvents kqueue inotify ReadDirectoryChangesW polling DirectorySnapshot Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Natural Language :: English Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: POSIX :: BSD Classifier: Operating System :: Microsoft :: Windows :: Windows NT/2000 Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Programming Language :: C Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: System :: Monitoring Classifier: Topic :: System :: Filesystems Classifier: Topic :: Utilities watchdog-0.9.0/src/watchdog.egg-info/SOURCES.txt0000644000175000017500000000407513341331653022033 0ustar danilodanilo00000000000000AUTHORS COPYING LICENSE MANIFEST.in README.rst changelog.rst setup.cfg setup.py docs/Makefile docs/dependencies.txt docs/echo.py.txt docs/eclipse_cdt_style.xml docs/make.bat docs/source/api.rst docs/source/conf.py docs/source/global.rst.inc docs/source/hacking.rst docs/source/index.rst docs/source/installation.rst docs/source/quickstart.rst docs/source/examples/logger.py docs/source/examples/patterns.py docs/source/examples/simple.py docs/source/examples/tricks.json docs/source/examples/tricks.yaml src/watchdog_fsevents.c src/watchdog/__init__.py src/watchdog/events.py src/watchdog/version.py src/watchdog/watchmedo.py src/watchdog.egg-info/PKG-INFO src/watchdog.egg-info/SOURCES.txt src/watchdog.egg-info/dependency_links.txt src/watchdog.egg-info/entry_points.txt src/watchdog.egg-info/not-zip-safe src/watchdog.egg-info/requires.txt src/watchdog.egg-info/top_level.txt src/watchdog/observers/__init__.py src/watchdog/observers/api.py src/watchdog/observers/fsevents.py src/watchdog/observers/fsevents2.py src/watchdog/observers/inotify.py src/watchdog/observers/inotify_buffer.py src/watchdog/observers/inotify_c.py src/watchdog/observers/kqueue.py src/watchdog/observers/polling.py src/watchdog/observers/read_directory_changes.py src/watchdog/observers/winapi.py src/watchdog/tricks/__init__.py src/watchdog/utils/__init__.py src/watchdog/utils/bricks.py src/watchdog/utils/compat.py src/watchdog/utils/decorators.py src/watchdog/utils/delayed_queue.py src/watchdog/utils/dirsnapshot.py src/watchdog/utils/echo.py src/watchdog/utils/event_backport.py src/watchdog/utils/importlib2.py src/watchdog/utils/platform.py src/watchdog/utils/unicode_paths.py src/watchdog/utils/win32stat.py tests/__init__.py tests/shell.py tests/test_delayed_queue.py tests/test_emitter.py tests/test_inotify_buffer.py tests/test_inotify_c.py tests/test_observer.py tests/test_skip_repeats_queue.py tests/test_snapshot_diff.py tests/legacy/test_watch_observers_winapi.py tests/legacy/test_watchdog_events.py tests/legacy/test_watchdog_observers_api.py tests/legacy/test_watchdog_observers_polling.py tests/legacy/utils.pywatchdog-0.9.0/src/watchdog.egg-info/dependency_links.txt0000644000175000017500000000000113341331653024207 0ustar danilodanilo00000000000000 watchdog-0.9.0/src/watchdog.egg-info/entry_points.txt0000644000175000017500000000006713341331653023442 0ustar danilodanilo00000000000000[console_scripts] watchmedo = watchdog.watchmedo:main watchdog-0.9.0/src/watchdog.egg-info/not-zip-safe0000644000175000017500000000000113341073371022367 0ustar danilodanilo00000000000000 watchdog-0.9.0/src/watchdog.egg-info/requires.txt0000644000175000017500000000005313341331653022537 0ustar danilodanilo00000000000000PyYAML>=3.10 argh>=0.24.1 pathtools>=0.1.1 watchdog-0.9.0/src/watchdog.egg-info/top_level.txt0000644000175000017500000000001113341331653022663 0ustar danilodanilo00000000000000watchdog watchdog-0.9.0/src/watchdog_fsevents.c0000644000175000017500000004700113341070440020524 0ustar danilodanilo00000000000000/** * watchdog_fsevents.c: Python-C bridge to the OS X FSEvents API. * * Copyright 2010 Malthe Borch * Copyright 2011 Yesudeep Mangalapilly * Copyright 2012 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #if (PY_VERSION_HEX < 0x02050000) && !defined(PY_SSIZE_T_MIN) typedef int Py_ssize_t; #define PY_SSIZE_T_MIN INT_MIN #define PY_SSIZE_T_MAX INT_MAX #endif /* Convenience macros to make code more readable. */ #define G_NOT(o) (!(o)) #define G_IS_NULL(o) ((o) == NULL) #define G_IS_NOT_NULL(o) ((o) != NULL) #define G_RETURN_NULL_IF_NULL(o) do{if(NULL == (o)){ return NULL; }}while(0) #define G_RETURN_NULL_IF(condition) do{if((condition)){ return NULL; }}while(0) #define G_RETURN_NULL_IF_NOT(condition) do{if(!(condition)){ return NULL; }}while(0) #define G_RETURN_IF(condition) do{if((condition)){ return; }}while(0) #define G_RETURN_IF_NOT(condition) do{if(!(condition)){ return; }}while(0) /* Error message definitions. */ #define ERROR_CANNOT_CALL_CALLBACK "Unable to call Python callback." /* Other information. */ #define MODULE_NAME "_watchdog_fsevents" /** * Event stream callback contextual information passed to * our ``watchdog_FSEventStreamCallback`` function by the * FSEvents API whenever an event occurs. */ typedef struct { /** * A pointer to the Python callback which will * will in turn be called by our event handler * with event information. The Python callback * function must accept 2 arguments, both of which * are Python lists:: * * def python_callback(event_paths, event_flags): * pass */ PyObject *python_callback; /** * A pointer to the associated ``FSEventStream`` * instance. */ FSEventStreamRef stream_ref; /** * A pointer to the associated ``CFRunLoop`` * instance. */ CFRunLoopRef run_loop_ref; /** * A pointer to the state of the Python thread. */ PyThreadState *thread_state; } StreamCallbackInfo; /** * Dictionary to keep track of which run loop * belongs to which emitter thread. */ PyObject *thread_to_run_loop = NULL; /** * Dictionary to keep track of which stream * belongs to which watch. */ PyObject *watch_to_stream = NULL; /** * PyCapsule destructor for Python 3 compatibility */ #if PY_MAJOR_VERSION >= 3 static void watchdog_pycapsule_destructor(PyObject *ptr) { void *p = PyCapsule_GetPointer(ptr, NULL); if (p) { PyMem_Free(p); } } #endif /** * This is the callback passed to the FSEvents API, which calls * the Python callback function, in turn, by passing in event data * as Python objects. * * :param stream_ref: * A pointer to an ``FSEventStream`` instance. * :param stream_callback_info_ref: * Callback context information passed by the FSEvents API. * This contains a reference to the Python callback that this * function calls in turn with information about the events. * :param num_events: * An unsigned integer representing the number of events * captured by the FSEvents API. * :param event_paths: * An array of NUL-terminated C strings representing event paths. * :param event_flags: * An array of ``FSEventStreamEventFlags`` unsigned integral * mask values. * :param event_ids: * An array of 64-bit unsigned integers representing event * identifiers. */ static void watchdog_FSEventStreamCallback(ConstFSEventStreamRef stream_ref, StreamCallbackInfo *stream_callback_info_ref, size_t num_events, const char *event_paths[], const FSEventStreamEventFlags event_flags[], const FSEventStreamEventId event_ids[]) { size_t i = 0; PyObject *callback_result = NULL; PyObject *path = NULL; PyObject *flags = NULL; PyObject *py_event_flags = NULL; PyObject *py_event_paths = NULL; PyThreadState *saved_thread_state = NULL; /* Acquire interpreter lock and save original thread state. */ PyGILState_STATE gil_state = PyGILState_Ensure(); saved_thread_state = PyThreadState_Swap(stream_callback_info_ref->thread_state); /* Convert event flags and paths to Python ints and strings. */ py_event_paths = PyList_New(num_events); py_event_flags = PyList_New(num_events); if (G_NOT(py_event_paths && py_event_flags)) { Py_DECREF(py_event_paths); Py_DECREF(py_event_flags); return /*NULL*/; } for (i = 0; i < num_events; ++i) { #if PY_MAJOR_VERSION >= 3 path = PyUnicode_FromString(event_paths[i]); flags = PyLong_FromLong(event_flags[i]); #else path = PyString_FromString(event_paths[i]); flags = PyInt_FromLong(event_flags[i]); #endif if (G_NOT(path && flags)) { Py_DECREF(py_event_paths); Py_DECREF(py_event_flags); return /*NULL*/; } PyList_SET_ITEM(py_event_paths, i, path); PyList_SET_ITEM(py_event_flags, i, flags); } /* Call the Python callback function supplied by the stream information * struct. The Python callback function should accept two arguments, * both being Python lists: * * def python_callback(event_paths, event_flags): * pass */ callback_result = \ PyObject_CallFunction(stream_callback_info_ref->python_callback, "OO", py_event_paths, py_event_flags); if (G_IS_NULL(callback_result)) { if (G_NOT(PyErr_Occurred())) { PyErr_SetString(PyExc_ValueError, ERROR_CANNOT_CALL_CALLBACK); } CFRunLoopStop(stream_callback_info_ref->run_loop_ref); } /* Release the lock and restore thread state. */ PyThreadState_Swap(saved_thread_state); PyGILState_Release(gil_state); } /** * Converts a list of Python strings to a ``CFMutableArray`` of * UTF-8 encoded ``CFString`` instances and returns a pointer to * the array. * * :param py_string_list: * List of Python strings. * :returns: * A pointer to ``CFMutableArray`` (that is, a * ``CFMutableArrayRef``) of UTF-8 encoded ``CFString`` * instances. */ static CFMutableArrayRef watchdog_CFMutableArrayRef_from_PyStringList(PyObject *py_string_list) { Py_ssize_t i = 0; Py_ssize_t string_list_size = 0; const char *c_string = NULL; CFMutableArrayRef array_of_cf_string = NULL; CFStringRef cf_string = NULL; PyObject *py_string = NULL; G_RETURN_NULL_IF_NULL(py_string_list); string_list_size = PyList_Size(py_string_list); /* Allocate a CFMutableArray. */ array_of_cf_string = CFArrayCreateMutable(kCFAllocatorDefault, 1, &kCFTypeArrayCallBacks); G_RETURN_NULL_IF_NULL(array_of_cf_string); /* Loop through the Python string list and copy strings to the * CFString array list. */ for (i = 0; i < string_list_size; ++i) { py_string = PyList_GetItem(py_string_list, i); G_RETURN_NULL_IF_NULL(py_string); #if PY_MAJOR_VERSION >= 3 if (PyUnicode_Check(py_string)) { c_string = PyUnicode_AsUTF8(py_string); } else { c_string = PyBytes_AS_STRING(py_string); } #else c_string = PyString_AS_STRING(py_string); #endif cf_string = CFStringCreateWithCString(kCFAllocatorDefault, c_string, kCFStringEncodingUTF8); CFArraySetValueAtIndex(array_of_cf_string, i, cf_string); CFRelease(cf_string); } return array_of_cf_string; } /** * Creates an instance of ``FSEventStream`` and returns a pointer * to the instance. * * :param stream_callback_info_ref: * Pointer to the callback context information that will be * passed by the FSEvents API to the callback handler specified * by the ``callback`` argument to this function. This * information contains a reference to the Python callback that * it must call in turn passing on the event information * as Python objects to the the Python callback. * :param py_paths: * A Python list of Python strings representing path names * to monitor. * :param callback: * A function pointer of type ``FSEventStreamCallback``. * :returns: * A pointer to an ``FSEventStream`` instance (that is, it returns * an ``FSEventStreamRef``). */ static FSEventStreamRef watchdog_FSEventStreamCreate(StreamCallbackInfo *stream_callback_info_ref, PyObject *py_paths, FSEventStreamCallback callback) { CFAbsoluteTime stream_latency = 0.01; CFMutableArrayRef paths = NULL; FSEventStreamRef stream_ref = NULL; /* Check arguments. */ G_RETURN_NULL_IF_NULL(py_paths); G_RETURN_NULL_IF_NULL(callback); /* Convert the Python paths list to a CFMutableArray. */ paths = watchdog_CFMutableArrayRef_from_PyStringList(py_paths); G_RETURN_NULL_IF_NULL(paths); /* Create the event stream. */ FSEventStreamContext stream_context = { 0, stream_callback_info_ref, NULL, NULL, NULL }; stream_ref = FSEventStreamCreate(kCFAllocatorDefault, callback, &stream_context, paths, kFSEventStreamEventIdSinceNow, stream_latency, kFSEventStreamCreateFlagNoDefer); CFRelease(paths); return stream_ref; } PyDoc_STRVAR(watchdog_add_watch__doc__, MODULE_NAME ".add_watch(emitter_thread, watch, callback, paths) -> None\ \nAdds a watch into the event loop for the given emitter thread.\n\n\ :param emitter_thread:\n\ The emitter thread.\n\ :param watch:\n\ The watch to add.\n\ :param callback:\n\ The callback function to call when an event occurs.\n\n\ Example::\n\n\ def callback(paths, flags):\n\ for path, flag in zip(paths, flags):\n\ print(\"%s=%ul\" % (path, flag))\n\ :param paths:\n\ A list of paths to monitor.\n"); static PyObject * watchdog_add_watch(PyObject *self, PyObject *args) { FSEventStreamRef stream_ref = NULL; StreamCallbackInfo *stream_callback_info_ref = NULL; CFRunLoopRef run_loop_ref = NULL; PyObject *emitter_thread = NULL; PyObject *watch = NULL; PyObject *paths_to_watch = NULL; PyObject *python_callback = NULL; PyObject *value = NULL; /* Ensure all arguments are received. */ G_RETURN_NULL_IF_NOT(PyArg_ParseTuple(args, "OOOO:schedule", &emitter_thread, &watch, &python_callback, &paths_to_watch)); /* Watch must not already be scheduled. */ G_RETURN_NULL_IF(PyDict_Contains(watch_to_stream, watch) == 1); /* Create an instance of the callback information structure. */ stream_callback_info_ref = PyMem_New(StreamCallbackInfo, 1); G_RETURN_NULL_IF_NULL(stream_callback_info_ref); /* Create an FSEvent stream and * Save the stream reference to the global watch-to-stream dictionary. */ stream_ref = watchdog_FSEventStreamCreate(stream_callback_info_ref, paths_to_watch, (FSEventStreamCallback) &watchdog_FSEventStreamCallback); #if PY_MAJOR_VERSION >= 3 value = PyCapsule_New(stream_ref, NULL, watchdog_pycapsule_destructor); #else value = PyCObject_FromVoidPtr(stream_ref, PyMem_Free); #endif PyDict_SetItem(watch_to_stream, watch, value); /* Get a reference to the runloop for the emitter thread * or to the current runloop. */ value = PyDict_GetItem(thread_to_run_loop, emitter_thread); if (G_IS_NULL(value)) { run_loop_ref = CFRunLoopGetCurrent(); } else { #if PY_MAJOR_VERSION >= 3 run_loop_ref = PyCapsule_GetPointer(value, NULL); #else run_loop_ref = PyCObject_AsVoidPtr(value); #endif } /* Schedule the stream with the obtained runloop. */ FSEventStreamScheduleWithRunLoop(stream_ref, run_loop_ref, kCFRunLoopDefaultMode); /* Set the stream information for the callback. * This data will be passed to our watchdog_FSEventStreamCallback function * by the FSEvents API whenever an event occurs. */ stream_callback_info_ref->python_callback = python_callback; stream_callback_info_ref->stream_ref = stream_ref; stream_callback_info_ref->run_loop_ref = run_loop_ref; stream_callback_info_ref->thread_state = PyThreadState_Get(); Py_INCREF(python_callback); /* Start the event stream. */ if (G_NOT(FSEventStreamStart(stream_ref))) { FSEventStreamInvalidate(stream_ref); FSEventStreamRelease(stream_ref); return NULL; } Py_INCREF(Py_None); return Py_None; } PyDoc_STRVAR(watchdog_read_events__doc__, MODULE_NAME ".read_events(emitter_thread) -> None\n\ Blocking function that runs an event loop associated with an emitter thread.\n\n\ :param emitter_thread:\n\ The emitter thread for which the event loop will be run.\n"); static PyObject * watchdog_read_events(PyObject *self, PyObject *args) { CFRunLoopRef run_loop_ref = NULL; PyObject *emitter_thread = NULL; PyObject *value = NULL; G_RETURN_NULL_IF_NOT(PyArg_ParseTuple(args, "O:loop", &emitter_thread)); PyEval_InitThreads(); /* Allocate information and store thread state. */ value = PyDict_GetItem(thread_to_run_loop, emitter_thread); if (G_IS_NULL(value)) { run_loop_ref = CFRunLoopGetCurrent(); #if PY_MAJOR_VERSION >= 3 value = PyCapsule_New(run_loop_ref, NULL, watchdog_pycapsule_destructor); #else value = PyCObject_FromVoidPtr(run_loop_ref, PyMem_Free); #endif PyDict_SetItem(thread_to_run_loop, emitter_thread, value); Py_INCREF(emitter_thread); Py_INCREF(value); } /* No timeout, block until events. */ Py_BEGIN_ALLOW_THREADS; CFRunLoopRun(); Py_END_ALLOW_THREADS; /* Clean up state information. */ if (PyDict_DelItem(thread_to_run_loop, emitter_thread) == 0) { Py_DECREF(emitter_thread); Py_INCREF(value); } G_RETURN_NULL_IF(PyErr_Occurred()); Py_INCREF(Py_None); return Py_None; } PyDoc_STRVAR(watchdog_remove_watch__doc__, MODULE_NAME ".remove_watch(watch) -> None\n\ Removes a watch from the event loop.\n\n\ :param watch:\n\ The watch to remove.\n"); static PyObject * watchdog_remove_watch(PyObject *self, PyObject *watch) { PyObject *value = PyDict_GetItem(watch_to_stream, watch); PyDict_DelItem(watch_to_stream, watch); #if PY_MAJOR_VERSION >= 3 FSEventStreamRef stream_ref = PyCapsule_GetPointer(value, NULL); #else FSEventStreamRef stream_ref = PyCObject_AsVoidPtr(value); #endif FSEventStreamStop(stream_ref); FSEventStreamInvalidate(stream_ref); FSEventStreamRelease(stream_ref); Py_INCREF(Py_None); return Py_None; } PyDoc_STRVAR(watchdog_stop__doc__, MODULE_NAME ".stop(emitter_thread) -> None\n\ Stops running the event loop from the specified thread.\n\n\ :param emitter_thread:\n\ The thread for which the event loop will be stopped.\n"); static PyObject * watchdog_stop(PyObject *self, PyObject *emitter_thread) { PyObject *value = PyDict_GetItem(thread_to_run_loop, emitter_thread); #if PY_MAJOR_VERSION >= 3 CFRunLoopRef run_loop_ref = PyCapsule_GetPointer(value, NULL); #else CFRunLoopRef run_loop_ref = PyCObject_AsVoidPtr(value); #endif /* Stop the run loop. */ if (G_IS_NOT_NULL(run_loop_ref)) { CFRunLoopStop(run_loop_ref); } Py_INCREF(Py_None); return Py_None; } /****************************************************************************** * Module initialization. *****************************************************************************/ PyDoc_STRVAR(watchdog_fsevents_module__doc__, "Low-level FSEvents Python/C API bridge."); static PyMethodDef watchdog_fsevents_methods[] = { {"add_watch", watchdog_add_watch, METH_VARARGS, watchdog_add_watch__doc__}, {"read_events", watchdog_read_events, METH_VARARGS, watchdog_read_events__doc__}, {"remove_watch", watchdog_remove_watch, METH_O, watchdog_remove_watch__doc__}, /* Aliases for compatibility with macfsevents. */ {"schedule", watchdog_add_watch, METH_VARARGS, "Alias for add_watch."}, {"loop", watchdog_read_events, METH_VARARGS, "Alias for read_events."}, {"unschedule", watchdog_remove_watch, METH_O, "Alias for remove_watch."}, {"stop", watchdog_stop, METH_O, watchdog_stop__doc__}, {NULL}, }; /** * Initialize the module globals. */ static void watchdog_module_init(void) { thread_to_run_loop = PyDict_New(); watch_to_stream = PyDict_New(); } /** * Adds various attributes to the Python module. * * :param module: * A pointer to the Python module object to inject * the attributes into. */ static void watchdog_module_add_attributes(PyObject *module) { PyObject *version_tuple = Py_BuildValue("(iii)", WATCHDOG_VERSION_MAJOR, WATCHDOG_VERSION_MINOR, WATCHDOG_VERSION_BUILD); PyModule_AddIntConstant(module, "POLLIN", kCFFileDescriptorReadCallBack); PyModule_AddIntConstant(module, "POLLOUT", kCFFileDescriptorWriteCallBack); /* Adds version information. */ PyModule_AddObject(module, "__version__", version_tuple); PyModule_AddObject(module, "version_string", Py_BuildValue("s", WATCHDOG_VERSION_STRING)); } #if PY_MAJOR_VERSION < 3 void initwatchdog_fsevents(void); /** * Initialize the Python 2.x module. */ void init_watchdog_fsevents(void) { PyObject *module = Py_InitModule3(MODULE_NAME, watchdog_fsevents_methods, watchdog_fsevents_module__doc__); watchdog_module_add_attributes(module); watchdog_module_init(); } #else /* !PY_MAJOR_VERSION < 3 */ static struct PyModuleDef watchdog_fsevents_module = { PyModuleDef_HEAD_INIT, MODULE_NAME, watchdog_fsevents_module__doc__, -1, watchdog_fsevents_methods }; /** * Initialize the Python 3.x module. */ PyMODINIT_FUNC PyInit__watchdog_fsevents(void){ PyObject *module = PyModule_Create(&watchdog_fsevents_module); watchdog_module_add_attributes(module); watchdog_module_init(); return module; } #endif /* !PY_MAJOR_VERSION < 3 */ watchdog-0.9.0/tests/0000755000175000017500000000000013341331653015222 5ustar danilodanilo00000000000000watchdog-0.9.0/tests/__init__.py0000644000175000017500000000161113341115007017323 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from sys import version_info if version_info < (2, 7): import unittest2 as unittest else: import unittest try: from Queue import Queue # Python 2 except ImportError: from queue import Queue # Python 3 __all__ = ["unittest", "Queue"] watchdog-0.9.0/tests/legacy/0000755000175000017500000000000013341331653016466 5ustar danilodanilo00000000000000watchdog-0.9.0/tests/legacy/test_watch_observers_winapi.py0000644000175000017500000000522413341070440024643 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os from tests import unittest try: import queue # IGNORE:F0401 except ImportError: import Queue as queue # IGNORE:F0401 from time import sleep from tests.shell import ( mkdir, mkdtemp, mv ) from watchdog.events import ( DirCreatedEvent, DirMovedEvent, ) from watchdog.observers.api import ObservedWatch from watchdog.utils import platform if platform.is_windows(): from watchdog.observers.read_directory_changes import WindowsApiEmitter as Emitter temp_dir = mkdtemp() def p(*args): """ Convenience function to join the temporary directory path with the provided arguments. """ return os.path.join(temp_dir, *args) class TestWindowsApiEmitter(unittest.TestCase): def setUp(self): self.event_queue = queue.Queue() self.watch = ObservedWatch(temp_dir, True) self.emitter = Emitter(self.event_queue, self.watch, timeout=0.2) def teardown(self): pass def test___init__(self): SLEEP_TIME = 1 self.emitter.start() sleep(SLEEP_TIME) mkdir(p('fromdir')) sleep(SLEEP_TIME) mv(p('fromdir'), p('todir')) sleep(SLEEP_TIME) self.emitter.stop() # What we need here for the tests to pass is a collection type # that is: # * unordered # * non-unique # A multiset! Python's collections.Counter class seems appropriate. expected = set([ DirCreatedEvent(p('fromdir')), DirMovedEvent(p('fromdir'), p('todir')), ]) got = set() while True: try: event, _ = self.event_queue.get_nowait() got.add(event) except queue.Empty: break print(got) self.assertEqual(expected, got) watchdog-0.9.0/tests/legacy/test_watchdog_events.py0000644000175000017500000004705313341116637023277 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from tests import unittest from watchdog.utils import has_attribute from pathtools.patterns import filter_paths from watchdog.events import ( FileDeletedEvent, FileModifiedEvent, FileCreatedEvent, DirDeletedEvent, DirModifiedEvent, DirCreatedEvent, FileMovedEvent, DirMovedEvent, FileSystemEventHandler, PatternMatchingEventHandler, RegexMatchingEventHandler, LoggingEventHandler, EVENT_TYPE_MODIFIED, EVENT_TYPE_CREATED, EVENT_TYPE_DELETED, EVENT_TYPE_MOVED, ) path_1 = '/path/xyz' path_2 = '/path/abc' class TestFileDeletedEvent(unittest.TestCase): def test___init__(self): event = FileDeletedEvent(path_1) self.assertEqual(path_1, event.src_path) self.assertEqual(EVENT_TYPE_DELETED, event.event_type) self.assertFalse(event.is_directory) # Inherited properties. def test_is_directory(self): event1 = FileDeletedEvent(path_1) self.assertFalse(event1.is_directory) class TestFileModifiedEvent(unittest.TestCase): def test___init__(self): event = FileModifiedEvent(path_1) self.assertEqual(path_1, event.src_path) self.assertEqual(EVENT_TYPE_MODIFIED, event.event_type) self.assertFalse(event.is_directory) # Inherited Properties def test_is_directory(self): event1 = FileModifiedEvent(path_1) self.assertFalse(event1.is_directory) class TestFileCreatedEvent(unittest.TestCase): def test___init__(self): event = FileCreatedEvent(path_1) self.assertEqual(path_1, event.src_path) self.assertEqual(EVENT_TYPE_CREATED, event.event_type) self.assertFalse(event.is_directory) class TestFileMovedEvent(unittest.TestCase): def test___init__(self): event = FileMovedEvent(path_1, path_2) self.assertEqual(path_1, event.src_path) self.assertEqual(path_2, event.dest_path) self.assertEqual(EVENT_TYPE_MOVED, event.event_type) self.assertFalse(event.is_directory) class TestDirDeletedEvent(unittest.TestCase): def test___init__(self): event = DirDeletedEvent(path_1) self.assertEqual(path_1, event.src_path) self.assertEqual(EVENT_TYPE_DELETED, event.event_type) self.assertTrue(event.is_directory) class TestDirModifiedEvent(unittest.TestCase): def test___init__(self): event = DirModifiedEvent(path_1) self.assertEqual(path_1, event.src_path) self.assertEqual(EVENT_TYPE_MODIFIED, event.event_type) self.assertTrue(event.is_directory) class TestDirCreatedEvent(unittest.TestCase): def test___init__(self): event = DirCreatedEvent(path_1) self.assertEqual(path_1, event.src_path) self.assertEqual(EVENT_TYPE_CREATED, event.event_type) self.assertTrue(event.is_directory) class TestFileSystemEventHandler(unittest.TestCase): def test_dispatch(self): dir_del_event = DirDeletedEvent('/path/blah.py') file_del_event = FileDeletedEvent('/path/blah.txt') dir_cre_event = DirCreatedEvent('/path/blah.py') file_cre_event = FileCreatedEvent('/path/blah.txt') dir_mod_event = DirModifiedEvent('/path/blah.py') file_mod_event = FileModifiedEvent('/path/blah.txt') dir_mov_event = DirMovedEvent('/path/blah.py', '/path/blah') file_mov_event = FileMovedEvent('/path/blah.txt', '/path/blah') all_events = [ dir_mod_event, dir_del_event, dir_cre_event, dir_mov_event, file_mod_event, file_del_event, file_cre_event, file_mov_event, ] def assert_equal(a, b): self.assertEqual(a, b) class TestableEventHandler(FileSystemEventHandler): def on_any_event(self, event): assert True def on_modified(self, event): assert_equal(event.event_type, EVENT_TYPE_MODIFIED) def on_deleted(self, event): assert_equal(event.event_type, EVENT_TYPE_DELETED) def on_moved(self, event): assert_equal(event.event_type, EVENT_TYPE_MOVED) def on_created(self, event): assert_equal(event.event_type, EVENT_TYPE_CREATED) handler = TestableEventHandler() for event in all_events: handler.dispatch(event) g_allowed_patterns = ["*.py", "*.txt"] g_ignore_patterns = ["*.foo"] class TestPatternMatchingEventHandler(unittest.TestCase): def test_dispatch(self): # Utilities. patterns = ['*.py', '*.txt'] ignore_patterns = ["*.pyc"] def assert_patterns(event): if has_attribute(event, 'dest_path'): paths = [event.src_path, event.dest_path] else: paths = [event.src_path] filtered_paths = filter_paths(paths, included_patterns=patterns, excluded_patterns=ignore_patterns, case_sensitive=False) self.assertTrue(filtered_paths) dir_del_event_match = DirDeletedEvent('/path/blah.py') dir_del_event_not_match = DirDeletedEvent('/path/foobar') dir_del_event_ignored = DirDeletedEvent('/path/foobar.pyc') file_del_event_match = FileDeletedEvent('/path/blah.txt') file_del_event_not_match = FileDeletedEvent('/path/foobar') file_del_event_ignored = FileDeletedEvent('/path/blah.pyc') dir_cre_event_match = DirCreatedEvent('/path/blah.py') dir_cre_event_not_match = DirCreatedEvent('/path/foobar') dir_cre_event_ignored = DirCreatedEvent('/path/foobar.pyc') file_cre_event_match = FileCreatedEvent('/path/blah.txt') file_cre_event_not_match = FileCreatedEvent('/path/foobar') file_cre_event_ignored = FileCreatedEvent('/path/blah.pyc') dir_mod_event_match = DirModifiedEvent('/path/blah.py') dir_mod_event_not_match = DirModifiedEvent('/path/foobar') dir_mod_event_ignored = DirModifiedEvent('/path/foobar.pyc') file_mod_event_match = FileModifiedEvent('/path/blah.txt') file_mod_event_not_match = FileModifiedEvent('/path/foobar') file_mod_event_ignored = FileModifiedEvent('/path/blah.pyc') dir_mov_event_match = DirMovedEvent('/path/blah.py', '/path/blah') dir_mov_event_not_match = DirMovedEvent('/path/foobar', '/path/blah') dir_mov_event_ignored = DirMovedEvent('/path/foobar.pyc', '/path/blah') file_mov_event_match = FileMovedEvent('/path/blah.txt', '/path/blah') file_mov_event_not_match = FileMovedEvent('/path/foobar', '/path/blah') file_mov_event_ignored = FileMovedEvent('/path/blah.pyc', '/path/blah') all_dir_events = [ dir_mod_event_match, dir_mod_event_not_match, dir_mod_event_ignored, dir_del_event_match, dir_del_event_not_match, dir_del_event_ignored, dir_cre_event_match, dir_cre_event_not_match, dir_cre_event_ignored, dir_mov_event_match, dir_mov_event_not_match, dir_mov_event_ignored, ] all_file_events = [ file_mod_event_match, file_mod_event_not_match, file_mod_event_ignored, file_del_event_match, file_del_event_not_match, file_del_event_ignored, file_cre_event_match, file_cre_event_not_match, file_cre_event_ignored, file_mov_event_match, file_mov_event_not_match, file_mov_event_ignored, ] all_events = all_file_events + all_dir_events def assert_check_directory(handler, event): self.assertFalse(handler.ignore_directories and event.is_directory) def assert_equal(a, b): self.assertEqual(a, b) class TestableEventHandler(PatternMatchingEventHandler): def on_any_event(self, event): assert_check_directory(self, event) def on_modified(self, event): assert_check_directory(self, event) assert_equal(event.event_type, EVENT_TYPE_MODIFIED) assert_patterns(event) def on_deleted(self, event): assert_check_directory(self, event) assert_equal(event.event_type, EVENT_TYPE_DELETED) assert_patterns(event) def on_moved(self, event): assert_check_directory(self, event) assert_equal(event.event_type, EVENT_TYPE_MOVED) assert_patterns(event) def on_created(self, event): assert_check_directory(self, event) assert_equal(event.event_type, EVENT_TYPE_CREATED) assert_patterns(event) no_dirs_handler = TestableEventHandler(patterns=patterns, ignore_patterns=ignore_patterns, ignore_directories=True) handler = TestableEventHandler(patterns=patterns, ignore_patterns=ignore_patterns, ignore_directories=False) for event in all_events: no_dirs_handler.dispatch(event) for event in all_events: handler.dispatch(event) def test___init__(self): handler1 = PatternMatchingEventHandler(g_allowed_patterns, g_ignore_patterns, True) handler2 = PatternMatchingEventHandler(g_allowed_patterns, g_ignore_patterns, False) self.assertEqual(handler1.patterns, g_allowed_patterns) self.assertEqual(handler1.ignore_patterns, g_ignore_patterns) self.assertTrue(handler1.ignore_directories) self.assertFalse(handler2.ignore_directories) def test_ignore_directories(self): handler1 = PatternMatchingEventHandler(g_allowed_patterns, g_ignore_patterns, True) handler2 = PatternMatchingEventHandler(g_allowed_patterns, g_ignore_patterns, False) self.assertTrue(handler1.ignore_directories) self.assertFalse(handler2.ignore_directories) def test_ignore_patterns(self): handler1 = PatternMatchingEventHandler(g_allowed_patterns, g_ignore_patterns, True) self.assertEqual(handler1.ignore_patterns, g_ignore_patterns) def test_patterns(self): handler1 = PatternMatchingEventHandler(g_allowed_patterns, g_ignore_patterns, True) self.assertEqual(handler1.patterns, g_allowed_patterns) g_allowed_regexes = [r".*\.py", r".*\.txt"] g_ignore_regexes = [r".*\.pyc"] class TestRegexMatchingEventHandler(unittest.TestCase): def test_dispatch(self): # Utilities. regexes = [r".*\.py", r".*\.txt"] ignore_regexes = [r".*\.pyc"] def assert_regexes(handler, event): if has_attribute(event, 'dest_path'): paths = [event.src_path, event.dest_path] else: paths = [event.src_path] filtered_paths = set() for p in paths: if any(r.match(p) for r in handler.regexes): filtered_paths.add(p) self.assertTrue(filtered_paths) dir_del_event_match = DirDeletedEvent('/path/blah.py') dir_del_event_not_match = DirDeletedEvent('/path/foobar') dir_del_event_ignored = DirDeletedEvent('/path/foobar.pyc') file_del_event_match = FileDeletedEvent('/path/blah.txt') file_del_event_not_match = FileDeletedEvent('/path/foobar') file_del_event_ignored = FileDeletedEvent('/path/blah.pyc') dir_cre_event_match = DirCreatedEvent('/path/blah.py') dir_cre_event_not_match = DirCreatedEvent('/path/foobar') dir_cre_event_ignored = DirCreatedEvent('/path/foobar.pyc') file_cre_event_match = FileCreatedEvent('/path/blah.txt') file_cre_event_not_match = FileCreatedEvent('/path/foobar') file_cre_event_ignored = FileCreatedEvent('/path/blah.pyc') dir_mod_event_match = DirModifiedEvent('/path/blah.py') dir_mod_event_not_match = DirModifiedEvent('/path/foobar') dir_mod_event_ignored = DirModifiedEvent('/path/foobar.pyc') file_mod_event_match = FileModifiedEvent('/path/blah.txt') file_mod_event_not_match = FileModifiedEvent('/path/foobar') file_mod_event_ignored = FileModifiedEvent('/path/blah.pyc') dir_mov_event_match = DirMovedEvent('/path/blah.py', '/path/blah') dir_mov_event_not_match = DirMovedEvent('/path/foobar', '/path/blah') dir_mov_event_ignored = DirMovedEvent('/path/foobar.pyc', '/path/blah') file_mov_event_match = FileMovedEvent('/path/blah.txt', '/path/blah') file_mov_event_not_match = FileMovedEvent('/path/foobar', '/path/blah') file_mov_event_ignored = FileMovedEvent('/path/blah.pyc', '/path/blah') all_dir_events = [ dir_mod_event_match, dir_mod_event_not_match, dir_mod_event_ignored, dir_del_event_match, dir_del_event_not_match, dir_del_event_ignored, dir_cre_event_match, dir_cre_event_not_match, dir_cre_event_ignored, dir_mov_event_match, dir_mov_event_not_match, dir_mov_event_ignored, ] all_file_events = [ file_mod_event_match, file_mod_event_not_match, file_mod_event_ignored, file_del_event_match, file_del_event_not_match, file_del_event_ignored, file_cre_event_match, file_cre_event_not_match, file_cre_event_ignored, file_mov_event_match, file_mov_event_not_match, file_mov_event_ignored, ] all_events = all_file_events + all_dir_events def assert_check_directory(handler, event): self.assertFalse(handler.ignore_directories and event.is_directory) def assert_equal(a, b): self.assertEqual(a, b) class TestableEventHandler(RegexMatchingEventHandler): def on_any_event(self, event): assert_check_directory(self, event) def on_modified(self, event): assert_check_directory(self, event) assert_equal(event.event_type, EVENT_TYPE_MODIFIED) assert_regexes(self, event) def on_deleted(self, event): assert_check_directory(self, event) assert_equal(event.event_type, EVENT_TYPE_DELETED) assert_regexes(self, event) def on_moved(self, event): assert_check_directory(self, event) assert_equal(event.event_type, EVENT_TYPE_MOVED) assert_regexes(self, event) def on_created(self, event): assert_check_directory(self, event) assert_equal(event.event_type, EVENT_TYPE_CREATED) assert_regexes(self, event) no_dirs_handler = TestableEventHandler(regexes=regexes, ignore_regexes=ignore_regexes, ignore_directories=True) handler = TestableEventHandler(regexes=regexes, ignore_regexes=ignore_regexes, ignore_directories=False) for event in all_events: no_dirs_handler.dispatch(event) for event in all_events: handler.dispatch(event) def test___init__(self): handler1 = RegexMatchingEventHandler(g_allowed_regexes, g_ignore_regexes, True) handler2 = RegexMatchingEventHandler(g_allowed_regexes, g_ignore_regexes, False) self.assertEqual([r.pattern for r in handler1.regexes], g_allowed_regexes) self.assertEqual([r.pattern for r in handler1.ignore_regexes], g_ignore_regexes) self.assertTrue(handler1.ignore_directories) self.assertFalse(handler2.ignore_directories) def test_ignore_directories(self): handler1 = RegexMatchingEventHandler(g_allowed_regexes, g_ignore_regexes, True) handler2 = RegexMatchingEventHandler(g_allowed_regexes, g_ignore_regexes, False) self.assertTrue(handler1.ignore_directories) self.assertFalse(handler2.ignore_directories) def test_ignore_regexes(self): handler1 = RegexMatchingEventHandler(g_allowed_regexes, g_ignore_regexes, True) self.assertEqual([r.pattern for r in handler1.ignore_regexes], g_ignore_regexes) def test_regexes(self): handler1 = RegexMatchingEventHandler(g_allowed_regexes, g_ignore_regexes, True) self.assertEqual([r.pattern for r in handler1.regexes], g_allowed_regexes) class _TestableEventHandler(LoggingEventHandler): def on_any_event(self, event): assert True def on_modified(self, event): super(_TestableEventHandler, self).on_modified(event) assert event.event_type == EVENT_TYPE_MODIFIED def on_deleted(self, event): super(_TestableEventHandler, self).on_deleted(event) assert event.event_type == EVENT_TYPE_DELETED def on_moved(self, event): super(_TestableEventHandler, self).on_moved(event) assert event.event_type == EVENT_TYPE_MOVED def on_created(self, event): super(_TestableEventHandler, self).on_created(event) assert event.event_type == EVENT_TYPE_CREATED class TestLoggingEventHandler(unittest.TestCase): def test_dispatch(self): # Utilities. dir_del_event = DirDeletedEvent('/path/blah.py') file_del_event = FileDeletedEvent('/path/blah.txt') dir_cre_event = DirCreatedEvent('/path/blah.py') file_cre_event = FileCreatedEvent('/path/blah.txt') dir_mod_event = DirModifiedEvent('/path/blah.py') file_mod_event = FileModifiedEvent('/path/blah.txt') dir_mov_event = DirMovedEvent('/path/blah.py', '/path/blah') file_mov_event = FileMovedEvent('/path/blah.txt', '/path/blah') all_events = [ dir_mod_event, dir_del_event, dir_cre_event, dir_mov_event, file_mod_event, file_del_event, file_cre_event, file_mov_event, ] handler = _TestableEventHandler() for event in all_events: handler.dispatch(event) watchdog-0.9.0/tests/legacy/test_watchdog_observers_api.py0000644000175000017500000000677613341070440024634 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import time from tests import unittest from watchdog.observers.api import ( BaseObserver, EventEmitter, ObservedWatch, EventDispatcher, EventQueue ) from watchdog.events import LoggingEventHandler, FileModifiedEvent class TestObservedWatch(unittest.TestCase): def test___eq__(self): watch1 = ObservedWatch('/foobar', True) watch2 = ObservedWatch('/foobar', True) watch_ne1 = ObservedWatch('/foo', True) watch_ne2 = ObservedWatch('/foobar', False) self.assertTrue(watch1.__eq__(watch2)) self.assertFalse(watch1.__eq__(watch_ne1)) self.assertFalse(watch1.__eq__(watch_ne2)) def test___ne__(self): watch1 = ObservedWatch('/foobar', True) watch2 = ObservedWatch('/foobar', True) watch_ne1 = ObservedWatch('/foo', True) watch_ne2 = ObservedWatch('/foobar', False) self.assertFalse(watch1.__ne__(watch2)) self.assertTrue(watch1.__ne__(watch_ne1)) self.assertTrue(watch1.__ne__(watch_ne2)) def test___repr__(self): observed_watch = ObservedWatch('/foobar', True) self.assertEqual('', observed_watch.__repr__()) class TestEventEmitter(unittest.TestCase): def test___init__(self): event_queue = EventQueue() watch = ObservedWatch('/foobar', True) event_emitter = EventEmitter(event_queue, watch, timeout=1) event_emitter.queue_event(FileModifiedEvent('/foobar/blah')) class TestEventDispatcher(unittest.TestCase): def test_dispatch_event(self): event = FileModifiedEvent('/foobar') watch = ObservedWatch('/path', True) class TestableEventDispatcher(EventDispatcher): def dispatch_event(self, event, watch): assert True event_dispatcher = TestableEventDispatcher() event_dispatcher.event_queue.put((event, watch)) event_dispatcher.start() time.sleep(1) event_dispatcher.stop() class TestBaseObserver(unittest.TestCase): def test_basic(self): observer = BaseObserver(EventEmitter) handler = LoggingEventHandler() watch = observer.schedule(handler, '/foobar', True) observer.add_handler_for_watch(handler, watch) observer.add_handler_for_watch(handler, watch) observer.remove_handler_for_watch(handler, watch) self.assertRaises(KeyError, observer.remove_handler_for_watch, handler, watch) observer.unschedule(watch) self.assertRaises(KeyError, observer.unschedule, watch) watch = observer.schedule(handler, '/foobar', True) observer.event_queue.put((FileModifiedEvent('/foobar'), watch)) observer.start() time.sleep(1) observer.unschedule_all() observer.stop() watchdog-0.9.0/tests/legacy/test_watchdog_observers_polling.py0000644000175000017500000001100413341111617025505 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os from tests import unittest try: import queue # IGNORE:F0401 except ImportError: import Queue as queue # IGNORE:F0401 from time import sleep from tests.shell import ( mkdir, mkdtemp, touch, rm, mv ) from watchdog.events import ( DirModifiedEvent, DirCreatedEvent, FileCreatedEvent, FileMovedEvent, FileModifiedEvent, DirMovedEvent, FileDeletedEvent, DirDeletedEvent ) from watchdog.observers.api import ObservedWatch from watchdog.observers.polling import PollingEmitter as Emitter temp_dir = mkdtemp() def p(*args): """ Convenience function to join the temporary directory path with the provided arguments. """ return os.path.join(temp_dir, *args) class TestPollingEmitter(unittest.TestCase): def setUp(self): self.event_queue = queue.Queue() self.watch = ObservedWatch(temp_dir, True) self.emitter = Emitter(self.event_queue, self.watch, timeout=0.2) def teardown(self): pass def test___init__(self): SLEEP_TIME = 0.4 self.emitter.start() sleep(SLEEP_TIME) mkdir(p('project')) sleep(SLEEP_TIME) mkdir(p('project', 'blah')) sleep(SLEEP_TIME) touch(p('afile')) sleep(SLEEP_TIME) touch(p('fromfile')) sleep(SLEEP_TIME) mv(p('fromfile'), p('project', 'tofile')) sleep(SLEEP_TIME) touch(p('afile')) sleep(SLEEP_TIME) mv(p('project', 'blah'), p('project', 'boo')) sleep(SLEEP_TIME) rm(p('project'), recursive=True) sleep(SLEEP_TIME) rm(p('afile')) sleep(SLEEP_TIME) self.emitter.stop() # What we need here for the tests to pass is a collection type # that is: # * unordered # * non-unique # A multiset! Python's collections.Counter class seems appropriate. expected = set( [ DirModifiedEvent(p()), DirCreatedEvent(p('project')), DirModifiedEvent(p('project')), DirCreatedEvent(p('project', 'blah')), FileCreatedEvent(p('afile')), DirModifiedEvent(p()), FileCreatedEvent(p('fromfile')), DirModifiedEvent(p()), DirModifiedEvent(p()), FileModifiedEvent(p('afile')), DirModifiedEvent(p('project')), DirModifiedEvent(p()), FileDeletedEvent(p('project', 'tofile')), DirDeletedEvent(p('project', 'boo')), DirDeletedEvent(p('project')), DirModifiedEvent(p()), FileDeletedEvent(p('afile')), ] ) expected.add(FileMovedEvent(p('fromfile'), p('project', 'tofile'))) expected.add(DirMovedEvent(p('project', 'blah'), p('project', 'boo'))) got = set() while True: try: event, _ = self.event_queue.get_nowait() got.add(event) except queue.Empty: break self.assertEqual(expected, got) def test_delete_watched_dir(self): SLEEP_TIME = 0.4 self.emitter.start() rm(p(''), recursive=True) sleep(SLEEP_TIME) self.emitter.stop() # What we need here for the tests to pass is a collection type # that is: # * unordered # * non-unique # A multiset! Python's collections.Counter class seems appropriate. expected = set( [ DirDeletedEvent(os.path.dirname(p(''))), ] ) got = set() while True: try: event, _ = self.event_queue.get_nowait() got.add(event) except queue.Empty: break self.assertEqual(expected, got) watchdog-0.9.0/tests/legacy/utils.py0000644000175000017500000000221013341070440020165 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import with_statement import collections def list_attributes(o, only_public=True): if only_public: def isattribute(o, attribute): return not (attribute.startswith('_') or isinstance(getattr(o, attribute), collections.Callable)) else: def isattribute(o, attribute): return not isinstance(getattr(o, attribute), collections.Callable) return [attribute for attribute in dir(o) if isattribute(o, attribute)] watchdog-0.9.0/tests/shell.py0000644000175000017500000000511113341121040016664 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ :module: tests.shell :synopsis: Common shell operations for testing. :author: yesudeep@google.com (Yesudeep Mangalapilly) """ from __future__ import with_statement import os.path import tempfile import shutil import errno # def tree(path='.', show_files=False): # print(path) # padding = '' # for root, directories, filenames in os.walk(path): # print(padding + os.path.basename(root) + os.path.sep) # padding = padding + ' ' # for filename in filenames: # print(padding + filename) def cd(path): os.chdir(path) def pwd(): path = os.getcwd() print(path) return path def mkdir(path, parents=False): """Creates a directory (optionally also creates all the parent directories in the path).""" if parents: try: os.makedirs(path) except OSError as e: if not e.errno == errno.EEXIST: raise else: os.mkdir(path) def rm(path, recursive=False): """Deletes files or directories.""" if os.path.isdir(path): if recursive: shutil.rmtree(path) # else: # os.rmdir(path) else: raise OSError("rm: %s: is a directory." % path) else: os.remove(path) def touch(path, times=None): """Updates the modified timestamp of a file or directory.""" if os.path.isdir(path): os.utime(path, times) else: with open(path, 'ab'): os.utime(path, times) def truncate(path): """Truncates a file.""" with open(path, 'wb'): os.utime(path, None) def mv(src_path, dest_path): """Moves files or directories.""" try: os.rename(src_path, dest_path) except OSError: # this will happen on windows os.remove(dest_path) os.rename(src_path, dest_path) def mkdtemp(): return tempfile.mkdtemp() def ls(path='.'): return os.listdir(path) watchdog-0.9.0/tests/test_delayed_queue.py0000644000175000017500000000153513341070440021444 0ustar danilodanilo00000000000000# -*- coding: utf-8 -*- # # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from time import time from watchdog.utils.delayed_queue import DelayedQueue def test_get(): q = DelayedQueue(2) q.put("") inserted = time() q.get() elapsed = time() - inserted assert 2.01 > elapsed > 1.99 watchdog-0.9.0/tests/test_emitter.py0000644000175000017500000001655413341116020020304 0ustar danilodanilo00000000000000# -*- coding: utf-8 -*- # # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import unicode_literals import os import time import pytest import logging from tests import Queue from functools import partial from .shell import mkdir, touch, mv, rm, mkdtemp from watchdog.utils import platform from watchdog.utils.unicode_paths import str_cls from watchdog.events import ( FileDeletedEvent, FileModifiedEvent, FileCreatedEvent, FileMovedEvent, DirDeletedEvent, DirModifiedEvent, DirCreatedEvent, ) from watchdog.observers.api import ObservedWatch pytestmark = pytest.mark.skipif(not platform.is_linux() and not platform.is_darwin(), reason="") if platform.is_linux(): from watchdog.observers.inotify import InotifyEmitter as Emitter elif platform.is_darwin(): from watchdog.observers.fsevents2 import FSEventsEmitter as Emitter from watchdog.observers.inotify import InotifyFullEmitter logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) def setup_function(function): global p, event_queue tmpdir = os.path.realpath(mkdtemp()) p = partial(os.path.join, tmpdir) event_queue = Queue() def start_watching(path=None, use_full_emitter=False): path = p('') if path is None else path global emitter if platform.is_linux() and use_full_emitter: emitter = InotifyFullEmitter(event_queue, ObservedWatch(path, recursive=True)) else: emitter = Emitter(event_queue, ObservedWatch(path, recursive=True)) if platform.is_darwin(): # FSEvents will report old evens (like create for mkdtemp in test # setup. Waiting for a considerable time seems to 'flush' the events. time.sleep(10) emitter.start() def teardown_function(function): emitter.stop() emitter.join(5) rm(p(''), recursive=True) assert not emitter.is_alive() def test_create(): start_watching() open(p('a'), 'a').close() event = event_queue.get(timeout=5)[0] assert event.src_path == p('a') assert isinstance(event, FileCreatedEvent) event = event_queue.get(timeout=5)[0] assert os.path.normpath(event.src_path) == os.path.normpath(p('')) assert isinstance(event, DirModifiedEvent) def test_delete(): touch(p('a')) start_watching() rm(p('a')) event = event_queue.get(timeout=5)[0] assert event.src_path == p('a') assert isinstance(event, FileDeletedEvent) event = event_queue.get(timeout=5)[0] assert os.path.normpath(event.src_path) == os.path.normpath(p('')) assert isinstance(event, DirModifiedEvent) def test_modify(): touch(p('a')) start_watching() touch(p('a')) event = event_queue.get(timeout=5)[0] assert event.src_path == p('a') assert isinstance(event, FileModifiedEvent) def test_move(): mkdir(p('dir1')) mkdir(p('dir2')) touch(p('dir1', 'a')) start_watching() mv(p('dir1', 'a'), p('dir2', 'b')) event = event_queue.get(timeout=5)[0] assert event.src_path == p('dir1', 'a') assert event.dest_path == p('dir2', 'b') assert isinstance(event, FileMovedEvent) event = event_queue.get(timeout=5)[0] assert event.src_path == p('dir1') assert isinstance(event, DirModifiedEvent) event = event_queue.get(timeout=5)[0] assert event.src_path == p('dir2') assert isinstance(event, DirModifiedEvent) def test_move_to(): mkdir(p('dir1')) mkdir(p('dir2')) touch(p('dir1', 'a')) start_watching(p('dir2')) mv(p('dir1', 'a'), p('dir2', 'b')) event = event_queue.get(timeout=5)[0] assert isinstance(event, FileCreatedEvent) assert event.src_path == p('dir2', 'b') def test_move_to_full(): mkdir(p('dir1')) mkdir(p('dir2')) touch(p('dir1', 'a')) start_watching(p('dir2'), use_full_emitter=True) mv(p('dir1', 'a'), p('dir2', 'b')) event = event_queue.get(timeout=5)[0] assert isinstance(event, FileMovedEvent) assert event.dest_path == p('dir2', 'b') assert event.src_path == None #Should equal none since the path was not watched def test_move_from(): mkdir(p('dir1')) mkdir(p('dir2')) touch(p('dir1', 'a')) start_watching(p('dir1')) mv(p('dir1', 'a'), p('dir2', 'b')) event = event_queue.get(timeout=5)[0] assert isinstance(event, FileDeletedEvent) assert event.src_path == p('dir1', 'a') def test_move_from_full(): mkdir(p('dir1')) mkdir(p('dir2')) touch(p('dir1', 'a')) start_watching(p('dir1'), use_full_emitter=True) mv(p('dir1', 'a'), p('dir2', 'b')) event = event_queue.get(timeout=5)[0] assert isinstance(event, FileMovedEvent) assert event.src_path == p('dir1', 'a') assert event.dest_path == None #Should equal None since path not watched def test_separate_consecutive_moves(): mkdir(p('dir1')) touch(p('dir1', 'a')) touch(p('b')) start_watching(p('dir1')) mv(p('dir1', 'a'), p('c')) mv(p('b'), p('dir1', 'd')) event = event_queue.get(timeout=5)[0] assert isinstance(event, FileDeletedEvent) assert event.src_path == p('dir1', 'a') assert isinstance(event_queue.get(timeout=5)[0], DirModifiedEvent) event = event_queue.get(timeout=5)[0] assert isinstance(event, FileCreatedEvent) assert event.src_path == p('dir1', 'd') assert isinstance(event_queue.get(timeout=5)[0], DirModifiedEvent) @pytest.mark.skipif(platform.is_linux(), reason="bug. inotify will deadlock") def test_delete_self(): mkdir(p('dir1')) start_watching(p('dir1')) rm(p('dir1'), True) event_queue.get(timeout=5)[0] def test_fast_subdirectory_creation_deletion(): root_dir = p('dir1') sub_dir = p('dir1', 'subdir1') times = 30 mkdir(root_dir) start_watching(root_dir) for unused in range(times): mkdir(sub_dir) rm(sub_dir, True) count = {DirCreatedEvent: 0, DirModifiedEvent: 0, DirDeletedEvent: 0} etype_for_dir = {DirCreatedEvent: sub_dir, DirModifiedEvent: root_dir, DirDeletedEvent: sub_dir} for unused in range(times * 4): event = event_queue.get(timeout=5)[0] logger.debug(event) etype = type(event) count[etype] += 1 assert event.src_path == etype_for_dir[etype] assert count[DirCreatedEvent] >= count[DirDeletedEvent] assert count[DirCreatedEvent] + count[DirDeletedEvent] >= count[DirModifiedEvent] assert count == {DirCreatedEvent: times, DirModifiedEvent: times * 2, DirDeletedEvent: times} def test_passing_unicode_should_give_unicode(): start_watching(p('')) touch(p('a')) event = event_queue.get(timeout=5)[0] assert isinstance(event.src_path, str_cls) def test_passing_bytes_should_give_bytes(): start_watching(p('').encode()) touch(p('a')) event = event_queue.get(timeout=5)[0] assert isinstance(event.src_path, bytes)watchdog-0.9.0/tests/test_inotify_buffer.py0000644000175000017500000000652413341116024021645 0ustar danilodanilo00000000000000# -*- coding: utf-8 -*- # # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import unicode_literals import os import random import pytest from .shell import mkdir, touch, mv, rm from watchdog.utils import platform pytestmark = pytest.mark.skipif(not platform.is_linux(), reason="") if platform.is_linux(): from watchdog.observers.inotify_buffer import InotifyBuffer def wait_for_move_event(read_event): while True: event = read_event() if isinstance(event, tuple) or event.is_move: return event @pytest.mark.timeout(5) def test_move_from(p): mkdir(p('dir1')) mkdir(p('dir2')) touch(p('dir1', 'a')) inotify = InotifyBuffer(p('dir1').encode()) mv(p('dir1', 'a'), p('dir2', 'b')) event = wait_for_move_event(inotify.read_event) assert event.is_moved_from assert event.src_path == p('dir1', 'a').encode() inotify.close() @pytest.mark.timeout(5) def test_move_to(p): mkdir(p('dir1')) mkdir(p('dir2')) touch(p('dir1', 'a')) inotify = InotifyBuffer(p('dir2').encode()) mv(p('dir1', 'a'), p('dir2', 'b')) event = wait_for_move_event(inotify.read_event) assert event.is_moved_to assert event.src_path == p('dir2', 'b').encode() inotify.close() @pytest.mark.timeout(5) def test_move_internal(p): mkdir(p('dir1')) mkdir(p('dir2')) touch(p('dir1', 'a')) inotify = InotifyBuffer(p('').encode(), recursive=True) mv(p('dir1', 'a'), p('dir2', 'b')) frm, to = wait_for_move_event(inotify.read_event) assert frm.src_path == p('dir1', 'a').encode() assert to.src_path == p('dir2', 'b').encode() inotify.close() @pytest.mark.timeout(10) def test_move_internal_batch(p): n = 100 mkdir(p('dir1')) mkdir(p('dir2')) files = ['%d' % i for i in range(n)] for f in files: touch(p('dir1', f)) inotify = InotifyBuffer(p('').encode(), recursive=True) random.shuffle(files) for f in files: mv(p('dir1', f), p('dir2', f)) # Check that all n events are paired i = 0 while i < n: frm, to = wait_for_move_event(inotify.read_event) assert os.path.dirname(frm.src_path).endswith(b'/dir1') assert os.path.dirname(to.src_path).endswith(b'/dir2') assert frm.name == to.name i += 1 inotify.close() @pytest.mark.timeout(5) def test_delete_watched_directory(p): mkdir(p('dir')) inotify = InotifyBuffer(p('dir').encode()) rm(p('dir'), recursive=True) # Wait for the event to be picked up inotify.read_event() # Ensure InotifyBuffer shuts down cleanly without raising an exception inotify.close() def test_close_should_terminate_thread(p): inotify = InotifyBuffer(p('').encode(), recursive=True) assert inotify.is_alive() inotify.close() assert not inotify.is_alive() watchdog-0.9.0/tests/test_inotify_c.py0000644000175000017500000000764013341070440020617 0ustar danilodanilo00000000000000from __future__ import unicode_literals import os import pytest import logging import contextlib from tests import Queue from functools import partial from .shell import rm, mkdtemp from watchdog.utils import platform from watchdog.events import DirCreatedEvent, DirDeletedEvent, DirModifiedEvent from watchdog.observers.api import ObservedWatch if platform.is_linux(): from watchdog.observers.inotify import InotifyFullEmitter, InotifyEmitter logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) def setup_function(function): global p, event_queue tmpdir = os.path.realpath(mkdtemp()) p = partial(os.path.join, tmpdir) event_queue = Queue() @contextlib.contextmanager def watching(path=None, use_full_emitter=False): path = p('') if path is None else path global emitter Emitter = InotifyFullEmitter if use_full_emitter else InotifyEmitter emitter = Emitter(event_queue, ObservedWatch(path, recursive=True)) emitter.start() yield emitter.stop() emitter.join(5) def teardown_function(function): rm(p(''), recursive=True) assert not emitter.is_alive() @pytest.mark.skipif(not platform.is_linux(), reason="Testing with inotify messages (Linux only)") def test_late_double_deletion(monkeypatch): inotify_fd = type(str("FD"), (object,), {})() # Empty object inotify_fd.last = 0 inotify_fd.wds = [] # CREATE DELETE CREATE DELETE DELETE_SELF IGNORE DELETE_SELF IGNORE inotify_fd.buf = ( # IN_CREATE|IS_DIR (wd = 1, path = subdir1) b"\x01\x00\x00\x00\x00\x01\x00\x40\x00\x00\x00\x00\x10\x00\x00\x00" b"\x73\x75\x62\x64\x69\x72\x31\x00\x00\x00\x00\x00\x00\x00\x00\x00" # IN_DELETE|IS_DIR (wd = 1, path = subdir1) b"\x01\x00\x00\x00\x00\x02\x00\x40\x00\x00\x00\x00\x10\x00\x00\x00" b"\x73\x75\x62\x64\x69\x72\x31\x00\x00\x00\x00\x00\x00\x00\x00\x00" ) * 2 + ( # IN_DELETE_SELF (wd = 2) b"\x02\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # IN_IGNORE (wd = 2) b"\x02\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # IN_DELETE_SELF (wd = 3) b"\x03\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # IN_IGNORE (wd = 3) b"\x03\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" ) os_read_bkp = os.read def fakeread(fd, length): if fd is inotify_fd: result, fd.buf = fd.buf[:length], fd.buf[length:] return result return os_read_bkp(fd, length) os_close_bkp = os.close def fakeclose(fd): if fd is not inotify_fd: os_close_bkp(fd) def inotify_init(): return inotify_fd def inotify_add_watch(fd, path, mask): fd.last += 1 logger.debug("New wd = %d" % fd.last) fd.wds.append(fd.last) return fd.last def inotify_rm_watch(fd, wd): logger.debug("Removing wd = %d" % wd) fd.wds.remove(wd) return 0 # Mocks the API! from watchdog.observers import inotify_c monkeypatch.setattr(os, "read", fakeread) monkeypatch.setattr(os, "close", fakeclose) monkeypatch.setattr(inotify_c, "inotify_init", inotify_init) monkeypatch.setattr(inotify_c, "inotify_add_watch", inotify_add_watch) monkeypatch.setattr(inotify_c, "inotify_rm_watch", inotify_rm_watch) with watching(p('')): # Watchdog Events for evt_cls in [DirCreatedEvent, DirDeletedEvent] * 2: event = event_queue.get(timeout=5)[0] assert isinstance(event, evt_cls) assert event.src_path == p('subdir1') event = event_queue.get(timeout=5)[0] assert isinstance(event, DirModifiedEvent) assert event.src_path == p('').rstrip(os.path.sep) assert inotify_fd.last == 3 # Number of directories assert inotify_fd.buf == b"" # Didn't miss any event assert inotify_fd.wds == [2, 3] # Only 1 is removed explicitly watchdog-0.9.0/tests/test_observer.py0000644000175000017500000000472113341070440020460 0ustar danilodanilo00000000000000# -*- coding: utf-8 -*- # # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import unicode_literals import pytest from watchdog.events import FileSystemEventHandler, FileModifiedEvent from watchdog.utils.compat import Event from watchdog.observers.api import EventEmitter, BaseObserver @pytest.fixture() def observer(request): observer = BaseObserver(EventEmitter) def finalizer(): try: observer.stop() except: pass request.addfinalizer(finalizer) return observer def test_schedule_should_start_emitter_if_running(observer): observer.start() observer.schedule(None, '') (emitter,) = observer.emitters assert emitter.is_alive() def test_schedule_should_not_start_emitter_if_not_running(observer): observer.schedule(None, '') (emitter,) = observer.emitters assert not emitter.is_alive() def test_start_should_start_emitter(observer): observer.schedule(None, '') observer.start() (emitter,) = observer.emitters assert emitter.is_alive() def test_stop_should_stop_emitter(observer): observer.schedule(None, '') observer.start() (emitter,) = observer.emitters assert emitter.is_alive() observer.stop() observer.join() assert not observer.is_alive() assert not emitter.is_alive() def test_unschedule_self(observer): """ Tests that unscheduling a watch from within an event handler correctly correctly unregisters emitter and handler without deadlocking. """ class EventHandler(FileSystemEventHandler): def on_modified(self, event): observer.unschedule(watch) unschedule_finished.set() unschedule_finished = Event() watch = observer.schedule(EventHandler(), '') observer.start() (emitter,) = observer.emitters emitter.queue_event(FileModifiedEvent('')) assert unschedule_finished.wait() assert len(observer.emitters) == 0 watchdog-0.9.0/tests/test_skip_repeats_queue.py0000644000175000017500000000442213341070440022524 0ustar danilodanilo00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011 Yesudeep Mangalapilly # Copyright 2012 Google, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from tests import unittest from watchdog.utils.bricks import SkipRepeatsQueue class TestSkipRepeatsQueue(unittest.TestCase): def test_basic_queue(self): q = SkipRepeatsQueue() e1 = (2, 'fred') e2 = (2, 'george') e3 = (4, 'sally') q.put(e1) q.put(e2) q.put(e3) self.assertEqual(e1, q.get()) self.assertEqual(e2, q.get()) self.assertEqual(e3, q.get()) self.assertTrue(q.empty()) def test_allow_nonconsecutive(self): q = SkipRepeatsQueue() e1 = (2, 'fred') e2 = (2, 'george') q.put(e1) q.put(e2) q.put(e1) # repeat the first entry self.assertEqual(e1, q.get()) self.assertEqual(e2, q.get()) self.assertEqual(e1, q.get()) self.assertTrue(q.empty()) def test_prevent_consecutive(self): q = SkipRepeatsQueue() e1 = (2, 'fred') e2 = (2, 'george') q.put(e1) q.put(e1) # repeat the first entry (this shouldn't get added) q.put(e2) self.assertEqual(e1, q.get()) self.assertEqual(e2, q.get()) self.assertTrue(q.empty()) def test_consecutives_allowed_across_empties(self): q = SkipRepeatsQueue() e1 = (2, 'fred') q.put(e1) q.put(e1) # repeat the first entry (this shouldn't get added) self.assertEqual(e1, q.get()) self.assertTrue(q.empty()) q.put(e1) # this repeat is allowed because 'last' added is now gone from queue self.assertEqual(e1, q.get()) self.assertTrue(q.empty()) watchdog-0.9.0/tests/test_snapshot_diff.py0000644000175000017500000000640613341116571021470 0ustar danilodanilo00000000000000# -*- coding: utf-8 -*- # # Copyright 2014 Thomas Amland # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import time from .shell import mkdir, touch, mv from watchdog.utils.dirsnapshot import DirectorySnapshot from watchdog.utils.dirsnapshot import DirectorySnapshotDiff from watchdog.utils import platform def wait(): """ Wait long enough for file/folder mtime to change. This is needed to be able to detected modifications. """ if platform.is_darwin() or platform.is_windows(): # on osx resolution of stat.mtime is only 1 second time.sleep(1.5) else: time.sleep(0.5) def test_move_to(p): mkdir(p('dir1')) mkdir(p('dir2')) touch(p('dir1', 'a')) ref = DirectorySnapshot(p('dir2')) mv(p('dir1', 'a'), p('dir2', 'b')) diff = DirectorySnapshotDiff(ref, DirectorySnapshot(p('dir2'))) assert diff.files_created == [p('dir2', 'b')] def test_move_from(p): mkdir(p('dir1')) mkdir(p('dir2')) touch(p('dir1', 'a')) ref = DirectorySnapshot(p('dir1')) mv(p('dir1', 'a'), p('dir2', 'b')) diff = DirectorySnapshotDiff(ref, DirectorySnapshot(p('dir1'))) assert diff.files_deleted == [p('dir1', 'a')] def test_move_internal(p): mkdir(p('dir1')) mkdir(p('dir2')) touch(p('dir1', 'a')) ref = DirectorySnapshot(p('')) mv(p('dir1', 'a'), p('dir2', 'b')) diff = DirectorySnapshotDiff(ref, DirectorySnapshot(p(''))) assert diff.files_moved == [(p('dir1', 'a'), p('dir2', 'b'))] assert diff.files_created == [] assert diff.files_deleted == [] def test_move_replace(p): mkdir(p('dir1')) mkdir(p('dir2')) touch(p('dir1', 'a')) touch(p('dir2', 'b')) ref = DirectorySnapshot(p('')) mv(p('dir1', 'a'), p('dir2', 'b')) diff = DirectorySnapshotDiff(ref, DirectorySnapshot(p(''))) assert diff.files_moved == [(p('dir1', 'a'), p('dir2', 'b'))] assert diff.files_deleted == [p('dir2', 'b')] assert diff.files_created == [] def test_dir_modify_on_create(p): ref = DirectorySnapshot(p('')) wait() touch(p('a')) diff = DirectorySnapshotDiff(ref, DirectorySnapshot(p(''))) assert diff.dirs_modified == [p('')] def test_dir_modify_on_move(p): mkdir(p('dir1')) mkdir(p('dir2')) touch(p('dir1', 'a')) ref = DirectorySnapshot(p('')) wait() mv(p('dir1', 'a'), p('dir2', 'b')) diff = DirectorySnapshotDiff(ref, DirectorySnapshot(p(''))) assert set(diff.dirs_modified) == set([p('dir1'), p('dir2')]) def test_detect_modify_for_moved_files(p): touch(p('a')) ref = DirectorySnapshot(p('')) wait() touch(p('a')) mv(p('a'), p('b')) diff = DirectorySnapshotDiff(ref, DirectorySnapshot(p(''))) assert diff.files_moved == [(p('a'), p('b'))] assert diff.files_modified == [p('a')]