././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1643211004.7548294 ftputil-5.0.3/0000775000175000017500000000000000000000000012205 5ustar00schwaschwa././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7849648 ftputil-5.0.3/LICENSE0000664000175000017500000002601600000000000013217 0ustar00schwaschwaLICENSE ======= All the software in the ftpuil distribution is covered by the modified BSD license. While ftputil has mainly been written by Stefan Schwarzer , others have also contributed suggestions and code. In particular, Evan Prodromou has contributed his lrucache module which is covered by both the modified BSD license and the Academic Free License. Modified BSD license (3-clause): ---------------------------------------------------------------------- Copyright (C) 2002-2020, Stefan Schwarzer and contributors (see `doc/contributors.txt`) 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 above author nor the names of the contributors to the software 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 AUTHOR 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. ---------------------------------------------------------------------- The Academic Free License: ---------------------------------------------------------------------- The Academic Free License v. 2.1 This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following notice immediately following the copyright notice for the Original Work: Licensed under the Academic Free License version 2.1 1) Grant of Copyright License. Licensor hereby grants You a world-wide, royalty-free, non-exclusive, perpetual, sublicenseable license to do the following: a) to reproduce the Original Work in copies; b) to prepare derivative works ("Derivative Works") based upon the Original Work; c) to distribute copies of the Original Work and Derivative Works to the public; d) to perform the Original Work publicly; and e) to display the Original Work publicly. 2) Grant of Patent License. Licensor hereby grants You a world-wide, royalty-free, non-exclusive, perpetual, sublicenseable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, to make, use, sell and offer for sale the Original Work and Derivative Works. 3) Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor hereby agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work, and by publishing the address of that information repository in a notice immediately following the copyright notice that applies to the Original Work. 4) Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior written permission of the Licensor. Nothing in this License shall be deemed to grant any rights to trademarks, copyrights, patents, trade secrets or any other intellectual property of Licensor except as expressly stated herein. No patent license is granted to make, use, sell or offer to sell embodiments of any patent claims other than the licensed claims defined in Section 2. No right is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under different terms from this License any Original Work that Licensor otherwise would have a right to license. 5) This section intentionally omitted. 6) Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. 7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately proceeding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to Original Work is granted hereunder except under this disclaimer. 8) Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to any person for any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to liability for death or personal injury resulting from Licensor's negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You. 9) Acceptance and Termination. If You distribute copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. Nothing else but this License (or another written agreement between Licensor and You) grants You permission to create Derivative Works based upon the Original Work or to exercise any of the rights granted in Section 1 herein, and any attempt to do so except under the terms of this License (or another written agreement between Licensor and You) is expressly prohibited by U.S. copyright law, the equivalent laws of other countries, and by international treaty. Therefore, by exercising any of the rights granted to You in Section 1 herein, You indicate Your acceptance of this License and all of its terms and conditions. 10) Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. 11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of the U.S. Copyright Act, 17 U.S.C. § 101 et seq., the equivalent laws of other countries, and international treaty. This section shall survive the termination of this License. 12) Attorneys Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. 13) Miscellaneous. This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. 14) Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For 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. 15) Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved. Permission is hereby granted to copy and distribute this license without modification. This license may not be modified without the express written permission of its copyright owner. ---------------------------------------------------------------------- ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1643211004.7548294 ftputil-5.0.3/PKG-INFO0000664000175000017500000000274700000000000013314 0ustar00schwaschwaMetadata-Version: 1.1 Name: ftputil Version: 5.0.3 Summary: High-level FTP client library (virtual file system and more) Home-page: https://ftputil.sschwarzer.net/ Author: Stefan Schwarzer Author-email: sschwarzer@sschwarzer.net License: Open source (revised BSD license) Download-URL: http://ftputil.sschwarzer.net/trac/attachment/wiki/Download/ftputil-5.0.3.tar.gz?format=raw Description: ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. Keywords: FTP,client,library,virtual file system Platform: Pure Python Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Other Environment Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 :: Only Classifier: Topic :: Internet :: File Transfer Protocol (FTP) Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Filesystems ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642884872.9786096 ftputil-5.0.3/VERSION0000664000175000017500000000000600000000000013251 0ustar00schwaschwa5.0.3 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1643211004.7538292 ftputil-5.0.3/doc/0000775000175000017500000000000000000000000012752 5ustar00schwaschwa././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7849648 ftputil-5.0.3/doc/README.txt0000664000175000017500000000367100000000000014457 0ustar00schwaschwaftputil ======= Purpose ------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. What's new? ----------- Fixed handling of empty strings as paths [1]. `FTPHost.path.isdir` (and `isfile` and `islink`) now return `False` for an empty path. Most public methods, with the exception of `FTPHost.chdir`, raise an `FTPIOError` if passed remote paths are empty. Documentation ------------- The documentation for ftputil can be found in the file ftputil.txt (reStructuredText format) or ftputil.html (recommended, generated from ftputil.txt). Prerequisites ------------- To use ftputil, you need Python, at least version 3.6. Installation ------------ *If you have an older version of ftputil installed, delete it or move it somewhere else, so that it doesn't conflict with the new version.* You can install ftputil with pip: pip install ftputil Unless you're installing ftputil in a virtual environment, you'll probably need root/administrator privileges. Note that ftputil versions with a different major version number won't be fully backward-compatible with the previous version. Examples are the changes from 2.8 to 3.0 and from 3.4 to 4.0.0. License ------- ftputil is open source software. It is distributed under the new/modified/revised BSD license (see http://opensource.org/licenses/BSD-3-Clause ). Authors ------- Stefan Schwarzer Evan Prodromou (lrucache module) (See also the file `doc/contributors.txt`.) Please provide feedback! It's certainly appreciated. :-) [1] https://ftputil.sschwarzer.net/trac/ticket/148 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1643209557.0380902 ftputil-5.0.3/doc/announcements.txt0000664000175000017500000014643600000000000016406 0ustar00schwaschwaftputil 5.0.3 is now available from https://ftputil.sschwarzer.net/download . Changes since the last stable release 5.0.2 ------------------------------------------- Fixed potential data loss bug with FTPS [1]. Upgrading is highly recommended. What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the `os`, `os.path` and `shutil` modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. See the documentation for details: https://ftputil.sschwarzer.net/trac/wiki/Documentation License ------- ftputil is open source software, released under the 3-clause BSD license (see https://opensource.org/licenses/BSD-3-Clause ). [1] https://ftputil.sschwarzer.net/trac/ticket/149 Best regards, Stefan ---------------------------------------------------------------------- ftputil 5.0.2 is now available from https://ftputil.sschwarzer.net/download . Changes since the last stable release 5.0.1 ------------------------------------------- Fixed handling of empty strings as paths [1]. `FTPHost.path.isdir` (and `isfile` and `islink`) now return `False` for an empty path. Most public methods, with the exception of `FTPHost.chdir`, raise an `FTPIOError` if passed remote paths are empty. What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the `os`, `os.path` and `shutil` modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. See the documentation for details: https://ftputil.sschwarzer.net/trac/wiki/Documentation License ------- ftputil is open source software, released under the 3-clause BSD license (see https://opensource.org/licenses/BSD-3-Clause ). [1] https://ftputil.sschwarzer.net/trac/ticket/148 Best regards, Stefan ---------------------------------------------------------------------- ftputil 5.0.1 is now available from https://ftputil.sschwarzer.net/download . Changes since the last stable release 5.0.0 ------------------------------------------- This release fixes a regression in ftputil 5.0.0. [1] What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the `os`, `os.path` and `shutil` modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. See the documentation for details: https://ftputil.sschwarzer.net/trac/wiki/Documentation License ------- ftputil is open source software, released under the 3-clause BSD license (see https://opensource.org/licenses/BSD-3-Clause ). [1] https://ftputil.sschwarzer.net/trac/ticket/145 Best regards, Stefan ---------------------------------------------------------------------- ftputil 5.0.0 is now available from https://ftputil.sschwarzer.net/download . Changes since the last stable release 4.0.0 ------------------------------------------- Backward-incompatible changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This ftputil version isn't fully backward-compatible with the previous version due to changes in the `ftplib` module in the standard library of Python 3.9 [1]. That said, if you only deal with directory and file paths which solely consist of ASCII characters, this change doesn't affect you. Here are some details. You can find more in the ftputil documentation [2] and in ticket #143 [3]. Internally, ftputil uses `ftplib.FTP` or compatible classes to perform most low-level FTP operations. In Python 3.8 and before, the default encoding for FTP paths was latin-1, but there was no official documentation on using a different encoding. In Python 3.9, the default encoding changed to UTF-8 and the encoding is configurable with an `ftplib.FTP` constructor argument. The documentation of ftputil 4.0.0 and earlier stated: - ftputil uses latin-1 encoding for paths - ftputil uses `ftplib.FTP` as the default session factory With the change of the default encoding in Python 3.9 these two statements are contradictory. To resolve the conflict, the new behavior of ftputil is: - ftputil uses `ftplib.FTP` as default session factory, but explicitly sets the path encoding to latin-1 (regardless of the Python version). This is the same behavior as in ftputil 4.0.0 or earlier if running under Python 3.8 or earlier. - If client code uses a custom session factory (i. e. the `session_factory` argument of the `FTPHost` constructor), ftputil will take the path encoding to use for `bytes` paths from the `encoding` attribute of an FTP session from this factory. If there's no such attribute, an exception is raised. Other changes ~~~~~~~~~~~~~ `ftputil.session.session_factory` got a new keyword argument `encoding` to set the path encoding of the sessions created by the factory. If the argument isn't specified, the path encoding will be taken from the `base_class` argument. (This means that the encoding will be different for `ftplib.FTP` in Python 3.8 or earlier vs. Python 3.9 or later.) What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the `os`, `os.path` and `shutil` modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. See the documentation for details: https://ftputil.sschwarzer.net/trac/wiki/Documentation License ------- ftputil is open source software, released under the 3-clause BSD license (see https://opensource.org/licenses/BSD-3-Clause ). [1] https://docs.python.org/3/whatsnew/3.9.html#changes-in-the-python-api "The encoding parameter has been added to the classes ftplib.FTP and ftplib.FTP_TLS as a keyword-only parameter, and the default encoding is changed from Latin-1 to UTF-8 to follow RFC 2640." [2] https://ftputil.sschwarzer.net/trac/wiki/Documentation [3] https://ftputil.sschwarzer.net/trac/ticket/143 Best regards, Stefan ---------------------------------------------------------------------- ftputil 4.0.0 is now available from https://ftputil.sschwarzer.net/download . Changes since the last stable release 3.4 ----------------------------------------- Backward-incompatible changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This ftputil version isn't fully backward-compatible with the previous version. The backward-incompatible changes are: - Python 2 is no longer supported. - The minimum supported Python 3 version is 3.6. - By default, time stamps in directory listings coming from the server are now assumed to be in UTC. Previously, listings were assumed to use the local time of the client. [1] Correspondingly, the definition of "time shift" has changed. The time shift is now defined as the time zone used in server listings (say, UTC+02:00) and UTC, in other words, the time shift now is the time zone offset applied in the server listings. In earlier ftputil versions, the time shift was defined as "time used in server listings" minus "local client time." - The flag `use_list_a_option` of `FTPHost` instances is now set to `False` by default. This option was intended to make life easier for users, but turned out to be problematic [2]. - As in `os.makedirs`, `FTPHost.makedirs` now supports the `exist_ok` flag and uses the default of `False`. You can get the behavior of ftputil 3.x by passing `exist_ok=True`. [3] If you need to use Python versions before 3.6, please use the previous stable ftputil version 3.4. Other changes ~~~~~~~~~~~~~ - Functions and methods which used to accept only `str` or `bytes` paths now _also_ accept `PathLike` objects [4, 5]. - Clear the stat cache when setting a new time shift value. [6] - ftputil now officially follows semantic versioning (SemVer) [7]. Actually ftputil has been following semantic versioning since a long time (probably since version 2.0 in 2004), but it was never explicitly guaranteed and new major versions were named x.0 instead of x.0.0 and new minor versions x.y instead of x.y.0. - Internal changes: The tests were moved to pytest. The old mocking approach was replaced by a "scripted session" approach. What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the `os`, `os.path` and `shutil` modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. See the documentation for details: https://ftputil.sschwarzer.net/trac/wiki/Documentation https://ftputil.sschwarzer.net/trac/wiki/WhatsNewInFtputil4.0.0 License ------- ftputil is open source software, released under the 3-clause BSD license (see https://opensource.org/licenses/BSD-3-Clause ). [1] https://ftputil.sschwarzer.net/trac/ticket/134 [2] https://ftputil.sschwarzer.net/trac/ticket/110 [3] https://ftputil.sschwarzer.net/trac/ticket/117 [4] https://docs.python.org/3/library/os.html#os.PathLike [5] https://ftputil.sschwarzer.net/trac/ticket/119 [6] https://ftputil.sschwarzer.net/trac/ticket/136 [7] https://semver.org/ Best regards, Stefan ---------------------------------------------------------------------- ftputil 3.4 is now available from http://ftputil.sschwarzer.net/download . Changes since version 3.3.1 --------------------------- - Several bugs were fixed [1-5]. - Added deprecation warnings for backward incompatibilities in the upcoming ftputil 4.0.0. Important note -------------- The next version of ftputil will be 4.0.0 (apart from small fixes in possible 3.4.x versions). ftputil 4.0.0 will make some backward-incompatible changes: - Support for Python 2 will be removed. There are several reasons for this, which are explained in [6]. - The flag `use_list_a_option` will be set to `False` by default. This option was intended to make life easier for users, but turned out to be problematic (see [7]). What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. See the documentation for details: http://ftputil.sschwarzer.net/trac/wiki/Documentation License ------- ftputil is open source software, released under the revised BSD license (see http://opensource.org/licenses/BSD-3-Clause ). [1] http://ftputil.sschwarzer.net/trac/ticket/107 [2] http://ftputil.sschwarzer.net/trac/ticket/109 [3] http://ftputil.sschwarzer.net/trac/ticket/112 [4] http://ftputil.sschwarzer.net/trac/ticket/113 [5] http://ftputil.sschwarzer.net/trac/ticket/114 [6] http://lists.sschwarzer.net/pipermail/ftputil/2017q3/000465.html [7] http://ftputil.sschwarzer.net/trac/ticket/110 Best regards, Stefan ---------------------------------------------------------------------- ftputil 3.3.1 is now available from http://ftputil.sschwarzer.net/download . Changes since version 3.3 ------------------------- - Fixed a bug where a 226 reply after a remote file close would only show up later when doing a `pwd` call on the session. [1] This resulted in an `ftplib.error_reply` exception when opening a remote file. Note that ftputil 3.0 broke backward compatibility with ftputil 2.8 and before. The differences are described here: http://ftputil.sschwarzer.net/trac/wiki/WhatsNewInFtputil3.0 What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. See the documentation for details: http://ftputil.sschwarzer.net/trac/wiki/Documentation License ------- ftputil is open source software, released under the revised BSD license (see http://opensource.org/licenses/BSD-3-Clause ). [1] http://ftputil.sschwarzer.net/trac/ticket/102 Stefan ---------------------------------------------------------------------- ftputil 3.3 is now available from http://ftputil.sschwarzer.net/download . Changes since version 3.2 ------------------------- - Added `rest` argument to `FTPHost.open` for recovery after interrupted transfers [1]. - Fixed handling of non-ASCII directory and file names under Python 2 [2]. Under Python 3, the directory and file names could already contain any characters from the ISO 5589-1 (latin-1) character set. Under Python 2, non-ASCII characters (even out of the latin-1 character set) resulted in a `UnicodeEncodeError`. Now Python 2 behaves like Python 3, supporting all latin-1 characters. Note that for interoperability between servers and clients it's still usually safest to use only ASCII characters for directory and file names. - Changed `FTPHost.makedirs` for better handling of "virtual directories" [3, 4]. Thanks to Roger Demetrescu for the implementation. - Small improvements [5, 6, 7] Upgrading is recommended. Note that ftputil 3.0 broke backward compatibility with ftputil 2.8 and before. The differences are described here: http://ftputil.sschwarzer.net/trac/wiki/WhatsNewInFtputil3.0 What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. See the documentation for details: http://ftputil.sschwarzer.net/trac/wiki/Documentation License ------- ftputil is open source software, released under the revised BSD license (see http://opensource.org/licenses/BSD-3-Clause ). [1] http://ftputil.sschwarzer.net/trac/ticket/61 [2] http://ftputil.sschwarzer.net/trac/ticket/100 [3] http://ftputil.sschwarzer.net/trac/ticket/86 [4] https://support.microsoft.com/en-us/kb/142853 [5] http://ftputil.sschwarzer.net/trac/ticket/89 [6] http://ftputil.sschwarzer.net/trac/ticket/91 [7] http://ftputil.sschwarzer.net/trac/ticket/92 Have fun! :-) Stefan ---------------------------------------------------------------------- ftputil 3.2 is now available from http://ftputil.sschwarzer.net/download . Changes since version 3.1 ------------------------- - For some platforms (notably Windows) modification datetimes before the epoch would cause an `OverflowError` [1]. Other platforms could return negative values. Since the Python documentation for the `time` module [2] points out that values before the epoch might cause problems, ftputil now sets the float value for such datetimes to 0.0. In theory, this might cause backward compatibility problems, but it's very unlikely since pre-epoch timestamps in directory listings should be very rare. - On some platforms, the `time.mktime` implementation could behave strange and accept invalid date/time values. For example, a day value of 32 would be accepted and implicitly cause a "wrap" to the next month. Such invalid values now result in a `ParserError`. - Make error handling more robust where the underlying FTP session factory (for example, `ftplib.FTP`) uses byte strings for exception messages. [3] - Improved error handling for directory listings. As just one example, previously a non-integer value for a day would unintentionally cause a `ValueError`. Now this causes a `ParserError`. - Extracted socket file adapter module [4] so that it can be used by other projects. Note that ftputil 3.0 broke backward compatibility with ftputil 2.8 and before. The differences are described here: http://ftputil.sschwarzer.net/trac/wiki/WhatsNewInFtputil3.0 What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. See the documentation for details: http://ftputil.sschwarzer.net/trac/wiki/Documentation License ------- ftputil is open source software, released under the revised BSD license (see http://opensource.org/licenses/BSD-3-Clause ). [1] http://ftputil.sschwarzer.net/trac/ticket/83 [2] https://docs.python.org/3/library/time.html [3] http://ftputil.sschwarzer.net/trac/ticket/85 [4] http://ftputil.sschwarzer.net/trac/wiki/SocketFileAdapter Have fun! :-) Stefan ---------------------------------------------------------------------- ftputil 3.1 is now available from http://ftputil.sschwarzer.net/download . Changes since version 3.0 ------------------------- - Added support for `followlinks` parameter in `FTPHost.walk`. [1] - Trying to pickle `FTPHost` and `FTPFile` objects now raises explicit `TypeError`s to make clear that not being able to pickle these objects is intentional. [2] - Improved exception messages for socket errors [3]. - Fixed handling of server error messages with non-ASCII characters when running under Python 2.x. [4] - Added a generic "session factory factory" to make creation of session factories easier for common use cases (encrypted connections, non-default port, active/passive mode, FTP session debug level and combination of these). [5] This includes a workaround for `M2Crypto.ftpslib.FTP_TLS`; this class won't be usable with ftputil 3.0 and up with just the session factory recipe described in the documentation. [6] - Don't assume time zone differences to always be full hours, but rather 15-minute units. [7] For example, according to [8], Nepal's time zone is UTC+05:45. - Improved documentation on timeout handling. This includes information on internal creation of additional FTP connections (for remote files, including uploads and downloads). This may help understand better why the `keep_alive` method is limited. Note that ftputil 3.0 broke backward compatibility with ftputil 2.8 and before. The differences are described here: http://ftputil.sschwarzer.net/trac/wiki/WhatsNewInFtputil3.0 What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. See the documentation for details: http://ftputil.sschwarzer.net/trac/wiki/Documentation License ------- ftputil is open source software, released under the revised BSD license (see http://opensource.org/licenses/BSD-3-Clause ). [1] http://ftputil.sschwarzer.net/trac/ticket/73 [2] http://ftputil.sschwarzer.net/trac/ticket/75 [3] http://ftputil.sschwarzer.net/trac/ticket/76 [4] http://ftputil.sschwarzer.net/trac/ticket/77 [5] http://ftputil.sschwarzer.net/trac/ticket/78 [6] http://ftputil.sschwarzer.net/trac/wiki/Documentation#session-factories [7] http://ftputil.sschwarzer.net/trac/ticket/81 [8] http://en.wikipedia.org/wiki/Timezone#List_of_UTC_offsets Have fun! :-) Stefan ---------------------------------------------------------------------- ftputil 3.0 is now available from http://ftputil.sschwarzer.net/download . Changes since version 2.8 ------------------------- Note: This version of ftputil is _not_ backward-compatible with earlier versions.See the links below for information on adapting existing client code. - This version adds Python 3 compatibility! :-) The same source is used for Python 2.x and Python 3.x. I had to change the API to find a good compromise for both Python versions. - ftputil now requires at least Python 2.6. - Remote file-like objects use the same semantics as Python's `io` module. (This is the same as for the built-in `open` function in Python 3.) - `ftputil.ftp_error` was renamed to `ftputil.error`. - For custom parsers, import `ftputil.parser` instead of `ftputil.stat`. For more information please read http://ftputil.sschwarzer.net/trac/wiki/Documentation http://ftputil.sschwarzer.net/trac/wiki/WhatsNewInFtputil3.0 What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. License ------- ftputil is Open Source software, released under the revised BSD license (see http://opensource.org/licenses/BSD-3-Clause ). Stefan ---------------------------------------------------------------------- ftputil 2.8 is now available from http://ftputil.sschwarzer.net/download . Changes since version 2.7.1 --------------------------- - After some discussion [1] I decided to remove the auto-probing before using the `-a` option for `LIST` [2] to find "hidden" files and directories. The option is used by default now, without probing for exceptions. If this new approach causes problems, you can use ftp_host = ftputil.FTPHost(...) ftp_host.use_list_a_option = False - Several bugs were fixed. [3] - The mailing lists have moved to ftputil@lists.sschwarzer.net ftputil-tickets@lists.sschwarzer.net The ftputil list [4] requires a subscription before you can post. The ftputil-tickets list [5] is read-only anyway. Thanks to Codespeak.net for having hosted the lists for almost ten years. :-) What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. Read the documentation at http://ftputil.sschwarzer.net/documentation . License ------- ftputil is Open Source software, released under the revised BSD license (see http://opensource.org/licenses/BSD-3-Clause ). [1] http://ftputil.sschwarzer.net/trac/ticket/65 [2] http://lists.sschwarzer.net/pipermail/ftputil/2012q3/000350.html [3] http://ftputil.sschwarzer.net/trac/ticket/39 http://ftputil.sschwarzer.net/trac/ticket/65 http://ftputil.sschwarzer.net/trac/ticket/66 http://ftputil.sschwarzer.net/trac/ticket/67 http://ftputil.sschwarzer.net/trac/ticket/69 [4] http://lists.sschwarzer.net/listinfo/ftputil http://lists.sschwarzer.net/listinfo/ftputil-tickets Stefan ---------------------------------------------------------------------- ftputil 2.7 is now available from http://ftputil.sschwarzer.net/download . Changes since version 2.6 ------------------------- - ftputil now explicitly tries to get hidden directory and file names (names starting with a dot) from the FTP server. [1] Before, ftputil used a `LIST` command to get directory listings, now it uses `LIST -a` if the server doesn't explicitly reject its usage upon login. Note that the server is free to ignore the `-a` option, so "hidden" directories and files may still not be visible. Please see [2] for details. If you have code that _relies_ on "hidden" directory or file names _not_ being visible, please update the code as necessary. If that's presumably not possible, please send feedback to the mailing list [3] or in private mail [4]. - A bug in the experimental synchronization code was fixed [5]. Thanks to Zhuo Qiang for his help. :-) What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. Read the documentation at http://ftputil.sschwarzer.net/documentation . License ------- ftputil is Open Source software, released under the revised BSD license (see http://www.opensource.org/licenses/bsd-license.php ). [1] http://ftputil.sschwarzer.net/trac/ticket/23 [2] http://ftputil.sschwarzer.net/trac/ticket/23#comment:4 [3] http://codespeak.net/mailman/listinfo/ftputil (note that you need to subscribe to the list to be able to post there) [4] sschwarzer@sschwarzer.net [5] http://ftputil.sschwarzer.net/trac/ticket/62 Stefan ---------------------------------------------------------------------- ftputil 2.6 is now available from http://ftputil.sschwarzer.net/download . Changes since version 2.5 ------------------------- - The stat caching has been improved. There's now an "auto-grow" feature for `FTPHost.listdir` which in turn applies to `FTPHost.walk`. Moreover, there were several performance optimizations. - A few bugs were fixed [1-3]. What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. Read the documentation at http://ftputil.sschwarzer.net/documentation . License ------- ftputil is Open Source software, released under the revised BSD license (see http://www.opensource.org/licenses/bsd-license.php ). [1] http://ftputil.sschwarzer.net/trac/ticket/53 [2] http://ftputil.sschwarzer.net/trac/ticket/55 [3] http://ftputil.sschwarzer.net/trac/ticket/56 Stefan ---------------------------------------------------------------------- ftputil 2.5 is now available from http://ftputil.sschwarzer.net/download . Changes since version 2.4.2 --------------------------- - As announced over a year ago [1], the `xreadlines` method for FTP file objects has been removed, and exceptions can no longer be accessed via the `ftputil` namespace. Only use `ftp_error` to access the exceptions. The distribution contains a small tool `find_deprecated_code.py` to scan a directory tree for the deprecated uses. Invoke the program with the `--help` option to see a description. - Upload and download methods now accept a `callback` argument to do things during a transfer. Modification time comparisons in `upload_if_newer` and `download_if_newer` now consider the timestamp precision of the remote file which may lead to some unnecessary transfers. These can be avoided by waiting at least a minute between calls of `upload_if_newer` (or `download_if_newer`) for the same file. See the documentation for details [2]. - The `FTPHost` class got a `keep_alive` method. It should be used carefully though, not routinely. Please read the description [3] in the documentation. - Several bugs were fixed [4-7]. - The source code was restructured. The tests are now in a `test` subdirectory and are no longer part of the release archive. You can still get them via the source repository. Licensing matters have been moved to a common `LICENSE` file. What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. Read the documentation at http://ftputil.sschwarzer.net/documentation . License ------- ftputil is Open Source software, released under the revised BSD license (see http://www.opensource.org/licenses/bsd-license.php ). [1] http://codespeak.net/pipermail/ftputil/2009q1/000256.html [2] http://ftputil.sschwarzer.net/trac/wiki/Documentation#uploading-and-downloading-files [3] http://ftputil.sschwarzer.net/trac/wiki/Documentation#keep-alive [4] http://ftputil.sschwarzer.net/trac/ticket/44 [5] http://ftputil.sschwarzer.net/trac/ticket/46 [6] http://ftputil.sschwarzer.net/trac/ticket/47 [7] http://ftputil.sschwarzer.net/trac/ticket/51 Stefan ---------------------------------------------------------------------- ftputil 2.4.2 is now available from http://ftputil.sschwarzer.net/download . Changes since version 2.4.1 --------------------------- - Some FTP servers seem to have problems using *any* directory argument which contains slashes. The new default for FTP commands now is to change into the directory before actually invoking the command on a relative path (report and fix suggestion by Nicola Murino). - Calling the method ``FTPHost.stat_cache.resize`` with an argument 0 caused an exception. This has been fixed; a zero cache size now of course doesn't cache anything but doesn't lead to a traceback either. - The installation script ``setup.py`` didn't work with the ``--home`` option because it still tried to install the documentation in a system directory (report by Albrecht Mühlenschulte). As a side effect, when using the *global* installation, the documentation is no longer installed in the ftputil package directory but in a subdirectory ``doc`` of a directory determined by Distutils. For example, on my system (Ubuntu 9.04) the documentation files are put into ``/usr/local/doc``. Upgrading is recommended. What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. Read the documentation at http://ftputil.sschwarzer.net/documentation . License ------- ftputil is Open Source software, released under the revised BSD license (see http://www.opensource.org/licenses/bsd-license.php ). Stefan ---------------------------------------------------------------------- ftputil 2.4.1 is now available from http://ftputil.sschwarzer.net/download . Changes since version 2.4 ------------------------- Several bugs were fixed: - On Windows, some accesses to the stat cache caused it to become inconsistent, which could also trigger exceptions (report and patch by Peter Stirling). - In ftputil 2.4, the use of ``super`` in the exception base class caused ftputil to fail on Python <2.5 (reported by Nicola Murino). ftputil is supposed to run with Python 2.3+. - The conversion of 12-hour clock times to 24-hour clock in the MS format parser was wrong for 12 AM and 12 PM. Upgrading is strongly recommended. What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. Read the documentation at http://ftputil.sschwarzer.net/documentation . License ------- ftputil is Open Source software, released under the revised BSD license (see http://www.opensource.org/licenses/bsd-license.php ). Stefan ---------------------------------------------------------------------- ftputil 2.4 is now available from http://ftputil.sschwarzer.net/download . Changes since version 2.3 ------------------------- The ``FTPHost`` class got a new method ``chmod``, similar to ``os.chmod``, to act on remote files. Thanks go to Tom Parker for the review. There's a new exception ``CommandNotImplementedError``, derived from ``PermanentError``, to denote commands not implemented by the FTP server or disabled by its administrator. Using the ``xreadlines`` method of FTP file objects causes a warning through Python's warnings framework. Upgrading is recommended. Incompatibility notice ---------------------- The ``xreadlines`` method will be removed in ftputil *2.5* as well as the direct access of exception classes via the ftputil module (e. g. ``ftputil.PermanentError``). However, the deprecated access causes no warning because that would be rather difficult to implement. The distribution contains a small tool find_deprecated_code.py to scan a directory tree for the deprecated uses. Invoke the program with the ``--help`` option to see a description. What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. Read the documentation at http://ftputil.sschwarzer.net/documentation . License ------- ftputil is Open Source software, released under the revised BSD license (see http://www.opensource.org/licenses/bsd-license.php ). Stefan ---------------------------------------------------------------------- ftputil 2.3 is now available from http://ftputil.sschwarzer.net/download . Changes since version 2.2.4 --------------------------- ftputil has got support for the ``with`` statement which was introduced by Python 2.5. You can now construct host and remote file objects in ``with`` statements and have them closed automatically (contributed by Roger Demetrescu). See the documentation for examples. What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. Read the documentation at http://ftputil.sschwarzer.net/documentation . License ------- ftputil is Open Source software, released under the revised BSD license (see http://www.opensource.org/licenses/bsd-license.php ). Stefan ---------------------------------------------------------------------- ftputil 2.2.4 is now available from http://ftputil.sschwarzer.net/download . Changes since version 2.2.3 --------------------------- This release fixes a bug in the ``makedirs`` call (report and fix by Richard Holden). Upgrading is recommended. What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. Read the documentation at http://ftputil.sschwarzer.net/documentation . License ------- ftputil is Open Source software, released under the revised BSD license (see http://www.opensource.org/licenses/bsd-license.php ). Stefan ---------------------------------------------------------------------- ftputil 2.2.3 is now available from http://ftputil.sschwarzer.net/download . Changes since version 2.2.2 --------------------------- This release fixes a bug in the ``makedirs`` call (report and fix by Julian, whose last name I don't know ;-) ). Upgrading is recommended. What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. Read the documentation at http://ftputil.sschwarzer.net/documentation . License ------- ftputil is Open Source software, released under the revised BSD license (see http://www.opensource.org/licenses/bsd-license.php ). Stefan ---------------------------------------------------------------------- ftputil 2.2.2 is now available from http://ftputil.sschwarzer.net/download . Changes since version 2.2.1 --------------------------- This bugfix release handles whitespace in path names more reliably (thanks to Johannes Strömberg). Upgrading is recommended. What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. Read the documentation at http://ftputil.sschwarzer.net/documentation . License ------- ftputil is Open Source software, released under the revised BSD license (see http://www.opensource.org/licenses/bsd-license.php ). Stefan ---------------------------------------------------------------------- ftputil 2.2.1 is now available from http://ftputil.sschwarzer.net/download . Changes since version 2.2 ------------------------- This bugfix release checks (and ignores) status code 451 when FTPFiles are closed (thanks go to Alexander Holyapin). Upgrading is recommended. What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. Read the documentation at http://ftputil.sschwarzer.net/trac/wiki/Documentation . License ------- ftputil is Open Source software, released under the revised BSD license (see http://www.opensource.org/licenses/bsd-license.php ). Stefan ---------------------------------------------------------------------- ftputil 2.2 is now available from http://ftputil.sschwarzer.net/download . Changes since version 2.1 ------------------------- - Results of stat calls (also indirect calls, i. e. listdir, isdir/isfile/islink, exists, getmtime etc.) are now cached and reused. This results in remarkable speedups for many use cases. Thanks to Evan Prodromou for his permission to add his lrucache module under ftputil's license. - The current directory is also locally cached, resulting in further speedups. - It's now possible to write and plug in custom parsers for directory formats which ftputil doesn't support natively. - File-like objects generated via ``FTPHost.file`` now support the iterator protocol (for line in some_file: ...). - The documentation has been updated accordingly. Read it under http://ftputil.sschwarzer.net/trac/wiki/Documentation . Possible incompatibilities: - This release requires at least Python 2.3. (Previous releases worked with Python versions from 2.1 up.) - The method ``FTPHost.set_directory_format`` has been removed, since the directory format (Unix or MS) is set automatically. (The new method ``set_parser`` is a different animal since it takes a parser object to parse "foreign" formats, not a string.) What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. License ------- ftputil 2.2 is Open Source software, released under the revised BSD license (see http://www.opensource.org/licenses/bsd-license.php ). Stefan ---------------------------------------------------------------------- The second beta version of ftputil 2.2 is available. You can download it from http://ftputil.sschwarzer.net/download . With respect to the first beta release, it's now possible to write and plug in custom parsers for FTP directory formats that ftputil doesn't know natively. The documentation has been updated accordingly. The documentation for this release is online at http://ftputil.sschwarzer.net/trac/wiki/Documentation#Documentationforftputil2.2b2 , so you can read about the extensions in more detail. Please download and test the release. Do you miss something which should be in this release? Are there any bugs? Stefan ---------------------------------------------------------------------- The first beta version of ftputil 2.2 is available. You can download it from http://ftputil.sschwarzer.net/download . With respect to the previous alpha release, the cache now uses the lrucache module by Evan Prodromou which is bundled with the ftputil distribution. (Evan also gave his permission to include the module under ftputil's modified BSD license instead of the Academic License.) The documentation for the cache and its control have been added to ftputil.txt / ftputil.html . File objects generated with FTPHost.file now support the iterator protocol (for line in some_file: ...). Please download and test the release. Do you miss something which should be in this release? Are there any bugs? Stefan ---------------------------------------------------------------------- Welcome to the first alpha release of ftputil 2.2, ftputil 2.2a1. Please download it from http://ftputil.sschwarzer.net/download . This version adds caching of stat results to ftputil. This also affects indirect calls via FTPHost.path, e. g. methods isfile, exists, getmtime, getsize. The test script at http://ftputil.sschwarzer.net/trac/browser/tags/release2_2a1/sandbox/list_dir_test.py runs about 20 times as fast as before adding caching! :-) As the "alpha" part implies, this release is not production-ready, it's even kind of experimental: The caching works but there's no cache entry expiration yet. (I plan to implement an LRU expiration strategy or something similar.) Apart from that, the release is tested as any production release. I suggest using the --prefix option for installing alpha releases. That said, it would be helpful if you tested this release and report your findings. When testing the code, please make sure that your code uses the ftputil version you intend (alpha vs. production version), e. g. by setting the PYTHONPATH environment variable. I'm very interested in the speedups - and any problems you encounter. Stefan ---------------------------------------------------------------------- ftputil 2.1.1 is now available from http://ftputil.sschwarzer.net/download . This release fixes a bug which happened when a client opened a large file on the server as a file-like object and read only a part of it. For details, see http://ftputil.sschwarzer.net/trac/ticket/17 . Stefan ---------------------------------------------------------------------- Changes since version 2.0 ------------------------- - Added new methods to the FTPHost class, namely makedirs, walk, rmtree. - The FTP server directory format ("Unix" vs. "Windows") is now set automatically (thanks to Andrew Ittner for testing it). - Border cases like inaccessible login directories and whitespace in directory names, are now handled more gracefully (based on input from Valeriy Pogrebitskiy, Tommy Sundström and H. Y. Chu). - The documentation was updated. - A Russian translation of the documentation (currently slightly behind) was contributed by Anton Stepanov. It's also on the website at http://ftputil.sschwarzer.net/trac/wiki/RussianDocumentation . - New website, http://ftputil.sschwarzer.net/ with wiki, issue tracker and Subversion repository (thanks to Trac!) Please enter not only bugs but also enhancement request into the issue tracker! Possible incompatibilities: - The exception hierarchy was changed slightly, which might break client code. See http://ftputil.sschwarzer.net/trac/changeset/489 for the change details and the possibly necessary code changes. - FTPHost.rmdir no longer removes non-empty directories. Use the new method FTPHost.rmtree for this. What is ftputil? ---------------- ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. License ------- ftputil 2.1 is Open Source software, released under the revised BSD license (see http://www.opensource.org/licenses/bsd-license.php ). Stefan ---------------------------------------------------------------------- ftputil 2.0.3 is now available at http://www.sschwarzer.net/python/python_software.html#ftputil . This release fixes (for most cases) some problems when logging into an FTP server with an inaccessible login directory, i. e. `getcwd()` returns "/some/login/dir" but `chdir("/some/login/dir")` fails. Thanks go to Valeriy Pogrebitskiy for investigating and reporting these problems. Stefan ---------------------------------------------------------------------- Here's ftputil 2.0 ! ftputil is a high-level alternative to Python's ftplib module. With ftputil, you can access directories and files on remote FTP servers almost as if they were in your local file system. This includes using file-like objects representing remote files. For future releases see http://www.sschwarzer.net/python/python_software.html or subscribe to the mailing list at http://codespeak.net/mailman/listinfo/ftputil What's new? ----------- From version 1.1 to 2.0, the following has changed: - ftputil has been re-organized and is now a Python package (the import statement is still the same) - installation via Python distutils - stat, upload_if_newer, download_if_newer etc. work correctly if the server is in another time zone than the client running ftputil (with help from Andrew Ittner); see section "Time zone correction" in the documentation - it's possible to set the directory listing format "manually" (though in most cases it's recognized automatically); see section "Stat'ing files and directories" - added a workaround regarding whitespace in directory names (thanks to Tommy Sundström and H. Y. Chu) - extended documentation and converted it to HTML format (now generated from reStructured Text) - several bugfixes - there's now a mailing list at http://codespeak.net/mailman/listinfo/ftputil (thanks to Holger Krekel) Documentation ------------- The documentation for ftputil can be found in the file ftputil.txt (reStructured Text format) or ftputil.html (recommended, generated from ftputil.txt). License ------- ftputil is Open Source Software. It is distributed under a BSD-style license (see the top of ftputil.py). Stefan ---------------------------------------------------------------------- ftputil 1.1 is released. You can find it at http://www.ndh.net/home/sschwarzer/python/python_software.html . ftputil provides a higher-level interface for FTP sessions than the ftplib module. FTP servers can be accessed via an interface similar to os and os.path. Remote files are accessible as file-like objects. New since version 1.0: - ftputil now runs under Python 2.1+ (not only 2.2+). - documentation - conditional upload/download (depending on local and remote file timestamps) - FTPHost.stat follows links - a session factory other than the default, ftplib.FTP, can be given in the FTPHost constructor; this allows to use classes derived from ftplib.FTP (like ftpslib.FTP_TLS from the M2Crypto package) - several bugfixes (mostly regarding byte count in text mode transfers) - unit test Stefan ---------------------------------------------------------------------- Hello Pythoneers :) I would like to announce ftputil.py, a module which provides a more friendly interface for FTP sessions than the ftplib module. The FTPHost objects generated from it allow many operations similar to those of os and os.path. Examples: # download some files from the login directory import ftputil host = ftputil.FTPHost('ftp.domain.com', 'user', 'secret') names = host.listdir(host.curdir) for name in names: if host.path.isreg(name): host.download(name, name, 'b') # remote, local, binary mode # make a new directory and copy a remote file into it host.mkdir('newdir') source = host.file('index.html', 'r') # file-like object target = host.file('newdir/index.html', 'w') # file-like object host.copyfileobj(source, target) # mimics shutil.copyfileobj source.close() target.close() Even host.path.walk works. :-) But slow. ;-) ftputil.py can be downloaded from http://www.ndh.net/home/sschwarzer/download/ftputil.py I would like to get your suggestions and comments. :-) Stefan P.S.: Thanks to Pedro Rodriguez for his helpful answer to my question in comp.lang.python :-) ---------------------------------------------------------------------- ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7859647 ftputil-5.0.3/doc/contributors.txt0000664000175000017500000000010500000000000016244 0ustar00schwaschwaStefan Schwarzer Evan Prodromou Roger Demetrescu Philippe Ombredanne ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1643211003.8118477 ftputil-5.0.3/doc/ftputil.html0000664000175000017500000031727300000000000015344 0ustar00schwaschwa ftputil -- a high-level FTP client library

ftputil -- a high-level FTP client library

Version: 5.0.3
Date: 2022-01-26
Summary:high-level FTP client library for Python
Keywords:FTP, ftplib substitute, virtual filesystem, pure Python
Author: Stefan Schwarzer <sschwarzer@sschwarzer.net>

Introduction

The ftputil module is a high-level interface to the ftplib module. The FTPHost objects generated from it allow many operations similar to those of os, os.path and shutil.

Example:

import ftputil

# Download some files from the login directory.
with ftputil.FTPHost("ftp.domain.com", "user", "password") as ftp_host:
    names = ftp_host.listdir(ftp_host.curdir)
    for name in names:
        if ftp_host.path.isfile(name):
            ftp_host.download(name, name)  # remote, local
    # Make a new directory and copy a remote file into it.
    ftp_host.mkdir("newdir")
    with ftp_host.open("index.html", "rb") as source:
        with ftp_host.open("newdir/index.html", "wb") as target:
            ftp_host.copyfileobj(source, target)  # similar to shutil.copyfileobj

Also, there are FTPHost.lstat and FTPHost.stat to request size and modification time of a file. The latter can also follow links, similar to os.stat. FTPHost.walk and FTPHost.path.walk work, too.

ftputil features

  • Method names are familiar from Python's os, os.path and shutil modules. For example, use os.path.join to join paths for a local file system and ftp_host.path.join to join paths for a remote FTP file system.
  • Remote file system navigation (getcwd, chdir)
  • Upload and download files (upload, upload_if_newer, download, download_if_newer)
  • Time zone synchronization between client and server (needed for upload_if_newer and download_if_newer)
  • Create and remove directories (mkdir, makedirs, rmdir, rmtree) and remove files (remove)
  • Get information about directories, files and links (listdir, stat, lstat, exists, isdir, isfile, islink, abspath, dirname, basename etc.)
  • Iterate over remote file systems (walk)
  • Local caching of results from lstat and stat calls to reduce network access (also applies to exists, getmtime etc.).
  • Read files from and write files to remote hosts via file-like objects (FTPHost.open; the generated file-like objects have the familiar methods like read, readline, readlines, write, writelines and close. You can also iterate over these files line by line in a for loop.

Exception hierarchy

The exceptions are in the namespace of the ftputil.error module, e. g. ftputil.error.TemporaryError.

The exception classes are organized as follows:

FTPError
    FTPOSError(FTPError, OSError)
        PermanentError(FTPOSError)
            CommandNotImplementedError(PermanentError)
        TemporaryError(FTPOSError)
    FTPIOError(FTPError)
    InternalError(FTPError)
        InaccessibleLoginDirError(InternalError)
        NoEncodingError(InternalError)
        ParserError(InternalError)
        RootDirError(InternalError)
        TimeShiftError(InternalError)

and are described here:

  • FTPError

    is the root of the exception hierarchy of the module.

  • FTPOSError

    is derived from OSError. This is for similarity between the os module and FTPHost objects. Compare

    try:
        os.chdir("nonexisting_directory")
    except OSError:
        ...
    

    with

    host = ftputil.FTPHost("host", "user", "password")
    try:
        host.chdir("nonexisting_directory")
    except OSError:
        ...
    

    Imagine a function

    def func(path, file):
        ...
    

    which works on the local file system and catches OSErrors. If you change the parameter list to

    def func(path, file, os=os):
        ...
    

    where os denotes the os module, you can call the function also as

    host = ftputil.FTPHost("host", "user", "password")
    func(path, file, os=host)
    

    to use the same code for both a local and remote file system. Another similarity between OSError and FTPOSError is that the latter holds the FTP server return code in the errno attribute of the exception object and the error text in strerror.

  • PermanentError

    is raised for 5xx return codes from the FTP server. This corresponds to ftplib.error_perm (though PermanentError and ftplib.error_perm are not identical).

  • CommandNotImplementedError

    indicates that an underlying command the code tries to use is not implemented. For an example, see the description of the FTPHost.chmod method.

  • TemporaryError

    is raised for FTP return codes from the 4xx category. This corresponds to ftplib.error_temp (though TemporaryError and ftplib.error_temp are not identical).

  • FTPIOError

    denotes an I/O error on the remote host. This appears mainly with file-like objects that are retrieved by calling FTPHost.open. Compare

    >>> try:
    ...     f = open("not_there")
    ... except IOError as obj:
    ...     print(obj.errno)
    ...     print(obj.strerror)
    ...
    2
    No such file or directory
    

    with

    >>> ftp_host = ftputil.FTPHost("host", "user", "password")
    >>> try:
    ...     f = ftp_host.open("not_there")
    ... except IOError as obj:
    ...     print(obj.errno)
    ...     print(obj.strerror)
    ...
    550
    550 not_there: No such file or directory.
    

    As you can see, both code snippets are similar. However, the error codes aren't the same.

  • InternalError

    subsumes exception classes for signaling errors due to limitations of the FTP protocol or the concrete implementation of ftputil.

  • InaccessibleLoginDirError

    This exception is raised if the directory in which "you" are placed upon login is not accessible, i. e. a chdir call with the directory as argument would fail.

  • NoEncodingError

    is raised if an FTP session instance doesn't have an encoding attribute (see also session factories).

  • ParserError

    is used for errors during the parsing of directory listings from the server. This exception is used by the FTPHost methods stat, lstat, and listdir.

  • RootDirError

    Because of the implementation of the lstat method it is not possible to do a stat call on the root directory /. If you know any way to do it, please let me know. :-)

    This problem does not affect stat calls on items in the root directory.

  • TimeShiftError

    is used to denote errors which relate to setting the time shift.

FTPHost objects

Construction

Introduction

FTPHost instances can be created with the following call:

ftp_host = ftputil.FTPHost(server, user, password, account,
                           session_factory=ftplib.FTP)

The first four parameters are strings with the same meaning as for the FTP class in the ftplib module. Usually the account and session_factory arguments aren't needed though.

FTPHost objects can also be used in a with statement:

import ftputil

with ftputil.FTPHost(server, user, password) as ftp_host:
    print(ftp_host.listdir(ftp_host.curdir))

After the with block, the FTPHost instance and the associated FTP sessions will be closed automatically.

If something goes wrong during the FTPHost construction or in the body of the with statement, the instance is closed as well. Exceptions will be propagated (as with try ... finally).

Session factories

The keyword argument session_factory may be used to generate FTP connections with other factories than the default ftplib.FTP. For example, the standard library of Python 3 contains a class ftplib.FTP_TLS which extends ftplib.FTP to use an encrypted connection.

In fact, all positional and keyword arguments other than session_factory are passed to the factory to generate a new background session. This also happens for every remote file that is opened; see below.

This functionality of the constructor also allows to wrap ftplib.FTP objects to do something that wouldn't be possible with the ftplib.FTP constructor alone.

As an example, assume you want to connect to another than the default port, but ftplib.FTP only offers this by means of its connect method, not via its constructor. One solution is to use a custom class as a session factory:

import ftplib
import ftputil

EXAMPLE_PORT = 50001

class MySession(ftplib.FTP):

    def __init__(self, host, userid, password, port):
        """Act like ftplib.FTP's constructor but connect to another port."""
        ftplib.FTP.__init__(self)
        self.connect(host, port)
        self.login(userid, password)

# Try _not_ to use an _instance_ `MySession()` as factory, -
# use the class itself.
with ftputil.FTPHost(host, userid, password, port=EXAMPLE_PORT,
                     session_factory=MySession) as ftp_host:
    # Use `ftp_host` as usual.
    ...

On login, the format of the directory listings (needed for stat'ing files and directories) should be determined automatically. If not, please enter a ticket.

For the most common uses you don't need to create your own session factory class though. The ftputil.session module has a function session_factory that can create session factories for a variety of parameters:

session_factory(base_class=ftplib.FTP,
                port=21,
                use_passive_mode=None,
                encrypt_data_channel=True,
                encoding=None,
                debug_level=None)

with

  • base_class is a base class to inherit a new session factory class from. By default, this is ftplib.FTP from the Python standard library.

  • port is the command channel port. The default is 21, used in most FTP server configurations.

  • use_passive_mode is either a boolean that determines whether passive mode should be used or None. None means to let the base class choose active or passive mode.

  • encrypt_data_channel defines whether to encrypt the data channel for secure connections. This is only supported for the base classes ftplib.FTP_TLS and M2Crypto.ftpslib.FTP_TLS, otherwise the parameter is ignored.

  • encoding can be a string to set the encoding of directory and file paths on the remote server. (This has nothing to do with the encoding of file contents!) If you pass a string and your base class is neither ftplib.FTP nor ftplib.FTP_TLS, the used heuristic in session_factory may not work reliably. Therefore, if in doubt, let encoding be None and define your base_class so that it sets the encoding you want.

    Note: In Python 3.9, the default path encoding for ftplib.FTP and ftplib.FTP_TLS changed from previously "latin-1" to "utf-8". Hence, if you don't pass an encoding to session_factory, you'll get different path encodings for Python 3.8 and earlier vs. Python 3.9 and later.

    If you're sure that you always use only ASCII characters in your remote paths, you don't need to worry about the path encoding and don't need to use the encoding argument.

  • debug_level sets the debug level for FTP session instances. The semantics is defined by the base class. For example, a debug level of 2 causes the most verbose output for Python's ftplib.FTP class.

All of these parameters can be combined. For example, you could use

import ftplib

import ftputil
import ftputil.session


my_session_factory = ftputil.session.session_factory(
                       base_class=ftpslib.FTP_TLS,
                       port=31,
                       encrypt_data_channel=True,
                       encoding="UTF-8",
                       debug_level=2)

with ftputil.FTPHost(server, user, password,
                     session_factory=my_session_factory) as ftp_host:
    ...

to create and use a session factory derived from ftplib.FTP_TLS that connects on command channel 31, will encrypt the data channel, use the UTF-8 encoding for remote paths and print output for debug level 2.

Note: Generally, you can achieve everything you can do with ftputil.session.session_factory with an explicit session factory as described at the start of this section.

Directory and file names

Note

Keep in mind that this section only applies to directory and file names, not file contents. Encoding and decoding for file contents is handled by the encoding argument for FTPHost.open.

Generally, paths can be str or bytes objects (or PathLike objects wrapping str or bytes). However, you can't mix different string types (bytes and str) in one call (for example in FTPHost.path.join). If a method gets a string argument (or a string argument wrapped in a PathLike object) and returns one or more strings, these strings will have the same string type (bytes or str) as the argument(s). Mixing different string types in one call (for example in FTPHost.path.join) isn't allowed and will cause a TypeError. These rules are the same as for local file system operations.

Although you can pass paths as str or bytes, the former is recommended. See below for the reason.

If you have directory or file names with non-ASCII characters, you need to be aware of the encoding the session factory (e. g. ftplib.FTP) uses. This needs to be the same encoding that the FTP server uses for the paths.

The following diagram shows string conversions on the way from your code to the remote FTP server. The opposite way works analogously, so encoding steps in the diagram become decoding steps and decoding steps in the diagram become encoding steps.

Both "branching points" in the upper and lower part of diagrams are independent, so depending on how you pass paths to ftputil and which file system API the FTP server uses, there are four possible combinations.

  +-----------+       +-----------+
  | Your code |       | Your code |
  +-----------+       +-----------+
       |                    |
       |  str               |  bytes
       v                    v
 +-------------+     +-------------+  decode with encoding of session,
 | ftputil API |     | ftputil API |  e. g. `ftplib.FTP` instance
 +-------------+     +-------------+
         \               /
          \     str     /
           v           v
         +---------------+  encode with encoding
         |  ftplib API   |  specified in `FTP` instance
         +---------------+
                 |
                 |  bytes
                 v
          +-------------+
          | socket API  |
          +-------------+
             /       \
            /         \                 local / client
 - - - - - / - - - - - \ - - - - - - - - - - - - - - - - - - - - - -
          /             \              remote / server
         /     bytes     \
        v                 v
 +------------+      +------------+  decode with encoding from
 | FTP server |      | FTP server |  FTP server configuration
 +------------+      +------------+
       |                   |
       |  bytes            |  str
       v                   v
+-------------+      +-------------+
| remote file |      | remote file |
| system API  |      | system API  |
+-------------+      +-------------+
        \                 /
         \      bytes    /
          v             v
       +-------------------+
       |    file system    |
       +-------------------+

As you can see at the top of the diagram, if you use str objects (regular unicode strings), there's one fewer decoding step, and so one fewer source of problems. If you use bytes objects for paths, ftputil tries to get the encoding for the FTP server from the encoding attribute of the session instance (say, an instance of ftplib.FTP). If no encoding attribute is present, a NoEncodingError is raised.

All encoding/decoding steps must use the same encoding, the encoding the server uses (at the bottom of the diagram). If the server uses the bytes from the socket directly, i. e. without an encoding step, you have to use the file system encoding.

Until and including Python 3.8, the encoding implicitly assumed by the ftplib module was latin-1, so using bytes was the safest strategy. However, Python 3.9 made the encoding configurable via an ftplib.FTP constructor argument encoding, but defaults to UTF-8.

If you don't pass a session factory to the ftputil.FTPHost constructor, ftputil will use latin-1 encoding for the paths. This is the same value as in earlier ftputil versions in combination with Python 3.8 and earlier.

Summary:

  • If possible, use only ASCII characters in paths.
  • If possible, pass paths to ftputil as str, not bytes.
  • If you use a custom session factory, the session instances created by the factory must have an encoding attribute with the name of the path encoding to use. If your session instances don't have an encoding attribute, ftputil raises a NoEncodingError when the session is created.

Hidden files and directories

Whether ftputil sees "hidden" files and directories (usually files or directories whose names start with a dot) depends on the FTP server configuration. By default, ftputil does not use the -a option in the FTP LIST command to find hidden files.

To tell the server to list hidden directories and files, set FTPHost.use_list_a_option to True:

ftp_host = ftputil.FTPHost(server, user, password, account,
                           session_factory=ftplib.FTP)
ftp_host.use_list_a_option = True

Caveats:

  • If the server doesn't understand the -a option at all, the server may interpret -a as the name of a file or directory, which can result in odd behavior. Therefore, use -a only if you're sure the server you're talking to supports it. Another approach is to have test code for -a support and fall back to not using the option.
  • Even if the server knows about the -a option, the server may be configured to ignore it.

FTPHost attributes and methods

Attributes

  • curdir, pardir, sep

    are strings which denote the current and the parent directory on the remote server. sep holds the path separator. Though RFC 959 (File Transfer Protocol) notes that these values may depend on the FTP server implementation, the Unix variants seem to work well in practice, even for non-Unix servers.

    Nevertheless, it's recommended that you don't hardcode these values for remote paths, but use FTPHost.path as you would use os.path to write platform-independent Python code for local filesystems. Keep in mind that most, but not all, arguments of FTPHost methods refer to remote directories or files. For example, in FTPHost.upload, the first argument is a local path and the second a remote path. Both of these should use their respective path separators.

Remote file system navigation

  • getcwd()

    returns the absolute current directory on the remote host. This method works like os.getcwd.

  • chdir(directory)

    sets the current directory on the FTP server. This resembles os.chdir, as you may have expected.

Uploading and downloading files

  • upload(source, target, callback=None)

    copies a local source file (given by a filename, i. e. a string) to the remote host under the name target. Both source and target may be absolute paths or relative to their corresponding current directory (on the local or the remote host, respectively).

    The file content is always transferred in binary mode.

    The callback, if given, will be invoked for each transferred chunk of data:

    callback(chunk)
    

    where chunk is a bytestring. An example usage of a callback method is to display a progress indicator.

  • download(source, target, callback=None)

    performs a download from the remote source file to a local target file. Both source and target are strings. See the description of upload for more details.

  • upload_if_newer(source, target, callback=None)

    is similar to the upload method. The only difference is that the upload is only invoked if the time of the last modification for the source file is more recent than that of the target file or the target doesn't exist at all. The check for the last modification time considers the precision of the timestamps and transfers a file "if in doubt". Consequently the code

    ftp_host.upload_if_newer("source_file", "target_file")
    time.sleep(10)
    ftp_host.upload_if_newer("source_file", "target_file")
    

    might upload the file again if the timestamp of the target file is precise up to a minute, which is typically the case because the remote datetime is determined by parsing a directory listing from the server. To avoid unnecessary transfers, wait at least a minute between calls of upload_if_newer for the same file. If it still seems that a file is uploaded unnecessarily (or not when it should), read the subsection on time shift settings.

    If an upload actually happened, the return value of upload_if_newer is a True, else False.

    Note that the method only checks the existence and/or the modification time of the source and target file; it doesn't compare any other file properties, say, the file size.

    This also means that if a transfer is interrupted, the remote file will have a newer modification time than the local file, and thus the transfer won't be repeated if upload_if_newer is used a second time. There are at least two possibilities after a failed upload:

    • use upload instead of upload_if_newer, or
    • remove the incomplete target file with FTPHost.remove, then use upload or upload_if_newer to transfer it again.
  • download_if_newer(source, target, callback=None)

    corresponds to upload_if_newer but performs a download from the server to the local host. Read the descriptions of download and upload_if_newer for more information. If a download actually happened, the return value is True, else False.

Time zone correction

For upload_if_newer and download_if_newer to work correctly, the time zone of the server must be taken into account. By default, ftputil assumes that the timestamps in server listings are in UTC.

  • set_time_shift(time_shift)

    sets the so-called time shift value, measured in seconds. The time shift here is defined as the difference between the time used in server listings and UTC.

    time_shift = server_time - utc_time
    

    For example, a server in Berlin/Germany set to the local time (currently UTC+03:00), would require a time shift value of 3 * 3600.0 = 10800.0 seconds to be handled correctly by ftputil's upload_if_newer and download_if_newer, as well as the stat and lstat calls.

    Note that servers don't necessarily send their file system listings in their local time zone. Some use UTC, which actually makes sense because UTC doesn't lead to an ambiguity when there's a switch back from the daylight saving time to the "normal" time of the server location.

    If the time shift value is invalid, for example its absolute value is larger than 24 hours, a TimeShiftError is raised.

    Note

    Versions of ftputil before 4.0.0 used a different definition of "time shift", server_time - local_client_time.

    This had the advantage that the default of 0.0 would be correct if the server was set to the same time zone as the client where ftputil runs. On the other hand, this approach meant that the time shift depended on two time zones, not only the one used on the server side. This could be confusing if server and client didn't use the same time zone.

    See also synchronize_times for a way to set the time shift with a simple method call. If you can't use synchronize_times and the server uses the same time zone as the client, you can set the time shift value with

    set_time_shift(
      round( (datetime.datetime.now() - datetime.datetime.utcnow()).seconds, -2 )
    )
    
  • time_shift()

    returns the currently-set time shift value. See set_time_shift above for its definition.

  • synchronize_times()

    synchronizes the local times of the server and the client, so that upload_if_newer and download_if_newer work as expected, even if the client and the server use different time zones. For this to work, all of the following conditions must be true:

    • The connection between server and client is established.
    • The client has write access to the directory that is current when synchronize_times is called.

    If you can't fulfill these conditions, you can nevertheless set the time shift value explicitly with set_time_shift. Trying to call synchronize_times if the above conditions aren't met results in a TimeShiftError exception.

Creating and removing directories

  • mkdir(path, [mode])

    makes the given directory on the remote host. This does not construct "intermediate" directories that don't already exist. The mode parameter is ignored; this is for compatibility with os.mkdir if an FTPHost object is passed into a function instead of the os module. See the explanation in the subsection Exception hierarchy.

  • makedirs(path, [mode], exist_ok=False)

    works similar to mkdir (see above), but also makes intermediate directories like os.makedirs. The mode parameter is only there for compatibility with os.makedirs and is ignored.

    exist_ok controls whether the existence of any directory but the last in the path should be considered an error. If the default False is used or passed to makedirs, ftputil will raise a PermanentError if any directory but the last already exists.

  • rmdir(path)

    removes the given remote directory. If it's not empty, raise a PermanentError.

  • rmtree(path, ignore_errors=False, onerror=None)

    removes the given remote, possibly non-empty, directory tree. The interface of this method is rather complex, in favor of compatibility with shutil.rmtree.

    If ignore_errors is set to a true value, errors are ignored. If ignore_errors is a false value and onerror isn't set, all exceptions occurring during the tree iteration and processing are raised. These exceptions are all of type PermanentError.

    To distinguish between different kinds of errors, pass in a callable for onerror. This callable must accept three arguments: func, path and exc_info. func is a bound method object, for example your_host_object.listdir. path is the path that was the recent argument of the respective method (listdir, remove, rmdir). exc_info is the exception info as it is gotten from sys.exc_info.

    The code of rmtree is taken from Python's shutil module and adapted for ftputil.

Local caching of file system information

Many of the above methods need access to the remote file system to obtain data on directories and files. To get the most recent data, each call to lstat, stat, exists, getmtime etc. would require to fetch a directory listing from the server, which can make the program very slow. This effect is more pronounced for operations which mostly scan the file system rather than transferring file data.

For this reason, ftputil by default saves the results from directory listings locally and reuses those results. This reduces network accesses and so speeds up the software a lot. However, since data is more rarely fetched from the server, the risk of obsolete data also increases. This will be discussed below.

Caching can be controlled -- if necessary at all -- via the stat_cache object in an FTPHost's namespace. For example, after calling

ftp_host = ftputil.FTPHost(host, user, password)

the cache can be accessed as ftp_host.stat_cache.

While ftputil usually manages the cache quite well, there are two possible reasons for modifying cache parameters.

The first is when the number of possible entries is too low. You may notice that when you are processing very large directories and the program becomes much slower than before. It's common for code to read a directory with listdir and then process the found directories and files. This can also happen implicitly by a call to FTPHost.walk. Since version 2.6 ftputil automatically increases the cache size if directories with more entries than the current maximum cache size are to be scanned. Most of the time, this works fine.

However, if you need access to stat data for several directories at the same time, you may need to increase the cache explicitly. This is done by the resize method:

ftp_host.stat_cache.resize(20000)

where the argument is the maximum number of lstat results to store (the default is 5000, in versions before 2.6 it was 1000). Note that each path on the server, e. g. "/home/schwa/some_dir", corresponds to a single cache entry. Methods like exists or getmtime all derive their results from a previously fetched lstat result.

The value 5000 above means that the cache will hold at most 5000 entries (unless increased automatically by an explicit or implicit listdir call, see above). If more are about to be stored, the entries which haven't been used for the longest time will be deleted to make place for newer entries.

The second possible reason to change the cache parameters is to avoid stale cache data. Caching is so effective because it reduces network accesses. This can also be a disadvantage if the file system data on the remote server changes after a stat result has been retrieved; the client, when looking at the cached stat data, will use obsolete information.

There are two potential ways to get such out-of-date stat data. The first happens when an FTPHost instance modifies a file path for which it has a cache entry, e. g. by calling remove or rmdir. Such changes are handled transparently; the path will be deleted from the cache. A different matter are changes unknown to the FTPHost object which inspects its cache. Obviously, for example, these are changes by programs running on the remote host. On the other hand, cache inconsistencies can also occur if two FTPHost objects change a file system simultaneously:

with (
  ftputil.FTPHost(server, user1, password1) as ftp_host1,
  ftputil.FTPHost(server, user1, password1) as ftp_host2
):
    stat_result1 = ftp_host1.stat("some_file")
    stat_result2 = ftp_host2.stat("some_file")
    ftp_host2.remove("some_file")
    # `ftp_host1` will still see the obsolete cache entry!
    print(ftp_host1.stat("some_file"))
    # Will raise an exception since an `FTPHost` object
    # knows of its own changes.
    print(ftp_host2.stat("some_file"))

At first sight, it may appear to be a good idea to have a shared cache among several FTPHost objects. After some thinking, this turns out to be very error-prone. For example, it won't help with different processes using ftputil. So, if you have to deal with concurrent write/read accesses to a server, you have to handle them explicitly.

The most useful tool for this is the invalidate method. In the example above, it could be used like this:

with (
  ftputil.FTPHost(server, user1, password1) as ftp_host1,
  ftputil.FTPHost(server, user1, password1) as ftp_host2
):
    stat_result1 = ftp_host1.stat("some_file")
    stat_result2 = ftp_host2.stat("some_file")
    ftp_host2.remove("some_file")
    # Invalidate using an absolute path.
    absolute_path = ftp_host1.path.abspath(
                      ftp_host1.path.join(ftp_host1.getcwd(), "some_file"))
    ftp_host1.stat_cache.invalidate(absolute_path)
    # Will now raise an exception as it should.
    print(ftp_host1.stat("some_file"))
    # Would raise an exception since an `FTPHost` object
    # knows of its own changes, even without `invalidate`.
    print(ftp_host2.stat("some_file"))

The method invalidate can be used on any absolute path, be it a directory, a file or a link.

By default, the cache entries (if not replaced by newer ones) are stored for an infinite time. That is, if you start your Python process using ftputil and let it run for three days a stat call may still access cache data that old. To avoid this, you can set the max_age attribute:

with ftputil.FTPHost(server, user, password) as ftp_host:
    ftp_host.stat_cache.max_age = 60 * 60  # = 3600 seconds

This sets the maximum age of entries in the cache to an hour. This means any entry older won't be retrieved from the cache but its data instead fetched again from the remote host and then again stored for up to an hour. To reset max_age to the default of unlimited age, i. e. cache entries never expire, use None as value.

If you are certain that the cache will be in the way, you can disable and later re-enable it completely with disable and enable:

with ftputil.FTPHost(server, user, password) as ftp_host:
    ftp_host.stat_cache.disable()
    ...
    ftp_host.stat_cache.enable()

During that time, the cache won't be used; all data will be fetched from the network. After enabling the cache again, its entries will be the same as when the cache was disabled, that is, entries won't get updated with newer data during this period. Note that even when the cache is disabled, the file system data in the code can become inconsistent:

with ftputil.FTPHost(server, user, password) as ftp_host:
    ftp_host.stat_cache.disable()
    if ftp_host.path.exists("some_file"):
        mtime = ftp_host.path.getmtime("some_file")

In that case, the file some_file may have been removed by another process between the calls to exists and getmtime!

Iteration over directories

  • walk(top, topdown=True, onerror=None, followlinks=False)

    iterates over a directory tree, similar to os.walk. Actually, FTPHost.walk uses the code from Python with just the necessary modifications, so see the linked documentation.

  • path.walk(path, func, arg)

    Similar to os.path.walk, the walk method in FTPHost.path can be used, though FTPHost.walk is probably easier to use.

Other methods

  • close()

    closes the connection to the remote host. After this, no more interaction with the FTP server is possible with this FTPHost object. Usually you don't need to close an FTPHost instance with close if you set up the instance in a with statement.

  • rename(source, target)

    renames the source file (or directory) on the FTP server.

  • chmod(path, mode)

    sets the access mode (permission flags) for the given path. The mode is an integer as returned for the mode by the stat and lstat methods. Be careful: Usually, mode values are written as octal numbers, for example 0755 to make a directory readable and writable for the owner, but not writable for the group and others. If you want to use such octal values, rely on Python's support for them:

    ftp_host.chmod("some_directory", 0o755)
    

    Not all FTP servers support the chmod command. In case of an exception, how do you know if the path doesn't exist or if the command itself is invalid? If the FTP server complies with RFC 959, it should return a status code 502 if the SITE CHMOD command isn't allowed. ftputil maps this special error response to a CommandNotImplementedError which is derived from PermanentError.

    So you need to code like this:

    with ftputil.FTPHost(server, user, password) as ftp_host:
        try:
            ftp_host.chmod("some_file", 0o644)
        except ftputil.error.CommandNotImplementedError:
            # `chmod` not supported
            ...
        except ftputil.error.PermanentError:
            # Possibly a non-existent file
            ...
    

    Because the CommandNotImplementedError is more specific, you have to test for it first.

  • copyfileobj(source, target, length=64*1024)

    copies the contents from the file-like object source to the file-like object target. The only difference to shutil.copyfileobj is the default buffer size. Note that arbitrary file-like objects can be used as arguments (e. g. local files, remote FTP files).

    However, the interfaces of source and target have to match; the string type read from source must be an accepted string type when written to target. For example, if you open source in Python 3 as a local text file and target as a remote file object in binary mode, the transfer will fail since source.read gives unicode strings (str) whereas target.write only accepts byte strings (bytes).

    See File-like objects for the construction and use of remote file-like objects.

  • set_parser(parser)

    sets a custom parser for FTP directories. Note that you have to pass in a parser instance, not the class.

    An extra section shows how to write own parsers if the default parsers in ftputil don't work for you.

  • keep_alive()

    attempts to keep the connection to the remote server active in order to prevent timeouts from happening. This method is primarily intended to keep the underlying FTP connection of an FTPHost object alive while a file is uploaded or downloaded. This will require either an extra thread while the upload or download is in progress or calling keep_alive from a callback function.

    The keep_alive method won't help if the connection has already timed out. In this case, a ftputil.error.TemporaryError is raised.

    If you want to use this method, keep in mind that FTP servers define a timeout for a reason. A timeout prevents running out of server connections because of clients that never disconnect on their own.

    Note that the keep_alive method does not affect the "hidden" FTP child connections established by FTPHost.open (see section FTPHost instances vs. FTP connections for details). You can't use keep_alive to avoid a timeout in a stalling transfer like this:

    with ftputil.FTPHost(server, userid, password) as ftp_host:
        with ftp_host.open("some_remote_file", "rb") as fobj:
            data = fobj.read(100)
            # _Futile_ attempt to avoid file connection timeout.
            for i in range(15):
                time.sleep(60)
                ftp_host.keep_alive()
            # Will raise an `ftputil.error.TemporaryError`.
            data += fobj.read()
    

File-like objects

Construction

Basics

FTPFile objects are returned by a call to FTPHost.open; never use the FTPFile constructor directly.

The APIs for remote file-like objects is modeled after the APIs of the built-in open function and its return value.

  • FTPHost.open(path, mode="r", buffering=None, encoding=None, errors=None, newline=None, rest=None)

    returns a file-like object that refers to the path on the remote host. This path may be absolute or relative to the current directory on the remote host (this directory can be determined with the getcwd method). As with local file objects, the default mode is "r", i. e. reading text files. Valid modes are "r", "rb", "w", and "wb".

    If a file is opened in binary mode, you must not specify an encoding. On the other hand, if you open a file in text mode, an encoding is used. By default, this is the return value of locale.getpreferredencoding, but you can (and probably should) specify a distinct encoding.

    If you open a file in binary mode, the read and write operations use bytes objects. That is, read operations return bytes and write operations only accept bytes.

    Similarly, text files always work with strings (str). Here, read operations return string and write operations only accept strings.

    The arguments buffering, errors and newline have the same semantics as in open.

    If the file is opened in binary mode, you may pass 0 or a positive integer for the rest argument. The argument is passed to the underlying FTP session instance (for example an instance of ftplib.FTP) to start reading or writing at the given byte offset. For example, if a remote file contains the letters "abcdef" in ASCII encoding, rest=3 will start reading at "d".

    Warning

    If you pass rest values which point after the file, the behavior is undefined and may even differ from one FTP server to another. Therefore, use the rest argument only for error recovery in case of interrupted transfers. You need to keep track of the transferred data so that you can provide a valid rest argument for a resumed transfer.

FTPHost.open can also be used in a with statement:

import ftputil

with ftputil.FTPHost(...) as ftp_host:
    ...
    with ftp_host.open("new_file", "w", encoding="utf8") as fobj:
        fobj.write("This is some text.")

At the end of the with block, the remote file will be closed automatically.

If something goes wrong during the construction of the file or in the body of the with statement, the file will be closed as well. Exceptions will be propagated as with try ... finally.

Attributes and methods

The methods

close()
read([count])
readline([count])
readlines()
write(data)
writelines(string_sequence)

and the attribute closed have the same semantics as for file objects of a local disk file system. The iterator protocol is supported as well, i. e. you can use a loop to read a file line by line:

with ftputil.FTPHost(server, user, password) as ftp_host:
    with ftp_host.open("some_file") as input_file:
        for line in input_file:
            # Do something with the line, e. g.
            print(line.strip().replace("ftplib", "ftputil"))

For more on file objects, see the section File objects in the Python Library Reference.

FTPHost instances vs. FTP connections

This section explains why keeping an FTPHost instance "alive" without timing out sometimes isn't trivial. If you always finish your FTP operations in time, you don't need to read this section.

The file transfer protocol is a stateful protocol. That means an FTP connection always is in a certain state. Each of these states can only change to certain other states under certain conditions triggered by the client or the server.

One of the consequences is that a single FTP connection can't be used at the same time, say, to transfer data on the FTP data channel and to create a directory on the remote host.

For example, consider this:

>>> import ftplib
>>> ftp = ftplib.FTP(server, user, password)
>>> ftp.pwd()
'/'
>>> # Start transfer. `CONTENTS` is a text file on the server.
>>> socket = ftp.transfercmd("RETR CONTENTS")
>>> socket
<socket._socketobject object at 0x7f801a6386e0>
>>> ftp.pwd()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python2.7/ftplib.py", line 578, in pwd
    return parse257(resp)
  File "/usr/lib64/python2.7/ftplib.py", line 842, in parse257
    raise error_reply, resp
ftplib.error_reply: 226-File successfully transferred
226 0.000 seconds (measured here), 5.60 Mbytes per second
>>>

Note that ftp is a single FTP connection, represented by an ftplib.FTP instance, not an ftputil.FTPHost instance.

On the other hand, consider this:

>>> import ftputil
>>> ftp_host = ftputil.FTPHost(server, user, password)
>>> ftp_host.getcwd()
>>> fobj = ftp_host.open("CONTENTS")
>>> fobj
<ftputil.file.FTPFile object at 0x7f8019d3aa50>
>>> ftp_host.getcwd()
u'/'
>>> fobj.readline()
u'Contents of FTP test directory\n'
>>> fobj.close()
>>>

To be able to start a file transfer (i. e. open a remote file for reading or writing) and still be able to use other FTP commands, ftputil uses a trick. For every remote file, ftputil creates a new FTP connection, called a child connection in the ftputil source code. (Actually, FTP connections belonging to closed remote files are re-used if they haven't timed out yet.)

In most cases this approach isn't noticeable by code using ftputil. However, the nice abstraction of dealing with a single FTP connection falls apart if one of the child connections times out. For example, if you open a remote file and work only with the initial "main" connection to navigate the file system, the FTP connection for the remote file may eventually time out.

While it's often relatively easy to prevent the "main" connection from timing out it's unfortunately practically impossible to do this for a remote file connection (apart from transferring some data, of course). For this reason, FTPHost.keep_alive affects only the main connection. Child connections may still time out if they're idle for too long.

Some more details:

  • A kind of "straightforward" way of keeping the main connection alive would be to call ftp_host.getcwd(). However, this doesn't work because ftputil caches the current directory and returns it without actually contacting the server. That's the main reason why there's a keep_alive method since it calls pwd on the FTP connection (i. e. the session object), which isn't a public attribute.
  • Some servers define not only an idle timeout but also a transfer timeout. This means the connection times out unless there's some transfer on the data channel for this connection. So ftputil's keep_alive doesn't prevent this timeout, but an ftp_host.listdir(ftp_host.curdir) call should do it. However, this transfers the data for the whole directory listing which might take some time if the directory has many entries.

Bottom line: If you can, you should organize your FTP actions so that you finish everything before a timeout happens.

Writing directory parsers

ftputil recognizes the two most widely-used FTP directory formats, Unix and MS style, and adjusts itself automatically. Almost every FTP server uses one of these formats.

However, if your server uses a format which is different from the two provided by ftputil, you can plug in a custom parser with a single method call and have ftputil use this parser.

For this, you need to write a parser class by inheriting from the class Parser in the ftputil.stat module. Here's an example:

import ftputil.error
import ftputil.stat

class XyzParser(ftputil.stat.Parser):
    """
    Parse the default format of the FTP server of the XYZ
    corporation.
    """

    def parse_line(self, line, time_shift=0.0):
        """
        Parse a `line` from the directory listing and return a
        corresponding `StatResult` object. If the line can't
        be parsed, raise `ftputil.error.ParserError`.

        The `time_shift` argument can be used to fine-tune the
        parsing of dates and times. See the class
        `ftputil.stat.UnixParser` for an example.
        """
        # Split the `line` argument and examine it further; if
        # something goes wrong, raise an `ftputil.error.ParserError`.
        ...
        # Make a `StatResult` object from the parts above.
        stat_result = ftputil.stat.StatResult(...)
        # `_st_name`, `_st_target` and `_st_mtime_precision` are optional.
        stat_result._st_name = ...
        stat_result._st_target = ...
        stat_result._st_mtime_precision = ...
        return stat_result

    # Define `ignores_line` only if the default in the base class
    # doesn't do enough!
    def ignores_line(self, line):
        """
        Return a true value if the line should be ignored. For
        example, the implementation in the base class handles
        lines like "total 17". On the other hand, if the line
        should be used for stat'ing, return a false value.
        """
        is_total_line = super().ignores_line(line)
        my_test = ...
        return is_total_line or my_test

A StatResult object is similar to the value returned by os.stat and is usually built with statements like

stat_result = StatResult(
                (st_mode, st_ino, st_dev, st_nlink, st_uid,
                 st_gid, st_size, st_atime, st_mtime, st_ctime))
stat_result._st_name = ...
stat_result._st_target = ...
stat_result._st_mtime_precision = ...

with the arguments of the StatResult constructor described in the following table.

Index Attribute os.stat type StatResult type Notes
0 st_mode int int  
1 st_ino long long  
2 st_dev long long  
3 st_nlink int int  
4 st_uid int str usually only available as string
5 st_gid int str usually only available as string
6 st_size long long  
7 st_atime int/float float  
8 st_mtime int/float float  
9 st_ctime int/float float  
- _st_name - str file name without directory part
- _st_target - str link target (may be absolute or relative)
- _st_mtime_precision - int st_mtime precision in seconds

If you can't extract all the desirable data from a line (for example, the MS format doesn't contain any information about the owner of a file), set the corresponding values in the StatResult instance to None.

Parser classes can use several helper methods which are defined in the class Parser:

  • parse_unix_mode parses strings like "drwxr-xr-x" and returns an appropriate st_mode integer value.
  • parse_unix_time returns a float number usable for the st_...time values by parsing arguments like "Nov"/"23"/"02:33" or "May"/"26"/"2005". Note that the method expects the timestamp string already split at whitespace.
  • parse_ms_time parses arguments like "10-23-01"/"03:25PM" and returns a float number like from time.mktime. Note that the method expects the timestamp string already split at whitespace.

Additionally, there's an attribute _month_numbers which maps lowercase three-letter month abbreviations to integers.

For more details, see the two "standard" parsers UnixParser and MSParser in the module ftputil/stat.py.

To actually use the parser, call the method set_parser of the FTPHost instance.

If you can't write a parser or don't want to, please ask on the ftputil mailing list. Possibly someone has already written a parser for your server or can help with it.

FAQ / Tips and tricks

Where can I get the latest version?

See the download page. Announcements will be sent to the mailing list. Announcements on major updates will also be posted to the Python announcements list.

Is there a mailing list on ftputil?

Yes, please visit https://ftputil.sschwarzer.net/mailinglist to subscribe or read the archives.

Though you can technically post without subscribing first I can't recommend it: The mails from non-subscribers have to be approved by me and because the arriving mails contain lots of spam, I rarely go through these mails.

I found a bug! What now?

Before reporting a bug, make sure that you already read this manual and tried the latest version of ftputil. There the bug might have already been fixed.

Please see https://ftputil.sschwarzer.net/issuetrackernotes for guidelines on entering a bug in ftputil's ticket system. If you are unsure if the behaviour you found is a bug or not, you should write to the ftputil mailing list. Never include confidential information (user id, password, file names, etc.) in the problem report! Be careful!

Does ftputil support TLS?

ftputil has no built-in TLS support.

On the other hand, there are two ways to get TLS support with ftputil:

  • The ftplib library has a class FTP_TLS that you can use for the session_factory keyword argument in the FTPHost constructor. You can't use the class directly though if you need additional setup code in comparison to ftplib.FTP, for example calling prot_p, to secure the data connection. On the other hand, ftputil.session.session_factory can be used to create a custom session factory.

  • If you have other requirements that session_factory can't fulfill, you may create your own session factory by inheriting from ftplib.FTP_TLS:

    import ftplib
    
    import ftputil
    
    
    class FTPTLSSession(ftplib.FTP_TLS):
    
        def __init__(self, host, user, password):
            ftplib.FTP_TLS.__init__(self)
            self.connect(host, port)
            self.login(user, password)
            # Set up encrypted data connection.
            self.prot_p()
            ...
    
    # Note the `session_factory` parameter. Pass the class, not
    # an instance.
    with ftputil.FTPHost(server, user, password,
                         session_factory=FTPTLSSession) as ftp_host:
        # Use `ftp_host` as usual.
        ...
    

How do I connect to a non-default port?

By default, an instantiated FTPHost object connects on the usual FTP port. If you have to use a different port, refer to the section Session factories.

How can I debug an FTP connection problem?

You can do this with a session factory. See Session factories.

If you want to change the debug level only temporarily after the connection is established, you can reach the session object as the _session attribute of the FTPHost instance and call _session.set_debuglevel. Note that the _session attribute should only be accessed for debugging. Calling arbitrary ftplib.FTP methods on the session object may cause bugs!

Conditional upload/download to/from a server in a different time zone

You may find that ftputil uploads or downloads files unnecessarily, or not when it should. Please see the section on time zone correction. It may even be sufficient to call synchronize_times.

When I use ftputil, all I get is a ParserError exception

The FTP server you connect to may use a directory format that ftputil doesn't understand. You can either write and plug in your own parser or ask on the mailing list for help.

I don't find an answer to my problem in this document

Please send an email with your problem report or question to the ftputil mailing list, and we'll see what we can do for you. :-)

Bugs and limitations

  • ftputil needs at least Python 3.6 to work.
  • Whether ftputil "sees" "hidden" directory and file names (i. e. names starting with a dot) depends on the configuration of the FTP server. See Hidden files and directories for details.
  • Due to the implementation of lstat it can not return a sensible value for the root directory / though stat'ing entries in the root directory isn't a problem. If you know an implementation that can do this, please let me know. The root directory is handled appropriately in FTPHost.path.exists/isfile/isdir/islink, though.
  • In multithreaded programs, you can have each thread use one or more FTPHost instances as long as no instance is shared with other threads.
  • Currently, it is not possible to continue an interrupted upload or download. Contact me if this causes problems for you.
  • There's exactly one cache for lstat results for each FTPHost object, i. e. there's no sharing of cache results determined by several FTPHost objects. See Local caching of file system information for the reasons.

Files

If not overwritten via installation options, the ftputil files reside in the ftputil package. There's also documentation in reStructuredText and in HTML format. The locations of these files after installation is system-dependent.

The files test_*.py and scripted_session.py are for unit-testing. If you only use ftputil, i. e. don't modify it, you can delete these files.

References

Authors

ftputil is written by Stefan Schwarzer <sschwarzer@sschwarzer.net> and contributors (see doc/contributors.txt).

The original lrucache module was written by Evan Prodromou <evan@prodromou.name>.

Feedback is appreciated. :-)

././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1643211003.3498569 ftputil-5.0.3/doc/ftputil.txt0000664000175000017500000020115200000000000015203 0ustar00schwaschwa``ftputil`` -- a high-level FTP client library ============================================== :Version: 5.0.3 :Date: 2022-01-26 :Summary: high-level FTP client library for Python :Keywords: FTP, ``ftplib`` substitute, virtual filesystem, pure Python :Author: Stefan Schwarzer .. contents:: Introduction ------------ The ``ftputil`` module is a high-level interface to the ftplib_ module. The `FTPHost objects`_ generated from it allow many operations similar to those of os_, `os.path`_ and `shutil`_. .. _ftplib: https://docs.python.org/library/ftplib.html .. _os: https://docs.python.org/library/os.html .. _`os.stat`: https://docs.python.org/library/os.html#os.stat .. _`os.path`: https://docs.python.org/library/os.path.html .. _`shutil`: https://docs.python.org/library/shutil.html Example:: import ftputil # Download some files from the login directory. with ftputil.FTPHost("ftp.domain.com", "user", "password") as ftp_host: names = ftp_host.listdir(ftp_host.curdir) for name in names: if ftp_host.path.isfile(name): ftp_host.download(name, name) # remote, local # Make a new directory and copy a remote file into it. ftp_host.mkdir("newdir") with ftp_host.open("index.html", "rb") as source: with ftp_host.open("newdir/index.html", "wb") as target: ftp_host.copyfileobj(source, target) # similar to shutil.copyfileobj Also, there are `FTPHost.lstat`_ and `FTPHost.stat`_ to request size and modification time of a file. The latter can also follow links, similar to `os.stat`_. `FTPHost.walk`_ and `FTPHost.path.walk`_ work, too. ``ftputil`` features -------------------- * Method names are familiar from Python's ``os``, ``os.path`` and ``shutil`` modules. For example, use ``os.path.join`` to join paths for a local file system and ``ftp_host.path.join`` to join paths for a remote FTP file system. * Remote file system navigation (``getcwd``, ``chdir``) * Upload and download files (``upload``, ``upload_if_newer``, ``download``, ``download_if_newer``) * Time zone synchronization between client and server (needed for ``upload_if_newer`` and ``download_if_newer``) * Create and remove directories (``mkdir``, ``makedirs``, ``rmdir``, ``rmtree``) and remove files (``remove``) * Get information about directories, files and links (``listdir``, ``stat``, ``lstat``, ``exists``, ``isdir``, ``isfile``, ``islink``, ``abspath``, ``dirname``, ``basename`` etc.) * Iterate over remote file systems (``walk``) * Local caching of results from ``lstat`` and ``stat`` calls to reduce network access (also applies to ``exists``, ``getmtime`` etc.). * Read files from and write files to remote hosts via file-like objects (``FTPHost.open``; the generated file-like objects have the familiar methods like ``read``, ``readline``, ``readlines``, ``write``, ``writelines`` and ``close``. You can also iterate over these files line by line in a ``for`` loop. Exception hierarchy ------------------- The exceptions are in the namespace of the ``ftputil.error`` module, e. g. ``ftputil.error.TemporaryError``. The exception classes are organized as follows:: FTPError FTPOSError(FTPError, OSError) PermanentError(FTPOSError) CommandNotImplementedError(PermanentError) TemporaryError(FTPOSError) FTPIOError(FTPError) InternalError(FTPError) InaccessibleLoginDirError(InternalError) NoEncodingError(InternalError) ParserError(InternalError) RootDirError(InternalError) TimeShiftError(InternalError) and are described here: - ``FTPError`` is the root of the exception hierarchy of the module. - ``FTPOSError`` is derived from ``OSError``. This is for similarity between the os module and ``FTPHost`` objects. Compare :: try: os.chdir("nonexisting_directory") except OSError: ... with :: host = ftputil.FTPHost("host", "user", "password") try: host.chdir("nonexisting_directory") except OSError: ... Imagine a function :: def func(path, file): ... which works on the local file system and catches ``OSErrors``. If you change the parameter list to :: def func(path, file, os=os): ... where ``os`` denotes the ``os`` module, you can call the function also as :: host = ftputil.FTPHost("host", "user", "password") func(path, file, os=host) to use the same code for both a local and remote file system. Another similarity between ``OSError`` and ``FTPOSError`` is that the latter holds the FTP server return code in the ``errno`` attribute of the exception object and the error text in ``strerror``. - ``PermanentError`` is raised for 5xx return codes from the FTP server. This corresponds to ``ftplib.error_perm`` (though ``PermanentError`` and ``ftplib.error_perm`` are *not* identical). - ``CommandNotImplementedError`` indicates that an underlying command the code tries to use is not implemented. For an example, see the description of the `FTPHost.chmod`_ method. - ``TemporaryError`` is raised for FTP return codes from the 4xx category. This corresponds to ``ftplib.error_temp`` (though ``TemporaryError`` and ``ftplib.error_temp`` are *not* identical). - ``FTPIOError`` denotes an I/O error on the remote host. This appears mainly with file-like objects that are retrieved by calling ``FTPHost.open``. Compare :: >>> try: ... f = open("not_there") ... except IOError as obj: ... print(obj.errno) ... print(obj.strerror) ... 2 No such file or directory with :: >>> ftp_host = ftputil.FTPHost("host", "user", "password") >>> try: ... f = ftp_host.open("not_there") ... except IOError as obj: ... print(obj.errno) ... print(obj.strerror) ... 550 550 not_there: No such file or directory. As you can see, both code snippets are similar. However, the error codes aren't the same. - ``InternalError`` subsumes exception classes for signaling errors due to limitations of the FTP protocol or the concrete implementation of ``ftputil``. - ``InaccessibleLoginDirError`` This exception is raised if the directory in which "you" are placed upon login is not accessible, i. e. a ``chdir`` call with the directory as argument would fail. - ``NoEncodingError`` is raised if an FTP session instance doesn't have an ``encoding`` attribute (see also `session factories`_). - ``ParserError`` is used for errors during the parsing of directory listings from the server. This exception is used by the ``FTPHost`` methods ``stat``, ``lstat``, and ``listdir``. - ``RootDirError`` Because of the implementation of the ``lstat`` method it is not possible to do a ``stat`` call on the root directory ``/``. If you know *any* way to do it, please let me know. :-) This problem does *not* affect stat calls on items *in* the root directory. - ``TimeShiftError`` is used to denote errors which relate to setting the `time shift`_. ``FTPHost`` objects ------------------- .. _`FTPHost construction`: Construction ~~~~~~~~~~~~ Introduction ```````````` ``FTPHost`` instances can be created with the following call:: ftp_host = ftputil.FTPHost(server, user, password, account, session_factory=ftplib.FTP) The first four parameters are strings with the same meaning as for the FTP class in the ``ftplib`` module. Usually the ``account`` and ``session_factory`` arguments aren't needed though. ``FTPHost`` objects can also be used in a ``with`` statement:: import ftputil with ftputil.FTPHost(server, user, password) as ftp_host: print(ftp_host.listdir(ftp_host.curdir)) After the ``with`` block, the ``FTPHost`` instance and the associated FTP sessions will be closed automatically. If something goes wrong during the ``FTPHost`` construction or in the body of the ``with`` statement, the instance is closed as well. Exceptions will be propagated (as with ``try ... finally``). .. _`session factory`: Session factories ````````````````` The keyword argument ``session_factory`` may be used to generate FTP connections with other factories than the default ``ftplib.FTP``. For example, the standard library of Python 3 contains a class ``ftplib.FTP_TLS`` which extends ``ftplib.FTP`` to use an encrypted connection. In fact, all positional and keyword arguments other than ``session_factory`` are passed to the factory to generate a new background session. This also happens for every remote file that is opened; see below. This functionality of the constructor also allows to wrap ``ftplib.FTP`` objects to do something that wouldn't be possible with the ``ftplib.FTP`` constructor alone. As an example, assume you want to connect to another than the default port, but ``ftplib.FTP`` only offers this by means of its ``connect`` method, not via its constructor. One solution is to use a custom class as a session factory:: import ftplib import ftputil EXAMPLE_PORT = 50001 class MySession(ftplib.FTP): def __init__(self, host, userid, password, port): """Act like ftplib.FTP's constructor but connect to another port.""" ftplib.FTP.__init__(self) self.connect(host, port) self.login(userid, password) # Try _not_ to use an _instance_ `MySession()` as factory, - # use the class itself. with ftputil.FTPHost(host, userid, password, port=EXAMPLE_PORT, session_factory=MySession) as ftp_host: # Use `ftp_host` as usual. ... On login, the format of the directory listings (needed for stat'ing files and directories) should be determined automatically. If not, please `enter a ticket`_. .. _`enter a ticket`: https://ftputil.sschwarzer.net/issuetrackernotes For the most common uses you don't need to create your own session factory class though. The ``ftputil.session`` module has a function ``session_factory`` that can create session factories for a variety of parameters:: session_factory(base_class=ftplib.FTP, port=21, use_passive_mode=None, encrypt_data_channel=True, encoding=None, debug_level=None) with - ``base_class`` is a base class to inherit a new session factory class from. By default, this is ``ftplib.FTP`` from the Python standard library. - ``port`` is the command channel port. The default is 21, used in most FTP server configurations. - ``use_passive_mode`` is either a boolean that determines whether passive mode should be used or ``None``. ``None`` means to let the base class choose active or passive mode. - ``encrypt_data_channel`` defines whether to encrypt the data channel for secure connections. This is only supported for the base classes ``ftplib.FTP_TLS`` and ``M2Crypto.ftpslib.FTP_TLS``, otherwise the parameter is ignored. - ``encoding`` can be a string to set the encoding of directory and file paths on the remote server. (This has nothing to do with the encoding of file contents!) If you pass a string and your base class is neither ``ftplib.FTP`` nor ``ftplib.FTP_TLS``, the used heuristic in ``session_factory`` may not work reliably. Therefore, if in doubt, let ``encoding`` be ``None`` and define your ``base_class`` so that it sets the encoding you want. Note: In Python 3.9, the default path encoding for ``ftplib.FTP`` and ``ftplib.FTP_TLS`` changed from previously "latin-1" to "utf-8". Hence, if you don't pass an ``encoding`` to ``session_factory``, you'll get different path encodings for Python 3.8 and earlier vs. Python 3.9 and later. If you're sure that you always use only ASCII characters in your remote paths, you don't need to worry about the path encoding and don't need to use the ``encoding`` argument. - ``debug_level`` sets the debug level for FTP session instances. The semantics is defined by the base class. For example, a debug level of 2 causes the most verbose output for Python's ``ftplib.FTP`` class. All of these parameters can be combined. For example, you could use :: import ftplib import ftputil import ftputil.session my_session_factory = ftputil.session.session_factory( base_class=ftpslib.FTP_TLS, port=31, encrypt_data_channel=True, encoding="UTF-8", debug_level=2) with ftputil.FTPHost(server, user, password, session_factory=my_session_factory) as ftp_host: ... to create and use a session factory derived from ``ftplib.FTP_TLS`` that connects on command channel 31, will encrypt the data channel, use the UTF-8 encoding for remote paths and print output for debug level 2. Note: Generally, you can achieve everything you can do with ``ftputil.session.session_factory`` with an explicit session factory as described at the start of this section. Directory and file names ~~~~~~~~~~~~~~~~~~~~~~~~ .. note:: Keep in mind that this section only applies to directory and file *names*, not file *contents*. Encoding and decoding for file contents is handled by the ``encoding`` argument for `FTPHost.open`_. Generally, paths can be ``str`` or ``bytes`` objects (or `PathLike`_ objects wrapping ``str`` or ``bytes``). However, you can't mix different string types (``bytes`` and ``str``) in one call (for example in ``FTPHost.path.join``). If a method gets a string argument (or a string argument wrapped in a PathLike_ object) and returns one or more strings, these strings will have the same string type (``bytes`` or ``str``) as the argument(s). Mixing different string types in one call (for example in ``FTPHost.path.join``) isn't allowed and will cause a ``TypeError``. These rules are the same as for local file system operations. .. _PathLike: https://docs.python.org/3/library/os.html#os.PathLike Although you can pass paths as ``str`` or ``bytes``, the former is recommended. See below for the reason. *If* you have directory or file names with non-ASCII characters, you need to be aware of the encoding the `session factory`_ (e. g. ``ftplib.FTP``) uses. This needs to be the same encoding that the FTP server uses for the paths. The following diagram shows string conversions on the way from your code to the remote FTP server. The opposite way works analogously, so encoding steps in the diagram become decoding steps and decoding steps in the diagram become encoding steps. Both "branching points" in the upper and lower part of diagrams are independent, so depending on how you pass paths to ftputil and which file system API the FTP server uses, there are four possible combinations. :: +-----------+ +-----------+ | Your code | | Your code | +-----------+ +-----------+ | | | str | bytes v v +-------------+ +-------------+ decode with encoding of session, | ftputil API | | ftputil API | e. g. `ftplib.FTP` instance +-------------+ +-------------+ \ / \ str / v v +---------------+ encode with encoding | ftplib API | specified in `FTP` instance +---------------+ | | bytes v +-------------+ | socket API | +-------------+ / \ / \ local / client - - - - - / - - - - - \ - - - - - - - - - - - - - - - - - - - - - - / \ remote / server / bytes \ v v +------------+ +------------+ decode with encoding from | FTP server | | FTP server | FTP server configuration +------------+ +------------+ | | | bytes | str v v +-------------+ +-------------+ | remote file | | remote file | | system API | | system API | +-------------+ +-------------+ \ / \ bytes / v v +-------------------+ | file system | +-------------------+ As you can see at the top of the diagram, if you use ``str`` objects (regular unicode strings), there's one fewer decoding step, and so one fewer source of problems. If you use ``bytes`` objects for paths, ftputil tries to get the encoding for the FTP server from the ``encoding`` attribute of the session instance (say, an instance of ``ftplib.FTP``). If no ``encoding`` attribute is present, a ``NoEncodingError`` is raised. All encoding/decoding steps must use the same encoding, the encoding the server uses (at the bottom of the diagram). If the server uses the bytes from the socket directly, i. e. without an encoding step, you have to use the file system encoding. Until and including Python 3.8, the encoding implicitly assumed by the ``ftplib`` module was latin-1, so using ``bytes`` was the safest strategy. However, Python 3.9 made the ``encoding`` configurable via an ``ftplib.FTP`` constructor argument ``encoding``, *but defaults to UTF-8*. If you don't pass a `session factory`_ to the ``ftputil.FTPHost`` constructor, ftputil will use latin-1 encoding for the paths. This is the same value as in earlier ftputil versions in combination with Python 3.8 and earlier. Summary: - If possible, use only ASCII characters in paths. - If possible, pass paths to ftputil as ``str``, not ``bytes``. - If you use a custom session factory, the session instances created by the factory must have an ``encoding`` attribute with the name of the path encoding to use. If your session instances don't have an ``encoding`` attribute, ftputil raises a ``NoEncodingError`` when the session is created. Hidden files and directories ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Whether ftputil sees "hidden" files and directories (usually files or directories whose names start with a dot) depends on the FTP server configuration. By default, ftputil does *not* use the ``-a`` option in the FTP ``LIST`` command to find hidden files. To tell the server to list hidden directories and files, set ``FTPHost.use_list_a_option`` to ``True``:: ftp_host = ftputil.FTPHost(server, user, password, account, session_factory=ftplib.FTP) ftp_host.use_list_a_option = True Caveats: - If the server doesn't understand the ``-a`` option at all, the server may interpret ``-a`` as the name of a file or directory, which can result in odd behavior. Therefore, use ``-a`` only if you're sure the server you're talking to supports it. Another approach is to have test code for ``-a`` support and fall back to not using the option. - Even if the server knows about the ``-a`` option, the server may be configured to ignore it. ``FTPHost`` attributes and methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Attributes `````````` - ``curdir``, ``pardir``, ``sep`` are strings which denote the current and the parent directory on the remote server. ``sep`` holds the path separator. Though `RFC 959`_ (File Transfer Protocol) notes that these values may depend on the FTP server implementation, the Unix variants seem to work well in practice, even for non-Unix servers. Nevertheless, it's recommended that you don't hardcode these values for remote paths, but use `FTPHost.path`_ as you would use ``os.path`` to write platform-independent Python code for local filesystems. Keep in mind that most, *but not all*, arguments of ``FTPHost`` methods refer to remote directories or files. For example, in `FTPHost.upload`_, the first argument is a local path and the second a remote path. Both of these should use their respective path separators. .. _`FTPHost.upload`: `Uploading and downloading files`_ Remote file system navigation ````````````````````````````` - ``getcwd()`` returns the absolute current directory on the remote host. This method works like ``os.getcwd``. - ``chdir(directory)`` sets the current directory on the FTP server. This resembles ``os.chdir``, as you may have expected. .. _`callback function`: Uploading and downloading files ``````````````````````````````` - ``upload(source, target, callback=None)`` copies a local source file (given by a filename, i. e. a string) to the remote host under the name target. Both ``source`` and ``target`` may be absolute paths or relative to their corresponding current directory (on the local or the remote host, respectively). The file content is always transferred in binary mode. The callback, if given, will be invoked for each transferred chunk of data:: callback(chunk) where ``chunk`` is a bytestring. An example usage of a callback method is to display a progress indicator. - ``download(source, target, callback=None)`` performs a download from the remote source file to a local target file. Both ``source`` and ``target`` are strings. See the description of ``upload`` for more details. .. _`upload_if_newer`: - ``upload_if_newer(source, target, callback=None)`` is similar to the ``upload`` method. The only difference is that the upload is only invoked if the time of the last modification for the source file is more recent than that of the target file or the target doesn't exist at all. The check for the last modification time considers the precision of the timestamps and transfers a file "if in doubt". Consequently the code :: ftp_host.upload_if_newer("source_file", "target_file") time.sleep(10) ftp_host.upload_if_newer("source_file", "target_file") might upload the file again if the timestamp of the target file is precise up to a minute, which is typically the case because the remote datetime is determined by parsing a directory listing from the server. To avoid unnecessary transfers, wait at least a minute between calls of ``upload_if_newer`` for the same file. If it still seems that a file is uploaded unnecessarily (or not when it should), read the subsection on `time shift`_ settings. If an upload actually happened, the return value of ``upload_if_newer`` is a ``True``, else ``False``. Note that the method only checks the existence and/or the modification time of the source and target file; it doesn't compare any other file properties, say, the file size. This also means that if a transfer is interrupted, the remote file will have a newer modification time than the local file, and thus the transfer won't be repeated if ``upload_if_newer`` is used a second time. There are at least two possibilities after a failed upload: - use ``upload`` instead of ``upload_if_newer``, or - remove the incomplete target file with ``FTPHost.remove``, then use ``upload`` or ``upload_if_newer`` to transfer it again. .. _`download_if_newer`: - ``download_if_newer(source, target, callback=None)`` corresponds to ``upload_if_newer`` but performs a download from the server to the local host. Read the descriptions of download and ``upload_if_newer`` for more information. If a download actually happened, the return value is ``True``, else ``False``. .. _`time shift`: .. _`time zone correction`: Time zone correction ```````````````````` For ``upload_if_newer`` and ``download_if_newer`` to work correctly, the time zone of the server must be taken into account. By default, ftputil assumes that the timestamps in server listings are in UTC_. .. _UTC: https://en.wikipedia.org/wiki/Utc .. _`set_time_shift`: - ``set_time_shift(time_shift)`` sets the so-called time shift value, measured in seconds. The time shift here is defined as the difference between the time used in server listings and UTC. :: time_shift = server_time - utc_time For example, a server in Berlin/Germany set to the local time (currently UTC+03:00), would require a time shift value of 3 * 3600.0 = 10800.0 seconds to be handled correctly by ftputil's ``upload_if_newer`` and ``download_if_newer``, as well as the ``stat`` and ``lstat`` calls. Note that servers don't necessarily send their file system listings in their local time zone. Some use UTC, which actually makes sense because UTC doesn't lead to an ambiguity when there's a switch back from the daylight saving time to the "normal" time of the server location. If the time shift value is invalid, for example its absolute value is larger than 24 hours, a ``TimeShiftError`` is raised. .. note:: Versions of ftputil before 4.0.0 used a different definition of "time shift", server_time - local_client_time. This had the advantage that the default of 0.0 would be correct *if* the server was set to the same time zone as the client where ftputil runs. On the other hand, this approach meant that the time shift depended on *two* time zones, not only the one used on the server side. This could be confusing if server and client *didn't* use the same time zone. See also `synchronize_times`_ for a way to set the time shift with a simple method call. If you can't use ``synchronize_times`` *and* the server uses the same time zone as the client, you can set the time shift value with :: set_time_shift( round( (datetime.datetime.now() - datetime.datetime.utcnow()).seconds, -2 ) ) - ``time_shift()`` returns the currently-set time shift value. See ``set_time_shift`` above for its definition. .. _`synchronize_times`: - ``synchronize_times()`` synchronizes the local times of the server and the client, so that `upload_if_newer`_ and `download_if_newer`_ work as expected, even if the client and the server use different time zones. For this to work, *all* of the following conditions must be true: - The connection between server and client is established. - The client has write access to the directory that is current when ``synchronize_times`` is called. If you can't fulfill these conditions, you can nevertheless set the time shift value explicitly with `set_time_shift`_. Trying to call ``synchronize_times`` if the above conditions aren't met results in a ``TimeShiftError`` exception. Creating and removing directories ````````````````````````````````` - ``mkdir(path, [mode])`` makes the given directory on the remote host. This does *not* construct "intermediate" directories that don't already exist. The ``mode`` parameter is ignored; this is for compatibility with ``os.mkdir`` if an ``FTPHost`` object is passed into a function instead of the ``os`` module. See the explanation in the subsection `Exception hierarchy`_. - ``makedirs(path, [mode], exist_ok=False)`` works similar to ``mkdir`` (see above), but also makes intermediate directories like ``os.makedirs``. The ``mode`` parameter is only there for compatibility with ``os.makedirs`` and is ignored. ``exist_ok`` controls whether the existence of any directory but the last in the ``path`` should be considered an error. If the default ``False`` is used or passed to ``makedirs``, ftputil will raise a ``PermanentError`` if any directory but the last already exists. - ``rmdir(path)`` removes the given remote directory. If it's not empty, raise a ``PermanentError``. - ``rmtree(path, ignore_errors=False, onerror=None)`` removes the given remote, possibly non-empty, directory tree. The interface of this method is rather complex, in favor of compatibility with ``shutil.rmtree``. If ``ignore_errors`` is set to a true value, errors are ignored. If ``ignore_errors`` is a false value *and* ``onerror`` isn't set, all exceptions occurring during the tree iteration and processing are raised. These exceptions are all of type ``PermanentError``. To distinguish between different kinds of errors, pass in a callable for ``onerror``. This callable must accept three arguments: ``func``, ``path`` and ``exc_info``. ``func`` is a bound method object, *for example* ``your_host_object.listdir``. ``path`` is the path that was the recent argument of the respective method (``listdir``, ``remove``, ``rmdir``). ``exc_info`` is the exception info as it is gotten from ``sys.exc_info``. The code of ``rmtree`` is taken from Python's ``shutil`` module and adapted for ``ftputil``. Removing files and links ```````````````````````` - ``remove(path)`` removes a file or link on the remote host, similar to ``os.remove``. - ``unlink(path)`` is an alias for ``remove``. Retrieving information about directories, files and links ````````````````````````````````````````````````````````` - ``listdir(path)`` returns a list containing the names of the files and directories in the given path, similar to `os.listdir`_. The special names ``.`` and ``..`` are not in the list. The methods ``lstat`` and ``stat`` (and some others) rely on the directory listing format used by the FTP server. When connecting to a host, ``FTPHost``'s constructor tries to guess the right format, which succeeds in most cases. However, if you get strange results or ``ParserError`` exceptions by a mere ``lstat`` call, please `enter a ticket`_. If ``lstat`` or ``stat`` give wrong modification dates or times, look at the methods that deal with time zone differences (`time zone correction`_). .. _`FTPHost.lstat`: - ``lstat(path)`` returns an object similar to that from `os.lstat`_. This is a kind of tuple with additional attributes; see the documentation of the ``os`` module for details. The result is derived by parsing the output of a ``LIST`` command on the server. Therefore, the result from ``FTPHost.lstat`` can not contain more information than the received text. In particular: - User and group ids can only be determined as strings, not as numbers, and that only if the server supplies them. This is usually the case with Unix servers but maybe not for other FTP servers. - Values for the time of the last modification may be rough, depending on the information from the server. For timestamps older than a year, this usually means that the precision of the modification timestamp value is not better than a day. For newer files, the information may be accurate to a minute. If the time of the last modification is before the epoch (usually 1970-01-01 UTC), set the time of the last modification to 0.0. - Links can only be recognized on servers that provide this information in the ``LIST`` output. - Stat attributes that can't be determined at all are set to ``None``. For example, a line of a directory listing may not contain the date/time of a directory's last modification. - There's a special problem with stat'ing the root directory. (Stat'ing things *in* the root directory is fine though.) In this case, a ``RootDirError`` is raised. This has to do with the algorithm used by ``(l)stat``, and I know of no approach which mends this problem. Currently, ``ftputil`` recognizes the common Unix-style and Microsoft/DOS-style directory formats. If you need to parse output from another server type, please write to the `ftputil mailing list`_. You may consider `writing your own parser`_. .. _`os.listdir`: https://docs.python.org/library/os.html#os.listdir .. _`os.lstat`: https://docs.python.org/library/os.html#os.lstat .. _`ftputil mailing list`: https://ftputil.sschwarzer.net/mailinglist .. _`writing your own parser`: `Writing directory parsers`_ .. _`FTPHost.stat`: - ``stat(path)`` returns ``stat`` information also for files which are pointed to by a link. This method follows multiple links until a regular file or directory is found. If an infinite link chain is encountered or the target of the last link in the chain doesn't exist, a ``PermanentError`` is raised. The limitations of the ``lstat`` method also apply to ``stat``. .. _`FTPHost.path`: ``FTPHost`` objects contain an attribute named ``path``, similar to `os.path`_. The following methods can be applied to the remote host with the same semantics as for ``os.path``: :: abspath(path) basename(path) commonprefix(path_list) dirname(path) exists(path) getmtime(path) getsize(path) isabs(path) isdir(path) isfile(path) islink(path) join(path1, path2, ...) normcase(path) normpath(path) split(path) splitdrive(path) splitext(path) walk(path, func, arg) Like Python's counterparts under `os.path`_, ``ftputil``'s ``is...`` methods return ``False`` if they can't find the path given by their argument. Local caching of file system information ```````````````````````````````````````` Many of the above methods need access to the remote file system to obtain data on directories and files. To get the most recent data, *each* call to ``lstat``, ``stat``, ``exists``, ``getmtime`` etc. would require to fetch a directory listing from the server, which can make the program *very* slow. This effect is more pronounced for operations which mostly scan the file system rather than transferring file data. For this reason, ``ftputil`` by default saves the results from directory listings locally and reuses those results. This reduces network accesses and so speeds up the software a lot. However, since data is more rarely fetched from the server, the risk of obsolete data also increases. This will be discussed below. Caching can be controlled -- if necessary at all -- via the ``stat_cache`` object in an ``FTPHost``'s namespace. For example, after calling :: ftp_host = ftputil.FTPHost(host, user, password) the cache can be accessed as ``ftp_host.stat_cache``. While ``ftputil`` usually manages the cache quite well, there are two possible reasons for modifying cache parameters. The first is when the number of possible entries is too low. You may notice that when you are processing very large directories and the program becomes much slower than before. It's common for code to read a directory with ``listdir`` and then process the found directories and files. This can also happen implicitly by a call to ``FTPHost.walk``. Since version 2.6 ``ftputil`` automatically increases the cache size if directories with more entries than the current maximum cache size are to be scanned. Most of the time, this works fine. However, if you need access to stat data for several directories at the same time, you may need to increase the cache explicitly. This is done by the ``resize`` method:: ftp_host.stat_cache.resize(20000) where the argument is the maximum number of ``lstat`` results to store (the default is 5000, in versions before 2.6 it was 1000). Note that each path on the server, e. g. "/home/schwa/some_dir", corresponds to a single cache entry. Methods like ``exists`` or ``getmtime`` all derive their results from a previously fetched ``lstat`` result. The value 5000 above means that the cache will hold *at most* 5000 entries (unless increased automatically by an explicit or implicit ``listdir`` call, see above). If more are about to be stored, the entries which haven't been used for the longest time will be deleted to make place for newer entries. The second possible reason to change the cache parameters is to avoid stale cache data. Caching is so effective because it reduces network accesses. This can also be a disadvantage if the file system data on the remote server changes after a stat result has been retrieved; the client, when looking at the cached stat data, will use obsolete information. There are two potential ways to get such out-of-date stat data. The first happens when an ``FTPHost`` instance modifies a file path for which it has a cache entry, e. g. by calling ``remove`` or ``rmdir``. Such changes are handled transparently; the path will be deleted from the cache. A different matter are changes unknown to the ``FTPHost`` object which inspects its cache. Obviously, for example, these are changes by programs running on the remote host. On the other hand, cache inconsistencies can also occur if two ``FTPHost`` objects change a file system simultaneously:: with ( ftputil.FTPHost(server, user1, password1) as ftp_host1, ftputil.FTPHost(server, user1, password1) as ftp_host2 ): stat_result1 = ftp_host1.stat("some_file") stat_result2 = ftp_host2.stat("some_file") ftp_host2.remove("some_file") # `ftp_host1` will still see the obsolete cache entry! print(ftp_host1.stat("some_file")) # Will raise an exception since an `FTPHost` object # knows of its own changes. print(ftp_host2.stat("some_file")) At first sight, it may appear to be a good idea to have a shared cache among several ``FTPHost`` objects. After some thinking, this turns out to be very error-prone. For example, it won't help with different processes using ``ftputil``. So, if you have to deal with concurrent write/read accesses to a server, you have to handle them explicitly. The most useful tool for this is the ``invalidate`` method. In the example above, it could be used like this:: with ( ftputil.FTPHost(server, user1, password1) as ftp_host1, ftputil.FTPHost(server, user1, password1) as ftp_host2 ): stat_result1 = ftp_host1.stat("some_file") stat_result2 = ftp_host2.stat("some_file") ftp_host2.remove("some_file") # Invalidate using an absolute path. absolute_path = ftp_host1.path.abspath( ftp_host1.path.join(ftp_host1.getcwd(), "some_file")) ftp_host1.stat_cache.invalidate(absolute_path) # Will now raise an exception as it should. print(ftp_host1.stat("some_file")) # Would raise an exception since an `FTPHost` object # knows of its own changes, even without `invalidate`. print(ftp_host2.stat("some_file")) The method ``invalidate`` can be used on any *absolute* path, be it a directory, a file or a link. By default, the cache entries (if not replaced by newer ones) are stored for an infinite time. That is, if you start your Python process using ``ftputil`` and let it run for three days a stat call may still access cache data that old. To avoid this, you can set the ``max_age`` attribute:: with ftputil.FTPHost(server, user, password) as ftp_host: ftp_host.stat_cache.max_age = 60 * 60 # = 3600 seconds This sets the maximum age of entries in the cache to an hour. This means any entry older won't be retrieved from the cache but its data instead fetched again from the remote host and then again stored for up to an hour. To reset `max_age` to the default of unlimited age, i. e. cache entries never expire, use ``None`` as value. If you are certain that the cache will be in the way, you can disable and later re-enable it completely with ``disable`` and ``enable``:: with ftputil.FTPHost(server, user, password) as ftp_host: ftp_host.stat_cache.disable() ... ftp_host.stat_cache.enable() During that time, the cache won't be used; all data will be fetched from the network. After enabling the cache again, its entries will be the same as when the cache was disabled, that is, entries won't get updated with newer data during this period. Note that even when the cache is disabled, the file system data in the code can become inconsistent:: with ftputil.FTPHost(server, user, password) as ftp_host: ftp_host.stat_cache.disable() if ftp_host.path.exists("some_file"): mtime = ftp_host.path.getmtime("some_file") In that case, the file ``some_file`` may have been removed by another process between the calls to ``exists`` and ``getmtime``! Iteration over directories `````````````````````````` .. _`FTPHost.walk`: - ``walk(top, topdown=True, onerror=None, followlinks=False)`` iterates over a directory tree, similar to `os.walk`_. Actually, ``FTPHost.walk`` uses the code from Python with just the necessary modifications, so see the linked documentation. .. _`os.walk`: https://docs.python.org/2/library/os.html#os.walk .. _`FTPHost.path.walk`: - ``path.walk(path, func, arg)`` Similar to ``os.path.walk``, the ``walk`` method in `FTPHost.path`_ can be used, though ``FTPHost.walk`` is probably easier to use. Other methods ````````````` - ``close()`` closes the connection to the remote host. After this, no more interaction with the FTP server is possible with this ``FTPHost`` object. Usually you don't need to close an ``FTPHost`` instance with ``close`` if you set up the instance in a ``with`` statement. - ``rename(source, target)`` renames the source file (or directory) on the FTP server. .. _`FTPHost.chmod`: - ``chmod(path, mode)`` sets the access mode (permission flags) for the given path. The mode is an integer as returned for the mode by the ``stat`` and ``lstat`` methods. Be careful: Usually, mode values are written as octal numbers, for example 0755 to make a directory readable and writable for the owner, but not writable for the group and others. If you want to use such octal values, rely on Python's support for them:: ftp_host.chmod("some_directory", 0o755) Not all FTP servers support the ``chmod`` command. In case of an exception, how do you know if the path doesn't exist or if the command itself is invalid? If the FTP server complies with `RFC 959`_, it should return a status code 502 if the ``SITE CHMOD`` command isn't allowed. ``ftputil`` maps this special error response to a ``CommandNotImplementedError`` which is derived from ``PermanentError``. So you need to code like this:: with ftputil.FTPHost(server, user, password) as ftp_host: try: ftp_host.chmod("some_file", 0o644) except ftputil.error.CommandNotImplementedError: # `chmod` not supported ... except ftputil.error.PermanentError: # Possibly a non-existent file ... Because the ``CommandNotImplementedError`` is more specific, you have to test for it first. .. _`RFC 959`: `RFC 959 - File Transfer Protocol (FTP)`_ - ``copyfileobj(source, target, length=64*1024)`` copies the contents from the file-like object ``source`` to the file-like object ``target``. The only difference to ``shutil.copyfileobj`` is the default buffer size. Note that arbitrary file-like objects can be used as arguments (e. g. local files, remote FTP files). However, the interfaces of ``source`` and ``target`` have to match; the string type read from ``source`` must be an accepted string type when written to ``target``. For example, if you open ``source`` in Python 3 as a local text file and ``target`` as a remote file object in binary mode, the transfer will fail since ``source.read`` gives unicode strings (``str``) whereas ``target.write`` only accepts byte strings (``bytes``). See `File-like objects`_ for the construction and use of remote file-like objects. .. _`set_parser`: - ``set_parser(parser)`` sets a custom parser for FTP directories. Note that you have to pass in a parser *instance*, not the class. An `extra section`_ shows how to write own parsers if the default parsers in ``ftputil`` don't work for you. .. _`extra section`: `Writing directory parsers`_ .. _`keep_alive`: - ``keep_alive()`` attempts to keep the connection to the remote server active in order to prevent timeouts from happening. This method is primarily intended to keep the underlying FTP connection of an ``FTPHost`` object alive while a file is uploaded or downloaded. This will require either an extra thread while the upload or download is in progress or calling ``keep_alive`` from a `callback function`_. The ``keep_alive`` method won't help if the connection has already timed out. In this case, a ``ftputil.error.TemporaryError`` is raised. If you want to use this method, keep in mind that FTP servers define a timeout for a reason. A timeout prevents running out of server connections because of clients that never disconnect on their own. Note that the ``keep_alive`` method does *not* affect the "hidden" FTP child connections established by ``FTPHost.open`` (see section `FTPHost instances vs. FTP connections`_ for details). You *can't* use ``keep_alive`` to avoid a timeout in a stalling transfer like this:: with ftputil.FTPHost(server, userid, password) as ftp_host: with ftp_host.open("some_remote_file", "rb") as fobj: data = fobj.read(100) # _Futile_ attempt to avoid file connection timeout. for i in range(15): time.sleep(60) ftp_host.keep_alive() # Will raise an `ftputil.error.TemporaryError`. data += fobj.read() .. _`FTPHost.open`: File-like objects ----------------- Construction ~~~~~~~~~~~~ Basics `````` ``FTPFile`` objects are returned by a call to ``FTPHost.open``; never use the ``FTPFile`` constructor directly. The APIs for remote file-like objects is modeled after the APIs of the built-in ``open`` function and its return value. - ``FTPHost.open(path, mode="r", buffering=None, encoding=None, errors=None, newline=None, rest=None)`` returns a file-like object that refers to the path on the remote host. This path may be absolute or relative to the current directory on the remote host (this directory can be determined with the ``getcwd`` method). As with local file objects, the default mode is "r", i. e. reading text files. Valid modes are "r", "rb", "w", and "wb". If a file is opened in binary mode, you *must not* specify an encoding. On the other hand, if you open a file in text mode, an encoding is used. By default, this is the return value of ``locale.getpreferredencoding``, but you can (and probably should) specify a distinct encoding. If you open a file in binary mode, the read and write operations use ``bytes`` objects. That is, read operations return ``bytes`` and write operations only accept ``bytes``. Similarly, text files always work with strings (``str``). Here, read operations return string and write operations only accept strings. The arguments ``buffering``, ``errors`` and ``newline`` have the same semantics as in open_. If the file is opened in binary mode, you may pass 0 or a positive integer for the ``rest`` argument. The argument is passed to the underlying FTP session instance (for example an instance of ``ftplib.FTP``) to start reading or writing at the given byte offset. For example, if a remote file contains the letters "abcdef" in ASCII encoding, ``rest=3`` will start reading at "d". .. warning:: If you pass ``rest`` values which point *after* the file, the behavior is undefined and may even differ from one FTP server to another. Therefore, use the ``rest`` argument only for error recovery in case of interrupted transfers. You need to keep track of the transferred data so that you can provide a valid ``rest`` argument for a resumed transfer. .. _`open`: https://docs.python.org/3/library/functions.html#open ``FTPHost.open`` can also be used in a ``with`` statement:: import ftputil with ftputil.FTPHost(...) as ftp_host: ... with ftp_host.open("new_file", "w", encoding="utf8") as fobj: fobj.write("This is some text.") At the end of the ``with`` block, the remote file will be closed automatically. If something goes wrong during the construction of the file or in the body of the ``with`` statement, the file will be closed as well. Exceptions will be propagated as with ``try ... finally``. Attributes and methods ~~~~~~~~~~~~~~~~~~~~~~ The methods :: close() read([count]) readline([count]) readlines() write(data) writelines(string_sequence) and the attribute ``closed`` have the same semantics as for file objects of a local disk file system. The iterator protocol is supported as well, i. e. you can use a loop to read a file line by line:: with ftputil.FTPHost(server, user, password) as ftp_host: with ftp_host.open("some_file") as input_file: for line in input_file: # Do something with the line, e. g. print(line.strip().replace("ftplib", "ftputil")) For more on file objects, see the section `File objects`_ in the Python Library Reference. .. _`file objects`: https://docs.python.org/3/glossary.html#term-file-object .. _`child_connections`: ``FTPHost`` instances vs. FTP connections ----------------------------------------- This section explains why keeping an ``FTPHost`` instance "alive" without timing out sometimes isn't trivial. If you always finish your FTP operations in time, you don't need to read this section. The file transfer protocol is a stateful protocol. That means an FTP connection always is in a certain state. Each of these states can only change to certain other states under certain conditions triggered by the client or the server. One of the consequences is that a single FTP connection can't be used at the same time, say, to transfer data on the FTP data channel and to create a directory on the remote host. For example, consider this:: >>> import ftplib >>> ftp = ftplib.FTP(server, user, password) >>> ftp.pwd() '/' >>> # Start transfer. `CONTENTS` is a text file on the server. >>> socket = ftp.transfercmd("RETR CONTENTS") >>> socket >>> ftp.pwd() Traceback (most recent call last): File "", line 1, in File "/usr/lib64/python2.7/ftplib.py", line 578, in pwd return parse257(resp) File "/usr/lib64/python2.7/ftplib.py", line 842, in parse257 raise error_reply, resp ftplib.error_reply: 226-File successfully transferred 226 0.000 seconds (measured here), 5.60 Mbytes per second >>> Note that ``ftp`` is a single FTP connection, represented by an ``ftplib.FTP`` instance, not an ``ftputil.FTPHost`` instance. On the other hand, consider this:: >>> import ftputil >>> ftp_host = ftputil.FTPHost(server, user, password) >>> ftp_host.getcwd() >>> fobj = ftp_host.open("CONTENTS") >>> fobj >>> ftp_host.getcwd() u'/' >>> fobj.readline() u'Contents of FTP test directory\n' >>> fobj.close() >>> To be able to start a file transfer (i. e. open a remote file for reading or writing) and still be able to use other FTP commands, ftputil uses a trick. For every remote file, ftputil creates a new FTP connection, called a child connection in the ftputil source code. (Actually, FTP connections belonging to closed remote files are re-used if they haven't timed out yet.) In most cases this approach isn't noticeable by code using ftputil. However, the nice abstraction of dealing with a single FTP connection falls apart if one of the child connections times out. For example, if you open a remote file and work only with the initial "main" connection to navigate the file system, the FTP connection for the remote file may eventually time out. While it's often relatively easy to prevent the "main" connection from timing out it's unfortunately practically impossible to do this for a remote file connection (apart from transferring some data, of course). For this reason, `FTPHost.keep_alive`_ affects only the main connection. Child connections may still time out if they're idle for too long. .. _`FTPHost.keep_alive`: `keep_alive`_ Some more details: - A kind of "straightforward" way of keeping the main connection alive would be to call ``ftp_host.getcwd()``. However, this doesn't work because ftputil caches the current directory and returns it without actually contacting the server. That's the main reason why there's a ``keep_alive`` method since it calls ``pwd`` on the FTP connection (i. e. the session object), which isn't a public attribute. - Some servers define not only an idle timeout but also a transfer timeout. This means the connection times out unless there's some transfer on the data channel for this connection. So ftputil's ``keep_alive`` doesn't prevent this timeout, but an ``ftp_host.listdir(ftp_host.curdir)`` call should do it. However, this transfers the data for the whole directory listing which might take some time if the directory has many entries. Bottom line: If you can, you should organize your FTP actions so that you finish everything before a timeout happens. Writing directory parsers ------------------------- ``ftputil`` recognizes the two most widely-used FTP directory formats, Unix and MS style, and adjusts itself automatically. Almost every FTP server uses one of these formats. However, if your server uses a format which is different from the two provided by ``ftputil``, you can plug in a custom parser with a single method call and have ``ftputil`` use this parser. For this, you need to write a parser class by inheriting from the class ``Parser`` in the ``ftputil.stat`` module. Here's an example:: import ftputil.error import ftputil.stat class XyzParser(ftputil.stat.Parser): """ Parse the default format of the FTP server of the XYZ corporation. """ def parse_line(self, line, time_shift=0.0): """ Parse a `line` from the directory listing and return a corresponding `StatResult` object. If the line can't be parsed, raise `ftputil.error.ParserError`. The `time_shift` argument can be used to fine-tune the parsing of dates and times. See the class `ftputil.stat.UnixParser` for an example. """ # Split the `line` argument and examine it further; if # something goes wrong, raise an `ftputil.error.ParserError`. ... # Make a `StatResult` object from the parts above. stat_result = ftputil.stat.StatResult(...) # `_st_name`, `_st_target` and `_st_mtime_precision` are optional. stat_result._st_name = ... stat_result._st_target = ... stat_result._st_mtime_precision = ... return stat_result # Define `ignores_line` only if the default in the base class # doesn't do enough! def ignores_line(self, line): """ Return a true value if the line should be ignored. For example, the implementation in the base class handles lines like "total 17". On the other hand, if the line should be used for stat'ing, return a false value. """ is_total_line = super().ignores_line(line) my_test = ... return is_total_line or my_test A ``StatResult`` object is similar to the value returned by `os.stat`_ and is usually built with statements like :: stat_result = StatResult( (st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size, st_atime, st_mtime, st_ctime)) stat_result._st_name = ... stat_result._st_target = ... stat_result._st_mtime_precision = ... with the arguments of the ``StatResult`` constructor described in the following table. ===== =================== ============ =================== ======================= Index Attribute os.stat type ``StatResult`` type Notes ===== =================== ============ =================== ======================= 0 st_mode int int 1 st_ino long long 2 st_dev long long 3 st_nlink int int 4 st_uid int str usually only available as string 5 st_gid int str usually only available as string 6 st_size long long 7 st_atime int/float float 8 st_mtime int/float float 9 st_ctime int/float float \- _st_name \- str file name without directory part \- _st_target \- str link target (may be absolute or relative) \- _st_mtime_precision \- int ``st_mtime`` precision in seconds ===== =================== ============ =================== ======================= If you can't extract all the desirable data from a line (for example, the MS format doesn't contain any information about the owner of a file), set the corresponding values in the ``StatResult`` instance to ``None``. Parser classes can use several helper methods which are defined in the class ``Parser``: - ``parse_unix_mode`` parses strings like "drwxr-xr-x" and returns an appropriate ``st_mode`` integer value. - ``parse_unix_time`` returns a float number usable for the ``st_...time`` values by parsing arguments like "Nov"/"23"/"02:33" or "May"/"26"/"2005". Note that the method expects the timestamp string already split at whitespace. - ``parse_ms_time`` parses arguments like "10-23-01"/"03:25PM" and returns a float number like from ``time.mktime``. Note that the method expects the timestamp string already split at whitespace. Additionally, there's an attribute ``_month_numbers`` which maps lowercase three-letter month abbreviations to integers. For more details, see the two "standard" parsers ``UnixParser`` and ``MSParser`` in the module ``ftputil/stat.py``. To actually *use* the parser, call the method `set_parser`_ of the ``FTPHost`` instance. If you can't write a parser or don't want to, please ask on the `ftputil mailing list`_. Possibly someone has already written a parser for your server or can help with it. FAQ / Tips and tricks --------------------- Where can I get the latest version? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ See the `download page`_. Announcements will be sent to the `mailing list`_. Announcements on major updates will also be posted to the `Python announcements list`_. .. _`download page`: https://ftputil.sschwarzer.net/download .. _`mailing list`: https://ftputil.sschwarzer.net/mailinglist .. _`Python announcements list`: https://mail.python.org/mailman3/lists/python-announce-list.python.org/ Is there a mailing list on ``ftputil``? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Yes, please visit https://ftputil.sschwarzer.net/mailinglist to subscribe or read the archives. Though you can *technically* post without subscribing first I can't recommend it: The mails from non-subscribers have to be approved by me and because the arriving mails contain *lots* of spam, I rarely go through these mails. I found a bug! What now? ~~~~~~~~~~~~~~~~~~~~~~~~ Before reporting a bug, make sure that you already read this manual and tried the `latest version`_ of ``ftputil``. There the bug might have already been fixed. .. _`latest version`: https://ftputil.sschwarzer.net/download Please see https://ftputil.sschwarzer.net/issuetrackernotes for guidelines on entering a bug in ``ftputil``'s ticket system. If you are unsure if the behaviour you found is a bug or not, you should write to the `ftputil mailing list`_. *Never* include confidential information (user id, password, file names, etc.) in the problem report! Be careful! Does ``ftputil`` support TLS? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``ftputil`` has no *built-in* TLS support. On the other hand, there are two ways to get TLS support with ftputil: - The ``ftplib`` library has a class ``FTP_TLS`` that you can use for the ``session_factory`` keyword argument in the ``FTPHost`` constructor. You can't use the class directly though *if* you need additional setup code in comparison to ``ftplib.FTP``, for example calling ``prot_p``, to secure the data connection. On the other hand, `ftputil.session.session_factory`_ can be used to create a custom session factory. - If you have other requirements that ``session_factory`` can't fulfill, you may create your own session factory by inheriting from ``ftplib.FTP_TLS``:: import ftplib import ftputil class FTPTLSSession(ftplib.FTP_TLS): def __init__(self, host, user, password): ftplib.FTP_TLS.__init__(self) self.connect(host, port) self.login(user, password) # Set up encrypted data connection. self.prot_p() ... # Note the `session_factory` parameter. Pass the class, not # an instance. with ftputil.FTPHost(server, user, password, session_factory=FTPTLSSession) as ftp_host: # Use `ftp_host` as usual. ... .. _`ftputil.session.session_factory`: `Session factories`_ How do I connect to a non-default port? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default, an instantiated ``FTPHost`` object connects on the usual FTP port. If you have to use a different port, refer to the section `Session factories`_. How do I set active or passive mode? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Please see the section `Session factories`_. How can I debug an FTP connection problem? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can do this with a session factory. See `Session factories`_. If you want to change the debug level only temporarily after the connection is established, you can reach the `session object`_ as the ``_session`` attribute of the ``FTPHost`` instance and call ``_session.set_debuglevel``. Note that the ``_session`` attribute should *only* be accessed for debugging. Calling arbitrary ``ftplib.FTP`` methods on the session object may *cause* bugs! .. _`session object`: `Session factories`_ Conditional upload/download to/from a server in a different time zone ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You may find that ``ftputil`` uploads or downloads files unnecessarily, or not when it should. Please see the section on `time zone correction`_. It may even be sufficient to call `synchronize_times`_. When I use ``ftputil``, all I get is a ``ParserError`` exception ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The FTP server you connect to may use a directory format that ``ftputil`` doesn't understand. You can either write and `plug in your own parser`_ or ask on the `mailing list`_ for help. .. _`plug in your own parser`: `Writing directory parsers`_ ``isdir``, ``isfile`` or ``islink`` incorrectly return ``False`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Like Python's counterparts under `os.path`_, ``ftputil``'s methods return ``False`` if they can't find the given path. Probably you used ``listdir`` on a directory and called ``is...()`` on the returned names. But if the argument for ``listdir`` wasn't the current directory, the paths won't be found and so all ``is...()`` variants will return ``False``. I don't find an answer to my problem in this document ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Please send an email with your problem report or question to the `ftputil mailing list`_, and we'll see what we can do for you. :-) Bugs and limitations -------------------- - ``ftputil`` needs at least Python 3.6 to work. - Whether ``ftputil`` "sees" "hidden" directory and file names (i. e. names starting with a dot) depends on the configuration of the FTP server. See `Hidden files and directories`_ for details. - Due to the implementation of ``lstat`` it can not return a sensible value for the root directory ``/`` though stat'ing entries *in* the root directory isn't a problem. If you know an implementation that can do this, please let me know. The root directory is handled appropriately in ``FTPHost.path.exists/isfile/isdir/islink``, though. - In multithreaded programs, you can have each thread use one or more ``FTPHost`` instances as long as no instance is shared with other threads. - Currently, it is not possible to continue an interrupted upload or download. Contact me if this causes problems for you. - There's exactly one cache for ``lstat`` results for each ``FTPHost`` object, i. e. there's no sharing of cache results determined by several ``FTPHost`` objects. See `Local caching of file system information`_ for the reasons. Files ----- If not overwritten via installation options, the ``ftputil`` files reside in the ``ftputil`` package. There's also documentation in `reStructuredText`_ and in HTML format. The locations of these files after installation is system-dependent. .. _`reStructuredText`: https://docutils.sourceforge.net/rst.html The files ``test_*.py`` and ``scripted_session.py`` are for unit-testing. If you only *use* ``ftputil``, i. e. *don't* modify it, you can delete these files. References ---------- - Postel J, Reynolds J. 1985. `RFC 959 - File Transfer Protocol (FTP)`_. - Python Software Foundation. 2020. `The Python Standard Library`_. .. _`RFC 959 - File Transfer Protocol (FTP)`: https://www.ietf.org/rfc/rfc959.txt .. _`The Python Standard Library`: https://docs.python.org/library/index.html Authors ------- ``ftputil`` is written by Stefan Schwarzer and contributors (see ``doc/contributors.txt``). The original ``lrucache`` module was written by Evan Prodromou . Feedback is appreciated. :-) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642273755.0534909 ftputil-5.0.3/doc/git_hashes_and_dates.txt0000664000175000017500000040671300000000000017646 0ustar00schwaschwabd48332917ea90bce3be0350b70d99351ffb5c71 2021-11-13 18:21:14 +0100 ebaa2da6654be32c6412544ae28d1b15bd45fcd5 2021-11-13 18:13:22 +0100 6bf3e41a6fdd13b36f1db8cab08b6da825c4c29e 2021-11-13 18:13:16 +0100 cf2b428f081f9815c20632a37189c041c9907567 2021-11-13 18:12:25 +0100 5708aae82110477199282602466edc1d77a722d1 2021-11-13 18:10:20 +0100 4bfd2f9377e0964bc206bbab64815c45a35ffc1b 2021-11-13 18:09:42 +0100 4c28237894683abdab8f9f362eeda5707c6a29dd 2021-11-13 18:08:35 +0100 d615279cd0f7dfc560f9b4eac3bd88572d25750b 2021-11-13 16:37:53 +0100 87fc00fbb446d73e8da83c9e1bb01d23b43bab0c 2021-11-13 16:17:59 +0100 2883b15faba98eea299e1bbe85e94235206ae515 2021-11-13 16:37:14 +0100 b0c8f69ada2fb3cc349559ffc2f8fe474fb5a12b 2021-08-07 00:23:33 +0200 d45d20b1a702a76e9ce9e14335ffc0842fdf5b7a 2021-07-17 21:58:10 +0200 8514c3b8cbc71aa5b89396436e5db7bccf08a358 2021-07-11 23:54:35 +0200 d2c024f09144a31bdfe0201ec2f3946263504d7a 2021-07-10 23:28:01 +0200 9d65c73e68042191ab20ac795a945e39445da579 2021-07-04 22:31:44 +0200 151948e24c51580486f9e1aa534a2a22b31f89d0 2021-07-04 22:25:04 +0200 f0e1aed3503a7f29a21a68309c8bf042ea0dd488 2021-07-04 22:21:15 +0200 58bac3bdcb4b5ecc3a59350a8638d819da5a5e69 2021-03-18 17:13:51 +0100 e17ab14f2e2af7bf850bf671d5f011d39a4e5c11 2021-03-18 16:55:25 +0100 5e0ee8320bb8b0b79cea0dd2b906852fb8a05348 2021-03-18 16:54:26 +0100 48b7da590692b7db656fef85d1cbd1cd4a2cdaf8 2021-03-18 16:52:44 +0100 266a8beef77208a9ee419c7ab7f0502a719b9cad 2021-03-17 22:58:50 +0100 2780951c5d200404e98fa6c359f9da353214887a 2021-03-17 21:42:56 +0100 71d59313a588006db64a17098a06c4067cc37fe5 2021-02-17 20:32:30 +0100 7c4ea87c4ff3f1a55008b79857c11c435e2d75a0 2021-02-17 20:21:37 +0100 3172bcb1ca3c42320ae15dd41d73046139b53ee6 2021-02-17 20:14:36 +0100 ccb7c06c44327bc68066d3f5a30e47241f33137c 2021-02-17 19:56:06 +0100 388422d3c8b88515b1d8048d4185908267beaabe 2021-02-17 19:43:17 +0100 9b910f04dd525eb4cb2f2ebfaf51b1c1b871dc86 2021-02-17 19:42:27 +0100 ccd7a639aee7297236fd11ae67c695e0793b370f 2021-02-17 19:41:03 +0100 a3f683be9ecc9c00541dabdb6c5749517fd871c1 2021-02-07 22:13:42 +0100 7e45a8376423c38bb5000747b62b05e11c702821 2021-02-01 19:37:48 +0100 b493df56da90f92bc6e238d411ef0ff9871d3753 2021-02-01 19:18:35 +0100 408cc7f8d7e76560beeddb877ac84d731fb63629 2021-02-01 19:18:28 +0100 71a2129c85a09d24f3d9c62b6c87a2f938209a0f 2021-02-01 19:16:52 +0100 22042cc46014c7c7c6dc94f3a20758287dfbbdaa 2021-02-01 19:01:53 +0100 27ca0ea14876ffc93733e1b3f1c3dddff9820922 2021-02-01 19:00:27 +0100 accf208e9e905d65deed08d732a19fa230ad20dd 2021-02-01 18:58:37 +0100 5292d5b9448f77746d929b2f75f0dc6166293f7a 2021-02-01 17:57:29 +0100 c0e4e9b29fd94c8279155cf3207ba0757e84d09a 2021-02-01 17:47:45 +0100 2df893ca83f33aae44c37859ac5be0c291ed3d69 2021-02-01 17:10:12 +0100 612fcb3b19ea24803dc4c1e219dbe3e27bb5c87e 2021-02-01 15:24:20 +0100 28eda2262319e517736ca0faa2e07c26bf22d1ff 2021-02-01 15:19:14 +0100 a2898d0722e0939dcb3b830843993c6f673ac2ea 2021-01-28 18:05:44 +0100 a7534e14dad315729ebb48348c29b820424b8daa 2021-01-28 13:10:09 +0100 d3307f21c3ae4ff7c3dcfbc3362f2883dca267ef 2021-01-28 12:58:45 +0100 f61953603145a71ba95b79268c7dc00f32173db9 2021-01-28 12:56:42 +0100 1efc64e6854a1ec1f23254f0a2dfaad5316d5ae3 2021-01-28 12:56:25 +0100 3f78557c9f198cf7542980d64087f84f2a9b71aa 2021-01-28 12:28:41 +0100 370ffc808e54fb3adb760e3d7e094d11b845e8ab 2021-01-28 12:25:13 +0100 1c45f9e1574c2667cbb59c800e207388379af055 2021-01-28 12:14:14 +0100 6259dd366d3e55ebdb604b0ee25510b0c02243c5 2021-01-28 11:52:17 +0100 397c2270f77a53bb8932ec2aac22e3de21119f53 2021-01-27 19:22:14 +0100 7c12da20aa375afb48b476f9d4b9f3168185e0cd 2021-01-27 17:50:21 +0100 7346d028a6eb69f4cda77c8fbd9285da450ae43f 2021-01-27 17:49:58 +0100 6051e9ec9cb916fe326324e2243db49278475c8b 2021-01-27 17:49:35 +0100 ab271f5488c148efc22a5d262aa72dec47863310 2021-01-27 16:29:33 +0100 90eb12eae5ff3df57f89003c581fd1949653eb4d 2021-01-27 15:47:37 +0100 7194c5116e5ccd8313d09d69410ef7db6bea1f42 2021-01-06 22:33:46 +0100 9943284714772c4f52ccb1775d5196912bd6e84f 2021-01-06 20:36:38 +0100 c0457bab35c3e522018ab10b646a4c6704f72c4e 2021-01-06 18:33:08 +0100 456fa5ce54dc421371cb9a53e7b37d7a33ee00e7 2021-01-06 17:54:12 +0100 d448dd6e8e99acec3bf9b2a5731b724ae2319d6a 2021-01-06 17:18:22 +0100 7dff8029f4b33925b8182aaf362595bdca6ff431 2021-01-06 17:16:47 +0100 143b6dcb9ff1766ca4949654e181296a42743e0a 2021-01-06 17:11:49 +0100 bf4439759fb550ce395f765b213509d4719d6df8 2021-01-05 19:33:40 +0100 aa9e6c84db869037f234a2633b0e130c244c7ce9 2020-12-01 18:06:44 +0100 907f0908f8b5c4afb1595421e89bd314848de10d 2020-12-01 13:59:20 +0100 7bca1f559c7b2e78a5a90d3dc09bca349150d27c 2020-11-30 21:23:55 +0100 1dee38c4f1a8021132c360ac5a8975b908fed8da 2020-11-30 21:15:05 +0100 6c9a37dd6922f9de727b26fa77b21f1cf97fd587 2020-11-30 20:58:39 +0100 878e4c51410bcdf1e7f5388cf99192cdf8fbae11 2020-11-30 20:42:39 +0100 11173337f75bd2f6156a282f37899c8e72fc3bfa 2020-11-30 19:49:57 +0100 20272663c0c39fac73179e53958ab30dbe493db0 2020-11-30 19:45:35 +0100 87c4475591c6ed6131e867b0549216da158f798c 2020-11-30 19:42:45 +0100 d0162aa3e37b5154c9b6e19ceac2cf87e7d07638 2020-11-30 19:18:08 +0100 d394c84b6bdd1497b3e81cb22717f05b8f746f60 2020-11-30 19:07:10 +0100 f62957dba4f14dc88d0241434fdee58debcef38e 2020-11-30 18:58:18 +0100 65ee063c4606c439555dbff6dee649daaeffe828 2020-11-30 18:50:12 +0100 7363ec6e3c709ec2e78cd4c5a860b06a39d89b53 2020-11-30 18:49:27 +0100 f8c9312db3840ee1f0c46e88d8600b0e23ea61d1 2020-11-30 18:23:53 +0100 e319861beaf601d595aa5165323b03235c3ad564 2020-11-29 00:03:40 +0100 84aad3f292bd078b2661dcebef7c50d956a186e5 2020-11-28 09:19:28 +0100 35117f9d2fa48c1aa8b35242956ae0b3fff75cad 2020-11-27 22:56:17 +0100 a564a20f92eec02439d114c826009f62cbaab39a 2020-11-27 22:45:38 +0100 2f370728d1f62d192d26c1a59f54c15fcca2170d 2020-11-27 22:30:49 +0100 4a97d3019baed034c75d29a7785f958094b7b2a6 2020-11-27 22:27:32 +0100 8200abc709f7da1fd65c6d83feedb67c644727cc 2020-11-27 22:19:24 +0100 f505bcb22d4890a6c44eb8bb004c1e9cc822282b 2020-11-27 22:18:27 +0100 38d7ab17c5a416bf509c164250d62f1a51bbbf44 2020-11-27 22:05:32 +0100 7084a66d4f12b533bad193e755aad6b765f6237e 2020-11-27 22:03:13 +0100 4301e2546d03c89469ed2832856d2d0825952dec 2020-11-27 21:59:34 +0100 27f9790bb10e9a7aa21aafd301eeb3a26b047c23 2020-11-27 21:58:38 +0100 9dfa0e6f02be2f8b2613e1eeeac2fb48d1edb925 2020-11-27 21:48:53 +0100 69fd316e2c2072acafc351e32049c80af8d57d3e 2020-11-27 21:35:10 +0100 eec463e488c0fe42fa7edff17a33b91806e733d8 2020-11-27 20:15:02 +0100 f5636bd7704af83e26ef8f846511259ad990d1e0 2020-11-27 20:13:28 +0100 6a0e483ae2c19e2f5a4305a8da212bc3eed4f739 2020-11-27 20:12:28 +0100 b675c1952ba5101fc1ce29c2114f93d0a40510d8 2020-11-27 16:35:33 +0100 fc594794ce492bfd4a78ebb356cbd3dbe1cbd375 2020-11-27 16:20:17 +0100 4dad1a8c4402d5ef62a43da7d1f26e72d339e2fd 2020-10-24 19:10:25 +0200 2d1dbdbefbc81b759e354ad5ee36617a9afee5f8 2020-10-24 19:07:09 +0200 9489ed005d6a7087ccd907d1fc411e7f1af7d896 2020-10-24 19:05:37 +0200 8dd269a30ef95a0c4d9641315109dab590bad85b 2020-10-24 19:04:17 +0200 7d66cc863275178a19391e4afda7f8860dfa77af 2020-10-24 19:01:34 +0200 e2a87286b9e9a96bd22c2c47c1d897dd28f36729 2020-06-13 19:33:27 +0200 71b31b5824fe92e27b643920679394557ef17bd0 2020-06-13 19:20:46 +0200 f2a0bdc6ffb04453c6bb030554c0680ca38fa88c 2020-06-13 19:16:17 +0200 f6b4bc80c0bcb3735738aa19cfceb4f0ad4a2701 2020-06-13 19:13:35 +0200 1761ed775a49784e62f1da1860e60c6f384a8fae 2020-06-13 19:03:50 +0200 d14cdb83f26c417f1a63f11e68f64651060d0cae 2020-06-13 19:00:38 +0200 aa3f5a654cc61e858f733f7fd7328329209b1e86 2020-06-13 18:57:18 +0200 1f903072abf764ad4464007ce44533b4b1d2fb84 2020-06-13 18:48:45 +0200 4d391a370a47b102c5627570c2b43e9be8cf1e32 2020-06-13 18:23:35 +0200 a3d3678819e9fa30d7cbc7dfe1b99eb2dd05b8b5 2020-06-13 18:21:49 +0200 806c2725b4a25e4edcbd303c32c371f0d4056f9d 2020-06-13 18:11:11 +0200 3a59ccd994dd89482e4de41d2a37e7d8c832e826 2020-06-13 18:10:41 +0200 ae44b7222a44514df5f044bdb050906fcdce52df 2020-06-13 18:07:44 +0200 3c7068933f048742c0c50972acb085e7337f367e 2020-06-13 17:57:46 +0200 7fa0b930a029e233e277958768ce37300eb0898b 2020-06-13 17:43:19 +0200 19c8ba202118a4e76aff58d6ab98d5ba1671099e 2020-06-13 17:39:55 +0200 fa8457bbca0e6fbe45257cc52a001eb081cc6313 2020-06-13 17:38:53 +0200 71ce5116d8afae148d8ee2ac70aecf08ebc0e5a4 2020-06-13 17:32:29 +0200 c1f4c40aa9ceb9db554c197faa1fa2489a87202d 2020-06-13 17:32:00 +0200 00eaebd5685c6222941cbce5017e2d5d61b51e52 2020-06-01 19:28:51 +0200 5c944112441da2aac6c66afa63d6b4b4dae8f704 2020-06-01 19:25:22 +0200 ac5cc1390a85a449cd5bb98746839400afd73248 2020-06-01 19:24:30 +0200 89a8db1b20db4558d1f573a46c6dac2d7f72374d 2020-06-01 19:23:05 +0200 d8fac9270e7f52bae9d5b91403bcc029d753887f 2020-06-01 19:12:52 +0200 7498ef7c1059a3b78d7ff55be0387faee752b355 2020-06-01 19:12:31 +0200 608313090a14e80201b39289da730bb6a9abb1b5 2020-06-01 18:58:26 +0200 d800d4fd9e0680abdd2aef653c968114a07d4637 2020-06-01 18:57:58 +0200 e7ef1ec7c10dc7d7b8d782c778145fb4ac60369e 2020-06-01 18:51:26 +0200 261904cf16a9764e9c9cc8bab9cd0f6d2e3077f5 2020-06-01 18:42:51 +0200 f859fdb3285d4e8dd407a5158c34617cc858d57a 2020-06-01 18:35:28 +0200 3d534c418f0588e52629117514efe2e26a017b63 2020-06-01 17:37:06 +0200 e831ee7d94d7d74cd9cf4faea1e335e137cf04ea 2020-06-01 17:33:40 +0200 9a24e503ba0e943366690287170ef8e92ea9fbd2 2020-06-01 17:33:09 +0200 ac14021ee88f44c25767f28ab61c71a42478a1fb 2020-05-31 23:37:14 +0200 da5206f7641a81c0f541c44812038cf5e3f69674 2020-05-31 23:12:05 +0200 c24f81c34fbfb5137f15b30c70bad930761fc2c0 2020-05-31 20:06:59 +0200 a0567e16015704d131ee54c5772b23516c353ae5 2020-05-31 19:38:16 +0200 55904108569e9d6bbf6d07a537e22739da289820 2020-05-31 19:32:40 +0200 234a44b7759772966a39441cb9ea27f4be87a521 2020-05-31 19:30:56 +0200 7cc6f0d1580643a17950e7be8950ae65d7416735 2020-05-31 19:09:12 +0200 94f57d09d8b42464e5d7c8b4d857cba4cf25f3bc 2020-05-31 19:08:08 +0200 5470a554fd500fe76e00f88a21188a3cf7111bdd 2020-05-31 19:06:42 +0200 9bae046c788aa4c64784123ac95cded0cac8a924 2020-05-25 13:56:16 +0200 a2843e387f3c3d6e17a8ef2fcf3a25df1006d761 2020-05-24 23:37:44 +0200 1b10ae618c0077dee63e954cb36226fc4636a278 2020-05-24 22:54:36 +0200 eb0bba1bf00b84a3a55e0cd7afb047b6cac7b7a0 2020-05-24 21:10:32 +0200 7a72ffa7773806111c1e2df12a7f114d93f91683 2020-04-11 21:59:18 +0200 549e64c4fdb1f189d7ae291f82a47082317fcb6c 2020-04-11 21:57:10 +0200 ab7aac3d70e4aa8361a4ec68a098def0f36afefe 2020-04-11 21:43:18 +0200 fad25f40da0492bcbe017b4dbf4fa183d135955f 2020-04-11 21:17:31 +0200 59486ffcea181cd2951573a806b19f87e87dc451 2020-04-11 20:40:30 +0200 7e5d9f1f1cb22ca69bf63fb638e39a3865a6e89d 2020-04-11 20:37:53 +0200 ab3b197facdafabcdb39be99634a302a5588bdcd 2020-04-11 20:33:31 +0200 a7ab48cb4f89a081ca4636518fc17a7b26fa61da 2020-04-11 19:49:06 +0200 2d1e7fc416ee9175b22bb56970289ba43f14e173 2020-04-11 19:40:25 +0200 d1ac5ecc064cbd076f896cf0d5e7aedecc4bc816 2020-04-11 19:24:57 +0200 f257822108316437a4ea4de206eb299901468ac3 2020-04-10 20:25:01 +0200 57e0383eedf9317016927ee10435dd6df7b0bc8d 2020-04-10 20:18:40 +0200 f0526e1755aca5256d52e9b66cd10587a0bae0aa 2020-04-10 16:30:10 +0200 c67516411f0b23ece252909de4d103fbc786c056 2020-04-10 16:29:41 +0200 1f252257a4b7381a79064a1f456bf2d9983157b9 2020-04-10 16:28:48 +0200 6da944d2e2ca0d539f732d005ecfa692ade68a14 2020-04-10 16:24:10 +0200 81ba3ecee9a3da184def0c155dabfa8e35943da9 2020-04-10 16:05:21 +0200 10c475b4c9361aa17d0648596133f226abb91fca 2020-04-05 22:13:56 +0200 d4df772105fd32d4792d4d6029fba080e9176af9 2020-04-05 21:57:55 +0200 d40193d81edcb4f1651358c7ad1eec3157d054b8 2020-01-03 20:00:10 +0100 f8587a023811c6f18c1977bb2b9c0afe3be37d06 2020-01-03 19:58:48 +0100 8b948ec67281ca914bcc3467093b81bf3631318f 2020-01-03 19:52:38 +0100 4429f0af3371df1c81ccfaec53baa0e3e873b0ed 2020-01-03 19:47:10 +0100 5f413ebe45deddab6b26a41178562bed19b9c81c 2020-01-03 19:37:56 +0100 70b5a9e4c11f8bbe41dfa1a24d023875bf1536c6 2020-01-03 19:34:47 +0100 db3e04f9d3a23dc22c8df6a15b584e8c4e2759e5 2020-01-03 19:34:24 +0100 b16bf44d34b3829cfb06137e5bdd8c2daab81ffd 2020-01-03 18:57:45 +0100 fc408c90ca46715e2e5705aa55084da2df790d05 2020-01-03 18:26:33 +0100 3ca470400fcfb8d5ce361bc4fb1a27e84f1e8186 2020-01-03 18:21:39 +0100 cd8a126b285c07969af7939aa402494ae3865f82 2020-01-03 13:11:47 +0100 d22e31529d79499df69936873d13d5d0b731699e 2020-01-03 13:08:42 +0100 97ee3980388212c922d8843629bd16f91b772f4c 2020-01-03 13:18:41 +0100 22285ca15a1061b7e213bac211e966a959c618ca 2020-01-02 22:49:00 +0100 388435f439d10b49f33db787895c267e9e462412 2020-01-02 21:54:52 +0100 49554a101c785d25b6b24c92ddd488920f535e97 2020-01-02 21:42:40 +0100 5330ffb2ada63d2e820165554fc4ddd50dad0ce3 2020-01-02 21:11:41 +0100 5926f8805bd3e36ea4e7af901922f38428c88fe8 2020-01-02 20:54:08 +0100 caaf92d505a6b2e6ab0d1522900affbe8fd26d2e 2020-01-02 20:52:20 +0100 5ebb00bdf3760b35c3cdbfb0ab385e5e365b801b 2020-01-01 22:43:33 +0100 cd658614f9d6aea63283101318717e198aaab378 2020-01-01 22:35:13 +0100 0aff51c552bc8540d24d15d6b5e8e05511e72a81 2020-01-01 22:34:28 +0100 96de3ca253dba6530a6a5fef8a7f8e6d1c561882 2020-01-01 22:29:43 +0100 1bf70ddcabbae37588951a35b8472bef220fccc1 2020-01-01 22:24:43 +0100 58337d79d6b68d4a034305e616ed73b567ef7bfd 2020-01-01 22:19:32 +0100 be18fd07e69d312a20163c21dc157445e9a63219 2020-01-01 22:16:22 +0100 f2db62bc58ffedf3f6b4bf6aadc5d1e24539608d 2020-01-01 21:57:19 +0100 a837a726488bfb72beb5053fbe392ca82cf0c91c 2020-01-01 20:10:38 +0100 f078468f8c8d866eb075b18beb95792dcb689e8a 2020-01-01 20:15:53 +0100 38010758f70d2bc3c1f1c471c218e01c8d7846f3 2020-01-01 19:36:14 +0100 a465f10ee6efbb8e67ac84d8683e6a30280b1d79 2020-01-01 19:34:02 +0100 a2267f4e11fd2acbd83b3e58c03963ff896cc4d2 2020-01-01 19:22:31 +0100 902b841e3bbff7e6e48566388b3d1ea7d8ad3cea 2020-01-01 18:48:16 +0100 ed75e83b1b2fc44b2778bee62002f7cd927a19dc 2020-01-01 18:55:42 +0100 85d66b4ba0da53f597b503233c323648b475fb4e 2020-01-01 18:26:12 +0100 2920e92d4a729f6be7c72de0eff39a70c67f3e46 2020-01-01 18:25:45 +0100 468c775b5fbfc1fe91db81b382c1613990f753a5 2020-01-01 18:12:40 +0100 ee0104d5f1432655f9406129bdf6d0ec941a7134 2020-01-01 18:11:46 +0100 9151d6ca1641b1e3d123abf9086e324771eef41b 2020-01-01 17:34:58 +0100 8d09473b2536adcaff973e6c1704f3b0a8a9de4b 2020-01-01 01:23:36 +0100 6e423d5acf3c39f1b392f35982158255d140895b 2020-01-01 15:48:27 +0100 2751c9878fdb5ca6440ee2ecdd221b8249380b26 2020-01-01 15:46:28 +0100 5b809383cdd346def8b96387c2973c72a46599d0 2020-01-01 15:38:18 +0100 6ed5887149b5528549ed43de7bcc6d9b6e29fdb4 2020-01-01 01:03:59 +0100 da4e0f7524eb4177b579da01c76de90a5edbc2da 2019-12-31 20:34:02 +0100 afda062e381ddfbebeca4970b25d96c5feaf9e71 2019-12-31 19:52:38 +0100 616cd569fe2d3f79d1b5d27386155b071047004b 2019-12-31 19:50:43 +0100 fe87596c818eaafa859e426bdf9e34cc0d3d0da5 2019-12-31 19:46:49 +0100 a865b248c14c76f00ad1ddfc96af85c7627538b5 2019-12-31 19:46:21 +0100 d9ac752cd534fe90622bda038078ce3f77c9c72e 2019-12-31 19:40:48 +0100 6e860ad52255851d67b501e5d777e760d37f4c1a 2019-12-31 19:36:23 +0100 95cc7774faec0e73b158f1f4fe782fb037d64552 2019-12-31 19:24:05 +0100 66a2036c78a73d8eb57e92277e7c3875fad86d41 2019-12-30 21:47:34 +0100 8b6805902c047e0a78e82a101974c484eee351a3 2019-12-30 20:10:50 +0100 15ee18cff5cefc6fd7cacbd1685b519c7bd2920e 2019-12-30 20:03:05 +0100 c2b45f82cc6adf44142035c93912493b49ffdaa3 2019-12-29 20:15:42 +0100 9ca8f4d39caa6914766a38f46a35a950830216c6 2019-12-29 20:03:22 +0100 87bd9d67b311212f94aa6ff469d58f49f27f3657 2019-12-29 19:36:26 +0100 c16d2c9a8291a21a988e00c63f314df793323fd3 2019-12-28 22:10:21 +0100 1a327624f8b3a3b79f987781a26e224573423e01 2019-12-28 22:00:31 +0100 fd2f69c03c8127d0509c575c95cb7f5a68e7bf53 2019-12-29 19:07:59 +0100 992c5809698738aee033ccc826183fbd7ab50d99 2019-12-28 21:07:30 +0100 f5a8fcc061ccdbad06b8bed2fb350237f280b664 2019-12-28 21:02:23 +0100 c9028e1d96336577a4a964ec870e97d5efa19bbf 2019-12-28 20:14:01 +0100 cf45aeeba416c3b06373f88f8958564d1186344e 2019-12-28 20:09:36 +0100 9be40026af495ef4290c0057be67dfbb571f8eb8 2019-12-28 19:55:10 +0100 25d9336409e99bc71e760b7c08176be4fc00abb6 2019-12-28 19:30:04 +0100 0f5d91af848c3b38108a97ddfae61925d718b471 2019-12-28 18:51:22 +0100 fb3e6b1123f1768e26c578894e11b90cb0c00bfc 2019-12-28 18:43:06 +0100 565f9858ca241e46ce77e6053af774c5a517e5e7 2019-12-28 18:42:35 +0100 04c077508cc55d52e1fc3f6c83d901c9016f1610 2019-07-11 22:33:49 +0200 81bd34ef857264f1ff864626766afb8aa0d977d8 2019-07-11 22:23:35 +0200 ec6beb37ee68ea17696f667ee4a1ee32444b50b3 2019-07-11 22:20:42 +0200 11166bd308a2d02ed0882e177641ec5f64889567 2019-07-11 22:20:03 +0200 0f9622879c0844a7fb929c1910ddc4510212eca3 2019-07-11 22:07:09 +0200 915d770c5cfd430639ea3cd3f4a3eee5c4ca4cf6 2019-07-11 22:00:15 +0200 9b9d90115d3d53243d23be3e25354c572b9900a2 2019-07-11 21:58:46 +0200 b0b460a91f93583118bb961e8132e2aa8ba45ce2 2019-07-11 21:48:33 +0200 a6d8d1b43ea98e0a667113e37710652a0c8b380f 2019-07-11 21:46:22 +0200 b0fab8e52be64cf8c82a74233cb3f3e71e393e68 2019-07-11 18:48:51 +0200 1108c2b78b54d01052aec36969cf8fe4fe3c1280 2019-07-10 23:24:16 +0200 0403db50b68b50bb8c362ae595e366ffa6069093 2019-07-10 23:22:23 +0200 1304a9f55ae346be0fc1b45c41d3c2469de47c13 2019-07-10 21:42:29 +0200 201b6db059adb058a2b9dedd35ac80d198d25879 2019-07-09 23:49:02 +0200 57ccdd8e694841a0c16ceb6c68fc1fe1cadf5bc3 2019-07-08 23:25:53 +0200 5fdde2f8d8fef45f8f2b8ac5ed6137effb9132bd 2019-07-08 23:21:36 +0200 6934c87c8888a7a93a5c03d206c6b7850f885d89 2019-07-08 23:10:17 +0200 02ed1a553dede629abda067604fa854d5f1c4c69 2019-07-08 22:57:38 +0200 45b1cb66bce0d95ddcbbb9a54f5374a444d3dce4 2019-06-30 21:08:06 +0200 0a4e8d3cb3cf95cf37fc2ae4043f3025421648cb 2019-06-30 21:06:47 +0200 51ce31e4ba52e43a8e9dcb82a25cc12c5452648c 2019-06-30 20:41:03 +0200 87654a5a1c157c1db25c5eb45663a17055354fbf 2019-06-30 17:41:17 +0200 d546a9418c4c7d262059734844aa316ca5296312 2019-06-30 16:40:26 +0200 71a40e4f438d31547ebd7adeffd006e346db1ee4 2019-06-30 16:15:52 +0200 ca5e0eb717bab9c9b9b6ef18c9db506b842d5175 2019-06-18 22:46:24 +0200 3b6621a449ba336433041049833ab4cae817a351 2019-06-18 22:34:06 +0200 04863645022dec6a8844a121b93c15191bcb5503 2019-06-18 19:46:02 +0200 fb21bd7c874fa55ffa6563ec0972ab8f6ec0d2ba 2019-06-18 19:40:07 +0200 9e507d7037382739370dc52f628eb8bf3c1880a4 2019-06-18 19:32:47 +0200 b10ce4683d1917a1e5e6a57ea3b57ea3f5b18d7d 2019-06-18 18:46:53 +0200 b38973ab4365b72cd92a3dc89658494e785fa3d8 2019-06-18 18:28:20 +0200 b14193b5a7bbf43075bdb1b61767cc8c44309203 2019-06-18 08:15:01 +0200 baa81d3f0db0301e5792d8412081dc20866d2780 2019-06-18 08:03:13 +0200 4f49f151e2dd32ea480488c3afc1be112f469807 2019-06-18 08:00:36 +0200 4772537e3db298adf53cde26d46279f0410af998 2019-06-18 01:36:31 +0200 14935beae02f94a6e482b46208460056ee5ff22d 2019-06-17 22:24:44 +0200 021ea6bcc4864dd13387f5f00afc4d448a0dd377 2019-06-17 22:18:01 +0200 4c53b608627e82df39ce0883f8d7222c33773da1 2019-06-17 22:16:09 +0200 d8ba6e829cc397773a4cfdcd8bac6423de3c9648 2019-06-17 22:09:53 +0200 fb99fd8a4ec520b309a7aaf6b988dea772153815 2019-06-17 21:57:33 +0200 96cb759b32222ece4c7a4645e457dd97031f56cf 2019-06-17 17:29:14 +0200 8460e520652899ce0dafda27c2f64945f28abcb3 2019-06-17 17:14:06 +0200 9f84e077abd617060b7e6f4d376083d20d0dcfda 2019-06-10 22:55:46 +0200 7a6f7a9429a4bb302465458eb30c818dfa78e1e3 2019-06-10 22:17:26 +0200 da9c720fdefc7690181d4fb9dd9f61504a59e18a 2019-06-10 22:11:26 +0200 45ae7df644a0696a3a21fd0e6947097a555f5e69 2019-06-10 22:08:58 +0200 50fc40dcb3371086b0843c215290119140ff9848 2019-06-10 21:59:13 +0200 d82b9d77782c6e6be6ad057229127e30761c5d74 2019-06-10 21:48:44 +0200 226bab858bb2dfb4fed22d24ef1d7b7b9e4f4714 2019-06-10 21:38:28 +0200 7ead105a1776b2ad89ecf9b2c0bd9dbeb800200a 2019-06-10 21:33:40 +0200 0bc86b1eac162425be8fb490fcab288eedb717b0 2019-06-10 17:04:58 +0200 912218121af81393f97c76d2bc7a7d098851f8f1 2019-06-10 16:58:35 +0200 38ef11306037233ff88e40aa5ed5c33ab43b23df 2019-06-09 20:42:53 +0200 359df37a025b4b8987a3a42108a2ca969f3d08b1 2019-06-09 20:40:18 +0200 6337814eb833f3faf8455850338fcdc0792b031b 2019-04-22 17:47:33 +0200 e44228c4544b40ceb83b541b86c0a2317646c46c 2019-04-22 17:34:14 +0200 7e8b910e93eb4d5793aec1d3ce7976db29fc9ac7 2019-04-22 16:12:39 +0200 0db03b6176c8f67c148aad115cc0157c058d04f7 2019-04-20 22:20:58 +0200 55085ca8b71dbd39dab872694484b7a9897e3692 2019-04-20 21:50:41 +0200 c7d319b253dd35aad329941c2b311f7acd58c5f1 2019-04-20 21:35:48 +0200 f881dfea41c6becd1d0034b29be94aff165ea5c7 2019-04-20 17:26:40 +0200 26901efd1c2347cbc36f425643cb11e9a6eb2778 2019-04-20 17:23:20 +0200 3837c67ce8ad6b6a1ca134d5c0f6f8a1090b74aa 2019-04-19 21:52:27 +0200 088195bbf268a3b0e3253ec666fad3e9192b44e6 2019-04-19 21:42:09 +0200 62b9008491a8cfe85061ba1f539c0154f1531f31 2019-04-19 18:31:50 +0200 10d9cbd9534528b9b35c5c7cc0b5e8335df9c184 2019-04-19 18:20:15 +0200 e99115fcdeda09807ee9377eee75f8311cf9c2a6 2019-04-19 18:18:23 +0200 39c17afac3e2ca0ba702cb86936a7b47b10ce5b4 2019-04-19 18:16:12 +0200 1fbee03f0325f3c2d8628c24ebfc7ff78768bd28 2019-04-19 18:15:27 +0200 75938f2b8eefe1d332126f3e8f4966ebb121ed2e 2019-04-19 17:51:10 +0200 d793ffe3943d784321fefeb4a2806f98295bba9d 2019-04-19 17:06:47 +0200 ccbccde8a064d5ce8aaa1fb97955d319e94bf156 2019-04-19 16:44:55 +0200 69c5b44a619a967cd000393c5879dc6780a5ffe9 2019-04-19 16:41:52 +0200 41a548a4b8e20b928747f24773d4a83c1a8cd912 2019-04-19 16:22:55 +0200 79a1928ceb947144b9fc4c499e70fcd7a6c2f9eb 2019-04-19 16:16:45 +0200 15517ac5c4eed28aa2afb24f569aaa1c1fcc1fc7 2019-04-19 16:09:06 +0200 fec80fc52f7e1fb8dba7656efc545181c7cbf9af 2019-04-19 16:04:53 +0200 639d7ca8be2f0a156ea6fa9afe29b173bc2b69de 2019-04-19 15:02:16 +0200 86af4f42a8b5af63f4f39dfa277d3b5d79d96c76 2019-04-19 14:51:11 +0200 215182ed2d8edb0e140524af99b68be826816678 2019-01-28 21:07:09 +0100 f878084442450ff44253cfd708da425e15e1d1f8 2019-01-28 21:01:24 +0100 1f7f5a3a4ef9491b897e650e0a19346e37f7bef7 2019-01-28 20:43:10 +0100 efcdf65bab325220bd24eaf60da1838c6fd4d4d0 2019-01-28 20:21:56 +0100 23cdb53434f948029dbabaf2df917b7ef41653a5 2019-01-28 19:45:44 +0100 c828180f1dc1f8604bdc55859d5e0fb1c843388c 2019-01-28 19:43:11 +0100 6caf1960e15e8e1125307e0a5ee0921b980140ec 2019-01-28 19:11:29 +0100 d3507893d48d31a16c450a84e05469e3a0033d2d 2019-01-02 12:19:19 +0100 5239c5494520b79bcaa84675409940695349967f 2019-01-02 12:17:16 +0100 03423c6ba33b3087d31b8ce532772101f4c1d2e6 2019-01-01 14:50:33 +0100 ac0f05adb40b0d4722077407f70481d3ee584bff 2019-01-01 14:30:30 +0100 3e481d35eb36b071ef5bb251c2835d8428e78958 2019-01-01 14:10:37 +0100 0d3bb3a81d349100fb18d676cc98f2a8c66d2365 2019-01-01 14:05:23 +0100 29b94b901e54c8ee62169f8c325ff29e5cd41d58 2019-01-01 14:04:13 +0100 f47b0d1b9166ea49dd7c46de5de58417a96a1a7e 2019-01-01 13:59:26 +0100 4f9039bcbf76540cf0bbbc716f23385c18443e79 2018-12-31 23:05:52 +0100 764fa9faadf12e8846c0d9e7eeb352e53d2f476f 2018-12-29 21:49:25 +0100 d0d9b8dacd6761482e5ffe0566636bba98ec1146 2018-12-29 21:31:03 +0100 6cd4853f28e9dd87da8bb51ab1793a4c54ad4f10 2018-12-28 22:18:45 +0100 aa55b9b947af51e57758135d4bf22d56686a54f0 2018-12-28 22:14:39 +0100 2d8013b50abd63fb0f0b09263986be0c22368e90 2018-12-28 22:08:13 +0100 aaf60dc090e068330259c00e7c5e5193e51f333b 2018-12-28 22:04:36 +0100 82cd142a9ce532c9ba54b425c77775487c4b42f9 2018-12-28 22:01:01 +0100 a95bfd5f040ba16014be65da0320663037add69e 2018-12-28 21:50:28 +0100 f26c6a97acda8c4b5fa593cc92fbbdb0836bb8d4 2018-12-28 21:48:16 +0100 2f32a988e5b48f987dba5d9552917b428d0bb5b7 2018-12-28 21:45:31 +0100 26126d45c6a747ce1c6dedc5c9825e7182a6172d 2018-12-28 21:43:52 +0100 8c9e553fe6dc64a24d4d30296acc14ec87b261c1 2018-12-28 21:42:48 +0100 9efdc51492d4ae147b5f6bacec4c9df6fd52c061 2018-12-26 21:27:32 +0100 01a5f9ae18a1f907ee28331817031861f32d39d0 2018-12-26 20:54:34 +0100 bcc04d7d91a053dbae42d20763cd6e5fdf879dc3 2018-12-26 20:52:16 +0100 b98c28ef4289b09ccaa1a524dba96c773d2c9119 2018-12-26 20:49:31 +0100 3ecdda54947338cc3ccb7603f139a068ba458910 2018-12-26 20:44:49 +0100 b056c9b283f50a127c7ed4ef24bafe23eadeb6a5 2018-12-26 20:33:02 +0100 4ce2fa5b347032de9f0c7a93a38a7c0097d8e539 2018-12-26 20:15:44 +0100 28e0c3840506c2a4ecf0494492eda110550195ca 2018-12-26 20:10:12 +0100 5c883a5719bb8f0cdd9e362d6eddfb7f861b96ec 2018-12-26 20:06:31 +0100 ef1b474ff71dde1252f3624bdabe61d93b637cc5 2018-12-26 20:02:40 +0100 25537b9abef3ccc931876bdbb19568f3f0af3691 2018-12-26 20:00:40 +0100 844ec7d8a7a20826819c4e815ebb6ed61b883358 2018-12-26 19:49:04 +0100 afab1403107c7aa8cfe5a987a140a69a31ae9f05 2018-12-26 15:22:08 +0100 8f0850380b9e30de13c2c7b5c3366a90708e9b94 2018-12-25 23:31:04 +0100 fe7000e5b65fbb76668c5d28f2257f25b7d2dafd 2018-12-25 23:22:27 +0100 7814f972b484c7b468918348b06d92a3b91214dc 2018-12-25 23:19:02 +0100 2a697518bf39eaf765eb924d8719d355933374ff 2018-12-25 23:05:32 +0100 2fc9178640da72afb87c78650dd08a806dfbc5f9 2018-12-25 23:05:14 +0100 3484d792f50ca104dc53b678aeded4b3c0b06d40 2018-12-25 22:45:26 +0100 45c996f65dffc07435d3247ec362aeff35611ea8 2018-12-25 22:42:40 +0100 51d05d24de5466d8466677a3f0e91ff5cf88cf21 2018-12-25 22:35:36 +0100 4d17e9313f93acf59d4f21966a17880f158344b2 2018-12-25 22:20:39 +0100 846b2f5740a673e84af71bb8eef28253c49079d7 2018-12-25 22:55:24 +0100 3f452912f05d773af0747851212f29f6bd4736bb 2018-12-25 22:10:57 +0100 858bbaec9f8c9430e11c5c7785a4af7a504e23c2 2018-12-25 22:08:28 +0100 5b2c50cfc9cc92be84b22a018b0d43ba90aefa52 2018-12-25 22:01:02 +0100 08adb8b5517cd60ebe0885085d8fe9b5fd673138 2017-11-08 08:47:11 +0100 2c78ad512e1d27c686ea23f51814e99a9cfad6fb 2017-11-08 08:43:11 +0100 6bb565da8508a56b77b2c3624ed8b6472ba056de 2017-11-08 08:33:06 +0100 ab5a5ef66b19a5b1bdbb6a3d5ea28774ed2651b0 2017-11-08 08:31:15 +0100 dbf26fa68ae720562718ccb08a430461b3b70e78 2017-11-08 08:28:49 +0100 a17bda627e7a58e2f2a9dee43d78b43b13030177 2017-11-08 08:24:44 +0100 f44c6ff71324fd60a3caf41542de05ae8cbeeb87 2017-11-08 08:18:10 +0100 9f28647cf42815aca976ba41e2f608667af8add3 2017-11-08 08:16:43 +0100 be15d1ccc95c8ee7c4168fa46fc1fdd261ca041c 2017-11-08 08:09:56 +0100 f1fd5a9c5d846d2bac204ef3de94f449ab6c4bd9 2017-11-08 07:54:56 +0100 fa0adba11af0414e35971a57038ba5924d975af4 2017-11-08 07:53:32 +0100 9323414a22b5b72b1719a5ac9311549f06578eb9 2017-11-05 20:22:03 +0100 051498dd43ea7a79a646dff125331b3cde8d2646 2017-11-05 20:21:30 +0100 6d66fc724f26c3da2a846aea323306b8d393e1a6 2017-11-05 19:05:37 +0100 82b7f01ae18637c734c1c861d130bd350185ab45 2017-11-05 18:59:18 +0100 cfc3608a3bad4409759944d37b778fec75b89920 2017-11-05 18:58:00 +0100 77a6010da4ff08a0542f13eaf46b63c5289dce8c 2017-11-05 18:52:24 +0100 7a8bf09df12782edd219f4d79edc7885393cb22f 2017-11-05 18:48:19 +0100 339f5fa8497c806220e4569713e64852a26fe616 2017-11-05 18:19:51 +0100 d816c740a1064e6e47265839134c0f68f975bac6 2017-10-29 00:35:10 +0200 115238e52cac5964287a644826ffb0c770d48520 2017-10-28 23:53:04 +0200 047fdd2e3b0ca570f7aa489b9e78be5d934ccdd8 2017-10-28 23:27:23 +0200 72730593b8e8c73b459cffa7e4770be009e7e703 2017-10-28 23:26:52 +0200 012c51bae8a3958447c724d4d6dcd738b0f94cb5 2017-10-28 23:15:46 +0200 6715528f9dd4f2af3e9465962a48097cf97cf406 2017-10-28 22:55:10 +0200 119855f61020f326091349fe1413a48e06a88624 2017-10-28 22:49:35 +0200 ddf89f30c17de33f95f01f8e0ef82e802abf33f5 2017-10-28 20:47:26 +0200 8872139480878c8545a9f3a3e9e5f9800fd81c5c 2017-10-28 19:41:54 +0200 fc406fc7569c59ef2fbb0d0c01f3d7de0e246c01 2017-10-28 19:33:33 +0200 abe69234a0f4abcf7d55c8b415e53643dfd91f46 2017-10-28 19:23:06 +0200 6a5798e816f97b60c8d20d1b2ca01dadd1e51ad5 2017-10-28 19:17:42 +0200 d9345907987d21d00b12314171a7ba651c768205 2017-10-28 18:38:46 +0200 0a4ae6d06592a1f5e05ba299b5ae0d44242441f1 2017-10-28 18:35:01 +0200 e1b28c362dc9f1cb894bda37e359292ba07fa9a9 2017-10-28 18:25:26 +0200 dafcbedf3c3e496ade698c6ebb82d599b80cf1a4 2017-10-28 17:51:02 +0200 a99272a203717bf053a6ad3788090a9d9bb36728 2017-10-28 17:18:49 +0200 ad4a3fabca5ce074495192e8a7e82b65c4323da6 2017-10-08 20:00:38 +0200 52de6fc3b3771e20fb1dc47ec9fd38a4620d37fd 2017-10-08 19:47:58 +0200 b5dc57c4c9b410fe3a7d652ea30465e9782a0bd0 2017-10-08 19:46:05 +0200 6a9c50599dc934f620a2524a4302932ea748e0b5 2017-10-08 19:43:58 +0200 67f2f22fd7b1e1095ea3b7bf8d45260b34399b00 2017-07-23 16:11:59 +0200 e8f6efd315f79990b6b75440e956aac78eddac59 2017-07-23 15:35:36 +0200 e647c44695e5c0b4c763367bfcac5aa04a17968a 2017-07-23 15:27:06 +0200 f8e50ceff76a2c6e262309b8bfa40c2152ff25dc 2017-07-23 14:54:45 +0200 763a53eb14a36fc31b73474e8c5f7796c4905caa 2016-07-27 22:54:56 +0200 e144a908267c9bfb69e71b7a617c3e26d5d447b5 2016-07-27 22:32:49 +0200 db4faecacddc49acc1408472c28aba102fb54e74 2016-07-27 21:48:34 +0200 55c7371d42d0334f3173c948ad92aa3526191813 2016-07-26 21:38:34 +0200 d05085d408be750cdcc8e92f89e8cb42a851b64e 2016-07-26 21:27:39 +0200 c04649d9b99b763f05537c38c2bec0056480329d 2016-07-26 21:26:28 +0200 98bbe6636e165f910e57d832beb2ae1b1b518054 2016-07-26 21:12:39 +0200 e3676de65845b6ce42ada64bdcc9fe9f7df1d040 2016-07-26 21:10:08 +0200 2fd5e82c0045c679c7faae33cc1e028b022d5b53 2016-07-26 21:07:45 +0200 f6fa89487ddb02aca5238e231434371f1cb8e248 2016-07-26 20:47:54 +0200 404542d0caa18830ff17d402c57d49be88af857f 2016-07-26 20:43:48 +0200 61457e311f5273f577037f81c19e6a91ac3a503e 2016-07-26 20:32:18 +0200 5a483360a35948b70b9bd1cc084ed8fe0df933e4 2016-07-26 19:57:39 +0200 760b21c5194beb38b0f2061ba652389b84de2c89 2016-07-26 19:39:00 +0200 9fc69ffd8aa1f65b313f9617b9492b1a944caa2d 2016-07-26 18:58:09 +0200 9da66d7ae5d23e34687fd1aa207eb0723c606cf4 2016-07-26 18:57:43 +0200 733428825fad2d746bb9256b4fa629a463cb072b 2016-07-26 18:34:34 +0200 34f0aab15b480dcb1fbd67c88abe46d87e8e0cac 2016-07-24 23:28:46 +0200 3b13d125c8762b6e0462ad755caa1ea0d8b75f02 2016-07-24 23:09:37 +0200 a84733850cb2400f89a1c5e73c39f95935480de6 2016-07-24 21:30:42 +0200 9bbaa8b27d2de65a49c8d2d11824efca005a5b7d 2016-07-24 23:11:53 +0200 3324c38479865bd4fd8f510692814cdbe5d56d37 2016-07-24 21:02:33 +0200 dd56085afed9f718eb82035104fe543196943d18 2016-07-24 18:48:12 +0200 c457ac6c0b9b4d192cd97ceefacfee5b524732e7 2016-06-02 05:04:16 +0000 7948f4acf73843b82e7568c437b08ebe7e61f7fd 2016-02-18 21:37:58 +0100 9fdb9d83406de369f58fbc47021545acc88ecf01 2016-02-18 21:37:10 +0100 e11db36c8ccdb82268e74957b17ec715f2f19525 2016-02-18 21:36:10 +0100 abab579857a96813322c7282fbb4f72cd9954320 2016-02-18 21:35:29 +0100 15bfff19be03aadc3272b623bca38b8ab8a8a53d 2016-02-18 21:28:54 +0100 2cb26a6676b3e2a64be83d9513dd55884b4c8712 2016-02-18 21:13:47 +0100 ea3101ee2c14159aa0f8e5a0f7ac895312b1fbac 2016-02-18 07:39:51 +0100 773a040b9d80363ccb91508193fe7248c6bafac0 2015-12-25 15:44:17 +0100 ad1c2ef221e3a43825d195bf7ed61022e245eb38 2015-12-25 15:21:37 +0100 308aa57fd79bc81baa1b93f8d085f233378f73ed 2015-12-25 15:20:49 +0100 4992222a71dc295887be5db41cd256c8ac86f601 2015-12-24 21:23:10 +0100 854a5ca568120560dc4679af16040f6ede7bbed7 2015-12-24 21:22:38 +0100 584dd2220cb92303322ba9d94d9758aa4e5895a1 2015-12-23 22:43:57 +0100 aa2d3dbe4a19b071c911e646d120a56da484f453 2015-12-23 20:33:58 +0100 236781615dd35adca1dbed54f2393196fbd14523 2015-12-23 20:18:45 +0100 c6d0136bf66260c302304a9c53848e08337d405d 2015-12-23 19:08:31 +0100 ca4dd6285754c05b2fa37b9c948ea27f3ef58d35 2015-12-23 18:47:24 +0100 af134b5c2f69c5b27cc3ff5a4d0e85cb47593912 2015-12-23 18:34:07 +0100 15a349e3f75a055e1fcd48f2570169c185ce12af 2015-12-23 18:29:53 +0100 d21af13815e4eb7f61c10c7e883e221c0eedc801 2015-12-23 17:56:01 +0100 86d652d1aa23642e188f25c4e7224c134b92aa18 2015-12-23 17:21:44 +0100 aa30554c49ad6719fed1b6f5bcd690690f8fd37c 2015-12-23 17:10:41 +0100 55ad5158ce7c798d7c54ea113a93df0364e6ec45 2015-12-22 20:55:22 +0100 271da08b936c34eb309298e00af5ce9ae21fe146 2015-12-22 20:53:39 +0100 4524fed556aa29fc7ce87e27537fbdf20ad95a2f 2015-12-22 20:06:08 +0100 019e2921d4ccff977ba795396ad51247bc2d5f00 2015-12-22 20:02:20 +0100 21d9df0d26acf8a35c8950e86de37a438e1ae25c 2015-12-22 17:23:41 +0100 e4d4d19902b27d51d4043a05e2076ed12acbc1fc 2015-11-22 19:31:03 +0100 5611ba25e175b17e0ce3b48c52befbb2ef27819c 2015-11-22 19:28:10 +0100 1c45e339d8ee2ab87ee655e3c20ce85f322fa3a6 2015-11-22 18:04:14 +0100 da98f7a58ffb8927f82088702b8d32ee30a139ff 2015-11-22 17:48:53 +0100 6d6c4371478f1e95b8437ceef27779efe2380258 2015-11-22 17:46:56 +0100 9c4c35ac2d7975cac578c631a933d93164424e5a 2015-11-22 17:37:22 +0100 9ecdf44f54f3f57436c26df70b1097da598fc059 2015-11-22 17:16:22 +0100 e7567e7faa7505c582c4a7ae7a0667ea7bf9b77d 2015-11-22 17:14:25 +0100 89971da64aaba1bd8c407b49d4f7479561934fb3 2015-11-01 23:02:42 +0100 e2a2ee52d1ab8a1d36986f7fada62c4173dc26c8 2015-11-01 22:50:46 +0100 e80ed840fef23b9764cf45258a845eb000576197 2015-11-01 22:24:13 +0100 7518d2b15a09b11864ef7fd62bfc3eea326455ba 2015-11-01 21:58:58 +0100 4ed85584a46662a70fbccfa6bbf6fbd6419c34a1 2015-11-01 21:55:42 +0100 1a524dfae6573866db77218c1a9ddd7c13871e1b 2015-11-01 21:46:44 +0100 4155591fb000bc8a0e5f38d3d54503c3b4c37c92 2015-05-25 18:16:30 +0200 05bbe2b6daf07af494fd2570f7b90275c78514ed 2015-05-25 18:06:47 +0200 594439db2b405d8e1a3c81daa65b5c1c155e4b4f 2015-05-25 18:06:09 +0200 38bcac4d9bf0190350a3dbd1acebaeaa51eed13c 2015-05-25 18:04:26 +0200 567235534c8addb6aeba0f93afd030aeffeb17e1 2015-05-25 18:03:39 +0200 fd53db64040d9c2e2dcfeb533d71d934120f1e0f 2015-04-02 10:01:53 +0200 57f51b019fe6bbe7435a3d0c2e2f05cb3fc73df1 2014-10-12 14:49:20 +0200 b102a60e72f42f86a78d75f62d632c8a56030945 2014-10-12 14:35:43 +0200 87a0e311a95eee14521508efa8acba6ed18a7696 2014-10-12 14:35:30 +0200 089ea8a89348b7d9b7168f77dff1470114552741 2014-10-12 14:31:31 +0200 8f56d7eb65dfd8716e31932a9e25d8d07fd6b181 2014-10-12 14:27:59 +0200 94bd4ba22c392af23172f1b76a63ef498dfa6886 2014-10-05 15:16:52 +0200 6d449c46f910bfed6d88bbfb82446778c42c67f5 2014-10-05 15:16:14 +0200 d7ff12ef9dd08112920b402e5980f06257f07e01 2014-10-04 20:17:25 +0200 4cf6aa16f6fef6314209d258bdef3849988067ca 2014-10-04 19:12:28 +0200 e3f1527088960ca1d3ec2a1b83ada6152400cad8 2014-10-04 19:07:49 +0200 7adf10cd57a9fa29a621e2a7e2044e355f204f1a 2014-10-04 18:43:31 +0200 7e82d22478f68a9172186ac44badfec25b8b567a 2014-10-04 18:43:11 +0200 8a28c4e4726bf26b621ef525c0cea0a9e50a7701 2014-10-04 18:38:55 +0200 3d062f4f4d0ea007c349fd675edc8c8d5a574151 2014-10-04 18:20:42 +0200 785ce7345cb19da1a70e79dee027ef593ce9bbd2 2014-10-04 18:12:22 +0200 e5c8c5ec043a01a421a2f36cdeba8e3651e1b479 2014-10-04 18:11:03 +0200 2df7a15dd635a5c195ac351225a395b2b2b14dee 2014-10-04 18:08:41 +0200 59357dd7ed91729b50836d584226cd64b0d82280 2014-10-04 17:46:18 +0200 37951371d6a9f982cb7ada0ed37008f7ff023872 2014-10-04 17:36:46 +0200 9bf7a17fd30160fb3c54f1e0b07b6902227c5fdf 2014-10-04 17:34:46 +0200 05c85b157f75f9285db5670836fff817d2908d26 2014-10-04 17:14:39 +0200 c470558c8ac9f7030c99c882fe7deab395c98b9a 2014-10-04 17:11:25 +0200 3e85c5d0e389b3d24069d6b6e30d921e72affcdb 2014-10-04 16:59:05 +0200 f98fe96b16ea18f9a71973bb8deb4ec90aebf361 2014-10-04 16:56:11 +0200 4450675903e48acca0be1ffec6920680346e166c 2014-10-04 13:41:10 +0200 b0b4d64b22bd28f8507103abf8e6c8bdb6bdb39b 2014-06-18 11:46:19 +0200 f02dcd7565fb7840e0ff1165e0adccee99f091f8 2014-10-04 11:06:40 +0200 f46b9ac62cea753c37bc076e6aaec7135f2f3197 2014-06-18 10:14:32 +0200 87e1747a2848b3d0bd5a112cd8b5e2c6a95b1d26 2014-10-04 10:09:43 +0200 4b8226ac53dde71c1224081d05656c54f91f10c8 2014-10-04 09:13:38 +0200 dc17ea1b037ee96df89a2067d6c0fbfc5cb1e78f 2014-10-04 08:43:42 +0200 91850d4fffd861b95ea532d8a4b3b0b699ae82c4 2014-10-04 08:42:36 +0200 85db55cce826041a8ec7187d9cb9d1265e2fa2ea 2014-10-04 08:34:31 +0200 1f5bd89da820610658fa35742021ee99cb574873 2014-08-05 18:56:38 +0000 ec18b535f1b6ea05ca5f1c50acb954bcd1265145 2014-08-05 05:57:31 +0000 cebb68df47d6d813f2e41228564d810d1fb56def 2014-08-05 05:56:56 +0000 3d4b394e45ed9942d3f3100af124524551d7bdf3 2014-08-03 17:57:07 +0000 f15ab581220606781e62c854914b8acd57cc66b4 2014-08-03 17:56:30 +0000 615f1f5f17f6b585fbc828ed7a8340f1990a4903 2014-08-03 17:45:24 +0000 789b19f43e32f82044541853bbf44da57d9bbf14 2014-08-03 19:32:00 +0200 8429ca3ed7a0a3bf97da48529fe0c4384a68b79e 2014-08-03 19:27:18 +0200 71c6c2d56c871f124b1150b942e270269de9a3b2 2014-07-19 17:52:12 +0200 accc463d0f94cdb20365ff33b76e95b61baa0a13 2014-07-19 08:56:27 +0200 4523711a68da0e3e3bcdb8e5cea9c8324c68458a 2014-07-19 08:52:10 +0200 bd7ed52a9e143e6a2c84f9b302dffff151e07f0d 2014-07-17 22:42:23 +0200 3f24e9137f7dc8e6750850d4423a3049dd73f7b0 2014-06-29 00:12:50 +0200 f7e3f834aee4ed830903541c13f83483ebcdea2b 2014-06-19 09:32:23 +0200 e6aeea739d19255635a77d1f1372dd8c2c1e55c1 2014-06-19 09:31:10 +0200 6e6a487a47c18605145591ab4e188f788ac767e3 2014-06-19 09:27:15 +0200 4ebf782fbdd53a82bb5d22eec02a108a48c11ffe 2014-06-17 22:17:19 +0200 4fd982d94393a69c76d9c01e55ba6ba352416d0a 2014-06-17 22:15:42 +0200 ad6ab9c800a861c54c50a9f945ca1d0f70544ea7 2014-06-16 07:36:21 +0200 c657e4379d83c59368ebf3cd7da3d468725f590d 2014-06-16 07:24:57 +0200 6232d457d6ee4f603221e0f0472d37047aa1119a 2014-06-16 07:11:50 +0200 f11b6b75e14c99e66014c82c5a17c0bf5cd4b8dc 2014-06-16 07:10:33 +0200 f0d9898355f60f09b7d3d8b141423461809c6f56 2014-06-16 07:08:50 +0200 cb2d0761756e60ee9496aaa9f9ad3e0a29cb122f 2014-06-09 17:25:07 +0200 a68271d21e75bc5bb710a6dd372f4eff0a7fca45 2014-06-09 17:21:36 +0200 d061b13546dd4ab1a376f2ef3fd493de5c48be86 2014-06-09 17:19:19 +0200 afb7c4ef8b5c67ec08e677383b55c712127da80f 2014-06-09 17:12:40 +0200 bf6b67faeb06690d3a4614efddb88c33da92a45f 2014-06-09 10:50:37 +0200 2011d4f8478ea60df8d43ceff1726e4f532a3bb8 2014-06-08 23:55:02 +0200 d426199ecf045d5c2f6f60fa661177d01921d7ac 2014-06-08 23:39:55 +0200 73e3c87ace0f983144b1390c397097dadabd6599 2014-06-08 23:38:53 +0200 f56e909dac738d85b149574fb6c2a77fe01f13de 2014-06-08 23:38:29 +0200 44cf845b3a2a7dab8d0eb1791caa5c4eb018011e 2014-06-08 23:26:20 +0200 a2bb6b4432fd148da2208b6b6831ea8755a59443 2014-06-08 19:08:52 +0200 1c4b4e02a4ec10b1a6ee087683d3837fc7a5e208 2014-06-08 18:47:06 +0200 180f878e065f73fbf67dedc378854cdb8527dd0c 2014-06-08 18:23:10 +0200 5580fc0e9743b35118540cd2b7c6e816b5e59b0b 2014-06-08 18:18:16 +0200 98de30ba6598e2bb14bfd0b6d23c9df403d80d30 2014-06-08 18:15:03 +0200 a6c7a81d8348d3cfe8804516de343d401569f7d3 2014-06-08 18:12:01 +0200 df4908c22dce8729b5721bdf53f2d568d5fd070f 2014-06-08 18:08:40 +0200 f3dd93f6df16ded4392e9a27bcc57e190bbf7b10 2014-06-08 18:08:21 +0200 4ba4b3a27d3e3810090445ba375e7a9d5def46bd 2014-06-08 10:56:26 +0200 7f86abb3cc0bdddd5db1a1c3829c840dd5a9f1f0 2014-06-08 10:51:31 +0200 df6013afe530df4a56770638054bc47d72103136 2014-06-08 10:47:31 +0200 b03cd68f3e116cde044785ba08a6c42071e360f7 2014-06-08 10:45:07 +0200 9bd1b699d290e1557162e2c437843373d9d6185c 2014-06-08 10:41:04 +0200 53c8a3f9f9028281b90c547aa4095c476a5892c0 2014-06-08 10:35:50 +0200 7822f3b3caf7190a91c74f3363df87bd7f4b7d93 2014-06-07 23:32:31 +0200 b337f77e1927d041df76f27325caa28fb9cdd15b 2014-06-07 10:32:11 +0200 2444640bb1b79a13922642b13e9fa85a2a4d192c 2014-06-07 10:26:38 +0200 de25cdf2d91f3a5b55dfd74eef0a0db6cd265c59 2014-06-07 10:12:34 +0200 522f64c55b3a233f7a4ef9943e9a3c44b06b0668 2014-06-07 10:11:10 +0200 1ced9abb46b887986e839eabc619891090d458ee 2014-06-07 10:06:24 +0200 7f15f444d373d45066a1c229f82a3f81bfd23420 2014-06-07 10:02:06 +0200 f929cee43a296d27860b4ba24f6e82118868d5ff 2014-06-07 09:50:58 +0200 99305b95276aab90ac1e3b3d7b2e8ca0f3750b44 2014-06-07 09:47:35 +0200 2e910f26e379c9522279e3fc38eb6a9a1c41cdee 2014-04-23 08:14:45 +0200 2f156c46e61018425aea6bbcd98933a1ad5513a7 2014-04-22 05:02:24 +0200 ac6b3ef9168737622761303e4da599e360ea1ef3 2014-04-21 12:50:04 +0200 f0919d55b94f56126910198c0bf69f856f4f8b49 2014-04-21 12:48:51 +0200 f0ed152669616347141b2251669eb647848cb2ae 2014-04-21 12:24:50 +0200 accf4230872adf02faab3528007580aee189f2f1 2014-04-21 12:15:51 +0200 84b6e91db8dba80869909c8856d3bb8f9abedcb0 2014-04-21 12:13:59 +0200 b1f704bceb2a404f85f7de3ed5406c2f801421d5 2014-04-21 12:06:24 +0200 b6d776c396cbdd95e1d2ea58c26444baab608059 2014-04-21 12:04:51 +0200 97ee5a39ddc51739973807d9694bef6df2c18b5d 2014-04-21 12:01:08 +0200 12c9fe7eb85314578630160e73378c53c2c71ae5 2014-04-21 11:59:09 +0200 21dd4bde34f54babd54e4ed4d057ca4961521ff0 2014-04-21 10:30:30 +0200 e08808270f234d2bbc1a7a6ca070743b7e044c6b 2014-04-21 10:27:09 +0200 ed34166652807e92d5c7e7890b8130f8b9d5465c 2014-04-21 10:20:33 +0200 86d654076757847497f30bdc6a3375734eb49062 2014-04-20 11:38:52 +0200 bf44acddfa909f34406db6d42f3af81cc42ada94 2014-04-20 11:22:49 +0200 db074769adc62c70891dc3e91d1e0aaa9d957f38 2014-04-20 11:17:18 +0200 7e2a8cb204a54045eafb9e77715fc30d35d2ee06 2014-04-19 23:47:52 +0200 b65b56a5201acab633f0ceebfad3ab82ea4d893b 2014-04-19 23:47:10 +0200 6f80295f04fb928de1efb13b3925fdc44a49f7cc 2014-04-19 23:16:52 +0200 51e52f45d5284bd8532eb3745bfaa6df6afc9f73 2014-04-19 19:13:39 +0200 cecbb557b3c201b38b96956bd9406848bbba28ea 2014-04-19 18:30:52 +0200 f5c41ed6c8030e6b02b1ed97c3d6a09c310933c1 2014-04-19 15:09:23 +0200 8928b1d2e0f96b9c000ba36277d99613e0ea6dad 2013-11-17 21:44:02 +0100 c62ef291e9efbeeeaf56a797fb5864dd4899179a 2013-11-17 19:36:23 +0100 5e6a2f5ce44b11c10746811c6c8738a7215e5961 2013-11-17 19:25:24 +0100 9052cd022fb012106dba37907fcf3c2637bcbd66 2013-11-17 19:21:54 +0100 7edee300fe0501ab62fb6b184c4f5924ee532d8a 2013-11-17 19:21:43 +0100 8e4066b4498ebc5ae877cc6a4ebed60f1d895374 2013-11-17 19:15:14 +0100 cb49c1ad6065241080866dd260129d0161bdaf89 2013-11-17 19:13:36 +0100 8e049977a25b7229a7c68357f1a99384a79655f4 2013-11-17 19:10:02 +0100 be28717d64481744f970cfe11e51b1bd01140c23 2013-11-03 13:56:35 +0100 9eb11d8782731e24b933e9b670ceea9625f99e5a 2013-11-03 13:51:15 +0100 163574681fa2d3cbe5985b35a9dbfc8dbf584593 2013-11-03 13:29:28 +0100 2433a1b5da055f967c808ad11192b72e0073dde8 2013-11-03 13:28:55 +0100 712370608526d0010d77315b42845343bfcd105a 2013-11-03 13:26:12 +0100 9991e5613a9850273b5fa1a174900dcc91207dda 2013-11-03 13:22:15 +0100 22d25ed216e603915c1ebce0812f49b318642dfa 2013-11-03 11:05:41 +0100 fc2e0095aeac4bb559c6f6958e4c63663c1f401f 2013-11-03 10:49:52 +0100 5c1ff2243a94f305a379777c5a68b946b6c79ce9 2013-11-03 10:43:07 +0100 06da60eda3dc087e5a2f2d9213efbc8aa4e28177 2013-11-03 10:36:47 +0100 98145547d751e302a0e121d51c5b79d0cb42c5ea 2013-11-03 10:35:41 +0100 6ff4ea8b9056b772f4a680eca22bb38886a91657 2013-11-03 10:18:08 +0100 58dfa5b041f75cdeb15475a2e0c356352facc860 2013-11-03 10:13:29 +0100 19da6b5a973b794c1a430bda8175f5ebc1cc01a6 2013-10-20 22:24:59 +0200 367eae68fbb01b9c78628b4e8a7b7743dd71b773 2013-10-20 21:54:49 +0200 0d246b7bf5194edc932ab772cf7322d9c079db57 2013-10-20 21:42:14 +0200 f380fc5b6134087a74c1fd9a74b6d03cc0a3039d 2013-10-20 21:40:20 +0200 f80807034aaa46adac307296975c823c3747110a 2013-10-20 20:19:57 +0200 8a61e7e863cb2db4d6870af0dc6a9da849e47187 2013-10-20 20:17:36 +0200 8c0171ca72494f0809a62cfae9fbd93c97ad7d9b 2013-10-20 20:14:26 +0200 7d9ea26ff8633bd67e8ecc10d38b6a1220256dd5 2013-10-20 20:01:14 +0200 1ecd5ea92fe2cad3c89f812d0863000bb5e9baea 2013-10-20 19:36:19 +0200 8b6130af0bb858a7e30e32e9dcd4b7362f302075 2013-10-20 19:34:33 +0200 1c8ff4e25067ef29e751e918e293f23acd8a71f3 2013-10-12 14:29:08 +0200 f8748ffee7d3ec39d866a387d189959551d25c01 2013-10-12 08:05:15 +0200 9abea4d767ffa205ab8b45a6a3a421e1fdc65565 2013-10-12 07:57:27 +0200 deae6da18d0d86b428fbaa377b29c8e41d1dcba3 2013-10-11 17:53:51 +0200 a79e39d92cb7a4cd2c6132563b0836904c5023ee 2013-10-05 17:22:35 +0200 f556cc6edbd2bd84b45a9eab166d29549c2d2738 2013-10-05 14:56:18 +0200 fb2c826d2191d090d1a6911388da8bc6b714523d 2013-10-05 14:27:16 +0200 7cfe94869054967aca9344486704c3abc580b402 2013-10-05 13:54:34 +0200 cf3bbe4da3ebf2b61aa48363872a0281088b383f 2013-10-05 13:53:46 +0200 694453ccb1db40382d55f5eb9cd16130067d31e2 2013-10-05 13:53:12 +0200 d22c346331f29af69381c00c9af228538593772e 2013-10-05 13:46:24 +0200 a91eac906f7622f119df0234753ee48e6b184c1a 2013-10-05 13:40:07 +0200 5b6891ace50bcf0ee928521cf3c21c83431f2a50 2013-10-05 13:39:42 +0200 977baccca9c0d34e42a21c90aa08540ff781ca0b 2013-10-05 12:35:28 +0200 73e832ae220bf7681fac6402e90c05f0eea82903 2013-10-05 12:32:39 +0200 70fe828daf542c1bcf11a27ede136534ccd7433d 2013-10-05 08:31:38 +0200 0468edc17e0b0e5804493ed7c3d6cc65a6bd7fe6 2013-10-04 22:30:13 +0200 cb3f24c84a86d08e5ecc9224efb14a383d54393f 2013-10-04 21:52:41 +0200 77ee9e710f7dea833910ae4ae5464880ed184602 2013-10-04 00:01:34 +0200 c5270a225b1930ce43cb0db035b46847e8e98772 2013-10-03 23:47:44 +0200 41f8d8129fe832d390282379f80e5aca119c0a83 2013-09-29 19:41:24 +0200 8e311b0f639713acfd1745488d2796f4572bef76 2013-09-29 19:40:39 +0200 be93b252fe375548e7938c74cf474664a91ff65c 2013-09-29 19:02:38 +0200 332b6ef93e21a862f4ed48e9d9f2424ad3d3378f 2013-09-29 19:01:29 +0200 41ba2b2179210374256e0a49f75c9409fa5ba0a8 2013-09-29 18:39:13 +0200 462f2288d9c39aca3a64e5dc4215033336de664f 2013-09-29 18:38:28 +0200 c081d5f9d8935257d857cf64da7837f8db93045c 2013-09-28 21:33:25 +0200 7e42b305e540e48f3313640415a3fe8c8d60acda 2013-09-28 21:14:13 +0200 5b1192af17e3ee2c1bdbfefdc1dba3e66f03f470 2013-09-28 21:10:24 +0200 9163d0b61e9e73e364cbf73a5702f1d8214d4fcf 2013-09-28 20:57:10 +0200 55e2e331871c7bfdfa8f5a73bc53b7e99a0795fd 2013-09-28 20:55:23 +0200 fec3c3514804b273e7ddded0fb5d77d0caa87e88 2013-09-28 20:55:00 +0200 ef6aa1b665cde846dfb48fab20dda0f2586ca3d5 2013-09-28 20:34:40 +0200 82184528208d32e6f16e764af08b7a4e158551ce 2013-09-28 20:09:59 +0200 8c789c80b8fe4fbcd2d4ce9522688be1d594c996 2013-09-28 14:59:27 +0200 a3948592c86260c8cb1b60d960a11a1179d5b72d 2013-09-28 14:48:33 +0200 11f40d79870b47c2c9f623d240b76ab440b5b1d9 2013-09-28 14:09:55 +0200 a165c72785b78abb5df25de2e7ee51787480744d 2013-09-28 14:05:33 +0200 a0592fc2cd95dcd8eb0725ea15158c916d3f86a6 2013-09-28 10:03:16 +0200 972e357ad85b8cdbd0915b6d30c03600db5af585 2013-09-28 10:02:06 +0200 5c308bf393b6bf4e31c02adb16476622fd99d7cf 2013-09-15 13:35:26 +0200 a99e4726a9b60c164e219db2af185d1fdd9bbfe8 2013-08-17 13:27:26 +0200 7e8a3e75a8f3ac7a20a4d23145281ecd68056286 2013-08-17 13:26:06 +0200 8610699253f4bb49a1cae40f8ecb07749b477c30 2013-08-17 13:14:26 +0200 0489ad9c39fd1a4842596fc7fbcd4af8de43a78e 2013-08-17 11:12:32 +0200 7f43826a5d5cc85771d83987c70b5569acb84833 2013-08-17 07:51:23 +0200 2b0ce536bdaf948325c9170a4c9b730aec296931 2013-08-17 07:45:40 +0200 114e1cb6f482072e97b2ca2d4d4fc18a66cfc363 2013-08-17 07:20:50 +0200 e143c66ab355cbc94186e9919e94571c6ca9575a 2013-08-11 14:16:02 +0200 614f327cc3433db5b67424d25f64a75afad9b9f7 2013-08-11 14:13:16 +0200 c36731e48b97c414d4b0ebdc12919379ef96d6cf 2013-08-11 14:09:38 +0200 92393d50940238fcaab0ea166d2e6b14d424764f 2013-08-11 14:05:58 +0200 09ddc8e7e65c5b4d82e90497e48dfe22a1f19a30 2013-08-11 14:01:16 +0200 98ed008449df96a80afead4ea0d2236a9e729c78 2013-08-11 13:49:12 +0200 aef54d6b28e3b2dca60e99c391832443b8984c6b 2013-08-11 13:48:41 +0200 f591d1523daab1fd61694a44a60d4bc4cb6be163 2013-08-10 19:37:45 +0200 0a81da3d8fe30c14fe08f29ebc9925539be4df9c 2013-08-10 19:33:47 +0200 009ddb31ea0657885c239f7e2dd1c4d3086b04e4 2013-08-10 19:31:48 +0200 8bc7943cea575fde8f5e011065e7d8b00cd70ef6 2013-08-10 19:27:22 +0200 7221acadea25197a847adf2a6ff4432efe0fdd1d 2013-08-10 19:24:55 +0200 1193200a1869cf9b7983f96f8a26eeff413733da 2013-08-10 19:24:04 +0200 cceed953af02022834a3e963aedfa73a22761280 2013-08-10 19:17:05 +0200 b817e124bb2d5760ccdb5980c5a9852a0c731466 2013-08-10 19:15:58 +0200 8648d37c33c37227de9e5596fc6b5d59f4706908 2013-08-10 19:12:57 +0200 b04d636e3561d1b03b7d670faa6edf040f40f9bd 2013-08-10 19:11:36 +0200 503901d2595f2a7ce1794b23b7368786cb288426 2013-08-10 19:05:39 +0200 2a4186100039cc7cf13f92e5b6025f35f7f42f25 2013-08-10 18:56:47 +0200 09b5425fb0a2106498921a67e33f5877f8aec50f 2013-08-10 18:41:25 +0200 6147300359540c109a67f3526ac7bdf427c8c811 2013-08-10 18:37:56 +0200 390abf38f797da443940b4ae1cf1aa264eadca2e 2013-08-10 18:36:43 +0200 744ecadcb9ca71b44ba6a3406745d2b75fba5295 2013-08-10 18:36:05 +0200 5acb9ed089d660450af409c82d5cc96dce5b7049 2013-08-10 14:41:08 +0200 e9919331566e377db639384ddd92fc698aad0e2b 2013-08-10 14:40:08 +0200 080cdd0c8849bea07aeba260b9968251ca9ed061 2013-08-10 14:33:45 +0200 cef2c23222ee14ddafeb69f71b78a8d742d08b30 2013-08-10 14:32:27 +0200 8f48e4d8bb50b2578d10157ee2d7e7353f775e77 2013-08-10 14:13:02 +0200 969bc78d7d107f2164b22565b9ad373907f2bc17 2013-08-10 14:02:25 +0200 da4311c4f978281adc0c0efcce6747b356d0e57f 2013-08-10 14:00:24 +0200 8108c59602010f84cf6c067381ce01927eb00692 2013-08-10 13:59:35 +0200 45b49125fb8d192fed1643b20c25341c1925d444 2013-08-10 11:48:05 +0200 9bbc7263a94f40419618e56a8f8b024b6ca230cc 2013-08-09 22:49:18 +0200 a028c7fc2b4c663ae0a55674451176d1fb533a3f 2013-08-09 22:42:45 +0200 8e62ac2fa437e4abf5d58cb0d4033ef5c968c893 2013-08-09 22:42:20 +0200 ab5d087d944136c1aa63387c9d24bdf9c4d20386 2013-08-09 22:40:58 +0200 179eeec503edf8616a5d26b4039a4cc4262ea084 2013-08-09 22:40:15 +0200 c306cfeec2358b1a8b70be9ec298ac3e96989885 2013-08-09 22:38:57 +0200 b1e2f3ff25239ab0afff5204479d1298068a88bc 2013-08-09 22:32:00 +0200 9c890c632c2183d1f1149b08ae5b60ac0dbab332 2013-08-09 22:31:07 +0200 4295f3f55bf07b573f423c046a6f92ba7dcb2ecd 2013-08-09 22:25:14 +0200 cec7c8fda8860c445785474393119402bd11f2ed 2013-08-09 22:23:57 +0200 6e741c40f31a61af3b25a10d8d182087aab69efb 2013-08-09 22:17:03 +0200 5b6012ebb2df3b76e8cbe0a73564ddfd9f713911 2013-08-09 22:16:10 +0200 60a507fd2da5d41bf1a82c09925577e815187a9c 2013-08-09 22:15:06 +0200 fb6ab540c482d1b7d68c370ec72c6efab9f0a0b6 2013-08-09 21:58:36 +0200 45dee79c3e24e3ba7a2dfaa71e5ba48d4eb7f9a6 2013-08-09 21:57:55 +0200 f8d7f253bcef9e441a6d681b4d726790076c1366 2013-08-09 21:55:55 +0200 b09f15e6f86756e7077d6ccea8a6d8dbf49dc31e 2013-08-09 10:00:30 +0200 a68ebef6454bb0d9630a581509062fb3a9f7cff1 2013-08-05 07:03:11 +0200 567c7ed2d96d8e09bfc3393d3056a19503a85188 2013-08-05 06:53:15 +0200 6cc113ad57c3062b22fd9ae2e895da57d40e0dac 2013-08-05 06:34:34 +0200 bc33e668f9119a210923c3c394e8ae5e205a1fd2 2013-08-05 06:33:25 +0200 970aa6cee8d2f05ef51cad866aef2ce334db2fe7 2013-08-05 06:26:09 +0200 16ac85fdde79683b4c505a521f2d16f6908b0b3a 2013-08-05 06:20:57 +0200 eb474b982bd95f7c3c1961e0f7071f3873f59b0b 2013-08-05 06:20:35 +0200 dee86cfdbc4cb4290e4ffacdf3a5d6fab54b19a7 2013-08-05 06:19:04 +0200 eb2906dcab4277a943c4dff187b0e50e06754ec6 2013-08-04 20:09:18 +0200 71b2a4279dc3158c4fde2bb78b087745823110f4 2013-08-04 17:29:02 +0200 c2c750846a0ee40a243193a19aa5dc13c3ca0d14 2013-08-04 16:20:31 +0200 efecbc591d49257e3ca2cbf00d8fc44189dd3ecd 2013-08-04 14:40:50 +0200 ed922f6c4b195c34639bd2c4bc98c284cd6db2b9 2013-08-04 14:38:13 +0200 b8baf15d98061e0ac97ff48346b2c8778f9683e4 2013-08-04 14:35:18 +0200 616e98dc23c9496c0d3ae53c5a6dae03abcc0823 2013-08-04 14:20:35 +0200 aee7323e7f67c7ec27fe5ee63b897d50d99addc7 2013-08-04 14:20:15 +0200 42b25ffd2daa9ead857154afbfcc817d7c1bd4ac 2013-08-04 13:42:38 +0200 e533e97cc02925fc9954039a0813615b1b8003ea 2013-08-04 13:33:28 +0200 4e5f31eb2184e2f8630c7685255fc856252a48c9 2013-08-04 13:33:04 +0200 e0bc18a064da5fb45dd0fd242c5ebf10c82486d1 2013-08-04 13:30:13 +0200 a31f3b05fb890167fd49de0e27322f85e6ab86bc 2013-08-04 13:29:16 +0200 dad047f4fd86582e0d0666991ee5ea94bf1eda28 2013-08-04 09:07:12 +0200 f64f9e2e0d6385b67d1409218268af383183128d 2013-08-04 09:04:13 +0200 14437488cbadbbd59367c0af11fa440a59583ed8 2013-08-04 09:03:42 +0200 b2ab1f6051022ffd2c0a4583373c33b27b7220a0 2013-08-04 00:02:54 +0200 c49635bd8942de60733ac325a71306d30578a80f 2013-08-04 00:01:42 +0200 f05ad5d8e60706e482d58b4f14e601040357ea31 2013-08-03 23:54:23 +0200 42ae6a5cef583c6549c12fa8d5c3ac86886352dc 2013-08-03 23:53:20 +0200 b0a9a9fa8b340d1bc6636215b8d4ed796391b8d1 2013-08-03 23:51:54 +0200 de4093cd715abdd775301ae84b9e31921af33578 2013-08-03 23:46:24 +0200 13a14a39157d79340415cbff77f8d58211991e2c 2013-08-03 23:41:53 +0200 996309f4fbfcfbfbcd03383430582f0a7141e00a 2013-08-03 22:12:37 +0200 72cdfd3748b9ee41bc35d51231b9ebe56eb46512 2013-08-03 21:36:22 +0200 2ee830294bee312aa5e8f4c8fc55c4744186185a 2013-08-03 21:32:58 +0200 1b31905440851d5a60a24202d31b5f313860ec72 2013-08-03 21:25:34 +0200 49c8aee7e6b8879f2f51538ac81b51fd274d0607 2013-08-03 21:22:48 +0200 13aab8380114d0db661ced7f581cc96b04e23496 2013-08-03 18:19:41 +0200 ef835a6fb28c55845bcb13b9b04999e5bdbc65a1 2013-08-03 16:37:13 +0200 4b32843fde9071d543ea1937e8c7204dea6a0a02 2013-08-03 16:35:54 +0200 e6dc303f879f4ce15358a25d7b9763b5c879f3c8 2013-08-03 16:34:33 +0200 537f9ac85ea8ae22357d73ef11b8b6d39fe23849 2013-08-03 16:32:43 +0200 5856794b3be410b0ad8810eb1ffddef13e183dae 2013-08-03 16:30:54 +0200 404e15e65bc51be52ab0833af6789f171eb79bc7 2013-08-03 16:20:01 +0200 7f1fd350b25c28f8e9da1e20a6463cb1aa012d66 2013-08-03 15:57:55 +0200 61a1b88c5880f82a7bea6dc834048cb2b4f83a56 2013-08-03 15:06:26 +0200 839c02fe322d90b59d58e5927ea83248d61500ed 2013-08-03 13:26:29 +0200 85e89c6a8b855085ac376a25ed6fbaf20103f4bf 2013-08-03 13:09:38 +0200 c924a64798ee1052efe1aeab7ba227f83dd0ed7d 2013-08-03 13:07:00 +0200 851d29221dfbeec7c59a76950fdee07c0d91b338 2013-08-03 12:57:31 +0200 079650fca229b4226d3c496fbb25653752eb32a3 2013-08-02 23:34:17 +0200 1dc033164982b4acfe3b88a773e371e377e05e35 2013-07-29 10:06:22 +0200 45e45c9b3b2050e004d29f6cf3f66d5ec604eab4 2013-07-29 07:05:52 +0200 294d83744b1b4e109908bf12e55061e1ec0e73bd 2013-07-29 07:03:57 +0200 90eb43386c3dcd2550c279856c7d03d36a71167d 2013-07-29 06:56:04 +0200 2da0c9e82e4f031405d26e62b70a540b8eb0ed85 2013-07-29 06:54:10 +0200 3a6c0dfd6176e2e4572caed8d4f2ff32b2a62d58 2013-07-29 06:50:17 +0200 cc8a95411cf02d64255f48de698764891ea7892e 2013-07-29 06:44:38 +0200 0952aa705526413a7f92421241f6a979cb9773ac 2013-07-28 20:14:39 +0200 65a18f93611c4de6a52c16dc764e5a7296623551 2013-07-28 20:00:55 +0200 efd83dfa7cdb2a2bb3f465aed5107b27db3947cd 2013-07-28 19:55:58 +0200 f68ed252401351134839b995f9ace5a7779f54ff 2013-07-28 19:51:04 +0200 acf3fe014e1c039edd63266fd13d9fa087605c78 2013-07-28 19:48:46 +0200 e5bd0ddaeef5070f8c7c624d0169f4a57375df85 2013-07-28 19:48:19 +0200 51e133c1700e1635d227c5882928176094cceadf 2013-07-28 19:45:22 +0200 4209af878d3cb18887fa2e8046bc90a23b381677 2013-07-28 19:42:28 +0200 c8d56836b6cf462c327290382c05eb1e17c2d04b 2013-07-28 19:40:00 +0200 727879f259c501b5598a28404ff963503a0aa50e 2013-07-28 19:37:41 +0200 02487b17d453fb0c2d29e00445a9876b5be074c3 2013-07-28 16:10:49 +0200 97a2b9237c9e18fec59aa1f1cb3a3af10e7e74c2 2013-07-28 16:06:29 +0200 a12e88aaf9f7b4d641e0efb33918f7d80235adfc 2013-07-28 16:04:56 +0200 21d575bd3f2e428749e49c20975a8405415c42c7 2013-07-28 13:40:55 +0200 72dec186557782b6fcea9f1bdcc9e988ae8a5b90 2013-07-28 13:33:05 +0200 676b206a658ab1c3ce1d18aae698c58fc7b1fb9a 2013-07-28 13:31:14 +0200 b5b1f7c713c5b5783a13f291d5019dba9a513672 2013-07-28 13:27:40 +0200 8688f8a325a37b14aae00e6dfd529c1793c396aa 2013-07-28 13:26:55 +0200 bd6ec9088d854562016dc4e4e30c1d3f66ec5548 2013-07-28 13:09:35 +0200 c2abb31e28f17e5a8518092ea2179f7234242b12 2013-07-28 13:00:12 +0200 645757ff63ca4492cd6c1a18527c811a88e3d0a2 2013-07-28 12:32:35 +0200 c43fe85ace6b1af359681a3a98b57bceb5153595 2013-07-28 12:21:44 +0200 12279c68773b7760ea695c3d2c8351c31143e3ae 2013-07-28 12:09:32 +0200 61f67d84231d249d984ab7b32cdeaaab74081270 2013-07-28 11:55:05 +0200 9ef54b8339787ff342c1a9b9241bc84783c69449 2013-07-28 11:39:39 +0200 01293faac19fea71afd72b2c6362e55870388a70 2013-07-28 11:28:21 +0200 d7d1d15c289c59d5d6e73b3890be2c6365e559ca 2013-07-28 11:22:36 +0200 20a179bf7209d273ccd430b7750f03ad9c072f7e 2013-07-28 11:21:26 +0200 29734af9f608630719f70cc90509aa51e891059a 2013-07-28 11:15:32 +0200 c839b0d5e9b69318e7d6038052bc612958e9bbc4 2013-07-28 11:09:54 +0200 35234696eecb30ac7009c71ea8a8b11e25ececa9 2013-07-28 10:03:54 +0200 6ca640c68caa6dcb33f673f33b9adac5dd28d5a7 2013-07-28 09:54:28 +0200 60a72fbbe6410c10209e915b9f00979b245f4b90 2013-07-28 09:11:26 +0200 9c224c7a7f9a366a63396d7774ebcb551f498908 2013-07-27 20:55:44 +0200 a558c1206be62851d8d7c87c633f465f6f0cdc92 2013-07-27 20:33:51 +0200 b51d8478d253c83dae3e092d56c51437cd224c9e 2013-07-27 20:18:17 +0200 e5e61ee135f756eeb17d7ab3c8dc51a54c96f981 2013-07-27 20:13:54 +0200 3cf8defe386352362fe3f8e8efc36650714a15fa 2013-07-27 18:40:26 +0200 f69f21d5f36bc369c43723112328e85ea29d2b21 2013-07-27 17:37:59 +0200 d87de66607c4f1896efb9a0891b48bb9f6e08084 2013-07-27 17:33:41 +0200 23df2dcc4d153aaf91649cb04741bc780dfbdc39 2013-07-27 17:26:18 +0200 14108800e58a7407f0ee8ecd1acfb8e26e26e617 2013-07-27 17:10:44 +0200 0ee005a5bf55e04f01213d1ccacab8b50ad423e7 2013-07-27 16:30:33 +0200 be97ba7f54c5f8aabbc8ed3ba4683065b20907d5 2013-07-27 16:05:25 +0200 cd6a88a866eeed7ecaddf2438aee04df20a4731d 2013-07-27 16:01:55 +0200 0dc400528783c36776ba3d542d19c80288694841 2013-07-27 15:39:31 +0200 46c7d674906a6baefe72043acb4326655bf2c1b4 2013-07-27 15:33:03 +0200 a352fea8ec836063ede520f5fbc08178f0923169 2013-07-27 14:56:46 +0200 6a09cfa7d396b55e9ae1d4dd383bf7042b2851f4 2013-07-27 14:36:33 +0200 09808521a53347cc0d2714b9fa329a8906abcf0d 2013-07-27 14:32:08 +0200 32cebd1f6cc5614f9d9f8c9f652180e51ad2f8b9 2013-07-07 21:44:07 +0200 1bd4aa0879451b99e8124e6fc51449a717c6f388 2013-07-07 21:29:35 +0200 ed82e28e4cd12d4f87b07d7670db21333433b79b 2013-07-07 21:21:59 +0200 bfa34a61e47e51aa884367132b2463afa5eb1c76 2013-07-07 20:55:13 +0200 ec24a47dbdedb423dddff9f9da27409f667de0b4 2013-07-07 20:37:56 +0200 7c0949269b1cb0d7583ab2f87e5c6be274606677 2013-07-07 18:33:52 +0200 bf8d5989ffa5c0e03bfa49bb741eb33c989f1db1 2013-05-20 19:21:03 +0200 de9a78d95f62ef65a702fe2e6b3928cb5dc452f0 2013-05-20 19:20:26 +0200 f0c7c27a4a1fa5cd8ef46bfb4010c633f1497f64 2013-05-20 19:19:32 +0200 480c1c9b6d081f6da5b7adec17d756894cce7b0d 2013-05-20 19:19:03 +0200 35f97e4c5e11af0f4950ba5fa1a9b38b9417b2de 2013-05-20 19:07:45 +0200 37d1bec9e9900b88846fddd89b288e5698113374 2013-05-20 19:06:51 +0200 c2cf4ebcc14d67f407b4638a1dec328d624906c0 2013-05-20 19:03:08 +0200 69184832b62e09321bf144eff241df7bc4d1808f 2013-05-20 18:59:24 +0200 15d0143e97c5dbbf802f008d15020305c911163e 2013-05-11 23:08:39 +0200 2c856983bf104b97ecf4d9fa9e198e2b3dd39d6e 2013-05-11 22:14:46 +0200 aa758e0d2926603186e02a2433618a50d0a5afb0 2013-04-15 07:04:29 +0200 e3e5ea69c6a25a8f2a229fecf7d358e0cef019f7 2013-04-15 02:32:29 +0200 5b83d0f008fec9d152a37e4e1143730ac5f309b5 2013-04-14 21:51:07 +0200 52be9c017a0cf7f23ee6969268da57f224ad18aa 2013-04-14 21:39:07 +0200 d042ca5eb4b82afe4ae14f7d6617c61cff8e6f88 2013-04-01 17:57:43 +0200 38261d4cefcd5cc9b35d6546b7c74f5ab61da608 2013-04-01 17:57:03 +0200 17195ab6974f1795041aebb3e6c05da1a5ff7cea 2013-04-01 17:54:30 +0200 cd72ee1ccf650da07a2debbafa12ad901aa83652 2013-04-01 15:47:11 +0200 4ef1fa0a73b3985eb34b2438bc71da25648ffcd7 2013-04-01 15:37:43 +0200 06442bd49cbf0dd11860f81646b146875d42474e 2013-04-01 15:37:01 +0200 c4f723a4eba859e9c1ab6c8dc1c02d7c49dd8ab4 2013-04-01 15:36:35 +0200 91be1dee939d5f8462df148cf17c8a0dd48643c5 2013-04-01 15:07:54 +0200 000edc4839a1ba0a14f17e7acd9cae689271f065 2013-04-01 14:52:05 +0200 9ac8de60c91a6e709fd5f20ac8af1b3b26109501 2013-04-01 14:44:44 +0200 eede36a1e5429dd44e1ee884a5e3ecfe0eca7ddd 2013-04-01 14:36:20 +0200 7ed02bbde79fd7ccd1cfffe194de5ecd3e5f6838 2013-04-01 13:12:26 +0200 3799114c633fd3b37e74542c429677f2d5027253 2013-04-01 13:09:35 +0200 d0ac338b966650d66beccc6770635145ef076904 2013-04-01 13:09:09 +0200 725924b20527dff5462461ed357f1f03037bf400 2013-04-01 13:08:03 +0200 8fc0a309039f4a7dd49e4cd4c416313ceec998a5 2013-04-01 09:26:00 +0200 96e6b4d0598d62fc3460b94c6c470574d28c39b0 2013-04-01 09:23:45 +0200 df0e262a3a6de1b992d2bb7b688c9af0850993cf 2013-03-31 19:53:17 +0200 eca4d0df95856ce9eb3086e4afdc13e46cacdb5c 2013-03-31 19:52:36 +0200 6cea4ecf9655a6d0e859ed21ab47e5bbf29cd8a5 2013-03-31 19:50:51 +0200 7094eeede98a8a70cc081a696d1c72aac541a514 2013-03-31 18:49:04 +0200 a978a1fdc50c92276cbf5104e1cd759af9e9f4fa 2013-03-31 17:09:22 +0200 1fb336184ec897bc261402941301d05055c308f7 2013-03-31 17:01:47 +0200 9e7558ae688a977296cad7d84b8376509f3ec46e 2013-03-31 16:57:33 +0200 a21cca89c7bc46a3ef452cc7226269df8d7b2077 2013-03-31 16:57:11 +0200 09673d59bf27b01b1c99cb63db05dea1a12315cc 2013-03-31 16:50:12 +0200 6cbab202365defc3211f24064405b16f69d57114 2013-03-31 13:16:00 +0200 c629cfbca72b17db071c3f4b88a825c7d1a4457e 2013-03-31 11:01:29 +0200 1b452550fd500ff190ac94cdd68fd33e8878e4dd 2013-03-31 00:27:53 +0100 45ea3c19e48b071fc898b0876debcbfbfa52009b 2013-03-31 00:24:59 +0100 67781ebda04d199e9dcbc1e61d214c353a5c01c7 2013-03-30 23:31:32 +0100 acf7243da30b87433a2099b7f50d25eb28086c42 2013-03-30 23:27:29 +0100 f2ae6213b2a59343ca97c1ce7f6881b484a7e2dd 2013-03-30 23:17:38 +0100 863882a327b3f3de168bbe448319f502bd03c47e 2013-03-30 22:38:51 +0100 b2df6deb028558e95962c8474a88ed202ee4dca4 2013-03-30 22:33:16 +0100 130951dc13203a086545730077feefa4badf4548 2013-03-30 20:17:02 +0100 4d650cd086bb3714a6ee1d722986dab1320f55e2 2013-03-30 19:57:23 +0100 059bdccfccf6a7350d4178a632958593cd66da19 2013-03-30 19:44:53 +0100 d34ce4ae25e919838389bc39c939f3608883343b 2013-03-30 19:39:16 +0100 e8c9d132b02c85fba5ace2683fc57449a10e209f 2013-03-30 10:31:37 +0100 bcdda785a2a7d8ae9f8fdd70c941ced9d1f4e64f 2013-03-30 10:24:03 +0100 07cb821779d115bddd34ce210d72cba1cafef63a 2013-03-30 10:23:30 +0100 2b35c5400f8c7cf639878901a4e058bfb8dc0933 2013-03-30 09:46:53 +0100 06862f0c35d4b780a4b631ce8be57657ec45b227 2013-03-30 09:36:39 +0100 dd83c9a8673fa78cd53c38f5bcf2b2d7b9edda15 2013-03-30 09:03:55 +0100 395217e53d3e5e83b8521e6eaa85ec07e8fed31e 2013-03-30 08:27:07 +0100 8f79b6b4fcecd191c2b33be978a24b9cfd08ade9 2013-03-30 08:24:04 +0100 b35d70deec74882d80297c7e9a039c1cb7cd36b0 2013-03-30 08:22:59 +0100 56a18dc6ccfb701d45a0290d2d6e54181930670b 2013-03-30 08:22:35 +0100 aed4f739d0c95ce08b4dd351b67f597d2cc12664 2013-03-30 08:17:19 +0100 212f0c39ebb958832b6917179cb5eabf721f4f6e 2013-03-29 22:30:50 +0100 bbec8f28f92b3af861a4c7a3913e1b096df4d64c 2013-03-29 22:30:30 +0100 1d93e66a40947ac4bcee7389e9aedbd37d6675fd 2013-03-29 21:29:20 +0100 40ee5276b8229e1d70a3ce257bc4abec3809804d 2013-03-29 20:12:05 +0100 b14cac6bcc6221b06acc7ef734aad53e8dc4b439 2013-03-29 20:10:56 +0100 8b8600cda77e854a465856d1f66fa4fd0f601bd1 2012-12-29 13:53:40 +0100 67685cf27c866e54bdd3c3af195dc734b431c62d 2012-12-29 13:51:52 +0100 d2a539c2dc7c4572956bb4ffefb5ec252dab77d2 2012-12-29 13:42:17 +0100 4f38761ed7f8aa6f7cfaa39d6fbde58143cd5256 2012-12-29 13:37:05 +0100 79a8326fb0433b4a60b96e4210eaca9ac32678a6 2012-12-29 13:26:18 +0100 63dc79990a66fe7732e65dfc75bc65e312220a11 2012-12-29 13:16:23 +0100 55430a220ef7e7b2809d982de2617cb3646700cf 2012-12-29 12:55:09 +0100 7027a1ea59ca2212b98bfca362ebe48df6f5ccf7 2012-12-29 12:53:55 +0100 7d765e8621675ed57184f34993dff545f1e11899 2012-12-29 12:48:35 +0100 1e575983704da6264b3fb59f05f5504026e3b6b4 2012-12-29 12:44:53 +0100 2d4af9a7046d2746e1d46dcd7824742ab285cdd3 2012-12-29 12:28:53 +0100 bba556c7420897e4b88480fd363c1563dc26dcce 2012-12-29 12:26:53 +0100 73ce29aabd16f634f40f74acffd17346bac61cd5 2012-12-29 12:11:28 +0100 2e1e5f44614126c40977c790a8670b7b0987d9ff 2012-12-29 11:55:28 +0100 a635049254d251da6e3c50d4edcbb9dcd0368cc2 2012-12-28 18:25:30 +0100 935ddb1b0d14a9958606660fa0a2158210b1d287 2012-12-28 18:10:41 +0100 11506029e0290b45ad2873d48720da16fcb5619c 2012-12-28 16:50:10 +0100 b459e28c75fd9e5ccb8ad859e91a4ea3f0caca09 2012-12-28 14:10:14 +0100 69aa319877e35fffbe8b9d4f3d5c8a9e7e29294f 2012-12-28 14:08:54 +0100 8fac4f77e0f4795d2519c9118cb3d0e68589b18b 2012-12-28 13:54:54 +0100 efb003d4e588ceed99de94c6648714f0c035aa5a 2012-12-28 13:39:29 +0100 62e7f2ec5e66e58c0fa83404db459eab01c7eb37 2012-12-28 13:39:09 +0100 fb0e57dbfb084691a149634cf55b285fe9e02062 2012-12-28 13:31:38 +0100 d7774b4ca84e4e338f721b6858a27953dce64075 2012-12-28 13:20:38 +0100 1cf355db7b9776f2660dbedfeb1c00e2dd3acc63 2012-12-28 12:35:57 +0100 4513bf421ae817576f5ec5714f6110bea21a2ac4 2012-12-28 12:15:47 +0100 e36e840309fed1dde07e85d6e65a5edead2103d5 2012-12-28 12:06:19 +0100 4d27d88be2dcf9cee18d6fce84f0153a8d357195 2012-12-28 11:40:06 +0100 73d4b0987bdf7e96e432342c9c65f22d0bb580e8 2012-12-28 11:33:42 +0100 24b1e74e0f451645f4ca7e476b8d6e6b47b07b04 2012-12-28 11:25:44 +0100 abeb678c1c56668541ef3d811d31c8083d477eda 2012-12-28 11:16:37 +0100 4f39a848e55ac15f5e17c72bdb052d8677bef788 2012-12-28 11:03:01 +0100 3e5e6187eff6fe2348665767bea739393d718585 2012-12-27 22:42:58 +0100 ebda8dd7ec50337a24d258b19828d73a41a03191 2012-12-27 22:25:35 +0100 7846b45f580c53de8d56f01ef0337b44414228c4 2012-12-27 21:04:21 +0100 ded431615c2b35e9ff267e9c4354d531c3819d68 2012-12-27 19:51:57 +0100 fca05aa2cbb23c8127382f1b9ede1bcface7c199 2012-08-12 09:17:58 +0200 c58080b6f129ef4bbe66e3e10f7eb0f5fd94c25d 2012-07-14 13:16:50 +0200 4d3e997e5f62be7366c6b4a0932b9872c4e77c63 2012-07-14 13:10:32 +0200 a2629c2c4dbefc1e526c5e4c8ead63f2baeba342 2012-07-14 13:08:38 +0200 57f35dfbacbd858917783f34d6e4226e0400041d 2012-07-14 13:04:42 +0200 55a7a7fad15bf6afc6adde82d29e99215fd32611 2012-07-14 13:03:46 +0200 beab34f13a8898ee980a70725ac9750ff7b9d247 2012-07-14 13:03:13 +0200 aed557f59bf1f1f0fa1472167aa838a6507f7a12 2012-07-14 13:00:41 +0200 f04f38bdb40a014c1df3ea878d9eed8d1607895e 2012-07-14 12:58:42 +0200 4a551d0e02019a9020bd83a1a197ba6d655f12cc 2012-07-11 22:24:22 +0200 20b3246ac357f92ae195ef60ee6a8f17b6f07315 2012-07-08 10:13:05 +0200 a40ace49a11988ac7ccfb3213d00ed6c17b183e8 2012-07-08 10:03:57 +0200 d4b667fba0b8c6ef4662d0c974c3ecfffbc253ae 2012-07-08 10:00:13 +0200 3d918debf02ac1c618a50ed755b353c629c500f8 2012-07-08 09:55:20 +0200 a4c0edfa3fd451aea61c2984ad346f177bc8c9a4 2012-07-08 09:53:10 +0200 5c52f01845fa6c438e5dfca830b5d03d6a8449bb 2012-07-08 09:52:16 +0200 c43c0177a8556009ad177dcb1d48ebbc6d4c0e28 2012-06-07 22:14:50 +0200 a95811ee3286d363ab4b4943535e549e49579f1e 2012-06-07 21:49:32 +0200 35ec41315fcf909dd9fe080265879ee8e40a9d32 2012-06-07 21:42:54 +0200 daa3a25b4f0aaa4489b58145c46ce501e55f7b8c 2012-06-07 21:30:04 +0200 771cd725e0f31735e94b842e9a68b8e15b41947a 2012-06-07 21:19:52 +0200 2741abfc95bca6458227196913fe0fa5c4a5fed3 2012-06-07 21:19:00 +0200 b0712eb0c83b07b8f26a734fddb9163b82419705 2012-06-07 21:14:47 +0200 e5aa64017beca5bf958d5fa77fdf5cd08f0aa06d 2012-06-07 21:07:05 +0200 fbe2b5cc233c1dd41e0cbac1b73c42a2f2b9d766 2012-06-07 21:05:40 +0200 82cf80010b9b6b8ea596ee137c957509c0e8495b 2012-06-07 21:02:15 +0200 9717608436edf2553f1a51d2d64b9583b4f73759 2012-06-07 21:00:07 +0200 1fd931432d99c8a8c9bc2bae504cb8c85b5d5979 2012-06-07 20:58:14 +0200 7643e08664ed808a8250af7bb66ba3325203d3e2 2012-06-07 20:54:31 +0200 2e30106cc5a9ff98ce1b48e59416622cb393b2cf 2012-06-03 15:27:31 +0200 67ce07c6c30db964861ec0adf327ae2db565a117 2012-06-03 11:47:02 +0200 2192b12d3b6894fea11f64c6c92f2875538e7d5a 2012-06-03 11:38:12 +0200 2106947823cc9771fcc39c5d66e69fee4193a21a 2012-06-03 11:34:13 +0200 7b8444527c39e3b50918289ab0e66b169bc16e1f 2012-06-03 11:33:18 +0200 107806d22ad5a6d27d930a9cd3b6a5c7ae9f6d06 2012-06-03 11:08:35 +0200 41fb9a17a711aa3bd1fc6184313e7da57944c051 2012-06-03 10:59:51 +0200 5808ca79c81bc135f1058031495e6f10fae80186 2012-06-03 10:59:02 +0200 6eabf73c00218a376636b8cfcd1e70cfbda44754 2012-06-03 10:58:15 +0200 ac808dc5d241b02249e86e632b933e8489cdfabe 2012-05-31 13:45:58 +0200 a0361b708184c13b1cb861ce61af178225f3906a 2012-05-31 09:24:50 +0200 437dddaa3fe53ed733c58541499c2fda79fb27c2 2012-05-31 08:54:12 +0200 02c0e84406c417fb86a5346c08c9202605341c1a 2012-05-30 14:41:42 +0200 7889ace899ea26c8510413fda9de257833a0e026 2012-05-30 14:15:57 +0200 1863cfc0580091c225af4e5e68c37523a5feae1b 2012-05-30 13:49:57 +0200 bffec8d0a4443b73d1c3b224088811cea4ec4992 2012-05-30 13:47:50 +0200 3e8e552a56d3857139b6225562e52c91d15059f9 2012-05-30 13:39:30 +0200 2fc6f61072faa811b34bb9da791ea24cd717c8a2 2012-05-28 22:54:26 +0200 571dce68b918cd9e682148627093161b0929500f 2011-11-20 10:01:53 +0100 08022843d7f8edacd20ae456b668e19057b34fe6 2011-11-20 09:58:40 +0100 27872e47e7d9e009b418455eddbc929a50f02fda 2011-11-20 09:47:53 +0100 fb02642fdb87d4ea2518d2558e280ca715e6178f 2011-10-12 21:38:15 +0200 21fcab8aa102fc259121813fa4546c9d3a78861c 2011-10-12 21:29:25 +0200 6497933a47a004e087b09b78507d6f3b0f5a9660 2011-10-12 06:57:50 +0200 10844f41d5a5c590d13145dd621d7b43a43b0281 2011-10-09 22:36:13 +0200 e08c9c2dbfc05d60e7a7ab1f4b4e8ef75fefe2e4 2011-10-09 21:10:42 +0200 e68db9a4e95a47f3130e64bf5d8f2e43cbe3c63b 2011-10-09 21:08:28 +0200 f4e49b9f803d1fc5ab43fdc452e28a109a344694 2011-10-09 17:07:46 +0200 ae8c1b40dd239f09a7cc4cd9a282041db9a4de35 2011-10-09 17:01:09 +0200 e6fb2a171a6fc67056b369d37b983181c7431b09 2011-10-09 16:52:53 +0200 b5810dc93e75ebcc89e13580c9890ddb71582f59 2011-10-09 16:25:21 +0200 eb048604168004aad84dd4232e47a15a7d60017e 2011-10-09 16:18:29 +0200 2a473fcb96a224e6e377823f781021e22abc737c 2011-10-09 16:02:51 +0200 ed37a6b10efb2ed22476b77eba1ce2384950fd0e 2011-10-09 16:01:40 +0200 9f41b66f39108608fd7d0ea84985090d3d1ce7dd 2011-10-09 15:49:57 +0200 4e33b7ee99e98cfcefd89a3b141185fd8fa25632 2011-10-09 15:38:06 +0200 f091e684ecd849cb0b8f2f1c1e3533e48cd77ba5 2011-10-09 15:29:20 +0200 d504d2c6e3c635259be71cc8e1b3f5a3c72f1564 2011-10-09 15:27:41 +0200 472adb3fc9291960597df489c84b4ec1b017b386 2011-10-09 15:01:09 +0200 5c7c695ec1041fbdaf23515c17240e92882785cd 2011-10-09 15:00:31 +0200 3d40ccb3bd7853a065965072270e6ecb5148e98d 2011-10-09 11:13:39 +0200 3dfcd3c967de054a7850ee55b06456e91fec7680 2011-10-09 11:10:23 +0200 8fb8acc998e50d4b43063e07f001be7a42547d3c 2011-10-09 10:48:24 +0200 acdae9e11809322dd5ef3b41fa5161dd8498221e 2011-10-09 10:47:44 +0200 26ca68376f42be77a1c014f5ff8d37b12937da30 2011-10-09 10:28:24 +0200 871643ea40153c5ccf4722eaf75ad7ea99325d56 2011-10-08 18:38:01 +0200 3611c30571fc2e60d6cc2ab21f3d6c5bbcff047b 2011-10-08 18:29:37 +0200 2386dc6f09ab4e1650a2ff167d312643823f4384 2011-10-08 18:17:24 +0200 7ac55e7fada4d04e18c6a5824be7c627d56d9802 2011-10-08 18:15:29 +0200 cb0376f73be19cff70c9d32c49dc6d9c0fc5038c 2011-10-08 17:57:00 +0200 5e7fc43463d14e83f083c4f3586d690bf7cdd865 2011-10-08 17:39:35 +0200 1594e108acebf88e3912c34d0b9a3ed7255f1623 2011-10-08 17:20:31 +0200 2b363faebaa49809ca48ce9d8786110462d78c13 2011-10-08 17:14:31 +0200 d6f3c4b591a277a4372d1542cfd3376bed118029 2011-06-22 11:44:39 +0200 02fcd764abfc16c595a5d911f9db1b1ffbc3bcde 2011-05-04 07:44:34 +0200 8eb9c389f5c0794d44884f7e72dc9067d2f0224a 2011-05-04 07:19:55 +0200 774cc1e1573a064af2a92a6fa4e95c4a0d3c7467 2011-05-04 07:17:45 +0200 01ca9ebbbb782ddd1f8f30b43e0b24c5ed5fe18a 2011-03-12 19:26:59 +0100 879c86bd016b8ba641058bb0e0c178f235e50c2a 2011-03-12 19:17:01 +0100 9a6e243ad9f110e644c1f2e91543c3825fcc59ef 2011-02-19 15:56:43 +0100 7da900a9bb4766c7632e0791c994c61cb40c1284 2011-02-19 15:56:21 +0100 bd23e6310c7fc72e774279804a5b92462f0395f0 2011-02-19 15:55:29 +0100 d830670af000a2331c54e318abd53cee920539dc 2011-02-19 15:52:28 +0100 74768341946517f7239ad062e6a6a1b867eb5e53 2011-02-19 15:51:52 +0100 cb74445eadb116e3c740debbee0270641ee7d1df 2011-02-19 15:48:13 +0100 80a2fc118393e9a57b50b8294d4cf51ac3e1a31a 2011-02-19 15:14:51 +0100 6d92d66cffe546132f9082ca7f1a9e01ad18c7e2 2011-02-19 15:00:16 +0100 bee4358e4465393350f9a710b25254545c22e91a 2011-01-28 20:27:34 +0100 625e4cc02827e8105c36b9bca2ed6a1f76faa495 2011-01-28 20:22:07 +0100 0df1a853d55bba461126621b661a7ee763c6c3e4 2011-01-17 20:17:05 +0100 c643e743da72a76b01d99ea5d1c25e52c1b9bb1f 2011-01-14 14:36:34 +0100 65e4979137273e85c9e1dbda2e9ce8f275c2238b 2011-01-14 14:36:05 +0100 848d139478773110fda7d092d826cb8b8bcc6815 2011-01-14 14:19:59 +0100 4a94ea9b5e94d22def5571e0f22a6ae446cbde85 2011-01-14 13:18:14 +0100 586732e5c02488de164babff2c142e55ee3a9f44 2011-01-14 12:11:15 +0100 747efac8647218100741f8f7277415c3f5348ca3 2011-01-14 11:04:12 +0100 b2be1ed4129d1d852cbd0d6acba545f5c3f6876d 2011-01-14 02:02:50 +0100 8bbfacd3bb5dae95d792ff70b1d164fcd0d5b2b1 2011-01-14 01:18:50 +0100 41f4507425e407d7fdb714d5f10892fdb358c5d3 2011-01-14 01:04:59 +0100 42a34acc430013a918be0a1799922d30191855c2 2011-01-13 22:42:53 +0100 62c94547f85916e82ee56198d55ca9fb5c99aa92 2011-01-13 22:34:38 +0100 e1534aaaca9329bae011b20c6bd11e8031d4ac53 2011-01-13 22:07:33 +0100 8b6505b9e736665ca4de9ca763093bfae19177b9 2011-01-13 20:25:12 +0100 afa1d483f0636eb3e17093c26b3d772a54853635 2011-01-13 20:23:56 +0100 c244a59eb5a5ffc120d456ff1371e98da0f4ebad 2011-01-13 20:05:26 +0100 9a02a3f52036369974582aa9095a8f88630fab2c 2011-01-13 19:40:50 +0100 6c5ba6ed06db81c2389164872d70bc334dda82c3 2011-01-13 19:17:33 +0100 dafe9982c3c1c5d51b4757cb833adc4935a7b4b9 2011-01-03 19:17:48 +0100 09ead9373a80ceb12e4c4c56d437a6976412597f 2011-01-03 19:10:04 +0100 f11857f2fe8eb1745f628581d3690f6e18b709fe 2011-01-03 12:29:31 +0100 537ffcfd7aba096b4ccf90910e38dd6ddd1bfb1f 2011-01-03 12:04:22 +0100 bc2d22936b28d5a4b3696cd67c151f91de83e5c9 2011-01-03 01:57:59 +0100 8fc1ad0c8a1807569bb1fe2eb7425ceb48a469dd 2011-01-02 18:59:50 +0100 c9306429ba76631eef6835c4159d92e7389c75d9 2011-01-02 18:27:33 +0100 412ab8de8b7ad87ce5b260bad631eb08044483a0 2011-01-02 18:25:45 +0100 d0d66598c347f51deeb7010cd7ba849171d10690 2011-01-02 18:05:59 +0100 b035f2ed7be9700a1f4678c3135591b178ef30e9 2011-01-02 12:57:22 +0100 afc3a5b74ab339617b7c5253e425065adfa23697 2010-12-25 18:04:50 +0100 a6dc2e9d826530ee076a7b8a1e35be3bfd7d9b22 2010-12-25 16:43:58 +0100 64da6558db7d8b617c6d791056d9df048be19476 2010-12-25 15:27:26 +0100 707ed0825f915d8cd23c08db5c5a22b34ebf9cd4 2010-12-25 01:14:25 +0100 8829c49ec09937ee6ccf2a2f0d367d141c439aa3 2010-12-25 00:14:31 +0100 29dc33cc8c04218a694415579ec503c580ae764c 2010-12-25 00:00:04 +0100 46e309e08ac3365d38a16ecfcef570336b86a70c 2010-12-11 13:49:10 +0100 f97754f4584f1b5eaeb6eeb1a2b2d767f6f9f0ed 2010-11-15 15:50:54 +0100 e46f590966ecfd1cd2a037d5ef2f9d63bdf4c630 2010-11-14 23:32:01 +0100 2c6bc16a4152d0f4d61474d42ef9f2c9a3a1d0df 2010-11-14 23:06:25 +0100 8f6ce57b858f888c1279ca1310e663773b2200c7 2010-11-14 22:53:55 +0100 927259c707a43adfcf4bf45b9e5939b3d83db40d 2010-11-14 22:38:40 +0100 ade5a9456a16eb7abaf12516d2fa8b7693cdfef7 2010-11-14 21:05:07 +0100 452bea58bd8249a8d3f2696918fb36b9afeed006 2010-10-24 14:14:05 +0200 3ef8a92a5c0b93f16dac69b46cc34a2c38c76007 2010-10-24 14:02:40 +0200 d768cb5e2f632683a91adc47e9d3a159545b131f 2010-10-24 14:02:20 +0200 4e29398a79bec81043c02a565b301225c767bff3 2010-10-24 13:56:07 +0200 abb2329747fd70df1b8f68edf4a5b52121eab9bd 2010-10-24 13:31:32 +0200 cc5ab014120874c3537a9fede59cdd1448d6d45d 2010-10-24 13:30:59 +0200 7638a99af48c9bc0da5c9e59fd2e66762d0c95d0 2010-10-24 13:29:46 +0200 0c9daf7e0fd42e9f47ecf8ad34a2063bbf3993b9 2010-10-24 13:29:23 +0200 e2f990281783cc1415807685841c6f2995eddd96 2010-09-22 14:51:37 +0200 204eae0a4f3c4ef398630f85b0afe6b4a63b483f 2010-09-19 16:48:00 +0200 bb0a1ae0f5f43a7e78b81a933795faa5cc754154 2010-09-19 13:10:02 +0200 bff3923c07fc8df2b1a6a39932bb60cf1f182db5 2010-09-19 13:00:31 +0200 68c3c3d97ac50f56b5fe96784b5ad1a0bc160d62 2010-09-19 12:59:10 +0200 ee8c83d549bd6d1ae2b733998c527b5e46c657ab 2010-09-19 12:50:15 +0200 cd4de384ce75385428c618117e45db385455d6c6 2010-09-19 12:49:16 +0200 b321bc33a5ffd222bdce53847f8a5303977d3803 2010-09-19 12:46:55 +0200 e6430dd02666f47043b8ce94f5e210241542e080 2010-09-19 12:45:01 +0200 3ecdbf9fed9fca1556d390396e43caf42cf34e4f 2010-09-19 12:43:48 +0200 5fd32a7408f63e6c74ebadfa44647b449ccd5bfa 2010-09-19 12:42:20 +0200 8edfad873a48da5af06597833e50bc9182328cac 2010-09-19 12:40:31 +0200 c68de2b9f19b9aab8d8179ee8707601f48800aff 2010-09-19 12:40:17 +0200 4da1077b05902b0189beb41a54dd6e2f9acd49c3 2010-09-19 12:31:52 +0200 80f5f0d7e8246c154b3d279d3805e66e9cb15add 2010-09-19 12:31:28 +0200 cd2881fb903560994b3e387c479e1485fea08b39 2010-09-19 12:18:43 +0200 94946e074a92a108194f1805ef1acfcb806576d3 2010-09-19 12:16:00 +0200 3b03754f6469ab48491f0b6341079b241018f41a 2010-09-19 12:13:07 +0200 6d5bdba71aa792969a33eac2ee59273fe863cd5e 2010-09-19 12:11:03 +0200 918a64da5458d8184ed61e9768391c20adcb462c 2010-09-19 12:05:07 +0200 542ab957e3dff9d5f71fad9fd5c0285171b71005 2010-09-19 12:04:43 +0200 fcb854ca18ac4eb4c84b44ab2d47464656f282fe 2010-09-19 11:54:21 +0200 24b372610ff46f7650c7791e1e1d9a0bdb04ed0a 2010-09-19 11:51:03 +0200 a278a1999ba2453f4738c61151e0c746aacea8d5 2010-09-19 11:50:13 +0200 5951ebe2be6d76a81e457bb2b496ea6f9eafb72e 2010-09-19 11:42:02 +0200 66f4087c3a5a43b8469e9b9e20c2beb3935437fa 2010-09-19 10:35:21 +0200 9b9376179718f29954c729d1c513ce741e47687d 2010-09-19 10:33:53 +0200 7ccea68b1ee5790063b0c24fa34433a184351ee6 2010-09-19 10:32:18 +0200 ce13c57e376aaf5ed669ea42d9de3ee231e4e916 2010-09-19 09:56:12 +0200 da2f7712537072c6c3fe6fc4edfb5b65a426b698 2010-09-19 07:52:48 +0200 ec53f9ed646d2ce0db2fc6870108117a4a31abaf 2010-09-19 00:02:24 +0200 028330caf6d5a4f7578c9ab52d65357e2074f968 2010-09-18 23:32:56 +0200 84ea185b7642e34af4ac23469f6111e56bb658ca 2010-09-18 23:22:21 +0200 c8457f7a1966a58d020f23552c042147fd393403 2010-09-18 23:10:01 +0200 c4a6827e9524bf2a5d487465727a66aa42e395f5 2010-09-18 23:08:57 +0200 8176eef6ce0dc0ef7d12c2dffdca839b7d3cae7d 2010-09-18 23:03:17 +0200 402efada2cf78c856c545a2fca6f060b19532193 2010-09-18 22:46:54 +0200 a7ceaef3cab1c453f9729258fcbfc92ecfd7ae8f 2010-09-18 15:45:04 +0200 6e26f9dc6ad39561f10cb3858530717ffadf33d8 2010-09-18 15:07:32 +0200 5c73295ac997d9d88a197309f9d63f229c542c85 2010-09-18 14:36:07 +0200 345e622d3326daa0277777c6ef0108918b3a0456 2010-09-18 13:11:36 +0200 c881fe8e1587b8acaba22c67d00172014692dd86 2010-09-18 13:10:26 +0200 7666900f4e807783911babd0c3aa335b7e7cf155 2010-09-18 10:33:38 +0200 7e3e66c6e1b50ef975d7c0ffef6571ec8f9c997e 2010-09-13 21:47:00 +0200 8802a1f409836995308d855f1c9276b9c1b1e671 2010-09-12 21:27:57 +0200 31f104daa1e3d328f42e33ac2ea0cd404cbb3568 2010-08-26 20:48:48 +0200 2113e2c9bf313ac74220448b8a66f6b21d3f412e 2010-08-26 19:59:48 +0200 dc3c288856547913309732f87cf0eaa601e03152 2010-08-26 19:50:23 +0200 b183e15b9c91d1e591da6e8613a77c33424e6059 2010-08-26 19:05:04 +0200 453e545c82b133839ba774349b131fbfb635c198 2010-08-26 18:52:59 +0200 ad9c079996b7d21b952a81821132dfef7a00a6b7 2010-08-23 23:13:29 +0200 d31ab894db1e5eb6b6474e93c7eaefa9a6eeeab0 2010-08-23 23:09:43 +0200 2efce461865e670be6b8a400722357a00b0d71ec 2010-08-23 23:07:56 +0200 f1890428efb711041b49ed93edbc3f9b99be1e9c 2010-08-23 17:42:11 +0200 87e5415c75716febc6df1d25d5683c7fc1f130ef 2010-08-23 15:11:23 +0200 e3cfefa53344de65735400953083ced26aa32186 2010-08-23 15:10:58 +0200 6425ef88591fd50b44b87d962c59ae2c90b8089b 2010-08-23 14:36:13 +0200 c19ba134e647fed33aedd1ed0614ec257352623e 2010-08-23 14:26:48 +0200 9dc5bd580393f7a9f181db2aad9c15b9a8d1eef1 2010-08-22 23:49:47 +0200 04d4fed9bd310a92ff3145aa50a114e4ddd16637 2010-08-22 22:03:29 +0200 08f2c7f8ab16c6c045722b6c5733b2af3d2329dd 2010-08-22 21:53:24 +0200 85b3475f93fa643e4976043170b0768577d80606 2010-08-22 21:51:21 +0200 a9df512636fb51725af9b2e9837794094d18d91d 2010-08-22 21:50:32 +0200 b41397a657fb2f7e945a9dbf01812cedf0496c2a 2010-08-22 20:42:37 +0200 9fcd9f952099ae4d41ae0ab62eb79d5fe24b67d1 2010-08-22 19:12:26 +0200 71420c2bb884603ce29b3e2dab139ef07448e18e 2010-08-22 19:11:39 +0200 e727923bc92b70fa25e18672fbee78fcecfa56ab 2010-08-22 18:58:14 +0200 527eaf492fdabd8d59ef8f52123bc4c5cbe8f6e7 2010-08-22 18:52:32 +0200 7e9b0167b44057e7ac541a3aa6090fd69aa5b9d5 2010-08-22 18:44:58 +0200 051a0d507c737fb95a49147d6d1961320e8c3039 2010-08-22 18:43:16 +0200 dec6387fa5cabee08f2af751ae906352a9f455bd 2010-08-22 18:37:41 +0200 1851f9b00591e6bd7cb0e32474909ff42337e607 2010-08-22 12:30:28 +0200 90bb460dc0e2019ceb2b952164a7ab405d051ab2 2010-08-22 12:11:58 +0200 5d26bb9aa4f772e5a1d94c4ebae4464d8a70d7b7 2010-08-22 11:35:52 +0200 939dd6b4df71e8e6d777e8d96229c10f93262b51 2010-08-22 10:53:51 +0200 ceba8db5c4821578878b360a9aacbd3bc16c5be2 2010-08-22 10:36:42 +0200 d4ca1462924567268c093991e1fc86ecd3eabb3f 2010-08-22 10:35:50 +0200 7ea38f1448131cd86603c802236a2ac6de6ab119 2010-08-22 10:12:46 +0200 20fea21eefd6304c6ee3df4530cd0a2730bf6c5b 2010-08-21 14:16:02 +0200 2cbc5dda462462ba13a9a60ec25574edacee798f 2010-08-21 14:11:46 +0200 f450dc97c96220dab1de1e06ac3bea1be2a7b7c6 2010-08-21 12:04:06 +0200 9800d40647b33e83eb462920f439c402d63b4310 2010-08-17 22:57:13 +0200 7755329d0d9df24e3f5c0965c643f38e0bc987ad 2010-08-17 22:42:26 +0200 9d10741f232c66e03cc68f2b2246158b18a3d5da 2010-08-17 22:28:02 +0200 72063e22d1112180f3d69d47815c548c71712fa4 2010-08-17 22:14:19 +0200 7e32ced6d3b7684527ecc48afed4cf1babe25faa 2010-08-17 22:08:05 +0200 6a2afa23960b2cfb05e107229a4bb1b9e0024e52 2010-08-14 11:47:12 +0200 5e26942e13c535911623d54850df17101ac9e808 2010-08-14 10:57:58 +0200 eadd05761c6483212fdad167931ab2bca85d1cf3 2010-08-14 10:46:57 +0200 f3e391af39dd0d1b38337f523eba860506f23b41 2010-08-13 22:18:15 +0200 4692ebc8635007dfdbd26bb6d0f163a76c7cf007 2010-08-13 22:17:26 +0200 df8c35f3d8ff0a4849120afbc1c26a7354a21f25 2010-08-13 22:15:51 +0200 a3b9c9b08da70cda3aee0430769fac2b11543ae1 2010-08-13 22:10:51 +0200 d0dfdca36b2f70f88690430e5e40d3ccb54fea15 2010-08-13 22:09:50 +0200 67fe6336e67ad34c4198c14e0c32f54a4282c1a7 2010-08-13 21:37:46 +0200 dbc42d57e9744c42ceebbe66b5b180b98ac016ba 2010-08-09 13:22:02 +0200 08dbc923d3692cf08d11132c3e4e5310a2cb8a52 2010-08-09 13:16:01 +0200 f33b45310945153037e248edcb93270ef60bc0f7 2010-08-09 13:12:36 +0200 da0cca873d6ff121e5ba6396420126767246a696 2010-08-08 13:16:04 +0200 b0945a35280953f90b9dcf10d79b7f50961bde7a 2010-08-08 08:10:22 +0200 d018944867b759f04f183c15165994c95228046e 2010-08-07 20:50:48 +0200 91ad596053d30abe62e77a687c2838e9f6db3ed9 2010-08-07 20:27:47 +0200 5d6d80a0b4deb1791ac1340bae41b31944833280 2010-08-07 20:26:08 +0200 17dd4e89529ff5ea8ac45ba27615640fa4661883 2010-08-07 14:51:44 +0200 6b09f6a3f9c1c5cda0d5d914c862248fda86c5d4 2010-08-07 14:51:14 +0200 82c2b160d789e06ec85e89decc98133f765ce27c 2010-08-07 14:30:12 +0200 0865437bea1dc362637c0d641399fb97eabf5992 2010-08-07 14:20:04 +0200 c838d06052db3346c0d13080397509ade6492c73 2010-08-07 14:18:48 +0200 b7bebbd8e4f7a7ead37cfb0d75732491d5048929 2010-08-05 23:51:02 +0200 fffc964c874a9d10f8888e2e9ea9443f87024dcd 2010-08-05 23:33:09 +0200 fe9d4f6941eb4644e426478efe2c76834c5656d1 2010-06-17 18:19:32 +0200 f5d39c35a587534fe222998f6de26700d771e48e 2010-06-17 14:57:38 +0200 3eab20aa648fb2c3c59f3559c9ba37d5cc6639f4 2010-06-17 14:56:27 +0200 e55cb0a1a3d2a860a275bb3480c6823160f7b387 2010-06-17 13:50:50 +0200 26c1ed8d88d35a635ace7d23965feddf5d5686da 2010-06-17 13:49:11 +0200 de0ea434ca8cd98d0b33cc29a2ced79f1fd0d4a1 2010-06-17 12:39:38 +0200 c6da44bb08ff220f1c758cc394549cdd053cad50 2010-06-17 12:32:11 +0200 19ea785ab7ab309b1d1e191dfdc0bb841af1c79b 2010-06-14 22:32:21 +0200 f2226fb8754a28d2faf9e5ec8bf0d41b063d765a 2010-05-02 22:41:43 +0200 e1fdfbfc02feadea1686b4377177654ce4ad9240 2010-05-02 22:30:30 +0200 768760f62cd0bb33791f379245dc04516b5ea13b 2010-05-02 22:15:10 +0200 5ec0a6d8da5e9b7a027ef2476618ccece1e1b1d8 2010-05-02 22:14:27 +0200 baafa5089f92c3ad8ad0572cf80f9a33b95fd39e 2010-05-02 20:48:41 +0200 41631af990c9eea5b52024f94c0d6303edf87033 2010-04-05 16:21:58 -0400 d5975618ef1f780e071c713f8e371efcc446200a 2010-04-05 15:35:57 -0400 213519bc74ffa4877f23cf6cb2f85d47c08ba5ae 2010-05-02 20:40:01 +0200 37d39698b55fa3530741ea785b88b91a121a6e8a 2010-04-12 22:35:38 +0200 1124eb01fd93b4601afabaf3c954f5c3117f2c3e 2010-04-12 21:51:07 +0200 c6c361329c8feaebd0e62eba4aff47d39203c3d0 2010-04-09 07:47:39 +0200 a3e846dd0ecf36db2896e3b32187ca5e370d06a9 2010-04-07 18:31:14 +0200 4362d437c8eedbf2b97513eb0130612008f210b9 2010-02-14 07:18:13 +0100 bb727f02e5d0f6d1ebd76bb5904b01b9f53ec596 2010-02-13 08:43:25 +0100 cf653e0ba8a91f38479a5d6e8fbeebda6409a8f8 2010-02-12 09:13:33 +0100 85fd6c8594ab7b8d174e4f0dabf60923f0ec2e47 2010-02-11 23:12:05 +0100 675a314ab7a8222bc97d3e272f828bdf81c4c868 2010-02-11 22:57:25 +0100 a94c6c04e8c11e7d2879140811313c484757d617 2010-02-11 22:43:49 +0100 31f0593a42c27d2419eec68621ae5c228bb18e13 2010-02-11 22:41:51 +0100 fcf436bbb2609f558d79f4dd215f69cf2f018795 2010-02-11 21:45:18 +0100 3e433233f199315705e690a306498810ffda1bd3 2010-02-11 21:36:28 +0100 00c3d9c5bd410fe7e1b3e6358c4f1ddb9a876e05 2010-02-11 21:18:49 +0100 815585581377409d98497530d58bb6e62e2141bc 2010-02-11 21:17:23 +0100 fd4dc3babcc7cf26b0411fc18e31a4e567ce5a15 2010-02-11 10:02:02 +0100 7ce158ce79ac88570801c91179732a8b0bad176a 2010-02-11 09:50:49 +0100 9f578216b3f7ad578bddf78d47eb3e5f65d806cd 2010-02-11 09:49:39 +0100 21b10feaf9ebc7510a043edcfb572dc9983a028a 2010-02-10 23:08:47 +0100 8851e30aaf45c7cb8de685f45447ab884c0692aa 2010-02-10 23:03:03 +0100 e0c30c1d4f558e4c7d755ce684891578e2c77093 2010-02-10 23:02:47 +0100 d59a99755bb8e1119842569c672f25c9d31f2813 2010-02-10 22:57:27 +0100 35e42298912bcb5bec97145df72ee47c69d1aa3e 2010-02-10 22:50:05 +0100 7c68d613b6b31024bac030e5ca7e0581272a7a8f 2010-02-10 22:46:37 +0100 4d6e47e522e84cb0e47405001a2ba69d735c4dc9 2010-02-10 22:38:56 +0100 9c27d1c83d91f79e6c6f8d253bf21f0664f7eba7 2010-02-10 22:24:50 +0100 75bc82ac79b3da69d63ba15e966c724ae0c02a26 2010-01-26 22:39:29 +0100 3b55b264b7e50cbdf599927bc2e6f2a2d443f290 2010-01-25 13:25:59 +0000 13a82c129df10e4238a1f602b28bcf11ab3382fe 2009-11-12 15:10:39 +0000 6afbf40376c80772a48a4f8f30a617a2503523ee 2009-11-12 14:28:04 +0000 3dd32503b672c6760963d005f098fc1261a42fd9 2009-11-12 14:17:27 +0000 a810877df1ba578e6643cebf2e7a86084a8068d4 2009-11-12 14:16:41 +0000 e018161371f11ac1f29e0fc51f0392f39f89a40e 2009-11-12 14:14:25 +0000 a941b2075d0635658d351f814cc859d14174c007 2009-11-06 11:41:15 +0000 a5c494724c8d8f6cf5753c9fbcab9d7cb657533e 2009-11-06 11:33:31 +0000 e7588310ff987833782bf1a96169efc525f8a54a 2009-11-01 22:18:42 +0000 5bee37c522fc2a92d2ccccb30ea9e0ca7979a897 2009-10-31 18:06:29 +0000 e2998bbc6c66b2700699f7b6361bd1f8dbb6d630 2009-10-31 17:50:31 +0000 2e1dc705fe34ca09c92aeba2501a2bc2f09734dd 2009-10-30 20:50:50 +0000 cc0a9d234dfb5f885835ccafe40b1bc3a27edd97 2009-10-30 19:37:10 +0000 f3f5f14d6c87eee3b233a832700908d59f465732 2009-10-30 19:29:45 +0000 863766ae3adefb9c7f64b26755588daf76482c8d 2009-10-30 19:28:39 +0000 f1c83f8788bf1a5b72c7df730be27fdc8050533a 2009-10-30 19:21:17 +0000 d338de24ca8a279f189440fd97cd4a642954f3cf 2009-10-30 19:19:29 +0000 a77d37720a57af4b7f1cc36883de1718e3052223 2009-10-30 19:17:31 +0000 6a55eb392d34cc0d830c7617e39c0157d83d4672 2009-07-22 07:44:49 +0000 f8533fafe42c5b7518fbd20b7fee072f960113b9 2009-07-22 07:44:01 +0000 8ce420d19f04adbb6d1b13a2d930d77b0763fb07 2009-07-20 11:13:15 +0000 292058c76d70692a7ca25c19df5d2daba3920524 2009-07-20 11:04:55 +0000 db8329ec5e4b634fd48769c5ae0886d6103d8d7a 2009-07-20 09:40:42 +0000 cfa7e7734934734a3c1625fe5cdafd08f1439ca5 2009-07-13 13:16:00 +0000 3eef2eafe32b7c2b7ce130e2a495790915d10385 2009-07-13 12:58:19 +0000 6cf9ec7fae11690f787492d25efd7849f854d57c 2009-07-11 15:51:21 +0000 1834f2f979fb64f6444289fdea9f3b8fdae0bdb7 2009-07-08 19:06:07 +0000 80ebe088bf0196a38119f572b554340a8ccce259 2009-07-08 15:27:10 +0000 e6adf3452ca27755e26dd02f9b0ca1acf30c89cf 2009-07-08 15:25:29 +0000 fa3a0567e85a269255477331703c1bc9e0370191 2009-05-10 14:07:31 +0000 2f81979a2232816193a18320bb311152e268292d 2009-05-10 14:06:52 +0000 acc95a38a15e13c57f9414b237f059c77803590d 2009-05-10 11:31:26 +0000 74db5fde78f9b7a411d7335410c8df34f29edbb4 2009-05-10 10:47:06 +0000 089348caf758a1f703dec45d7f275d925bc48150 2009-05-10 10:35:06 +0000 65a1febefd340c10af23cd728e92b3bbc76a5c75 2009-05-10 10:31:23 +0000 95a6158134ca71a8746b039c7d998808a34282d1 2009-05-10 10:21:38 +0000 516465319da7843f26afa0dda5efc1bab7c668ef 2009-05-10 07:52:09 +0000 87c45de8ff359f652c4cd5b89fb03bdb6ec7271a 2009-05-09 11:28:32 +0000 05e2141ecb7d74a9066daf55141392097144e21d 2009-05-09 10:33:09 +0000 0589d9a47794e380506ec5bec8c0b3010f578d4d 2009-05-09 08:52:58 +0000 6a15e1e7c21cb082efe0baddc70ade6d3099558e 2009-05-06 20:25:40 +0000 c3e00fa936868fedb933f808c1e03cec41c14cc6 2009-05-06 20:25:03 +0000 e814104dee253bffdbb3d69c5dc57b65542d24d5 2009-05-06 20:22:39 +0000 53e80afa1fa55503b79519edbff183a2c1c957fa 2009-05-03 20:00:39 +0000 e223c8527cce4a239fe6c01a12ebbe121bf617ca 2009-05-03 18:17:42 +0000 1fe45891a63c56c8df1edb01035297c8d3e43633 2009-05-03 18:12:41 +0000 71a23318d9fa871f123f976b59d2576c7a74a426 2009-05-03 17:54:37 +0000 ec5d8e6cdcd5878187ac4ce3d7607b012340aeb4 2009-04-30 21:08:19 +0000 acfb6e2fb4b298b73ff8297a4be440f7c4a14e8f 2009-04-30 20:59:47 +0000 433085753156e352715badffc23c915d30f217b7 2009-04-30 20:55:02 +0000 3567d0d9fff8c7717b5800c227cb3c91e8fbd385 2009-04-30 20:45:28 +0000 f708a4307f15ba8c4018cdcc8b635162297b9dfb 2009-04-30 20:17:54 +0000 057cc6d85a004b31bf3905af9efcac9efaac440e 2009-04-30 20:16:26 +0000 ecf036384b24dae27d31b88360ff52453ece966d 2009-04-11 08:32:56 +0000 acb681614fed8b30e0f1090503c7a3b20a72beca 2009-04-11 08:31:59 +0000 3fe11fe91872907e2d1437c41a27901f4ab4b126 2009-04-11 08:30:50 +0000 dd7c7a64121dc21fc4e40b8b2e47f845a9172ebe 2009-04-11 08:20:59 +0000 5761e71c3f50aa62d0a9cca17551bc05e98a0372 2009-04-11 08:15:08 +0000 0790af9aa3deef8f589e05f7498640f32375713e 2009-04-11 07:48:51 +0000 b84253a974fcab711ca005c2f5584ebd5c83ad80 2009-04-11 07:42:03 +0000 a2195289c75b1ddcfa18a00489b9e451771612ac 2009-04-05 18:09:43 +0000 b0bfe206baecf804bff77a9ffc641ca464a7b4aa 2009-04-05 16:28:42 +0000 63513be963443d0a63be8ff736b96be5b0c47508 2009-04-05 12:45:29 +0000 d10644e5e013ca4ebb390fca67a47b9d00c072c5 2009-02-15 13:03:48 +0000 9198caa47b018e778c14b1ff2056ea872ef01846 2009-02-15 12:48:11 +0000 460d2ca09d20fa1c0247229db59358bf7f3284ea 2009-01-03 21:21:32 +0000 3d8efb6e009da9fc271e4ac4901c25017a9d84bf 2009-01-01 20:15:38 +0000 f618042f3d9e954ac2062b1eb4809740e77037e9 2009-01-01 19:42:38 +0000 5e7442607b9cf87ca0de01017967b0d6a1a8d89a 2009-01-01 19:41:22 +0000 399a5bedbf4b8b6abea7b85a671c4f6ab091a6bd 2009-01-01 19:12:03 +0000 dcf60ebf1b33b9ebb7c387615bd3163c06c424c0 2009-01-01 18:04:12 +0000 1b35c2a0229a29072ec337392409b96125c64018 2009-01-01 17:47:47 +0000 10bb32e75b3377c59f2a882522a26b0a584f729e 2009-01-01 17:26:30 +0000 0af979e9d947ef01bb0b7e4ba5fa9fdc3c0b3d6c 2009-01-01 17:25:19 +0000 5bcc5955b7ac4a6252a6e2e29a32da4c5bca314c 2009-01-01 17:20:30 +0000 58d0cdcf84f0fac0a52f1ce5694840394fcf7234 2009-01-01 17:09:32 +0000 a60570dc4bd56b9e1c9cc5a109670fa492bef8c1 2009-01-01 17:06:30 +0000 aaa5f0e44b4fcf959ac20b61bfaa02006f34d19a 2009-01-01 17:04:21 +0000 7ccffe8ae3dfb1a6d2b81284cf9ddd0a38b14194 2009-01-01 17:01:45 +0000 62d64f21d4067e8e2923877443893c360ec485c3 2009-01-01 16:41:15 +0000 a653117c1c78302dd9e12f1b53d43240112ef5c3 2009-01-01 16:21:27 +0000 f0a624627efe8cec083538fd00579580d838152b 2009-01-01 14:57:33 +0000 7056ca0febbbae2d859a479b5bb75b8494c7b594 2008-12-28 12:44:11 +0000 4d7fa28996818511a60a6e1c357d8caef2ca27b1 2008-12-28 12:40:43 +0000 1c774a2842793919b822218980442d817a4fa82d 2008-12-28 12:39:50 +0000 a1a7910b42e23dcc97e034d51bf81f8118ff5c60 2008-12-28 12:37:28 +0000 6d1a4078b4abfe6fe441741a59f243be02221cec 2008-12-28 12:36:45 +0000 f0735794cb1bd814a813413b848c2ace8a7a2e1b 2008-12-28 12:26:37 +0000 a4072d8478de125c805476d7ecb28947e6c1f8c9 2008-12-28 12:24:34 +0000 c2e17490cda87a37b8f6b1ff4818a7c257473c50 2008-12-28 12:17:52 +0000 3ecbbc6b051d9bb91c5533852a6e92cda9d871f5 2008-12-28 12:16:59 +0000 d45071684e13aa92e6898fedbb23e558a51c5915 2008-12-28 12:09:40 +0000 7106b10b63d8aceb3c532e926704be0db4ac2647 2008-12-28 12:09:24 +0000 17384372f1d93e176ee33c353b6f00b6b222877e 2008-12-28 11:59:04 +0000 6e4a1e6688ce801bbc7fd027e67759f9b8fdf43d 2008-12-28 11:52:56 +0000 4ac71ce48d54937e56a037eba9ae5e553f6c2ed4 2008-12-28 11:51:44 +0000 7d6891e323ca1a1824cb5cb2b60fbc98a8402e1e 2008-12-26 22:22:11 +0000 9ea9ca953b088e638cb25f425d8481d8befa6766 2008-12-23 23:23:22 +0000 09c4295c4a778d135d5191236fcce91087b62b3c 2008-12-19 16:43:06 +0000 4bec18e7cbf4fd7f78d845ef78734702b26afcdc 2008-12-17 14:30:22 +0000 4b1b803581afaaa374587a7ee4ec8262316cc6fb 2008-12-16 20:37:52 +0000 3987c068f884e25b063f313421e48a16380b1d33 2008-12-16 20:36:50 +0000 f439c125738ce2ed002670593136c6a4a014ffdb 2008-12-16 20:35:56 +0000 ff0f468c13eb260925552eea990c819c3791b618 2008-12-16 20:34:23 +0000 66f781ef42108950f3f4d8bb72e3e84488d9e237 2008-12-16 20:33:43 +0000 3d4c26917b46958cc5ef24183ca5f7251d4dac23 2008-12-16 18:16:52 +0000 3e41a3af03ebf9036c468595b8382502b0f60925 2008-12-16 18:16:12 +0000 1b93908973ee1d0eb655a7bfdc5fe68693b15130 2008-12-16 18:11:28 +0000 3055d1ce4a476ec317506c1e028eb583080fd61a 2008-12-16 18:10:57 +0000 f9c49dcb67716ae4739258c981958dbb0f44a50d 2008-12-16 18:01:54 +0000 d03686e4dc9f707c1b97a3c400838bbc5d6f1624 2008-12-16 17:24:26 +0000 7316aa77b36aa7fdbecb185c8206e063323cd706 2008-12-16 16:25:13 +0000 ab2014cc4993783241e6c8e4f4b2a2ef60c8c3f9 2008-12-16 16:18:55 +0000 4911dac6cf53d427266b27148641f534a72b9f74 2008-12-16 16:18:19 +0000 9c6d112ed5b67bbf571277c6f1fbfb6aefce0412 2008-12-16 16:03:39 +0000 0b0f323ad6a61841baa2c1a188515b469ae99238 2008-12-16 14:35:01 +0000 8ce243a467e5ee23c63a46a0a31f0754cf73f27b 2008-12-16 14:30:11 +0000 9c2d2e7ad5e6eff18fb6fd632121a2838bf339d9 2008-12-16 14:29:50 +0000 662dc4b1f3caf0ba986209263700e208b033ec4c 2008-12-16 14:28:04 +0000 6b6541482815853afaa81d820be9b500ccea0015 2008-12-16 14:14:11 +0000 11350c52e7cc31363599429d71dc0695eaa29dd6 2008-12-16 10:28:54 +0000 47106ba2a2de9f6734b848c6d544b31697f34fc1 2008-12-16 10:28:28 +0000 fb5de4fb355b9fe0ab4d195937773a770fc3ecc7 2008-12-16 10:18:01 +0000 69e91cfac30b6e51f104c6a5ecc9fd1cbefa5b36 2008-12-16 10:17:12 +0000 17ce2ed998540d5b3da9704a95c5a72a93042dda 2008-12-16 10:16:28 +0000 9878d68f5e788233094eec0f7d87cb3b4b611cdf 2008-12-16 09:40:59 +0000 1668ca854ab970612e82f89423ac190da8f04400 2008-12-16 09:21:50 +0000 9ef77132ce6cf01812365877a9105eff1122eabc 2008-12-16 07:07:46 +0000 952208f708210ab344c9d62d7262b1e3e5afed02 2008-12-15 22:23:25 +0000 2e090c9d2dec7a4d72ff24f1de7f1c7afc170e15 2008-12-15 21:17:42 +0000 2b54bc317838d97f60e4292576f8f6976302f42c 2008-12-15 20:38:20 +0000 b9da877c793de579940557e447a79656a533d4f2 2008-09-15 02:27:28 +0000 1b9410a443ca91730cfec4b0599fb26dc285ac8f 2008-09-06 20:04:08 +0000 6c0a87fb16469e4ba9e1cb256a17d81c3b6fe06f 2008-09-06 19:50:18 +0000 4d79fd6adcf6a6deacf1a65749531ebd6709404a 2008-09-06 19:30:58 +0000 5ba3fedbd58571ccd08aee9e02d79f960d240fa8 2008-09-06 19:27:16 +0000 7d63ace41cfac479399c57bc73582166ee1963be 2008-09-06 19:13:50 +0000 36e5035707cf4b76670da2cd23c07153d0cc8106 2008-09-06 19:01:03 +0000 51d65ce65a3cace19b38fa6af538595d7954a300 2008-09-05 21:31:50 +0000 73c55b7f89c849e79715095d8a6c9f05d69adb70 2008-09-03 19:04:06 +0000 6cce41f2029698baa071499cdcb18c5491ee8719 2008-08-31 19:26:08 +0000 d5960dce4d89c12a0f27d82657311267a2774b8d 2008-08-31 13:51:44 +0000 66497a4a86b63fddeff2046315ab3952a850b4b4 2008-08-31 12:52:18 +0000 55c95c5a4dae8f2c70a026583164a9dfddd8db49 2008-08-31 12:49:27 +0000 9296354a511a9055b139cd3e617fc8bff685d6d8 2008-08-30 19:43:31 +0000 aafd31b6c9a0802aca012450b46e42aaa021e718 2008-07-30 20:29:21 +0000 741b959dbeb6910f4f787faaeefb1b7c0326c684 2008-07-30 19:33:39 +0000 70a14e95569dc12db29a0110e70312406d47361d 2008-07-30 18:51:51 +0000 5a5c4ccbc9f261896161275f961a4b31e838b3f9 2008-07-30 04:58:13 +0000 c87cb81a2ce60e708af9fd7574ffb982e9d62177 2008-07-29 18:21:33 +0000 3b9b13624209bf00dd35057905a21ecf88fbeae5 2007-07-22 15:16:21 +0000 71fc1241b710f86b781aa5c8a1457cf865186cfd 2007-07-22 14:45:12 +0000 3c7f507aee31ef3b399430f1c05146bc5dd4b3e6 2007-07-22 12:39:10 +0000 9e0d1fcf05c99cf2e4a8adb40725a2500302ede0 2007-06-03 18:43:08 +0000 41a74bc8e454405e38b122d66d7ab5e379f005cc 2007-06-03 18:24:33 +0000 7b06f8006d401ecf236f6f7d0e62212dce53c650 2007-06-03 18:23:32 +0000 b80b30dcd052e0a9b8cb9d4c192fee58792ca123 2007-06-03 18:22:08 +0000 0b101b75b2052630bc786fa7b9467a28684d57e3 2007-06-03 16:01:35 +0000 4bdd2d1f11984ac7ab60bb0427aa4c9d2e1aeefa 2007-06-03 16:00:44 +0000 2a0c3ddb2a045ca82365fc7c01ccd17eee87cf27 2007-06-03 15:16:01 +0000 54282f9c1c2e564a8ca29a1e13da9e1667d034b9 2007-06-03 15:14:30 +0000 336c8d24ce03e92d3e1c642ed6529695487f9531 2007-06-03 14:45:25 +0000 0e42e86a5c029a4a3f0bf229dccc42e9093b5619 2007-05-30 22:27:33 +0000 b392f291d8be2717473df7a671c1747c6d5dd0b7 2007-04-22 12:44:03 +0000 4ae72075ba7be3db9028cc414a3c2276074ec711 2007-04-22 12:39:27 +0000 d8bc3ef2d0754923f135de580ae1b79876572bf3 2007-04-22 12:30:14 +0000 e77163bb967360b1fe01aa648842b1e120ae69a9 2007-04-22 12:17:56 +0000 a929d5aec037e5031df73e302d80c98558e5dfe4 2007-04-21 19:37:40 +0000 b551d8c79d820b3c6414be1b09514487ed43efd8 2007-04-16 11:51:32 +0000 2e42a67d82702395a03f792a1141105d8d455b31 2007-04-16 08:29:59 +0000 927805c9777032abed33892f3ef2260abb4e89e6 2007-04-16 08:14:05 +0000 bc9d84d94da89b2d96a61c77910d3beead955dc4 2007-04-16 08:13:22 +0000 abd20ebde885f40f7b0e34061f0dddecb1a3d881 2007-04-16 02:00:30 +0000 13685fcd565bad87ffedd34ffc420a01f93d7717 2007-04-16 01:59:15 +0000 442916d32f801b248c2a125912dedf7784eed411 2007-04-16 01:11:42 +0000 a16e504f780dd845536842637b68f9fee64799fd 2007-04-16 01:07:10 +0000 85807f5ed50ea077498b9f7b7baf127334cc431f 2007-04-15 21:34:46 +0000 2f23e745f30929e906e89da1c918f0934a7c5830 2007-04-14 17:46:51 +0000 d426eae496388494ece6f66aad7a3f883f330cc9 2007-02-01 20:55:47 +0000 2b6a181714383b8955cf16e4a70ed0f36f4e44ad 2007-01-31 23:04:05 +0000 5bb00b7abd5633a70e1fb49a65f03427a9625b48 2007-01-31 23:03:36 +0000 21db58ac9051730b7616db15e4cca4d179a07059 2007-01-26 22:40:05 +0000 63e0fb909994e1840b70a137e4d43f5512ca90d5 2007-01-26 22:39:29 +0000 921c7fdf51a4c03c4443dfdb69d75557bd66210d 2007-01-26 08:10:19 +0000 1198ebd645155389576e1668463cecb599fb146b 2007-01-26 08:09:42 +0000 d8012d65dde4f6911250c892f30af7f437efc991 2007-01-26 08:04:51 +0000 4b922913a76b930894211d37f7cdf8a04a35a532 2006-12-24 22:31:49 +0000 4ebf2b49aca539b9170d268150a87848db84f3f4 2006-12-24 22:24:11 +0000 77bf1119ec0d9b84abe3faeb137652753c728d4e 2006-12-24 22:22:49 +0000 eaf594bf2d597cd4832dcf33b9ec9b68862214cd 2006-12-24 22:12:55 +0000 4a7ee0b16c973e5ac1afb60225d5ae940767334c 2006-12-13 07:17:34 +0000 21a5da904c001ca89bc022ed199a5efff4ccb09b 2006-11-25 11:50:49 +0000 ae9f288ec30e0d5a8f1595343f7c1aede0cbc6f8 2006-11-24 23:35:32 +0000 4771654e2a518ef797207ae5692e00a537f020c9 2006-11-24 12:56:50 +0000 5168d89d0e093c33784562d2acbe5f8a5059d61e 2006-11-23 20:05:27 +0000 6b8a94e28fe081659d2da1240b3a2010bb98ebf2 2006-11-23 19:22:23 +0000 bc205356e3a72f200de9c6293505ec76161d87a1 2006-11-23 19:12:25 +0000 e6b0a70fc4408b35ad9fc22082af16439cd6c962 2006-11-23 19:10:21 +0000 5626585b0bc08bd714db2e0ba35645c2e899e944 2006-11-23 19:06:27 +0000 b8f240affc05eca47698e30813f0919b33d0ac59 2006-11-23 19:04:52 +0000 d73f8f90c85d50928c37994fd4d80e9a136de6d6 2006-11-23 19:01:17 +0000 2f8d91de0d8b25a5f78b816685c034366678db81 2006-11-23 19:00:57 +0000 cb88bf50ae9882bc61ba5c08fd848be2f9497e11 2006-11-23 18:39:12 +0000 fb2a154a0bd2adf43b22d5b5a18e3f763a435383 2006-11-23 18:37:14 +0000 b3c83a4f8ac7740fe4755cf9f4f58c1fa92fba28 2006-11-23 18:34:10 +0000 3b8df4ae8d95a3124218b2c41e28642ceaf1ddac 2006-11-23 18:22:43 +0000 34c5671a282f216f39d77620ea7de251d3902df9 2006-11-23 17:51:56 +0000 4a4d05ff5cefc16079b61877ce7d2e9f2498f424 2006-11-23 17:25:29 +0000 89e483ddadd844ad7fb49c07e035ce39283f87b7 2006-11-23 17:19:10 +0000 a950fc345e3a7a38e22dbcd2e560def5d6c0976c 2006-11-23 16:47:50 +0000 fcc1a065a0a169688cfe0df9f34c31cbe77986be 2006-11-23 16:42:47 +0000 6fe0b96dee1639b188f28171187fc2cc6ecfc0cb 2006-11-23 14:41:29 +0000 60dcfbd712035d18a55bb9fa02e17119212426b6 2006-11-23 11:58:30 +0000 b2aefa7686d3051e48ae73a795992dba1076e9e7 2006-11-23 01:33:53 +0000 4655c6262af0b84395508e7fd3c936d5a8aa04a0 2006-11-23 01:33:01 +0000 123bc7169b0c11c0b68195bb9f0e9d78557dc3d5 2006-11-23 01:30:07 +0000 1526ac2fce118e982b9a4b98fa1a8d67b44b90eb 2006-11-23 01:19:55 +0000 516cf71da214ed63ada44ebfefe566bf87cc1ecf 2006-11-23 00:28:21 +0000 ab33a14f28d5ac2f428f980bf6404f94b3cf3a94 2006-11-23 00:27:50 +0000 785613a8a4f2123f076e326cd42101fa9e0c74a9 2006-11-23 00:27:23 +0000 b7d29fdd52de94c63b935d2d358cf7e966f970d1 2006-11-23 00:09:10 +0000 53816e0d186056c3110c4c33037fb11938e9a899 2006-11-22 23:56:06 +0000 dabeefbbc158727b073119a7fa0c3164eeff51be 2006-11-22 23:01:51 +0000 7c23886fd311db4a0991193ff5e9f116b6dbed80 2006-11-22 22:49:46 +0000 1404f2b045f4bde95ae28ca837b0143b29d304b0 2006-11-22 22:38:16 +0000 e232443616e19aad35f10ec23d2ebc93b114f326 2006-11-22 22:27:26 +0000 b07f814cfdc35ca668a7209bf7f2f7bcf696636b 2006-11-22 22:18:57 +0000 6cdacd8cdd01a15be34fb5d3d77eb208c99309a7 2006-11-22 22:15:32 +0000 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 2006-10-12 21:53:33 +0000 39e6e6bd1586ab87adab546e50de89a35c32359e 2006-08-19 12:23:19 +0000 d24fb670e9e2c222b6a73dfcbb37948a46620bd2 2006-08-19 11:53:59 +0000 5c03ca3710199a9f2941c085fc724aef830e0e5d 2006-08-19 11:51:23 +0000 94adfec1b22d8c81637e7e7d9416978bb8c9edc7 2006-08-19 11:12:24 +0000 f4ab5556d8724d08f6b798aae0e62173133872a4 2006-08-19 11:11:23 +0000 6558bcbca32be24d6e842f6f1846b64b3599aa00 2006-08-19 10:58:15 +0000 735f17111e8e8bba434903bdbc7272f8783252c4 2006-08-19 10:50:39 +0000 dd198821ecbbfe7ca59844cdb4cfc281d66cedd3 2006-08-17 12:16:07 +0000 c9dbc43e258764ace3aeb77da6f2f7096f23485e 2006-08-17 11:50:13 +0000 d041a54bb5880b637b9c9777f1a0015bb14b80c2 2006-08-07 21:42:41 +0000 172146551514850b00b009384fda512d0e19052a 2006-08-07 13:30:26 +0000 77ae36b4319275b31a89aabc65922fbe306ee725 2006-07-20 20:38:45 +0000 df9325fb6215f5637ac5d3df8c12e2135b6f2d4a 2006-07-16 23:03:46 +0000 7804e16d2e079be8b8622fe4bcc99685b8bf2bcc 2006-04-01 15:15:46 +0000 68da3d441f047a1011bdf318cf32add665e661f9 2006-03-31 11:18:52 +0000 f85672415663732d6055718d20e71099c0733bf4 2006-03-30 21:25:47 +0000 a778aabcb06b5b646c8c66643c9e2c53451ba2fd 2006-03-30 21:25:26 +0000 286c68f2eb5f1c01c0ddc1c0a6f6ccc0c57775e0 2006-03-30 21:21:14 +0000 fbf8b54792409fd82b03895d1b6232940e9d6805 2006-03-30 21:16:04 +0000 367971c54ce589e793db0665f54f975ffd1d1542 2006-03-30 21:12:01 +0000 d25e79c2fb127b2a39195ec18e267444519783ab 2006-03-30 21:08:23 +0000 18405bea18c9e9fbaf231ce24fe5d8357d5c5828 2006-03-30 20:58:28 +0000 6d310013df6445b2019d14a06c5351b4a98a29b3 2006-03-30 20:05:33 +0000 1ba7631147f6802510b24a73250dca4dc3653453 2006-03-30 19:53:32 +0000 7346caab91884aa669a0099415c9740b7ab45d77 2006-03-02 00:55:44 +0000 039f5440d90a3a387bbced8e689f4958411a6e32 2006-03-01 12:36:05 +0000 f23e4a53c66e99e153f637ad519f102832a6b3f4 2006-03-01 12:32:21 +0000 3c214324994daa7efea6d36f5afaaea1cf605478 2006-03-01 12:31:43 +0000 7d61776eb168cfb9999649ff9d0b495df39640bf 2006-03-01 12:18:51 +0000 d6d8350f3f16ba40f87c144a96e06ae8dfca410d 2006-03-01 12:18:18 +0000 42b3190303e66260e8c63757fda395b28d013408 2006-03-01 12:16:52 +0000 ec155572362dc4f3d6c02d326854cfa55b6c8f14 2006-03-01 12:14:37 +0000 c0f424975374842274ed0bd65e8a613f9d6670f2 2006-02-19 17:01:37 +0000 ac363edcdc3e21c3ba13dbbf2f206940447a4355 2006-02-19 11:59:27 +0000 62e0d710b82dd159bd134b96f2f72a23869e541d 2006-02-19 02:31:55 +0000 cb720af2eef058dde12977be647c464216119b3b 2006-02-19 00:28:29 +0000 9b126c537837c9c0eac50e726f5323abd4c98201 2006-02-19 00:25:06 +0000 40a05288c3f1fa1b2e811875acf2d892de4d97c5 2006-02-19 00:21:07 +0000 329d96540c1f329de4c8ca801d73af3a3f2ee072 2006-02-18 22:08:24 +0000 011baf119a753fa895fa5ccb7d9d9e2a9aad09f0 2006-02-18 21:50:14 +0000 c60d6332c6224d37d9373d52e979fe52dca13f66 2006-02-18 21:48:46 +0000 5c57fdaa91655be537091f2bffac2780803b1369 2006-02-18 21:35:38 +0000 c86f94243d70f3daa794661765d1ccbcda932aa9 2006-02-18 21:32:10 +0000 ddd84bafeb2a1a69a930695acef141582a55c03a 2006-02-18 21:29:57 +0000 4d9c632d0f7fd7d3fbc38fa6a73f01238f8b1893 2006-02-18 20:40:50 +0000 31e34ecae0a1ed58aeb1547217d35092bb344031 2006-02-06 23:50:18 +0000 465e35a2d8336c5872a2a60f6a813c0006364b58 2006-02-06 22:39:20 +0000 136cb569e7e79fe6eea74c0737ea632e1cda533f 2006-02-06 21:38:59 +0000 a21e1e1a9bf906876d5034492aef8981e4c19e36 2006-02-06 21:37:15 +0000 2da82b07df9979a7d0be250842717801ff380b63 2006-02-06 21:33:38 +0000 71ed43d7553e431cb257e6bc3740305a5f01351a 2006-02-06 21:18:05 +0000 dc3f1798d33452094d4d203db5601e79b1745b02 2006-02-06 21:04:30 +0000 da6af232eee2a1cecd7b08111276a0b8959c119c 2006-02-06 20:28:13 +0000 3b603dda99ac4ff5f836622a5abb047533d84646 2006-02-06 20:26:15 +0000 b48feaa6a76f1e84aedc1bee81b221b745207c6c 2006-02-06 20:25:27 +0000 5d2e48e2283dcea55b956dc773cf58e8d950d956 2006-02-06 19:43:26 +0000 90cb0fce6e4e26ad552b731f1b29eade074f3fbd 2006-02-06 18:39:17 +0000 cdedda69ec793270d45f54ec9d0906ac59e5a19d 2006-02-06 18:38:44 +0000 4c01b45c3f28199242e1b9254072fa273dc7f75a 2006-02-06 14:58:22 +0000 d9555d0ac4f02da426235902bed58d6e5d50bc34 2006-02-06 13:45:33 +0000 a4c37ab5b93040a4fb03b3935d329d92b22d438a 2006-02-06 13:43:48 +0000 a74c1572ef7f95b9cfbab926b7964b1ae24e7d05 2006-02-06 13:30:26 +0000 7b146e0ae40f5f3386f1518a799b3011cb538b9c 2006-02-06 13:24:33 +0000 5be7d4fc18a100891ed339746226a699be68540b 2006-02-06 13:06:03 +0000 9e6a7fd6202ce1a37602f989c9283a299fb10294 2006-02-05 15:54:08 +0000 b74ee3652b127a8f0cab2229891344424bd64299 2006-02-05 15:44:34 +0000 6e4f59d016511ce2fadec202ec878a88ec1a63b6 2006-02-05 15:33:52 +0000 1f328fc2d7ebe21216b666b342e9edc73e0a509b 2006-02-05 11:05:29 +0000 1926bc7b26e7de0745b276bcd2ec11dfdc06fdf8 2006-02-05 01:42:30 +0000 c110f73413ade693efc24b986dcd26ed8a987143 2006-02-05 01:03:16 +0000 5e5aaab65642be827d87bbe009090ca8f33abd88 2006-02-05 01:00:18 +0000 8549dcb3dc5cee26a3c2c5c13c385461de305158 2006-02-05 00:48:42 +0000 8bc439ce6ad63a032053217e9423851998fccede 2006-02-04 23:21:48 +0000 968045204b79120e526b1ffb9ac8616285f1c7ee 2006-02-04 23:19:45 +0000 00afece67c167d77ea13b82905c3db75f5b4359d 2006-02-04 23:18:33 +0000 04b79100b4133290ffb091f2a9c08e8fabcb12fd 2006-02-04 22:41:07 +0000 f1e5f8c36b5c3a1d444ea46076e2130c1e50c760 2006-02-04 20:47:09 +0000 2972d84727272f87b80b72de8c599f4ece86e6e6 2006-02-04 20:34:43 +0000 9d1268bf9f7a8f3178a665de4c508d2bf19fe329 2006-02-04 20:34:12 +0000 10f8d16c98843bd38a162beaa2057864023c1b42 2006-02-04 10:00:43 +0000 e3ce8d11e1e6a9dfabd8f07487913d3b77413fb4 2006-02-03 00:05:14 +0000 27dd28703599888533576143705d7939489ff89d 2006-02-03 00:04:04 +0000 098760b2fd4e526405275627bab1301609b54f50 2006-02-02 23:55:57 +0000 10b99e9876f9b5b1b47fcfc4d18b71e55396de55 2006-02-02 23:53:07 +0000 b95677457955d3d4d93dce24385095b2ce0f6508 2006-02-02 23:51:44 +0000 2bb41fc98780ec62a52e1aa7fab36fc761620282 2006-02-02 11:33:08 +0000 63b13e282782f0f1a2816215076a1ce73f77d951 2006-02-01 01:23:57 +0000 58b6042f925684f438c1f201d6c69fa4913bc46b 2006-01-29 02:08:28 +0000 97cb209d93bfecdd182acd4a074bbaf9e26b0c83 2006-01-29 02:06:12 +0000 e58e7728b1150336b8c207ad48362f9c6af1d75b 2006-01-29 02:04:19 +0000 53e6637978f1bdc881136b794b250b9902610c35 2006-01-28 22:12:30 +0000 69c7df2afe89fae84025a0ca26d1e44325c87bcc 2006-01-28 22:03:50 +0000 04bae8b1f4038e8a3eb6c6588ecfbe9e197afca4 2006-01-28 22:02:04 +0000 5ad5b87456c99961ebc9ba1efab154bb92ebc5e2 2006-01-28 21:59:59 +0000 d83b27ccca59bc7871dfd758781dda041b35fc5b 2006-01-28 21:50:30 +0000 c32fadf12afc3c5d45d33ee0c0a08dd0a866ee49 2006-01-28 20:22:45 +0000 14074a6d104f7df78facaf3d92dcaf041c334316 2006-01-28 20:22:20 +0000 dbb5b27774b64580dcfb5e9bf786820d5bb3bcb3 2006-01-28 20:21:10 +0000 59f0da070e80b521ced3a7ae6c279e8fac6c4a06 2006-01-28 19:40:52 +0000 438f5860d4c041006c39980861b3b4b34e300598 2006-01-28 19:29:08 +0000 1018bc76ab847ed689502dabb437a76e507b1cbe 2006-01-28 16:53:37 +0000 37ac845b35f6dc6217de40668f61f653e161136c 2006-01-28 16:52:32 +0000 3a15e7f1936e3590626eba8dfad7004cde07144b 2006-01-28 16:50:00 +0000 a2b1f2e6acb246be3737f770d71ce20f7ee876b8 2006-01-27 21:58:55 +0000 9149c45bb81efa63f8c71e5874e40ef438659b39 2006-01-27 21:40:50 +0000 256d3c9296f6dc68d41f4b4a733e55376aa081ea 2006-01-27 21:38:08 +0000 ad5bd609f83f657cfdf3dc885aff94d8e3fe678a 2006-01-27 21:36:29 +0000 82c2160da5a2dd2e678c595fe182be265817eab2 2005-07-01 04:51:58 +0000 52bfc3c42142b20aa771349e09c4556cc576c51c 2005-06-09 17:40:58 +0000 05740f38d458658a4367073074ad51349c7603eb 2005-06-09 17:12:19 +0000 55801c53255a776aa618ff625f53dc385afd9d50 2005-06-09 17:01:56 +0000 5b2eba713e887668948a13fa5d402dfa59be16c0 2005-06-09 17:01:35 +0000 11af1aa1fe3f5e872f38116011b609e15b8ee9d4 2005-06-09 17:00:32 +0000 f75aa7ecd58c5c9b4e13edc85c584307b1998698 2005-06-09 16:57:59 +0000 6e5c560bf8f99489acd622e5c2dde483ee7b9272 2005-06-09 16:56:17 +0000 859f1b42fda79d6fd384afc859f16367fcf7c356 2005-06-09 16:55:57 +0000 f442f60b53fc7a67fafd4ee2fc6a6acb85a43de3 2005-06-09 16:40:19 +0000 492e337fc94565a479ee3f064d011e0a9514bcd2 2005-06-09 16:28:47 +0000 7af6440f9eb5201de263d5a45138cdc51d048a6b 2005-06-09 16:27:16 +0000 1bbacc54fa72545ea72a5a0fffca48a7156f9c96 2005-06-05 15:57:26 +0000 469c54bde2f5756c281fa1b3ed6bb9cdbdea0ada 2005-01-08 12:45:24 +0000 ab098366011bd3af397dc6bd651115c489703851 2004-08-09 19:31:44 +0000 96f7ed7b96f5c1161084a184216ab0b391ef6faf 2004-07-29 22:25:18 +0000 dbf672bd8eefa9b03b3bb6c6af2977928fe9fa66 2004-07-29 22:14:11 +0000 7bd5710b36e34884b44e92d27404a21c0b2d2b38 2004-07-29 20:50:35 +0000 591edc0132caf72fb06b2cc6e132528e3958e189 2004-07-29 20:20:26 +0000 1e734e1da46a6834c8a3095a48d7467682855c03 2004-07-29 19:47:13 +0000 72e86dfe6baa718736f38ee41abb46b18792cb2e 2004-07-12 22:09:42 +0000 80ea616be52b169961895621b54128f115860357 2004-07-12 22:08:45 +0000 4a37ae710a435d3a83845805e5df8e677e896627 2004-07-12 22:02:12 +0000 acf8b7411f1c9bab665cc99f19ec9ff0f39f7fcc 2004-07-12 22:01:46 +0000 267a88086dfa36e13d42c315c73906ec3e6e7ff4 2004-07-12 21:15:34 +0000 6c28296b5993d061e7500cb4a56967b3429f9a17 2004-07-12 21:13:03 +0000 7d1f04639a3aed6e8cf14fb82734649bb98bcb22 2004-07-09 22:12:14 +0000 875dfe6feb99050465c4d7efbfd66caaff82b05c 2004-07-09 21:52:07 +0000 ab123f51a6ed514e3cb9281f97e62c5cbec6a893 2004-07-09 21:51:24 +0000 6c48cad0db2afb69a9224aed05d59b7b131371da 2004-07-09 21:49:24 +0000 34b2990f056024ca30db85a6005cda7b55e5fae1 2004-07-09 21:48:34 +0000 55e12495fb07820c12a326eefd2ad25a009ca02f 2004-07-09 21:47:15 +0000 5c8256cfad8ddf4949476b8f8e8e963daa584158 2004-07-09 21:44:04 +0000 17071b1938225ba819e4c770b974d49ace3b896c 2004-07-09 20:51:34 +0000 47011b11ededd397429386458c693bcfa711b8da 2004-07-09 20:45:49 +0000 cfe27484b2b9d67ba3daa641020e4d9bd4970e73 2004-04-18 19:08:30 +0000 d887f8b90b4b672cf0aeaff53da9fa0c4bb06a67 2004-04-18 19:08:03 +0000 f1d5f01fa62cdb912c456c28b9cf0695d6fb858e 2004-04-18 18:49:35 +0000 31e46506e3e7665af3723171590c2f479cd405f1 2004-04-18 18:37:07 +0000 fc145c7254dc3ecf13e32d1a05efe588121919c4 2004-04-18 18:35:56 +0000 ff52bc17196375f5326302caff5ec7a85815eca0 2004-04-18 18:35:37 +0000 d9e13dc32eec7381937b47bb406908490c0e339c 2004-04-18 18:34:45 +0000 6549584821165300b649b55116b29efbd9e60b23 2004-03-08 21:20:25 +0000 8d28a7ced267bccfc97169ace88449d7ab2dfbf6 2004-02-27 23:41:46 +0000 f8ab76eff97a3fecd960c7366604af91bb563b33 2004-02-27 23:34:52 +0000 1ab52e92cdb32225191a0d0fe3a78f22355b9dba 2004-02-27 23:21:55 +0000 11d833df847067c68e9db2ecb0fe11044c8c0bf9 2004-02-27 23:20:34 +0000 1205fb05f87c1c3405e5bf5a28c7fbe1d83700a6 2004-02-27 23:18:00 +0000 dc91c2c62da75310ca0ec9ce4eec7facd053a654 2004-02-27 22:57:05 +0000 4fb6708da50c7b557293de3b391384bcf9f85634 2004-02-27 18:45:09 +0000 95e7427abe5eda1e2ea64816b54353038ce7cab3 2004-02-27 18:39:50 +0000 c8b5f40d003414828bf83c0b2dcd8dc5b7d9ed27 2004-02-27 18:37:58 +0000 1fcb4c4fd8173663a9ccf2fefdc5b1dd65f595e2 2004-02-26 22:57:06 +0000 9b4dfd258910dc2cb03785aea42cd09246be3d7e 2004-02-08 17:26:50 +0000 4bb6ded69b6b2157b43f7593e7e00929f3fccb6f 2004-02-08 17:23:30 +0000 3c972c66f54c2dbd7c36c42d013581d1e69648fb 2004-02-08 17:22:26 +0000 fb57daf9e71b5eadf6bd89863d23cbd6ab4a6b73 2004-02-08 17:21:47 +0000 3f2339aa0729deb659bf422fb787fc055d01160b 2004-02-08 16:49:50 +0000 5f349edc09d3811b217bb8140fe01ab5351fcecf 2004-02-01 21:03:30 +0000 54daeecac16efa57599bf3bb06d4d333854d3f8b 2004-01-31 22:53:09 +0000 be05f06ba32f1e57d5d9d9b665aae2176fd78123 2004-01-31 22:52:38 +0000 d6ab12bcb27f112a08810819cc21d25a1969152c 2004-01-31 22:46:15 +0000 11928f5e06e577a42f2410293b017986b0cd435f 2004-01-31 22:37:53 +0000 7c43b57650a1ecd9c846bf169b12fe979775af96 2004-01-31 22:25:51 +0000 fc5b9f4eac0468b31b7988f65b673fff613bce00 2004-01-31 22:17:59 +0000 c6034341c6df86c067c203ff89f3a1f9c085e536 2004-01-31 22:17:39 +0000 7103d24ffc89292c3799f2fd7304788ccc9776c5 2004-01-31 21:25:01 +0000 076f4dd1000f10e09d74b2a28531d6087e74d8d1 2004-01-31 21:24:32 +0000 9796fb24e48a79ce46bdfdb04840a837c0342fab 2004-01-01 21:31:56 +0000 cf981443a0f94b20bc7856b1664db145b3dbfff6 2003-12-30 21:05:09 +0000 9da1cf5f12013d60bb84d36c0d84a98398ca8935 2003-12-30 20:50:39 +0000 5c1b7ce7128c08277329c03c8e3a917703786795 2003-12-30 20:45:00 +0000 d10055dd3719c84ba15bbe0589bcd22f304bca00 2003-12-30 20:41:45 +0000 1a6d23383b639bf92d2b6edaad5db7db48267c40 2003-12-28 19:29:35 +0000 be590cb6438f96f467f6d40a90fb958b8da0b395 2003-12-28 19:18:28 +0000 48f3bdc704036fe56d14df738c1edfade5421bf4 2003-12-28 19:17:29 +0000 c5ac085204422dc9378a3d80be7b88f89231d4a8 2003-10-30 20:08:11 +0000 bbdd2933f1eac1fbd9f054aa1f2065d3b04f69b4 2003-10-30 20:07:40 +0000 f8bf51495dc46dd919c4cfda86fbf2fe663360a1 2003-10-30 20:07:10 +0000 18176ec4f28c5f6097be99658669c428bafad8f0 2003-10-30 20:00:23 +0000 2d09c3f365e8a805a2d0229e72c507a3968eac00 2003-10-30 19:56:59 +0000 759cf9bf4c61ad803dc9c5e3ce7a874eafb98e11 2003-10-30 19:55:22 +0000 4a85895910a5367825f9100677959ac5a39627b1 2003-10-30 19:54:17 +0000 9271093b7ddda97d0b61474318993cdc4beb4315 2003-10-30 19:52:31 +0000 32a997bb3fd65fd1bbead509fd9db8bc17106666 2003-10-30 19:51:09 +0000 2eceb90a030f5d0285e3df47d221728c3f3d7f0c 2003-10-30 19:49:53 +0000 1145263a83c5b7b4b3d2bf3a596766dc5b1b527a 2003-10-30 19:25:57 +0000 301bb84a186c15bcc0d12329ce210b79ff898c0e 2003-10-30 19:25:57 +0000 1bafe488000563083d2379af131b7689f31a2fb8 2003-10-30 18:51:23 +0000 24bca15af04d79aaa45e3437f359860e1f86b8d9 2003-10-29 23:01:50 +0000 43165b46d7846313443cce6e40fd0f87917d960c 2003-10-29 22:59:51 +0000 ce5c6d1a1abd05ba78e99a966438600cf970af09 2003-10-25 23:06:00 +0000 798fdd1f8576a527cc67321f3561fd71b2d29159 2003-10-25 22:26:20 +0000 5eba46f03e2dba78dbb744a185badd961f60f941 2003-10-25 21:33:56 +0000 74433f78b378d0e373f1f063ca63c7900b0291bb 2003-10-25 18:35:49 +0000 e83e2a1d8407c07ff718d5f0f2e2b77711800ed4 2003-10-25 18:13:12 +0000 b0ec4accf2de8e640ab064ecfce7db56d8ffe2e1 2003-10-25 18:11:59 +0000 727c3dc038174a48469b1cbf75bd9097e6f67edf 2003-10-25 18:10:15 +0000 129d322d93f0e2a8c1f8c8e2866be05aa33e44d0 2003-10-25 18:08:53 +0000 5a66fe97e5b36e1003391c6dbceacbffe8ebc8b9 2003-10-25 18:02:52 +0000 2260fa244b0a5d265ce7e7c394d5d053daa26bf5 2003-10-12 20:22:23 +0000 591c44abd2f510a3dc397a29013fa1d0394a7edf 2003-10-10 18:23:39 +0000 faf8506525cbf3f5e17f8136222adf055ce91f0d 2003-10-10 18:23:19 +0000 8261bd253d3b878396ca796b756ea619c76c9bab 2003-10-10 18:11:28 +0000 50e0374029d026026539294d9c5c0b3072bed9cc 2003-10-10 18:10:20 +0000 22bc6b736d460f8ad09576b5f1c2061f2af19756 2003-10-10 18:09:28 +0000 164839dde7f2ebe4d691504f2d94d57fffe7efdc 2003-10-10 17:57:16 +0000 c7356b4ce8b797c444d99d7f9d2f690eee6632a8 2003-10-10 17:56:55 +0000 b048a9b73b71f887d80edb506be42f84963146e6 2003-10-10 17:50:01 +0000 792950c3fe8cb57c1126f38a462264af9f335bae 2003-10-10 17:48:07 +0000 79889b5fdea8eaf8c9c860de2ba883462c15e4b8 2003-10-10 17:47:23 +0000 9d7d43653e5015a11bd850e42e779b359779b9f7 2003-10-06 20:03:56 +0000 d35d2723108e000d5f2b503775bffc7ed3b319f6 2003-10-05 18:54:38 +0000 f3eb78fa2b45711268ff2f33f8e4d2e27a684ade 2003-10-05 18:45:53 +0000 1beb2a18ea274401213eb01f1c711ba6fe9ec1a0 2003-10-05 18:44:34 +0000 e90f7c4461cac5ea7165f4adcec3e65218618d7e 2003-10-05 18:26:07 +0000 e21f06e96ce91a344afaa921ba28f03379a5881a 2003-10-05 18:25:39 +0000 acd8ef06d3680cfcf8604435a30ced750356b287 2003-10-05 18:24:58 +0000 7ae5db4509421bcfce9f1880ab8919fe23fe9e13 2003-10-05 17:57:28 +0000 3ea62ca744e5d8ecba3ff7792bacf7bf285388ec 2003-10-05 17:55:57 +0000 46fc6d18d740168553c23db1d87ef8af8bf0952c 2003-10-05 17:11:21 +0000 35170b8e07316f86204ebac38df74e4144f83328 2003-10-05 17:10:39 +0000 e2059e966cc9e794787e64861fabb965a3d01e44 2003-10-05 17:09:59 +0000 fdb50cfc99fbd42c54c5cc6b1c72dd9e8af64385 2003-10-05 15:53:52 +0000 01ebfa201b31e6e45d3655ca950f185a9e17bb27 2003-10-05 15:51:23 +0000 c62dd6c7554294d8369190a1ec0cff496c3f9c93 2003-10-05 15:51:08 +0000 061e8bc2ef29582372a02e82c0dc59515667a231 2003-10-05 15:50:28 +0000 41976f85d49239f0bd700495d49ca97feb0822be 2003-10-04 21:50:13 +0000 30889994141704ce6902d95c7200e46cfa7cda88 2003-10-04 21:48:03 +0000 66cc35e5febb00c50b6e9c04a84b26eec9c71248 2003-10-04 21:47:28 +0000 71f5b6a022b00f25de6036c78fd9331f9aa67fde 2003-10-04 21:46:25 +0000 5484d86d58b2db59fcdee501122aaf8f5559061a 2003-10-04 21:45:37 +0000 093a23541597b893b56275694b7ae0dae0cb0a29 2003-10-04 21:45:06 +0000 383e73d8af08b381e8a43643e0f87a3189a17c29 2003-10-04 21:09:12 +0000 17c05e246710ce8ff756c8fce2f072410e0636de 2003-10-04 20:53:04 +0000 714fa6fb3877521a793ff2000d0c3f41bbabe527 2003-10-04 20:49:08 +0000 536609ffc1f03817035706a76c8ecf1f1f9ac06d 2003-10-04 20:47:06 +0000 9cc3c6a68186eebfff192243bacfd48776b3d4d3 2003-10-04 20:16:45 +0000 4ebca1ed229b813f8247c031c6e5acd497549eba 2003-10-04 20:00:03 +0000 8e552cfaedba4f15b691cdc108b509dd813ede05 2003-10-04 19:55:16 +0000 470ce6f7261c3932d6c2dfa44eef69f97412535e 2003-10-04 19:51:48 +0000 9ddf33896d854d8abc42dd0ea4b42c86cd234fc4 2003-10-04 19:29:32 +0000 3227acca32db09d3418837d9eb9e843c1973294f 2003-10-04 18:59:55 +0000 92d9f18cfbdefa988dc44519143fbbe63e71553e 2003-10-04 18:59:29 +0000 f28a33bbcbf41e2806c59de7538535adfafbf69b 2003-10-04 18:58:37 +0000 8e8d8da3f55e5bd2747b01e85aeb04d97fd8e7bd 2003-10-04 18:19:03 +0000 2d4e2d03bcd7eea5ea0f8114be25306fdf5e913a 2003-10-04 18:01:55 +0000 61d7fbab9c83e1d6b21a5e3d18ac5c2aa2527b36 2003-10-04 18:01:35 +0000 3582c40e47ec9e89d15da7debd472ddd7d7fc975 2003-10-04 18:01:07 +0000 1003210a8a0919ac90299af0377bfa5cc456838c 2003-10-04 17:54:17 +0000 c3d706404264df60bcc4a93edc13f9fad8a6dc2c 2003-10-04 17:37:14 +0000 23bb33459aaacaca15daf3bdd035f50b01f0e94e 2003-10-04 17:27:21 +0000 4ad95eeaa48922727967e957ad3e6e883f8687b4 2003-10-04 17:24:47 +0000 4c5bf5000109ed3a3f5272a837f31f9f870c6bbd 2003-10-04 14:43:44 +0000 17d8b7886f6a7574f98d597c06f58d5e52549975 2003-10-04 14:39:23 +0000 c8cffb88a618556c23793c446752b6c0f0c19e48 2003-10-04 14:38:01 +0000 cbe9692aae1d8c0b03882f7fdc989a1a488f770b 2003-10-04 14:35:04 +0000 5e7435fcb1a82ce644c91126fd8ec008bfec50bb 2003-10-04 14:33:44 +0000 9270434122d84a8ef4fee9fb420503854d8d82bf 2003-10-04 14:07:53 +0000 4433cd26f4a0df01781fd704d83db1e1d4ad9141 2003-08-11 20:35:30 +0000 ea31bbaf0a9413243aee16f758dfe5a925d0f98b 2003-08-11 20:32:17 +0000 3caaa78984fe7be6a8059f6a10a677373f3cbc72 2003-08-11 20:21:16 +0000 98b07ab54ac5dadf47fe37a2ea3bf81eb2055424 2003-08-11 20:03:41 +0000 6d95d1504d7ce9de421859c229c9ad99c0cdc7ae 2003-08-11 20:01:23 +0000 0129e681666a8aae462ffa8ac200d95a5229acbc 2003-08-11 19:53:24 +0000 aba8cd2be79943ff34f9b1556b483c27d0ab28a6 2003-08-10 21:41:36 +0000 7f9e73b285bde6531721ddaa66ef5f833e05fff1 2003-06-09 19:37:56 +0000 fbb3338dad0bf5639055e05a44ae9dbf7f382869 2003-06-09 19:33:00 +0000 6d04552a02f569d46d9252d9739939c8ff9bf841 2003-06-09 19:31:11 +0000 3d626f53d3d577491fa5076c42cb7cfac7469426 2003-06-09 19:25:11 +0000 cc0d71dd981685a4530abf8e0dbdde4254d0b7f8 2003-06-09 19:17:15 +0000 bce4f4381c452e834e131a40107e5bcd30786f27 2003-06-09 19:14:10 +0000 6878be1d5d29457182b23b1f4350977e2894e338 2003-06-09 19:06:01 +0000 d2ae237facce3601d7996e5fb5a9c98e10429813 2003-06-09 19:05:12 +0000 9c74ec5a89231542a429f22f35c9d98e83b58ed5 2003-06-09 18:38:05 +0000 e23d37ea3df604af056640e121dceace45071f00 2003-06-09 18:29:32 +0000 dd2b20e3adefb4b4f9e60acfc7772553aadadf47 2003-06-09 18:19:01 +0000 95c3a80cf9aafb33a32618e826be11c80e76368a 2003-06-09 18:16:45 +0000 08e3ae022528f75e565cfc9ff3a7f5584907f173 2003-06-09 18:00:13 +0000 9a003fb96381b78b0568b75ccff878e534ecf378 2003-06-09 17:42:43 +0000 c8eff18364b0da9a83cb059a8fab1f1c2ebd5f0a 2003-06-09 17:23:51 +0000 dc396d8916ada56af5de8c96c569132a1e1e4094 2003-06-09 17:22:57 +0000 d22cf502fc5940c488c8a884694c92d0bcd22f42 2003-06-09 16:53:22 +0000 3ed8071e2cf697d08c0594c9057ab3eeb1646160 2003-06-09 16:52:57 +0000 f0ddb24658372079e45cf0afdfe1880d89cbd1cd 2003-06-09 16:45:56 +0000 8d2faba5665710219a0d9a01e2f361451fa14b5c 2003-06-09 16:04:49 +0000 c9da3222729ca837737f3a46eb1290d3670fed4c 2003-06-09 16:00:38 +0000 9d962ecd0165568cddbff4091630a38c5ff0a3f2 2003-06-09 15:56:59 +0000 750a98dc1bcafc688734df1df13ac6336cb08697 2003-06-09 15:53:50 +0000 a74bf236a1272f4004a5b1f5c9647f6e4a511dbf 2003-06-09 15:52:02 +0000 0e7b867c438caf962ed52f696ba1cd4c5586e246 2003-06-09 15:50:47 +0000 46727459df70aaa284a94815187dc78c92970b18 2003-06-09 12:47:29 +0000 50610c4fe9c1d999e3a3a421b0fe94438aa7c719 2003-06-09 12:34:09 +0000 6ed30824931a202581e2edb8b71a3bc46e3482ac 2003-06-09 12:32:00 +0000 b3a43bd4f43ffb1d401a884eab21d437e76d0337 2003-06-09 12:26:28 +0000 4e3d026a684f9e622d7b6578ee943dd145ef986d 2003-06-08 19:47:13 +0000 1e32e104d16e19ffa24d00382ac93864416de796 2003-06-08 19:44:32 +0000 e2e675905009cf18684c41f20693bdb4c4eca55e 2003-06-08 19:42:06 +0000 d83a8dac3ec02dfed64585fa8b74ee9b9547f00b 2003-06-08 19:05:38 +0000 2176478ec51b1dc92bfa89a1dab387ddcf2cf921 2003-03-16 19:21:26 +0000 35654277037b253ec6b50b90d11e953b904b4737 2003-03-16 19:20:54 +0000 08a6afa66db6aa0e0bb489d91ed3fff7695bf859 2003-03-16 19:13:36 +0000 e7cc8eef33e360657a34373e4f021f7a5fe3dfd9 2003-03-16 19:11:15 +0000 a46eb97d67d6ef517fd4023dfb17d2bc27863a34 2003-03-16 19:08:30 +0000 19d44575b44b9d30270628c89959e58fe7eb83b4 2003-03-16 19:07:50 +0000 4a4226e4816b5d07c99dba12bd3dd63b50897afa 2003-03-16 18:54:47 +0000 be2f0dd34256aacb4bf1d4f9c0a168ed92700fc0 2003-03-16 18:53:00 +0000 5f59d5887a127ede6060e7d1c16716692a02a8f4 2003-03-16 18:52:10 +0000 b5238d6f6cbca6c697b6446c21029399b92f7ce0 2003-03-16 18:46:34 +0000 2fd0c23e8d47c7bd73f1279787076ae5ce29719d 2003-03-16 18:41:36 +0000 786c5551fea4f0be3edf9837f2a4a04a084b00d3 2003-03-16 18:40:47 +0000 facd7aef0a46b667caea90cd6f658fbaeb9323d7 2003-03-16 18:39:33 +0000 ccce88e4580a57fc192128776ede41c432b9aafe 2003-03-16 18:39:07 +0000 d63ebb7467399e2df2e1b1812e12d4c0cecba56c 2003-03-15 23:03:23 +0000 25f25a30e2735433f741976d9d6400447977c544 2003-03-15 22:44:00 +0000 35fd28e0f92258591c41a7791e5c90116fe18383 2003-03-15 22:35:37 +0000 db82af12e51df682de4fc581237de2dc2269dba4 2003-03-15 22:22:11 +0000 47c377feae14a8391b7a55aad73add3edb40e229 2003-03-15 22:05:51 +0000 81c21063a2ed35b453e4611ba1442d178f182ae6 2003-03-15 21:57:39 +0000 12933251c776333fbb6060ade81fbb8b75ecbc63 2003-03-15 21:54:05 +0000 98ef22f7fa0fe491e6972f236f2bee5c50065571 2003-03-15 21:40:05 +0000 c90448f03e3eba78939a9c7a76adcbed10051524 2003-03-15 21:38:51 +0000 b643576b060ea6c832e541f37e992c1e48005d20 2003-03-15 21:22:31 +0000 3a9a19cb3c42819f55faa50bd34cf275587d18a0 2003-03-15 21:21:37 +0000 f451a3444ce78102aa581283d6a5b361b179b8c3 2003-03-15 18:57:22 +0000 4a1c5a4a1df914e92e8c85f36da0805634af290a 2003-03-15 18:55:45 +0000 a35d5bbc60fc89b22d3088a7c0da28a77cb02f9b 2003-03-15 18:52:17 +0000 0a6ca7e9b48c90eaff39b17b86371788a89a782d 2003-03-15 18:50:17 +0000 750f60e5b30c4a1d943a830d521a8d82cec895ab 2003-03-15 18:46:17 +0000 d09d7e35708f4d3ed0d5151a636e05ab772264e1 2003-03-15 18:42:20 +0000 708851b52b8ca7c8881b82c4faebdc14bac715e3 2003-01-26 18:16:57 +0000 2656c475f7b96bd23fc8bfdc4ef98d7ad4d8f0cf 2002-10-22 21:14:21 +0000 1508705f0699722d9265fd4a044b67c9970f2284 2002-10-22 21:07:53 +0000 8ba64e4f1355987e1b9bb3c1d0c8b5f309e59eaa 2002-10-22 21:01:30 +0000 fb628964d3ffeb28f293ab7c214ac5f489289fb5 2002-10-22 20:58:17 +0000 2b743bd9e004c3a379c6a1c06aa10184baf6744b 2002-05-29 11:50:17 +0000 5d41c8ba46718fba261899c0bf40459cfa41ca0a 2002-05-29 11:42:12 +0000 ffe4f5d57cb8109697843cb8628565982d4dc67c 2002-05-26 23:57:13 +0000 76cf4993397501ecfc5b8b81b2346dd7b67999f9 2002-05-26 23:55:41 +0000 acb103fa156eb3669118352ac358eb09a65db4ab 2002-05-26 23:44:08 +0000 53b5dc1d8ad21e30fd8f624278ed9e49afd62ee4 2002-04-20 23:05:59 +0000 dd0ed0f2bc618cc78bf43b1d51b2bd8564317ea1 2002-04-07 21:36:04 +0000 f6b8cf7939608aa57821bd6773d222fe26b919dd 2002-04-03 22:05:59 +0000 13ad0ab7a61b1822a08dceeb88acd557366a24ec 2002-04-03 21:57:21 +0000 5b34973f483943860467af7b2cdfbdb5f3d21981 2002-04-03 21:50:24 +0000 98b7c273ac9a343380789fc42d35af3fc6d8de07 2002-04-03 21:49:42 +0000 ee43d8e461ad0a96075dbe296a4a27edb64fc809 2002-04-03 21:47:41 +0000 0cef057ba2fead019a5b5ff681e8126021de7e50 2002-04-02 09:10:51 +0000 ea7599467a5eb3b449f75b4742153d5950f54e3f 2002-04-01 20:37:45 +0000 c3305abf87c6335004f1404695d1ba9f1eb95296 2002-04-01 20:32:29 +0000 3064e2d146006cd236c2fdd4a7ae1c9389e57441 2002-04-01 20:27:15 +0000 f87c233da2e2fc51cc446b6103508d88d966acca 2002-04-01 19:48:30 +0000 ec01d37e6274228335e16edabbd4a0e2c93e7002 2002-04-01 19:22:27 +0000 d126fdd3c41f630969be2fd1eff15c4807b90665 2002-04-01 19:18:20 +0000 05a9c11b09f09618b792cfdee03af171023e06af 2002-04-01 19:15:50 +0000 7db13c05eb551a6e2e84f2b5004d48c5bfd63cb5 2002-04-01 19:13:56 +0000 0dcf7c7ab6cd1bee504cb2386fe0432089710371 2002-04-01 19:02:07 +0000 483efaf39fd85a26943bfb9bd1b1d6652cbeb061 2002-04-01 18:29:30 +0000 5137e56d221e5e452df7d87fec65f75d5952849a 2002-04-01 17:41:55 +0000 101a4a8af7eb75f9157464b63607268c2ccc2005 2002-04-01 16:54:56 +0000 f7985e42e1414e79cf49bdf799b6a64307c6d952 2002-04-01 16:54:35 +0000 be9dc299451987da4454c84a4b03021cfeb33e07 2002-04-01 16:26:16 +0000 7f353e0700691952a5951c664f6f2bb46869b9c2 2002-04-01 16:25:38 +0000 7c689fa7a011005befa2c6ddd840f7eb7b65354f 2002-04-01 16:24:05 +0000 86f121f92fe7f4bbc6d5f80a934e095d9de09cc7 2002-04-01 16:22:57 +0000 ec3897c405f0775c9f849a366b6615a5e5688133 2002-04-01 15:07:39 +0000 729930a85e1030f73c2fd5fc208fb555ac1dfbfb 2002-04-01 15:06:36 +0000 682470e95b0dc60759ad69bad9c9fbd87d1c8283 2002-04-01 15:05:55 +0000 bfbee4ebb7afc125fc124b7cbadb71a345c6dc1c 2002-04-01 14:31:38 +0000 fabfa4c6a979690d1e93ab626475c49154bd9448 2002-04-01 14:14:16 +0000 d4cdcb260a6fdc55c3f868bcdbe6303eb1f0d9d1 2002-04-01 13:58:28 +0000 3b5725c546660731ef4d28f937c236be0b7ce2bd 2002-04-01 13:56:33 +0000 59022307dcedd43cd9fd2bf4ca89da6938dead3b 2002-04-01 13:45:11 +0000 eedea5194180e484fed5a29563139ae9292a99d3 2002-04-01 13:40:05 +0000 341e36006df2ba7848acd0c63b8f787c7ce36faf 2002-04-01 13:05:04 +0000 3aceb1aff49b4aa83352b0282171e018e64561bd 2002-03-31 23:20:35 +0000 e595e8f9b3577bab27bc2be658007db69ea521ba 2002-03-31 23:19:01 +0000 72eb319067758e17ef8bc2e64c8b373daa3cefbb 2002-03-31 23:18:06 +0000 9cf6e93d4e257bc5892e8984c167aa1bf2435476 2002-03-31 23:17:26 +0000 2d0a9c86058ef022562aba0eb47761b2fb7613c6 2002-03-31 20:36:28 +0000 187a31931bc84bbed2fd49b839312a7197abece0 2002-03-30 22:57:47 +0000 a1e3ba1149887f3293326b5990195ac116e59d0e 2002-03-30 22:53:42 +0000 3f8d3b6486875f2569ebc1afd88fcbc8d0a84188 2002-03-30 22:48:53 +0000 e9f51d51abdb8e298d17cd75a4b6ebcf37352350 2002-03-30 22:46:43 +0000 09a0591ae7a25e6581a4c161f7940b03b5a398d8 2002-03-30 22:44:41 +0000 29bbad2bf2ec7b2cc79191bdabcd95a6c231453d 2002-03-30 22:38:12 +0000 a99b37d1ec0c7c99bcc52ef0ee76ca9a1fda5a26 2002-03-30 22:36:31 +0000 5a84bdf9462aceeb2710e574bf7a0327337f1580 2002-03-30 22:18:20 +0000 0b3af43fef054e46f4bd6bfc55c1623cf7496b66 2002-03-30 22:14:53 +0000 6a29d58953f3577019bbbc5ea76d91e0a35bf12f 2002-03-30 22:11:59 +0000 b7b45dfca1086e83d8e364cc257917af73ce3cf3 2002-03-30 22:08:45 +0000 dc8799e62b81abee3541966806e13920669d8dda 2002-03-30 21:54:38 +0000 155dd5597bf9be8edf2e0a1e59ca7e146467d409 2002-03-30 21:50:25 +0000 b91fa79041ff15a94a614dea2c87cdf7ff6d2950 2002-03-30 21:22:24 +0000 e13dc87190a22945b1b4b3329868b5cfc0fb373d 2002-03-30 21:20:28 +0000 39b1e5b20d85e148d9dce26be39ece556fe04d26 2002-03-30 18:56:56 +0000 c7f9d26275a98d930a7419db2a3412dec5092071 2002-03-30 18:46:59 +0000 e494479a2389e4a85a3e0262e47462dfde6d9bd9 2002-03-30 18:45:55 +0000 6c88b25d74f05c7b1c6a2cd640a49d0e5ba2892e 2002-03-30 18:40:23 +0000 a5db44c85f8f2d4a8cf579bfbb435c4c54a839d8 2002-03-30 18:38:23 +0000 36bf79a72fd711ca3299dd912023871d88eff5b3 2002-03-30 18:36:39 +0000 6bc333bac4a276fca99fc43fb9647db9074b6755 2002-03-30 18:29:23 +0000 c93b6ebe2a13bee3815d7efcccfa07cd877db7ad 2002-03-30 18:13:47 +0000 bf32b282b179fa745b0bc083e3a5b041bb333584 2002-03-30 17:51:53 +0000 c44cfd8ec246ad0af15a4d9b524860e31ececed6 2002-03-30 17:44:11 +0000 8c79ded65e6db15cff2f3ea7b280cc33601e4c26 2002-03-30 17:40:03 +0000 769da8c1b1d0ee5d844882b2796c292287c0ad14 2002-03-30 17:37:29 +0000 00c5a13f3daca65cbc10fc7e7a938df678bf9844 2002-03-30 16:50:08 +0000 dd17b9bacde255e44e26a73a40d3b4df86b8ca1e 2002-03-30 16:48:07 +0000 e421de0ba96c8eabc9ee45bb66212f15f118cfaf 2002-03-30 15:36:25 +0000 bfb163f7c1084a0b38216c889212aba390233e60 2002-03-30 15:31:30 +0000 15a844bea79d34dc8d1907c644d01815ac7eb3c8 2002-03-30 15:28:35 +0000 58fb1d1d5cf45e62d05ef1f7e97db45fdf77703f 2002-03-30 15:24:45 +0000 f934116c17d2e669c390f7cd76730f2b9dcc1f34 2002-03-30 15:11:29 +0000 ab747afea607b84da6128da69fe0a585273dcede 2002-03-30 15:10:27 +0000 2d0704f7ba6d9c362876138b6957f62e48639615 2002-03-30 14:53:01 +0000 8e56806c71c9612a5182b54fa2250bf2651ad2fc 2002-03-30 14:49:17 +0000 fe01e2d9002394a9f73effbf10baf6d56d503395 2002-03-29 23:18:10 +0000 b9c8ac418eb49c4c262af5f2c170fcb39d2cb8a6 2002-03-29 23:16:32 +0000 8f5d218bfb006ac7ee72e49f8cad9c9c45a8e7be 2002-03-29 23:04:52 +0000 4594bd6890484fe1dc54ed9fd3ad7c195d5944d6 2002-03-29 23:03:13 +0000 26e12b6c2439e808766b94820d8e9dde33e828c8 2002-03-29 21:52:43 +0000 41a727742a352be5e748a31ee4fce0cddb304eac 2002-03-29 21:51:52 +0000 097d9023c90c623d24b30f14e6da3f037191f87a 2002-03-29 21:50:41 +0000 e3853acfe846a84402838173c793b7f86232ca5f 2002-03-29 21:47:11 +0000 68bd697a21e8fe8fc462cd59b7e0cfc84e90cd93 2002-03-29 18:50:33 +0000 d3122fd706c628059dbe5e43644535e5d79c6945 2002-03-29 18:34:38 +0000 f871e3b99d8cb289d3a7aa8df09c3e5f1247fb67 2002-03-29 18:33:55 +0000 7387a0dff36e9986958d8db46ecda8c85c47fe4d 2002-03-29 18:19:24 +0000 98775c816490ce2cb7d72920f4ea9606f8b3427f 2002-03-29 18:18:27 +0000 f449ab3cfcd5a7002e97dd3766e4c37fb20c52bf 2002-03-29 18:17:14 +0000 cbf826253db10d742d701a9d60a335143cb1d69a 2002-03-29 16:31:20 +0000 41cf7f67f477013b943e3b16dedae65df01f8836 2002-03-29 16:29:26 +0000 2c6e468ed2c1dc315438cffaf6720afc5e15f55e 2002-03-29 16:28:54 +0000 0360a2ad995e913b75829e4e9966a395dafb0448 2002-03-29 16:23:08 +0000 ecfba17d7a1674d482e037ea86b33646483c7813 2002-03-29 16:12:26 +0000 062dbd67b961ee704d6e1d44b0bf66404f8345ca 2002-02-12 22:36:51 +0000 eef4819a5b5a08826b6dd4f71d99ea38abbe3b2d 2002-02-12 22:22:27 +0000 98c9873892cfc42b15597b3febe89c4441eb8ed1 2002-02-12 22:00:02 +0000 aa32b5b51d67b1fc5e5a4716e0269757c56ca010 2002-02-12 21:33:01 +0000 d755191096ca7f4c9aedc37d0f529fffebcf8352 2002-02-06 23:48:34 +0000 70486df5a4f9edc43dc25c146debd8ca2c4ac78d 2002-02-06 23:39:02 +0000 adb8324374a1f3312bbf9004f3bbcf68d35ad2e6 2002-01-30 20:18:38 +0000 9d81148af112b5d2fa6f12c7ac1255e744bc9136 2002-01-29 16:02:34 +0000 9ea037a733630301db49f74f35a97db53eae635c 2002-01-28 18:22:12 +0000 ae1fdb46455f80dcd39c34b7e68a89a1acf85b34 2002-01-28 17:51:24 +0000 88ffc1ef5e72c413b235e80e53b9415690d92324 2002-01-28 17:50:04 +0000 c5877f29bba9942bdf938531e81ddc7f098b970f 2002-01-28 16:53:50 +0000 6e0e591e7dfb5aed534cb4905ab8629d625ed636 2002-01-28 16:53:33 +0000 3c0f26113b32da4f02f22359ce2e366d0459e583 2002-01-28 16:49:29 +0000 2eed02e355b799eef882e0a49335fda352dc0d74 2002-01-28 01:06:46 +0000 ea0ab632cb7954adaf0479a15d56faf2255fdb59 2002-01-28 00:38:16 +0000 e8e54f4773a5f54529ac994d01c3af0733b243c9 2002-01-28 00:36:54 +0000 7ddacbab3ac80fb131ebc8d0d2820a337b272fbd 2002-01-27 23:13:16 +0000 7d3a0f2f8de80fd091b6ade4ff176fb065f7b339 2002-01-27 23:12:08 +0000 7c8d09e1b52ac297b229b84dd2559999f67bca42 2002-01-27 20:59:48 +0000 699609897df7e3206b7a4915204a227874ba53f6 2002-01-27 20:40:03 +0000 7ae5e04212e71194c060b2423f2780a26bc6b28c 2002-01-27 20:37:14 +0000 a6b0e34386d8ff0076532da3fed1d594d689dc73 2002-01-27 20:34:30 +0000 1483dcbf2baa68fe86f3205be4ab9c9a4f2a2618 2002-01-27 20:27:11 +0000 d2e217d0ecb0665f993a708585b105a550d9e32d 2002-01-27 20:25:20 +0000 8db84fb0eda588c4b967bf9042bd0697a9c90a9b 2002-01-27 20:05:53 +0000 51bb846cb43cd376ecbdd8219de90cda08c0f915 2002-01-27 19:56:23 +0000 b056667a71f096184a324633e6e4fb023df71cba 2002-01-27 19:54:41 +0000 41cc26e881e2f970fd8b9b093ddd3490d6075405 2002-01-27 18:42:24 +0000 005cf40f1582dbf64008723497d8fee82c64aeeb 2002-01-27 18:35:33 +0000 6184fadef5b13d344cb05b8d836d0fa8dacb2625 2002-01-27 18:22:52 +0000 63258df62aaabbbe2578602c08aac80af28ee1d2 2002-01-27 18:05:29 +0000 dd63cf30820f3908b7f2f2239be7640fcc7793f5 2002-01-26 22:32:53 +0000 0fcf958f54a5231aab84bf2b8bd48a17ecfdec6a 2002-01-26 21:55:36 +0000 ed1e929bec35eb2667d310b07bf1fca4cd96e690 2002-01-26 21:48:32 +0000 7c26c548d0497cf3bc5a6af60a602cf852e331b1 2002-01-26 21:16:45 +0000 c8b7e388af9d3196772eb8791abbfd871d242fbd 2002-01-26 21:08:31 +0000 a2176b1abd622f6b084c6daf8fb1ef01258f7d81 2002-01-26 20:38:09 +0000 949638dc946bfcaf1b6979ed02842ee7c9f4c511 2002-01-23 18:09:20 +0000 5839d058a68b51ceb40ff087e8760843ecd15b12 2002-01-23 16:04:14 +0000 e45c6d8ec1c52b8c70b598107f040eb6a0c2f7bc 2002-01-23 14:09:09 +0000 de4e5f0c42610ce91e44011cf9ceda916831acec 2002-01-23 13:26:11 +0000 7b0bf4289cd430b197a072494bd5dda75591755e 2002-01-23 13:25:54 +0000 3501f764c68b3234b323a63889c98d6fe1e5a7d6 2002-01-23 12:44:44 +0000 fc2868f546c7b1cc2bb588bbdb519eeaab636cf4 2002-01-23 12:39:53 +0000 44f44613ce3c212b594ca0c9f1a4d94a8007a740 2002-01-23 12:35:24 +0000 877a4de76d5792fded365591f6ef964eb5468c3b 2002-01-23 12:28:12 +0000 1a5cb4b13ec084b25727d8a6991bc7e390932251 2002-01-23 12:24:27 +0000 ceafbbd0d966708b4640dcc16f591ce4858e221a 2002-01-22 22:11:06 +0000 df9bc1688d3b364d5d80817e9abd9c939bddf8ba 2002-01-22 21:35:10 +0000 390240752146735ff43bcc05ee060fc0272e5055 2002-01-22 21:34:41 +0000 bb30f16bc2920c6f60c8c29c95acc3858351864c 2002-01-22 20:19:30 +0000 1ae56d8bf688d5c1f5e5e168f05043c1c78d8cd9 2002-01-22 00:44:26 +0000 2990fbe90cb48098e1b34192ee21b963f3f4a893 2002-01-21 23:16:28 +0000 d1af608b83af40fd5a03ebd747c2fae8450be9ce 2002-01-21 23:01:59 +0000 2a549bc51338bdc23948aaac24f9edbec8b0210e 2002-01-21 22:35:37 +0000 1067f83345e532e202bf578d761f1bc112006994 2002-01-21 22:16:19 +0000 471ea3543cc1a89fa1d52260ab153813aeb69861 2002-01-21 16:26:39 +0000 c1fecd53a0d69cafdb47550922f75cb9c6ba2afe 2002-01-21 16:13:05 +0000 1d8f4a8b8365f33da491ff129400fe4216b14a08 2002-01-21 16:10:20 +0000 785db0c8c82120516347badd5fc71316917ff967 2002-01-21 16:04:48 +0000 7a38c94e7ec3a8eae4373b021a7ec5e2f8e50255 2002-01-21 15:49:47 +0000 5b0593978d32d81c9815dd2c51947d775dce000f 2002-01-21 00:49:00 +0000 bd415d6cd57dd626a8086b48afd4b18a20c76b8e 2002-01-20 23:40:30 +0000 9472cecb67eaf1073ae099f0e32db0baff7a6c1e 2002-01-20 23:01:49 +0000 ec6df2314c90c61969531a4cdd08584548b03729 2002-01-20 22:36:27 +0000 20727d3dbd8a21adce231249cd052a0ca6caa632 2002-01-20 20:31:46 +0000 e8ae5c342ba596c9f17912d959ee5df55e9e7fcf 2002-01-20 18:56:39 +0000 7853cbfdf0ba4a1095bc4d9fdac47e41e1867099 2002-01-20 18:53:25 +0000 cbdd9f5e344562dc233fb145dba184b0825522e5 2002-01-20 18:45:17 +0000 64b0d0f34c30109f408a7ae9567656ca45e69c8a 2002-01-20 18:44:39 +0000 19cecdc57278dbb032b9991c110e5e8110f598e2 2002-01-20 18:06:37 +0000 f02699500f6d30152d5c9ef1466a44da0b33e603 2002-01-20 18:06:01 +0000 f78ba2e9b9e940bdc4b6bd27ab574180756261de 2002-01-20 17:35:42 +0000 3247e8d647beed1f1d31b585e574b4f3337bb43b 2002-01-20 17:29:43 +0000 dfc5de826012032815c303c5d9b7ee7fbca84764 2002-01-20 01:40:06 +0000 cb595cd0f6919c19f102331ddc2459e5932effdd 2002-01-19 23:20:25 +0000 b07471eddfa8082e6148c80da1a0072f7c8d4294 2002-01-19 23:19:30 +0000 6ff804c63977be980cbe981333e202b8ed20d594 2002-01-19 23:05:35 +0000 6db3ac74d5cb62e85dd7764cc69e3714b682cf05 2002-01-19 22:37:06 +0000 c642b63c6d2e497ed002191719cd1e950306c3f9 2002-01-19 22:28:18 +0000 c38882f95e2de69e2ff34f722f9495a45a55855c 2002-01-19 21:51:54 +0000 0f1271bb8cd2eeaf3a9c7bc0352c24fa9e53d09a 2002-01-19 21:50:44 +0000 1ade7827a3a88c6deaeb15e4d18e32308ba6f95a 2002-01-19 21:36:04 +0000 fdee1359cc9ef06ee23947f593aaa0a398d16944 2002-01-19 21:33:52 +0000 891ae48259fc44a1305097324184110fad2e0431 2002-01-19 20:56:32 +0000 881d14bb810e0c19967f5f5d115bd0c5951ae723 2002-01-13 18:34:17 +0000 6ee74d943d3bd2516ed378a025b70e686e13f88e 2002-01-13 18:03:11 +0000 19332298e7c2c01b03fdb3ad18e2a70a2a1eabdd 2002-01-13 14:11:21 +0000 5fc258d01fae93c3726ac535ce0a09dd06463673 2002-01-13 01:08:26 +0000 54ff764ee6d9ec13fbd3f935db5ee58a9d2465c5 2002-01-12 22:05:52 +0000 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642275243.4654572 ftputil-5.0.3/doc/hash_map.txt0000664000175000017500000051513300000000000015303 0ustar00schwaschwa# Mercurial hash, Git hash 9db916ed2259acaf93b0fd3226673ccc1f3f04da 54ff764ee6d9ec13fbd3f935db5ee58a9d2465c5 e93155993afc30db56ee9dce996b21c88db51513 5fc258d01fae93c3726ac535ce0a09dd06463673 834304b432688ab06f22b94c6e993f02719fa74b 19332298e7c2c01b03fdb3ad18e2a70a2a1eabdd 26f4388829214ea020c911595564c72abe664ba7 6ee74d943d3bd2516ed378a025b70e686e13f88e 8c490fb1ee4e7372198ee40bf008011b18afd020 881d14bb810e0c19967f5f5d115bd0c5951ae723 5880f30b0a9cd57ebc2991d0f0f1fd1dd3119835 891ae48259fc44a1305097324184110fad2e0431 4be21d2e461902dbb158e1846f927d2998b25d62 fdee1359cc9ef06ee23947f593aaa0a398d16944 7c63c553d2c5c51e9ac08e6086e5037f2f895b14 1ade7827a3a88c6deaeb15e4d18e32308ba6f95a 2c1667efde362dec6efc34d0aadade208d9337e2 0f1271bb8cd2eeaf3a9c7bc0352c24fa9e53d09a 625d8c4e7feaca3aeac0ffaa65418e3e4a9977af c38882f95e2de69e2ff34f722f9495a45a55855c 910a53e1f0a1e58d012949d6b486478d327f36fb c642b63c6d2e497ed002191719cd1e950306c3f9 70d391ee15a51dbf45364398b07334e814e95942 6db3ac74d5cb62e85dd7764cc69e3714b682cf05 fadd6cf83d9f917075470fd7275b226ad711ae4d 6ff804c63977be980cbe981333e202b8ed20d594 3e7d631aad3a64d52125677f3bc41e800561951a b07471eddfa8082e6148c80da1a0072f7c8d4294 e08352e2ad7e6196c69b236a3b9e502f85138af6 cb595cd0f6919c19f102331ddc2459e5932effdd 5a61737f3d98609af28a53c0f518be4d983c1aaf dfc5de826012032815c303c5d9b7ee7fbca84764 96792a7f21f9a44e020349f850aa70c8b1a26ac7 3247e8d647beed1f1d31b585e574b4f3337bb43b e61f5f5de95b876ff85ce69b80e8593abc4cce22 f78ba2e9b9e940bdc4b6bd27ab574180756261de adb98b60e012ac1f10db1a11e6a716f309b4484a f02699500f6d30152d5c9ef1466a44da0b33e603 1a308017f13a12769669021586cfe3f71351746e 19cecdc57278dbb032b9991c110e5e8110f598e2 63cb5572a0b8927dd8270608b4027412a6c2691f 64b0d0f34c30109f408a7ae9567656ca45e69c8a 67ef83b2d60d3b88451fbc9061a1b9de08c8d13a cbdd9f5e344562dc233fb145dba184b0825522e5 7e80c5defa38f085a089ce803096bd6fef9a2f31 7853cbfdf0ba4a1095bc4d9fdac47e41e1867099 627c1889e085e7f4366d22d9bc8523b6c7884f66 e8ae5c342ba596c9f17912d959ee5df55e9e7fcf b307973afc4d07997eb31d3920488441251e601f 20727d3dbd8a21adce231249cd052a0ca6caa632 6bdc2d713d9ec37693ec76a44b82a69d487e8400 ec6df2314c90c61969531a4cdd08584548b03729 ee38675205c26a154a7b68238730974c6848993c 9472cecb67eaf1073ae099f0e32db0baff7a6c1e d2a22a5e9b9fc5cb753414d8ca99b40911a066f9 bd415d6cd57dd626a8086b48afd4b18a20c76b8e 45455e40d4d18e310d3da0645ddeb9bc231a2e95 5b0593978d32d81c9815dd2c51947d775dce000f 8ece8c81a0cada6fbc69870c6608fd8169beab86 7a38c94e7ec3a8eae4373b021a7ec5e2f8e50255 232a35f6ba7abd3f365066e967abcc53ee7fc7af 785db0c8c82120516347badd5fc71316917ff967 67acc14f0d9d5f96507edeb65881bc4b3fb34d8c 1d8f4a8b8365f33da491ff129400fe4216b14a08 6b7ef120a57e01edbf394618345da8370df84e1d c1fecd53a0d69cafdb47550922f75cb9c6ba2afe a258732bb1675d7142c0e3f25e2107297f42c63b 471ea3543cc1a89fa1d52260ab153813aeb69861 d274c54611f4ffc908b769a4eba6a534672a4849 1067f83345e532e202bf578d761f1bc112006994 f7703507d4cbcc868498ec56b9bfa56716e2ab2e 2a549bc51338bdc23948aaac24f9edbec8b0210e 886b2a9281ef1bb404ea4309d09ee756f423cb9f d1af608b83af40fd5a03ebd747c2fae8450be9ce 53b9a482638ee85f2cb729510050a38a34dcb3ed 2990fbe90cb48098e1b34192ee21b963f3f4a893 e68b708ff4ac75f96fb81a7da57ead617d30b48a 1ae56d8bf688d5c1f5e5e168f05043c1c78d8cd9 8f793817276dbcfc25536dce396429298d236b75 bb30f16bc2920c6f60c8c29c95acc3858351864c ef12b53f131a7dc3665dea928c3536a563ba8138 390240752146735ff43bcc05ee060fc0272e5055 17abc311e8c971a88550861b758fc24f49a56004 df9bc1688d3b364d5d80817e9abd9c939bddf8ba d97334098af43d6e4c321424a97ae618a0178a88 ceafbbd0d966708b4640dcc16f591ce4858e221a e6b17eeae47be6f7780dc9d2ab289a02ae562816 1a5cb4b13ec084b25727d8a6991bc7e390932251 c439e01f88549c91c175ad40c45d47ad3761e0ee 877a4de76d5792fded365591f6ef964eb5468c3b e4f91515d3ae22aa43b79c84a85945ee91218d7c 44f44613ce3c212b594ca0c9f1a4d94a8007a740 0d0443d9b41f732b0e8be617fac62b6ba33e2b55 fc2868f546c7b1cc2bb588bbdb519eeaab636cf4 a2b37e8ac5adea07d52ee0871968f762e708bc7c 3501f764c68b3234b323a63889c98d6fe1e5a7d6 3bd327c8db8e83de9d442ff6ef30f8f6f75510bd 7b0bf4289cd430b197a072494bd5dda75591755e 80d94964ef0f231c4e0e5cbdcd430460685e73ef de4e5f0c42610ce91e44011cf9ceda916831acec fffd328bda86d8180a53c668c4c40034ef2ea5f1 e45c6d8ec1c52b8c70b598107f040eb6a0c2f7bc 2372a6c1206fd20537017456bc6f78c364bb2d47 5839d058a68b51ceb40ff087e8760843ecd15b12 55e459e80b782d94e3e77eea2f3710777470821d 949638dc946bfcaf1b6979ed02842ee7c9f4c511 0665575c8fe326fccefed9bf8715490d711a0c7c a2176b1abd622f6b084c6daf8fb1ef01258f7d81 e4ff75e5d51910ef3576bb704a52ad7141e93d87 c8b7e388af9d3196772eb8791abbfd871d242fbd b3198f220e121c1bac828caa8541ba56bea5bf41 7c26c548d0497cf3bc5a6af60a602cf852e331b1 b22285189805cb9547190b723107d6418c2c0ca8 ed1e929bec35eb2667d310b07bf1fca4cd96e690 c0187f8be3cad0d45a7b56260bb47a5e94e35191 0fcf958f54a5231aab84bf2b8bd48a17ecfdec6a 5d5ecd33bbb849a4313fcfc0ba7dd681c0533fa3 dd63cf30820f3908b7f2f2239be7640fcc7793f5 eb88ecbbabf65f60a20877ed5b20fd9a46f1d9ac 63258df62aaabbbe2578602c08aac80af28ee1d2 635267ae49b0e8a7f6f90f8743bdcaa4ad2faccc 6184fadef5b13d344cb05b8d836d0fa8dacb2625 42b3330f7da8b8853203ab937dbc0df4b45c261b 005cf40f1582dbf64008723497d8fee82c64aeeb 674fdaa3ed95b4febaa2201c5a54c8b4db8da8a5 41cc26e881e2f970fd8b9b093ddd3490d6075405 b713a25444c3575780ad58e737eafe95402ac49c b056667a71f096184a324633e6e4fb023df71cba afef405a8504e6cd10bfa44f39cd5cc3d90df8d2 51bb846cb43cd376ecbdd8219de90cda08c0f915 b7c61741289a70431dad0545a6356bf569fa66f8 8db84fb0eda588c4b967bf9042bd0697a9c90a9b fcbf8f2158f0b5c9f39723db5ec4799348b438e0 d2e217d0ecb0665f993a708585b105a550d9e32d b6cedb2b5e51938aa68d2c7e0eb6ba88d392e884 1483dcbf2baa68fe86f3205be4ab9c9a4f2a2618 dd44675e6517e38222935aaf51915626f669c73f a6b0e34386d8ff0076532da3fed1d594d689dc73 a306c74c0efef660e06fa96105df23849b33ae4d 7ae5e04212e71194c060b2423f2780a26bc6b28c 55db7cd5debc417486c0322c2d6ce8e3294a6176 699609897df7e3206b7a4915204a227874ba53f6 64dad0a42c7c22a082782b41ec59f4a4a4affb46 7c8d09e1b52ac297b229b84dd2559999f67bca42 748383d405fae5359b9c5f890ff588a9d3199271 7d3a0f2f8de80fd091b6ade4ff176fb065f7b339 4e82a8cc74e0356fe31774cfc892815d9fe8f584 7ddacbab3ac80fb131ebc8d0d2820a337b272fbd 55389f8fef361867dcfe3f58c5f0b1d5eda43923 e8e54f4773a5f54529ac994d01c3af0733b243c9 a71fcedcf52324101ec291c016e20d67bc230c5c ea0ab632cb7954adaf0479a15d56faf2255fdb59 6239271cb827e8af731f86738cd0871d4124f3dc 2eed02e355b799eef882e0a49335fda352dc0d74 c727e01ffc5505b8f28769ce93d7dffbfea162be 3c0f26113b32da4f02f22359ce2e366d0459e583 8ea4ee8def839cb15a11d3b89e920a650a29a8de 6e0e591e7dfb5aed534cb4905ab8629d625ed636 7a3e3aa0ce8b119ca72a9f44d63bb669c67fc16d c5877f29bba9942bdf938531e81ddc7f098b970f d6971cbed0670456d1133a7ab0d6c5466cd77452 88ffc1ef5e72c413b235e80e53b9415690d92324 69cb753ae8dbca7f3fe546d0e49280114e1e32a4 ae1fdb46455f80dcd39c34b7e68a89a1acf85b34 16a7531b3fe099c6250fdbf4aaa931a1ac62ae15 9ea037a733630301db49f74f35a97db53eae635c be6804debfbb91d9fb61aba904086ab2fe8d04c1 9d81148af112b5d2fa6f12c7ac1255e744bc9136 1401e72bb177b1d7fe4ea743db74838da6994a2e adb8324374a1f3312bbf9004f3bbcf68d35ad2e6 0d79a5127226d6d985ee1ec06f54e3fdc413932a 70486df5a4f9edc43dc25c146debd8ca2c4ac78d 8edc69613c5eec03869aeccfc45c8646f38f548e d755191096ca7f4c9aedc37d0f529fffebcf8352 c3ac3e4e38cd0c1f6658490fac2af4e1d1743586 aa32b5b51d67b1fc5e5a4716e0269757c56ca010 21c101725cbb2b12e3bb3c4d96981fc1f1b3fd58 98c9873892cfc42b15597b3febe89c4441eb8ed1 d9e1ee4f985762e46691bcf5e82ba3d4b645b121 eef4819a5b5a08826b6dd4f71d99ea38abbe3b2d c54e15c23c7c63ebe422ff2eba30debaab8a01e3 062dbd67b961ee704d6e1d44b0bf66404f8345ca 63f0df89bc7d5911c9422d7e0e20f3c1740b69a2 ecfba17d7a1674d482e037ea86b33646483c7813 9e95c1890c335a39d8525fbc7e6992cf3c8f7ecf 0360a2ad995e913b75829e4e9966a395dafb0448 507d3c41a2912e229f130cc6b32a059ba672e1d9 2c6e468ed2c1dc315438cffaf6720afc5e15f55e 1fb1c3cb848b5788a8cbfb540ea82da137ec8459 41cf7f67f477013b943e3b16dedae65df01f8836 e7a542807e564e441ef54a792ec00ecab25d2ea5 cbf826253db10d742d701a9d60a335143cb1d69a 6ef78c01c467985f2268d29a5995649d9319102c f449ab3cfcd5a7002e97dd3766e4c37fb20c52bf 2d9dd2410935c3410277b9f7c60dac6ac1acc5ff 98775c816490ce2cb7d72920f4ea9606f8b3427f 868081123ce07f627527bdd64fd2fcf4fdfac4fa 7387a0dff36e9986958d8db46ecda8c85c47fe4d 86a6feb8338de115d2d80e9df42320daf24b87b6 f871e3b99d8cb289d3a7aa8df09c3e5f1247fb67 c168f95b9eab125dfb94fb273f746191e4199361 d3122fd706c628059dbe5e43644535e5d79c6945 b01a3e4c03d194320d874d4937cb6ca83388192b 68bd697a21e8fe8fc462cd59b7e0cfc84e90cd93 6023174cb7a299158f6ad3d93b6a02777d6b20b0 e3853acfe846a84402838173c793b7f86232ca5f 6b8985f5b13984b463676a3b450e8df7f3d85d8a 097d9023c90c623d24b30f14e6da3f037191f87a 22d6f4d364052ce3d8ba576917d8df562ec4163e 41a727742a352be5e748a31ee4fce0cddb304eac b4dfe4880a8613a0fa38f762849a848ff16669a8 26e12b6c2439e808766b94820d8e9dde33e828c8 69b7bbd0660ef249a529564506e9bfda30bb13ea 4594bd6890484fe1dc54ed9fd3ad7c195d5944d6 707ecf45c4fcbdc3ca0523b5c7766e9280d38667 8f5d218bfb006ac7ee72e49f8cad9c9c45a8e7be 42607e69afec73881ce516ec91ce14669366911c b9c8ac418eb49c4c262af5f2c170fcb39d2cb8a6 b5a396d460260fcb9a372986cc6cbacd600f6de4 fe01e2d9002394a9f73effbf10baf6d56d503395 6ea91ee6ff3866499e444ab216d830e80b1b64ce 8e56806c71c9612a5182b54fa2250bf2651ad2fc bb4d77bc1ce2d82c106efb87d02c7eba23683445 2d0704f7ba6d9c362876138b6957f62e48639615 d939ecb1d2d84f89aecb5c3954b620fd3d05ec91 ab747afea607b84da6128da69fe0a585273dcede 79c99882db3014b5d6e68a2cfa52d7f677f6b837 f934116c17d2e669c390f7cd76730f2b9dcc1f34 3e2596186b74ab54e24877b63434c41f3ae20c4c 58fb1d1d5cf45e62d05ef1f7e97db45fdf77703f 6e4e619eba0e075b1707cecc7d4977f5d2648dcd 15a844bea79d34dc8d1907c644d01815ac7eb3c8 165cd62a8148d305030a3ed1f9bca7edba0fc7c3 bfb163f7c1084a0b38216c889212aba390233e60 1990d77f340f421f8818a115c3876ac7c37a3783 e421de0ba96c8eabc9ee45bb66212f15f118cfaf 954d34b033513a218fe2bcc5fe20f4cfbe90c509 dd17b9bacde255e44e26a73a40d3b4df86b8ca1e eb6a93d64ce597f0bc92d626c11a6f5389184717 00c5a13f3daca65cbc10fc7e7a938df678bf9844 8fe742211bc86bc04ea6c9ab1d0edd58bebcb31c 769da8c1b1d0ee5d844882b2796c292287c0ad14 63e3ebde7bd130c5a0c4e25fd94d7eaacc60ae77 8c79ded65e6db15cff2f3ea7b280cc33601e4c26 c36b3f4f97e621f270f1adbc8d2c8863390db3a8 c44cfd8ec246ad0af15a4d9b524860e31ececed6 2ef8d9c96b03e2a2cd588b46f90937f9113fd6e5 bf32b282b179fa745b0bc083e3a5b041bb333584 1b6121d007a5bf55f917bef7e9865edeb9b1d42b c93b6ebe2a13bee3815d7efcccfa07cd877db7ad 5612e72b89c85d1f1345a02b5371fdd8d198c5d1 6bc333bac4a276fca99fc43fb9647db9074b6755 1761ce206ad349d18b92de2219b0392e7587fec9 36bf79a72fd711ca3299dd912023871d88eff5b3 b3b1aae8d0953b451be528cc4e031d8739379b11 a5db44c85f8f2d4a8cf579bfbb435c4c54a839d8 8a497e659b8bbc7cba0dc572ad190466f2686a9c 6c88b25d74f05c7b1c6a2cd640a49d0e5ba2892e d2602b98fd78336821237e8139715ec89f081f82 e494479a2389e4a85a3e0262e47462dfde6d9bd9 88cc06dc4f8c15c7e57030011ff680979aa8a1f6 c7f9d26275a98d930a7419db2a3412dec5092071 4667403ffb5931c37e11603aab6c5abfc7e81c55 39b1e5b20d85e148d9dce26be39ece556fe04d26 c2a46c957a00fe58599893fde72fd823105e5535 e13dc87190a22945b1b4b3329868b5cfc0fb373d c955585f2fde7af6887fbc5667cee93efa6e16c0 b91fa79041ff15a94a614dea2c87cdf7ff6d2950 8498db577333c2a0400352425c7d7d056e0fc904 155dd5597bf9be8edf2e0a1e59ca7e146467d409 8b964abba1db20ba1841733b3097bf8799354cb2 dc8799e62b81abee3541966806e13920669d8dda 4367105bc97b63e80e04710bd94812fe60e87e22 b7b45dfca1086e83d8e364cc257917af73ce3cf3 fded3cf504d8c90f58e5d8a2c22ceb91b709f9ce 6a29d58953f3577019bbbc5ea76d91e0a35bf12f 046b754c68042bb30bae045c4cd2b391c2f55665 0b3af43fef054e46f4bd6bfc55c1623cf7496b66 b9a01a1c2c200d7f09c6211c48e493b8d9e5c34f 5a84bdf9462aceeb2710e574bf7a0327337f1580 e4be3e861085640a80c84798d63488f7f3b5e18c a99b37d1ec0c7c99bcc52ef0ee76ca9a1fda5a26 c622a1920a7000bbadd62189deb3ad35da6b7012 29bbad2bf2ec7b2cc79191bdabcd95a6c231453d 3bfbb14a2c25231798d178e4b2e3f7e68e35aafa 09a0591ae7a25e6581a4c161f7940b03b5a398d8 eff13ed57afc8b1503e7eba7934016475d56dccb e9f51d51abdb8e298d17cd75a4b6ebcf37352350 535beed99fa962c20a44d008c71c29c7792d2ca9 3f8d3b6486875f2569ebc1afd88fcbc8d0a84188 fbee507186b1f97b4118f591f9123ded8b1de725 a1e3ba1149887f3293326b5990195ac116e59d0e 46eae1ff960b0b478b5999e5ff5e2407af7ee55b 187a31931bc84bbed2fd49b839312a7197abece0 0de437c433283764435e99e2b84ea5c99a87f23c 2d0a9c86058ef022562aba0eb47761b2fb7613c6 24e12bcb6cca757e5f3daa465f417b474f13c834 9cf6e93d4e257bc5892e8984c167aa1bf2435476 c0cee0002080cc472d22a7aa94218721e6664edb 72eb319067758e17ef8bc2e64c8b373daa3cefbb ebeb60ff34b2d0e9b92516d3ecc286dfacf18a18 e595e8f9b3577bab27bc2be658007db69ea521ba 00fcb45c2d5c2ec3922c7002639c0f234206b863 3aceb1aff49b4aa83352b0282171e018e64561bd 7760bb1655f5ebf1bc064df5505767d7331c8fee 341e36006df2ba7848acd0c63b8f787c7ce36faf cccc2d44327422f333bcb681e80bb48dc3b04abf eedea5194180e484fed5a29563139ae9292a99d3 091e6cb64a7edd5a78b33f67dd521b6df875cdb7 59022307dcedd43cd9fd2bf4ca89da6938dead3b 5809390c1c8d213011b7b546303f6d6f41a1a914 3b5725c546660731ef4d28f937c236be0b7ce2bd c342d2f5be81114ae7f917feed10da61fba1944c d4cdcb260a6fdc55c3f868bcdbe6303eb1f0d9d1 efe7eb078ff6f91c91b24351dab44327caa228c2 fabfa4c6a979690d1e93ab626475c49154bd9448 96721385347ac6fd67a73cc8fa842da7c5cd722c bfbee4ebb7afc125fc124b7cbadb71a345c6dc1c 2d44067315d7d48097b73b5bd4ed0c0387be1f15 682470e95b0dc60759ad69bad9c9fbd87d1c8283 8ecd52b03684752d8e7abe5f66ee531af408619e 729930a85e1030f73c2fd5fc208fb555ac1dfbfb 0de19ee9363788cf619df8531f06d46f1fbc0c30 ec3897c405f0775c9f849a366b6615a5e5688133 11c06d8337b1b099f4187e78e8c81ca970158595 86f121f92fe7f4bbc6d5f80a934e095d9de09cc7 21d56106487fb6bd6ff58af44816c62db9bfd087 7c689fa7a011005befa2c6ddd840f7eb7b65354f 6bc1598eec42365d8a446205cc15a4f6c6506e7d 7f353e0700691952a5951c664f6f2bb46869b9c2 58baa1c01679f09f0af22f14782a35dda162e3b9 be9dc299451987da4454c84a4b03021cfeb33e07 c5bbd756c39c7dd5e41b675c22c0b524d99bb44a f7985e42e1414e79cf49bdf799b6a64307c6d952 53bb6503b8eec57546e4de732134a3d3d61a6b2a 101a4a8af7eb75f9157464b63607268c2ccc2005 fa6fd2b51c6d5df939d38fecf734ca299d5c730d 5137e56d221e5e452df7d87fec65f75d5952849a 260d502d3f215092759acd12b039edbdc3ab3949 483efaf39fd85a26943bfb9bd1b1d6652cbeb061 555cd0fa0b6a665e03c782084d352f32b6413874 0dcf7c7ab6cd1bee504cb2386fe0432089710371 e274821cb6eb77d973e27b59f59b97943b953adb 7db13c05eb551a6e2e84f2b5004d48c5bfd63cb5 dab8d2f94899d6e39c5cf44c938dc0a8059af9e2 05a9c11b09f09618b792cfdee03af171023e06af ac9cea0097060a9bfd3e9ddfde095f77cf7fb559 d126fdd3c41f630969be2fd1eff15c4807b90665 4723dca546a939ee0ca32e1ff3a5dfe5b138c719 ec01d37e6274228335e16edabbd4a0e2c93e7002 79d49ecaba7e3c88bbef553f88e0a6521663b073 f87c233da2e2fc51cc446b6103508d88d966acca 66d52c85c23a0c6a6023895f7e899110d7c88a96 3064e2d146006cd236c2fdd4a7ae1c9389e57441 0eecfb0698d5b2d3c32ffe2c07cd4afaf46a6e10 c3305abf87c6335004f1404695d1ba9f1eb95296 fc1a1df64b691672233e5f12fdc3cd54c03fd722 ea7599467a5eb3b449f75b4742153d5950f54e3f 63a31d79fc6d7c7db52e7dc427ccaf4cd925b589 0cef057ba2fead019a5b5ff681e8126021de7e50 0020c2923f59656ee35166fc8028b2c426935af6 ee43d8e461ad0a96075dbe296a4a27edb64fc809 3e90b1b0dc4ee0d3861823313a99bbc322538425 98b7c273ac9a343380789fc42d35af3fc6d8de07 f860d316ad50db66e7c8b11f1c2f8b6c3a4189d8 5b34973f483943860467af7b2cdfbdb5f3d21981 737c9c16bf3fe44ccb4adefafbcbc18072200f4c 13ad0ab7a61b1822a08dceeb88acd557366a24ec 99e6d4a2c5a0f52e27d37de41d0d308e074d7828 f6b8cf7939608aa57821bd6773d222fe26b919dd 85b8c11b21829fb99e2472bba2440d956c7a8027 dd0ed0f2bc618cc78bf43b1d51b2bd8564317ea1 04ab5a54489bed14c7f7a4e85d2643a7054ffbf6 53b5dc1d8ad21e30fd8f624278ed9e49afd62ee4 6dbf67e913e676d2511841e4d5459b6ae15d72f6 acb103fa156eb3669118352ac358eb09a65db4ab 1eef91169c646a21e7263bc6d5bb48d001ad5482 76cf4993397501ecfc5b8b81b2346dd7b67999f9 ffeb6a4e7b534d6abd594d121489e862333172e1 ffe4f5d57cb8109697843cb8628565982d4dc67c 166654a407e89da9cfdcf4bb24875a3b9d9f9d8d 5d41c8ba46718fba261899c0bf40459cfa41ca0a 9864aa0befbb2719007e1f3c540dfd408a20efb9 2b743bd9e004c3a379c6a1c06aa10184baf6744b 63f31b87b3343cf3522ccad8bafe4c8497a13734 fb628964d3ffeb28f293ab7c214ac5f489289fb5 05cd435882884f3e30f06f417b0cca3e92824c31 8ba64e4f1355987e1b9bb3c1d0c8b5f309e59eaa 1372b96e3bea02a3700b110491e937e11130f2fa 1508705f0699722d9265fd4a044b67c9970f2284 003a40a10b6df8754bb9a3f9bd519315963923f2 2656c475f7b96bd23fc8bfdc4ef98d7ad4d8f0cf 1a9240094e7935378e96699added6f06ec19e56e 708851b52b8ca7c8881b82c4faebdc14bac715e3 c139b745ac2486a009a59bfc0fe8c7658ccbc456 d09d7e35708f4d3ed0d5151a636e05ab772264e1 78447a5234843621cb65a805dc030d898c3993d3 750f60e5b30c4a1d943a830d521a8d82cec895ab ff666749b632c91991d72284177ff18b3dc8accf 0a6ca7e9b48c90eaff39b17b86371788a89a782d 4faa1d0d8dbc6c650521cac94200d75b667db8cc a35d5bbc60fc89b22d3088a7c0da28a77cb02f9b 29ca502cdad55a53a8250e53b82cfc067df3a217 4a1c5a4a1df914e92e8c85f36da0805634af290a 7f0fd434c82b98aa8f755dd1cfe7f1020b797d6c f451a3444ce78102aa581283d6a5b361b179b8c3 1ead266501ef9dd5876c8324159371478d631ca6 3a9a19cb3c42819f55faa50bd34cf275587d18a0 0a1ef28f5917d25377c807a20005ae72821ab9bf b643576b060ea6c832e541f37e992c1e48005d20 71b4e5e23cfef785c3136ce09a23e8bd2cdf6d72 c90448f03e3eba78939a9c7a76adcbed10051524 1a4ec4074f1908ac2a87242f121cec4f7e11d33f 98ef22f7fa0fe491e6972f236f2bee5c50065571 c3ccda9125e8826da23b8f3db2f75f417efa5882 12933251c776333fbb6060ade81fbb8b75ecbc63 b959a007165d6350f8cf098394ba0d843bbb3605 81c21063a2ed35b453e4611ba1442d178f182ae6 f40e7795d634d35d7534c3064c3e65547ce9aa96 47c377feae14a8391b7a55aad73add3edb40e229 9dd038c4ed75dd043a3d89b2de1e6cbf778c2dbb db82af12e51df682de4fc581237de2dc2269dba4 8f9bf8c05e9a591b09e90c607d9841bc40031a2d 35fd28e0f92258591c41a7791e5c90116fe18383 686bd8c39f6caa84e7b7bff986663914b0d06db9 25f25a30e2735433f741976d9d6400447977c544 a9fe73654ee15cb5d40e3c72289ac8e065726440 d63ebb7467399e2df2e1b1812e12d4c0cecba56c 51d91513b685dd9ec0fcd30cc4079fc969bbb151 ccce88e4580a57fc192128776ede41c432b9aafe 31f7934104aaabd735f50347e064294c3ddedfe2 facd7aef0a46b667caea90cd6f658fbaeb9323d7 a77ad580f2afa4c463a0410169a8014bd3a8f87a 786c5551fea4f0be3edf9837f2a4a04a084b00d3 49fe012c8a5f24f80b9cc5dd974e7aee4bc09993 2fd0c23e8d47c7bd73f1279787076ae5ce29719d f6ca14c3dddafe17ca8730b8e3d0391aeb040192 b5238d6f6cbca6c697b6446c21029399b92f7ce0 f786d968f0a932679922d7e4d78451850d3a22ca 5f59d5887a127ede6060e7d1c16716692a02a8f4 d46da97dc874d78306a975cc618bf57dcd849830 be2f0dd34256aacb4bf1d4f9c0a168ed92700fc0 f1fe7c7a37405f04528093bbde1f100f94b8c2bb 4a4226e4816b5d07c99dba12bd3dd63b50897afa dba658ea1495b1d900886f7bdb69edbefcc4409b 19d44575b44b9d30270628c89959e58fe7eb83b4 0a4ac626c5b84ced2fee0f24fada42499a5fa98f a46eb97d67d6ef517fd4023dfb17d2bc27863a34 b0b8bd39ed17f078fef641d0cd607360fc3bfc1f e7cc8eef33e360657a34373e4f021f7a5fe3dfd9 cd9b8e32be3b44ad36c67606b964a85021cfefa9 08a6afa66db6aa0e0bb489d91ed3fff7695bf859 51fd597e35ea5e4a4e262fbfe31fefc34d14a33c 35654277037b253ec6b50b90d11e953b904b4737 160daf5b0a94ee0512540bedaaafdaaf88c02b54 2176478ec51b1dc92bfa89a1dab387ddcf2cf921 3a5666a824b41dcb34b5faf7e1eeb37f91e220fd d83a8dac3ec02dfed64585fa8b74ee9b9547f00b cfff8c3db6b8375664ba5982b869231766cd0e7b e2e675905009cf18684c41f20693bdb4c4eca55e eb20da006eca11b10eca41c3a39d505d6216cb3b 1e32e104d16e19ffa24d00382ac93864416de796 c2e98e292d7ab0046c563d21b089a2ece7f3345d 4e3d026a684f9e622d7b6578ee943dd145ef986d 1b6c7b44513647de073e418eccb7ffa0fdfb3f4a b3a43bd4f43ffb1d401a884eab21d437e76d0337 d3ef709456c51628925b29985bb96b2a3b3aeb36 6ed30824931a202581e2edb8b71a3bc46e3482ac de8684c9435f6c2713b4305a9e813910f7d066ff 50610c4fe9c1d999e3a3a421b0fe94438aa7c719 9d8d0ac2e0d5f77f31740cd5dc938586d7f59b64 46727459df70aaa284a94815187dc78c92970b18 b702aae1c49b8c2bd42b3079d935e30025973e8e 0e7b867c438caf962ed52f696ba1cd4c5586e246 4ea01c5104a0597dd74c934e1e2fb9f1032ac2e5 a74bf236a1272f4004a5b1f5c9647f6e4a511dbf a969b12f42eaaeb6cd759620a47fb31ae411dd8a 750a98dc1bcafc688734df1df13ac6336cb08697 96542ca6b5eba2a85d1a6415ff380c19cb44e37b 9d962ecd0165568cddbff4091630a38c5ff0a3f2 e439ef5262c1466c6a205d5d3933b62301d9b5ba c9da3222729ca837737f3a46eb1290d3670fed4c 433b30f85c57dd167c1515ebb1f30cb771bcbc9a 8d2faba5665710219a0d9a01e2f361451fa14b5c 4491659bea5e676592e7998371bda6f347518839 f0ddb24658372079e45cf0afdfe1880d89cbd1cd 1febce624019fb3305a9a503c671d5818aa51672 3ed8071e2cf697d08c0594c9057ab3eeb1646160 1481b2223e6292f315596d4896a259ac63200a9e d22cf502fc5940c488c8a884694c92d0bcd22f42 21052a55ba9f0f7191c373e4959ae62154a35fe3 dc396d8916ada56af5de8c96c569132a1e1e4094 e10e51fe2001a5d5745ac702ac07ae6c4b5c3d62 c8eff18364b0da9a83cb059a8fab1f1c2ebd5f0a 2005b37968d7eaaf2c31997fe454e9667dc2e8da 9a003fb96381b78b0568b75ccff878e534ecf378 8d5a24ced345565ef802d1782713d336f404e442 08e3ae022528f75e565cfc9ff3a7f5584907f173 715b9c01a120fd0b6052d80f48499e32db6738c6 95c3a80cf9aafb33a32618e826be11c80e76368a 147d59af6dfc291790772484b94a0096bf33945a dd2b20e3adefb4b4f9e60acfc7772553aadadf47 d83f220cce89bf6ea890e747e2a298e1c736311f e23d37ea3df604af056640e121dceace45071f00 37fcd66ab2d0b842daaf9b4fca323082fdc63244 9c74ec5a89231542a429f22f35c9d98e83b58ed5 bfd9c99e7d0bb1bd44a997e8bdfe37be6433a624 d2ae237facce3601d7996e5fb5a9c98e10429813 28b1dd6b283bfd683de52e10ac0bc941f1c4d78c 6878be1d5d29457182b23b1f4350977e2894e338 f58a322211cf8c95f938b536ab5a3a524e8123bd bce4f4381c452e834e131a40107e5bcd30786f27 0bebb5381df6052fc7a78c96f035d343a6275432 cc0d71dd981685a4530abf8e0dbdde4254d0b7f8 2ac3e3daee227f60f118bc0da965355ef4396f25 3d626f53d3d577491fa5076c42cb7cfac7469426 35b492c6f398aea7bc82d292add2f24cc26a8193 6d04552a02f569d46d9252d9739939c8ff9bf841 7c33dc2a37b828605b729b5aa913fa84203c9b69 fbb3338dad0bf5639055e05a44ae9dbf7f382869 6cc36c8cfc119fc861989609697a1a01984cfc57 7f9e73b285bde6531721ddaa66ef5f833e05fff1 ad8e135de0ba720af8db1d81c1b51b6abbba9b20 aba8cd2be79943ff34f9b1556b483c27d0ab28a6 21dbff0003caf14e09ed7d795b40ba9ce311a8c7 0129e681666a8aae462ffa8ac200d95a5229acbc 10ae2ae12014d8c9a8c5d51f676acf620bb67a70 6d95d1504d7ce9de421859c229c9ad99c0cdc7ae 47f7b71b6f6ba4272261187cfd34e4c6779d87d3 98b07ab54ac5dadf47fe37a2ea3bf81eb2055424 1891cc709d71f8f6a957185d4eab5828ea2fa1cf 3caaa78984fe7be6a8059f6a10a677373f3cbc72 f498f86933acf0656525c5afb8ed3f43a54d76b4 ea31bbaf0a9413243aee16f758dfe5a925d0f98b a31d2f88b4932a49c0011d54353669ffd617203c 4433cd26f4a0df01781fd704d83db1e1d4ad9141 0fef0c7a080f10c26b6d1af45ac2251fca3a90f2 9270434122d84a8ef4fee9fb420503854d8d82bf b732b1c74708849b3f7b4c850612084e3c3f3fdf 5e7435fcb1a82ce644c91126fd8ec008bfec50bb e51ad8256e60331fbb565f68d605a2dc55393095 cbe9692aae1d8c0b03882f7fdc989a1a488f770b c175e0dba6860c182e5d5f9a688271ec8d267cfa c8cffb88a618556c23793c446752b6c0f0c19e48 8d1d82a525203bb831043d0130b16edceccfb658 17d8b7886f6a7574f98d597c06f58d5e52549975 7d7a42eaa7a0d8a1d1dfaed524a66d703c9671e3 4c5bf5000109ed3a3f5272a837f31f9f870c6bbd 5a245a87472d12cf159c0bb654764b3422822305 4ad95eeaa48922727967e957ad3e6e883f8687b4 5a2aa918c9afa205b47e59bf69c3de37b042c47e 23bb33459aaacaca15daf3bdd035f50b01f0e94e fae4666ee5d0cb3720491038bb8b347369b62f5b c3d706404264df60bcc4a93edc13f9fad8a6dc2c fc3cdefd1b0a07cbcf413e7560531ab45de1f56b 1003210a8a0919ac90299af0377bfa5cc456838c 13a2831d686480e6dead26c5cc46bf694461b41f 3582c40e47ec9e89d15da7debd472ddd7d7fc975 d17d6dbf91b23b70a29e6ab8fe198edf5bc1892c 61d7fbab9c83e1d6b21a5e3d18ac5c2aa2527b36 aa26052a7525fb587f5cf21bf223bc87d5b945ec 2d4e2d03bcd7eea5ea0f8114be25306fdf5e913a 6d749d560381fbe1783bd2e31be8b3fa6c13b2cf 8e8d8da3f55e5bd2747b01e85aeb04d97fd8e7bd f1492fb1dc8c5ac255eabd747786a2476ff289b3 f28a33bbcbf41e2806c59de7538535adfafbf69b 30e543570160a73e513bfa201c6ba0ba6b1b9012 92d9f18cfbdefa988dc44519143fbbe63e71553e 429aa3afc896a05993a357755aa9731c7039242a 3227acca32db09d3418837d9eb9e843c1973294f bf6c25c4cf672d289a539716741c80c95845a5a0 9ddf33896d854d8abc42dd0ea4b42c86cd234fc4 e3ea216ae3097412f85792d4d258df989978b096 470ce6f7261c3932d6c2dfa44eef69f97412535e dfff0cb22387542a9bdfe0ec3c904572793572ac 8e552cfaedba4f15b691cdc108b509dd813ede05 100a1b838af7c357fee88590f167bc34a8a470c8 4ebca1ed229b813f8247c031c6e5acd497549eba 21ef1becc865be39828804d028dd3b623efe0c0c 9cc3c6a68186eebfff192243bacfd48776b3d4d3 e6de63f1644606d855cae00a0b5d6957aa25f4c4 536609ffc1f03817035706a76c8ecf1f1f9ac06d 1901959bef483af66a41571638a0e2d2337ecd4e 714fa6fb3877521a793ff2000d0c3f41bbabe527 08b77c5184f62e095cc42fa6b6214aa510ef2a2c 17c05e246710ce8ff756c8fce2f072410e0636de d74ab753fa894839f4c5a3ca1793016668db51d8 383e73d8af08b381e8a43643e0f87a3189a17c29 7e1966b7757e30807df323c252462ce58b590b26 093a23541597b893b56275694b7ae0dae0cb0a29 566e28572bc5a782fcef4f20f39560dd5701fbb6 5484d86d58b2db59fcdee501122aaf8f5559061a b538d3d7f502a7f8adbd3d8c8f39e51771de26b4 71f5b6a022b00f25de6036c78fd9331f9aa67fde bbc24ecbd22255e9f7f7fc859927c11c85833dd1 66cc35e5febb00c50b6e9c04a84b26eec9c71248 51dc4a585f577bc679577a193ec12fd3e3b7ae81 30889994141704ce6902d95c7200e46cfa7cda88 6332566cb7885c046f48cb8e8bbc584eafb8de31 41976f85d49239f0bd700495d49ca97feb0822be 1bbdb2677e28c9f1eac23e49c9a830eaf7e7d87a 061e8bc2ef29582372a02e82c0dc59515667a231 cea9f5fdbc1a2c22353c61bb0e077607b2158afa c62dd6c7554294d8369190a1ec0cff496c3f9c93 8e5e40619aff45c9bbef5b69d422fdf3b46f86c8 01ebfa201b31e6e45d3655ca950f185a9e17bb27 1939a5ac0635fec851c576a71fbeab90b6c7de21 fdb50cfc99fbd42c54c5cc6b1c72dd9e8af64385 29d010bc66957bf7c1500cc9616b1a1ec3339440 e2059e966cc9e794787e64861fabb965a3d01e44 df3dc0d856c59478de1c17063f84b3221038ffc4 35170b8e07316f86204ebac38df74e4144f83328 6b29c0d8d2e4fa36028328eb30ed49a67b0834ee 46fc6d18d740168553c23db1d87ef8af8bf0952c d0434f9ab00a890307aee80f7232a61568852a8d 3ea62ca744e5d8ecba3ff7792bacf7bf285388ec ad4102279aa277e7265dbb57d3cb2ae6e3a7b16a 7ae5db4509421bcfce9f1880ab8919fe23fe9e13 60ca5098b431512959be41ee92353d8a1ba6cc96 acd8ef06d3680cfcf8604435a30ced750356b287 65f7300bc005af25eb042fd3a0db36eda7e008f6 e21f06e96ce91a344afaa921ba28f03379a5881a ecb985edf672abc5a7ef9c9643c0d0dc5502b41c e90f7c4461cac5ea7165f4adcec3e65218618d7e cb94a096af8428c4024ddb332c7dbc34ef433713 1beb2a18ea274401213eb01f1c711ba6fe9ec1a0 748b710a69d0fe824a4006959be9b4ad61e0b5f7 f3eb78fa2b45711268ff2f33f8e4d2e27a684ade 2d03f6315e4508eb22c242a09400bfe1b9b5cf1e d35d2723108e000d5f2b503775bffc7ed3b319f6 50aeb83009f9dc1f76f90378c004a6a5c256534e 9d7d43653e5015a11bd850e42e779b359779b9f7 bf2e42b64ab4273b61b113e896eb973878a62108 79889b5fdea8eaf8c9c860de2ba883462c15e4b8 8dd38feea99184eae21e63872c83b27a882ecf66 792950c3fe8cb57c1126f38a462264af9f335bae fe5e5e2059f007d2b47c3959f1da8243c81f6a53 b048a9b73b71f887d80edb506be42f84963146e6 20f70bee7305a4db2409020f3832b6449f8556fd c7356b4ce8b797c444d99d7f9d2f690eee6632a8 adec0d01108537b453b0f918001a11fc4175e2a3 164839dde7f2ebe4d691504f2d94d57fffe7efdc 29d192952f4a75c44a34b510676790ac30bdf4cd 22bc6b736d460f8ad09576b5f1c2061f2af19756 49c8c9860601f73183b351797a17a460148bc825 50e0374029d026026539294d9c5c0b3072bed9cc 371974bf88cbbc0c78f25a3a07a331b5526631e2 8261bd253d3b878396ca796b756ea619c76c9bab 246f95ada04b99cb9a0d2f96ef88e04b6991f118 faf8506525cbf3f5e17f8136222adf055ce91f0d 6c06aff68fb1c208e75ab62dc2258fc212cb56f7 591c44abd2f510a3dc397a29013fa1d0394a7edf aa8d97ffb50e091f4af888b4351d840a53f01c06 2260fa244b0a5d265ce7e7c394d5d053daa26bf5 0adf2f75734a74b98b2ba9609f918f703ee51100 5a66fe97e5b36e1003391c6dbceacbffe8ebc8b9 30e40b2db88507bf5c565d1ea80ee4bee3586b92 129d322d93f0e2a8c1f8c8e2866be05aa33e44d0 cbca1271f262f42581979ded0bb8b10ab4a4083b 727c3dc038174a48469b1cbf75bd9097e6f67edf 7d0613e239c631b4393f8889ded4de8705eaf109 b0ec4accf2de8e640ab064ecfce7db56d8ffe2e1 baeddd847608eb1fc4c7a3bebbe14e1b0b434036 e83e2a1d8407c07ff718d5f0f2e2b77711800ed4 7a71ef707194ef9b9721a810986479998c0ae1e6 74433f78b378d0e373f1f063ca63c7900b0291bb b2d7a19be1efa46b6b0c30cf4d477532278d4c29 5eba46f03e2dba78dbb744a185badd961f60f941 74ea296f38b626d4173bf7dfa393b3418a7861ad 798fdd1f8576a527cc67321f3561fd71b2d29159 87c58ee53026120dd75d1f379198fb4a2a8bf819 ce5c6d1a1abd05ba78e99a966438600cf970af09 113beb5c2ac350970e25560360a68c9eb398e99a 43165b46d7846313443cce6e40fd0f87917d960c f573e621076dd573bb5d46c4415d6be3c3101882 24bca15af04d79aaa45e3437f359860e1f86b8d9 6fd9af870132c6c1032f4ef058160522653d976d 1bafe488000563083d2379af131b7689f31a2fb8 4375d56b4a62a8675bbaf280450ab42c9d3ce5f4 301bb84a186c15bcc0d12329ce210b79ff898c0e 72f895916692d488ddec89dd0f0d4e89e33d8179 2eceb90a030f5d0285e3df47d221728c3f3d7f0c 50b601b90d2bd94bc34a25e8ac8f64d599cddf75 32a997bb3fd65fd1bbead509fd9db8bc17106666 1ec5d8c90b109ecf2d7c368434f97698112858c2 9271093b7ddda97d0b61474318993cdc4beb4315 2c339acf43bb2790f72682381fe15617ef83d0c8 4a85895910a5367825f9100677959ac5a39627b1 8fefc22e2992e4493f8b85c8014e48fc91c8538a 759cf9bf4c61ad803dc9c5e3ce7a874eafb98e11 2c6b86883fa46e42935917a4efd849849b1c6010 2d09c3f365e8a805a2d0229e72c507a3968eac00 1bd9f2e35b34722d0cc9476a765f4bca1f42a349 18176ec4f28c5f6097be99658669c428bafad8f0 fda7b0720f3093020b2b7fcb6ac89cc911eccb99 f8bf51495dc46dd919c4cfda86fbf2fe663360a1 6f1a9179411f724469b91b603b239b0a1592afb3 bbdd2933f1eac1fbd9f054aa1f2065d3b04f69b4 81a5e60f62ab15d55b41c1b039ab5cfdbc843c51 c5ac085204422dc9378a3d80be7b88f89231d4a8 d458fa0f01bcdb51c523c6300a4ff1c05363eccd 48f3bdc704036fe56d14df738c1edfade5421bf4 9b1e7731ac3e80d455f31863458f4681876fb27d be590cb6438f96f467f6d40a90fb958b8da0b395 f98444989eca6c5289553237136dca0bfa2081e9 1a6d23383b639bf92d2b6edaad5db7db48267c40 87765f85d8654f7b0782a16efc97720f75b57a19 d10055dd3719c84ba15bbe0589bcd22f304bca00 059056ecde3d90b293d4f3009d035092629233e7 5c1b7ce7128c08277329c03c8e3a917703786795 24c6f23434753b33b8588584ed7c63be7fa14233 9da1cf5f12013d60bb84d36c0d84a98398ca8935 6120ec64d8925fb61c0db8a19510f29bf2d41a46 cf981443a0f94b20bc7856b1664db145b3dbfff6 98b4d4c7e78d7df21a3fb267866444205d9c901e 9796fb24e48a79ce46bdfdb04840a837c0342fab 4c5900a4f02212c018a0c4cd44d5cd5320b61dbd 076f4dd1000f10e09d74b2a28531d6087e74d8d1 db75d0115a270946801be71b7f5a455b09b5b131 7103d24ffc89292c3799f2fd7304788ccc9776c5 6bdc647903658884e5931ff2c77b307886797f35 c6034341c6df86c067c203ff89f3a1f9c085e536 c70dc905a864f3ea3b9de0979f12cbdb6fd23fe2 fc5b9f4eac0468b31b7988f65b673fff613bce00 bb414bd4a7d351b7b6eedb48cb9585a8845d15c0 7c43b57650a1ecd9c846bf169b12fe979775af96 6ca3a39b851cbcbcb051f407d7c6fe5e21fa9c11 11928f5e06e577a42f2410293b017986b0cd435f 332ea16d964bbc87915aa65528f3e2d84d7c8ba9 d6ab12bcb27f112a08810819cc21d25a1969152c 413db5a95b7bc8e91c4c8180ec4d13b00dc86750 be05f06ba32f1e57d5d9d9b665aae2176fd78123 f3ba17cc598514f765a40bf68fab4351be0709d1 54daeecac16efa57599bf3bb06d4d333854d3f8b aa47ecfa21a6a57e4d1d98bb8e6972bcb6a19aa8 5f349edc09d3811b217bb8140fe01ab5351fcecf 41a4ef5748d6f73d2a4b659da37650116a228028 3f2339aa0729deb659bf422fb787fc055d01160b c8b996dac4433b0f75994002b545447db39703d1 fb57daf9e71b5eadf6bd89863d23cbd6ab4a6b73 dee1c9514b897e7b8d6d514e73b81aa8412fe607 3c972c66f54c2dbd7c36c42d013581d1e69648fb dbdb29e50a154eb454331dd70eac98da69739c4d 4bb6ded69b6b2157b43f7593e7e00929f3fccb6f de5b379beb2e2c9c28f099116144d683db9bfd77 9b4dfd258910dc2cb03785aea42cd09246be3d7e 32b4fee4a985ff9632dada75ed58b2d32271bf49 1fcb4c4fd8173663a9ccf2fefdc5b1dd65f595e2 c1ba407a251de7e68631b3392c180f11e065d1ac c8b5f40d003414828bf83c0b2dcd8dc5b7d9ed27 a46de9b176337e211d6dd20900c11e6c35b35202 95e7427abe5eda1e2ea64816b54353038ce7cab3 71df641e062cfab836ecd19c8a83a419fbf9dd10 4fb6708da50c7b557293de3b391384bcf9f85634 83484cd4637b336d5dc56c89920a5852c26f3724 dc91c2c62da75310ca0ec9ce4eec7facd053a654 ba71337722300ee22438d96a0bde46a9850a0409 1205fb05f87c1c3405e5bf5a28c7fbe1d83700a6 4c8a89493abb87f9e8083ed8d5ce2b4d66e4ec6b 11d833df847067c68e9db2ecb0fe11044c8c0bf9 959fc7d8e387f239cb497537c01a4487994a417d 1ab52e92cdb32225191a0d0fe3a78f22355b9dba 292d2aa2b83ce2ae4cd225dd8e9201e56def603f f8ab76eff97a3fecd960c7366604af91bb563b33 c00e7e6e47abe8b1167db4f64aaaaf6071df7748 8d28a7ced267bccfc97169ace88449d7ab2dfbf6 91e29360302b562a1b2ee285f6f1c4637158b73a 6549584821165300b649b55116b29efbd9e60b23 1e74aa42299e16ac3d22b1279510c9d5f4f4f90f d9e13dc32eec7381937b47bb406908490c0e339c 9778ae096beeba3c95e6590772fc9f2c889e42bd ff52bc17196375f5326302caff5ec7a85815eca0 3537d808aca7e279cd078165a4f44f6d8dcf9e33 fc145c7254dc3ecf13e32d1a05efe588121919c4 d37722f46160cbe45856d3943b7b210671f57e44 31e46506e3e7665af3723171590c2f479cd405f1 d42a78d1f5607cbc17264eaa3eb21885e991bdfe f1d5f01fa62cdb912c456c28b9cf0695d6fb858e 8d1e114607035ba1e1b812bd94f1efd4b63f5a8b d887f8b90b4b672cf0aeaff53da9fa0c4bb06a67 b3e9c9d9e1e4af87d82f57d49ee2624e054089e9 cfe27484b2b9d67ba3daa641020e4d9bd4970e73 5d8c2113714344a21be0c169a1133beb89c9c655 47011b11ededd397429386458c693bcfa711b8da 66f2d2079e8656494ca4c650204385e184eb6ac4 17071b1938225ba819e4c770b974d49ace3b896c 4367472eff8f5ad5efacec44dd2fda0717dd3761 5c8256cfad8ddf4949476b8f8e8e963daa584158 c2f78e3d150217ba65352bbd6412b4c2d0faf77b 55e12495fb07820c12a326eefd2ad25a009ca02f dde7fb0c741224410b76dbd6308c1173b0ff85c4 34b2990f056024ca30db85a6005cda7b55e5fae1 259eecde397cac7de70c9674fdcf946a36c3cdb7 6c48cad0db2afb69a9224aed05d59b7b131371da e27d76182fa6b8adce34c6339479f07cb7fd2989 ab123f51a6ed514e3cb9281f97e62c5cbec6a893 fc06dc38bb99f3ccf906cfc1bf9a8b5ad8bbaa5e 875dfe6feb99050465c4d7efbfd66caaff82b05c 2e7adfc91a3c1907ab4023d4b206aae934f9895e 7d1f04639a3aed6e8cf14fb82734649bb98bcb22 9696c38f7f3a42c62b0a503e67e924a19a667117 6c28296b5993d061e7500cb4a56967b3429f9a17 15213e697557717512dbf6f606f6eba95a425add 267a88086dfa36e13d42c315c73906ec3e6e7ff4 8780446c1f1facb77ff5f00838c9983c3ee8cce9 acf8b7411f1c9bab665cc99f19ec9ff0f39f7fcc 85870ef242ae43ed8321e8954df662189221e619 4a37ae710a435d3a83845805e5df8e677e896627 2f8408858383015ba1338ce0ee1c4f0cef332836 80ea616be52b169961895621b54128f115860357 3a8af43ad6f5355ec19317e4d8945af2b3f8cf20 72e86dfe6baa718736f38ee41abb46b18792cb2e 2ad1f63147b857512a3be0609e7318b87f3fdb5c 1e734e1da46a6834c8a3095a48d7467682855c03 f8d0c19e1722a3e16eda9e21d305340d2aecabc3 591edc0132caf72fb06b2cc6e132528e3958e189 30609c6e20a6b1f8cd21aebc368482e59de79f41 7bd5710b36e34884b44e92d27404a21c0b2d2b38 606ef382340d3519ee72609f5da9e442aa0164e1 dbf672bd8eefa9b03b3bb6c6af2977928fe9fa66 9ebfdf3d73b7ad31dd2badae70e30eeaecd78b74 96f7ed7b96f5c1161084a184216ab0b391ef6faf 6b192ecdfc28868c0a323f1783c052ac4482a2ef ab098366011bd3af397dc6bd651115c489703851 c7209398741453272d72226c2d193045b12c4f06 469c54bde2f5756c281fa1b3ed6bb9cdbdea0ada 1ef3513a3ce6b87aae5618b94def59181773b4a3 1bbacc54fa72545ea72a5a0fffca48a7156f9c96 438026f99bb99c81277b1373f5db631741b0dea1 7af6440f9eb5201de263d5a45138cdc51d048a6b a64d9f632a8e2813cc5de107555b4905a5bc9470 492e337fc94565a479ee3f064d011e0a9514bcd2 45a32afc08fb17f900a036ce87877bb365dd6d90 f442f60b53fc7a67fafd4ee2fc6a6acb85a43de3 b43fc5d687e70a17428abc9b204021bb4a4a844e 859f1b42fda79d6fd384afc859f16367fcf7c356 15e51fe11b4cbd60826dad59baa365ec8419b29c 6e5c560bf8f99489acd622e5c2dde483ee7b9272 5aa5ae2ec09112eb866e3d4a24e3fe3e982c52cf f75aa7ecd58c5c9b4e13edc85c584307b1998698 c199add3ba2a0e4a4691f879549bd26f82599d8b 11af1aa1fe3f5e872f38116011b609e15b8ee9d4 093813ebd5794692b9be10818fc03a62abb6c726 5b2eba713e887668948a13fa5d402dfa59be16c0 87f7545c6a50e3b84887ecab1d363f19246b85fb 55801c53255a776aa618ff625f53dc385afd9d50 1b87d89a6d9d47c53aea49d98bc06511a007c653 05740f38d458658a4367073074ad51349c7603eb cf50c70790ba693bc97aee36d540c9f23dcd3770 52bfc3c42142b20aa771349e09c4556cc576c51c e2e44f550605ee2396703b26aa575249482035ab 82c2160da5a2dd2e678c595fe182be265817eab2 24181ff46676f47d69e1aa5fcf89df625ef24587 ad5bd609f83f657cfdf3dc885aff94d8e3fe678a e459f34b88fb83adcf5527668cdc5f2ee9d79477 256d3c9296f6dc68d41f4b4a733e55376aa081ea b6ac38e2bd70e4aae2d18652ee05dbf98e913750 9149c45bb81efa63f8c71e5874e40ef438659b39 ca674d35920d84e5de4587c4d786b91923e6a879 a2b1f2e6acb246be3737f770d71ce20f7ee876b8 1b4ea95cf898440f55e6a75abc2922a9c25b7176 3a15e7f1936e3590626eba8dfad7004cde07144b 0aca079e7ecdd0dac841755008977146c774e044 37ac845b35f6dc6217de40668f61f653e161136c 1a9af17a0165e91f722f7d36690712fc8dc1a9f5 1018bc76ab847ed689502dabb437a76e507b1cbe f892aa696b0bcbb2cef32e53b9e6d0f1dc665224 438f5860d4c041006c39980861b3b4b34e300598 6722e5fca54ec37a3c97542c104915d3f9318934 59f0da070e80b521ced3a7ae6c279e8fac6c4a06 ff78568498a1abdcacc0d82cfa2741a3fa55bc37 dbb5b27774b64580dcfb5e9bf786820d5bb3bcb3 7a6ffdf8949b3338141c7c9aa202ff95ba9f5ad0 14074a6d104f7df78facaf3d92dcaf041c334316 4184061abe7ad699370aad4a8b047dc79847d245 c32fadf12afc3c5d45d33ee0c0a08dd0a866ee49 365374eef1fb0bc12626b015b3a5e791c9ee4dea d83b27ccca59bc7871dfd758781dda041b35fc5b c45f498f1060b582efe436a064c22bd602aaa7bb 5ad5b87456c99961ebc9ba1efab154bb92ebc5e2 86d16eb9fb27b150906a15f270497b2f55dbacbd 04bae8b1f4038e8a3eb6c6588ecfbe9e197afca4 75984ea90e4759a5455c746667e983ee24a2bd77 69c7df2afe89fae84025a0ca26d1e44325c87bcc 293a79fd5097f2cac0750e9474a2501a5e90a82a 53e6637978f1bdc881136b794b250b9902610c35 5bb28ccda817aa8cf93696af375f1c85daaa3eb7 e58e7728b1150336b8c207ad48362f9c6af1d75b d23258f75b65f6f31bcb1949b3bca1f2067aabe5 97cb209d93bfecdd182acd4a074bbaf9e26b0c83 f331e0b199adec8fd6ba9037efa6dd776ff41978 58b6042f925684f438c1f201d6c69fa4913bc46b d751fe9f018964f24493ded70c319d426581d7bb 63b13e282782f0f1a2816215076a1ce73f77d951 c2e4c654ae8ab92d2e797c667e16d498c5bb2e6b 2bb41fc98780ec62a52e1aa7fab36fc761620282 b076d3ebdafcbe7af65531c614daac7692610d3d b95677457955d3d4d93dce24385095b2ce0f6508 1e20ca71482ab725dd8f122502245b090f7de053 10b99e9876f9b5b1b47fcfc4d18b71e55396de55 1bac6c89ddf706123dc23e50fc04af908565ae80 098760b2fd4e526405275627bab1301609b54f50 1e91469fbf244b9fa202ba40c7a94be438865f46 27dd28703599888533576143705d7939489ff89d 119a34559b1e08a9ca517e44e3804c8e0f22e6c8 e3ce8d11e1e6a9dfabd8f07487913d3b77413fb4 6340a607d6c135488bcfaf381bb3a529f5edaf74 10f8d16c98843bd38a162beaa2057864023c1b42 f5f0bf8286e0f7e15b8366f26c5b79b64a8c1bb0 9d1268bf9f7a8f3178a665de4c508d2bf19fe329 b73b5eb1f2c675807116b51d37d8cd43fe356b2c 2972d84727272f87b80b72de8c599f4ece86e6e6 17ce2164b30f342f89dac09ef6245054f27d199b f1e5f8c36b5c3a1d444ea46076e2130c1e50c760 dfa799fdf92327d7287dcb58af83c18e0c2484b7 04b79100b4133290ffb091f2a9c08e8fabcb12fd 99b172b92d4181692b65975b34de938d92334a73 00afece67c167d77ea13b82905c3db75f5b4359d 5c772aa7f8877d54dcf875834230b71bea41a7bf 968045204b79120e526b1ffb9ac8616285f1c7ee e183639bc3f98bf30b315d4220f633fced8350d2 8bc439ce6ad63a032053217e9423851998fccede a0acc362be40a19d419c218b729471b4c0e3faee 8549dcb3dc5cee26a3c2c5c13c385461de305158 33e8e5a070cb418af54f32d46ca821deeeb159b1 5e5aaab65642be827d87bbe009090ca8f33abd88 9f5ec52f635cc0f08616ba93624d28bed1c15468 c110f73413ade693efc24b986dcd26ed8a987143 9784b14d58c35ddbef97d82d854e528a3f65e5ba 1926bc7b26e7de0745b276bcd2ec11dfdc06fdf8 5183048698b12d0b535b4b317f35c4a0578ef812 1f328fc2d7ebe21216b666b342e9edc73e0a509b 8b8975fc135dd7a31bbd69d45c16ddf1024975c6 6e4f59d016511ce2fadec202ec878a88ec1a63b6 cc5bfe25c71b571fd71c3c4103db0a7be9f6df18 b74ee3652b127a8f0cab2229891344424bd64299 2cff42f0cc106c19f6ffb1343e07859e8550032e 9e6a7fd6202ce1a37602f989c9283a299fb10294 28354286868b8088b2f937f0d18df6f2e4cdb6b4 5be7d4fc18a100891ed339746226a699be68540b 9ccdaa182d6997e9596662b16deb0b0e477d54c8 7b146e0ae40f5f3386f1518a799b3011cb538b9c efb7b88db9b137280105ffab55d2ab7bee7b551d a74c1572ef7f95b9cfbab926b7964b1ae24e7d05 53470193e22b41402122fc1c9b306fb3bd85cb8c a4c37ab5b93040a4fb03b3935d329d92b22d438a edc97f5c9d33b436292b7cc5494a6f18a57331ce d9555d0ac4f02da426235902bed58d6e5d50bc34 d3a64ca4087c0dce7c659dd894686ffc816fbc89 4c01b45c3f28199242e1b9254072fa273dc7f75a 13996691c16c568fae1cd38bf38e0161f0a63f56 cdedda69ec793270d45f54ec9d0906ac59e5a19d 34454ddf1c9684e1dbd2acf432ea07caf2243d2e 90cb0fce6e4e26ad552b731f1b29eade074f3fbd b98ed005b1be754de4779db7948c41799dfc7e08 5d2e48e2283dcea55b956dc773cf58e8d950d956 a50aaa96d8fcd970b80e490572853738f0780bd7 b48feaa6a76f1e84aedc1bee81b221b745207c6c e1da62b3b54a6e1ad3f935ed6734ca8be5cfa339 3b603dda99ac4ff5f836622a5abb047533d84646 710045177a071db338eb6612cda5a73d7356f92e da6af232eee2a1cecd7b08111276a0b8959c119c a40dbd8736e57e3b21f5d2d11867773afe57e89b dc3f1798d33452094d4d203db5601e79b1745b02 be4b6164f280db0eec61c1ed119b666af848f334 71ed43d7553e431cb257e6bc3740305a5f01351a f0d33047a58d18d489029dc2caeffc1cc530c395 2da82b07df9979a7d0be250842717801ff380b63 d35053c2f0e7f7d8672bb997ffe11e6e1cb0b007 a21e1e1a9bf906876d5034492aef8981e4c19e36 210e88e14aff7b275641b0452c4de8f70020d47a 136cb569e7e79fe6eea74c0737ea632e1cda533f e68724ac688d4be2570a3a95e93eb4c8151ca80e 465e35a2d8336c5872a2a60f6a813c0006364b58 dbb9a9d9321e7e24c5892804f0d05a2e69119fd7 31e34ecae0a1ed58aeb1547217d35092bb344031 e5c90d782f044549b624cf24948d9ad98a996024 4d9c632d0f7fd7d3fbc38fa6a73f01238f8b1893 c5b0ebdb9574b53c6ca37125ecab6973f99396d9 ddd84bafeb2a1a69a930695acef141582a55c03a bbb12d16377fc28f5d3d82d803f80c2d86e50058 c86f94243d70f3daa794661765d1ccbcda932aa9 0646003fa0a0d19e9c02d39981215aa679596ca8 5c57fdaa91655be537091f2bffac2780803b1369 23ddeb50d25ae1b4132023435ef4211188da129a c60d6332c6224d37d9373d52e979fe52dca13f66 c07c11ba8d3ebc75cc2abf14b79de9dda58b140d 011baf119a753fa895fa5ccb7d9d9e2a9aad09f0 efc7a2f2d16b11ebc264901d8c4f02b78cb6ef2a 329d96540c1f329de4c8ca801d73af3a3f2ee072 2f44a5efbf43e7a185fc3fffecd2b9ecfaa11ef0 40a05288c3f1fa1b2e811875acf2d892de4d97c5 a34f7794b476d42099cdd352fef59be480c74890 9b126c537837c9c0eac50e726f5323abd4c98201 09adfbf0b02f6ff1f0ac28148c1a9f2307c39293 cb720af2eef058dde12977be647c464216119b3b 7e61a5b0e3e79017c1e2bfb4ea83a6d71aa2c151 62e0d710b82dd159bd134b96f2f72a23869e541d d6ddfef5b1d9f7289e388479862947a9ce8a7277 ac363edcdc3e21c3ba13dbbf2f206940447a4355 58faa90f78a44eb35e19828cf5ff8e5246f72a55 c0f424975374842274ed0bd65e8a613f9d6670f2 af9785ca9b1c7ada312fa374ea482d837356fc25 ec155572362dc4f3d6c02d326854cfa55b6c8f14 20754f9a40b0c6ed5b5aa887da12676bb55096fb 42b3190303e66260e8c63757fda395b28d013408 4bae7997ee9b6708eb902fef0a8b3a88e6c14ad6 d6d8350f3f16ba40f87c144a96e06ae8dfca410d 0dee32f7b6812ddbc9f6cb987d0eb3690888ec81 7d61776eb168cfb9999649ff9d0b495df39640bf f8808288ae2379a8418165c21cd5e0149e5ab8a0 3c214324994daa7efea6d36f5afaaea1cf605478 76297ac3c2b802bfcc317bab829cb542ff5a1bae f23e4a53c66e99e153f637ad519f102832a6b3f4 f4b1fe507d1f530f9712886bbd48a4f267f8c68e 039f5440d90a3a387bbced8e689f4958411a6e32 64663f67b004d3be7cc29862bd98ddb7adf415ee 7346caab91884aa669a0099415c9740b7ab45d77 6e8ccbb3c51295c66ce2145ac0a94849c5684c6a 1ba7631147f6802510b24a73250dca4dc3653453 23069faad58f3227097d95eadf179aabfa129ac5 6d310013df6445b2019d14a06c5351b4a98a29b3 9dddb389a9c3b70148d157c6df3ade28601068e1 18405bea18c9e9fbaf231ce24fe5d8357d5c5828 ae8a3541b3a67d1e23feb9c32c21de3673b7bcc5 d25e79c2fb127b2a39195ec18e267444519783ab 80bf47430542e5b2fa845d1f324c0c0d384657c2 367971c54ce589e793db0665f54f975ffd1d1542 21525eb838d41f4c508ed7d988f8a739948d97fe fbf8b54792409fd82b03895d1b6232940e9d6805 874767a97458334d8a1c97acef669a3f3df6a92c 286c68f2eb5f1c01c0ddc1c0a6f6ccc0c57775e0 7d432d25d0b65cf9e3b48b59a834256312e36c88 a778aabcb06b5b646c8c66643c9e2c53451ba2fd 709496c34c87e96e48b4b1ac6df9a178025a840c f85672415663732d6055718d20e71099c0733bf4 b69badc3b25c5d1ddcd81f71123ef7bcedfa6b99 68da3d441f047a1011bdf318cf32add665e661f9 034a8bf8214bccf5ec087b00cee41c4055934c55 7804e16d2e079be8b8622fe4bcc99685b8bf2bcc a80f4e5dcba916b872b8b3f95ab2f8563583f43c df9325fb6215f5637ac5d3df8c12e2135b6f2d4a 03f81d4f2eea018ac208e96038f853592a9d9578 df9325fb6215f5637ac5d3df8c12e2135b6f2d4a 1b5db4835f74d15ad6b53874b498cf105e05a26d df9325fb6215f5637ac5d3df8c12e2135b6f2d4a 0ac2841758e216d4b4d2dec896655c6f8fc4de6a 77ae36b4319275b31a89aabc65922fbe306ee725 df8e74261cafd52548f5f14b7a82d831f779e4f5 172146551514850b00b009384fda512d0e19052a b79155d713d493ad0ae3966b3c7c589dd39aae62 d041a54bb5880b637b9c9777f1a0015bb14b80c2 4981041e6e69385805c48d16af25fef5483b1662 c9dbc43e258764ace3aeb77da6f2f7096f23485e 7de51a61f52adea6022d2d9f6c82e8ff74e2dcac dd198821ecbbfe7ca59844cdb4cfc281d66cedd3 bad81b103d7cfb17563471011e011e79be8ad9cb 735f17111e8e8bba434903bdbc7272f8783252c4 ef68796417697bdb41088fbc61b57b0e7f15c66b 6558bcbca32be24d6e842f6f1846b64b3599aa00 3c8dcf4c803593988c865b31a8628d71a9798913 f4ab5556d8724d08f6b798aae0e62173133872a4 ea898f19e4cebde5172fab67d5bfb96e9ce3f9aa 94adfec1b22d8c81637e7e7d9416978bb8c9edc7 a10d7eb25ee62d86adbf648850251da07a2aa77f 5c03ca3710199a9f2941c085fc724aef830e0e5d ca7d7602e30208fb7f0f34aa9742e41897b70317 d24fb670e9e2c222b6a73dfcbb37948a46620bd2 0b3e33aee4a75b05a08728b7cb7d2f8b444d1640 39e6e6bd1586ab87adab546e50de89a35c32359e cbd0c5346f486ff27fed1856fb4893c61f743202 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 468cd27a4377faba7c2eac8dab6894c417e68966 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 3eec907a31ce92089c1b07469f18c9657a56de85 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 a807c01d17f4bcece49bc0fb0a8c1884a155115c bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 116aad1f76d7b3b7384ab6c3e82387540fb0e753 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 8cd8577fb13927996a291aaa381021d295f08c9f bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 f0657c692a3f799e9da4afd876e7b998843f2d4a bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 f775ed97d207a7c2d233bba760c4fefd386ab95e bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 51f781c40dafa78dce34f31ab903f1c6e25673e6 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 76616eb39cab18f8a89c7b696848b65f13e0195a bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 5001b1e5ea56388ba0d84d5c3342b2fe6ab18a7a bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 449d24ae830682d72dc044754abe8dc15b2c97eb bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 755b538cb146b5a842888a589435e69809a56db4 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 a2c8c2c58a2af5a9fc0cfb5a2d399f30a29662e7 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 a636f024a831a6e5a6388c3dab7926522107b36a bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 31fe71fcdd4f65d06a323dcc1beae7bd52f6c5c9 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 1dccfc3c76b20982d7ee75b692b2edf2b3d227bb bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 26164e36adc09fedb81d16c861ef32780abb5432 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 b20b6da7d082c91b060c37e04ebf79795a3a298f bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 0f602ec3347df3d0a38e29e46466cbf78af50d9a bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 14667be6a379dbeeb14a9db6ce24dd08c12d8d7c bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 9f9876445d7fb6187e42b1cd7774e99a26e05480 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 74a24984f41d0214ea9896f1a2ccc28dda8de3f7 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 fb34001a1d3958287ebaef2cfe0d7934a71d367c bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 683b3776c26ddd7ecc13332cf187075def716f24 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 49d17798af7bb597b45e5179b896ccfccbde6eda bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 5a43ee5e33c12c6d0115b9df7e087fe6edb8629a bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 34080664ac12c64b331e3bbd3c2afaad7374a452 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 14454aca79be7d6d5d8d0a50db31adbce68a8b69 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 dd9df94227ccf781be6fe72c60061cf41c59ff74 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 4683d9ed51ea894b89708b27bf5b7bb35f92ac83 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 72ebc25b5c85b949bcd577d534b331530359a65a bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 4d05178dc3af03d2d65dddc9a81b737a5fd00179 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 f70e3c428b541172e94f31928cdcda5dd01b10b1 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 b809f63540770af162d545db91d79bb5d6785a23 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 1df4c98d966c672caedcadb3a7498ca97385e166 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 dfdfc8d079e2615b0fb28b799a9f7f628c0ef20a bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 3bb87e1bdae591df349884bc9c6c264fae7e80d9 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 71bf82018417fbdee412bab4ba3289aa7350fbc2 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 2654b5037ac03af2453ebc29bbcb3b92f0229d2a bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 303585eee8f4caf6631e40df197d887b7eff398e bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 59c8e31d7fcf5067ffec72d4df9095f34c4aab23 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 3a6566773c0de74df1f3cae72892ef0fa23feedd bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 5c5035d784a271e2589d20769fffb420d051bfd0 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 2259d416bedf3f136402c32159c5dcec8c0b11ad bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 1598dd7ef548d5e20fe5caf9563681c41702e4ff bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 d4cfbef5634ae80db53005e8c033a274480ebbc4 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 79fe7ae18727084c27b2b4ddecb4d29ee5c2ae29 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 06608a9096646c65f029fc3a8f7cfb52971ad26f bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 417ecf074ea675eb0194211fb20ff47e291d5afb bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 f0832919e4bcdabfd2f1886f106a4351597d205c bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 92867832b7a3c309bc83cdb5999103c9438f109b bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 1ab7b9a1d482d6cfc9563f11b3fda1fd76310643 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 ea9c079e5f5fcd8b8f75cf5fca9224cab28b7f6e bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 ccad912adb879808f5a489663aa1d57d0c2d5de6 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 3376329092e514f2d9aed5c6799b8a9b5e8f374d bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 71dfdb2991e5788329f6ba61f610e26f756edfdd bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 2786879e1803480d4f7da44932b15107a2c1ed0e bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 475571f05c7711eb2b84e2aefccc665b7a2874df bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 677d7e4f849a06471e08a7d70e8c5fc815c0e52a bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 87250caa0077342347369235752c32c670f0dbcd bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 0b945aa60ed38b30d804fc7e8b0e38fc7b5cf03b bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 503954c425f7ff401c5136377a28db95d769fce2 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 fee84e3496a65622608f1e362fa8afaf730e979c bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 7d901252fc5e1a01c4f5f2cae3730dc8e22d66b3 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 e4529820713217c21319b60ec8ef5b8783782a74 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 923443c9cd9072241293beaf1c94d7bbffabe59f bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 83dda12c086ff9263bfdbe58bf2e6a899ad0c860 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 22a0f6aa73644caebfac29dfde762b0389bdbb37 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 26bd1014b2b792369350817ef7cc0702b3a07ad1 bdd1533b0e1b470a0f3ecabd690ee0db6f955b98 34a5c709c98931e50590e4a158bb4c13f566a58b 6cdacd8cdd01a15be34fb5d3d77eb208c99309a7 f2ab969f29b5d2f6b6a2c64c62138368ff34ca62 b07f814cfdc35ca668a7209bf7f2f7bcf696636b 2ac8410b0a70f67bbde034737ca90c1cd0bce1dc e232443616e19aad35f10ec23d2ebc93b114f326 5a9c273041690504057eb71847bc3f608f1cea6f 1404f2b045f4bde95ae28ca837b0143b29d304b0 3536e49fc67e0073292045ca49aadc31bf6fe058 7c23886fd311db4a0991193ff5e9f116b6dbed80 ec9258eac326bf266b6fed441a992e279b1b8e5d dabeefbbc158727b073119a7fa0c3164eeff51be b94a6dd1df28629d12c1358cd1260ac4f328f518 53816e0d186056c3110c4c33037fb11938e9a899 2ab2c8a08d73197d0323b98f4416d50223372206 b7d29fdd52de94c63b935d2d358cf7e966f970d1 5e63dda13a86a6464b6b6ae51a18f89c242e00d7 785613a8a4f2123f076e326cd42101fa9e0c74a9 8739e5d1edbdb91b9d648501490aac5e0e8375d6 ab33a14f28d5ac2f428f980bf6404f94b3cf3a94 ef6c5dfa43795370819d73eb4df226161e3f8225 516cf71da214ed63ada44ebfefe566bf87cc1ecf 72d6b46688d7cf2f1fce07f6f3c748a3797ec4fc 1526ac2fce118e982b9a4b98fa1a8d67b44b90eb d72cf4f7bb290be218c8cf7760b1f376024c015d 123bc7169b0c11c0b68195bb9f0e9d78557dc3d5 46557535213cda9b3c78df71600be205b02d8a4f 4655c6262af0b84395508e7fd3c936d5a8aa04a0 165734ae2b14cbf8b718687866907e3fe42bfd37 b2aefa7686d3051e48ae73a795992dba1076e9e7 4a92c70558e102bcff249376d2396a6478b015c1 60dcfbd712035d18a55bb9fa02e17119212426b6 efbd28c6bc3d3561ad6ccd43c598a4a4059669dd 6fe0b96dee1639b188f28171187fc2cc6ecfc0cb 16b027960943e8208719d86a546f1fb16ad7c67e fcc1a065a0a169688cfe0df9f34c31cbe77986be 9f40aaf8fda1732d889dd54ff14d232d00469ccf a950fc345e3a7a38e22dbcd2e560def5d6c0976c 3a4e22be7734db799989d7edebc56681935b266c 89e483ddadd844ad7fb49c07e035ce39283f87b7 9cf4a9ec1edef9f9623d673b5cd243a59742da1e 4a4d05ff5cefc16079b61877ce7d2e9f2498f424 f25a6fb0339b0d94ab38cbb3c3865a024e41d859 34c5671a282f216f39d77620ea7de251d3902df9 dd1901b918312c328939b4dc8b24a2a8d4f8c783 3b8df4ae8d95a3124218b2c41e28642ceaf1ddac 42c46e115fc10f4d933b74687d8bdf6c7dc9fbd4 b3c83a4f8ac7740fe4755cf9f4f58c1fa92fba28 70cdc7a8398f42396088b816f141eadea976ffd2 fb2a154a0bd2adf43b22d5b5a18e3f763a435383 cd6dd66b67b1cd9b97c5adaa9094959f49aca080 cb88bf50ae9882bc61ba5c08fd848be2f9497e11 416a6f2379a609f03d2c6557192b56f741c04990 2f8d91de0d8b25a5f78b816685c034366678db81 462b66d7e29f8094df805574663bd069a81de971 d73f8f90c85d50928c37994fd4d80e9a136de6d6 c7fa164c1701177624230dafbdac8fb737705432 b8f240affc05eca47698e30813f0919b33d0ac59 fcc88740384405427031661d64f06b59dd1e2d79 5626585b0bc08bd714db2e0ba35645c2e899e944 64f441817a6a6d22f1d543b8a3350a7d67ecb434 e6b0a70fc4408b35ad9fc22082af16439cd6c962 e940c4c4a23b0279a3d2012aafd7ee702c57365f bc205356e3a72f200de9c6293505ec76161d87a1 70efc486dcf8b80e23a84772a524427f4288ed31 6b8a94e28fe081659d2da1240b3a2010bb98ebf2 ff9169cf7d443e67d1d51afc903c87ed669a0ca1 5168d89d0e093c33784562d2acbe5f8a5059d61e c3028d2a9cadd7b58a584effe81235e172848e31 4771654e2a518ef797207ae5692e00a537f020c9 04366302282927d0d46b9ac9cfa8ee77c4c753db ae9f288ec30e0d5a8f1595343f7c1aede0cbc6f8 69be188f3e6e3b2c287d8ae00b3aee605a676c8d 21a5da904c001ca89bc022ed199a5efff4ccb09b e7d68f07ef60209a7bceee47a7db15273fafa5fd 4a7ee0b16c973e5ac1afb60225d5ae940767334c c567eedcdb0a168cf7a3e023257702070213a7c0 eaf594bf2d597cd4832dcf33b9ec9b68862214cd ebe70ab7417db3d394c7412f7af79e14992c1935 77bf1119ec0d9b84abe3faeb137652753c728d4e 2512d0af8676d1c20af0789713c3e6448ebabf3d 4ebf2b49aca539b9170d268150a87848db84f3f4 8e3fbb31cc805ea4323de49e6bcc8e7c8b30cbbd 4b922913a76b930894211d37f7cdf8a04a35a532 8213195b860e10c4d0d4d6ba6b76f0eff10926c5 d8012d65dde4f6911250c892f30af7f437efc991 e0dc9f569cd7bf643452c8f5fab644291456cfdb 1198ebd645155389576e1668463cecb599fb146b c6d9cb7ba777420e25bd5947492a560492d190a7 921c7fdf51a4c03c4443dfdb69d75557bd66210d 043f0537db5d86f264b2c5f84dfac4d91bb4f07d 63e0fb909994e1840b70a137e4d43f5512ca90d5 cf3c835487fd55dfa0de9f49bfe8533bf8d201f4 21db58ac9051730b7616db15e4cca4d179a07059 e899ee3bf82fd8030ccda09271b395934e39ec80 5bb00b7abd5633a70e1fb49a65f03427a9625b48 4a9c7b6fc1b7e718b516fa224c6d8b8e1662efc8 2b6a181714383b8955cf16e4a70ed0f36f4e44ad df9a626f0a8008265f49aa1bdc9c8c8f85c2863c d426eae496388494ece6f66aad7a3f883f330cc9 5e58ca95316977c5e578565f2460ca2cbebc8a2d 2f23e745f30929e906e89da1c918f0934a7c5830 a49b4aec010704e0fc3a345ef6fa79b4e6e0c64d 85807f5ed50ea077498b9f7b7baf127334cc431f 217c5f6100762b55cb80d4976a435d153a5264e5 a16e504f780dd845536842637b68f9fee64799fd 74cac1b559d1c3eb408c329ab0053f59c8169c44 442916d32f801b248c2a125912dedf7784eed411 dd7f0cd9ea3d79a9971e29c91efff9f73680bb11 13685fcd565bad87ffedd34ffc420a01f93d7717 5fd2a61be7a5022ef42fa6b29463838bf5b190f9 abd20ebde885f40f7b0e34061f0dddecb1a3d881 ec6f8388053fe842e5cb326efa4db5af3d0ef431 bc9d84d94da89b2d96a61c77910d3beead955dc4 c53c37156c9240d00bca43120dbf9f68e32323ee 927805c9777032abed33892f3ef2260abb4e89e6 1dec7cfdba78915dce12c67f4deeeea03922a8dc 2e42a67d82702395a03f792a1141105d8d455b31 889d04fb3522da806217ead84211d21e2d314006 b551d8c79d820b3c6414be1b09514487ed43efd8 dd77acbb83f7710386557d360851f27a8933c758 a929d5aec037e5031df73e302d80c98558e5dfe4 00780fbaee399f9ddcdafb0979289b566076f93e e77163bb967360b1fe01aa648842b1e120ae69a9 8679ed051764f197031d84cc410767c4e1699fc6 d8bc3ef2d0754923f135de580ae1b79876572bf3 2c12221c4a737e212480aa2bee0412057d0216f4 4ae72075ba7be3db9028cc414a3c2276074ec711 e555c14cb2f2b3ff3ef4c1d4677fe35118e9bc26 b392f291d8be2717473df7a671c1747c6d5dd0b7 1a53d8b2dcd7a03c3ff1ea31e47f07cc72676d20 0e42e86a5c029a4a3f0bf229dccc42e9093b5619 a946c5764c793b1d60d1307d131567cb4024c5d4 336c8d24ce03e92d3e1c642ed6529695487f9531 146753140c92a0e677ed2da4c5ca508322dcca15 54282f9c1c2e564a8ca29a1e13da9e1667d034b9 3f97934659f9f40279a4225c744beac6404e7bc2 2a0c3ddb2a045ca82365fc7c01ccd17eee87cf27 c74c48ecec8eaa8875dfdfa096754878ce691e45 4bdd2d1f11984ac7ab60bb0427aa4c9d2e1aeefa cc436d27336930035103fa18289694bc10c9c88b 0b101b75b2052630bc786fa7b9467a28684d57e3 16e76b94e500d2354f98e52ffffbdca17ef6d71b b80b30dcd052e0a9b8cb9d4c192fee58792ca123 556f08b08c54409d9ef24ad0cf746c46903081e6 7b06f8006d401ecf236f6f7d0e62212dce53c650 f4405b7674a32246671361a5502dbc19ed07cbc6 41a74bc8e454405e38b122d66d7ab5e379f005cc 71ec8c36b32d2e7eaca187433292f08478d65845 9e0d1fcf05c99cf2e4a8adb40725a2500302ede0 9d7ee9d45ffe75d21f02ad2deb61b38afeaf47c4 3c7f507aee31ef3b399430f1c05146bc5dd4b3e6 589248058eaa3da98b0ce901024ee7dbf8b08e16 71fc1241b710f86b781aa5c8a1457cf865186cfd a09e4a4f0c9d3025c2beab5f40a1533a53f06d00 3b9b13624209bf00dd35057905a21ecf88fbeae5 9d8097e1dd3a0fd393386694f6b60526bfe5c0e2 c87cb81a2ce60e708af9fd7574ffb982e9d62177 6e078f1c4942437803148fa00d4d3379a82e6b2d 5a5c4ccbc9f261896161275f961a4b31e838b3f9 911549769e8a78da73b937f82a8f563fb50265cd 70a14e95569dc12db29a0110e70312406d47361d 1cfa3425de2713f553aea609f86f005ad6b612b3 741b959dbeb6910f4f787faaeefb1b7c0326c684 543018bbb890621af107768a16719ffff62063a2 aafd31b6c9a0802aca012450b46e42aaa021e718 fbc868918cf7e3c2e0983c58502b6b7bd59adabf 9296354a511a9055b139cd3e617fc8bff685d6d8 d08ca0ca87b3ae05fd58c0890f77a9f703db70f6 55c95c5a4dae8f2c70a026583164a9dfddd8db49 f6b3f0c89892eea5e388d9d3c6e90c3d5da9ac09 66497a4a86b63fddeff2046315ab3952a850b4b4 906a4f95da58144a2907b4e00885be02420620c0 d5960dce4d89c12a0f27d82657311267a2774b8d 20c14c99b0a5c824ecda1acc7c763dd68c35fdf3 6cce41f2029698baa071499cdcb18c5491ee8719 6b3dd855918a556b61de4538423f99be9c0ca177 73c55b7f89c849e79715095d8a6c9f05d69adb70 73ad91d942b3b03f086a5f33baa594166aeebee9 51d65ce65a3cace19b38fa6af538595d7954a300 adc6d6c4c5150076df9980cd84902a5e32d1f2b0 36e5035707cf4b76670da2cd23c07153d0cc8106 13ff52c9cdf1fa9454cddc26755a5abe0c134e96 7d63ace41cfac479399c57bc73582166ee1963be 5d0c3397524947ef91b9ab2a269545ff3db492d8 5ba3fedbd58571ccd08aee9e02d79f960d240fa8 3e86c3bf1e8e28ef96118731c3326ede2fcc8c3a 4d79fd6adcf6a6deacf1a65749531ebd6709404a 1c2921b9f36c278c8530a45e3c7b2a3ee7261c58 6c0a87fb16469e4ba9e1cb256a17d81c3b6fe06f a1b7c141121b14f8bf0dddca0929f0e67fb6f88b 1b9410a443ca91730cfec4b0599fb26dc285ac8f 6c3570f0fd5c2c5ca398b13f4e96e3869740291d b9da877c793de579940557e447a79656a533d4f2 ea85fe1fd8154ea00ddda19087ea6936df718716 2b54bc317838d97f60e4292576f8f6976302f42c 3d67776ae5c3e9e0089f4be0b45e0e13cae3d302 2e090c9d2dec7a4d72ff24f1de7f1c7afc170e15 f5f57be747224fc60d1b0716977fc71e694e8b65 952208f708210ab344c9d62d7262b1e3e5afed02 616f25cfc357d74be9b298d31f03792274a73e0a 9ef77132ce6cf01812365877a9105eff1122eabc 3da749db59a71057bfd4a24eded8d61418b3411c 1668ca854ab970612e82f89423ac190da8f04400 e0b3a3cde421b18bba072dee4211d6f1ee252a24 9878d68f5e788233094eec0f7d87cb3b4b611cdf 03855a7b4676376109826ec783447cf913975d0a 17ce2ed998540d5b3da9704a95c5a72a93042dda 680e6a3c66706b6c82ea76090dd1685be45fc84e 69e91cfac30b6e51f104c6a5ecc9fd1cbefa5b36 c0ab7063491b348f32317fcb63bf7fb68c8d1574 fb5de4fb355b9fe0ab4d195937773a770fc3ecc7 37900fae2d363b8e0ac5eaf8e93f1d3014a6a394 47106ba2a2de9f6734b848c6d544b31697f34fc1 c377d222ca12f46cb24d2b0b1e7f38c4ccb32487 11350c52e7cc31363599429d71dc0695eaa29dd6 5353ec9a050ed18ecad1386362db1fa48f58f067 6b6541482815853afaa81d820be9b500ccea0015 22a4bfedaf89c15c7cdf66b38422c0bb1b7a45c8 662dc4b1f3caf0ba986209263700e208b033ec4c ce67cced118f64e31425736cdb1224ce356f7910 9c2d2e7ad5e6eff18fb6fd632121a2838bf339d9 d0ea977217e6f02201d0cf04dcc3a3562a1ef1a7 8ce243a467e5ee23c63a46a0a31f0754cf73f27b b49269498696ef2834ddaa7b1c27d7d655f8d0eb 0b0f323ad6a61841baa2c1a188515b469ae99238 f3f570705fe74c8c86a167a1d3cc3c530148affb 9c6d112ed5b67bbf571277c6f1fbfb6aefce0412 73a52962b097966aa9653e91702559b9bfda2bd2 4911dac6cf53d427266b27148641f534a72b9f74 afd89ecbd727c8af25760d6c2ef1ff3fa0b2d888 ab2014cc4993783241e6c8e4f4b2a2ef60c8c3f9 42b9376b114fd12507117b3f21e436c0efdd24f1 7316aa77b36aa7fdbecb185c8206e063323cd706 e842d927263bd8d2d87d118e9e4da36c19dc88a7 d03686e4dc9f707c1b97a3c400838bbc5d6f1624 f833490d67411f77fbd07d57fff0aaaa9fadaf3f f9c49dcb67716ae4739258c981958dbb0f44a50d 1b27ffe9a904f48720b500f66b1ff9ab06633a4f 3055d1ce4a476ec317506c1e028eb583080fd61a e4fd26d9ff2721996988cf088afc47dae0b6349b 1b93908973ee1d0eb655a7bfdc5fe68693b15130 607c9181be8765089ea1da6f0cdd21f0143ddd30 3e41a3af03ebf9036c468595b8382502b0f60925 ffc00d64ee93c8273fe7b75e0c0363ef964946f6 3d4c26917b46958cc5ef24183ca5f7251d4dac23 0d93a978ad858846e5b8aa337bf08a6e3cdb9fa3 66f781ef42108950f3f4d8bb72e3e84488d9e237 42c8d0551fc8637308d492f1d70a5e476004ec3b ff0f468c13eb260925552eea990c819c3791b618 6e0fdfa22eeb4fa4dd8aaf683ccba2237b8abc53 f439c125738ce2ed002670593136c6a4a014ffdb 9548c798d3f8b3bd481fdf71c7163f363e435dff 3987c068f884e25b063f313421e48a16380b1d33 cea0938649bfbc95b2ed05f67301edfe9a8ae335 4b1b803581afaaa374587a7ee4ec8262316cc6fb 768b047b51ac5009e6e1074a74e608cf5e204ee2 4bec18e7cbf4fd7f78d845ef78734702b26afcdc b1b5a237a8ca186f68a175cbb6cc3647f71ca5de 09c4295c4a778d135d5191236fcce91087b62b3c 27623d5a923757f1e9a65967499912cb710d7bcf 9ea9ca953b088e638cb25f425d8481d8befa6766 7853d8340c0f96a45d68befdd56e104819700852 7d6891e323ca1a1824cb5cb2b60fbc98a8402e1e d8e611fd3012c79a543397eed89ea68332d6b8b4 4ac71ce48d54937e56a037eba9ae5e553f6c2ed4 0f9c7274e4352903f6db6ddcd4fc54c12822145b 6e4a1e6688ce801bbc7fd027e67759f9b8fdf43d 7f95dce5ef6646ecadadf88e7c3b865c0bd6481a 17384372f1d93e176ee33c353b6f00b6b222877e e4f6f7346c697c53cafba1bc0293894d3ca9c340 7106b10b63d8aceb3c532e926704be0db4ac2647 9a994e8c06e4faea75b7ac92fc16abd711e08fbb d45071684e13aa92e6898fedbb23e558a51c5915 15d1bd65b4520072c53432d751ff88510e181ffb 3ecbbc6b051d9bb91c5533852a6e92cda9d871f5 3cdd3be89edd4406daf97d9ee022c73e6f25bd98 c2e17490cda87a37b8f6b1ff4818a7c257473c50 94114c042f28c2969e6e96dab1f11247fbb071fd a4072d8478de125c805476d7ecb28947e6c1f8c9 04e99bb9cd15825f4c2902fb597e7cc8d119dd35 f0735794cb1bd814a813413b848c2ace8a7a2e1b b0840cfdb26584396c6a8a9668f43e8967839bf2 6d1a4078b4abfe6fe441741a59f243be02221cec d87146681af594494333d80dadfa56db050c61de a1a7910b42e23dcc97e034d51bf81f8118ff5c60 9fbb716966f02b26f788912cd88845ba8c1d64b4 1c774a2842793919b822218980442d817a4fa82d 1b2665163d8cae32e3e485e91e71ca7c5433227f 4d7fa28996818511a60a6e1c357d8caef2ca27b1 5030c7856184162115795d290fec8fff6da3469d 7056ca0febbbae2d859a479b5bb75b8494c7b594 7a9d13b116cf3259708fdd9d9c31c3476a13d8b5 f0a624627efe8cec083538fd00579580d838152b 32f229d603080e144f01b9f96ba5109d0abd0445 a653117c1c78302dd9e12f1b53d43240112ef5c3 ab500b95e0eef098d3b9daf3921af2a4f2ec81cd 62d64f21d4067e8e2923877443893c360ec485c3 a545ccb6241b9907eef201b3c117a0ce6664b324 7ccffe8ae3dfb1a6d2b81284cf9ddd0a38b14194 1fee0f787e575f371244570f8b583f042827dfe6 aaa5f0e44b4fcf959ac20b61bfaa02006f34d19a 20afcd92491b4d33bc4f19b6ebd63c3241bfad20 a60570dc4bd56b9e1c9cc5a109670fa492bef8c1 b3860ad4a7f18ed4d27c85fba732370736325aa3 58d0cdcf84f0fac0a52f1ce5694840394fcf7234 c94855dd806310348a894ae04efd078aadb49c44 5bcc5955b7ac4a6252a6e2e29a32da4c5bca314c ed665b85e8d0968a6ba081fc0bd39ff4c0da22a1 0af979e9d947ef01bb0b7e4ba5fa9fdc3c0b3d6c ce270f3beb830d8392d99f31be86b94f6823734d 10bb32e75b3377c59f2a882522a26b0a584f729e f701ea8fea094e13a87a93287c843a6df1f37f0a 1b35c2a0229a29072ec337392409b96125c64018 c3cdc4e4165e02e300dd533f5af9bb5028c8d404 dcf60ebf1b33b9ebb7c387615bd3163c06c424c0 79c640bc80ea82d0fada5b01a189ff9daa52ca34 399a5bedbf4b8b6abea7b85a671c4f6ab091a6bd 700612900b37fa8d984715ac25da8cb18b16d210 5e7442607b9cf87ca0de01017967b0d6a1a8d89a 8ef5bc0d184febeea7b458344dea8f692d7c8009 f618042f3d9e954ac2062b1eb4809740e77037e9 12a0a3d8e8105bcf7cc519838dcecb1060d5709e 3d8efb6e009da9fc271e4ac4901c25017a9d84bf 684625bf9a1a9d26fcfad6b1db6a7bbd9cc7263c 460d2ca09d20fa1c0247229db59358bf7f3284ea d24996d8d029ad77e2a0b758b4474c84f1c12bf0 9198caa47b018e778c14b1ff2056ea872ef01846 da2f3a875b36cb5bb98e537906a2f98e074584dd d10644e5e013ca4ebb390fca67a47b9d00c072c5 b5489dc35a4e0121d90979f5dab2d6605e436bdd 63513be963443d0a63be8ff736b96be5b0c47508 2166f443d615e21bc6e5465eb09694fa96755fdc b0bfe206baecf804bff77a9ffc641ca464a7b4aa de1877c8c9d63252a00aef1178d4072eae7a1c58 a2195289c75b1ddcfa18a00489b9e451771612ac 8ca6b70c01d7bf1ca1830b30b239be0753968676 b84253a974fcab711ca005c2f5584ebd5c83ad80 e30f19b0424b73d62d0fc5737cf13fa4874c73e7 0790af9aa3deef8f589e05f7498640f32375713e 3f242dcc1f22edcdb08897465f569f1bfe3d2c8b 5761e71c3f50aa62d0a9cca17551bc05e98a0372 1a6bf04c66fadc651621ac6ddb0de1deaf25fbc5 dd7c7a64121dc21fc4e40b8b2e47f845a9172ebe e630c1599d674d5fbf15f0949cf978d95205c78f 3fe11fe91872907e2d1437c41a27901f4ab4b126 f8810caff2799b98a7ddee39f8229f9eb791ee90 acb681614fed8b30e0f1090503c7a3b20a72beca 1ce961cbb4b84486e41b1f2451576ab7ef290379 ecf036384b24dae27d31b88360ff52453ece966d 2f6b2934a20716c142f75ac1f05c59e4809a2119 057cc6d85a004b31bf3905af9efcac9efaac440e 0ae4120c904060ba6a97a36195180716de21fcaf f708a4307f15ba8c4018cdcc8b635162297b9dfb e8690b575b068e0cf70629e5a331fe6c4da30128 3567d0d9fff8c7717b5800c227cb3c91e8fbd385 26ec6614a1afb1a6f643802c73585e34f4bad6b4 433085753156e352715badffc23c915d30f217b7 7515d06b228483c0e1a113f89b3c6cab5bfe9696 acfb6e2fb4b298b73ff8297a4be440f7c4a14e8f 4da2efa4bf8f8793006e1d7e8b1e2e585fcb2837 ec5d8e6cdcd5878187ac4ce3d7607b012340aeb4 0b29d67d42101f0b86b748b58bf95795e711ef77 71a23318d9fa871f123f976b59d2576c7a74a426 b263a58e4a48c9627e4c438c264b1a1feef499ca 1fe45891a63c56c8df1edb01035297c8d3e43633 b3672c4b4ce5baa1ce7fed5021c870bd1f7e5318 e223c8527cce4a239fe6c01a12ebbe121bf617ca 2148fae72310871c95e06246256d4cae2ffa2869 53e80afa1fa55503b79519edbff183a2c1c957fa 06f681345c6e539adb3d239c29251fa2b51f410e e814104dee253bffdbb3d69c5dc57b65542d24d5 99f2a5c66f27cd2acc6b01dbc614c3b2a41898d8 c3e00fa936868fedb933f808c1e03cec41c14cc6 0ef45c839810e085129b68891eed2c12f23ed7af 6a15e1e7c21cb082efe0baddc70ade6d3099558e 1bf78b8bafef08f5e2ccd0eba94cbca01f81112c 0589d9a47794e380506ec5bec8c0b3010f578d4d 743125307fe74c488eb9f7a03832c5ff0c14c3e4 05e2141ecb7d74a9066daf55141392097144e21d 6929c27acdc9fca9e31af2c66e462f1830fd8432 87c45de8ff359f652c4cd5b89fb03bdb6ec7271a 7a14276b224cc3ad6b71b2cdb6d73df7e92f0096 516465319da7843f26afa0dda5efc1bab7c668ef ff1b73253239567914fbec2aae0dff923531fb10 95a6158134ca71a8746b039c7d998808a34282d1 c08941e6112b52cfd39da0c6dad1026d36bb9c68 65a1febefd340c10af23cd728e92b3bbc76a5c75 b37577197f7e81e9cc5b9c63dcef6888cd5615b4 089348caf758a1f703dec45d7f275d925bc48150 7823a108a00ba799010137462740c33d34297492 74db5fde78f9b7a411d7335410c8df34f29edbb4 667708101939194ca483f2ce9cdd64f233a08574 acc95a38a15e13c57f9414b237f059c77803590d 52cb5a25265abe708dac54204b7ead86f9214e77 2f81979a2232816193a18320bb311152e268292d aaeff6db25783dc857d40adc3ce4401b371f29b6 fa3a0567e85a269255477331703c1bc9e0370191 1c51f078c784f1e8ae14dcb66d18eb8582157551 e6adf3452ca27755e26dd02f9b0ca1acf30c89cf 558a2720e0f1714e529c1202f51aa35802b93489 80ebe088bf0196a38119f572b554340a8ccce259 9b759d5191e2a5323b88d7668226fd9ea144b6b4 1834f2f979fb64f6444289fdea9f3b8fdae0bdb7 6f76e6fd41ab17cc9086c4e8de1dadde365830c2 6cf9ec7fae11690f787492d25efd7849f854d57c 0e00af87ab0659f6eb8276cf4a909510fbab87c4 3eef2eafe32b7c2b7ce130e2a495790915d10385 031617cedff3ac8725c70814c6320ce6041092c4 cfa7e7734934734a3c1625fe5cdafd08f1439ca5 3064fdee60b09f02136b54890642f782fc27d60f db8329ec5e4b634fd48769c5ae0886d6103d8d7a d16f222c8d446405311c4e223d121373945b997a 292058c76d70692a7ca25c19df5d2daba3920524 2d2d01ad2ec54ba1a73928069a17fe17f09d54a2 8ce420d19f04adbb6d1b13a2d930d77b0763fb07 45423de79effdd4c39f41fcbe702f7d22c0a4c6f f8533fafe42c5b7518fbd20b7fee072f960113b9 be61e2bed030e001974074d8e6c848cd05fb35bc 6a55eb392d34cc0d830c7617e39c0157d83d4672 2a804a48e36da03630eed5ee8fc085ffb8f20a46 a77d37720a57af4b7f1cc36883de1718e3052223 f1bd27bc249ab3a5a4abafe2225ecb36d9647c00 d338de24ca8a279f189440fd97cd4a642954f3cf 6c2f19cdd2c627d82f9f998a6bec6da066825fb7 f1c83f8788bf1a5b72c7df730be27fdc8050533a 9040ba4422b442997ffc75d2ee99fedf57a6b2a8 863766ae3adefb9c7f64b26755588daf76482c8d 8cb525920db59ad9980c71afaca1f3766ab4ebd1 f3f5f14d6c87eee3b233a832700908d59f465732 27b6f88cee1e147f0c37911f970f09c817b09f3d cc0a9d234dfb5f885835ccafe40b1bc3a27edd97 c85ba0f4c53247739e2ad7c78ff55d4d827c4407 2e1dc705fe34ca09c92aeba2501a2bc2f09734dd 01e5b681b92cb9b4412de2336c7e71adce32309a e2998bbc6c66b2700699f7b6361bd1f8dbb6d630 c223441a5d31b638d719178023cc66333db0bc35 5bee37c522fc2a92d2ccccb30ea9e0ca7979a897 5ff5c73e495bbc35d1d97ebf28b97e98861e4f5a e7588310ff987833782bf1a96169efc525f8a54a 81517281879a368be83648bfd3a19765bc31949c a5c494724c8d8f6cf5753c9fbcab9d7cb657533e 7920a39a379c9f569204b2983693c30e7ba221e9 a941b2075d0635658d351f814cc859d14174c007 4a42bfcbe1118ef11bad1d6c7687caa6426cc9b2 e018161371f11ac1f29e0fc51f0392f39f89a40e be7dd6979d1159798f9b9c26783b401fa7a66c2c a810877df1ba578e6643cebf2e7a86084a8068d4 e90102d055afe57b6d157bce47d31c9a4292568b 3dd32503b672c6760963d005f098fc1261a42fd9 3335b387c7d239f8390913dd4636c70ad9fd1b3a 6afbf40376c80772a48a4f8f30a617a2503523ee dba35d241916d1a9f0725f67d1ab201c08cb39d4 13a82c129df10e4238a1f602b28bcf11ab3382fe b13b95ef169bdd544e3f322ceff32bb4a8a10e18 3b55b264b7e50cbdf599927bc2e6f2a2d443f290 d048667076ea32630c1f2f478dfdbb924334f3c0 3b55b264b7e50cbdf599927bc2e6f2a2d443f290 282afff04687c7c58160063ce3f03f5fcda1fb19 75bc82ac79b3da69d63ba15e966c724ae0c02a26 0b6b3575b981707f308fc3605264d4b6ac5c8707 9c27d1c83d91f79e6c6f8d253bf21f0664f7eba7 8b7350da9fc5be8b12552cd1c0c7fe35d9a245d3 4d6e47e522e84cb0e47405001a2ba69d735c4dc9 1ada264aac33b3cd58ba61d8866c63e26fffab20 7c68d613b6b31024bac030e5ca7e0581272a7a8f 00ccc7e3a02ccaf02875c1d056c1ff40777f0c0d 35e42298912bcb5bec97145df72ee47c69d1aa3e 09c54f758f7480ed7ac1d5acbb6428b9e83775e7 d59a99755bb8e1119842569c672f25c9d31f2813 7f168e1858f4fea8c14b247f5ef40b4244f94511 e0c30c1d4f558e4c7d755ce684891578e2c77093 45f611a9960b6ed36032eebd8aeba24cfb1c733c 8851e30aaf45c7cb8de685f45447ab884c0692aa d887d0aa8e841c1bd85420eb14f55600ab8214d0 21b10feaf9ebc7510a043edcfb572dc9983a028a 91d8a2eb41e417d2142d4b4f3996107facc1aae1 9f578216b3f7ad578bddf78d47eb3e5f65d806cd a665b9c8654840487458f1031caf1cf20f9b9fe8 7ce158ce79ac88570801c91179732a8b0bad176a e4cc091e185891e020bf0ec1e0849189ee21033d fd4dc3babcc7cf26b0411fc18e31a4e567ce5a15 d476eb3525aad6040a070e592ca4ecaec1ad2c3a 815585581377409d98497530d58bb6e62e2141bc 3fb05d282ab33f66b4da89f5e1138a0a004efde0 00c3d9c5bd410fe7e1b3e6358c4f1ddb9a876e05 0997baf2fa682511a2484eff8b95f9da36ece338 3e433233f199315705e690a306498810ffda1bd3 cf4837ae5cd38b2b7e1db8572d3237a5d71c35ea fcf436bbb2609f558d79f4dd215f69cf2f018795 75ce9f860c896a72fdfd55cb5bc060781413ba65 31f0593a42c27d2419eec68621ae5c228bb18e13 f883b3708318107a1e50def6c43dd78434062d0a a94c6c04e8c11e7d2879140811313c484757d617 d48cc6f1cce26f5de13bd8dbfc85d0d97581a4ad 675a314ab7a8222bc97d3e272f828bdf81c4c868 bb371fc05f5b4c58dc18d62f9163d1450635fe4f 85fd6c8594ab7b8d174e4f0dabf60923f0ec2e47 5afd2d6f8f4c623bded05bcfdedb2d00c4f58247 cf653e0ba8a91f38479a5d6e8fbeebda6409a8f8 2c47ed8955d1b2ab43aa68a2d3df5b7b245aa80c bb727f02e5d0f6d1ebd76bb5904b01b9f53ec596 d0769f40f16d20ee09886b8705e723b4a0a41631 4362d437c8eedbf2b97513eb0130612008f210b9 c42526868085191ca2e089c6394671da85ed4841 d5975618ef1f780e071c713f8e371efcc446200a 921c26224650390052a04624833b35f5a2cdda34 41631af990c9eea5b52024f94c0d6303edf87033 39a3da8880a729090d2e1b6d56b361ccee12613c a3e846dd0ecf36db2896e3b32187ca5e370d06a9 2fdba7fd42badc7c86d20cd5bcec0a14e7a176e1 c6c361329c8feaebd0e62eba4aff47d39203c3d0 bf6c26a5a1fe7a8a0f686f672b8285f017f8bf0b 1124eb01fd93b4601afabaf3c954f5c3117f2c3e 856df189534fea589203cbc74debe6c53b2933e0 37d39698b55fa3530741ea785b88b91a121a6e8a 1f0de3ff78a67911bbeb77d90eb115d884dcf1c5 213519bc74ffa4877f23cf6cb2f85d47c08ba5ae 23a3a3ff2b3b6a82b2b9e09078dd09b6ed82ac11 baafa5089f92c3ad8ad0572cf80f9a33b95fd39e a22500102c64c42e2db5b16673f2eee2673a7425 5ec0a6d8da5e9b7a027ef2476618ccece1e1b1d8 e9642100117044e66af5c88828171328959a0cd0 768760f62cd0bb33791f379245dc04516b5ea13b fc9b090d6bb765f8c643a920a8aca21ea754cc32 e1fdfbfc02feadea1686b4377177654ce4ad9240 8084768736841fe9aa79ae6da18ba2c4c284a3f7 f2226fb8754a28d2faf9e5ec8bf0d41b063d765a 91c408781e68a360de8ab6f70f70015b8150be67 19ea785ab7ab309b1d1e191dfdc0bb841af1c79b 256c3f3143740792f7f18b8cc6b91c218d0313d8 c6da44bb08ff220f1c758cc394549cdd053cad50 6ee82a8d62ddf59f93a990359ce959ade1076fc8 de0ea434ca8cd98d0b33cc29a2ced79f1fd0d4a1 4f4a4541b97a7c230f991f924484b71f39da701b 26c1ed8d88d35a635ace7d23965feddf5d5686da 4a9fa6aea435980875fda4b823a73ea46d25ce3f e55cb0a1a3d2a860a275bb3480c6823160f7b387 30884edad2ee04e3219ababf80158ef24c6660d4 3eab20aa648fb2c3c59f3559c9ba37d5cc6639f4 dc80d10de7eb5c4e24f8d29c34a7eab52f51826e f5d39c35a587534fe222998f6de26700d771e48e ca06daa965b8633e351c074ce8bf3185ba8f9b0a fe9d4f6941eb4644e426478efe2c76834c5656d1 ca198c4bfc06d9e89aac770d2e36a9bf9fd16686 fffc964c874a9d10f8888e2e9ea9443f87024dcd 970603a0e29f2514525f6ad67d8e74351d830fd4 b7bebbd8e4f7a7ead37cfb0d75732491d5048929 2967dc7b477edf4acc26f4a7ce4fb457b20cf549 c838d06052db3346c0d13080397509ade6492c73 9ed0953ba538a97fef128189c4686aecfcbb4ba7 0865437bea1dc362637c0d641399fb97eabf5992 ef33312c206ab1f50ebbc4cc821d09deca2bc67f 82c2b160d789e06ec85e89decc98133f765ce27c bb69f050f315462cacef8d9557cf8e78d92fa746 6b09f6a3f9c1c5cda0d5d914c862248fda86c5d4 886bbc6eec76290b78ddc28ea3ec0a5800167e7b 17dd4e89529ff5ea8ac45ba27615640fa4661883 e1089787793b197662c0481449dbc5a6c09a841b 5d6d80a0b4deb1791ac1340bae41b31944833280 50e62f7588b715854cab41ccf6f75992bed645bb 91ad596053d30abe62e77a687c2838e9f6db3ed9 983975cc5ce2bb5cf43ab362e0eb29543d0175d7 d018944867b759f04f183c15165994c95228046e 9ef33ceff744cb41c384e20e84790635081e2067 b0945a35280953f90b9dcf10d79b7f50961bde7a 8099ce8f7e624b853eacca0c9cebdac89f829fbd da0cca873d6ff121e5ba6396420126767246a696 777a915c8edc99e3858c6a07c2f56644e6855fcf f33b45310945153037e248edcb93270ef60bc0f7 214bfa2fdf80b60f7d82488d43d4b02b5dde63b5 08dbc923d3692cf08d11132c3e4e5310a2cb8a52 4651e714221fa4df66bf537797783249cdd565b7 dbc42d57e9744c42ceebbe66b5b180b98ac016ba 2860d99e23e22f9a1f36dcdd52024675a492a87f 67fe6336e67ad34c4198c14e0c32f54a4282c1a7 0fb29f6679eac4148fdc32fdad1955b0db9b3e19 d0dfdca36b2f70f88690430e5e40d3ccb54fea15 9b83e45a0fa75abd04d6e427ea7477101685c8dc a3b9c9b08da70cda3aee0430769fac2b11543ae1 48000fd0e6f32ec0ea736844ea5da6fc01db7699 df8c35f3d8ff0a4849120afbc1c26a7354a21f25 2cd9310306d14aa089bde9d61cf184704c8b0bcd 4692ebc8635007dfdbd26bb6d0f163a76c7cf007 ac9deb53d85c68d7ff4585fff00a706ddba0fac1 f3e391af39dd0d1b38337f523eba860506f23b41 0ab21328ee0b32cdb818322048368a60ed665729 eadd05761c6483212fdad167931ab2bca85d1cf3 6dbd16e82b2e099493b71276171ed119a86f1bc8 5e26942e13c535911623d54850df17101ac9e808 30117454bae3ceee5077137cee2dfeba9857b31e 6a2afa23960b2cfb05e107229a4bb1b9e0024e52 c53434dae47ddd0dd06daa3bb60f89416c75626f 7e32ced6d3b7684527ecc48afed4cf1babe25faa aecd87cac282ab85f757fc3fa7975b99d397ff56 72063e22d1112180f3d69d47815c548c71712fa4 3020210874f5bad659ce2049fe93212f42217fd3 9d10741f232c66e03cc68f2b2246158b18a3d5da ae67396588a43a16e719ae8b6861c085b3547b2e 7755329d0d9df24e3f5c0965c643f38e0bc987ad 97a404840d1f342909cbd0f8821417f506019b76 9800d40647b33e83eb462920f439c402d63b4310 2326b0881180bdf1de442e3a0e062b2098e0293e f450dc97c96220dab1de1e06ac3bea1be2a7b7c6 827cf34a8cf6d9f3d643d94e3d4ab44ae50eaeec 2cbc5dda462462ba13a9a60ec25574edacee798f 6efd15417150b40be176c59e57eb0bbb0863c1df 20fea21eefd6304c6ee3df4530cd0a2730bf6c5b 3f0d1fb253eeae4425f9a8fc4a23f8d08bbd950d 7ea38f1448131cd86603c802236a2ac6de6ab119 534501434d192fc99aaa1d2d816a6b2e2fab48b2 d4ca1462924567268c093991e1fc86ecd3eabb3f 90cf1273a25323a51a9496d58b5bdff5ba1bc128 ceba8db5c4821578878b360a9aacbd3bc16c5be2 8d51f3b91fc08ff1db8ab2d95e18f8cef84ec25d 939dd6b4df71e8e6d777e8d96229c10f93262b51 01f5700bc724668e84f8e5060d12eefc6a7c0d6d 5d26bb9aa4f772e5a1d94c4ebae4464d8a70d7b7 ce7101ab1755c312fa4bb843dea79f81999d4281 90bb460dc0e2019ceb2b952164a7ab405d051ab2 7cec326d2364695711560f98b2918a5caed1c683 1851f9b00591e6bd7cb0e32474909ff42337e607 2046f7989e50986d1b57f68982961343dc446554 dec6387fa5cabee08f2af751ae906352a9f455bd 7107bc5c6f602b1cf8c8ccd5c073b8ef02410dd7 051a0d507c737fb95a49147d6d1961320e8c3039 9081cbb403f7dcacf547bea7ae7ecd428b894546 7e9b0167b44057e7ac541a3aa6090fd69aa5b9d5 062f3cf8b131aa5f684af956776221cdfac5ba10 527eaf492fdabd8d59ef8f52123bc4c5cbe8f6e7 04f7c1e38c32a6faa4a0c4ccca1f424903978e6e e727923bc92b70fa25e18672fbee78fcecfa56ab 1bdaf30881057d370a0f772f830ad3eb45c8d4dc 71420c2bb884603ce29b3e2dab139ef07448e18e 2639fba954acc3433b78bfb3c8dcfc541b401a2c 9fcd9f952099ae4d41ae0ab62eb79d5fe24b67d1 f4a38ed07366cc38c22a0e1913ae6cefda08941c b41397a657fb2f7e945a9dbf01812cedf0496c2a f745dcd627b2c1b1443ba2a634c4258629b31afb a9df512636fb51725af9b2e9837794094d18d91d 1308732708fd125bfe2f7f3f3d16d2219ea2927c 85b3475f93fa643e4976043170b0768577d80606 7565a6b81a7b444621a10dbe66c96fb8df5b46fc 08f2c7f8ab16c6c045722b6c5733b2af3d2329dd 7629bea5b6ffcab9f9347672267d09d3807b8ad9 04d4fed9bd310a92ff3145aa50a114e4ddd16637 db5336cce7f3fa4360ddbc782ea24b1b4ed862b2 9dc5bd580393f7a9f181db2aad9c15b9a8d1eef1 acabbe6bb90838fd72789539c90e717992e7c59b c19ba134e647fed33aedd1ed0614ec257352623e cff9d1c349f775e42e43009199ff4c2373b41272 6425ef88591fd50b44b87d962c59ae2c90b8089b 241cd8e32ebdc28fc8a42ed9e765e18b15950212 e3cfefa53344de65735400953083ced26aa32186 315e5f9e644653c0020607e76d8f105e770f2f8e 87e5415c75716febc6df1d25d5683c7fc1f130ef 53d109b2639ffaa1c6cdcfb937b2fea6fb552833 f1890428efb711041b49ed93edbc3f9b99be1e9c 5485b320a78b44a12b05b4d119abf36855dee056 2efce461865e670be6b8a400722357a00b0d71ec 52ebff73dafa90849646bcd41389f6ede694186b d31ab894db1e5eb6b6474e93c7eaefa9a6eeeab0 fe986b2da629701070eb10dc8494b02bef0c28d2 ad9c079996b7d21b952a81821132dfef7a00a6b7 0f43f2462890e76c3a5b94e5168ada44829810c1 453e545c82b133839ba774349b131fbfb635c198 abd5c7bc2405c4a0fc7f33c4eb4579fb59edcd8b b183e15b9c91d1e591da6e8613a77c33424e6059 9cbafd321b59a9d3a3c9761ff75f92f8fed95f5c dc3c288856547913309732f87cf0eaa601e03152 bdabba7d2fa141f1c359669f4a911720f1879573 2113e2c9bf313ac74220448b8a66f6b21d3f412e b5deaf62621dc7fce2aa62ddf0ce67935d459fe9 31f104daa1e3d328f42e33ac2ea0cd404cbb3568 b6c5ee39a15564a234e941cecd20effcc352e124 8802a1f409836995308d855f1c9276b9c1b1e671 45c530a885e67fc2e478d753932920cb15c6a274 7e3e66c6e1b50ef975d7c0ffef6571ec8f9c997e 51ab1674b94bd88502ead442817060751a4b5017 7666900f4e807783911babd0c3aa335b7e7cf155 2cc96743b7cf3994167e930874c9edf8cc5d5b7d c881fe8e1587b8acaba22c67d00172014692dd86 5cfa3c73c415b1e6aff723b13386959ff2b0674f 345e622d3326daa0277777c6ef0108918b3a0456 05f29b7c220c07c312b2aa6548e2f95bae8162db 5c73295ac997d9d88a197309f9d63f229c542c85 22f536ed0c575700e86763f38851123044dd2dd6 6e26f9dc6ad39561f10cb3858530717ffadf33d8 ac645c973d1b42ae1433817e5057b9eb3300ee47 a7ceaef3cab1c453f9729258fcbfc92ecfd7ae8f 9bb99e81d7af60e0343f0f87b0623f3b44ddd929 402efada2cf78c856c545a2fca6f060b19532193 0f011278394e34e8655e7e1cc364eca0a766909f 8176eef6ce0dc0ef7d12c2dffdca839b7d3cae7d f7eba7e1ee1eb93f6b6c7e91a4f01eb05ad913f8 c4a6827e9524bf2a5d487465727a66aa42e395f5 83cf6881d66cf3ff90f4e2bc1e220fcbafe1270b c8457f7a1966a58d020f23552c042147fd393403 a2ad3bf8986a8368f82fc89aa22f685e3cc24eac 84ea185b7642e34af4ac23469f6111e56bb658ca 6eb09d55364fc3ff175b010703c91b23db1207e2 028330caf6d5a4f7578c9ab52d65357e2074f968 831bde6050d36e91144631445bde8f89c30526eb ec53f9ed646d2ce0db2fc6870108117a4a31abaf 9a183bccd0c202c18f0073eab4bbaa4e319bdbff da2f7712537072c6c3fe6fc4edfb5b65a426b698 409a88ec624c3d407cde0d270fb7b7ec67f8e625 ce13c57e376aaf5ed669ea42d9de3ee231e4e916 666fa45cfed3ba7e62ea8f56d544c29ca26bc08e 7ccea68b1ee5790063b0c24fa34433a184351ee6 beb5daeaa1cf66adde45328d9d1186777d7c8de3 9b9376179718f29954c729d1c513ce741e47687d 4b88da43e392e1cc78fff17ffc3f6e79d81519e5 66f4087c3a5a43b8469e9b9e20c2beb3935437fa 642f5c89983b66794f6c96d9b29f294b2376ce68 5951ebe2be6d76a81e457bb2b496ea6f9eafb72e 381e7dbce80ba4d61855963e381fccd6d3b7d716 a278a1999ba2453f4738c61151e0c746aacea8d5 63913ae03156784a00eedb3d870137512f230c22 24b372610ff46f7650c7791e1e1d9a0bdb04ed0a 46b9c3a4c326f148a88b6d2420aebe9530d3470c fcb854ca18ac4eb4c84b44ab2d47464656f282fe 9a85e20830866b0e8bc66125714cac0f552fe238 542ab957e3dff9d5f71fad9fd5c0285171b71005 a4b3ec3f182d08f5aed524c042f8153013baadbb 918a64da5458d8184ed61e9768391c20adcb462c 757e8a1ab8c50bdef9e9e9035a56fb1828f8b3d6 6d5bdba71aa792969a33eac2ee59273fe863cd5e ea261b01a3201c7ab6c6fe43e94ad36b0e2fd7c1 3b03754f6469ab48491f0b6341079b241018f41a bf38f7cdddf54b3597cdc5c937ca5635e153b777 94946e074a92a108194f1805ef1acfcb806576d3 2d2dc487b56f4d803339036af1d0b39ba2ab158b cd2881fb903560994b3e387c479e1485fea08b39 2aba8277f4a6c408743e4f4be397b35b0866f6d3 80f5f0d7e8246c154b3d279d3805e66e9cb15add 4a866c1222d006935d79a46bed99d319dfb7c32d 4da1077b05902b0189beb41a54dd6e2f9acd49c3 0ad185fe59a5f78d6801325a903bae552f169fb2 c68de2b9f19b9aab8d8179ee8707601f48800aff dca9bad34862aee680dd16cc0469ecdd7b1cad3d 8edfad873a48da5af06597833e50bc9182328cac 3d6eb7a0080d34554766b117f87c50971b220be9 5fd32a7408f63e6c74ebadfa44647b449ccd5bfa e1ed2635fe9d3a0ab7d08b03811340da31d96390 3ecdbf9fed9fca1556d390396e43caf42cf34e4f 18fbd08e259c5776b9cdc5c39bf154f8821a6b73 e6430dd02666f47043b8ce94f5e210241542e080 6d0614c51aee2cce5c4cc7ff5fe332fcac5975bd b321bc33a5ffd222bdce53847f8a5303977d3803 b3e71293bea974a399679d5fbba0786e7089109b cd4de384ce75385428c618117e45db385455d6c6 40d1c96222188c75e0ed9741c3490907a2402166 ee8c83d549bd6d1ae2b733998c527b5e46c657ab a2291ffbd464b7222686b1017741916606dc69d5 68c3c3d97ac50f56b5fe96784b5ad1a0bc160d62 edecba42d736e6a79cbc7cfae7e448adc6186fb2 bff3923c07fc8df2b1a6a39932bb60cf1f182db5 3ba1e21b2064d7493074be8e4802de67c689c7b0 bb0a1ae0f5f43a7e78b81a933795faa5cc754154 f305f13e5599e5909219246a7c50b0852a03f55b 204eae0a4f3c4ef398630f85b0afe6b4a63b483f 6b1e394b59cbcb801d56923d5ed5aec04790d746 e2f990281783cc1415807685841c6f2995eddd96 6a0d674c2700f51bbde2269730b4e4cf49735a16 0c9daf7e0fd42e9f47ecf8ad34a2063bbf3993b9 1576d09192d19bd941fd0f736210138aa5a3d3bc 7638a99af48c9bc0da5c9e59fd2e66762d0c95d0 6f0c72ee2d8990725948cff2f39c63603037e829 cc5ab014120874c3537a9fede59cdd1448d6d45d 19406cc999148fca19988928d26c9f408d53217e abb2329747fd70df1b8f68edf4a5b52121eab9bd 5e583161b047449c4ff20475986f686663fce17f 4e29398a79bec81043c02a565b301225c767bff3 fecedcf18e0775623cf147e07a5caecd517cd1a9 d768cb5e2f632683a91adc47e9d3a159545b131f 2d2947318ee4bdc04b70874014a2c3dc032eb553 3ef8a92a5c0b93f16dac69b46cc34a2c38c76007 512d373510f5f7011318c5844e0b1dcbb062264f 452bea58bd8249a8d3f2696918fb36b9afeed006 e916ed71e7b42b0d0433692cf5d492583cb91f49 ade5a9456a16eb7abaf12516d2fa8b7693cdfef7 7ee81a2ca43aab9743337c41b5f173d41f8b9ec7 927259c707a43adfcf4bf45b9e5939b3d83db40d 8c59e4da5479758c550d5f689582f03da8d5d917 8f6ce57b858f888c1279ca1310e663773b2200c7 8d0c84ff8f5901a8dafc36474beaa29a70c4e450 2c6bc16a4152d0f4d61474d42ef9f2c9a3a1d0df 4e6094ea32b73c9b1876362656965971651396b9 e46f590966ecfd1cd2a037d5ef2f9d63bdf4c630 3364b910eecaab996e29d870327e9c9c3f9977ee f97754f4584f1b5eaeb6eeb1a2b2d767f6f9f0ed 22b24283d7ba303ac2176eb4bc1ebb8a52828bad 46e309e08ac3365d38a16ecfcef570336b86a70c 6b0799f232cb7b2abc37c3b96fd2da003273a462 29dc33cc8c04218a694415579ec503c580ae764c 33cb5f221305d8d7f9a882836031595585786faf 8829c49ec09937ee6ccf2a2f0d367d141c439aa3 feefb419cb76143683ed24eb8ad4dcb966dcca5f 707ed0825f915d8cd23c08db5c5a22b34ebf9cd4 36e9d67790b1d5c3ea934cd53d883b3eb17e9681 64da6558db7d8b617c6d791056d9df048be19476 0fb499033581c843995782282b9ced59e7c4a1b4 a6dc2e9d826530ee076a7b8a1e35be3bfd7d9b22 a23c222a0ddd694af5d197884d3c8982b323064b afc3a5b74ab339617b7c5253e425065adfa23697 1817b6413cb481c45f79abb0113ba42d93ec061b b035f2ed7be9700a1f4678c3135591b178ef30e9 2640715ee9aa54a2bd25c1a8b919195acaa44ebe d0d66598c347f51deeb7010cd7ba849171d10690 566222014b88d87557b0e17d9e359b70bb494278 412ab8de8b7ad87ce5b260bad631eb08044483a0 4114b0c56880a8d5bde1490ade73eb6386dc426d c9306429ba76631eef6835c4159d92e7389c75d9 9a6c30fec6e14b2976a224116ee30c7b9afe0f12 8fc1ad0c8a1807569bb1fe2eb7425ceb48a469dd d80319ac961e5ad988aa50db1c1d31680c8ff7e3 bc2d22936b28d5a4b3696cd67c151f91de83e5c9 8af462499e667f2660e684408eaa7162ebaff5dd 537ffcfd7aba096b4ccf90910e38dd6ddd1bfb1f 400f8ed8234dbadb0087dcc818a446ff89d70ead f11857f2fe8eb1745f628581d3690f6e18b709fe 43f21847746ce64defdf5c32820cd2df0fdf40e9 09ead9373a80ceb12e4c4c56d437a6976412597f 2fdd8b4a68fffd009f59b5b1f3679c895f415144 dafe9982c3c1c5d51b4757cb833adc4935a7b4b9 ad9e78cc49b4bf59315b2852a5605b81f32b5681 6c5ba6ed06db81c2389164872d70bc334dda82c3 a1aa86ac5c512c016f738f6abd9b9c0d64eaf23d 9a02a3f52036369974582aa9095a8f88630fab2c 92659ebce6c1c1a71a973224505203e95464b8b0 c244a59eb5a5ffc120d456ff1371e98da0f4ebad bf7e7b248e407ec36ebb179abca00b18fc32a630 afa1d483f0636eb3e17093c26b3d772a54853635 6d59213b9b9a653bd29c4d8055a0d1cfec30893b 8b6505b9e736665ca4de9ca763093bfae19177b9 0ea671ca3b89e4cbf23a16de0224f9eccae438da e1534aaaca9329bae011b20c6bd11e8031d4ac53 da5f806b90c5920bd074ab17fae4b4086962a89b 62c94547f85916e82ee56198d55ca9fb5c99aa92 5d5d651239b0aaba9cf510d9d35193d864a8899c 42a34acc430013a918be0a1799922d30191855c2 31dc1d0cefb00cf307391be6a4311da8d0c49f0a 41f4507425e407d7fdb714d5f10892fdb358c5d3 5f96d1ce65e95328981bbc4db1dc7f0cb9d9534b 8bbfacd3bb5dae95d792ff70b1d164fcd0d5b2b1 9aad77f6351065189bd7e75dd083b6384fece201 b2be1ed4129d1d852cbd0d6acba545f5c3f6876d 436afb025892adf7bbdfc98281ae35c9982d4e3f 747efac8647218100741f8f7277415c3f5348ca3 e6e6c5c9ee1f2de79db4ef185fe73d0e5756dece 586732e5c02488de164babff2c142e55ee3a9f44 e6a8df3bdd3f447ed71917b2a286b19f83332dcb 4a94ea9b5e94d22def5571e0f22a6ae446cbde85 9b9e745fe45f8e3f21c10461f659f95c0ff80516 848d139478773110fda7d092d826cb8b8bcc6815 533063f1f2aa77a5bc43ba2fbd5dad432b0efbfd 65e4979137273e85c9e1dbda2e9ce8f275c2238b 73b36426743ff991dc209135903ed090b542b14b c643e743da72a76b01d99ea5d1c25e52c1b9bb1f 9df4c055c8ac293c98361268c43945a7c7dcc6db 0df1a853d55bba461126621b661a7ee763c6c3e4 9fd5343ea589f448a730a01fe1ba1400cf6abb1d 625e4cc02827e8105c36b9bca2ed6a1f76faa495 52e1ad6ca38c03f56da8f6b71781c8b68e0e4592 bee4358e4465393350f9a710b25254545c22e91a cc9c0d744b617034c69280363090f8d51506ac8b 6d92d66cffe546132f9082ca7f1a9e01ad18c7e2 dd9d9c95c7b684aedbeaa02e0a97183e232ad037 80a2fc118393e9a57b50b8294d4cf51ac3e1a31a cda658357c719ac0b0d1f2ec8b57ae093b3b4502 cb74445eadb116e3c740debbee0270641ee7d1df dd8a486adb3f24ad243c79e296c580801608a1f8 74768341946517f7239ad062e6a6a1b867eb5e53 e8f4b9c61ea956fafe6ef1c55904af342685cc03 d830670af000a2331c54e318abd53cee920539dc c9638ee8a0033c511f0bdfd47847ba689b7bbfc7 bd23e6310c7fc72e774279804a5b92462f0395f0 2cdaacb588604a437f144f3555c8cbcfaa969080 7da900a9bb4766c7632e0791c994c61cb40c1284 65def5a0c4e44b154e6a01de833a58a3e452f204 9a6e243ad9f110e644c1f2e91543c3825fcc59ef 6c5544ed54b263a6dc9fd57af8db51268396ce9d 879c86bd016b8ba641058bb0e0c178f235e50c2a 8d1de01035fb6364ff55a6b366aee9231127ffae 01ca9ebbbb782ddd1f8f30b43e0b24c5ed5fe18a 01eac2e2d95cb64e6fea7ad904347690668281e5 774cc1e1573a064af2a92a6fa4e95c4a0d3c7467 6afe578bf80dd910614cc5539603a9439565bf32 8eb9c389f5c0794d44884f7e72dc9067d2f0224a aa51ce6d6d2741aec47515294e1cea38536682eb 02fcd764abfc16c595a5d911f9db1b1ffbc3bcde 3eff11ee04f7e41b933ff6edfe1eaaf026793365 d6f3c4b591a277a4372d1542cfd3376bed118029 69f36851eeb3821916aeb5986ca65056e90569bd 2b363faebaa49809ca48ce9d8786110462d78c13 c1355f5c1ec375912ee423931b1c84ca71f3306d 1594e108acebf88e3912c34d0b9a3ed7255f1623 17927247eb671ad36e3ea368288e39711b0610fd 5e7fc43463d14e83f083c4f3586d690bf7cdd865 74ce3fd587d13ea914024a761f43163e8dc177a5 cb0376f73be19cff70c9d32c49dc6d9c0fc5038c 4a02780f33f85f63c7fb012958b5762309fe7863 7ac55e7fada4d04e18c6a5824be7c627d56d9802 f2b4a91964d40f7035902968324f791ade7282f6 2386dc6f09ab4e1650a2ff167d312643823f4384 efec89351e37b2b428b208ca71f3fd4b71049137 3611c30571fc2e60d6cc2ab21f3d6c5bbcff047b 0307a50583c96bb0c8111e74d4d89352c1bab177 871643ea40153c5ccf4722eaf75ad7ea99325d56 b4bccbe857df52c14b1d895acb6b67daaceea1ca 26ca68376f42be77a1c014f5ff8d37b12937da30 b325f156adbcd2e5555a8430285d6fac22e7daed acdae9e11809322dd5ef3b41fa5161dd8498221e b74d52b5274c12ec976aeeddf587cd2e85fa0bcc 8fb8acc998e50d4b43063e07f001be7a42547d3c f32e253c2fe06595f0d9ea79e591ee6788510cf3 3dfcd3c967de054a7850ee55b06456e91fec7680 7d7269e5ea809975074a2ec9123db45b9450f570 3d40ccb3bd7853a065965072270e6ecb5148e98d 7ecef07b7ce3033a8c534a598b1668b87b5cf682 5c7c695ec1041fbdaf23515c17240e92882785cd ee8bd48a29c141701d63e67981cbdf5e62ffd80c 472adb3fc9291960597df489c84b4ec1b017b386 ec00a6d43782be83ec742c65ab45fb93ef109f01 d504d2c6e3c635259be71cc8e1b3f5a3c72f1564 84dff3267bb42e752e49573fbb66efb13a847bd4 f091e684ecd849cb0b8f2f1c1e3533e48cd77ba5 1d517fdde0a5b2a3fa1bae4ba33667ab1d7296f1 4e33b7ee99e98cfcefd89a3b141185fd8fa25632 b08db135bfa1e5c163ad0cc23ba448be75382471 9f41b66f39108608fd7d0ea84985090d3d1ce7dd 29753daf6453ed34d8116fed1197a6f3f3a3caaf ed37a6b10efb2ed22476b77eba1ce2384950fd0e b4e65e5b4807497c0ac8d023e893da9c6a5b8dec 2a473fcb96a224e6e377823f781021e22abc737c 1ed0dd6d52f4506f3b9068d411a9eec8f00065a8 eb048604168004aad84dd4232e47a15a7d60017e 3d75745b529c4d633dad7c735678228f97dd8c7a b5810dc93e75ebcc89e13580c9890ddb71582f59 4248cd9da26abd7e0bb57deb9b0b2d4a4c4a598f e6fb2a171a6fc67056b369d37b983181c7431b09 20111cc15fff05de485d4b5a54de893abfd71da0 ae8c1b40dd239f09a7cc4cd9a282041db9a4de35 d96c47434a04acc33df284792fa75bd64a5d7c27 f4e49b9f803d1fc5ab43fdc452e28a109a344694 bbf6efe5999e7260f16c8f5a8779f477d3845783 e68db9a4e95a47f3130e64bf5d8f2e43cbe3c63b 3c8886f4724b2b38591f6bb3635ea2d8864b54f7 e08c9c2dbfc05d60e7a7ab1f4b4e8ef75fefe2e4 aa69703148f352837853a4205048f369cbdc1c6d 10844f41d5a5c590d13145dd621d7b43a43b0281 74c90412806df838f3b4cc52e8a60d1e952bca9f 6497933a47a004e087b09b78507d6f3b0f5a9660 54ac55f199a7e8800bf1186a0c148528c52a356f 21fcab8aa102fc259121813fa4546c9d3a78861c 325018bf6786bc8fe81cb011ca5a190259cbe19b fb02642fdb87d4ea2518d2558e280ca715e6178f 4e88cafd4845fb977f388c39f5e61ee6c9ec6b0e 27872e47e7d9e009b418455eddbc929a50f02fda 4614780201325cf7526f9d4b41e36c218682bb47 08022843d7f8edacd20ae456b668e19057b34fe6 002060bde30f9a8c4c3f0a502b51ced7c486115a 571dce68b918cd9e682148627093161b0929500f 0c8c7aeddf12dfbc8f23685eac26d23ec4e9f456 2fc6f61072faa811b34bb9da791ea24cd717c8a2 e634dcfbf4cf5fcaafc4f321ec11a8954ec6f0c0 3e8e552a56d3857139b6225562e52c91d15059f9 de8409a74382c53b2be7061727ae15a3d5d50bcb bffec8d0a4443b73d1c3b224088811cea4ec4992 d70661abd9b7376e28cbe1bf4b0fd8aeb5c87626 1863cfc0580091c225af4e5e68c37523a5feae1b eedc57b42526986def5b5aa4bf9d5019dedc1f28 7889ace899ea26c8510413fda9de257833a0e026 5eb53d367a7bf6a683bd14607b8fa848999a182a 02c0e84406c417fb86a5346c08c9202605341c1a 092f9bd6f7d0408bd5b3105c2778ccc1c1f3e398 437dddaa3fe53ed733c58541499c2fda79fb27c2 d2dbf33a41c23c981a4b8111bc5d78db4afd7342 a0361b708184c13b1cb861ce61af178225f3906a 34fde55180be95412c33a5b37bafd5dbe3cba6e1 ac808dc5d241b02249e86e632b933e8489cdfabe 1065cffe804c97518172646c8befa3d0451d759e 6eabf73c00218a376636b8cfcd1e70cfbda44754 d2e4fca44c9beb6c07edeb7b0a2554d41c18e063 5808ca79c81bc135f1058031495e6f10fae80186 072104f6ae0ecc324b260739b7c256dfab9bfdca 41fb9a17a711aa3bd1fc6184313e7da57944c051 9e1e802b81161909ea67a293e2cff7f89daf6911 107806d22ad5a6d27d930a9cd3b6a5c7ae9f6d06 fc10c6f09d42d68414bce2d221fcd0fef5ad748a 7b8444527c39e3b50918289ab0e66b169bc16e1f 4ef94c67b51955daafe9ecd22fc4232b9f80df12 2106947823cc9771fcc39c5d66e69fee4193a21a 46e0727bfd6f0b79d446b2f023e381b0bec3135f 2192b12d3b6894fea11f64c6c92f2875538e7d5a f10d7830c67cc67256f47298ef9b7d9046745b3a 67ce07c6c30db964861ec0adf327ae2db565a117 d1d80d3da2e7625b853eabe25a8e51e548bbd630 2e30106cc5a9ff98ce1b48e59416622cb393b2cf 74627f3b724c9f03b12873eafe6bcaf0d6668bba 7643e08664ed808a8250af7bb66ba3325203d3e2 f7a5990de8b1a7998732e06e5742c797b4ae543c 1fd931432d99c8a8c9bc2bae504cb8c85b5d5979 5923b2c8a8ead18c691526f5a810335b49308fa4 9717608436edf2553f1a51d2d64b9583b4f73759 3b0fce08bd96e93859b8b2fb52cce5f09c6fba46 82cf80010b9b6b8ea596ee137c957509c0e8495b 707932c6211fc2a1f06a12102f0e4697244fb44d fbe2b5cc233c1dd41e0cbac1b73c42a2f2b9d766 35bb66bca6a5382f68d7fe5e895117af26dc328d e5aa64017beca5bf958d5fa77fdf5cd08f0aa06d 0a61089b07969d43469ff4881b58dd2efa6f1c23 b0712eb0c83b07b8f26a734fddb9163b82419705 543535bc896854bd2a7abb6b8527a0fb2d8e0c4c 2741abfc95bca6458227196913fe0fa5c4a5fed3 f04cc77aec7f8f67c29306b13c6f67568b1e98ef 771cd725e0f31735e94b842e9a68b8e15b41947a a23fbf069b613d0bbdd2627cfd31c41eaaadb6e3 daa3a25b4f0aaa4489b58145c46ce501e55f7b8c 973d0bcb40a539a73df6a39cd68d3af66609698e 35ec41315fcf909dd9fe080265879ee8e40a9d32 c9567d5182acde2380206b7fdd7f4540545c6b5f a95811ee3286d363ab4b4943535e549e49579f1e 04b66347d901cc4d41ec8bf659b7829c3b7aeb72 c43c0177a8556009ad177dcb1d48ebbc6d4c0e28 963a016ecfbd4da427b079fb8b679651a5634cc7 5c52f01845fa6c438e5dfca830b5d03d6a8449bb 35afbf694bb5ea1f5b9e581eb6d275fc9b7e5d14 a4c0edfa3fd451aea61c2984ad346f177bc8c9a4 0374889861b3c80a27c0d57de06fe81aaa7b6d1d 3d918debf02ac1c618a50ed755b353c629c500f8 2aa1895f6d3a705b1583d2262ce19b8e61f59644 d4b667fba0b8c6ef4662d0c974c3ecfffbc253ae 3d95ea5f1e1cadeffa8628c4f8754f81b481ad10 a40ace49a11988ac7ccfb3213d00ed6c17b183e8 89307e7980954e96465a13801cb4b6e631dbb0ef 20b3246ac357f92ae195ef60ee6a8f17b6f07315 532f9c0abe36b85a765b06344218ff4ece9017d4 4a551d0e02019a9020bd83a1a197ba6d655f12cc c44b61aebac5e45392dc75b6f934f4a1ebf99bbd f04f38bdb40a014c1df3ea878d9eed8d1607895e ae2d56944cb35b5d41fdab4f65065e19f3ff123c aed557f59bf1f1f0fa1472167aa838a6507f7a12 96777d037bbb31c4535e13eeca60957413bfe00e beab34f13a8898ee980a70725ac9750ff7b9d247 c6ba56b0274a36678e831c49eacd5ba7fbbbf687 55a7a7fad15bf6afc6adde82d29e99215fd32611 22fd94a53ca3ae0c3726aa364b390305fea53d43 57f35dfbacbd858917783f34d6e4226e0400041d 5613bb8f6997ce72a1544ef24e58ddc1b21af995 a2629c2c4dbefc1e526c5e4c8ead63f2baeba342 893ecbbaeb37253a933cdf2bd6ebaaa3e2546676 4d3e997e5f62be7366c6b4a0932b9872c4e77c63 f0a71d9d9ac3f1e74b8b04a5c8270fe469db4f09 c58080b6f129ef4bbe66e3e10f7eb0f5fd94c25d 53ccd0913f97de463fc72db0dd65fc39ebbaf455 fca05aa2cbb23c8127382f1b9ede1bcface7c199 d287c561138689f6a46e3bfe733e81249d58fe7b ded431615c2b35e9ff267e9c4354d531c3819d68 eca5938250d0662029c816163f2a971a6e27742a 7846b45f580c53de8d56f01ef0337b44414228c4 93a7aab450aa5c5a4faa6d4817bea16af567a93b ebda8dd7ec50337a24d258b19828d73a41a03191 efd6e56cc4be11c8f31999f19fcaf9a2fe827514 3e5e6187eff6fe2348665767bea739393d718585 31a1b5455047989d87dfa208eafab75da4a2d202 4f39a848e55ac15f5e17c72bdb052d8677bef788 3bdcd99a54de03c7c3910ff9b84821169fb939e1 abeb678c1c56668541ef3d811d31c8083d477eda f5d224a3097bab2d591d75b02e68cfdc00be6d0b 24b1e74e0f451645f4ca7e476b8d6e6b47b07b04 30f2f124324b544d4d95db407c4418701658b689 73d4b0987bdf7e96e432342c9c65f22d0bb580e8 1a34a82b258f2b6cec3a1225d14966009e05e05a 4d27d88be2dcf9cee18d6fce84f0153a8d357195 a650995c4c94fffed90bb740f099fe9014bbda41 e36e840309fed1dde07e85d6e65a5edead2103d5 d4e7c0a9c974f336937729ce47fade4212ad2f1a 4513bf421ae817576f5ec5714f6110bea21a2ac4 533f968af9aa43e2af85c64ce839a56aeb730747 1cf355db7b9776f2660dbedfeb1c00e2dd3acc63 6f766348d7e3cd37d0b72da3a769806cd2ac6c7a d7774b4ca84e4e338f721b6858a27953dce64075 dad9ba62e4802f66458a07c725cbbfd2d725a49e fb0e57dbfb084691a149634cf55b285fe9e02062 74bde432d0ec5a1d8b26069e341c5c554f65a1a4 62e7f2ec5e66e58c0fa83404db459eab01c7eb37 8f572d179bb9399797ddd79c526b1140f42887b5 efb003d4e588ceed99de94c6648714f0c035aa5a e159ad18e35bd07f9813c47ea25fe4243e3f0e92 8fac4f77e0f4795d2519c9118cb3d0e68589b18b 6d7ff24a9288a5fa752b4a69ecf925305d4bc107 69aa319877e35fffbe8b9d4f3d5c8a9e7e29294f 638be2231c9bf63f09f27154678fcaab83091c15 b459e28c75fd9e5ccb8ad859e91a4ea3f0caca09 09f76157a11a6686ef4295b4cdf9dea61ba64ac1 11506029e0290b45ad2873d48720da16fcb5619c 6f7b16432a3866196add82d329ff00199aa6774a 935ddb1b0d14a9958606660fa0a2158210b1d287 57d342190d2e962a7b3530d088d0ecd7815b9312 a635049254d251da6e3c50d4edcbb9dcd0368cc2 839cc56be942bce2bd9dc9991030ea230d2d208d 2e1e5f44614126c40977c790a8670b7b0987d9ff 748cf00ca7464976039a7f34c49356d26c067aeb 73ce29aabd16f634f40f74acffd17346bac61cd5 ec8f89d0df0a12e8f548276c8832062a89cf227a bba556c7420897e4b88480fd363c1563dc26dcce 5e2f79b71e61bc3049c59e04ecb3003d8dded21c 2d4af9a7046d2746e1d46dcd7824742ab285cdd3 615c41ddccb4bbd7846b937a319a22462b8d6417 1e575983704da6264b3fb59f05f5504026e3b6b4 aa9d05931754a057e9c20405894b74a8e674ce9e 7d765e8621675ed57184f34993dff545f1e11899 5bb1fd9abef833a4ff3233c70b033762a5244c63 7027a1ea59ca2212b98bfca362ebe48df6f5ccf7 7a12815a7f85f9ba29a93674149841721df4a61d 55430a220ef7e7b2809d982de2617cb3646700cf 1eca56d5176da2f9e251ab7abcbdf3e4a43ef3cc 63dc79990a66fe7732e65dfc75bc65e312220a11 72be93a76a34b23a6a0d3b3cecc8685f3a006c86 79a8326fb0433b4a60b96e4210eaca9ac32678a6 d89ecdd1eb8bceafa44d3540b57de1bd361f4cbb 4f38761ed7f8aa6f7cfaa39d6fbde58143cd5256 d8f61bf4d84f8a1662a02c84b96c6390323566f3 d2a539c2dc7c4572956bb4ffefb5ec252dab77d2 503c91e442e7217200c488e1d836b55745773bbe 67685cf27c866e54bdd3c3af195dc734b431c62d 7b006ef24226a5d13bffbcb29ddd557935ebfa8a 8b8600cda77e854a465856d1f66fa4fd0f601bd1 50c6f742cb0958d81cd9cfaa095cd8f12d91e87f b14cac6bcc6221b06acc7ef734aad53e8dc4b439 41a53d30c4cbdad0e4d2914499ef15d88dcea6c5 40ee5276b8229e1d70a3ce257bc4abec3809804d 053956fc506cffff2a0dfe865117fa639659cca3 1d93e66a40947ac4bcee7389e9aedbd37d6675fd beb33039ab617a87867e9285834e5e19b7b917ef bbec8f28f92b3af861a4c7a3913e1b096df4d64c 1ef0c4ae10a6ad686fcbe374bf3c6f766a52a5ab 212f0c39ebb958832b6917179cb5eabf721f4f6e ec22ffd24fd4506f01caebb5e55bacabe7376f15 aed4f739d0c95ce08b4dd351b67f597d2cc12664 70fbddd97991710de5675dc61b1df3ee7d8d5fc9 56a18dc6ccfb701d45a0290d2d6e54181930670b 5e5f0234da08ef186b026492c85230320673c912 b35d70deec74882d80297c7e9a039c1cb7cd36b0 9c9b4b8039ec8e7457bce9aaae47d6c9f1a3f535 8f79b6b4fcecd191c2b33be978a24b9cfd08ade9 e7e19b4044c95d5bf0345c84b49f588b775376b1 395217e53d3e5e83b8521e6eaa85ec07e8fed31e eeb97c846556856c6200e7d5d83ed0fe4bf19d09 dd83c9a8673fa78cd53c38f5bcf2b2d7b9edda15 4a0e7b06df3588a8fc09f6efa264e54786985db9 06862f0c35d4b780a4b631ce8be57657ec45b227 80ac4f31116bc156e6b7fed6c7e9397908b50b13 2b35c5400f8c7cf639878901a4e058bfb8dc0933 cb0671073cbdcd4860ab242af31993a04aac2cea 07cb821779d115bddd34ce210d72cba1cafef63a 65debd76143495a0c8363878e508dd8a49fb8e66 bcdda785a2a7d8ae9f8fdd70c941ced9d1f4e64f 5b840cc6d2f6ea67478f1a22f98fd2b29b4f6ff9 e8c9d132b02c85fba5ace2683fc57449a10e209f 08c3d37b6a5f41e77f31faea97efe208c95a9c25 d34ce4ae25e919838389bc39c939f3608883343b d953d12ea592ab4f15e8632bada5f1575b3450b3 059bdccfccf6a7350d4178a632958593cd66da19 a62823dbff88e5d57694344da1f880269d181174 4d650cd086bb3714a6ee1d722986dab1320f55e2 46a745d8cf24d5517ca3c55c21804926c657aac8 130951dc13203a086545730077feefa4badf4548 a0759cf884c89cadcda18a740256493334428819 b2df6deb028558e95962c8474a88ed202ee4dca4 6f3cd8c0203df26cfa5e39a6347d6fe6caa25f70 863882a327b3f3de168bbe448319f502bd03c47e 9510803a64b5c4fa5a04fb03828abbd78beb1089 f2ae6213b2a59343ca97c1ce7f6881b484a7e2dd 29b0cda2b042bfbb266cf3f250c43ae00cb0ff7c acf7243da30b87433a2099b7f50d25eb28086c42 381efdb168209c6ccebc76004125b7e2e762e9a5 67781ebda04d199e9dcbc1e61d214c353a5c01c7 04acdc17fd703c52b59736d294156ff27676426d 45ea3c19e48b071fc898b0876debcbfbfa52009b 3f2a93157688da0add95ce4e7c3722829e765f43 1b452550fd500ff190ac94cdd68fd33e8878e4dd 155bd636297f783029e5860199a4f2e17d2b24e0 c629cfbca72b17db071c3f4b88a825c7d1a4457e 798c6f9a368c6ec2332d0f47a0b9fc8bea5b9671 6cbab202365defc3211f24064405b16f69d57114 d881134d56be5db4af40760d0106d68516911963 09673d59bf27b01b1c99cb63db05dea1a12315cc 68e188506dfc1657ced4619b1a6cee1029c608ac a21cca89c7bc46a3ef452cc7226269df8d7b2077 f1758ba150878742f607996487930ac887dbe846 9e7558ae688a977296cad7d84b8376509f3ec46e 10f060b314b3a95e3aec8aa7081eb22e22373586 1fb336184ec897bc261402941301d05055c308f7 f9907a6ac165260ef072961750678e901a81f585 a978a1fdc50c92276cbf5104e1cd759af9e9f4fa 0eba346a99e510b933306df8d86e2080081706ee 7094eeede98a8a70cc081a696d1c72aac541a514 e535d1e695bb42050a47d32c880224ff9213b235 6cea4ecf9655a6d0e859ed21ab47e5bbf29cd8a5 ca831077ee880d02595b4a5514fdfefd76d7321e eca4d0df95856ce9eb3086e4afdc13e46cacdb5c ed538c7ddaf9bc5ba1a1b41cc042cb944b6f7b44 df0e262a3a6de1b992d2bb7b688c9af0850993cf dab84886a610dd12598497706d514440479de295 96e6b4d0598d62fc3460b94c6c470574d28c39b0 391cf8bd432c952f532bb239f77d7fa586dabf5e 8fc0a309039f4a7dd49e4cd4c416313ceec998a5 ce1dd15928a6ce072422a2c9a7d7e5cf8280b00f 725924b20527dff5462461ed357f1f03037bf400 27a977d2adf069a947e2f568d9aafd28936e8e1c d0ac338b966650d66beccc6770635145ef076904 52a84290b63240578acd2219551ed8efd90b22d2 3799114c633fd3b37e74542c429677f2d5027253 b692c848b5289abaaeccca88a650950272ec168c 7ed02bbde79fd7ccd1cfffe194de5ecd3e5f6838 fcd06e442601b8effa1d41025b8b59aa6977311d eede36a1e5429dd44e1ee884a5e3ecfe0eca7ddd 2431977ff7537e417887e9aabe56e6f15b8540bb 9ac8de60c91a6e709fd5f20ac8af1b3b26109501 83454865d8b8284849a421d29608b05e6f991b3c 000edc4839a1ba0a14f17e7acd9cae689271f065 915cca3e8523338a113c87e7c8592793e8abfdf3 91be1dee939d5f8462df148cf17c8a0dd48643c5 14f5a485635c5e7326d18e12492bde6a5d0830b7 c4f723a4eba859e9c1ab6c8dc1c02d7c49dd8ab4 9902972c1dbfc79e088fa898bbe12d2f41b3a6c0 06442bd49cbf0dd11860f81646b146875d42474e 3cbe256a36ef58000fd84da08823f61d7c9d5de7 4ef1fa0a73b3985eb34b2438bc71da25648ffcd7 0b2dd3293484666891d9705a15016b7b3ac18399 cd72ee1ccf650da07a2debbafa12ad901aa83652 d9dd88896c47a6bba2b2724da5d3d861e752e061 17195ab6974f1795041aebb3e6c05da1a5ff7cea 619a05c768beaa36c83628872fae3b5b5146f011 38261d4cefcd5cc9b35d6546b7c74f5ab61da608 204acb448e041c48ab8d93206db1c28361daf0a9 d042ca5eb4b82afe4ae14f7d6617c61cff8e6f88 26c02d135501ab93dbe3d8d9ad13fe2dbae5bfb1 52be9c017a0cf7f23ee6969268da57f224ad18aa e64c9174e0943eccb8e415462dd7dbfb4f9c6698 5b83d0f008fec9d152a37e4e1143730ac5f309b5 2d80ff6ff84f256b6677e946b92009e6608cc457 e3e5ea69c6a25a8f2a229fecf7d358e0cef019f7 7a68758eca4b055cf8cd215907aa8d2afa6ff648 aa758e0d2926603186e02a2433618a50d0a5afb0 2b25b09f7575a4f40299bb2287cf46d708a52592 2c856983bf104b97ecf4d9fa9e198e2b3dd39d6e 550d9aa72860f5b07479c29b59f91adc694c39ae 15d0143e97c5dbbf802f008d15020305c911163e 74645670366d4e4b10c5f1f85ea218b96cc3b12e 69184832b62e09321bf144eff241df7bc4d1808f 270e96649e032429ad8dfe9c1588d0a841328f10 c2cf4ebcc14d67f407b4638a1dec328d624906c0 1e6f78ec2ec3c8d9e5ea48730797d72a14259944 37d1bec9e9900b88846fddd89b288e5698113374 8e783c16aabe777742fb5e5736796b988f91b847 35f97e4c5e11af0f4950ba5fa1a9b38b9417b2de 76febcae60343ee6aeac531619f3c8e366e006ee 480c1c9b6d081f6da5b7adec17d756894cce7b0d 4f4822ace8bcd81d81e507f6a893dbf7ac3a6b60 f0c7c27a4a1fa5cd8ef46bfb4010c633f1497f64 94068225a817d96a5aad4693dd1492587bddcda7 de9a78d95f62ef65a702fe2e6b3928cb5dc452f0 1f6cd420d24555d2b09d74f9a03c17dd550e9c16 bf8d5989ffa5c0e03bfa49bb741eb33c989f1db1 e33a125156000b373acd166ee5cb546ad8f5f7c3 7c0949269b1cb0d7583ab2f87e5c6be274606677 82997771f284951e1ccb5e920a9aa7d1eaba1ebc ec24a47dbdedb423dddff9f9da27409f667de0b4 8962440d80caaf08e7252d1a5d7eff24772af19c bfa34a61e47e51aa884367132b2463afa5eb1c76 abf6d2e720ee6ab1f7c29eb3540bb8c1c93f1f91 ed82e28e4cd12d4f87b07d7670db21333433b79b 79f1bcc7c83a866bbb84265c585bb125412ae1fa 1bd4aa0879451b99e8124e6fc51449a717c6f388 995030100b1079c231551015a5ad30d0b03e00b3 32cebd1f6cc5614f9d9f8c9f652180e51ad2f8b9 dc0733466e9ad5c83ef4f52d50f7b35cb6465f00 09808521a53347cc0d2714b9fa329a8906abcf0d df6edc8c9bf62ed075d35e66118d39c95043d6d5 6a09cfa7d396b55e9ae1d4dd383bf7042b2851f4 bd62ca902ed63a7b324035ce3b5a7d74bf8eefe6 a352fea8ec836063ede520f5fbc08178f0923169 4b44f3aaf220454a3fbd96833c12ed27d0181ad1 46c7d674906a6baefe72043acb4326655bf2c1b4 d548db8e69f81798897657f4501dccce8abfb84d 0dc400528783c36776ba3d542d19c80288694841 00ec8110ab34b6b335bc8deedba7aa02accbca46 cd6a88a866eeed7ecaddf2438aee04df20a4731d 02bb387ba13210e62f37752c05898d2580b3253e be97ba7f54c5f8aabbc8ed3ba4683065b20907d5 ddafaa19d3963bfd9d4b3b3a0b2147c5a64be769 0ee005a5bf55e04f01213d1ccacab8b50ad423e7 882dbeef3ce7d25ec64a15f5327faa0fd701f036 14108800e58a7407f0ee8ecd1acfb8e26e26e617 2424b5cd7838e39b1cbb0a6ded688e897e8ca993 23df2dcc4d153aaf91649cb04741bc780dfbdc39 25af8d9b921e88224af07f583dd564d44f26c43c d87de66607c4f1896efb9a0891b48bb9f6e08084 27fda472d8bba9c503c881def8911953cd3048e4 f69f21d5f36bc369c43723112328e85ea29d2b21 3194e86fdd5c495a9f8c53e52953da7fd115c62f 3cf8defe386352362fe3f8e8efc36650714a15fa 474bef8aec9d56ce8e6b077143e5425c3976c800 e5e61ee135f756eeb17d7ab3c8dc51a54c96f981 573e91b7a76cbdb037d7ca82f553e9e0bf5f8c32 b51d8478d253c83dae3e092d56c51437cd224c9e 268a1b38d5d61b71989815b00f93342a78300fdd a558c1206be62851d8d7c87c633f465f6f0cdc92 f52a4595bb282db936d75625a7a6ac13ae150a3a 9c224c7a7f9a366a63396d7774ebcb551f498908 f0f99d08ca4a1e36e8778a28b23b5f1d6bd0054a 60a72fbbe6410c10209e915b9f00979b245f4b90 67f70218bf101238fc99244f996480d1b1117342 6ca640c68caa6dcb33f673f33b9adac5dd28d5a7 ee4cae69f0b3de6b172b6c7b4f21f08b90695bcd 35234696eecb30ac7009c71ea8a8b11e25ececa9 2193bd99bfe8c826abcc6300c9ca57c9bc524ab0 c839b0d5e9b69318e7d6038052bc612958e9bbc4 71461cd66b44fe8954121999b9bc2f50fb73939e 29734af9f608630719f70cc90509aa51e891059a 37b53fae3dacec906417528fdb89a708dfb22652 20a179bf7209d273ccd430b7750f03ad9c072f7e 79b53355202be612a1e60f9649a7837501a37454 d7d1d15c289c59d5d6e73b3890be2c6365e559ca 5bb4009de02dcb8c36710cc77a50c7383eac03e4 01293faac19fea71afd72b2c6362e55870388a70 0b990d69fc65a32f33319152d71d20c96a783df0 9ef54b8339787ff342c1a9b9241bc84783c69449 5da7dcc5188250bc541ea1bb01ad04404ec3c280 61f67d84231d249d984ab7b32cdeaaab74081270 ceaf2aecbeb675ed3ba7db758cedc3dfb79709e8 12279c68773b7760ea695c3d2c8351c31143e3ae ec941369fe8db51c13484e76de9858feb73fce60 c43fe85ace6b1af359681a3a98b57bceb5153595 902a6b7c281e99d568030ea67a1c346511e8f815 645757ff63ca4492cd6c1a18527c811a88e3d0a2 162a67d7da8902696fd6461c1f19c8a28cdb24b8 c2abb31e28f17e5a8518092ea2179f7234242b12 330e3c36aa0c2658ba73d9f2aedf990fe2702c2d bd6ec9088d854562016dc4e4e30c1d3f66ec5548 831382d92a2d0dbf04debed0e0e1f85b33823e6c 8688f8a325a37b14aae00e6dfd529c1793c396aa 95ee31f53fd0d982918c941744f6738a38e33625 b5b1f7c713c5b5783a13f291d5019dba9a513672 36ae2e9c65fef2e9844f47a797c57fcd05a3b6e3 676b206a658ab1c3ce1d18aae698c58fc7b1fb9a b3b2bc8e5e448e8ab123cad5d3eb5630d0e782ae 72dec186557782b6fcea9f1bdcc9e988ae8a5b90 8bc75b91d1ee1f94b974ed250863816fbc63de5c 21d575bd3f2e428749e49c20975a8405415c42c7 8201e537b17039b290ee11e6a017518a5474b0ae a12e88aaf9f7b4d641e0efb33918f7d80235adfc 25228b207849d4768f8f716424c65c42857bf7ad 97a2b9237c9e18fec59aa1f1cb3a3af10e7e74c2 6062277d8c309ba858cf84e351c7967c608744cb 02487b17d453fb0c2d29e00445a9876b5be074c3 a5d848de704024c4882ee86a8e48c8fa589c67a3 727879f259c501b5598a28404ff963503a0aa50e 13b3ce5d06a4ebb08733d252d78a2ba6bdc8516e c8d56836b6cf462c327290382c05eb1e17c2d04b 21db0d42c3e6fa294e362683a1dc702be87a990f 4209af878d3cb18887fa2e8046bc90a23b381677 4776bda3e5db9771c706087aebf4f0ab427261b7 51e133c1700e1635d227c5882928176094cceadf a2cb7858c20c9224ed0640fb7ff94b443b442adb e5bd0ddaeef5070f8c7c624d0169f4a57375df85 c2c1be1206c13040b9dfb0f78d6f6f1be96c7f4d acf3fe014e1c039edd63266fd13d9fa087605c78 ab8fabd0ca65f9e5bc789908c8e53d4a5d325c2f f68ed252401351134839b995f9ace5a7779f54ff 845deb1380ad77d9d8bb7ccb138ebd60758ebf24 efd83dfa7cdb2a2bb3f465aed5107b27db3947cd 9949b7fa038e65895902495547685b98cdebfbd0 65a18f93611c4de6a52c16dc764e5a7296623551 4f651581662c39fc32e7b7de549208f3290df655 0952aa705526413a7f92421241f6a979cb9773ac 1b7fd6431f019946048d92f8f9543239c9fc1c3c cc8a95411cf02d64255f48de698764891ea7892e b5354cad477009a5fae0ea63541d37c2892b9a91 3a6c0dfd6176e2e4572caed8d4f2ff32b2a62d58 62c040e5772f99a23b10c9f466a83fca2d557e16 2da0c9e82e4f031405d26e62b70a540b8eb0ed85 f03652cab159f53b6b729c3212e9f3293c02191e 90eb43386c3dcd2550c279856c7d03d36a71167d 858550fef89749cb7f835ba469b0cd73f1f30878 294d83744b1b4e109908bf12e55061e1ec0e73bd caa398845bd031893a42e8f5a3057b3cd9694165 45e45c9b3b2050e004d29f6cf3f66d5ec604eab4 ad040098c3f60ee53de29ae237d062006a74341d 1dc033164982b4acfe3b88a773e371e377e05e35 1c3ce765195c588efe09897717f675d5e96d6837 079650fca229b4226d3c496fbb25653752eb32a3 544d87dd313387558f12f85e49d688c38b2f6f46 851d29221dfbeec7c59a76950fdee07c0d91b338 ee4d997b3287c31b9a401bca5eabb105009eed52 c924a64798ee1052efe1aeab7ba227f83dd0ed7d f56ba409991b03815cb9750933f1c76f2c265db2 85e89c6a8b855085ac376a25ed6fbaf20103f4bf 7f318aa1500da81877e7f635a42dfcc125851716 839c02fe322d90b59d58e5927ea83248d61500ed 912fbf0e444d5e266ecda1c9f3ac30501bbc5a73 61a1b88c5880f82a7bea6dc834048cb2b4f83a56 cf70e2390a495478be0b25c2a38a7e77d6ada21d 7f1fd350b25c28f8e9da1e20a6463cb1aa012d66 fe74a6e8bd8e4c69731efa16e53d6ca7947ae5d8 404e15e65bc51be52ab0833af6789f171eb79bc7 82ff1e349236de1266b87513c0fc5f0c5058272c 5856794b3be410b0ad8810eb1ffddef13e183dae d92e65eca7a4c5839eed244835935aa27d5ba6d2 537f9ac85ea8ae22357d73ef11b8b6d39fe23849 24ed43ecb7a0f47eef0fb80455e2b5eff63380ba e6dc303f879f4ce15358a25d7b9763b5c879f3c8 84ef913d35888b8b815fa606320a8c315e601845 4b32843fde9071d543ea1937e8c7204dea6a0a02 9bd4e7a16db1bf60acb271df3c280c5af71b6559 ef835a6fb28c55845bcb13b9b04999e5bdbc65a1 1af9e71969a8dc87903ad161f0d4cdd73edbb449 13aab8380114d0db661ced7f581cc96b04e23496 4c716b8a4fb943188445fc100b1f3d330708b7ae 49c8aee7e6b8879f2f51538ac81b51fd274d0607 ebf96932d4aa0189005cf1d78f74850dbc30959e 1b31905440851d5a60a24202d31b5f313860ec72 01b85f6a12689c5502023653a87416ee921690de 2ee830294bee312aa5e8f4c8fc55c4744186185a 080bb1258eaab647e14418ea9b347eff688a2cf6 72cdfd3748b9ee41bc35d51231b9ebe56eb46512 d22cad74b9a83f2c44619ea294884e5ec0acf8fd 996309f4fbfcfbfbcd03383430582f0a7141e00a 6d9cf7b19a696f42ddbac2345571455cf8293ea0 13a14a39157d79340415cbff77f8d58211991e2c 466bef64179c0b5405d58aa9b24077a7d989dfb4 de4093cd715abdd775301ae84b9e31921af33578 31e5c7822c7eeb444928b70726c9498e3cd1b695 b0a9a9fa8b340d1bc6636215b8d4ed796391b8d1 91b619d1161d00f688deef0145a74e3330723518 42ae6a5cef583c6549c12fa8d5c3ac86886352dc 7751d4eedc7982384cb6782b71508f1d5d162c5c f05ad5d8e60706e482d58b4f14e601040357ea31 4a191c2a95b9e9cbe94c922fde453c15e0da8ff7 c49635bd8942de60733ac325a71306d30578a80f a80b825469e466c9110c8df17e3c5cb2ff7865db b2ab1f6051022ffd2c0a4583373c33b27b7220a0 98bad2eafa6a261e8aca9cef1617556f068911a9 14437488cbadbbd59367c0af11fa440a59583ed8 87abaa0118ca94962f38b8f7cca5523a74a75ce3 f64f9e2e0d6385b67d1409218268af383183128d 074f23d6892c659695237375145dca739361596f dad047f4fd86582e0d0666991ee5ea94bf1eda28 a21e319d7ef94ce89f7bda49780d8ef8cc5b2149 a31f3b05fb890167fd49de0e27322f85e6ab86bc 6e77caf2074913804811ebf424b3be9efb5cd3ff e0bc18a064da5fb45dd0fd242c5ebf10c82486d1 bd741c9b6153b8a450f8bb2f582499cfa955105b 4e5f31eb2184e2f8630c7685255fc856252a48c9 1d93da752e9dc3b0ff32aa0a39d1552349ddd514 e533e97cc02925fc9954039a0813615b1b8003ea 47ad6411aa1b746c06b40431f9e37db80f76c9b8 42b25ffd2daa9ead857154afbfcc817d7c1bd4ac 6206bba7423ed988160020b1a57dd1f3a333cd66 aee7323e7f67c7ec27fe5ee63b897d50d99addc7 455584a90a15b46f78a00f7be7237dbe850ff6bd 616e98dc23c9496c0d3ae53c5a6dae03abcc0823 38fe6d580314bcffe1479f0c28c65e9454903f9e b8baf15d98061e0ac97ff48346b2c8778f9683e4 0d6881019b086f88bb9291e748dad19f9fd570b3 ed922f6c4b195c34639bd2c4bc98c284cd6db2b9 8d4ee79423cc01777f2ed6a846d82bef20ff2413 efecbc591d49257e3ca2cbf00d8fc44189dd3ecd de34af5b0c2f6fe54c70a72b99a398db4ab0b449 c2c750846a0ee40a243193a19aa5dc13c3ca0d14 bcadc5b1504f34e82f51e69853ee34e63bab0a24 71b2a4279dc3158c4fde2bb78b087745823110f4 4a555f3a0bebb6f9ff6b7b056cd6e177d7bba8f1 eb2906dcab4277a943c4dff187b0e50e06754ec6 59f96a21614e7af13197b504c085e0825afea289 dee86cfdbc4cb4290e4ffacdf3a5d6fab54b19a7 0f03cd20a83c3ed6f9fa03a7df8a4c2da66d80c2 eb474b982bd95f7c3c1961e0f7071f3873f59b0b 4dd29f5fc80ffb9138392e47a217d5f2c77904a9 16ac85fdde79683b4c505a521f2d16f6908b0b3a aac0ef7a30b8e703443c390e6be87a16c89024fc 970aa6cee8d2f05ef51cad866aef2ce334db2fe7 e1a51d19e6dee9d8d5b002de5de0616f43aa6876 bc33e668f9119a210923c3c394e8ae5e205a1fd2 441729150af2e632021a3c74c10d6e2325c9dfbb 6cc113ad57c3062b22fd9ae2e895da57d40e0dac 28ecf0816cccc2a888fb23704ddadedf583334d4 567c7ed2d96d8e09bfc3393d3056a19503a85188 c6ea981ba302005358f866cd01c59ea73ac229a0 a68ebef6454bb0d9630a581509062fb3a9f7cff1 f8d23036c955aa455ecdd73b4a4fb45356878580 b09f15e6f86756e7077d6ccea8a6d8dbf49dc31e 466c5dcbaa3d62cdb2ba61b422740bff65a3da4d f8d7f253bcef9e441a6d681b4d726790076c1366 8905f01326dc81c8d896c3b9e3ac061e706cf1fa 45dee79c3e24e3ba7a2dfaa71e5ba48d4eb7f9a6 bc0549880663b243a377391ccf4e2993b1eeb185 fb6ab540c482d1b7d68c370ec72c6efab9f0a0b6 275e841b1f87a3869088ef660f9a7c9143b4cccc 60a507fd2da5d41bf1a82c09925577e815187a9c ea1cb67267afb92ee2026cdcaad447414c974ee0 5b6012ebb2df3b76e8cbe0a73564ddfd9f713911 085f86aa90de7a04d0a8c7dddf69d969ff46c769 6e741c40f31a61af3b25a10d8d182087aab69efb a95de681fc6aaa7cf285a54a0ee5a30566f17a5f cec7c8fda8860c445785474393119402bd11f2ed 3db7bf1391583febf19f7c2c68fcbe0823647cf4 4295f3f55bf07b573f423c046a6f92ba7dcb2ecd c5990a35ac00c7eedc83df11739a4ec5a084e4e5 9c890c632c2183d1f1149b08ae5b60ac0dbab332 9b3d1729bf236a1b8d1afb8994c3f24acdc0bdd6 b1e2f3ff25239ab0afff5204479d1298068a88bc 09c125c0e49797e7b6d60df1460d5f4feb7f53fd c306cfeec2358b1a8b70be9ec298ac3e96989885 feaf5beeb81caef681fa244da234552ee60875ce 179eeec503edf8616a5d26b4039a4cc4262ea084 007c339ec499a3b2f14037a7ab47add671e270a8 ab5d087d944136c1aa63387c9d24bdf9c4d20386 ea8cd656340ed3e65f16fec5d10a7d5c8e1d8ce2 8e62ac2fa437e4abf5d58cb0d4033ef5c968c893 895485895a6d7ada1111880542bf0c42e695b7e1 a028c7fc2b4c663ae0a55674451176d1fb533a3f 3ee39f39e6990588f254429889ae559a9cec24f7 9bbc7263a94f40419618e56a8f8b024b6ca230cc 1256719fba0cb6de27ed4f1e1b98e3b1be15e932 45b49125fb8d192fed1643b20c25341c1925d444 a781fb556bd32bbfd887048e2a5bd284810ff8f8 8108c59602010f84cf6c067381ce01927eb00692 53f2c2989c4cd2bd057d04b78fc60db1e92392f6 da4311c4f978281adc0c0efcce6747b356d0e57f 5a7d34842fcaafb318a73ba77c8523677842c329 969bc78d7d107f2164b22565b9ad373907f2bc17 511e0b7fd2f2950a03b49a0c0468550a83a3369c 8f48e4d8bb50b2578d10157ee2d7e7353f775e77 2f5cdc44a364168a5487c5278c5a593f2b6d799b cef2c23222ee14ddafeb69f71b78a8d742d08b30 87341a32675eb2ceae81ee32e2e603a6d9a661ad 080cdd0c8849bea07aeba260b9968251ca9ed061 f83288ed57fd5c3a09378f1aa419faabc83012aa e9919331566e377db639384ddd92fc698aad0e2b 0c92875ff134117db001df44f4a4de58dac11155 5acb9ed089d660450af409c82d5cc96dce5b7049 edb591b9fd2b2b57272467b44861711dcc33bd0c 744ecadcb9ca71b44ba6a3406745d2b75fba5295 ddefec8a79717603cb58012882afb5c23ba8271e 390abf38f797da443940b4ae1cf1aa264eadca2e a717444115dd15e962a97ca257c2558455cfd14e 6147300359540c109a67f3526ac7bdf427c8c811 f92e0ffc81e42ce4dcd96e5d2277c8bd097bbd94 09b5425fb0a2106498921a67e33f5877f8aec50f 512d14e785600587ef7c8d0321f349c85b0b3388 2a4186100039cc7cf13f92e5b6025f35f7f42f25 2dfbe62db5dbbc8d415fbfc3baff3f9b6bbbbd1a 503901d2595f2a7ce1794b23b7368786cb288426 6f36a0c154b97bd0be71fb3fb9f02e0a94a06a4f b04d636e3561d1b03b7d670faa6edf040f40f9bd 03edbeac0ea2ba1a7411246ef07697ff3a8dc574 8648d37c33c37227de9e5596fc6b5d59f4706908 5d77497247935698351315d0da891b0bd388137e b817e124bb2d5760ccdb5980c5a9852a0c731466 90f4d4389c2d0119d59356f7af94555da9399119 cceed953af02022834a3e963aedfa73a22761280 d56ad5fcaf27bfcfe5ca0dd0299564749a39e7ef 1193200a1869cf9b7983f96f8a26eeff413733da cdb90f5ff95af99499df94e9572a45022b704e9d 7221acadea25197a847adf2a6ff4432efe0fdd1d 858f68a30b9b050fc4218dd36f17aaf108abcb86 8bc7943cea575fde8f5e011065e7d8b00cd70ef6 a0fc141841fa578c5dbe4a4a23518fc092b0bb31 009ddb31ea0657885c239f7e2dd1c4d3086b04e4 e5e2c2b08907562a98e449949860d83202551fd4 0a81da3d8fe30c14fe08f29ebc9925539be4df9c 4d6d482652818597838ffc8ac4980c96e4592123 f591d1523daab1fd61694a44a60d4bc4cb6be163 e0d1d3eb29ab184c1753bfea7c3ea987783f9870 aef54d6b28e3b2dca60e99c391832443b8984c6b c30a391cdfd31b5f3a821c455a872d9c924316db 98ed008449df96a80afead4ea0d2236a9e729c78 6541b5cfb54369dcc8d98cfbb8beb69f325afb08 09ddc8e7e65c5b4d82e90497e48dfe22a1f19a30 cc4195ca6092c050097c6f40406cb52a0182a01f 92393d50940238fcaab0ea166d2e6b14d424764f 8af30d2fe61c8aa38958d0a56de52a70be186514 c36731e48b97c414d4b0ebdc12919379ef96d6cf b9bb3382f29804c5eadcd57dd62470877aaa33ab 614f327cc3433db5b67424d25f64a75afad9b9f7 10f3e23a7f71d21177e71037ad71819d6f26f25c e143c66ab355cbc94186e9919e94571c6ca9575a 75723c27527a602999af9442cfed96e798da6dd0 114e1cb6f482072e97b2ca2d4d4fc18a66cfc363 60ea9c49399e9968a9b7b76942c2fd9dbe239519 2b0ce536bdaf948325c9170a4c9b730aec296931 f5b6d8aa0f0b8d0fd92389e840aca9cc2ebf33bb 7f43826a5d5cc85771d83987c70b5569acb84833 d299785a0e5c4f90193b4256547ed574f2504623 0489ad9c39fd1a4842596fc7fbcd4af8de43a78e 4aa794c0caba6e130524ed0e16349238a1f83304 8610699253f4bb49a1cae40f8ecb07749b477c30 a197f04aeaaa55174c51af234e4d6fefa3f6de6e 7e8a3e75a8f3ac7a20a4d23145281ecd68056286 6b7fd56c05402901c0e0ec91cdf5b318a59356ac a99e4726a9b60c164e219db2af185d1fdd9bbfe8 ba1f3358738cdda420be82e51d68a9dfd5f5516d 5c308bf393b6bf4e31c02adb16476622fd99d7cf 2a2c20239cfcb8941adfa3d1c77ee69b046cf1ec 972e357ad85b8cdbd0915b6d30c03600db5af585 263ff8525b1b4d9c60ed647723f6d004e35853da a0592fc2cd95dcd8eb0725ea15158c916d3f86a6 57fdaf1a14ec2eba0c4580753b458419eb602761 a165c72785b78abb5df25de2e7ee51787480744d 172e52872e47daec3c888c12ef893b7aff111c37 11f40d79870b47c2c9f623d240b76ab440b5b1d9 ef4f17b7ab923b33985de50bfd5e718ec496dafa a3948592c86260c8cb1b60d960a11a1179d5b72d 34365ec797701f7f6a0271c6e8693ceae0787cfa 8c789c80b8fe4fbcd2d4ce9522688be1d594c996 8c240eb2acb35e922e8a043c3a6a7a4a1a60160c 82184528208d32e6f16e764af08b7a4e158551ce 965e19df9f764229a9c9695cdd7b6602cc2b4df1 ef6aa1b665cde846dfb48fab20dda0f2586ca3d5 9ad6461678138bb788497f4f1bc8a98ce7fa83f6 fec3c3514804b273e7ddded0fb5d77d0caa87e88 205f1758dcf974ba0bb6fdb80d0f301e2a41a852 55e2e331871c7bfdfa8f5a73bc53b7e99a0795fd 493fd61694682d2e7a0acd0548d79f6099ed93f1 9163d0b61e9e73e364cbf73a5702f1d8214d4fcf a229eabbb7ef2940484bcaf47a574f826a3fecc7 5b1192af17e3ee2c1bdbfefdc1dba3e66f03f470 642dd0b5318bad798c032d3fd65034a019f7c607 7e42b305e540e48f3313640415a3fe8c8d60acda 87d64526a322dbdae2e5c56a3fb7d6c68be77079 c081d5f9d8935257d857cf64da7837f8db93045c f6d4bb9dad06db173b86412f00abfc853a1ee129 462f2288d9c39aca3a64e5dc4215033336de664f 73a3f4fe94d444e29833ebfec6772113996b3009 41ba2b2179210374256e0a49f75c9409fa5ba0a8 a339fb709eb36ecb5175c62ed90eabda1b237c1c 332b6ef93e21a862f4ed48e9d9f2424ad3d3378f 63767429438114e93347cc8a62d612552d394c47 be93b252fe375548e7938c74cf474664a91ff65c 621ceaae65ef9ccd7461719aa3484f37f7045f08 8e311b0f639713acfd1745488d2796f4572bef76 c1fb78bf6d5379cd266468f2558accd6e48502ed 41f8d8129fe832d390282379f80e5aca119c0a83 00c663e0a4efe60d891cf44b7f805e58e1887bfd c5270a225b1930ce43cb0db035b46847e8e98772 d0c0815736f6017fd7b23cfc49b89be8c0a96801 77ee9e710f7dea833910ae4ae5464880ed184602 470345d921fd039000ed292f50df611f6b0a75cb cb3f24c84a86d08e5ecc9224efb14a383d54393f a98861420c5fee2a02d9fdddb316c3835ba51838 0468edc17e0b0e5804493ed7c3d6cc65a6bd7fe6 e01ef87c1d6ec2f32279fca649947567c39635bb 70fe828daf542c1bcf11a27ede136534ccd7433d a8342c4c1e6ae35a7076b850c4634934dbf9b8a2 73e832ae220bf7681fac6402e90c05f0eea82903 4f26ad9b542774ad62b481d2a0e321b3305779f4 977baccca9c0d34e42a21c90aa08540ff781ca0b 51343766217682daa4145ec8f12fc4f35cf53ddd 5b6891ace50bcf0ee928521cf3c21c83431f2a50 84b402c839040651236f86cb1c84637db997f4f0 a91eac906f7622f119df0234753ee48e6b184c1a 7918d8747e7aa0f78f1458d3c4bfe66fe971137e d22c346331f29af69381c00c9af228538593772e 28d74bc43bfd3c0ddc9f3f8ec882e5d407755250 694453ccb1db40382d55f5eb9cd16130067d31e2 acbe574532a2e4ba4c8f3fa7f72dff3bf5d37dde cf3bbe4da3ebf2b61aa48363872a0281088b383f 77dd3d06f44f6463706fb1ef28ff0c115f8479a2 7cfe94869054967aca9344486704c3abc580b402 a1c9d7fd439fe8b87f45fdbb91b11b27ae531355 fb2c826d2191d090d1a6911388da8bc6b714523d 233234b92f4095e90a243f2abc4a81c9c7a73195 f556cc6edbd2bd84b45a9eab166d29549c2d2738 6b36c4505547eb5b1f5423ec10fb1cd09266579d a79e39d92cb7a4cd2c6132563b0836904c5023ee 333ba779b015a7bfe6d164a61b6860c49c268da5 deae6da18d0d86b428fbaa377b29c8e41d1dcba3 16a38ffbf76429195e8a0ac1ace43f8250447a52 9abea4d767ffa205ab8b45a6a3a421e1fdc65565 920d7379a36ad177b1fed4a3d3b0e7e8f179dad7 f8748ffee7d3ec39d866a387d189959551d25c01 8523e31675278fb1cad363bc4d54c2de25517101 1c8ff4e25067ef29e751e918e293f23acd8a71f3 52a06a6acf5fc6498f5bc12e5f38f54bf033a9eb 8b6130af0bb858a7e30e32e9dcd4b7362f302075 d7e5549508b7fb6cde328c3c648078a307ba175b 1ecd5ea92fe2cad3c89f812d0863000bb5e9baea c153986e62ac231a683e01e4276a070ac17597b0 7d9ea26ff8633bd67e8ecc10d38b6a1220256dd5 b17b6f2108db2c8919aba7ee542017aae0f308eb 8c0171ca72494f0809a62cfae9fbd93c97ad7d9b 22d7f6132882db139aea3396333f1d43fb369cf4 8a61e7e863cb2db4d6870af0dc6a9da849e47187 65b33e7b428774f590692e67065f958e3c973dd5 f80807034aaa46adac307296975c823c3747110a 8eda04475b2f8ada2ebe41ccd8db23365d709049 f380fc5b6134087a74c1fd9a74b6d03cc0a3039d 91f86bccc5a3a3a77befb745f28ed724c3eab6c1 0d246b7bf5194edc932ab772cf7322d9c079db57 3f1f8e4f4f1d534fc5f3b28bce66a7fa66697ce8 367eae68fbb01b9c78628b4e8a7b7743dd71b773 defd9fc5cd71404012f45eae1eff17bee869bf55 19da6b5a973b794c1a430bda8175f5ebc1cc01a6 d16e39a9b3516de2f71d05ea6aa0f66acb363a86 58dfa5b041f75cdeb15475a2e0c356352facc860 b29eca7aad79b0106825e5ff2f96a6a33dac32cf 6ff4ea8b9056b772f4a680eca22bb38886a91657 3d1d330ab05e4016ea9bf262d9414cbf871beb72 98145547d751e302a0e121d51c5b79d0cb42c5ea 236791277fada4ac4e6c98a1c16b5b9cafad6d7c 06da60eda3dc087e5a2f2d9213efbc8aa4e28177 7b88b0ca311eed8c02082a11dbc6aa8569aba915 5c1ff2243a94f305a379777c5a68b946b6c79ce9 f798ea67901a96ee40c2c9aff370356fe44be78a fc2e0095aeac4bb559c6f6958e4c63663c1f401f c46ca52124597de315778707e5b6a01c1cbd6792 22d25ed216e603915c1ebce0812f49b318642dfa aba09c99a4205164f9ccd97b4f346353c27abe8a 9991e5613a9850273b5fa1a174900dcc91207dda 9d43b25ca4f1ef6ca88380d4e613e2a614bcc74c 712370608526d0010d77315b42845343bfcd105a 3861d56f1b295d610b15f5ab0cd7db01a728b6ea 2433a1b5da055f967c808ad11192b72e0073dde8 af8edba0b52dede4f53b4802a9ece4d09ca78c73 163574681fa2d3cbe5985b35a9dbfc8dbf584593 7791454e5553859774f3d73c8c11af1601d8514d 9eb11d8782731e24b933e9b670ceea9625f99e5a a00c35456e1058b639398e049b17f0871c1fe72b be28717d64481744f970cfe11e51b1bd01140c23 15c269c2424f2e4529ef9eae6b70396e56966586 8e049977a25b7229a7c68357f1a99384a79655f4 e7fe341e3f1aaf329f01d799246f1a421dea2f4e cb49c1ad6065241080866dd260129d0161bdaf89 072a73571d961e8c9ce18578153731a76de4f9da 8e4066b4498ebc5ae877cc6a4ebed60f1d895374 4783d8278b240be0aa11765f99b06e11ce45f297 7edee300fe0501ab62fb6b184c4f5924ee532d8a eb34a0a07d6d509598d091f89885486b72e0e1d3 9052cd022fb012106dba37907fcf3c2637bcbd66 4b0a829a17d78746cccf58b1af5f8bca71e1718f 5e6a2f5ce44b11c10746811c6c8738a7215e5961 f8ff778390820cde2192a6864d4c1e9e18fb4169 c62ef291e9efbeeeaf56a797fb5864dd4899179a dbe39cf8a31c39883095af78529cbf5db0e8cdd3 8928b1d2e0f96b9c000ba36277d99613e0ea6dad 9d44889174f4b3b400d65ed72ca1e8fc1b35c6cb f5c41ed6c8030e6b02b1ed97c3d6a09c310933c1 6e82349a3a1438803ccb76a698de7abe00c790e8 cecbb557b3c201b38b96956bd9406848bbba28ea 600fb1c435a9f5afb67d9ad5a1fbde89b16c456a 51e52f45d5284bd8532eb3745bfaa6df6afc9f73 0079a3a8be44e5b2b688352f3833fed440a15560 6f80295f04fb928de1efb13b3925fdc44a49f7cc a675c0a0157a34a4b618bd4e3afb53f45f915be2 b65b56a5201acab633f0ceebfad3ab82ea4d893b 20df4fdbedc9bba50d1e164ccc0e3305f6e2e2c2 7e2a8cb204a54045eafb9e77715fc30d35d2ee06 f70b18e91500af5e1c40adfd0efaed1cbd0c2c83 db074769adc62c70891dc3e91d1e0aaa9d957f38 4a0098ea7ecb767cf6d3f97746e2ecad2883cf31 bf44acddfa909f34406db6d42f3af81cc42ada94 6feef85c45b26e1f06a2e431c21649b476675fa9 86d654076757847497f30bdc6a3375734eb49062 0500f24778e2ef14901b73c41853df51bc099be3 ed34166652807e92d5c7e7890b8130f8b9d5465c ca12f324b30bc6dc7b757875285082d01c540a45 e08808270f234d2bbc1a7a6ca070743b7e044c6b 1fa8877022e172ae537bfb2e1643918afd410119 21dd4bde34f54babd54e4ed4d057ca4961521ff0 c9a1598aba177b3e72ce66eb35ae36a80c244eb8 12c9fe7eb85314578630160e73378c53c2c71ae5 c92c1acc42c22af6467bfd4e3942719e2856857c 97ee5a39ddc51739973807d9694bef6df2c18b5d 30df847fea398ff0d284b423b47b42c22aeba41d b6d776c396cbdd95e1d2ea58c26444baab608059 b2b2297fba51c9bca867824d52c4928171329822 b1f704bceb2a404f85f7de3ed5406c2f801421d5 93300e42354f8433cd29e83f424af0a10f7e20a0 84b6e91db8dba80869909c8856d3bb8f9abedcb0 a3fac26bf149079008697a15cd74efc66a84178b accf4230872adf02faab3528007580aee189f2f1 25a5b264907cf555047e1479971b392b730461ec f0ed152669616347141b2251669eb647848cb2ae f8ac6ddfcc1cf3bfc32f6c7a4bc4f02c4b78e641 f0919d55b94f56126910198c0bf69f856f4f8b49 f430087c23c102d7a371d6b83ab6f7796e3d02c0 ac6b3ef9168737622761303e4da599e360ea1ef3 cc42f6ab9d3ee949cca6d1a72d22f50be4b87b6a 2f156c46e61018425aea6bbcd98933a1ad5513a7 23f0bc574981a8f8f0306334658153a64003abf9 2e910f26e379c9522279e3fc38eb6a9a1c41cdee 9d08fbe3c75097ef429da2fa03758afc4968194b 99305b95276aab90ac1e3b3d7b2e8ca0f3750b44 1e0a779febee9026587fa42f7a3b8313cf106c28 f929cee43a296d27860b4ba24f6e82118868d5ff 896e116fb44766230facfcac78d23e91d36c37fd 7f15f444d373d45066a1c229f82a3f81bfd23420 f40b9f6738d0c7943f2cb1ed49f0b7b1cf942825 1ced9abb46b887986e839eabc619891090d458ee fdb6f0b71ee1d4c5d42211f7d442ead207fc9025 522f64c55b3a233f7a4ef9943e9a3c44b06b0668 452a03a5b1ff3138d327d63153ebb021ca99da91 de25cdf2d91f3a5b55dfd74eef0a0db6cd265c59 a31eac83a28a41643cb6acc740cc9e4b7cae15ae 2444640bb1b79a13922642b13e9fa85a2a4d192c d0f1e9066ef9027ac5836afbf254df046f74240c b337f77e1927d041df76f27325caa28fb9cdd15b ce5c6ccebd47448e2847b622bf5a1ad5c7331f6e 7822f3b3caf7190a91c74f3363df87bd7f4b7d93 f77c94a10b7b5a04149dd00323dace9be80472ab 53c8a3f9f9028281b90c547aa4095c476a5892c0 62b40a241d297be0b9a4190da857c336997c2ea8 9bd1b699d290e1557162e2c437843373d9d6185c ac09b6d5a74599380a366c2d9daa394df759450c b03cd68f3e116cde044785ba08a6c42071e360f7 2e15dc8b58d5cc3aef9b0c5422021ad9e793edc2 df6013afe530df4a56770638054bc47d72103136 5af5890055a3fb5f6f8b9f960f20d3ce6aaf74e0 7f86abb3cc0bdddd5db1a1c3829c840dd5a9f1f0 4d880f3271b230ce56f5a19dc260c906e7377a65 4ba4b3a27d3e3810090445ba375e7a9d5def46bd 141eca2069c4f71fe83a1da8d77356076816ef41 f3dd93f6df16ded4392e9a27bcc57e190bbf7b10 50e394499e78de0a74287324fbcdc3599bf40dce df4908c22dce8729b5721bdf53f2d568d5fd070f 51b06db2c5f66e37d4986b68b52a3b80952a2b2d a6c7a81d8348d3cfe8804516de343d401569f7d3 8b20fe2ccb3abda622263e4c6d4cdaf07cb8a60a 98de30ba6598e2bb14bfd0b6d23c9df403d80d30 da2260917c8591a3956701142b1faf4f7a979b76 5580fc0e9743b35118540cd2b7c6e816b5e59b0b c2a6894ec18d84d7a2f8fe6ffcb9e5e4b08660a0 180f878e065f73fbf67dedc378854cdb8527dd0c 75f09a84fa263eb79fce0043d8f267171a82de4b 1c4b4e02a4ec10b1a6ee087683d3837fc7a5e208 349506cfb444adaf84dcd10fe6746096b5d5651f a2bb6b4432fd148da2208b6b6831ea8755a59443 9218cbe591b01c5e70188a933a99b4477722869f 44cf845b3a2a7dab8d0eb1791caa5c4eb018011e 7db0799716dcd27bdff8f3ab348a3d78c0f4b9cf f56e909dac738d85b149574fb6c2a77fe01f13de 1f446103ade63e51cedee465aaf21d380eb6bc13 73e3c87ace0f983144b1390c397097dadabd6599 46c35b1087c0c7e7ee33cc7ce1934bf7b90fc7f4 d426199ecf045d5c2f6f60fa661177d01921d7ac d04d9045c9ad3eb50ccb43c733393463b0d58d47 2011d4f8478ea60df8d43ceff1726e4f532a3bb8 1f8037993f30f1c7e4164f304de6ba9b9dfe518a bf6b67faeb06690d3a4614efddb88c33da92a45f f494d534e297560b37641ad5dd2176600175b974 afb7c4ef8b5c67ec08e677383b55c712127da80f a540b8a9cccd6e68fbe11506b8a0eb551c99f6c1 d061b13546dd4ab1a376f2ef3fd493de5c48be86 65fa04ccd04db0c0930e5fd980d4c8a3f6a77e64 a68271d21e75bc5bb710a6dd372f4eff0a7fca45 d6788e481bf2c1ae0c1f0b84ca6fa3ece09ec491 cb2d0761756e60ee9496aaa9f9ad3e0a29cb122f 696006f3351dee641a9577d75501211bedd547ce f0d9898355f60f09b7d3d8b141423461809c6f56 abf013fcae1ca6980260c8ddd2f234af0cf4086a f11b6b75e14c99e66014c82c5a17c0bf5cd4b8dc f7022523f2f0a12ef3f8f0d986ed840775f35a43 6232d457d6ee4f603221e0f0472d37047aa1119a 2ca4990ba940dd89cea692eb5a73f5c080c29a49 c657e4379d83c59368ebf3cd7da3d468725f590d 963488f28417ebdf784bd3a448e6bccb24aff7c8 ad6ab9c800a861c54c50a9f945ca1d0f70544ea7 fb5a46433c780c142e59f1b5cab7286577a0832f 4fd982d94393a69c76d9c01e55ba6ba352416d0a e9d24222f45ed8a6b3057d7ae24f781c1cfd815e 4ebf782fbdd53a82bb5d22eec02a108a48c11ffe a48f63e7ca649d54660433c02ae093742d791324 f46b9ac62cea753c37bc076e6aaec7135f2f3197 3cf705ab80117471e996eb30dfe2bbc34914efba b0b4d64b22bd28f8507103abf8e6c8bdb6bdb39b 1356833417586fddb859002adf492a2d741b75b7 6e6a487a47c18605145591ab4e188f788ac767e3 1a72b2809d480c3a3d7ed8de7d9f463e68f2cf15 e6aeea739d19255635a77d1f1372dd8c2c1e55c1 9fb7fdb5f0380384289bab97a374b96678e006d1 f7e3f834aee4ed830903541c13f83483ebcdea2b bed8feb3aa8102730db76b3eb1482d3f07b0f4ee 3f24e9137f7dc8e6750850d4423a3049dd73f7b0 91b2e6dc9108f37e0b3c63ab52278e17e9dfd363 bd7ed52a9e143e6a2c84f9b302dffff151e07f0d e56bed3af3e7874d5e6ed3030a7df78d18ce4ae3 4523711a68da0e3e3bcdb8e5cea9c8324c68458a ff0903ab2a2845a22427df6f047e385d144f93e2 accc463d0f94cdb20365ff33b76e95b61baa0a13 6fd5cd296fdafa99a348a5ec20fb63e4f7a20f2e 71c6c2d56c871f124b1150b942e270269de9a3b2 00ce7c202d1e9442afce1658c8da0b961665f913 8429ca3ed7a0a3bf97da48529fe0c4384a68b79e 28b942fdea7cc02285921a4e23b8e81851b85365 789b19f43e32f82044541853bbf44da57d9bbf14 b985336e9cfb3d8ea674d7936708168f3eaddbb2 615f1f5f17f6b585fbc828ed7a8340f1990a4903 2fd69205fd0a652498f3054b5cf4c5c218ab4f57 f15ab581220606781e62c854914b8acd57cc66b4 87247eac384d3307c705f8473da61e3f5f5d4668 3d4b394e45ed9942d3f3100af124524551d7bdf3 d221c4a1dae564b791a735524bd1ff69ea2af390 cebb68df47d6d813f2e41228564d810d1fb56def 208451f8a4b20e24fd79b4da600e32c9113c8580 ec18b535f1b6ea05ca5f1c50acb954bcd1265145 32341ddc64cf5538b47a4091c81bd5fd7eb9a94a 1f5bd89da820610658fa35742021ee99cb574873 406c84da6e3bbdc7b02bcc27c3e606982bd13be5 85db55cce826041a8ec7187d9cb9d1265e2fa2ea d870f3bce9aee1b5cdf094428b4ffa223afbc4d8 91850d4fffd861b95ea532d8a4b3b0b699ae82c4 ae410297f2f474e0086c582388caed585a2bb1b5 dc17ea1b037ee96df89a2067d6c0fbfc5cb1e78f 2a46cd8e2ea6311e086e312c0d595a49ac31e285 4b8226ac53dde71c1224081d05656c54f91f10c8 c5b353a1c23d2d4bb8848e53f6333de10a4d5e91 87e1747a2848b3d0bd5a112cd8b5e2c6a95b1d26 52d92c08ba60e4a9bc264390d6a19c3319d1876e f02dcd7565fb7840e0ff1165e0adccee99f091f8 9f7123eafbab7f601a11dc8626b5968e2c730ee0 4450675903e48acca0be1ffec6920680346e166c 20521872d3ff473847cea99cb444e6bdc2d27266 f98fe96b16ea18f9a71973bb8deb4ec90aebf361 2043671d14fa9b10ea2c31d598ff531928d0398a 3e85c5d0e389b3d24069d6b6e30d921e72affcdb 2d21406a6d7f052090dac2070f8c7f0a14d0d64c c470558c8ac9f7030c99c882fe7deab395c98b9a 79363bf644de43f71412128b379048f665824102 05c85b157f75f9285db5670836fff817d2908d26 663b036bb8f72fef060850664d1a7da7881a207f 9bf7a17fd30160fb3c54f1e0b07b6902227c5fdf b3b06ecffc0a9b0cfba53673bfb7fc80d454011c 37951371d6a9f982cb7ada0ed37008f7ff023872 87c82eae29b662e077e0acd18ed9c5b78640451d 59357dd7ed91729b50836d584226cd64b0d82280 f2998542a2210f7f46254e0c31c889507bac8e04 2df7a15dd635a5c195ac351225a395b2b2b14dee eebe89a0505b9754080cae33a78e8ebb25a22d5b e5c8c5ec043a01a421a2f36cdeba8e3651e1b479 bca0ec28c76cadebd122f0b4332b5eff4caa0bbe 785ce7345cb19da1a70e79dee027ef593ce9bbd2 7146d114d459282531c4529d0a6c154bde176024 3d062f4f4d0ea007c349fd675edc8c8d5a574151 95647a76b6c5d56ff801ebc0234b51b9ee12d00c 8a28c4e4726bf26b621ef525c0cea0a9e50a7701 5c6832f729b7f57af006402b66dbb56841a80205 7e82d22478f68a9172186ac44badfec25b8b567a 8e468c5979b6ee87f35655b697e2feccc25f2625 7adf10cd57a9fa29a621e2a7e2044e355f204f1a 83918d4f5088801d9b643dc4d81224a44c169d61 e3f1527088960ca1d3ec2a1b83ada6152400cad8 589885ddb72e7891185d67fffb21d82dcc648665 4cf6aa16f6fef6314209d258bdef3849988067ca 654ffd48590d9e2dbab169e08e0ff31199a09d76 d7ff12ef9dd08112920b402e5980f06257f07e01 55b5b70fd3f2950d7e7bb9a3353f67644a52429a 6d449c46f910bfed6d88bbfb82446778c42c67f5 32e2f583fa76e98e280058b853602cda193fcd80 94bd4ba22c392af23172f1b76a63ef498dfa6886 96ec705911530d96931c7bf6d6cfc56bcf156508 8f56d7eb65dfd8716e31932a9e25d8d07fd6b181 3e37af2bbc0e9ebba7c73b718f73d04bff00d5ee 089ea8a89348b7d9b7168f77dff1470114552741 8816cecb61a3cbba443b6d0df65169fe3b4dc7b9 87a0e311a95eee14521508efa8acba6ed18a7696 515308677e532d6509d1f8d5601593e963494fac b102a60e72f42f86a78d75f62d632c8a56030945 24dd735e6baee252cbe7017ed28b8848f0266971 57f51b019fe6bbe7435a3d0c2e2f05cb3fc73df1 6b49dedd1dcf1e1bdb94968fba2b98856b0e6aaf fd53db64040d9c2e2dcfeb533d71d934120f1e0f 80f299bfc985bfc96da4ee448f7cf5be98e3839f 567235534c8addb6aeba0f93afd030aeffeb17e1 a231d9cf62d60ce648db6205155988b364d34700 38bcac4d9bf0190350a3dbd1acebaeaa51eed13c bef13515973987fb66e3d800e32ec0a72d06fc69 594439db2b405d8e1a3c81daa65b5c1c155e4b4f d55cdbeb1a4ba6a4a0fa86f148da5a79cf5c6083 05bbe2b6daf07af494fd2570f7b90275c78514ed 3a206c1d43a8953d7b1e05cc395575930976dbfd 4155591fb000bc8a0e5f38d3d54503c3b4c37c92 4dd8b4d6aba7ac199e0003ef71bfbceeb2b470c7 1a524dfae6573866db77218c1a9ddd7c13871e1b 5a4abb73e8a162e74c659f4ce498b6d7efdb578a 4ed85584a46662a70fbccfa6bbf6fbd6419c34a1 350fad454a39b6eeb63d9fb8bdab2eefadd85fdd 7518d2b15a09b11864ef7fd62bfc3eea326455ba 086cea3091146d0a448488a1deae2fae960bf94d e80ed840fef23b9764cf45258a845eb000576197 3bdb8233c7f2be92d5e52843a17194dd29d5caeb e2a2ee52d1ab8a1d36986f7fada62c4173dc26c8 f22e1a2a3313d71b8153aeef9cf29fdb9289c7ee 89971da64aaba1bd8c407b49d4f7479561934fb3 023d01d088bd25cd7aa255ec291fe680389580da e7567e7faa7505c582c4a7ae7a0667ea7bf9b77d b3cb37bf770eb52ce7b14ebfb309779fd0237b6a 9ecdf44f54f3f57436c26df70b1097da598fc059 5b2f60275a3fd3ae53bfaa8f5328cfd270e20773 9c4c35ac2d7975cac578c631a933d93164424e5a 61b6654aed0a1763508e4091542c3b4dce0f1c8c 6d6c4371478f1e95b8437ceef27779efe2380258 5065ec7ab12f2f7da746d5a7365da2e7cabed4dc da98f7a58ffb8927f82088702b8d32ee30a139ff 618b3060406164f535bc932dbb811d26d4a2006c 1c45e339d8ee2ab87ee655e3c20ce85f322fa3a6 53b92b8e9ccaada70a0359d0c85628aac305e17f 5611ba25e175b17e0ce3b48c52befbb2ef27819c f1bdb11422abd9a077f919b7d45505d9e4798613 e4d4d19902b27d51d4043a05e2076ed12acbc1fc f6d7fe5a44bbbabefa94e0d86dd10812de59135d 21d9df0d26acf8a35c8950e86de37a438e1ae25c f0b18efd52e597c3a7d2139a49d56f114cad4dbd 019e2921d4ccff977ba795396ad51247bc2d5f00 88b0ffa3b6ee396422d649ef2c1d89efe5aacf3b 4524fed556aa29fc7ce87e27537fbdf20ad95a2f b250e8ed87a61c724bb0eb171193b39be78737c1 271da08b936c34eb309298e00af5ce9ae21fe146 dac2d0091bf95a53fa85e7e2fe7f4201f9794c13 55ad5158ce7c798d7c54ea113a93df0364e6ec45 3cb69eca3e41d1113343c0b09fdc3865eded920d aa30554c49ad6719fed1b6f5bcd690690f8fd37c 66ef713f4fe8c86c499262995cf2fc1c11191a53 86d652d1aa23642e188f25c4e7224c134b92aa18 525a118b3085c7fb3fab1528352e6d7696ce1ee0 d21af13815e4eb7f61c10c7e883e221c0eedc801 60662e7b943d6cced20e5c84dd123193cd064821 15a349e3f75a055e1fcd48f2570169c185ce12af 6466275a2c63c3ac87ba15362fe4de3499d0f911 af134b5c2f69c5b27cc3ff5a4d0e85cb47593912 585ca820596ed278f1b371e214f07de247205f31 ca4dd6285754c05b2fa37b9c948ea27f3ef58d35 52b8ea2582cc5891fe411f76f8300a9c19608014 c6d0136bf66260c302304a9c53848e08337d405d c84dd64b3eea91fe8b8e7370e486cf7f6b3cb01f 236781615dd35adca1dbed54f2393196fbd14523 c15d23632c7d6a327969a627e5131a6673669193 aa2d3dbe4a19b071c911e646d120a56da484f453 4d5b3930d8889a575534b61d3e9e24810e2c3bf6 584dd2220cb92303322ba9d94d9758aa4e5895a1 081e294de5ce373912d37701431ced0f1c5a741f 854a5ca568120560dc4679af16040f6ede7bbed7 88c4f857a57a135d4e01592f5c53f47d29d3946d 4992222a71dc295887be5db41cd256c8ac86f601 2a179afe3823d1c0e43c6a2d633b6e30de5ffffa 308aa57fd79bc81baa1b93f8d085f233378f73ed 38a720e9501c096bb5c1c883ba32c100b6b0a938 ad1c2ef221e3a43825d195bf7ed61022e245eb38 b02a9cea6443b478270019ec06a59d7492fc4430 773a040b9d80363ccb91508193fe7248c6bafac0 f6ab8c23435e9cb915917ed03ccde018f163b2b2 ea3101ee2c14159aa0f8e5a0f7ac895312b1fbac 0b722dda197df75d4fbc5e9f3596db235cf147a4 2cb26a6676b3e2a64be83d9513dd55884b4c8712 e7a19b88c541af0dd5d2c54772012630ace56429 15bfff19be03aadc3272b623bca38b8ab8a8a53d 8a846e32b6dee5467b444739760ce901ee0aca56 abab579857a96813322c7282fbb4f72cd9954320 3569616c74eb931f2595cfcde342ac7fae9309f1 e11db36c8ccdb82268e74957b17ec715f2f19525 6068f004ac3dd7c448e75ba8843a3bbdcd1fe6ff 9fdb9d83406de369f58fbc47021545acc88ecf01 28728176a32978a40237a15f8ed62a6cf91e8e25 7948f4acf73843b82e7568c437b08ebe7e61f7fd 1fda2565e609e14663e6d26e864600e82d9876ee c457ac6c0b9b4d192cd97ceefacfee5b524732e7 29ca006a3381c50cd830554fd8ce64f78bd2b0c4 dd56085afed9f718eb82035104fe543196943d18 dc8d763c21c50b6ceffcbd3ba863c826bcbfa68d 3324c38479865bd4fd8f510692814cdbe5d56d37 1bfbb2e5d2e801eb81987ceb18302894ff7d3c7a a84733850cb2400f89a1c5e73c39f95935480de6 a04dd0813cc98607cc8c0a3d03b5849989c9163a 3b13d125c8762b6e0462ad755caa1ea0d8b75f02 de4ce0c5b7ed2d6db17187ad15abfd66d2ccf5c1 9bbaa8b27d2de65a49c8d2d11824efca005a5b7d f2273a0e1ea9b24758f485b0bb37614bb6160afd 34f0aab15b480dcb1fbd67c88abe46d87e8e0cac 039a9b8f04a765257375a5371038fc44296bdf16 733428825fad2d746bb9256b4fa629a463cb072b 04320be5049aaac77df2a4252002f232fe3cff2a 9da66d7ae5d23e34687fd1aa207eb0723c606cf4 d72217c661d1d1a1774b7700a2283e265ca0e498 9fc69ffd8aa1f65b313f9617b9492b1a944caa2d da5ba2ff0217062fafa8071edcd944de5592ebcc 760b21c5194beb38b0f2061ba652389b84de2c89 c538f1c8b22aec84bd2dc654511cef00309ac7f2 5a483360a35948b70b9bd1cc084ed8fe0df933e4 f0e353b392fd83e2c445ea24a14f2d81ef7e026c 61457e311f5273f577037f81c19e6a91ac3a503e 76779608d03503967f50d232d360c662fbdbbbfa 404542d0caa18830ff17d402c57d49be88af857f b86501e21e09f27ecd8c204431c22cc86d50dbac f6fa89487ddb02aca5238e231434371f1cb8e248 d66da25dc032c7f90c55105a692d08a53fa5f8dc 2fd5e82c0045c679c7faae33cc1e028b022d5b53 08bc36ca7a926557d4b87da06717f811b735b841 e3676de65845b6ce42ada64bdcc9fe9f7df1d040 30dd49b6f22c3c8ffd5798fdb30014d23d256d0b 98bbe6636e165f910e57d832beb2ae1b1b518054 43e76a5a352d574f2761dd4ffa6326db9e288b8e c04649d9b99b763f05537c38c2bec0056480329d abc86a3e6e0e7061efc045cae4208443b4c03f0c d05085d408be750cdcc8e92f89e8cb42a851b64e 93ea351f922be6080e747c5be1980c0ca777a59b 55c7371d42d0334f3173c948ad92aa3526191813 0fa0dbf974430dd5bdc31ea9fca641d3fa0ee4d5 db4faecacddc49acc1408472c28aba102fb54e74 db924600a3c3449a9de03dbf2e1af81be1d6f371 e144a908267c9bfb69e71b7a617c3e26d5d447b5 40280d912474cffa5c3650a6375cfbaab897580a 763a53eb14a36fc31b73474e8c5f7796c4905caa 2ef276ee794b39a525f510672b94ac148c8adb58 f8e50ceff76a2c6e262309b8bfa40c2152ff25dc b5f33ec89ec0ab998bc3382e4958c218aa7bebf8 e647c44695e5c0b4c763367bfcac5aa04a17968a afa733e4a84d7d76f5314329e1b314da6d5eddbf e8f6efd315f79990b6b75440e956aac78eddac59 00248d2423d40f44507a29249d89e5d2ef0698f6 67f2f22fd7b1e1095ea3b7bf8d45260b34399b00 e6aab79e4df38c90ea195e6f4841f6d3a409e9ff 6a9c50599dc934f620a2524a4302932ea748e0b5 7199091e3f2a5dad43e96d604694b764152c45be b5dc57c4c9b410fe3a7d652ea30465e9782a0bd0 57f54c874d4eca11526634f345217ed6b92d34ac 52de6fc3b3771e20fb1dc47ec9fd38a4620d37fd 2a3350ab93747356593e6c76dbe4e8b454bbdc93 ad4a3fabca5ce074495192e8a7e82b65c4323da6 108c4eb98c666f91910e193b9cf21ca66db38360 a99272a203717bf053a6ad3788090a9d9bb36728 2cb5fa8e92060c41cd6cc05c0d5b23a908005620 dafcbedf3c3e496ade698c6ebb82d599b80cf1a4 b2e19844d6632ff54b35e5e088664cab9707e70d e1b28c362dc9f1cb894bda37e359292ba07fa9a9 1ce0dd183c17373b524c29a48421dc2b5cec3e1b 0a4ae6d06592a1f5e05ba299b5ae0d44242441f1 441d337b287836e1acd7cf247db715db6d18feb0 d9345907987d21d00b12314171a7ba651c768205 b4c9b089b6b84d0b023ceabc1b76710037be581d 6a5798e816f97b60c8d20d1b2ca01dadd1e51ad5 4027740cdd2dcb928b12f5ed265866038a3d6154 abe69234a0f4abcf7d55c8b415e53643dfd91f46 80aa676d81b53d3f02b64ab057ae0c91e6649b68 fc406fc7569c59ef2fbb0d0c01f3d7de0e246c01 65a09765b238ed8c22ee48ff04bf748c43ed4712 8872139480878c8545a9f3a3e9e5f9800fd81c5c 7cfac974784c9ab46b485a7758b81c374ad993f3 ddf89f30c17de33f95f01f8e0ef82e802abf33f5 ec059f9d9dc87e008da22c9462630c0298c27df0 119855f61020f326091349fe1413a48e06a88624 6f9cfd296288342f13cbef938965953352256c62 6715528f9dd4f2af3e9465962a48097cf97cf406 a0a8365cdb122b7fcf5506d27a7a85e02f2f212a 012c51bae8a3958447c724d4d6dcd738b0f94cb5 9665209106024c0b057d5b4716c490c7de37ba38 72730593b8e8c73b459cffa7e4770be009e7e703 c0463bfcfccbc3f4e24cad4dc39d1a9d49e9eb90 047fdd2e3b0ca570f7aa489b9e78be5d934ccdd8 c48d3e1a3e8aab609cd2c69b7aa48c971a253d41 115238e52cac5964287a644826ffb0c770d48520 93401904f9bc2e77a705bdb87f031859a5c6b954 d816c740a1064e6e47265839134c0f68f975bac6 19c512e280cad0ec361b11b473edb03f0d760f89 339f5fa8497c806220e4569713e64852a26fe616 107aeb7e9ca4c06fc10db8870bc1921996946268 7a8bf09df12782edd219f4d79edc7885393cb22f 0296a2f3d1189b24aa46500bdeba8c69cf3aea54 77a6010da4ff08a0542f13eaf46b63c5289dce8c ef338e57de470ee53f55b1f9903885a2830deaa7 cfc3608a3bad4409759944d37b778fec75b89920 3e8f94e0ba637f89c0555f807db7e2a7c096fa90 82b7f01ae18637c734c1c861d130bd350185ab45 fbc3763b574fd72ecf629b54df3ba06f9971d2ee 6d66fc724f26c3da2a846aea323306b8d393e1a6 b73b382816450b787afbb4dae277daa1705b6022 051498dd43ea7a79a646dff125331b3cde8d2646 f6f15214652a29180980b7bd08faeabd90c8f2e0 9323414a22b5b72b1719a5ac9311549f06578eb9 3fdafd40c78a007a0eb6132ef87d56d8995235a1 fa0adba11af0414e35971a57038ba5924d975af4 97f5bfb96c1a3927e250b1ad8fa96f82c4dcdabb f1fd5a9c5d846d2bac204ef3de94f449ab6c4bd9 906d7eda1e0b1fc16f7581abe0ba2fe21c38637d be15d1ccc95c8ee7c4168fa46fc1fdd261ca041c c2d1df5941ab3ee4612d9cbf3b3d188f0a246a0c 9f28647cf42815aca976ba41e2f608667af8add3 b2458e82900611ada9eec857390b5fd24753a13d f44c6ff71324fd60a3caf41542de05ae8cbeeb87 9d55cee384ec4b63b6d38f4865d81818bddf7815 a17bda627e7a58e2f2a9dee43d78b43b13030177 dbb73e1fa806ebb8fe28c3b56b20b6edb6ba082f dbf26fa68ae720562718ccb08a430461b3b70e78 97e1e1d0456cb75d11fcdaa7a6db1b74e0002472 ab5a5ef66b19a5b1bdbb6a3d5ea28774ed2651b0 c8b54de02a4deabe8deed4bc31907554be1ee3b7 6bb565da8508a56b77b2c3624ed8b6472ba056de 04919ae8899756ab2c296f89abff176b7f31f258 2c78ad512e1d27c686ea23f51814e99a9cfad6fb 07fb900b689ce8a567312fabe0a15c02253863c3 08adb8b5517cd60ebe0885085d8fe9b5fd673138 7704f9b8cf357a38014f6e671833a7cf0721c1de 5b2c50cfc9cc92be84b22a018b0d43ba90aefa52 c83a541585d6b57b0056b14d661570383d33ba64 858bbaec9f8c9430e11c5c7785a4af7a504e23c2 c825f0cc96a3f44a4d1f0f2865b156433f9fb52b 3f452912f05d773af0747851212f29f6bd4736bb 1b17d07f3a883e5aad7074268a7cab0cc6977ec1 4d17e9313f93acf59d4f21966a17880f158344b2 f146a1ea66aa51ed5fdbea49a58f39024b66398b 51d05d24de5466d8466677a3f0e91ff5cf88cf21 dd97ab89a44468179c0c5bad73b29c2272ef63f5 45c996f65dffc07435d3247ec362aeff35611ea8 01215a325738a620846e1626ab5d54278bed3bae 3484d792f50ca104dc53b678aeded4b3c0b06d40 766c15f83205a4a2ad8db77e7206fcc94ff9e2fe 846b2f5740a673e84af71bb8eef28253c49079d7 208cec5f17976a09464749776701787b2bf7355a 2fc9178640da72afb87c78650dd08a806dfbc5f9 827cfaff87d7ec41c29c2ed56301b7783cb60e5b 2a697518bf39eaf765eb924d8719d355933374ff 8bed138bc404e7c6ef38801df26d19606ba38272 7814f972b484c7b468918348b06d92a3b91214dc 560867ae70f0918db7e37868246fc691f95ac2f3 fe7000e5b65fbb76668c5d28f2257f25b7d2dafd a10aefe0f71f3bac9b0a33139dc3e6f5af933137 8f0850380b9e30de13c2c7b5c3366a90708e9b94 3557f65ded13eb5998b813e8ffbab39a903bfbc6 afab1403107c7aa8cfe5a987a140a69a31ae9f05 2500d9b34941f0d296c03797924f7ada358aa62d 844ec7d8a7a20826819c4e815ebb6ed61b883358 7d731aea536062e943f04a11a24d64b328ddaeb9 25537b9abef3ccc931876bdbb19568f3f0af3691 0570e7f70e00d5292b5db0600e912fbf2a91da4c ef1b474ff71dde1252f3624bdabe61d93b637cc5 7cd9bcf4122624dc1a850b1fe735361943d929db 5c883a5719bb8f0cdd9e362d6eddfb7f861b96ec b9ad96ee9c7bd28d685237296061332bbe1b4935 28e0c3840506c2a4ecf0494492eda110550195ca 2652fb65ded9058de654b412a49145d89ac1ca87 4ce2fa5b347032de9f0c7a93a38a7c0097d8e539 f0d526af56862ebfafbc4eb563465d1d5becd8f1 b056c9b283f50a127c7ed4ef24bafe23eadeb6a5 01e2cbf52b1e98782750bb9260b2883688c1bae6 3ecdda54947338cc3ccb7603f139a068ba458910 28b990e4255c05a6ac4f5a1613b7011ed845299b b98c28ef4289b09ccaa1a524dba96c773d2c9119 a4c634fe6853203ff8d70ddada8a1fd389ed334c bcc04d7d91a053dbae42d20763cd6e5fdf879dc3 a602a48f5f12c68aacf7e1b88215a9194f95538a 01a5f9ae18a1f907ee28331817031861f32d39d0 9f3c5bd51a5410417968a014b21f94d25ffd8144 9efdc51492d4ae147b5f6bacec4c9df6fd52c061 4052933a7485f44aedce393dba482e5da55403d5 8c9e553fe6dc64a24d4d30296acc14ec87b261c1 97c07a8b60c9aa37c776640b525a12533daafc8d 26126d45c6a747ce1c6dedc5c9825e7182a6172d 4c45dfcbf33ab7331f7c2941221dd1869a8735e5 2f32a988e5b48f987dba5d9552917b428d0bb5b7 4dee27f19dca4f5f8b8ce2d0347460d30036b4a1 f26c6a97acda8c4b5fa593cc92fbbdb0836bb8d4 fe7717b0c3ad6444632be920b49569b59baff71e a95bfd5f040ba16014be65da0320663037add69e 8a0ab80584c7717681557550d9608f323a2a3ad0 82cd142a9ce532c9ba54b425c77775487c4b42f9 bf5ae0f905203473fd0209e3bc5629b2a5061a15 aaf60dc090e068330259c00e7c5e5193e51f333b 0a9be69f9436f26be820607ba998be140500c390 2d8013b50abd63fb0f0b09263986be0c22368e90 9993f6af7b4048555c27f9b9fc1284f5622bb9db aa55b9b947af51e57758135d4bf22d56686a54f0 4b773b472e7e6607c8b063a85892d00975cc8ebd 6cd4853f28e9dd87da8bb51ab1793a4c54ad4f10 194d13209ff4e976fae00150a82b3ed5a55ea71c d0d9b8dacd6761482e5ffe0566636bba98ec1146 2402b8a7517873c3af6581af696a270ff5833dc9 764fa9faadf12e8846c0d9e7eeb352e53d2f476f 72d797a82efe795203349fdc676d58e467f05c89 4f9039bcbf76540cf0bbbc716f23385c18443e79 ffa05876b46064c92ffe5ca5aedbf4cfc777fd43 f47b0d1b9166ea49dd7c46de5de58417a96a1a7e 59ed56ce841c74a5ddab76d09b94759dd40b44dd 29b94b901e54c8ee62169f8c325ff29e5cd41d58 a72b82f6aeaa8695dd5469b0a3679271c0205077 0d3bb3a81d349100fb18d676cc98f2a8c66d2365 2930f3422938fde8c2f07b351b588d87ad27cf1b 3e481d35eb36b071ef5bb251c2835d8428e78958 91115f000cd9b8d3e814d41e0061f136625bc1bf ac0f05adb40b0d4722077407f70481d3ee584bff 1d6514ef2206f08d025b6f229e1eb7d983bd14f3 03423c6ba33b3087d31b8ce532772101f4c1d2e6 54a4611470c398e6b5f503bcedd14ce1d85544c1 5239c5494520b79bcaa84675409940695349967f ab3eaedc03cba116ea605308e217dc135c272cd2 d3507893d48d31a16c450a84e05469e3a0033d2d c7abe644763bc05b483f9fd8a2ad05b19b494ed9 6caf1960e15e8e1125307e0a5ee0921b980140ec ad291ad872455b8f14040d7e9b4fb061c724c6af c828180f1dc1f8604bdc55859d5e0fb1c843388c 480538e14451a972288baea40f142ec7f988d9e7 23cdb53434f948029dbabaf2df917b7ef41653a5 ebd10c7d0d1f8dd69a7c5dcb66103a6333b5999e efcdf65bab325220bd24eaf60da1838c6fd4d4d0 e207929eef6ccec3904d59df1ce0e7c91cdfb365 1f7f5a3a4ef9491b897e650e0a19346e37f7bef7 5eb6ef13362aa1f717ffb14bd8ea1e9314b30e3b f878084442450ff44253cfd708da425e15e1d1f8 23c1639ea0e5fb8764fd49790ce89b003dd1f75d 215182ed2d8edb0e140524af99b68be826816678 eab86b88e8ea6c0cfc1eb03525dc737e5778dee7 86af4f42a8b5af63f4f39dfa277d3b5d79d96c76 f0c8711b57d5f0f949b99ac60ab763621e02b21b 639d7ca8be2f0a156ea6fa9afe29b173bc2b69de 22c600474048014e926eb8f20bbc29300232886d fec80fc52f7e1fb8dba7656efc545181c7cbf9af 6027514990a89da91a075643531fc21052afeeaa 15517ac5c4eed28aa2afb24f569aaa1c1fcc1fc7 cd4643cb9969a50265e606ce45b36eb5826c72cb 79a1928ceb947144b9fc4c499e70fcd7a6c2f9eb 172c44d9b83c4680f855aeecaa60f9d1d6c51ce9 41a548a4b8e20b928747f24773d4a83c1a8cd912 9bec86daea2d29e4ab6d54459848e446c54fa07d 69c5b44a619a967cd000393c5879dc6780a5ffe9 cdafd091b9d1200e9be1971e84ca9b9da9057ca3 ccbccde8a064d5ce8aaa1fb97955d319e94bf156 3aa4a955ed6664373efee22400b71ea7ee131356 d793ffe3943d784321fefeb4a2806f98295bba9d de2dec8839fa5f0e9ecda8f208ef4ca24b2749f0 75938f2b8eefe1d332126f3e8f4966ebb121ed2e 54ec3c0fbe70063531bbc937a68844555ff4ef1d 1fbee03f0325f3c2d8628c24ebfc7ff78768bd28 a25fab65d6b19f9d17045f3735bfa99cb234e1e3 39c17afac3e2ca0ba702cb86936a7b47b10ce5b4 554879f6050a000a8d87d90cf8fd526d7a1435ae e99115fcdeda09807ee9377eee75f8311cf9c2a6 8e0cb12054ea0a9240ab6a7c739012517a70725b 10d9cbd9534528b9b35c5c7cc0b5e8335df9c184 88ac92027072bded7405880613edb8ff13884bd8 62b9008491a8cfe85061ba1f539c0154f1531f31 866f74bb022068a7e875b5032e28e645e00ec5c2 088195bbf268a3b0e3253ec666fad3e9192b44e6 2ae14d87d0ef48e9281c9fc31a0769a418ee42a2 3837c67ce8ad6b6a1ca134d5c0f6f8a1090b74aa be1e64b05a25d3f4e24389d0fab204f03d45b66f 26901efd1c2347cbc36f425643cb11e9a6eb2778 d9c22cc77a24b89ed6214ba9bbebfda180e4053c f881dfea41c6becd1d0034b29be94aff165ea5c7 6bf3f11c4eb153549b75838cd540e5d7f432b87b c7d319b253dd35aad329941c2b311f7acd58c5f1 ebfb895f1abb45999c7aa14ea50762f9e0552afb 55085ca8b71dbd39dab872694484b7a9897e3692 f793533cee5464273c7c7605b5abade720f9b581 0db03b6176c8f67c148aad115cc0157c058d04f7 3133202c909c90216ce6af4781e9e18c9d5c9337 7e8b910e93eb4d5793aec1d3ce7976db29fc9ac7 5eccaa7bf8fe0305280586f28b32065c187e2529 e44228c4544b40ceb83b541b86c0a2317646c46c 866a59de515d5fd21d298b50b937de054bfff021 6337814eb833f3faf8455850338fcdc0792b031b a1e5112eb836619da55a92dba3752b32cdb07dd4 359df37a025b4b8987a3a42108a2ca969f3d08b1 82b72a77706d774fe8db99107f5212f79666c3f9 38ef11306037233ff88e40aa5ed5c33ab43b23df 8352e0d49d6902a9216399c287921abcd60ff8dd 912218121af81393f97c76d2bc7a7d098851f8f1 26d05e1010442e0e05022190e2e51c8f1fc83575 0bc86b1eac162425be8fb490fcab288eedb717b0 10014a3ee2485c4fdfe080fcee8b648e81b7ddeb 7ead105a1776b2ad89ecf9b2c0bd9dbeb800200a 25d9514b2cef47609090993ae1a83304a6d171d1 226bab858bb2dfb4fed22d24ef1d7b7b9e4f4714 d54db23b933ba43095371ceb9fd09d2a8f04adad d82b9d77782c6e6be6ad057229127e30761c5d74 7d0043ea6eb1dd5ac40565db911169696f119451 50fc40dcb3371086b0843c215290119140ff9848 f8e4a49b6c62fcf7db89c490782785f55c66f0fe 45ae7df644a0696a3a21fd0e6947097a555f5e69 70f6c1457158eaf8b7e87e5851cfe090290ff203 da9c720fdefc7690181d4fb9dd9f61504a59e18a cb4ed831f41ec5f5eef86b1f0d1c958d7e4472c3 7a6f7a9429a4bb302465458eb30c818dfa78e1e3 555e10d7d30242c9e77a485cd938c612e3ce040c 9f84e077abd617060b7e6f4d376083d20d0dcfda 88d20a857d6dc513e5332388ace03236618029fe 8460e520652899ce0dafda27c2f64945f28abcb3 afdbffb32f8daae2a09be4866bfd19f33345a992 96cb759b32222ece4c7a4645e457dd97031f56cf 6a898515802defd5436700ac20f7a9733b61fb4c fb99fd8a4ec520b309a7aaf6b988dea772153815 65b79d35761d31b0b6bf404c34a8ab57ff703205 d8ba6e829cc397773a4cfdcd8bac6423de3c9648 f1fa4cf11231cf9f20bbb699605b6da9dfdd1cab 4c53b608627e82df39ce0883f8d7222c33773da1 03b28a125c978852fe9c418f7ca3ad4d79cb47c0 021ea6bcc4864dd13387f5f00afc4d448a0dd377 840277a0829648dfb3cfc2f40bf16df2dfb88f0a 14935beae02f94a6e482b46208460056ee5ff22d 20115e5cfc4697b961a4aca31edcddc94d5407c6 4772537e3db298adf53cde26d46279f0410af998 b6ce48af3ecfd9414feefc483216b5fb1e9f1d23 4f49f151e2dd32ea480488c3afc1be112f469807 a2c9f94db9cdf0f38fedc61309e56a3c33d36a3a baa81d3f0db0301e5792d8412081dc20866d2780 2f7af0774ec86c12c3fd569e438178583834553d b14193b5a7bbf43075bdb1b61767cc8c44309203 f0523f8daee8460d7ab7c5c617e5164320ef0741 b38973ab4365b72cd92a3dc89658494e785fa3d8 c589604f8a4b6fa4094779623fe50ee28dbf33da b10ce4683d1917a1e5e6a57ea3b57ea3f5b18d7d bb81569fff7ea921e91fe4556a28e563fd8bf308 9e507d7037382739370dc52f628eb8bf3c1880a4 89490275e1c5db474a0090abb3f08e6d0e6c425f fb21bd7c874fa55ffa6563ec0972ab8f6ec0d2ba 66015241a6d5ee90567a800ad5741d7e73f49a88 04863645022dec6a8844a121b93c15191bcb5503 2a6cad87b143d747778bbaf1d60d75aa310fd888 3b6621a449ba336433041049833ab4cae817a351 969e915c85d765717c8858e5ab154fbfd3c9b044 ca5e0eb717bab9c9b9b6ef18c9db506b842d5175 588dd642fb254928c38b82447c6bb439614d59fc 71a40e4f438d31547ebd7adeffd006e346db1ee4 d8f5fdc5815b28b2046a8bce05ba5910a8d91370 d546a9418c4c7d262059734844aa316ca5296312 f35237913b7db635aad69966ae66330516a4f61e 87654a5a1c157c1db25c5eb45663a17055354fbf eeddeb17229cac0de51da20ad400702eac4aa8a8 51ce31e4ba52e43a8e9dcb82a25cc12c5452648c 72c0f9a06e122bbf5d87fcdce3f96b377ac40c3e 0a4e8d3cb3cf95cf37fc2ae4043f3025421648cb ae7c589024897d6accb598ee706bb6b8a2cf34f6 45b1cb66bce0d95ddcbbb9a54f5374a444d3dce4 6cd5eb8a99229dff024a4d0ed84fc96a70562f45 02ed1a553dede629abda067604fa854d5f1c4c69 7e3be8fbaa8c04a2b4634fd92b8cb6d9594b3bbc 6934c87c8888a7a93a5c03d206c6b7850f885d89 a9cfa2d05ba3149811ec17a85a9fa9b796f8832c 5fdde2f8d8fef45f8f2b8ac5ed6137effb9132bd 2cde398ff6cdb806fa76a4584cdb79f0f547a828 57ccdd8e694841a0c16ceb6c68fc1fe1cadf5bc3 d82623ded860de83908ebb835a95fa1758e8a8e2 201b6db059adb058a2b9dedd35ac80d198d25879 cc9e84b0a418d9bd8e24a4e12402c1c68d31dba8 1304a9f55ae346be0fc1b45c41d3c2469de47c13 1fd91d46a098d6d9ca8bf5938d289c711c285775 0403db50b68b50bb8c362ae595e366ffa6069093 85cbeb115959b768a82d47da93d062eecd8f5e93 1108c2b78b54d01052aec36969cf8fe4fe3c1280 a0187c74a47f1dd54da0fb9dace81686274f27c7 b0fab8e52be64cf8c82a74233cb3f3e71e393e68 09a1118bdb06b63f31a60446440bcb2d1dbd249e a6d8d1b43ea98e0a667113e37710652a0c8b380f 41d9643b7ead8faa587b3f9a9db2267c042b325a b0b460a91f93583118bb961e8132e2aa8ba45ce2 08106f3051f130ec381ef9cf00260cb65ed87074 9b9d90115d3d53243d23be3e25354c572b9900a2 b99f9334f9e2cb2e1959e3da2bcce6e0d356c99b 915d770c5cfd430639ea3cd3f4a3eee5c4ca4cf6 e206d84f38af9660c4462865ade82c6701ceff21 0f9622879c0844a7fb929c1910ddc4510212eca3 d9438dacfdf4d6bba38899993c7e9a6397056fb3 11166bd308a2d02ed0882e177641ec5f64889567 80e8e9872c20e089813897a14a18cbb4e967518c ec6beb37ee68ea17696f667ee4a1ee32444b50b3 09d8b66d08dceee6ea93be04140ea0365088671c 81bd34ef857264f1ff864626766afb8aa0d977d8 442b8ca8a85e0e8352571ac5d4a5ee7f5e7e6b92 04c077508cc55d52e1fc3f6c83d901c9016f1610 8778221240b42c6d92c1229ec9f251f3be4733b1 565f9858ca241e46ce77e6053af774c5a517e5e7 b106e2ecdec82045338a9ef206f84efdd08fd09c fb3e6b1123f1768e26c578894e11b90cb0c00bfc 7cca1ad2711f0bad9665a2af15ac5aad5cca6f04 0f5d91af848c3b38108a97ddfae61925d718b471 677583e6e3c631a89e746fa3079e7683cd8026ab 25d9336409e99bc71e760b7c08176be4fc00abb6 c7affcde25cf13cde05f83bcbfa5ca504555a97e 9be40026af495ef4290c0057be67dfbb571f8eb8 14b6386a4f8926324e83d13fcea47b33c5562737 cf45aeeba416c3b06373f88f8958564d1186344e c04924e27d3b460e4b8e2c77f2f2cdc0d6d266af c9028e1d96336577a4a964ec870e97d5efa19bbf 9c738419f52cc9faffe075f2d5c1794aad18300a f5a8fcc061ccdbad06b8bed2fb350237f280b664 19881b69f792b779e939287929c96814408dbafe 992c5809698738aee033ccc826183fbd7ab50d99 8d988c761774b1671d1136f23676db5799e560d8 1a327624f8b3a3b79f987781a26e224573423e01 4c4b99d0c627429055afec926e75e5c7fa88c1ee c16d2c9a8291a21a988e00c63f314df793323fd3 a577970005af88e6822aa7ef229e5d07c6c13fc9 fd2f69c03c8127d0509c575c95cb7f5a68e7bf53 7c1c214458f642917f640b75989a7b73ea38af25 87bd9d67b311212f94aa6ff469d58f49f27f3657 e542560d38f9576a8f51c9e64a203ead3a142275 9ca8f4d39caa6914766a38f46a35a950830216c6 c8844d7dd08284511b91b3d4a39f590317389365 c2b45f82cc6adf44142035c93912493b49ffdaa3 79cb926b5e6ffa3b2189331a64e8ee0d003b08df 15ee18cff5cefc6fd7cacbd1685b519c7bd2920e 4d78d97c21b72fb69f836cf5f977446d313cae95 8b6805902c047e0a78e82a101974c484eee351a3 2b56c710153e8ab842f23a7df0e19e7332de8a38 66a2036c78a73d8eb57e92277e7c3875fad86d41 e489c7fb42d95647bb5e0d8616ef5c18ef904462 95cc7774faec0e73b158f1f4fe782fb037d64552 9b383c76caf715c3b88ee4f79a0995cebdf56752 6e860ad52255851d67b501e5d777e760d37f4c1a 17a2935104e280bea9121e5a181540cff42ce549 d9ac752cd534fe90622bda038078ce3f77c9c72e 6b4bf291d1e3e99deb5175417769bfde0eb6893d a865b248c14c76f00ad1ddfc96af85c7627538b5 8e98efdccb047c3cdff30e9dc75aaca59cfbf1e1 fe87596c818eaafa859e426bdf9e34cc0d3d0da5 3d600fbc01499d10a61d61e8088634e9a6663e2c 616cd569fe2d3f79d1b5d27386155b071047004b 305bca956bcc1c9ab2de583943c87963f1657792 afda062e381ddfbebeca4970b25d96c5feaf9e71 f588265d2fd6fc8877a08b9730d72f9236fd5f2e da4e0f7524eb4177b579da01c76de90a5edbc2da 435b04a1c0406d5da51d834ca2bc7946b9c2d7bf 6ed5887149b5528549ed43de7bcc6d9b6e29fdb4 f0f5c7f9993d06420c2e8fd0394f8b4991eb19b6 8d09473b2536adcaff973e6c1704f3b0a8a9de4b 551f3d4ddf2914ab314a836854149b47e14e8d66 5b809383cdd346def8b96387c2973c72a46599d0 050ea0503b44f591e58060e3936aa9494358cf9b 2751c9878fdb5ca6440ee2ecdd221b8249380b26 db8ce45a55ff99a759b2de0206116fe4bf7ab968 6e423d5acf3c39f1b392f35982158255d140895b a07e12d6fdfb81ed737045d33a87daa52560f944 9151d6ca1641b1e3d123abf9086e324771eef41b 33aa9270f45ddc936ae00e5707b88b15d76b4110 ee0104d5f1432655f9406129bdf6d0ec941a7134 831dacddb87c192fe9847f7aeb2a619ef4cd8ae7 468c775b5fbfc1fe91db81b382c1613990f753a5 7b014bcd92bbe0c4a4de9e88254e7043155a4394 2920e92d4a729f6be7c72de0eff39a70c67f3e46 0ea562acdc6c397244a0173afd3de0c7e1c71650 85d66b4ba0da53f597b503233c323648b475fb4e c8a0a54c39f16afb9ab74eb1bea6840cc8affd13 902b841e3bbff7e6e48566388b3d1ea7d8ad3cea c6f5f6f0e02702a3d4fc8fb13052eb4a227a04bf ed75e83b1b2fc44b2778bee62002f7cd927a19dc a3f58fa3920e789cc848d2040db4c4722ca4304f a2267f4e11fd2acbd83b3e58c03963ff896cc4d2 dd2d5ff8c81013f7bb3b44fdf10538f183805468 a465f10ee6efbb8e67ac84d8683e6a30280b1d79 305c0878e9892c427762fe0f0e2b3862b9834711 38010758f70d2bc3c1f1c471c218e01c8d7846f3 eb178ea1b589c6ee694e12cd06babe1361acab76 a837a726488bfb72beb5053fbe392ca82cf0c91c 31c01275f303c70125acf6ab9c70c71e15044bf6 f078468f8c8d866eb075b18beb95792dcb689e8a 15c2cc4fdb14fad6577fa93ed9cf2f95ae8fc3c3 f2db62bc58ffedf3f6b4bf6aadc5d1e24539608d e067bc331d9ecff090f3105b340f37a92563f536 be18fd07e69d312a20163c21dc157445e9a63219 42ece407f2fe1f1510aa1113cf92f4c3c98bd2d4 58337d79d6b68d4a034305e616ed73b567ef7bfd d986c34ffffb864475231fc3eca1acefc47afbfd 1bf70ddcabbae37588951a35b8472bef220fccc1 2c759f11ddd2eccfb3164fc265b8a8fd46b522f7 96de3ca253dba6530a6a5fef8a7f8e6d1c561882 eb173e9673db1ec58e2b0b471ce52c1740ed832e 0aff51c552bc8540d24d15d6b5e8e05511e72a81 3c3ac50e0334524bb3023391240d074cfd98675f cd658614f9d6aea63283101318717e198aaab378 aaeeadd56119b06a14ed4b9b637c0e2679061a4a 5ebb00bdf3760b35c3cdbfb0ab385e5e365b801b f1349dbc8276aaa2b4b7dca3320e1afd84245a5e caaf92d505a6b2e6ab0d1522900affbe8fd26d2e 31ad8b3fcd2e8b69a8a580c399d48bb48c187b4f 5926f8805bd3e36ea4e7af901922f38428c88fe8 a90d4f5855633d14f9f5d5c08a801846137863f0 5330ffb2ada63d2e820165554fc4ddd50dad0ce3 102788710723a6040d301218911fb0e750d5343f 49554a101c785d25b6b24c92ddd488920f535e97 616e46ca84e039935b1abe89840d114d1391ae10 388435f439d10b49f33db787895c267e9e462412 93d4fddb80c3dd727e77c27591eccee1dda59859 22285ca15a1061b7e213bac211e966a959c618ca b4a652aba0d8f575d10b772cc13a34f3e0a756a4 d22e31529d79499df69936873d13d5d0b731699e d0719980b5d7b69444115feeceab6e0de1b6eac4 cd8a126b285c07969af7939aa402494ae3865f82 009406c12b114270ee1417a02046cbc07575251e 97ee3980388212c922d8843629bd16f91b772f4c f5ee6970ff95d348669504c8a85d33bc85974798 3ca470400fcfb8d5ce361bc4fb1a27e84f1e8186 ec1e0f95c44d76d8c4714c06dda526e35d4ca382 fc408c90ca46715e2e5705aa55084da2df790d05 218bed8ed75fb1d28000c25d091372023fc38509 b16bf44d34b3829cfb06137e5bdd8c2daab81ffd eaba6ad52ac2faeca6ee1efa368f3a1a554078ac db3e04f9d3a23dc22c8df6a15b584e8c4e2759e5 485a35f13bfacb4c0e3a19c9f79eb5e66d582224 70b5a9e4c11f8bbe41dfa1a24d023875bf1536c6 925fdea3d316ec9ff1f09b3fb2a9960ebf09f60c 5f413ebe45deddab6b26a41178562bed19b9c81c 3122630aed5e8f06bcffee5ad7d1507803a8e16a 4429f0af3371df1c81ccfaec53baa0e3e873b0ed 153479ad3faa26586d865a94e33b833fbf0e26eb 8b948ec67281ca914bcc3467093b81bf3631318f 5f189e1f2fc11a08def8b0246e0c3cc6602ec262 f8587a023811c6f18c1977bb2b9c0afe3be37d06 174493b3a382db98363b94aea75f1a26591688da d40193d81edcb4f1651358c7ad1eec3157d054b8 a526dce5108f997253c45f5fecbbccf59f9c33f2 d4df772105fd32d4792d4d6029fba080e9176af9 ed871b3f42c46159fe649ef5fdc06ce0dfc096e2 10c475b4c9361aa17d0648596133f226abb91fca 012ebe07956d91db6613f19943424b7cd53efe9c 81ba3ecee9a3da184def0c155dabfa8e35943da9 e1f302aa89d0008fdab70a42dcc351ad62d1a174 6da944d2e2ca0d539f732d005ecfa692ade68a14 b0227e74eaff12eefbc26c141aee3f8f5b577010 1f252257a4b7381a79064a1f456bf2d9983157b9 4be05b9c21f321bffae2ab4857920602ad1b03b1 c67516411f0b23ece252909de4d103fbc786c056 da97ee1b95991bd39cd0677a8f7de40d9bfab110 f0526e1755aca5256d52e9b66cd10587a0bae0aa 7b4b21f5da09e8784c8460598eaaa4a12c22eba6 57e0383eedf9317016927ee10435dd6df7b0bc8d f4b90fa97634b8ddae3fa4e686bc3050c8589cd1 f257822108316437a4ea4de206eb299901468ac3 68fd6debe77cb571979f7ebd7965d85ad09e76ac d1ac5ecc064cbd076f896cf0d5e7aedecc4bc816 225ad3bcdc07ce04a2a6ae303452c101be000756 2d1e7fc416ee9175b22bb56970289ba43f14e173 81bbc72efc93575c808629e57667a703e421b495 a7ab48cb4f89a081ca4636518fc17a7b26fa61da 7e2299845b863f020283a111b095e09ea5f9deaf ab3b197facdafabcdb39be99634a302a5588bdcd ed7b9bb519bb553b5e53792794fb43de867d4c3d 7e5d9f1f1cb22ca69bf63fb638e39a3865a6e89d 1c9ad5ef55795f6692607eeb7054b95e1a6f75f6 59486ffcea181cd2951573a806b19f87e87dc451 834c3a213ba72f55feb6322df625ee916b835b2b fad25f40da0492bcbe017b4dbf4fa183d135955f 3cbfe0a457db28e3149f371d4462b74f9de56dc0 ab7aac3d70e4aa8361a4ec68a098def0f36afefe e84ebbbace3b24b92731ed13626254d6cac650b8 549e64c4fdb1f189d7ae291f82a47082317fcb6c df2e1f8893035fc5d7956be1c50ff2d33a2fed0e 7a72ffa7773806111c1e2df12a7f114d93f91683 38acf28f3905871d327f3f15cd6fac73f9ddae7c eb0bba1bf00b84a3a55e0cd7afb047b6cac7b7a0 eea2502c267671da623313be1f2c888237245fd3 1b10ae618c0077dee63e954cb36226fc4636a278 f16e9b5084b334ab78943d8557852c75add004bf a2843e387f3c3d6e17a8ef2fcf3a25df1006d761 e19cc39c2c9c40287d4ddcdcbd8f2791b2b9683b 9bae046c788aa4c64784123ac95cded0cac8a924 bc64a54e638c623302acdd9109cdf1f262b1f218 5470a554fd500fe76e00f88a21188a3cf7111bdd b0cb3871ed21b5fe6787c18e1be7f2f63087586c 94f57d09d8b42464e5d7c8b4d857cba4cf25f3bc 3cb8a9021f0094ae650a057be97d7882011dc6df 7cc6f0d1580643a17950e7be8950ae65d7416735 fa10c2270a5ee8234ac777089eec494bff1bc1f5 234a44b7759772966a39441cb9ea27f4be87a521 7167c5d4816807dc7fa0bdab6b3b09d7179766af 55904108569e9d6bbf6d07a537e22739da289820 b6819f7ff82554a8984e8cde8950a75fa56a3a76 a0567e16015704d131ee54c5772b23516c353ae5 7d2ec0a6382dd52784a64d0038be34769ea09ea8 c24f81c34fbfb5137f15b30c70bad930761fc2c0 2cace274d6e106504e88a3829375ae319f86caed da5206f7641a81c0f541c44812038cf5e3f69674 eaaa0e62a502288ee62ba473b872ae8e15a83e0d ac14021ee88f44c25767f28ab61c71a42478a1fb 2ec437f9c646a95289119542fa074b864e6a7553 9a24e503ba0e943366690287170ef8e92ea9fbd2 bd2806b14191fe43df3867b3bcf23608e9d05650 e831ee7d94d7d74cd9cf4faea1e335e137cf04ea 569db197ac300bf30f9e26dcfee1eb7eba84a6f0 3d534c418f0588e52629117514efe2e26a017b63 da3720c4e78b0f62cc25aff56066a347ff138efc f859fdb3285d4e8dd407a5158c34617cc858d57a 0fd6800d8b801876baa0df6a6090e92a67da385f 261904cf16a9764e9c9cc8bab9cd0f6d2e3077f5 7c54384dae7b8e3cb4f2f26587a100262e0212cd e7ef1ec7c10dc7d7b8d782c778145fb4ac60369e 9923cf4e61539e6b983bf5aa2f16443fd6a03e03 d800d4fd9e0680abdd2aef653c968114a07d4637 74d9f4d277a60a8141b5303a2682c416b2e8ba40 608313090a14e80201b39289da730bb6a9abb1b5 7818f738a9d3d8bf558b21e9ce6a9cbce04cdfe5 7498ef7c1059a3b78d7ff55be0387faee752b355 8dc87cd18aa0080f3f044b69825fc0252b4d96ee d8fac9270e7f52bae9d5b91403bcc029d753887f e4892f2b089c5be1c74892a4161d4668a20d138a 89a8db1b20db4558d1f573a46c6dac2d7f72374d 7c0ff3a58166f0e9e5792dd8f6079d4fa4f268c3 ac5cc1390a85a449cd5bb98746839400afd73248 c84d66593e5b43659b048ae0a44a4df6397eed8f 5c944112441da2aac6c66afa63d6b4b4dae8f704 d3f5c7e00844152fd101b0680685ae8c629e506b 00eaebd5685c6222941cbce5017e2d5d61b51e52 3eb7f8b1718f5d3daafd619a4981e98389f37b67 c1f4c40aa9ceb9db554c197faa1fa2489a87202d e82905db45954059c46d15f76b458e636fdf8a9d 71ce5116d8afae148d8ee2ac70aecf08ebc0e5a4 9d1397a58184554d30deebfb3eee00ad7e7ada5b fa8457bbca0e6fbe45257cc52a001eb081cc6313 12ebe174d7f35f3cec64cf3a7144c119fc84d477 19c8ba202118a4e76aff58d6ab98d5ba1671099e 212a0901df1458915af89795bce064f60e27ce9b 7fa0b930a029e233e277958768ce37300eb0898b e324f94946c499bf9f44a3c685018f979d2992f0 3c7068933f048742c0c50972acb085e7337f367e ca114ac5a7870bf6d63356c9bc2b3258940ef95a ae44b7222a44514df5f044bdb050906fcdce52df 9d48041ad00fcb9042d41b0015ff6ebef799b9db 3a59ccd994dd89482e4de41d2a37e7d8c832e826 de3d4e5d1a567af8d8592d466105a2d310076744 806c2725b4a25e4edcbd303c32c371f0d4056f9d 2eb5be23848352f9c1cd4d53cd1aaa4203b19b05 a3d3678819e9fa30d7cbc7dfe1b99eb2dd05b8b5 cd0ce0df335f122fd214f67fa40daae08f844a4f 4d391a370a47b102c5627570c2b43e9be8cf1e32 4764a50f10e0c6c0fbd18ee40f25551ef38c6714 1f903072abf764ad4464007ce44533b4b1d2fb84 5bee71d55be79a5c2093f7322fa07e56c03a71ff aa3f5a654cc61e858f733f7fd7328329209b1e86 606c6266c79fea1a753dabedf808c77099a3eae1 d14cdb83f26c417f1a63f11e68f64651060d0cae 69af68a16d7084c4da0fc94b55b0de6782557ccb 1761ed775a49784e62f1da1860e60c6f384a8fae 8562e30d0b48b369aa680427151523e1f888404d f6b4bc80c0bcb3735738aa19cfceb4f0ad4a2701 33ee8972ff3709c22d5afbc23570a963a1b5fd69 f2a0bdc6ffb04453c6bb030554c0680ca38fa88c c63d87d6e4e8e31652a3b265fef0d863ff91b0f7 71b31b5824fe92e27b643920679394557ef17bd0 21a6a2fa424e564dd06d4e496c3174b52470a94c e2a87286b9e9a96bd22c2c47c1d897dd28f36729 424aea01122a7cf1c16248c97e3aace23c555653 7d66cc863275178a19391e4afda7f8860dfa77af d985aa73279e5791142305077a373af96e584fc6 8dd269a30ef95a0c4d9641315109dab590bad85b 7af75829e34f00edf19c084f57dd3c90261b9a19 9489ed005d6a7087ccd907d1fc411e7f1af7d896 b2b21ae7db1cb25a160508af818aeced40937c76 2d1dbdbefbc81b759e354ad5ee36617a9afee5f8 542e411f58ffaf896428ebbb470c28fd4665f268 4dad1a8c4402d5ef62a43da7d1f26e72d339e2fd 9bc4725b15ebc958c91753ddda7d4901cf5c500c fc594794ce492bfd4a78ebb356cbd3dbe1cbd375 670e6173429eb32c271af3d8a2f95424524b1f39 b675c1952ba5101fc1ce29c2114f93d0a40510d8 29b2bae665edc69e6f4919b5cb28f4e24dcf844d 6a0e483ae2c19e2f5a4305a8da212bc3eed4f739 b5dc24f8d863646e59c202c8150bd730cb929b6e f5636bd7704af83e26ef8f846511259ad990d1e0 672e324b15d383c1950bd238badc5acab0bb0d81 eec463e488c0fe42fa7edff17a33b91806e733d8 1dd7278644dd4a6f1dcc497457d7d756dad443c2 69fd316e2c2072acafc351e32049c80af8d57d3e b6fe02ff004da7b1ee8d2e010fb3c43642fc2ab2 9dfa0e6f02be2f8b2613e1eeeac2fb48d1edb925 24b7014a4fb819f55d98342c70c49ff6c4c545f3 27f9790bb10e9a7aa21aafd301eeb3a26b047c23 7016db098192aef930dc839b1d0f798cd4d59c4d 4301e2546d03c89469ed2832856d2d0825952dec f17b8e8afed8736c461fda4e9bcf19cd81d3fc4e 7084a66d4f12b533bad193e755aad6b765f6237e 69a6fb599409c189386f46c8fd6a6379db21e7fe 38d7ab17c5a416bf509c164250d62f1a51bbbf44 c5721b43ab1fb63ebfef1daa742b871637c8ddd1 f505bcb22d4890a6c44eb8bb004c1e9cc822282b 055bbbbba62af77a7dc7b03d54ea843b98ae9562 8200abc709f7da1fd65c6d83feedb67c644727cc df5ee67aa60def265a49dbc8d2f2dd78e9cfa5e4 4a97d3019baed034c75d29a7785f958094b7b2a6 d6af4566e0fa68a8165d5f8d10ba725a026b4be4 2f370728d1f62d192d26c1a59f54c15fcca2170d 7b878e67318a26580308f0bef26bbba0eb6e3d87 a564a20f92eec02439d114c826009f62cbaab39a 1a1ab0ff1745d7077d476ae0a13ddd6d70e51292 35117f9d2fa48c1aa8b35242956ae0b3fff75cad 4910616f288dd57756e242eedd9945d1b6d9ba30 84aad3f292bd078b2661dcebef7c50d956a186e5 c219752b5d4ee6c3761e69abb514d3202b9826b3 e319861beaf601d595aa5165323b03235c3ad564 1cf1fe78dd816615a1c0e86a1079f6d30436f03f f8c9312db3840ee1f0c46e88d8600b0e23ea61d1 1c1f2b371ab5249f2a1761d770b37cd276219d12 7363ec6e3c709ec2e78cd4c5a860b06a39d89b53 c49235b09a67ae953a49a8fecd2e0002ddd5d7ef 65ee063c4606c439555dbff6dee649daaeffe828 8ae153555894a7e061ce097d0d3e74c909aea5a8 f62957dba4f14dc88d0241434fdee58debcef38e 6d56d76e59ab6013e5c142d4ef46e9cb41bb34f1 d394c84b6bdd1497b3e81cb22717f05b8f746f60 fa4b91eec358bb8856c8ba8e998434edc6ef9cd5 d0162aa3e37b5154c9b6e19ceac2cf87e7d07638 4be14c0d42a750b647e1c3777b730fcef4de5aac 87c4475591c6ed6131e867b0549216da158f798c ffb473bc8263b3aa709715d1708fa9c36b10cdb4 20272663c0c39fac73179e53958ab30dbe493db0 c2712bb002dea3750a9fd63921ce4a504c6aef41 11173337f75bd2f6156a282f37899c8e72fc3bfa 4d943001bbdd640d497eee80b60089a2ab434fe5 878e4c51410bcdf1e7f5388cf99192cdf8fbae11 cf2177ee054b3123d8360f5aa14e34a13bb53753 6c9a37dd6922f9de727b26fa77b21f1cf97fd587 1eab96c8af798d67ee70670382bf3fa5b664cb57 1dee38c4f1a8021132c360ac5a8975b908fed8da ab68a8416f5f5a60469eee35374a6c5436cc975c 7bca1f559c7b2e78a5a90d3dc09bca349150d27c 59b78101f4505d934da4ab8695c35db447e25a85 907f0908f8b5c4afb1595421e89bd314848de10d 016c9d1d0e8f06b2673464d76053b1dcaf7465ae aa9e6c84db869037f234a2633b0e130c244c7ce9 cc5bcc8166052544f449f21d3f64ffd9ef66a979 bf4439759fb550ce395f765b213509d4719d6df8 2b9889bc49d025196dda6f519313fc725f9adef0 143b6dcb9ff1766ca4949654e181296a42743e0a d8fc41786c69f876ac06fc9e19966b839fe15449 7dff8029f4b33925b8182aaf362595bdca6ff431 bd164503fe5718308a2a392e0455372b140482d7 d448dd6e8e99acec3bf9b2a5731b724ae2319d6a 5b5ac0e7f191d5fbce8a14c5b76983f525117b21 456fa5ce54dc421371cb9a53e7b37d7a33ee00e7 d9aae73241665393ef1e582cdfac63e10b36d450 c0457bab35c3e522018ab10b646a4c6704f72c4e 806db56bceaf9b09c2fb87966e383c92b700a86f 9943284714772c4f52ccb1775d5196912bd6e84f b0f43547abc7e8e768aba6a052f995932727d1a6 7194c5116e5ccd8313d09d69410ef7db6bea1f42 72a34b226c8d6383aa25842ce6e5f7f65d332a57 90eb12eae5ff3df57f89003c581fd1949653eb4d 6140fb27fe6ed667a2567eea6bea68e2f7c370b9 ab271f5488c148efc22a5d262aa72dec47863310 3fd92baa7cb04ef4586b8df445b00ae43fc9a9ae 6051e9ec9cb916fe326324e2243db49278475c8b aa15029facb4da187a33f73975706234db47c233 7346d028a6eb69f4cda77c8fbd9285da450ae43f d36882f78aae79d40ce9879e060168be8d835719 7c12da20aa375afb48b476f9d4b9f3168185e0cd 99fb8a5f1ab67fe5e5a0db63661761d91a39d906 397c2270f77a53bb8932ec2aac22e3de21119f53 4b81570af0f37c1ee2af4eeaa750f6f38eff1db2 6259dd366d3e55ebdb604b0ee25510b0c02243c5 d1192e577ef23a971d35ab9addfc98ffa4aa26fe 1c45f9e1574c2667cbb59c800e207388379af055 5a655d4cf373110a8d00085aebeb83457de4fed3 370ffc808e54fb3adb760e3d7e094d11b845e8ab 25cd7d1417f9271247cbff6f2b5c17a1c68f4ec8 3f78557c9f198cf7542980d64087f84f2a9b71aa 6022e65454ebd5b66f4add4c42a637fc4273cf47 1efc64e6854a1ec1f23254f0a2dfaad5316d5ae3 13a2942e32856acb03674cdd4df6f098274aa548 f61953603145a71ba95b79268c7dc00f32173db9 2f7ddf3d11124b6eee7dd3c19415d0a0f04566cd d3307f21c3ae4ff7c3dcfbc3362f2883dca267ef 66da304cf6abd2a342cd4f237835c1a802e5dad9 a7534e14dad315729ebb48348c29b820424b8daa ddaa0b9bbaeec6eae570637684b390bef6cad263 a2898d0722e0939dcb3b830843993c6f673ac2ea d268915b0b9f0e724c58e5edd9635cab91dc4215 28eda2262319e517736ca0faa2e07c26bf22d1ff 8fc721629792b7dd94d2fb09567b5e6dccddf232 612fcb3b19ea24803dc4c1e219dbe3e27bb5c87e 76ad27c7212545846ce6cfe1ddb715fbcbde0fc1 2df893ca83f33aae44c37859ac5be0c291ed3d69 e59335e8b9348700e578c2cf0199b5c85a7753b8 c0e4e9b29fd94c8279155cf3207ba0757e84d09a 0a801e8f639c42993eaa3ce47d5a11ee15e823f8 5292d5b9448f77746d929b2f75f0dc6166293f7a 0de064144674bb1084d09a577532f540c0f5cd91 accf208e9e905d65deed08d732a19fa230ad20dd b5fb0d2b25c13a6bdd44a5c301d1afa3ab28a96a 27ca0ea14876ffc93733e1b3f1c3dddff9820922 f2974f53005e1a875a9d140bf526a78362571c6c 22042cc46014c7c7c6dc94f3a20758287dfbbdaa ac94bc65473183999c44eb2d4625617decc0b4c7 71a2129c85a09d24f3d9c62b6c87a2f938209a0f 44fd9795c26f88d75b3bcc97d951af9490e18bd8 408cc7f8d7e76560beeddb877ac84d731fb63629 90572dc48eadd1430056ab9856644b486c82dd4e b493df56da90f92bc6e238d411ef0ff9871d3753 1d80db315313da8e02d850b1a07400cba258d2c2 7e45a8376423c38bb5000747b62b05e11c702821 6dacebd7afdebf4d594a7ee3b141f09fc55915b2 a3f683be9ecc9c00541dabdb6c5749517fd871c1 e5ca1e941312bbf1636193f6689f4190e333dee2 ccd7a639aee7297236fd11ae67c695e0793b370f a33bbab202fbb7d2fee575f21eeebef9b82db17e 9b910f04dd525eb4cb2f2ebfaf51b1c1b871dc86 84c0806ec7344ced606fe5d2e9818fe818dc45b7 388422d3c8b88515b1d8048d4185908267beaabe 9062908e1c7d6bef864c4817fb22a201f31a2046 ccb7c06c44327bc68066d3f5a30e47241f33137c 77f3111483f17ba4450473a79ff13db80fe39a98 3172bcb1ca3c42320ae15dd41d73046139b53ee6 6eb8285406e09b159d601b19504b4be265fde12d 7c4ea87c4ff3f1a55008b79857c11c435e2d75a0 1a71a77c4f89829d1008244b8ba1c8f6d5a6a64d 71d59313a588006db64a17098a06c4067cc37fe5 c876a8dc49cc42906fcc6e2291b5c562e1d65083 2780951c5d200404e98fa6c359f9da353214887a b5f89833bc4d414877350ce391fd2de3635ec68b 266a8beef77208a9ee419c7ab7f0502a719b9cad fe3a2eb678401edcbe082eb2af619a4db058adfe 48b7da590692b7db656fef85d1cbd1cd4a2cdaf8 e2921988fbe1e946c513435a6c0e0302cb489185 5e0ee8320bb8b0b79cea0dd2b906852fb8a05348 d51581372627854de292d5459dd5521265d54301 e17ab14f2e2af7bf850bf671d5f011d39a4e5c11 2503abd0b568b11ddfec79431b5c5207153c2596 58bac3bdcb4b5ecc3a59350a8638d819da5a5e69 40b947d6bf48b5c29d8b43132e006dc24f4e346f f0e1aed3503a7f29a21a68309c8bf042ea0dd488 2d5a22c336262e6ec10a1319e920d26df75a2953 151948e24c51580486f9e1aa534a2a22b31f89d0 b4c617f1285caf40082b628cb9ea4443a957b290 9d65c73e68042191ab20ac795a945e39445da579 113850abd1ef3f619e48b810bf4e90f2ffb7cf6d d2c024f09144a31bdfe0201ec2f3946263504d7a 9cf1a44e7e657396ec6251bc44602205343118c7 8514c3b8cbc71aa5b89396436e5db7bccf08a358 0d6cf68fff00442f53f1661d3db1f1ceb8b74859 d45d20b1a702a76e9ce9e14335ffc0842fdf5b7a 8a83e9e974554cdbe93c551006722cf04367db4d b0c8f69ada2fb3cc349559ffc2f8fe474fb5a12b 3ea9ef80d165bf63b4b4844bc6fec3779d478eef 87fc00fbb446d73e8da83c9e1bb01d23b43bab0c 74efe91d74b7dd39a6b379f6d4aa7da12a6b4b4e 2883b15faba98eea299e1bbe85e94235206ae515 f3cbaa1dbc214f2da12eaf7086942838cf4f2467 d615279cd0f7dfc560f9b4eac3bd88572d25750b 909e4c6cd2b730022125d88a492f7c38a9ba9146 4c28237894683abdab8f9f362eeda5707c6a29dd 38d907c0611a1c1e695a6c5a42632548ffa77f6f 4bfd2f9377e0964bc206bbab64815c45a35ffc1b 9a026b33724c4ecbbc5c7becd502b749e1097448 5708aae82110477199282602466edc1d77a722d1 bd8daaf9741a51409885f1a7fc5e451828703879 cf2b428f081f9815c20632a37189c041c9907567 cffaf9b7478cf1a9b27f13ccc1442982f80559a9 6bf3e41a6fdd13b36f1db8cab08b6da825c4c29e d7fbfbf4c8a96c3ecfcd0ada76eebd1b213ae016 ebaa2da6654be32c6412544ae28d1b15bd45fcd5 69d8f83d5d514376c1115b76878388c0063317d5 bd48332917ea90bce3be0350b70d99351ffb5c71 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642273755.0534909 ftputil-5.0.3/doc/hg_hashes_and_dates.txt0000664000175000017500000042024300000000000017453 0ustar00schwaschwa69d8f83d5d514376c1115b76878388c0063317d5 2021-11-13 18:21:14 +0100 d7fbfbf4c8a96c3ecfcd0ada76eebd1b213ae016 2021-11-13 18:13:22 +0100 cffaf9b7478cf1a9b27f13ccc1442982f80559a9 2021-11-13 18:13:16 +0100 bd8daaf9741a51409885f1a7fc5e451828703879 2021-11-13 18:12:25 +0100 9a026b33724c4ecbbc5c7becd502b749e1097448 2021-11-13 18:10:20 +0100 38d907c0611a1c1e695a6c5a42632548ffa77f6f 2021-11-13 18:09:42 +0100 909e4c6cd2b730022125d88a492f7c38a9ba9146 2021-11-13 18:08:35 +0100 f3cbaa1dbc214f2da12eaf7086942838cf4f2467 2021-11-13 16:37:53 +0100 3ea9ef80d165bf63b4b4844bc6fec3779d478eef 2021-11-13 16:17:59 +0100 74efe91d74b7dd39a6b379f6d4aa7da12a6b4b4e 2021-11-13 16:37:14 +0100 8a83e9e974554cdbe93c551006722cf04367db4d 2021-08-07 00:23:33 +0200 0d6cf68fff00442f53f1661d3db1f1ceb8b74859 2021-07-17 21:58:10 +0200 9cf1a44e7e657396ec6251bc44602205343118c7 2021-07-11 23:54:35 +0200 113850abd1ef3f619e48b810bf4e90f2ffb7cf6d 2021-07-10 23:28:01 +0200 b4c617f1285caf40082b628cb9ea4443a957b290 2021-07-04 22:31:44 +0200 2d5a22c336262e6ec10a1319e920d26df75a2953 2021-07-04 22:25:04 +0200 40b947d6bf48b5c29d8b43132e006dc24f4e346f 2021-07-04 22:21:15 +0200 2503abd0b568b11ddfec79431b5c5207153c2596 2021-03-18 17:13:51 +0100 d51581372627854de292d5459dd5521265d54301 2021-03-18 16:55:25 +0100 e2921988fbe1e946c513435a6c0e0302cb489185 2021-03-18 16:54:26 +0100 fe3a2eb678401edcbe082eb2af619a4db058adfe 2021-03-18 16:52:44 +0100 b5f89833bc4d414877350ce391fd2de3635ec68b 2021-03-17 22:58:50 +0100 c876a8dc49cc42906fcc6e2291b5c562e1d65083 2021-03-17 21:42:56 +0100 1a71a77c4f89829d1008244b8ba1c8f6d5a6a64d 2021-02-17 20:32:30 +0100 6eb8285406e09b159d601b19504b4be265fde12d 2021-02-17 20:21:37 +0100 77f3111483f17ba4450473a79ff13db80fe39a98 2021-02-17 20:14:36 +0100 9062908e1c7d6bef864c4817fb22a201f31a2046 2021-02-17 19:56:06 +0100 84c0806ec7344ced606fe5d2e9818fe818dc45b7 2021-02-17 19:43:17 +0100 a33bbab202fbb7d2fee575f21eeebef9b82db17e 2021-02-17 19:42:27 +0100 e5ca1e941312bbf1636193f6689f4190e333dee2 2021-02-17 19:41:03 +0100 6dacebd7afdebf4d594a7ee3b141f09fc55915b2 2021-02-07 22:13:42 +0100 1d80db315313da8e02d850b1a07400cba258d2c2 2021-02-01 19:37:48 +0100 90572dc48eadd1430056ab9856644b486c82dd4e 2021-02-01 19:18:35 +0100 44fd9795c26f88d75b3bcc97d951af9490e18bd8 2021-02-01 19:18:28 +0100 ac94bc65473183999c44eb2d4625617decc0b4c7 2021-02-01 19:16:52 +0100 f2974f53005e1a875a9d140bf526a78362571c6c 2021-02-01 19:01:53 +0100 b5fb0d2b25c13a6bdd44a5c301d1afa3ab28a96a 2021-02-01 19:00:27 +0100 0de064144674bb1084d09a577532f540c0f5cd91 2021-02-01 18:58:37 +0100 0a801e8f639c42993eaa3ce47d5a11ee15e823f8 2021-02-01 17:57:29 +0100 e59335e8b9348700e578c2cf0199b5c85a7753b8 2021-02-01 17:47:45 +0100 76ad27c7212545846ce6cfe1ddb715fbcbde0fc1 2021-02-01 17:10:12 +0100 8fc721629792b7dd94d2fb09567b5e6dccddf232 2021-02-01 15:24:20 +0100 d268915b0b9f0e724c58e5edd9635cab91dc4215 2021-02-01 15:19:14 +0100 ddaa0b9bbaeec6eae570637684b390bef6cad263 2021-01-28 18:05:44 +0100 66da304cf6abd2a342cd4f237835c1a802e5dad9 2021-01-28 13:10:09 +0100 2f7ddf3d11124b6eee7dd3c19415d0a0f04566cd 2021-01-28 12:58:45 +0100 13a2942e32856acb03674cdd4df6f098274aa548 2021-01-28 12:56:42 +0100 6022e65454ebd5b66f4add4c42a637fc4273cf47 2021-01-28 12:56:25 +0100 25cd7d1417f9271247cbff6f2b5c17a1c68f4ec8 2021-01-28 12:28:41 +0100 5a655d4cf373110a8d00085aebeb83457de4fed3 2021-01-28 12:25:13 +0100 d1192e577ef23a971d35ab9addfc98ffa4aa26fe 2021-01-28 12:14:14 +0100 4b81570af0f37c1ee2af4eeaa750f6f38eff1db2 2021-01-28 11:52:17 +0100 99fb8a5f1ab67fe5e5a0db63661761d91a39d906 2021-01-27 19:22:14 +0100 d36882f78aae79d40ce9879e060168be8d835719 2021-01-27 17:50:21 +0100 aa15029facb4da187a33f73975706234db47c233 2021-01-27 17:49:58 +0100 3fd92baa7cb04ef4586b8df445b00ae43fc9a9ae 2021-01-27 17:49:35 +0100 6140fb27fe6ed667a2567eea6bea68e2f7c370b9 2021-01-27 16:29:33 +0100 72a34b226c8d6383aa25842ce6e5f7f65d332a57 2021-01-27 15:47:37 +0100 b0f43547abc7e8e768aba6a052f995932727d1a6 2021-01-06 22:33:46 +0100 806db56bceaf9b09c2fb87966e383c92b700a86f 2021-01-06 20:36:38 +0100 d9aae73241665393ef1e582cdfac63e10b36d450 2021-01-06 18:33:08 +0100 5b5ac0e7f191d5fbce8a14c5b76983f525117b21 2021-01-06 17:54:12 +0100 bd164503fe5718308a2a392e0455372b140482d7 2021-01-06 17:18:22 +0100 d8fc41786c69f876ac06fc9e19966b839fe15449 2021-01-06 17:16:47 +0100 2b9889bc49d025196dda6f519313fc725f9adef0 2021-01-06 17:11:49 +0100 cc5bcc8166052544f449f21d3f64ffd9ef66a979 2021-01-05 19:33:40 +0100 016c9d1d0e8f06b2673464d76053b1dcaf7465ae 2020-12-01 18:06:44 +0100 59b78101f4505d934da4ab8695c35db447e25a85 2020-12-01 13:59:20 +0100 ab68a8416f5f5a60469eee35374a6c5436cc975c 2020-11-30 21:23:55 +0100 1eab96c8af798d67ee70670382bf3fa5b664cb57 2020-11-30 21:15:05 +0100 cf2177ee054b3123d8360f5aa14e34a13bb53753 2020-11-30 20:58:39 +0100 4d943001bbdd640d497eee80b60089a2ab434fe5 2020-11-30 20:42:39 +0100 c2712bb002dea3750a9fd63921ce4a504c6aef41 2020-11-30 19:49:57 +0100 ffb473bc8263b3aa709715d1708fa9c36b10cdb4 2020-11-30 19:45:35 +0100 4be14c0d42a750b647e1c3777b730fcef4de5aac 2020-11-30 19:42:45 +0100 fa4b91eec358bb8856c8ba8e998434edc6ef9cd5 2020-11-30 19:18:08 +0100 6d56d76e59ab6013e5c142d4ef46e9cb41bb34f1 2020-11-30 19:07:10 +0100 8ae153555894a7e061ce097d0d3e74c909aea5a8 2020-11-30 18:58:18 +0100 c49235b09a67ae953a49a8fecd2e0002ddd5d7ef 2020-11-30 18:50:12 +0100 1c1f2b371ab5249f2a1761d770b37cd276219d12 2020-11-30 18:49:27 +0100 1cf1fe78dd816615a1c0e86a1079f6d30436f03f 2020-11-30 18:23:53 +0100 c219752b5d4ee6c3761e69abb514d3202b9826b3 2020-11-29 00:03:40 +0100 4910616f288dd57756e242eedd9945d1b6d9ba30 2020-11-28 09:19:28 +0100 1a1ab0ff1745d7077d476ae0a13ddd6d70e51292 2020-11-27 22:56:17 +0100 7b878e67318a26580308f0bef26bbba0eb6e3d87 2020-11-27 22:45:38 +0100 d6af4566e0fa68a8165d5f8d10ba725a026b4be4 2020-11-27 22:30:49 +0100 df5ee67aa60def265a49dbc8d2f2dd78e9cfa5e4 2020-11-27 22:27:32 +0100 055bbbbba62af77a7dc7b03d54ea843b98ae9562 2020-11-27 22:19:24 +0100 c5721b43ab1fb63ebfef1daa742b871637c8ddd1 2020-11-27 22:18:27 +0100 69a6fb599409c189386f46c8fd6a6379db21e7fe 2020-11-27 22:05:32 +0100 f17b8e8afed8736c461fda4e9bcf19cd81d3fc4e 2020-11-27 22:03:13 +0100 7016db098192aef930dc839b1d0f798cd4d59c4d 2020-11-27 21:59:34 +0100 24b7014a4fb819f55d98342c70c49ff6c4c545f3 2020-11-27 21:58:38 +0100 b6fe02ff004da7b1ee8d2e010fb3c43642fc2ab2 2020-11-27 21:48:53 +0100 1dd7278644dd4a6f1dcc497457d7d756dad443c2 2020-11-27 21:35:10 +0100 672e324b15d383c1950bd238badc5acab0bb0d81 2020-11-27 20:15:02 +0100 b5dc24f8d863646e59c202c8150bd730cb929b6e 2020-11-27 20:13:28 +0100 29b2bae665edc69e6f4919b5cb28f4e24dcf844d 2020-11-27 20:12:28 +0100 670e6173429eb32c271af3d8a2f95424524b1f39 2020-11-27 16:35:33 +0100 9bc4725b15ebc958c91753ddda7d4901cf5c500c 2020-11-27 16:20:17 +0100 542e411f58ffaf896428ebbb470c28fd4665f268 2020-10-24 19:10:25 +0200 b2b21ae7db1cb25a160508af818aeced40937c76 2020-10-24 19:07:09 +0200 7af75829e34f00edf19c084f57dd3c90261b9a19 2020-10-24 19:05:37 +0200 d985aa73279e5791142305077a373af96e584fc6 2020-10-24 19:04:17 +0200 424aea01122a7cf1c16248c97e3aace23c555653 2020-10-24 19:01:34 +0200 21a6a2fa424e564dd06d4e496c3174b52470a94c 2020-06-13 19:33:27 +0200 c63d87d6e4e8e31652a3b265fef0d863ff91b0f7 2020-06-13 19:20:46 +0200 33ee8972ff3709c22d5afbc23570a963a1b5fd69 2020-06-13 19:16:17 +0200 8562e30d0b48b369aa680427151523e1f888404d 2020-06-13 19:13:35 +0200 69af68a16d7084c4da0fc94b55b0de6782557ccb 2020-06-13 19:03:50 +0200 606c6266c79fea1a753dabedf808c77099a3eae1 2020-06-13 19:00:38 +0200 5bee71d55be79a5c2093f7322fa07e56c03a71ff 2020-06-13 18:57:18 +0200 4764a50f10e0c6c0fbd18ee40f25551ef38c6714 2020-06-13 18:48:45 +0200 cd0ce0df335f122fd214f67fa40daae08f844a4f 2020-06-13 18:23:35 +0200 2eb5be23848352f9c1cd4d53cd1aaa4203b19b05 2020-06-13 18:21:49 +0200 de3d4e5d1a567af8d8592d466105a2d310076744 2020-06-13 18:11:11 +0200 9d48041ad00fcb9042d41b0015ff6ebef799b9db 2020-06-13 18:10:41 +0200 ca114ac5a7870bf6d63356c9bc2b3258940ef95a 2020-06-13 18:07:44 +0200 e324f94946c499bf9f44a3c685018f979d2992f0 2020-06-13 17:57:46 +0200 212a0901df1458915af89795bce064f60e27ce9b 2020-06-13 17:43:19 +0200 12ebe174d7f35f3cec64cf3a7144c119fc84d477 2020-06-13 17:39:55 +0200 9d1397a58184554d30deebfb3eee00ad7e7ada5b 2020-06-13 17:38:53 +0200 e82905db45954059c46d15f76b458e636fdf8a9d 2020-06-13 17:32:29 +0200 3eb7f8b1718f5d3daafd619a4981e98389f37b67 2020-06-13 17:32:00 +0200 d3f5c7e00844152fd101b0680685ae8c629e506b 2020-06-01 19:28:51 +0200 c84d66593e5b43659b048ae0a44a4df6397eed8f 2020-06-01 19:25:22 +0200 7c0ff3a58166f0e9e5792dd8f6079d4fa4f268c3 2020-06-01 19:24:30 +0200 e4892f2b089c5be1c74892a4161d4668a20d138a 2020-06-01 19:23:05 +0200 8dc87cd18aa0080f3f044b69825fc0252b4d96ee 2020-06-01 19:12:52 +0200 7818f738a9d3d8bf558b21e9ce6a9cbce04cdfe5 2020-06-01 19:12:31 +0200 74d9f4d277a60a8141b5303a2682c416b2e8ba40 2020-06-01 18:58:26 +0200 9923cf4e61539e6b983bf5aa2f16443fd6a03e03 2020-06-01 18:57:58 +0200 7c54384dae7b8e3cb4f2f26587a100262e0212cd 2020-06-01 18:51:26 +0200 0fd6800d8b801876baa0df6a6090e92a67da385f 2020-06-01 18:42:51 +0200 da3720c4e78b0f62cc25aff56066a347ff138efc 2020-06-01 18:35:28 +0200 569db197ac300bf30f9e26dcfee1eb7eba84a6f0 2020-06-01 17:37:06 +0200 bd2806b14191fe43df3867b3bcf23608e9d05650 2020-06-01 17:33:40 +0200 2ec437f9c646a95289119542fa074b864e6a7553 2020-06-01 17:33:09 +0200 eaaa0e62a502288ee62ba473b872ae8e15a83e0d 2020-05-31 23:37:14 +0200 2cace274d6e106504e88a3829375ae319f86caed 2020-05-31 23:12:05 +0200 7d2ec0a6382dd52784a64d0038be34769ea09ea8 2020-05-31 20:06:59 +0200 b6819f7ff82554a8984e8cde8950a75fa56a3a76 2020-05-31 19:38:16 +0200 7167c5d4816807dc7fa0bdab6b3b09d7179766af 2020-05-31 19:32:40 +0200 fa10c2270a5ee8234ac777089eec494bff1bc1f5 2020-05-31 19:30:56 +0200 3cb8a9021f0094ae650a057be97d7882011dc6df 2020-05-31 19:09:12 +0200 b0cb3871ed21b5fe6787c18e1be7f2f63087586c 2020-05-31 19:08:08 +0200 bc64a54e638c623302acdd9109cdf1f262b1f218 2020-05-31 19:06:42 +0200 e19cc39c2c9c40287d4ddcdcbd8f2791b2b9683b 2020-05-25 13:56:16 +0200 f16e9b5084b334ab78943d8557852c75add004bf 2020-05-24 23:37:44 +0200 eea2502c267671da623313be1f2c888237245fd3 2020-05-24 22:54:36 +0200 38acf28f3905871d327f3f15cd6fac73f9ddae7c 2020-05-24 21:10:32 +0200 df2e1f8893035fc5d7956be1c50ff2d33a2fed0e 2020-04-11 21:59:18 +0200 e84ebbbace3b24b92731ed13626254d6cac650b8 2020-04-11 21:57:10 +0200 3cbfe0a457db28e3149f371d4462b74f9de56dc0 2020-04-11 21:43:18 +0200 834c3a213ba72f55feb6322df625ee916b835b2b 2020-04-11 21:17:31 +0200 1c9ad5ef55795f6692607eeb7054b95e1a6f75f6 2020-04-11 20:40:30 +0200 ed7b9bb519bb553b5e53792794fb43de867d4c3d 2020-04-11 20:37:53 +0200 7e2299845b863f020283a111b095e09ea5f9deaf 2020-04-11 20:33:31 +0200 81bbc72efc93575c808629e57667a703e421b495 2020-04-11 19:49:06 +0200 225ad3bcdc07ce04a2a6ae303452c101be000756 2020-04-11 19:40:25 +0200 68fd6debe77cb571979f7ebd7965d85ad09e76ac 2020-04-11 19:24:57 +0200 f4b90fa97634b8ddae3fa4e686bc3050c8589cd1 2020-04-10 20:25:01 +0200 7b4b21f5da09e8784c8460598eaaa4a12c22eba6 2020-04-10 20:18:40 +0200 da97ee1b95991bd39cd0677a8f7de40d9bfab110 2020-04-10 16:30:10 +0200 4be05b9c21f321bffae2ab4857920602ad1b03b1 2020-04-10 16:29:41 +0200 b0227e74eaff12eefbc26c141aee3f8f5b577010 2020-04-10 16:28:48 +0200 e1f302aa89d0008fdab70a42dcc351ad62d1a174 2020-04-10 16:24:10 +0200 012ebe07956d91db6613f19943424b7cd53efe9c 2020-04-10 16:05:21 +0200 ed871b3f42c46159fe649ef5fdc06ce0dfc096e2 2020-04-05 22:13:56 +0200 a526dce5108f997253c45f5fecbbccf59f9c33f2 2020-04-05 21:57:55 +0200 174493b3a382db98363b94aea75f1a26591688da 2020-01-03 20:00:10 +0100 5f189e1f2fc11a08def8b0246e0c3cc6602ec262 2020-01-03 19:58:48 +0100 153479ad3faa26586d865a94e33b833fbf0e26eb 2020-01-03 19:52:38 +0100 3122630aed5e8f06bcffee5ad7d1507803a8e16a 2020-01-03 19:47:10 +0100 925fdea3d316ec9ff1f09b3fb2a9960ebf09f60c 2020-01-03 19:37:56 +0100 485a35f13bfacb4c0e3a19c9f79eb5e66d582224 2020-01-03 19:34:47 +0100 eaba6ad52ac2faeca6ee1efa368f3a1a554078ac 2020-01-03 19:34:24 +0100 218bed8ed75fb1d28000c25d091372023fc38509 2020-01-03 18:57:45 +0100 ec1e0f95c44d76d8c4714c06dda526e35d4ca382 2020-01-03 18:26:33 +0100 f5ee6970ff95d348669504c8a85d33bc85974798 2020-01-03 18:21:39 +0100 d0719980b5d7b69444115feeceab6e0de1b6eac4 2020-01-03 13:11:47 +0100 b4a652aba0d8f575d10b772cc13a34f3e0a756a4 2020-01-03 13:08:42 +0100 009406c12b114270ee1417a02046cbc07575251e 2020-01-03 13:18:41 +0100 93d4fddb80c3dd727e77c27591eccee1dda59859 2020-01-02 22:49:00 +0100 616e46ca84e039935b1abe89840d114d1391ae10 2020-01-02 21:54:52 +0100 102788710723a6040d301218911fb0e750d5343f 2020-01-02 21:42:40 +0100 a90d4f5855633d14f9f5d5c08a801846137863f0 2020-01-02 21:11:41 +0100 31ad8b3fcd2e8b69a8a580c399d48bb48c187b4f 2020-01-02 20:54:08 +0100 f1349dbc8276aaa2b4b7dca3320e1afd84245a5e 2020-01-02 20:52:20 +0100 aaeeadd56119b06a14ed4b9b637c0e2679061a4a 2020-01-01 22:43:33 +0100 3c3ac50e0334524bb3023391240d074cfd98675f 2020-01-01 22:35:13 +0100 eb173e9673db1ec58e2b0b471ce52c1740ed832e 2020-01-01 22:34:28 +0100 2c759f11ddd2eccfb3164fc265b8a8fd46b522f7 2020-01-01 22:29:43 +0100 d986c34ffffb864475231fc3eca1acefc47afbfd 2020-01-01 22:24:43 +0100 42ece407f2fe1f1510aa1113cf92f4c3c98bd2d4 2020-01-01 22:19:32 +0100 e067bc331d9ecff090f3105b340f37a92563f536 2020-01-01 22:16:22 +0100 15c2cc4fdb14fad6577fa93ed9cf2f95ae8fc3c3 2020-01-01 21:57:19 +0100 eb178ea1b589c6ee694e12cd06babe1361acab76 2020-01-01 20:10:38 +0100 31c01275f303c70125acf6ab9c70c71e15044bf6 2020-01-01 20:15:53 +0100 305c0878e9892c427762fe0f0e2b3862b9834711 2020-01-01 19:36:14 +0100 dd2d5ff8c81013f7bb3b44fdf10538f183805468 2020-01-01 19:34:02 +0100 a3f58fa3920e789cc848d2040db4c4722ca4304f 2020-01-01 19:22:31 +0100 c8a0a54c39f16afb9ab74eb1bea6840cc8affd13 2020-01-01 18:48:16 +0100 c6f5f6f0e02702a3d4fc8fb13052eb4a227a04bf 2020-01-01 18:55:42 +0100 0ea562acdc6c397244a0173afd3de0c7e1c71650 2020-01-01 18:26:12 +0100 7b014bcd92bbe0c4a4de9e88254e7043155a4394 2020-01-01 18:25:45 +0100 831dacddb87c192fe9847f7aeb2a619ef4cd8ae7 2020-01-01 18:12:40 +0100 33aa9270f45ddc936ae00e5707b88b15d76b4110 2020-01-01 18:11:46 +0100 a07e12d6fdfb81ed737045d33a87daa52560f944 2020-01-01 17:34:58 +0100 f0f5c7f9993d06420c2e8fd0394f8b4991eb19b6 2020-01-01 01:23:36 +0100 db8ce45a55ff99a759b2de0206116fe4bf7ab968 2020-01-01 15:48:27 +0100 050ea0503b44f591e58060e3936aa9494358cf9b 2020-01-01 15:46:28 +0100 551f3d4ddf2914ab314a836854149b47e14e8d66 2020-01-01 15:38:18 +0100 435b04a1c0406d5da51d834ca2bc7946b9c2d7bf 2020-01-01 01:03:59 +0100 f588265d2fd6fc8877a08b9730d72f9236fd5f2e 2019-12-31 20:34:02 +0100 305bca956bcc1c9ab2de583943c87963f1657792 2019-12-31 19:52:38 +0100 3d600fbc01499d10a61d61e8088634e9a6663e2c 2019-12-31 19:50:43 +0100 8e98efdccb047c3cdff30e9dc75aaca59cfbf1e1 2019-12-31 19:46:49 +0100 6b4bf291d1e3e99deb5175417769bfde0eb6893d 2019-12-31 19:46:21 +0100 17a2935104e280bea9121e5a181540cff42ce549 2019-12-31 19:40:48 +0100 9b383c76caf715c3b88ee4f79a0995cebdf56752 2019-12-31 19:36:23 +0100 e489c7fb42d95647bb5e0d8616ef5c18ef904462 2019-12-31 19:24:05 +0100 2b56c710153e8ab842f23a7df0e19e7332de8a38 2019-12-30 21:47:34 +0100 4d78d97c21b72fb69f836cf5f977446d313cae95 2019-12-30 20:10:50 +0100 79cb926b5e6ffa3b2189331a64e8ee0d003b08df 2019-12-30 20:03:05 +0100 c8844d7dd08284511b91b3d4a39f590317389365 2019-12-29 20:15:42 +0100 e542560d38f9576a8f51c9e64a203ead3a142275 2019-12-29 20:03:22 +0100 7c1c214458f642917f640b75989a7b73ea38af25 2019-12-29 19:36:26 +0100 4c4b99d0c627429055afec926e75e5c7fa88c1ee 2019-12-28 22:10:21 +0100 8d988c761774b1671d1136f23676db5799e560d8 2019-12-28 22:00:31 +0100 a577970005af88e6822aa7ef229e5d07c6c13fc9 2019-12-29 19:07:59 +0100 19881b69f792b779e939287929c96814408dbafe 2019-12-28 21:07:30 +0100 9c738419f52cc9faffe075f2d5c1794aad18300a 2019-12-28 21:02:23 +0100 c04924e27d3b460e4b8e2c77f2f2cdc0d6d266af 2019-12-28 20:14:01 +0100 14b6386a4f8926324e83d13fcea47b33c5562737 2019-12-28 20:09:36 +0100 c7affcde25cf13cde05f83bcbfa5ca504555a97e 2019-12-28 19:55:10 +0100 677583e6e3c631a89e746fa3079e7683cd8026ab 2019-12-28 19:30:04 +0100 7cca1ad2711f0bad9665a2af15ac5aad5cca6f04 2019-12-28 18:51:22 +0100 b106e2ecdec82045338a9ef206f84efdd08fd09c 2019-12-28 18:43:06 +0100 8778221240b42c6d92c1229ec9f251f3be4733b1 2019-12-28 18:42:35 +0100 442b8ca8a85e0e8352571ac5d4a5ee7f5e7e6b92 2019-07-11 22:33:49 +0200 09d8b66d08dceee6ea93be04140ea0365088671c 2019-07-11 22:23:35 +0200 80e8e9872c20e089813897a14a18cbb4e967518c 2019-07-11 22:20:42 +0200 d9438dacfdf4d6bba38899993c7e9a6397056fb3 2019-07-11 22:20:03 +0200 e206d84f38af9660c4462865ade82c6701ceff21 2019-07-11 22:07:09 +0200 b99f9334f9e2cb2e1959e3da2bcce6e0d356c99b 2019-07-11 22:00:15 +0200 08106f3051f130ec381ef9cf00260cb65ed87074 2019-07-11 21:58:46 +0200 41d9643b7ead8faa587b3f9a9db2267c042b325a 2019-07-11 21:48:33 +0200 09a1118bdb06b63f31a60446440bcb2d1dbd249e 2019-07-11 21:46:22 +0200 a0187c74a47f1dd54da0fb9dace81686274f27c7 2019-07-11 18:48:51 +0200 85cbeb115959b768a82d47da93d062eecd8f5e93 2019-07-10 23:24:16 +0200 1fd91d46a098d6d9ca8bf5938d289c711c285775 2019-07-10 23:22:23 +0200 cc9e84b0a418d9bd8e24a4e12402c1c68d31dba8 2019-07-10 21:42:29 +0200 d82623ded860de83908ebb835a95fa1758e8a8e2 2019-07-09 23:49:02 +0200 2cde398ff6cdb806fa76a4584cdb79f0f547a828 2019-07-08 23:25:53 +0200 a9cfa2d05ba3149811ec17a85a9fa9b796f8832c 2019-07-08 23:21:36 +0200 7e3be8fbaa8c04a2b4634fd92b8cb6d9594b3bbc 2019-07-08 23:10:17 +0200 6cd5eb8a99229dff024a4d0ed84fc96a70562f45 2019-07-08 22:57:38 +0200 ae7c589024897d6accb598ee706bb6b8a2cf34f6 2019-06-30 21:08:06 +0200 72c0f9a06e122bbf5d87fcdce3f96b377ac40c3e 2019-06-30 21:06:47 +0200 eeddeb17229cac0de51da20ad400702eac4aa8a8 2019-06-30 20:41:03 +0200 f35237913b7db635aad69966ae66330516a4f61e 2019-06-30 17:41:17 +0200 d8f5fdc5815b28b2046a8bce05ba5910a8d91370 2019-06-30 16:40:26 +0200 588dd642fb254928c38b82447c6bb439614d59fc 2019-06-30 16:15:52 +0200 969e915c85d765717c8858e5ab154fbfd3c9b044 2019-06-18 22:46:24 +0200 2a6cad87b143d747778bbaf1d60d75aa310fd888 2019-06-18 22:34:06 +0200 66015241a6d5ee90567a800ad5741d7e73f49a88 2019-06-18 19:46:02 +0200 89490275e1c5db474a0090abb3f08e6d0e6c425f 2019-06-18 19:40:07 +0200 bb81569fff7ea921e91fe4556a28e563fd8bf308 2019-06-18 19:32:47 +0200 c589604f8a4b6fa4094779623fe50ee28dbf33da 2019-06-18 18:46:53 +0200 f0523f8daee8460d7ab7c5c617e5164320ef0741 2019-06-18 18:28:20 +0200 2f7af0774ec86c12c3fd569e438178583834553d 2019-06-18 08:15:01 +0200 a2c9f94db9cdf0f38fedc61309e56a3c33d36a3a 2019-06-18 08:03:13 +0200 b6ce48af3ecfd9414feefc483216b5fb1e9f1d23 2019-06-18 08:00:36 +0200 20115e5cfc4697b961a4aca31edcddc94d5407c6 2019-06-18 01:36:31 +0200 840277a0829648dfb3cfc2f40bf16df2dfb88f0a 2019-06-17 22:24:44 +0200 03b28a125c978852fe9c418f7ca3ad4d79cb47c0 2019-06-17 22:18:01 +0200 f1fa4cf11231cf9f20bbb699605b6da9dfdd1cab 2019-06-17 22:16:09 +0200 65b79d35761d31b0b6bf404c34a8ab57ff703205 2019-06-17 22:09:53 +0200 6a898515802defd5436700ac20f7a9733b61fb4c 2019-06-17 21:57:33 +0200 afdbffb32f8daae2a09be4866bfd19f33345a992 2019-06-17 17:29:14 +0200 88d20a857d6dc513e5332388ace03236618029fe 2019-06-17 17:14:06 +0200 555e10d7d30242c9e77a485cd938c612e3ce040c 2019-06-10 22:55:46 +0200 cb4ed831f41ec5f5eef86b1f0d1c958d7e4472c3 2019-06-10 22:17:26 +0200 70f6c1457158eaf8b7e87e5851cfe090290ff203 2019-06-10 22:11:26 +0200 f8e4a49b6c62fcf7db89c490782785f55c66f0fe 2019-06-10 22:08:58 +0200 7d0043ea6eb1dd5ac40565db911169696f119451 2019-06-10 21:59:13 +0200 d54db23b933ba43095371ceb9fd09d2a8f04adad 2019-06-10 21:48:44 +0200 25d9514b2cef47609090993ae1a83304a6d171d1 2019-06-10 21:38:28 +0200 10014a3ee2485c4fdfe080fcee8b648e81b7ddeb 2019-06-10 21:33:40 +0200 26d05e1010442e0e05022190e2e51c8f1fc83575 2019-06-10 17:04:58 +0200 8352e0d49d6902a9216399c287921abcd60ff8dd 2019-06-10 16:58:35 +0200 82b72a77706d774fe8db99107f5212f79666c3f9 2019-06-09 20:42:53 +0200 a1e5112eb836619da55a92dba3752b32cdb07dd4 2019-06-09 20:40:18 +0200 866a59de515d5fd21d298b50b937de054bfff021 2019-04-22 17:47:33 +0200 5eccaa7bf8fe0305280586f28b32065c187e2529 2019-04-22 17:34:14 +0200 3133202c909c90216ce6af4781e9e18c9d5c9337 2019-04-22 16:12:39 +0200 f793533cee5464273c7c7605b5abade720f9b581 2019-04-20 22:20:58 +0200 ebfb895f1abb45999c7aa14ea50762f9e0552afb 2019-04-20 21:50:41 +0200 6bf3f11c4eb153549b75838cd540e5d7f432b87b 2019-04-20 21:35:48 +0200 d9c22cc77a24b89ed6214ba9bbebfda180e4053c 2019-04-20 17:26:40 +0200 be1e64b05a25d3f4e24389d0fab204f03d45b66f 2019-04-20 17:23:20 +0200 2ae14d87d0ef48e9281c9fc31a0769a418ee42a2 2019-04-19 21:52:27 +0200 866f74bb022068a7e875b5032e28e645e00ec5c2 2019-04-19 21:42:09 +0200 88ac92027072bded7405880613edb8ff13884bd8 2019-04-19 18:31:50 +0200 8e0cb12054ea0a9240ab6a7c739012517a70725b 2019-04-19 18:20:15 +0200 554879f6050a000a8d87d90cf8fd526d7a1435ae 2019-04-19 18:18:23 +0200 a25fab65d6b19f9d17045f3735bfa99cb234e1e3 2019-04-19 18:16:12 +0200 54ec3c0fbe70063531bbc937a68844555ff4ef1d 2019-04-19 18:15:27 +0200 de2dec8839fa5f0e9ecda8f208ef4ca24b2749f0 2019-04-19 17:51:10 +0200 3aa4a955ed6664373efee22400b71ea7ee131356 2019-04-19 17:06:47 +0200 cdafd091b9d1200e9be1971e84ca9b9da9057ca3 2019-04-19 16:44:55 +0200 9bec86daea2d29e4ab6d54459848e446c54fa07d 2019-04-19 16:41:52 +0200 172c44d9b83c4680f855aeecaa60f9d1d6c51ce9 2019-04-19 16:22:55 +0200 cd4643cb9969a50265e606ce45b36eb5826c72cb 2019-04-19 16:16:45 +0200 6027514990a89da91a075643531fc21052afeeaa 2019-04-19 16:09:06 +0200 22c600474048014e926eb8f20bbc29300232886d 2019-04-19 16:04:53 +0200 f0c8711b57d5f0f949b99ac60ab763621e02b21b 2019-04-19 15:02:16 +0200 eab86b88e8ea6c0cfc1eb03525dc737e5778dee7 2019-04-19 14:51:11 +0200 23c1639ea0e5fb8764fd49790ce89b003dd1f75d 2019-01-28 21:07:09 +0100 5eb6ef13362aa1f717ffb14bd8ea1e9314b30e3b 2019-01-28 21:01:24 +0100 e207929eef6ccec3904d59df1ce0e7c91cdfb365 2019-01-28 20:43:10 +0100 ebd10c7d0d1f8dd69a7c5dcb66103a6333b5999e 2019-01-28 20:21:56 +0100 480538e14451a972288baea40f142ec7f988d9e7 2019-01-28 19:45:44 +0100 ad291ad872455b8f14040d7e9b4fb061c724c6af 2019-01-28 19:43:11 +0100 c7abe644763bc05b483f9fd8a2ad05b19b494ed9 2019-01-28 19:11:29 +0100 ab3eaedc03cba116ea605308e217dc135c272cd2 2019-01-02 12:19:19 +0100 54a4611470c398e6b5f503bcedd14ce1d85544c1 2019-01-02 12:17:16 +0100 1d6514ef2206f08d025b6f229e1eb7d983bd14f3 2019-01-01 14:50:33 +0100 91115f000cd9b8d3e814d41e0061f136625bc1bf 2019-01-01 14:30:30 +0100 2930f3422938fde8c2f07b351b588d87ad27cf1b 2019-01-01 14:10:37 +0100 a72b82f6aeaa8695dd5469b0a3679271c0205077 2019-01-01 14:05:23 +0100 59ed56ce841c74a5ddab76d09b94759dd40b44dd 2019-01-01 14:04:13 +0100 ffa05876b46064c92ffe5ca5aedbf4cfc777fd43 2019-01-01 13:59:26 +0100 72d797a82efe795203349fdc676d58e467f05c89 2018-12-31 23:05:52 +0100 2402b8a7517873c3af6581af696a270ff5833dc9 2018-12-29 21:49:25 +0100 194d13209ff4e976fae00150a82b3ed5a55ea71c 2018-12-29 21:31:03 +0100 4b773b472e7e6607c8b063a85892d00975cc8ebd 2018-12-28 22:18:45 +0100 9993f6af7b4048555c27f9b9fc1284f5622bb9db 2018-12-28 22:14:39 +0100 0a9be69f9436f26be820607ba998be140500c390 2018-12-28 22:08:13 +0100 bf5ae0f905203473fd0209e3bc5629b2a5061a15 2018-12-28 22:04:36 +0100 8a0ab80584c7717681557550d9608f323a2a3ad0 2018-12-28 22:01:01 +0100 fe7717b0c3ad6444632be920b49569b59baff71e 2018-12-28 21:50:28 +0100 4dee27f19dca4f5f8b8ce2d0347460d30036b4a1 2018-12-28 21:48:16 +0100 4c45dfcbf33ab7331f7c2941221dd1869a8735e5 2018-12-28 21:45:31 +0100 97c07a8b60c9aa37c776640b525a12533daafc8d 2018-12-28 21:43:52 +0100 4052933a7485f44aedce393dba482e5da55403d5 2018-12-28 21:42:48 +0100 9f3c5bd51a5410417968a014b21f94d25ffd8144 2018-12-26 21:27:32 +0100 a602a48f5f12c68aacf7e1b88215a9194f95538a 2018-12-26 20:54:34 +0100 a4c634fe6853203ff8d70ddada8a1fd389ed334c 2018-12-26 20:52:16 +0100 28b990e4255c05a6ac4f5a1613b7011ed845299b 2018-12-26 20:49:31 +0100 01e2cbf52b1e98782750bb9260b2883688c1bae6 2018-12-26 20:44:49 +0100 f0d526af56862ebfafbc4eb563465d1d5becd8f1 2018-12-26 20:33:02 +0100 2652fb65ded9058de654b412a49145d89ac1ca87 2018-12-26 20:15:44 +0100 b9ad96ee9c7bd28d685237296061332bbe1b4935 2018-12-26 20:10:12 +0100 7cd9bcf4122624dc1a850b1fe735361943d929db 2018-12-26 20:06:31 +0100 0570e7f70e00d5292b5db0600e912fbf2a91da4c 2018-12-26 20:02:40 +0100 7d731aea536062e943f04a11a24d64b328ddaeb9 2018-12-26 20:00:40 +0100 2500d9b34941f0d296c03797924f7ada358aa62d 2018-12-26 19:49:04 +0100 3557f65ded13eb5998b813e8ffbab39a903bfbc6 2018-12-26 15:22:08 +0100 a10aefe0f71f3bac9b0a33139dc3e6f5af933137 2018-12-25 23:31:04 +0100 560867ae70f0918db7e37868246fc691f95ac2f3 2018-12-25 23:22:27 +0100 8bed138bc404e7c6ef38801df26d19606ba38272 2018-12-25 23:19:02 +0100 827cfaff87d7ec41c29c2ed56301b7783cb60e5b 2018-12-25 23:05:32 +0100 208cec5f17976a09464749776701787b2bf7355a 2018-12-25 23:05:14 +0100 01215a325738a620846e1626ab5d54278bed3bae 2018-12-25 22:45:26 +0100 dd97ab89a44468179c0c5bad73b29c2272ef63f5 2018-12-25 22:42:40 +0100 f146a1ea66aa51ed5fdbea49a58f39024b66398b 2018-12-25 22:35:36 +0100 1b17d07f3a883e5aad7074268a7cab0cc6977ec1 2018-12-25 22:20:39 +0100 766c15f83205a4a2ad8db77e7206fcc94ff9e2fe 2018-12-25 22:55:24 +0100 c825f0cc96a3f44a4d1f0f2865b156433f9fb52b 2018-12-25 22:10:57 +0100 c83a541585d6b57b0056b14d661570383d33ba64 2018-12-25 22:08:28 +0100 7704f9b8cf357a38014f6e671833a7cf0721c1de 2018-12-25 22:01:02 +0100 07fb900b689ce8a567312fabe0a15c02253863c3 2017-11-08 08:47:11 +0100 04919ae8899756ab2c296f89abff176b7f31f258 2017-11-08 08:43:11 +0100 c8b54de02a4deabe8deed4bc31907554be1ee3b7 2017-11-08 08:33:06 +0100 97e1e1d0456cb75d11fcdaa7a6db1b74e0002472 2017-11-08 08:31:15 +0100 dbb73e1fa806ebb8fe28c3b56b20b6edb6ba082f 2017-11-08 08:28:49 +0100 9d55cee384ec4b63b6d38f4865d81818bddf7815 2017-11-08 08:24:44 +0100 b2458e82900611ada9eec857390b5fd24753a13d 2017-11-08 08:18:10 +0100 c2d1df5941ab3ee4612d9cbf3b3d188f0a246a0c 2017-11-08 08:16:43 +0100 906d7eda1e0b1fc16f7581abe0ba2fe21c38637d 2017-11-08 08:09:56 +0100 97f5bfb96c1a3927e250b1ad8fa96f82c4dcdabb 2017-11-08 07:54:56 +0100 3fdafd40c78a007a0eb6132ef87d56d8995235a1 2017-11-08 07:53:32 +0100 f6f15214652a29180980b7bd08faeabd90c8f2e0 2017-11-05 20:22:03 +0100 b73b382816450b787afbb4dae277daa1705b6022 2017-11-05 20:21:30 +0100 fbc3763b574fd72ecf629b54df3ba06f9971d2ee 2017-11-05 19:05:37 +0100 3e8f94e0ba637f89c0555f807db7e2a7c096fa90 2017-11-05 18:59:18 +0100 ef338e57de470ee53f55b1f9903885a2830deaa7 2017-11-05 18:58:00 +0100 0296a2f3d1189b24aa46500bdeba8c69cf3aea54 2017-11-05 18:52:24 +0100 107aeb7e9ca4c06fc10db8870bc1921996946268 2017-11-05 18:48:19 +0100 19c512e280cad0ec361b11b473edb03f0d760f89 2017-11-05 18:19:51 +0100 93401904f9bc2e77a705bdb87f031859a5c6b954 2017-10-29 00:35:10 +0200 c48d3e1a3e8aab609cd2c69b7aa48c971a253d41 2017-10-28 23:53:04 +0200 c0463bfcfccbc3f4e24cad4dc39d1a9d49e9eb90 2017-10-28 23:27:23 +0200 9665209106024c0b057d5b4716c490c7de37ba38 2017-10-28 23:26:52 +0200 a0a8365cdb122b7fcf5506d27a7a85e02f2f212a 2017-10-28 23:15:46 +0200 6f9cfd296288342f13cbef938965953352256c62 2017-10-28 22:55:10 +0200 ec059f9d9dc87e008da22c9462630c0298c27df0 2017-10-28 22:49:35 +0200 7cfac974784c9ab46b485a7758b81c374ad993f3 2017-10-28 20:47:26 +0200 65a09765b238ed8c22ee48ff04bf748c43ed4712 2017-10-28 19:41:54 +0200 80aa676d81b53d3f02b64ab057ae0c91e6649b68 2017-10-28 19:33:33 +0200 4027740cdd2dcb928b12f5ed265866038a3d6154 2017-10-28 19:23:06 +0200 b4c9b089b6b84d0b023ceabc1b76710037be581d 2017-10-28 19:17:42 +0200 441d337b287836e1acd7cf247db715db6d18feb0 2017-10-28 18:38:46 +0200 1ce0dd183c17373b524c29a48421dc2b5cec3e1b 2017-10-28 18:35:01 +0200 b2e19844d6632ff54b35e5e088664cab9707e70d 2017-10-28 18:25:26 +0200 2cb5fa8e92060c41cd6cc05c0d5b23a908005620 2017-10-28 17:51:02 +0200 108c4eb98c666f91910e193b9cf21ca66db38360 2017-10-28 17:18:49 +0200 2a3350ab93747356593e6c76dbe4e8b454bbdc93 2017-10-08 20:00:38 +0200 57f54c874d4eca11526634f345217ed6b92d34ac 2017-10-08 19:47:58 +0200 7199091e3f2a5dad43e96d604694b764152c45be 2017-10-08 19:46:05 +0200 e6aab79e4df38c90ea195e6f4841f6d3a409e9ff 2017-10-08 19:43:58 +0200 00248d2423d40f44507a29249d89e5d2ef0698f6 2017-07-23 16:11:59 +0200 afa733e4a84d7d76f5314329e1b314da6d5eddbf 2017-07-23 15:35:36 +0200 b5f33ec89ec0ab998bc3382e4958c218aa7bebf8 2017-07-23 15:27:06 +0200 2ef276ee794b39a525f510672b94ac148c8adb58 2017-07-23 14:54:45 +0200 40280d912474cffa5c3650a6375cfbaab897580a 2016-07-27 22:54:56 +0200 db924600a3c3449a9de03dbf2e1af81be1d6f371 2016-07-27 22:32:49 +0200 0fa0dbf974430dd5bdc31ea9fca641d3fa0ee4d5 2016-07-27 21:48:34 +0200 93ea351f922be6080e747c5be1980c0ca777a59b 2016-07-26 21:38:34 +0200 abc86a3e6e0e7061efc045cae4208443b4c03f0c 2016-07-26 21:27:39 +0200 43e76a5a352d574f2761dd4ffa6326db9e288b8e 2016-07-26 21:26:28 +0200 30dd49b6f22c3c8ffd5798fdb30014d23d256d0b 2016-07-26 21:12:39 +0200 08bc36ca7a926557d4b87da06717f811b735b841 2016-07-26 21:10:08 +0200 d66da25dc032c7f90c55105a692d08a53fa5f8dc 2016-07-26 21:07:45 +0200 b86501e21e09f27ecd8c204431c22cc86d50dbac 2016-07-26 20:47:54 +0200 76779608d03503967f50d232d360c662fbdbbbfa 2016-07-26 20:43:48 +0200 f0e353b392fd83e2c445ea24a14f2d81ef7e026c 2016-07-26 20:32:18 +0200 c538f1c8b22aec84bd2dc654511cef00309ac7f2 2016-07-26 19:57:39 +0200 da5ba2ff0217062fafa8071edcd944de5592ebcc 2016-07-26 19:39:00 +0200 d72217c661d1d1a1774b7700a2283e265ca0e498 2016-07-26 18:58:09 +0200 04320be5049aaac77df2a4252002f232fe3cff2a 2016-07-26 18:57:43 +0200 039a9b8f04a765257375a5371038fc44296bdf16 2016-07-26 18:34:34 +0200 f2273a0e1ea9b24758f485b0bb37614bb6160afd 2016-07-24 23:28:46 +0200 a04dd0813cc98607cc8c0a3d03b5849989c9163a 2016-07-24 23:09:37 +0200 1bfbb2e5d2e801eb81987ceb18302894ff7d3c7a 2016-07-24 21:30:42 +0200 de4ce0c5b7ed2d6db17187ad15abfd66d2ccf5c1 2016-07-24 23:11:53 +0200 dc8d763c21c50b6ceffcbd3ba863c826bcbfa68d 2016-07-24 21:02:33 +0200 29ca006a3381c50cd830554fd8ce64f78bd2b0c4 2016-07-24 18:48:12 +0200 1fda2565e609e14663e6d26e864600e82d9876ee 2016-06-02 05:04:16 +0000 28728176a32978a40237a15f8ed62a6cf91e8e25 2016-02-18 21:37:58 +0100 6068f004ac3dd7c448e75ba8843a3bbdcd1fe6ff 2016-02-18 21:37:10 +0100 3569616c74eb931f2595cfcde342ac7fae9309f1 2016-02-18 21:36:10 +0100 8a846e32b6dee5467b444739760ce901ee0aca56 2016-02-18 21:35:29 +0100 e7a19b88c541af0dd5d2c54772012630ace56429 2016-02-18 21:28:54 +0100 0b722dda197df75d4fbc5e9f3596db235cf147a4 2016-02-18 21:13:47 +0100 f6ab8c23435e9cb915917ed03ccde018f163b2b2 2016-02-18 07:39:51 +0100 b02a9cea6443b478270019ec06a59d7492fc4430 2015-12-25 15:44:17 +0100 38a720e9501c096bb5c1c883ba32c100b6b0a938 2015-12-25 15:21:37 +0100 2a179afe3823d1c0e43c6a2d633b6e30de5ffffa 2015-12-25 15:20:49 +0100 88c4f857a57a135d4e01592f5c53f47d29d3946d 2015-12-24 21:23:10 +0100 081e294de5ce373912d37701431ced0f1c5a741f 2015-12-24 21:22:38 +0100 4d5b3930d8889a575534b61d3e9e24810e2c3bf6 2015-12-23 22:43:57 +0100 c15d23632c7d6a327969a627e5131a6673669193 2015-12-23 20:33:58 +0100 c84dd64b3eea91fe8b8e7370e486cf7f6b3cb01f 2015-12-23 20:18:45 +0100 52b8ea2582cc5891fe411f76f8300a9c19608014 2015-12-23 19:08:31 +0100 585ca820596ed278f1b371e214f07de247205f31 2015-12-23 18:47:24 +0100 6466275a2c63c3ac87ba15362fe4de3499d0f911 2015-12-23 18:34:07 +0100 60662e7b943d6cced20e5c84dd123193cd064821 2015-12-23 18:29:53 +0100 525a118b3085c7fb3fab1528352e6d7696ce1ee0 2015-12-23 17:56:01 +0100 66ef713f4fe8c86c499262995cf2fc1c11191a53 2015-12-23 17:21:44 +0100 3cb69eca3e41d1113343c0b09fdc3865eded920d 2015-12-23 17:10:41 +0100 dac2d0091bf95a53fa85e7e2fe7f4201f9794c13 2015-12-22 20:55:22 +0100 b250e8ed87a61c724bb0eb171193b39be78737c1 2015-12-22 20:53:39 +0100 88b0ffa3b6ee396422d649ef2c1d89efe5aacf3b 2015-12-22 20:06:08 +0100 f0b18efd52e597c3a7d2139a49d56f114cad4dbd 2015-12-22 20:02:20 +0100 f6d7fe5a44bbbabefa94e0d86dd10812de59135d 2015-12-22 17:23:41 +0100 f1bdb11422abd9a077f919b7d45505d9e4798613 2015-11-22 19:31:03 +0100 53b92b8e9ccaada70a0359d0c85628aac305e17f 2015-11-22 19:28:10 +0100 618b3060406164f535bc932dbb811d26d4a2006c 2015-11-22 18:04:14 +0100 5065ec7ab12f2f7da746d5a7365da2e7cabed4dc 2015-11-22 17:48:53 +0100 61b6654aed0a1763508e4091542c3b4dce0f1c8c 2015-11-22 17:46:56 +0100 5b2f60275a3fd3ae53bfaa8f5328cfd270e20773 2015-11-22 17:37:22 +0100 b3cb37bf770eb52ce7b14ebfb309779fd0237b6a 2015-11-22 17:16:22 +0100 023d01d088bd25cd7aa255ec291fe680389580da 2015-11-22 17:14:25 +0100 f22e1a2a3313d71b8153aeef9cf29fdb9289c7ee 2015-11-01 23:02:42 +0100 3bdb8233c7f2be92d5e52843a17194dd29d5caeb 2015-11-01 22:50:46 +0100 086cea3091146d0a448488a1deae2fae960bf94d 2015-11-01 22:24:13 +0100 350fad454a39b6eeb63d9fb8bdab2eefadd85fdd 2015-11-01 21:58:58 +0100 5a4abb73e8a162e74c659f4ce498b6d7efdb578a 2015-11-01 21:55:42 +0100 4dd8b4d6aba7ac199e0003ef71bfbceeb2b470c7 2015-11-01 21:46:44 +0100 3a206c1d43a8953d7b1e05cc395575930976dbfd 2015-05-25 18:16:30 +0200 d55cdbeb1a4ba6a4a0fa86f148da5a79cf5c6083 2015-05-25 18:06:47 +0200 bef13515973987fb66e3d800e32ec0a72d06fc69 2015-05-25 18:06:09 +0200 a231d9cf62d60ce648db6205155988b364d34700 2015-05-25 18:04:26 +0200 80f299bfc985bfc96da4ee448f7cf5be98e3839f 2015-05-25 18:03:39 +0200 6b49dedd1dcf1e1bdb94968fba2b98856b0e6aaf 2015-04-02 10:01:53 +0200 24dd735e6baee252cbe7017ed28b8848f0266971 2014-10-12 14:49:20 +0200 515308677e532d6509d1f8d5601593e963494fac 2014-10-12 14:35:43 +0200 8816cecb61a3cbba443b6d0df65169fe3b4dc7b9 2014-10-12 14:35:30 +0200 3e37af2bbc0e9ebba7c73b718f73d04bff00d5ee 2014-10-12 14:31:31 +0200 96ec705911530d96931c7bf6d6cfc56bcf156508 2014-10-12 14:27:59 +0200 32e2f583fa76e98e280058b853602cda193fcd80 2014-10-05 15:16:52 +0200 55b5b70fd3f2950d7e7bb9a3353f67644a52429a 2014-10-05 15:16:14 +0200 654ffd48590d9e2dbab169e08e0ff31199a09d76 2014-10-04 20:17:25 +0200 589885ddb72e7891185d67fffb21d82dcc648665 2014-10-04 19:12:28 +0200 83918d4f5088801d9b643dc4d81224a44c169d61 2014-10-04 19:07:49 +0200 8e468c5979b6ee87f35655b697e2feccc25f2625 2014-10-04 18:43:31 +0200 5c6832f729b7f57af006402b66dbb56841a80205 2014-10-04 18:43:11 +0200 95647a76b6c5d56ff801ebc0234b51b9ee12d00c 2014-10-04 18:38:55 +0200 7146d114d459282531c4529d0a6c154bde176024 2014-10-04 18:20:42 +0200 bca0ec28c76cadebd122f0b4332b5eff4caa0bbe 2014-10-04 18:12:22 +0200 eebe89a0505b9754080cae33a78e8ebb25a22d5b 2014-10-04 18:11:03 +0200 f2998542a2210f7f46254e0c31c889507bac8e04 2014-10-04 18:08:41 +0200 87c82eae29b662e077e0acd18ed9c5b78640451d 2014-10-04 17:46:18 +0200 b3b06ecffc0a9b0cfba53673bfb7fc80d454011c 2014-10-04 17:36:46 +0200 663b036bb8f72fef060850664d1a7da7881a207f 2014-10-04 17:34:46 +0200 79363bf644de43f71412128b379048f665824102 2014-10-04 17:14:39 +0200 2d21406a6d7f052090dac2070f8c7f0a14d0d64c 2014-10-04 17:11:25 +0200 2043671d14fa9b10ea2c31d598ff531928d0398a 2014-10-04 16:59:05 +0200 20521872d3ff473847cea99cb444e6bdc2d27266 2014-10-04 16:56:11 +0200 9f7123eafbab7f601a11dc8626b5968e2c730ee0 2014-10-04 13:41:10 +0200 3cf705ab80117471e996eb30dfe2bbc34914efba 2014-06-18 11:46:19 +0200 52d92c08ba60e4a9bc264390d6a19c3319d1876e 2014-10-04 11:06:40 +0200 a48f63e7ca649d54660433c02ae093742d791324 2014-06-18 10:14:32 +0200 c5b353a1c23d2d4bb8848e53f6333de10a4d5e91 2014-10-04 10:09:43 +0200 2a46cd8e2ea6311e086e312c0d595a49ac31e285 2014-10-04 09:13:38 +0200 ae410297f2f474e0086c582388caed585a2bb1b5 2014-10-04 08:43:42 +0200 d870f3bce9aee1b5cdf094428b4ffa223afbc4d8 2014-10-04 08:42:36 +0200 32341ddc64cf5538b47a4091c81bd5fd7eb9a94a 2014-08-05 18:56:38 +0000 208451f8a4b20e24fd79b4da600e32c9113c8580 2014-08-05 05:57:31 +0000 d221c4a1dae564b791a735524bd1ff69ea2af390 2014-08-05 05:56:56 +0000 87247eac384d3307c705f8473da61e3f5f5d4668 2014-08-03 17:57:07 +0000 2fd69205fd0a652498f3054b5cf4c5c218ab4f57 2014-08-03 17:56:30 +0000 b985336e9cfb3d8ea674d7936708168f3eaddbb2 2014-08-03 17:45:24 +0000 406c84da6e3bbdc7b02bcc27c3e606982bd13be5 2014-10-04 08:34:31 +0200 28b942fdea7cc02285921a4e23b8e81851b85365 2014-08-03 19:32:00 +0200 00ce7c202d1e9442afce1658c8da0b961665f913 2014-08-03 19:27:18 +0200 6fd5cd296fdafa99a348a5ec20fb63e4f7a20f2e 2014-07-19 17:52:12 +0200 dbe39cf8a31c39883095af78529cbf5db0e8cdd3 2013-11-17 21:44:02 +0100 ff0903ab2a2845a22427df6f047e385d144f93e2 2014-07-19 08:56:27 +0200 e56bed3af3e7874d5e6ed3030a7df78d18ce4ae3 2014-07-19 08:52:10 +0200 91b2e6dc9108f37e0b3c63ab52278e17e9dfd363 2014-07-17 22:42:23 +0200 bed8feb3aa8102730db76b3eb1482d3f07b0f4ee 2014-06-29 00:12:50 +0200 9fb7fdb5f0380384289bab97a374b96678e006d1 2014-06-19 09:32:23 +0200 1a72b2809d480c3a3d7ed8de7d9f463e68f2cf15 2014-06-19 09:31:10 +0200 1356833417586fddb859002adf492a2d741b75b7 2014-06-19 09:27:15 +0200 e9d24222f45ed8a6b3057d7ae24f781c1cfd815e 2014-06-17 22:17:19 +0200 fb5a46433c780c142e59f1b5cab7286577a0832f 2014-06-17 22:15:42 +0200 963488f28417ebdf784bd3a448e6bccb24aff7c8 2014-06-16 07:36:21 +0200 2ca4990ba940dd89cea692eb5a73f5c080c29a49 2014-06-16 07:24:57 +0200 f7022523f2f0a12ef3f8f0d986ed840775f35a43 2014-06-16 07:11:50 +0200 abf013fcae1ca6980260c8ddd2f234af0cf4086a 2014-06-16 07:10:33 +0200 696006f3351dee641a9577d75501211bedd547ce 2014-06-16 07:08:50 +0200 d6788e481bf2c1ae0c1f0b84ca6fa3ece09ec491 2014-06-09 17:25:07 +0200 65fa04ccd04db0c0930e5fd980d4c8a3f6a77e64 2014-06-09 17:21:36 +0200 a540b8a9cccd6e68fbe11506b8a0eb551c99f6c1 2014-06-09 17:19:19 +0200 f494d534e297560b37641ad5dd2176600175b974 2014-06-09 17:12:40 +0200 1f8037993f30f1c7e4164f304de6ba9b9dfe518a 2014-06-09 10:50:37 +0200 d04d9045c9ad3eb50ccb43c733393463b0d58d47 2014-06-08 23:55:02 +0200 46c35b1087c0c7e7ee33cc7ce1934bf7b90fc7f4 2014-06-08 23:39:55 +0200 1f446103ade63e51cedee465aaf21d380eb6bc13 2014-06-08 23:38:53 +0200 7db0799716dcd27bdff8f3ab348a3d78c0f4b9cf 2014-06-08 23:38:29 +0200 9218cbe591b01c5e70188a933a99b4477722869f 2014-06-08 23:26:20 +0200 349506cfb444adaf84dcd10fe6746096b5d5651f 2014-06-08 19:08:52 +0200 75f09a84fa263eb79fce0043d8f267171a82de4b 2014-06-08 18:47:06 +0200 c2a6894ec18d84d7a2f8fe6ffcb9e5e4b08660a0 2014-06-08 18:23:10 +0200 da2260917c8591a3956701142b1faf4f7a979b76 2014-06-08 18:18:16 +0200 8b20fe2ccb3abda622263e4c6d4cdaf07cb8a60a 2014-06-08 18:15:03 +0200 51b06db2c5f66e37d4986b68b52a3b80952a2b2d 2014-06-08 18:12:01 +0200 50e394499e78de0a74287324fbcdc3599bf40dce 2014-06-08 18:08:40 +0200 141eca2069c4f71fe83a1da8d77356076816ef41 2014-06-08 18:08:21 +0200 4d880f3271b230ce56f5a19dc260c906e7377a65 2014-06-08 10:56:26 +0200 5af5890055a3fb5f6f8b9f960f20d3ce6aaf74e0 2014-06-08 10:51:31 +0200 2e15dc8b58d5cc3aef9b0c5422021ad9e793edc2 2014-06-08 10:47:31 +0200 ac09b6d5a74599380a366c2d9daa394df759450c 2014-06-08 10:45:07 +0200 62b40a241d297be0b9a4190da857c336997c2ea8 2014-06-08 10:41:04 +0200 f77c94a10b7b5a04149dd00323dace9be80472ab 2014-06-08 10:35:50 +0200 ce5c6ccebd47448e2847b622bf5a1ad5c7331f6e 2014-06-07 23:32:31 +0200 d0f1e9066ef9027ac5836afbf254df046f74240c 2014-06-07 10:32:11 +0200 a31eac83a28a41643cb6acc740cc9e4b7cae15ae 2014-06-07 10:26:38 +0200 452a03a5b1ff3138d327d63153ebb021ca99da91 2014-06-07 10:12:34 +0200 fdb6f0b71ee1d4c5d42211f7d442ead207fc9025 2014-06-07 10:11:10 +0200 f40b9f6738d0c7943f2cb1ed49f0b7b1cf942825 2014-06-07 10:06:24 +0200 896e116fb44766230facfcac78d23e91d36c37fd 2014-06-07 10:02:06 +0200 1e0a779febee9026587fa42f7a3b8313cf106c28 2014-06-07 09:50:58 +0200 9d08fbe3c75097ef429da2fa03758afc4968194b 2014-06-07 09:47:35 +0200 23f0bc574981a8f8f0306334658153a64003abf9 2014-04-23 08:14:45 +0200 cc42f6ab9d3ee949cca6d1a72d22f50be4b87b6a 2014-04-22 05:02:24 +0200 f430087c23c102d7a371d6b83ab6f7796e3d02c0 2014-04-21 12:50:04 +0200 f8ac6ddfcc1cf3bfc32f6c7a4bc4f02c4b78e641 2014-04-21 12:48:51 +0200 25a5b264907cf555047e1479971b392b730461ec 2014-04-21 12:24:50 +0200 a3fac26bf149079008697a15cd74efc66a84178b 2014-04-21 12:15:51 +0200 93300e42354f8433cd29e83f424af0a10f7e20a0 2014-04-21 12:13:59 +0200 b2b2297fba51c9bca867824d52c4928171329822 2014-04-21 12:06:24 +0200 30df847fea398ff0d284b423b47b42c22aeba41d 2014-04-21 12:04:51 +0200 c92c1acc42c22af6467bfd4e3942719e2856857c 2014-04-21 12:01:08 +0200 c9a1598aba177b3e72ce66eb35ae36a80c244eb8 2014-04-21 11:59:09 +0200 1fa8877022e172ae537bfb2e1643918afd410119 2014-04-21 10:30:30 +0200 ca12f324b30bc6dc7b757875285082d01c540a45 2014-04-21 10:27:09 +0200 0500f24778e2ef14901b73c41853df51bc099be3 2014-04-21 10:20:33 +0200 6feef85c45b26e1f06a2e431c21649b476675fa9 2014-04-20 11:38:52 +0200 4a0098ea7ecb767cf6d3f97746e2ecad2883cf31 2014-04-20 11:22:49 +0200 f70b18e91500af5e1c40adfd0efaed1cbd0c2c83 2014-04-20 11:17:18 +0200 20df4fdbedc9bba50d1e164ccc0e3305f6e2e2c2 2014-04-19 23:47:52 +0200 a675c0a0157a34a4b618bd4e3afb53f45f915be2 2014-04-19 23:47:10 +0200 0079a3a8be44e5b2b688352f3833fed440a15560 2014-04-19 23:16:52 +0200 600fb1c435a9f5afb67d9ad5a1fbde89b16c456a 2014-04-19 19:13:39 +0200 6e82349a3a1438803ccb76a698de7abe00c790e8 2014-04-19 18:30:52 +0200 9d44889174f4b3b400d65ed72ca1e8fc1b35c6cb 2014-04-19 15:09:23 +0200 f8ff778390820cde2192a6864d4c1e9e18fb4169 2013-11-17 19:36:23 +0100 4b0a829a17d78746cccf58b1af5f8bca71e1718f 2013-11-17 19:25:24 +0100 eb34a0a07d6d509598d091f89885486b72e0e1d3 2013-11-17 19:21:54 +0100 4783d8278b240be0aa11765f99b06e11ce45f297 2013-11-17 19:21:43 +0100 072a73571d961e8c9ce18578153731a76de4f9da 2013-11-17 19:15:14 +0100 e7fe341e3f1aaf329f01d799246f1a421dea2f4e 2013-11-17 19:13:36 +0100 15c269c2424f2e4529ef9eae6b70396e56966586 2013-11-17 19:10:02 +0100 a00c35456e1058b639398e049b17f0871c1fe72b 2013-11-03 13:56:35 +0100 7791454e5553859774f3d73c8c11af1601d8514d 2013-11-03 13:51:15 +0100 af8edba0b52dede4f53b4802a9ece4d09ca78c73 2013-11-03 13:29:28 +0100 3861d56f1b295d610b15f5ab0cd7db01a728b6ea 2013-11-03 13:28:55 +0100 9d43b25ca4f1ef6ca88380d4e613e2a614bcc74c 2013-11-03 13:26:12 +0100 aba09c99a4205164f9ccd97b4f346353c27abe8a 2013-11-03 13:22:15 +0100 c46ca52124597de315778707e5b6a01c1cbd6792 2013-11-03 11:05:41 +0100 f798ea67901a96ee40c2c9aff370356fe44be78a 2013-11-03 10:49:52 +0100 7b88b0ca311eed8c02082a11dbc6aa8569aba915 2013-11-03 10:43:07 +0100 236791277fada4ac4e6c98a1c16b5b9cafad6d7c 2013-11-03 10:36:47 +0100 3d1d330ab05e4016ea9bf262d9414cbf871beb72 2013-11-03 10:35:41 +0100 b29eca7aad79b0106825e5ff2f96a6a33dac32cf 2013-11-03 10:18:08 +0100 d16e39a9b3516de2f71d05ea6aa0f66acb363a86 2013-11-03 10:13:29 +0100 defd9fc5cd71404012f45eae1eff17bee869bf55 2013-10-20 22:24:59 +0200 3f1f8e4f4f1d534fc5f3b28bce66a7fa66697ce8 2013-10-20 21:54:49 +0200 91f86bccc5a3a3a77befb745f28ed724c3eab6c1 2013-10-20 21:42:14 +0200 8eda04475b2f8ada2ebe41ccd8db23365d709049 2013-10-20 21:40:20 +0200 65b33e7b428774f590692e67065f958e3c973dd5 2013-10-20 20:19:57 +0200 22d7f6132882db139aea3396333f1d43fb369cf4 2013-10-20 20:17:36 +0200 b17b6f2108db2c8919aba7ee542017aae0f308eb 2013-10-20 20:14:26 +0200 c153986e62ac231a683e01e4276a070ac17597b0 2013-10-20 20:01:14 +0200 d7e5549508b7fb6cde328c3c648078a307ba175b 2013-10-20 19:36:19 +0200 52a06a6acf5fc6498f5bc12e5f38f54bf033a9eb 2013-10-20 19:34:33 +0200 8523e31675278fb1cad363bc4d54c2de25517101 2013-10-12 14:29:08 +0200 920d7379a36ad177b1fed4a3d3b0e7e8f179dad7 2013-10-12 08:05:15 +0200 16a38ffbf76429195e8a0ac1ace43f8250447a52 2013-10-12 07:57:27 +0200 333ba779b015a7bfe6d164a61b6860c49c268da5 2013-10-11 17:53:51 +0200 6b36c4505547eb5b1f5423ec10fb1cd09266579d 2013-10-05 17:22:35 +0200 233234b92f4095e90a243f2abc4a81c9c7a73195 2013-10-05 14:56:18 +0200 a1c9d7fd439fe8b87f45fdbb91b11b27ae531355 2013-10-05 14:27:16 +0200 77dd3d06f44f6463706fb1ef28ff0c115f8479a2 2013-10-05 13:54:34 +0200 acbe574532a2e4ba4c8f3fa7f72dff3bf5d37dde 2013-10-05 13:53:46 +0200 28d74bc43bfd3c0ddc9f3f8ec882e5d407755250 2013-10-05 13:53:12 +0200 7918d8747e7aa0f78f1458d3c4bfe66fe971137e 2013-10-05 13:46:24 +0200 84b402c839040651236f86cb1c84637db997f4f0 2013-10-05 13:40:07 +0200 51343766217682daa4145ec8f12fc4f35cf53ddd 2013-10-05 13:39:42 +0200 4f26ad9b542774ad62b481d2a0e321b3305779f4 2013-10-05 12:35:28 +0200 a8342c4c1e6ae35a7076b850c4634934dbf9b8a2 2013-10-05 12:32:39 +0200 e01ef87c1d6ec2f32279fca649947567c39635bb 2013-10-05 08:31:38 +0200 a98861420c5fee2a02d9fdddb316c3835ba51838 2013-10-04 22:30:13 +0200 470345d921fd039000ed292f50df611f6b0a75cb 2013-10-04 21:52:41 +0200 d0c0815736f6017fd7b23cfc49b89be8c0a96801 2013-10-04 00:01:34 +0200 00c663e0a4efe60d891cf44b7f805e58e1887bfd 2013-10-03 23:47:44 +0200 c1fb78bf6d5379cd266468f2558accd6e48502ed 2013-09-29 19:41:24 +0200 621ceaae65ef9ccd7461719aa3484f37f7045f08 2013-09-29 19:40:39 +0200 63767429438114e93347cc8a62d612552d394c47 2013-09-29 19:02:38 +0200 a339fb709eb36ecb5175c62ed90eabda1b237c1c 2013-09-29 19:01:29 +0200 73a3f4fe94d444e29833ebfec6772113996b3009 2013-09-29 18:39:13 +0200 f6d4bb9dad06db173b86412f00abfc853a1ee129 2013-09-29 18:38:28 +0200 87d64526a322dbdae2e5c56a3fb7d6c68be77079 2013-09-28 21:33:25 +0200 642dd0b5318bad798c032d3fd65034a019f7c607 2013-09-28 21:14:13 +0200 a229eabbb7ef2940484bcaf47a574f826a3fecc7 2013-09-28 21:10:24 +0200 493fd61694682d2e7a0acd0548d79f6099ed93f1 2013-09-28 20:57:10 +0200 205f1758dcf974ba0bb6fdb80d0f301e2a41a852 2013-09-28 20:55:23 +0200 9ad6461678138bb788497f4f1bc8a98ce7fa83f6 2013-09-28 20:55:00 +0200 965e19df9f764229a9c9695cdd7b6602cc2b4df1 2013-09-28 20:34:40 +0200 8c240eb2acb35e922e8a043c3a6a7a4a1a60160c 2013-09-28 20:09:59 +0200 34365ec797701f7f6a0271c6e8693ceae0787cfa 2013-09-28 14:59:27 +0200 ef4f17b7ab923b33985de50bfd5e718ec496dafa 2013-09-28 14:48:33 +0200 172e52872e47daec3c888c12ef893b7aff111c37 2013-09-28 14:09:55 +0200 57fdaf1a14ec2eba0c4580753b458419eb602761 2013-09-28 14:05:33 +0200 263ff8525b1b4d9c60ed647723f6d004e35853da 2013-09-28 10:03:16 +0200 2a2c20239cfcb8941adfa3d1c77ee69b046cf1ec 2013-09-28 10:02:06 +0200 ba1f3358738cdda420be82e51d68a9dfd5f5516d 2013-09-15 13:35:26 +0200 6b7fd56c05402901c0e0ec91cdf5b318a59356ac 2013-08-17 13:27:26 +0200 a197f04aeaaa55174c51af234e4d6fefa3f6de6e 2013-08-17 13:26:06 +0200 4aa794c0caba6e130524ed0e16349238a1f83304 2013-08-17 13:14:26 +0200 d299785a0e5c4f90193b4256547ed574f2504623 2013-08-17 11:12:32 +0200 f5b6d8aa0f0b8d0fd92389e840aca9cc2ebf33bb 2013-08-17 07:51:23 +0200 60ea9c49399e9968a9b7b76942c2fd9dbe239519 2013-08-17 07:45:40 +0200 75723c27527a602999af9442cfed96e798da6dd0 2013-08-17 07:20:50 +0200 10f3e23a7f71d21177e71037ad71819d6f26f25c 2013-08-11 14:16:02 +0200 b9bb3382f29804c5eadcd57dd62470877aaa33ab 2013-08-11 14:13:16 +0200 8af30d2fe61c8aa38958d0a56de52a70be186514 2013-08-11 14:09:38 +0200 cc4195ca6092c050097c6f40406cb52a0182a01f 2013-08-11 14:05:58 +0200 6541b5cfb54369dcc8d98cfbb8beb69f325afb08 2013-08-11 14:01:16 +0200 c30a391cdfd31b5f3a821c455a872d9c924316db 2013-08-11 13:49:12 +0200 e0d1d3eb29ab184c1753bfea7c3ea987783f9870 2013-08-11 13:48:41 +0200 4d6d482652818597838ffc8ac4980c96e4592123 2013-08-10 19:37:45 +0200 e5e2c2b08907562a98e449949860d83202551fd4 2013-08-10 19:33:47 +0200 a0fc141841fa578c5dbe4a4a23518fc092b0bb31 2013-08-10 19:31:48 +0200 858f68a30b9b050fc4218dd36f17aaf108abcb86 2013-08-10 19:27:22 +0200 cdb90f5ff95af99499df94e9572a45022b704e9d 2013-08-10 19:24:55 +0200 d56ad5fcaf27bfcfe5ca0dd0299564749a39e7ef 2013-08-10 19:24:04 +0200 90f4d4389c2d0119d59356f7af94555da9399119 2013-08-10 19:17:05 +0200 5d77497247935698351315d0da891b0bd388137e 2013-08-10 19:15:58 +0200 03edbeac0ea2ba1a7411246ef07697ff3a8dc574 2013-08-10 19:12:57 +0200 6f36a0c154b97bd0be71fb3fb9f02e0a94a06a4f 2013-08-10 19:11:36 +0200 2dfbe62db5dbbc8d415fbfc3baff3f9b6bbbbd1a 2013-08-10 19:05:39 +0200 512d14e785600587ef7c8d0321f349c85b0b3388 2013-08-10 18:56:47 +0200 f92e0ffc81e42ce4dcd96e5d2277c8bd097bbd94 2013-08-10 18:41:25 +0200 a717444115dd15e962a97ca257c2558455cfd14e 2013-08-10 18:37:56 +0200 ddefec8a79717603cb58012882afb5c23ba8271e 2013-08-10 18:36:43 +0200 edb591b9fd2b2b57272467b44861711dcc33bd0c 2013-08-10 18:36:05 +0200 0c92875ff134117db001df44f4a4de58dac11155 2013-08-10 14:41:08 +0200 f83288ed57fd5c3a09378f1aa419faabc83012aa 2013-08-10 14:40:08 +0200 87341a32675eb2ceae81ee32e2e603a6d9a661ad 2013-08-10 14:33:45 +0200 2f5cdc44a364168a5487c5278c5a593f2b6d799b 2013-08-10 14:32:27 +0200 511e0b7fd2f2950a03b49a0c0468550a83a3369c 2013-08-10 14:13:02 +0200 5a7d34842fcaafb318a73ba77c8523677842c329 2013-08-10 14:02:25 +0200 53f2c2989c4cd2bd057d04b78fc60db1e92392f6 2013-08-10 14:00:24 +0200 a781fb556bd32bbfd887048e2a5bd284810ff8f8 2013-08-10 13:59:35 +0200 1256719fba0cb6de27ed4f1e1b98e3b1be15e932 2013-08-10 11:48:05 +0200 3ee39f39e6990588f254429889ae559a9cec24f7 2013-08-09 22:49:18 +0200 895485895a6d7ada1111880542bf0c42e695b7e1 2013-08-09 22:42:45 +0200 ea8cd656340ed3e65f16fec5d10a7d5c8e1d8ce2 2013-08-09 22:42:20 +0200 007c339ec499a3b2f14037a7ab47add671e270a8 2013-08-09 22:40:58 +0200 feaf5beeb81caef681fa244da234552ee60875ce 2013-08-09 22:40:15 +0200 09c125c0e49797e7b6d60df1460d5f4feb7f53fd 2013-08-09 22:38:57 +0200 9b3d1729bf236a1b8d1afb8994c3f24acdc0bdd6 2013-08-09 22:32:00 +0200 c5990a35ac00c7eedc83df11739a4ec5a084e4e5 2013-08-09 22:31:07 +0200 3db7bf1391583febf19f7c2c68fcbe0823647cf4 2013-08-09 22:25:14 +0200 a95de681fc6aaa7cf285a54a0ee5a30566f17a5f 2013-08-09 22:23:57 +0200 085f86aa90de7a04d0a8c7dddf69d969ff46c769 2013-08-09 22:17:03 +0200 ea1cb67267afb92ee2026cdcaad447414c974ee0 2013-08-09 22:16:10 +0200 275e841b1f87a3869088ef660f9a7c9143b4cccc 2013-08-09 22:15:06 +0200 bc0549880663b243a377391ccf4e2993b1eeb185 2013-08-09 21:58:36 +0200 8905f01326dc81c8d896c3b9e3ac061e706cf1fa 2013-08-09 21:57:55 +0200 466c5dcbaa3d62cdb2ba61b422740bff65a3da4d 2013-08-09 21:55:55 +0200 f8d23036c955aa455ecdd73b4a4fb45356878580 2013-08-09 10:00:30 +0200 c6ea981ba302005358f866cd01c59ea73ac229a0 2013-08-05 07:03:11 +0200 28ecf0816cccc2a888fb23704ddadedf583334d4 2013-08-05 06:53:15 +0200 441729150af2e632021a3c74c10d6e2325c9dfbb 2013-08-05 06:34:34 +0200 e1a51d19e6dee9d8d5b002de5de0616f43aa6876 2013-08-05 06:33:25 +0200 aac0ef7a30b8e703443c390e6be87a16c89024fc 2013-08-05 06:26:09 +0200 4dd29f5fc80ffb9138392e47a217d5f2c77904a9 2013-08-05 06:20:57 +0200 0f03cd20a83c3ed6f9fa03a7df8a4c2da66d80c2 2013-08-05 06:20:35 +0200 59f96a21614e7af13197b504c085e0825afea289 2013-08-05 06:19:04 +0200 4a555f3a0bebb6f9ff6b7b056cd6e177d7bba8f1 2013-08-04 20:09:18 +0200 bcadc5b1504f34e82f51e69853ee34e63bab0a24 2013-08-04 17:29:02 +0200 de34af5b0c2f6fe54c70a72b99a398db4ab0b449 2013-08-04 16:20:31 +0200 8d4ee79423cc01777f2ed6a846d82bef20ff2413 2013-08-04 14:40:50 +0200 0d6881019b086f88bb9291e748dad19f9fd570b3 2013-08-04 14:38:13 +0200 38fe6d580314bcffe1479f0c28c65e9454903f9e 2013-08-04 14:35:18 +0200 455584a90a15b46f78a00f7be7237dbe850ff6bd 2013-08-04 14:20:35 +0200 6206bba7423ed988160020b1a57dd1f3a333cd66 2013-08-04 14:20:15 +0200 47ad6411aa1b746c06b40431f9e37db80f76c9b8 2013-08-04 13:42:38 +0200 1d93da752e9dc3b0ff32aa0a39d1552349ddd514 2013-08-04 13:33:28 +0200 bd741c9b6153b8a450f8bb2f582499cfa955105b 2013-08-04 13:33:04 +0200 6e77caf2074913804811ebf424b3be9efb5cd3ff 2013-08-04 13:30:13 +0200 a21e319d7ef94ce89f7bda49780d8ef8cc5b2149 2013-08-04 13:29:16 +0200 074f23d6892c659695237375145dca739361596f 2013-08-04 09:07:12 +0200 87abaa0118ca94962f38b8f7cca5523a74a75ce3 2013-08-04 09:04:13 +0200 98bad2eafa6a261e8aca9cef1617556f068911a9 2013-08-04 09:03:42 +0200 a80b825469e466c9110c8df17e3c5cb2ff7865db 2013-08-04 00:02:54 +0200 4a191c2a95b9e9cbe94c922fde453c15e0da8ff7 2013-08-04 00:01:42 +0200 7751d4eedc7982384cb6782b71508f1d5d162c5c 2013-08-03 23:54:23 +0200 91b619d1161d00f688deef0145a74e3330723518 2013-08-03 23:53:20 +0200 31e5c7822c7eeb444928b70726c9498e3cd1b695 2013-08-03 23:51:54 +0200 466bef64179c0b5405d58aa9b24077a7d989dfb4 2013-08-03 23:46:24 +0200 6d9cf7b19a696f42ddbac2345571455cf8293ea0 2013-08-03 23:41:53 +0200 d22cad74b9a83f2c44619ea294884e5ec0acf8fd 2013-08-03 22:12:37 +0200 080bb1258eaab647e14418ea9b347eff688a2cf6 2013-08-03 21:36:22 +0200 01b85f6a12689c5502023653a87416ee921690de 2013-08-03 21:32:58 +0200 ebf96932d4aa0189005cf1d78f74850dbc30959e 2013-08-03 21:25:34 +0200 4c716b8a4fb943188445fc100b1f3d330708b7ae 2013-08-03 21:22:48 +0200 1af9e71969a8dc87903ad161f0d4cdd73edbb449 2013-08-03 18:19:41 +0200 9bd4e7a16db1bf60acb271df3c280c5af71b6559 2013-08-03 16:37:13 +0200 84ef913d35888b8b815fa606320a8c315e601845 2013-08-03 16:35:54 +0200 24ed43ecb7a0f47eef0fb80455e2b5eff63380ba 2013-08-03 16:34:33 +0200 d92e65eca7a4c5839eed244835935aa27d5ba6d2 2013-08-03 16:32:43 +0200 82ff1e349236de1266b87513c0fc5f0c5058272c 2013-08-03 16:30:54 +0200 fe74a6e8bd8e4c69731efa16e53d6ca7947ae5d8 2013-08-03 16:20:01 +0200 cf70e2390a495478be0b25c2a38a7e77d6ada21d 2013-08-03 15:57:55 +0200 912fbf0e444d5e266ecda1c9f3ac30501bbc5a73 2013-08-03 15:06:26 +0200 7f318aa1500da81877e7f635a42dfcc125851716 2013-08-03 13:26:29 +0200 f56ba409991b03815cb9750933f1c76f2c265db2 2013-08-03 13:09:38 +0200 ee4d997b3287c31b9a401bca5eabb105009eed52 2013-08-03 13:07:00 +0200 544d87dd313387558f12f85e49d688c38b2f6f46 2013-08-03 12:57:31 +0200 1c3ce765195c588efe09897717f675d5e96d6837 2013-08-02 23:34:17 +0200 ad040098c3f60ee53de29ae237d062006a74341d 2013-07-29 10:06:22 +0200 caa398845bd031893a42e8f5a3057b3cd9694165 2013-07-29 07:05:52 +0200 858550fef89749cb7f835ba469b0cd73f1f30878 2013-07-29 07:03:57 +0200 f03652cab159f53b6b729c3212e9f3293c02191e 2013-07-29 06:56:04 +0200 62c040e5772f99a23b10c9f466a83fca2d557e16 2013-07-29 06:54:10 +0200 b5354cad477009a5fae0ea63541d37c2892b9a91 2013-07-29 06:50:17 +0200 1b7fd6431f019946048d92f8f9543239c9fc1c3c 2013-07-29 06:44:38 +0200 4f651581662c39fc32e7b7de549208f3290df655 2013-07-28 20:14:39 +0200 9949b7fa038e65895902495547685b98cdebfbd0 2013-07-28 20:00:55 +0200 845deb1380ad77d9d8bb7ccb138ebd60758ebf24 2013-07-28 19:55:58 +0200 ab8fabd0ca65f9e5bc789908c8e53d4a5d325c2f 2013-07-28 19:51:04 +0200 c2c1be1206c13040b9dfb0f78d6f6f1be96c7f4d 2013-07-28 19:48:46 +0200 a2cb7858c20c9224ed0640fb7ff94b443b442adb 2013-07-28 19:48:19 +0200 4776bda3e5db9771c706087aebf4f0ab427261b7 2013-07-28 19:45:22 +0200 21db0d42c3e6fa294e362683a1dc702be87a990f 2013-07-28 19:42:28 +0200 13b3ce5d06a4ebb08733d252d78a2ba6bdc8516e 2013-07-28 19:40:00 +0200 a5d848de704024c4882ee86a8e48c8fa589c67a3 2013-07-28 19:37:41 +0200 6062277d8c309ba858cf84e351c7967c608744cb 2013-07-28 16:10:49 +0200 25228b207849d4768f8f716424c65c42857bf7ad 2013-07-28 16:06:29 +0200 8201e537b17039b290ee11e6a017518a5474b0ae 2013-07-28 16:04:56 +0200 8bc75b91d1ee1f94b974ed250863816fbc63de5c 2013-07-28 13:40:55 +0200 b3b2bc8e5e448e8ab123cad5d3eb5630d0e782ae 2013-07-28 13:33:05 +0200 36ae2e9c65fef2e9844f47a797c57fcd05a3b6e3 2013-07-28 13:31:14 +0200 95ee31f53fd0d982918c941744f6738a38e33625 2013-07-28 13:27:40 +0200 831382d92a2d0dbf04debed0e0e1f85b33823e6c 2013-07-28 13:26:55 +0200 330e3c36aa0c2658ba73d9f2aedf990fe2702c2d 2013-07-28 13:09:35 +0200 162a67d7da8902696fd6461c1f19c8a28cdb24b8 2013-07-28 13:00:12 +0200 902a6b7c281e99d568030ea67a1c346511e8f815 2013-07-28 12:32:35 +0200 ec941369fe8db51c13484e76de9858feb73fce60 2013-07-28 12:21:44 +0200 ceaf2aecbeb675ed3ba7db758cedc3dfb79709e8 2013-07-28 12:09:32 +0200 5da7dcc5188250bc541ea1bb01ad04404ec3c280 2013-07-28 11:55:05 +0200 0b990d69fc65a32f33319152d71d20c96a783df0 2013-07-28 11:39:39 +0200 5bb4009de02dcb8c36710cc77a50c7383eac03e4 2013-07-28 11:28:21 +0200 79b53355202be612a1e60f9649a7837501a37454 2013-07-28 11:22:36 +0200 37b53fae3dacec906417528fdb89a708dfb22652 2013-07-28 11:21:26 +0200 71461cd66b44fe8954121999b9bc2f50fb73939e 2013-07-28 11:15:32 +0200 2193bd99bfe8c826abcc6300c9ca57c9bc524ab0 2013-07-28 11:09:54 +0200 ee4cae69f0b3de6b172b6c7b4f21f08b90695bcd 2013-07-28 10:03:54 +0200 67f70218bf101238fc99244f996480d1b1117342 2013-07-28 09:54:28 +0200 f0f99d08ca4a1e36e8778a28b23b5f1d6bd0054a 2013-07-28 09:11:26 +0200 f52a4595bb282db936d75625a7a6ac13ae150a3a 2013-07-27 20:55:44 +0200 268a1b38d5d61b71989815b00f93342a78300fdd 2013-07-27 20:33:51 +0200 573e91b7a76cbdb037d7ca82f553e9e0bf5f8c32 2013-07-27 20:18:17 +0200 474bef8aec9d56ce8e6b077143e5425c3976c800 2013-07-27 20:13:54 +0200 3194e86fdd5c495a9f8c53e52953da7fd115c62f 2013-07-27 18:40:26 +0200 27fda472d8bba9c503c881def8911953cd3048e4 2013-07-27 17:37:59 +0200 25af8d9b921e88224af07f583dd564d44f26c43c 2013-07-27 17:33:41 +0200 2424b5cd7838e39b1cbb0a6ded688e897e8ca993 2013-07-27 17:26:18 +0200 882dbeef3ce7d25ec64a15f5327faa0fd701f036 2013-07-27 17:10:44 +0200 ddafaa19d3963bfd9d4b3b3a0b2147c5a64be769 2013-07-27 16:30:33 +0200 02bb387ba13210e62f37752c05898d2580b3253e 2013-07-27 16:05:25 +0200 00ec8110ab34b6b335bc8deedba7aa02accbca46 2013-07-27 16:01:55 +0200 d548db8e69f81798897657f4501dccce8abfb84d 2013-07-27 15:39:31 +0200 4b44f3aaf220454a3fbd96833c12ed27d0181ad1 2013-07-27 15:33:03 +0200 bd62ca902ed63a7b324035ce3b5a7d74bf8eefe6 2013-07-27 14:56:46 +0200 df6edc8c9bf62ed075d35e66118d39c95043d6d5 2013-07-27 14:36:33 +0200 dc0733466e9ad5c83ef4f52d50f7b35cb6465f00 2013-07-27 14:32:08 +0200 995030100b1079c231551015a5ad30d0b03e00b3 2013-07-07 21:44:07 +0200 79f1bcc7c83a866bbb84265c585bb125412ae1fa 2013-07-07 21:29:35 +0200 abf6d2e720ee6ab1f7c29eb3540bb8c1c93f1f91 2013-07-07 21:21:59 +0200 8962440d80caaf08e7252d1a5d7eff24772af19c 2013-07-07 20:55:13 +0200 82997771f284951e1ccb5e920a9aa7d1eaba1ebc 2013-07-07 20:37:56 +0200 e33a125156000b373acd166ee5cb546ad8f5f7c3 2013-07-07 18:33:52 +0200 1f6cd420d24555d2b09d74f9a03c17dd550e9c16 2013-05-20 19:21:03 +0200 94068225a817d96a5aad4693dd1492587bddcda7 2013-05-20 19:20:26 +0200 4f4822ace8bcd81d81e507f6a893dbf7ac3a6b60 2013-05-20 19:19:32 +0200 76febcae60343ee6aeac531619f3c8e366e006ee 2013-05-20 19:19:03 +0200 8e783c16aabe777742fb5e5736796b988f91b847 2013-05-20 19:07:45 +0200 1e6f78ec2ec3c8d9e5ea48730797d72a14259944 2013-05-20 19:06:51 +0200 270e96649e032429ad8dfe9c1588d0a841328f10 2013-05-20 19:03:08 +0200 74645670366d4e4b10c5f1f85ea218b96cc3b12e 2013-05-20 18:59:24 +0200 550d9aa72860f5b07479c29b59f91adc694c39ae 2013-05-11 23:08:39 +0200 2b25b09f7575a4f40299bb2287cf46d708a52592 2013-05-11 22:14:46 +0200 7a68758eca4b055cf8cd215907aa8d2afa6ff648 2013-04-15 07:04:29 +0200 2d80ff6ff84f256b6677e946b92009e6608cc457 2013-04-15 02:32:29 +0200 e64c9174e0943eccb8e415462dd7dbfb4f9c6698 2013-04-14 21:51:07 +0200 26c02d135501ab93dbe3d8d9ad13fe2dbae5bfb1 2013-04-14 21:39:07 +0200 204acb448e041c48ab8d93206db1c28361daf0a9 2013-04-01 17:57:43 +0200 619a05c768beaa36c83628872fae3b5b5146f011 2013-04-01 17:57:03 +0200 d9dd88896c47a6bba2b2724da5d3d861e752e061 2013-04-01 17:54:30 +0200 0b2dd3293484666891d9705a15016b7b3ac18399 2013-04-01 15:47:11 +0200 3cbe256a36ef58000fd84da08823f61d7c9d5de7 2013-04-01 15:37:43 +0200 9902972c1dbfc79e088fa898bbe12d2f41b3a6c0 2013-04-01 15:37:01 +0200 14f5a485635c5e7326d18e12492bde6a5d0830b7 2013-04-01 15:36:35 +0200 915cca3e8523338a113c87e7c8592793e8abfdf3 2013-04-01 15:07:54 +0200 83454865d8b8284849a421d29608b05e6f991b3c 2013-04-01 14:52:05 +0200 2431977ff7537e417887e9aabe56e6f15b8540bb 2013-04-01 14:44:44 +0200 fcd06e442601b8effa1d41025b8b59aa6977311d 2013-04-01 14:36:20 +0200 b692c848b5289abaaeccca88a650950272ec168c 2013-04-01 13:12:26 +0200 52a84290b63240578acd2219551ed8efd90b22d2 2013-04-01 13:09:35 +0200 27a977d2adf069a947e2f568d9aafd28936e8e1c 2013-04-01 13:09:09 +0200 ce1dd15928a6ce072422a2c9a7d7e5cf8280b00f 2013-04-01 13:08:03 +0200 391cf8bd432c952f532bb239f77d7fa586dabf5e 2013-04-01 09:26:00 +0200 dab84886a610dd12598497706d514440479de295 2013-04-01 09:23:45 +0200 ed538c7ddaf9bc5ba1a1b41cc042cb944b6f7b44 2013-03-31 19:53:17 +0200 ca831077ee880d02595b4a5514fdfefd76d7321e 2013-03-31 19:52:36 +0200 e535d1e695bb42050a47d32c880224ff9213b235 2013-03-31 19:50:51 +0200 0eba346a99e510b933306df8d86e2080081706ee 2013-03-31 18:49:04 +0200 f9907a6ac165260ef072961750678e901a81f585 2013-03-31 17:09:22 +0200 10f060b314b3a95e3aec8aa7081eb22e22373586 2013-03-31 17:01:47 +0200 f1758ba150878742f607996487930ac887dbe846 2013-03-31 16:57:33 +0200 68e188506dfc1657ced4619b1a6cee1029c608ac 2013-03-31 16:57:11 +0200 d881134d56be5db4af40760d0106d68516911963 2013-03-31 16:50:12 +0200 798c6f9a368c6ec2332d0f47a0b9fc8bea5b9671 2013-03-31 13:16:00 +0200 155bd636297f783029e5860199a4f2e17d2b24e0 2013-03-31 11:01:29 +0200 3f2a93157688da0add95ce4e7c3722829e765f43 2013-03-31 00:27:53 +0100 04acdc17fd703c52b59736d294156ff27676426d 2013-03-31 00:24:59 +0100 381efdb168209c6ccebc76004125b7e2e762e9a5 2013-03-30 23:31:32 +0100 29b0cda2b042bfbb266cf3f250c43ae00cb0ff7c 2013-03-30 23:27:29 +0100 9510803a64b5c4fa5a04fb03828abbd78beb1089 2013-03-30 23:17:38 +0100 6f3cd8c0203df26cfa5e39a6347d6fe6caa25f70 2013-03-30 22:38:51 +0100 a0759cf884c89cadcda18a740256493334428819 2013-03-30 22:33:16 +0100 46a745d8cf24d5517ca3c55c21804926c657aac8 2013-03-30 20:17:02 +0100 a62823dbff88e5d57694344da1f880269d181174 2013-03-30 19:57:23 +0100 d953d12ea592ab4f15e8632bada5f1575b3450b3 2013-03-30 19:44:53 +0100 08c3d37b6a5f41e77f31faea97efe208c95a9c25 2013-03-30 19:39:16 +0100 5b840cc6d2f6ea67478f1a22f98fd2b29b4f6ff9 2013-03-30 10:31:37 +0100 65debd76143495a0c8363878e508dd8a49fb8e66 2013-03-30 10:24:03 +0100 cb0671073cbdcd4860ab242af31993a04aac2cea 2013-03-30 10:23:30 +0100 80ac4f31116bc156e6b7fed6c7e9397908b50b13 2013-03-30 09:46:53 +0100 4a0e7b06df3588a8fc09f6efa264e54786985db9 2013-03-30 09:36:39 +0100 eeb97c846556856c6200e7d5d83ed0fe4bf19d09 2013-03-30 09:03:55 +0100 e7e19b4044c95d5bf0345c84b49f588b775376b1 2013-03-30 08:27:07 +0100 9c9b4b8039ec8e7457bce9aaae47d6c9f1a3f535 2013-03-30 08:24:04 +0100 5e5f0234da08ef186b026492c85230320673c912 2013-03-30 08:22:59 +0100 70fbddd97991710de5675dc61b1df3ee7d8d5fc9 2013-03-30 08:22:35 +0100 ec22ffd24fd4506f01caebb5e55bacabe7376f15 2013-03-30 08:17:19 +0100 1ef0c4ae10a6ad686fcbe374bf3c6f766a52a5ab 2013-03-29 22:30:50 +0100 beb33039ab617a87867e9285834e5e19b7b917ef 2013-03-29 22:30:30 +0100 053956fc506cffff2a0dfe865117fa639659cca3 2013-03-29 21:29:20 +0100 41a53d30c4cbdad0e4d2914499ef15d88dcea6c5 2013-03-29 20:12:05 +0100 50c6f742cb0958d81cd9cfaa095cd8f12d91e87f 2013-03-29 20:10:56 +0100 7b006ef24226a5d13bffbcb29ddd557935ebfa8a 2012-12-29 13:53:40 +0100 503c91e442e7217200c488e1d836b55745773bbe 2012-12-29 13:51:52 +0100 d8f61bf4d84f8a1662a02c84b96c6390323566f3 2012-12-29 13:42:17 +0100 d89ecdd1eb8bceafa44d3540b57de1bd361f4cbb 2012-12-29 13:37:05 +0100 72be93a76a34b23a6a0d3b3cecc8685f3a006c86 2012-12-29 13:26:18 +0100 1eca56d5176da2f9e251ab7abcbdf3e4a43ef3cc 2012-12-29 13:16:23 +0100 7a12815a7f85f9ba29a93674149841721df4a61d 2012-12-29 12:55:09 +0100 5bb1fd9abef833a4ff3233c70b033762a5244c63 2012-12-29 12:53:55 +0100 aa9d05931754a057e9c20405894b74a8e674ce9e 2012-12-29 12:48:35 +0100 615c41ddccb4bbd7846b937a319a22462b8d6417 2012-12-29 12:44:53 +0100 5e2f79b71e61bc3049c59e04ecb3003d8dded21c 2012-12-29 12:28:53 +0100 ec8f89d0df0a12e8f548276c8832062a89cf227a 2012-12-29 12:26:53 +0100 748cf00ca7464976039a7f34c49356d26c067aeb 2012-12-29 12:11:28 +0100 839cc56be942bce2bd9dc9991030ea230d2d208d 2012-12-29 11:55:28 +0100 57d342190d2e962a7b3530d088d0ecd7815b9312 2012-12-28 18:25:30 +0100 6f7b16432a3866196add82d329ff00199aa6774a 2012-12-28 18:10:41 +0100 09f76157a11a6686ef4295b4cdf9dea61ba64ac1 2012-12-28 16:50:10 +0100 638be2231c9bf63f09f27154678fcaab83091c15 2012-12-28 14:10:14 +0100 6d7ff24a9288a5fa752b4a69ecf925305d4bc107 2012-12-28 14:08:54 +0100 e159ad18e35bd07f9813c47ea25fe4243e3f0e92 2012-12-28 13:54:54 +0100 8f572d179bb9399797ddd79c526b1140f42887b5 2012-12-28 13:39:29 +0100 74bde432d0ec5a1d8b26069e341c5c554f65a1a4 2012-12-28 13:39:09 +0100 dad9ba62e4802f66458a07c725cbbfd2d725a49e 2012-12-28 13:31:38 +0100 6f766348d7e3cd37d0b72da3a769806cd2ac6c7a 2012-12-28 13:20:38 +0100 533f968af9aa43e2af85c64ce839a56aeb730747 2012-12-28 12:35:57 +0100 d4e7c0a9c974f336937729ce47fade4212ad2f1a 2012-12-28 12:15:47 +0100 a650995c4c94fffed90bb740f099fe9014bbda41 2012-12-28 12:06:19 +0100 1a34a82b258f2b6cec3a1225d14966009e05e05a 2012-12-28 11:40:06 +0100 30f2f124324b544d4d95db407c4418701658b689 2012-12-28 11:33:42 +0100 f5d224a3097bab2d591d75b02e68cfdc00be6d0b 2012-12-28 11:25:44 +0100 3bdcd99a54de03c7c3910ff9b84821169fb939e1 2012-12-28 11:16:37 +0100 31a1b5455047989d87dfa208eafab75da4a2d202 2012-12-28 11:03:01 +0100 efd6e56cc4be11c8f31999f19fcaf9a2fe827514 2012-12-27 22:42:58 +0100 93a7aab450aa5c5a4faa6d4817bea16af567a93b 2012-12-27 22:25:35 +0100 eca5938250d0662029c816163f2a971a6e27742a 2012-12-27 21:04:21 +0100 d287c561138689f6a46e3bfe733e81249d58fe7b 2012-12-27 19:51:57 +0100 53ccd0913f97de463fc72db0dd65fc39ebbaf455 2012-08-12 09:17:58 +0200 f0a71d9d9ac3f1e74b8b04a5c8270fe469db4f09 2012-07-14 13:16:50 +0200 893ecbbaeb37253a933cdf2bd6ebaaa3e2546676 2012-07-14 13:10:32 +0200 5613bb8f6997ce72a1544ef24e58ddc1b21af995 2012-07-14 13:08:38 +0200 22fd94a53ca3ae0c3726aa364b390305fea53d43 2012-07-14 13:04:42 +0200 c6ba56b0274a36678e831c49eacd5ba7fbbbf687 2012-07-14 13:03:46 +0200 96777d037bbb31c4535e13eeca60957413bfe00e 2012-07-14 13:03:13 +0200 ae2d56944cb35b5d41fdab4f65065e19f3ff123c 2012-07-14 13:00:41 +0200 c44b61aebac5e45392dc75b6f934f4a1ebf99bbd 2012-07-14 12:58:42 +0200 532f9c0abe36b85a765b06344218ff4ece9017d4 2012-07-11 22:24:22 +0200 89307e7980954e96465a13801cb4b6e631dbb0ef 2012-07-08 10:13:05 +0200 3d95ea5f1e1cadeffa8628c4f8754f81b481ad10 2012-07-08 10:03:57 +0200 2aa1895f6d3a705b1583d2262ce19b8e61f59644 2012-07-08 10:00:13 +0200 0374889861b3c80a27c0d57de06fe81aaa7b6d1d 2012-07-08 09:55:20 +0200 35afbf694bb5ea1f5b9e581eb6d275fc9b7e5d14 2012-07-08 09:53:10 +0200 963a016ecfbd4da427b079fb8b679651a5634cc7 2012-07-08 09:52:16 +0200 04b66347d901cc4d41ec8bf659b7829c3b7aeb72 2012-06-07 22:14:50 +0200 c9567d5182acde2380206b7fdd7f4540545c6b5f 2012-06-07 21:49:32 +0200 973d0bcb40a539a73df6a39cd68d3af66609698e 2012-06-07 21:42:54 +0200 a23fbf069b613d0bbdd2627cfd31c41eaaadb6e3 2012-06-07 21:30:04 +0200 f04cc77aec7f8f67c29306b13c6f67568b1e98ef 2012-06-07 21:19:52 +0200 543535bc896854bd2a7abb6b8527a0fb2d8e0c4c 2012-06-07 21:19:00 +0200 0a61089b07969d43469ff4881b58dd2efa6f1c23 2012-06-07 21:14:47 +0200 35bb66bca6a5382f68d7fe5e895117af26dc328d 2012-06-07 21:07:05 +0200 707932c6211fc2a1f06a12102f0e4697244fb44d 2012-06-07 21:05:40 +0200 3b0fce08bd96e93859b8b2fb52cce5f09c6fba46 2012-06-07 21:02:15 +0200 5923b2c8a8ead18c691526f5a810335b49308fa4 2012-06-07 21:00:07 +0200 f7a5990de8b1a7998732e06e5742c797b4ae543c 2012-06-07 20:58:14 +0200 74627f3b724c9f03b12873eafe6bcaf0d6668bba 2012-06-07 20:54:31 +0200 d1d80d3da2e7625b853eabe25a8e51e548bbd630 2012-06-03 15:27:31 +0200 f10d7830c67cc67256f47298ef9b7d9046745b3a 2012-06-03 11:47:02 +0200 46e0727bfd6f0b79d446b2f023e381b0bec3135f 2012-06-03 11:38:12 +0200 4ef94c67b51955daafe9ecd22fc4232b9f80df12 2012-06-03 11:34:13 +0200 fc10c6f09d42d68414bce2d221fcd0fef5ad748a 2012-06-03 11:33:18 +0200 9e1e802b81161909ea67a293e2cff7f89daf6911 2012-06-03 11:08:35 +0200 072104f6ae0ecc324b260739b7c256dfab9bfdca 2012-06-03 10:59:51 +0200 d2e4fca44c9beb6c07edeb7b0a2554d41c18e063 2012-06-03 10:59:02 +0200 1065cffe804c97518172646c8befa3d0451d759e 2012-06-03 10:58:15 +0200 34fde55180be95412c33a5b37bafd5dbe3cba6e1 2012-05-31 13:45:58 +0200 d2dbf33a41c23c981a4b8111bc5d78db4afd7342 2012-05-31 09:24:50 +0200 092f9bd6f7d0408bd5b3105c2778ccc1c1f3e398 2012-05-31 08:54:12 +0200 5eb53d367a7bf6a683bd14607b8fa848999a182a 2012-05-30 14:41:42 +0200 eedc57b42526986def5b5aa4bf9d5019dedc1f28 2012-05-30 14:15:57 +0200 d70661abd9b7376e28cbe1bf4b0fd8aeb5c87626 2012-05-30 13:49:57 +0200 de8409a74382c53b2be7061727ae15a3d5d50bcb 2012-05-30 13:47:50 +0200 e634dcfbf4cf5fcaafc4f321ec11a8954ec6f0c0 2012-05-30 13:39:30 +0200 0c8c7aeddf12dfbc8f23685eac26d23ec4e9f456 2012-05-28 22:54:26 +0200 002060bde30f9a8c4c3f0a502b51ced7c486115a 2011-11-20 10:01:53 +0100 325018bf6786bc8fe81cb011ca5a190259cbe19b 2011-10-12 21:38:15 +0200 54ac55f199a7e8800bf1186a0c148528c52a356f 2011-10-12 21:29:25 +0200 74c90412806df838f3b4cc52e8a60d1e952bca9f 2011-10-12 06:57:50 +0200 4614780201325cf7526f9d4b41e36c218682bb47 2011-11-20 09:58:40 +0100 4e88cafd4845fb977f388c39f5e61ee6c9ec6b0e 2011-11-20 09:47:53 +0100 aa69703148f352837853a4205048f369cbdc1c6d 2011-10-09 22:36:13 +0200 3c8886f4724b2b38591f6bb3635ea2d8864b54f7 2011-10-09 21:10:42 +0200 bbf6efe5999e7260f16c8f5a8779f477d3845783 2011-10-09 21:08:28 +0200 d96c47434a04acc33df284792fa75bd64a5d7c27 2011-10-09 17:07:46 +0200 20111cc15fff05de485d4b5a54de893abfd71da0 2011-10-09 17:01:09 +0200 4248cd9da26abd7e0bb57deb9b0b2d4a4c4a598f 2011-10-09 16:52:53 +0200 3d75745b529c4d633dad7c735678228f97dd8c7a 2011-10-09 16:25:21 +0200 1ed0dd6d52f4506f3b9068d411a9eec8f00065a8 2011-10-09 16:18:29 +0200 b4e65e5b4807497c0ac8d023e893da9c6a5b8dec 2011-10-09 16:02:51 +0200 29753daf6453ed34d8116fed1197a6f3f3a3caaf 2011-10-09 16:01:40 +0200 b08db135bfa1e5c163ad0cc23ba448be75382471 2011-10-09 15:49:57 +0200 1d517fdde0a5b2a3fa1bae4ba33667ab1d7296f1 2011-10-09 15:38:06 +0200 84dff3267bb42e752e49573fbb66efb13a847bd4 2011-10-09 15:29:20 +0200 ec00a6d43782be83ec742c65ab45fb93ef109f01 2011-10-09 15:27:41 +0200 ee8bd48a29c141701d63e67981cbdf5e62ffd80c 2011-10-09 15:01:09 +0200 7ecef07b7ce3033a8c534a598b1668b87b5cf682 2011-10-09 15:00:31 +0200 7d7269e5ea809975074a2ec9123db45b9450f570 2011-10-09 11:13:39 +0200 f32e253c2fe06595f0d9ea79e591ee6788510cf3 2011-10-09 11:10:23 +0200 b74d52b5274c12ec976aeeddf587cd2e85fa0bcc 2011-10-09 10:48:24 +0200 b325f156adbcd2e5555a8430285d6fac22e7daed 2011-10-09 10:47:44 +0200 b4bccbe857df52c14b1d895acb6b67daaceea1ca 2011-10-09 10:28:24 +0200 0307a50583c96bb0c8111e74d4d89352c1bab177 2011-10-08 18:38:01 +0200 efec89351e37b2b428b208ca71f3fd4b71049137 2011-10-08 18:29:37 +0200 f2b4a91964d40f7035902968324f791ade7282f6 2011-10-08 18:17:24 +0200 4a02780f33f85f63c7fb012958b5762309fe7863 2011-10-08 18:15:29 +0200 74ce3fd587d13ea914024a761f43163e8dc177a5 2011-10-08 17:57:00 +0200 17927247eb671ad36e3ea368288e39711b0610fd 2011-10-08 17:39:35 +0200 c1355f5c1ec375912ee423931b1c84ca71f3306d 2011-10-08 17:20:31 +0200 69f36851eeb3821916aeb5986ca65056e90569bd 2011-10-08 17:14:31 +0200 3eff11ee04f7e41b933ff6edfe1eaaf026793365 2011-06-22 11:44:39 +0200 aa51ce6d6d2741aec47515294e1cea38536682eb 2011-05-04 07:44:34 +0200 6afe578bf80dd910614cc5539603a9439565bf32 2011-05-04 07:19:55 +0200 01eac2e2d95cb64e6fea7ad904347690668281e5 2011-05-04 07:17:45 +0200 8d1de01035fb6364ff55a6b366aee9231127ffae 2011-03-12 19:26:59 +0100 6c5544ed54b263a6dc9fd57af8db51268396ce9d 2011-03-12 19:17:01 +0100 65def5a0c4e44b154e6a01de833a58a3e452f204 2011-02-19 15:56:43 +0100 2cdaacb588604a437f144f3555c8cbcfaa969080 2011-02-19 15:56:21 +0100 c9638ee8a0033c511f0bdfd47847ba689b7bbfc7 2011-02-19 15:55:29 +0100 e8f4b9c61ea956fafe6ef1c55904af342685cc03 2011-02-19 15:52:28 +0100 dd8a486adb3f24ad243c79e296c580801608a1f8 2011-02-19 15:51:52 +0100 cda658357c719ac0b0d1f2ec8b57ae093b3b4502 2011-02-19 15:48:13 +0100 dd9d9c95c7b684aedbeaa02e0a97183e232ad037 2011-02-19 15:14:51 +0100 cc9c0d744b617034c69280363090f8d51506ac8b 2011-02-19 15:00:16 +0100 52e1ad6ca38c03f56da8f6b71781c8b68e0e4592 2011-01-28 20:27:34 +0100 9fd5343ea589f448a730a01fe1ba1400cf6abb1d 2011-01-28 20:22:07 +0100 9df4c055c8ac293c98361268c43945a7c7dcc6db 2011-01-17 20:17:05 +0100 73b36426743ff991dc209135903ed090b542b14b 2011-01-14 14:36:34 +0100 533063f1f2aa77a5bc43ba2fbd5dad432b0efbfd 2011-01-14 14:36:05 +0100 9b9e745fe45f8e3f21c10461f659f95c0ff80516 2011-01-14 14:19:59 +0100 e6a8df3bdd3f447ed71917b2a286b19f83332dcb 2011-01-14 13:18:14 +0100 e6e6c5c9ee1f2de79db4ef185fe73d0e5756dece 2011-01-14 12:11:15 +0100 436afb025892adf7bbdfc98281ae35c9982d4e3f 2011-01-14 11:04:12 +0100 9aad77f6351065189bd7e75dd083b6384fece201 2011-01-14 02:02:50 +0100 5f96d1ce65e95328981bbc4db1dc7f0cb9d9534b 2011-01-14 01:18:50 +0100 31dc1d0cefb00cf307391be6a4311da8d0c49f0a 2011-01-14 01:04:59 +0100 5d5d651239b0aaba9cf510d9d35193d864a8899c 2011-01-13 22:42:53 +0100 da5f806b90c5920bd074ab17fae4b4086962a89b 2011-01-13 22:34:38 +0100 0ea671ca3b89e4cbf23a16de0224f9eccae438da 2011-01-13 22:07:33 +0100 6d59213b9b9a653bd29c4d8055a0d1cfec30893b 2011-01-13 20:25:12 +0100 bf7e7b248e407ec36ebb179abca00b18fc32a630 2011-01-13 20:23:56 +0100 92659ebce6c1c1a71a973224505203e95464b8b0 2011-01-13 20:05:26 +0100 a1aa86ac5c512c016f738f6abd9b9c0d64eaf23d 2011-01-13 19:40:50 +0100 ad9e78cc49b4bf59315b2852a5605b81f32b5681 2011-01-13 19:17:33 +0100 2fdd8b4a68fffd009f59b5b1f3679c895f415144 2011-01-03 19:17:48 +0100 43f21847746ce64defdf5c32820cd2df0fdf40e9 2011-01-03 19:10:04 +0100 400f8ed8234dbadb0087dcc818a446ff89d70ead 2011-01-03 12:29:31 +0100 8af462499e667f2660e684408eaa7162ebaff5dd 2011-01-03 12:04:22 +0100 d80319ac961e5ad988aa50db1c1d31680c8ff7e3 2011-01-03 01:57:59 +0100 9a6c30fec6e14b2976a224116ee30c7b9afe0f12 2011-01-02 18:59:50 +0100 4114b0c56880a8d5bde1490ade73eb6386dc426d 2011-01-02 18:27:33 +0100 566222014b88d87557b0e17d9e359b70bb494278 2011-01-02 18:25:45 +0100 2640715ee9aa54a2bd25c1a8b919195acaa44ebe 2011-01-02 18:05:59 +0100 1817b6413cb481c45f79abb0113ba42d93ec061b 2011-01-02 12:57:22 +0100 a23c222a0ddd694af5d197884d3c8982b323064b 2010-12-25 18:04:50 +0100 0fb499033581c843995782282b9ced59e7c4a1b4 2010-12-25 16:43:58 +0100 36e9d67790b1d5c3ea934cd53d883b3eb17e9681 2010-12-25 15:27:26 +0100 feefb419cb76143683ed24eb8ad4dcb966dcca5f 2010-12-25 01:14:25 +0100 33cb5f221305d8d7f9a882836031595585786faf 2010-12-25 00:14:31 +0100 6b0799f232cb7b2abc37c3b96fd2da003273a462 2010-12-25 00:00:04 +0100 22b24283d7ba303ac2176eb4bc1ebb8a52828bad 2010-12-11 13:49:10 +0100 3364b910eecaab996e29d870327e9c9c3f9977ee 2010-11-15 15:50:54 +0100 4e6094ea32b73c9b1876362656965971651396b9 2010-11-14 23:32:01 +0100 8d0c84ff8f5901a8dafc36474beaa29a70c4e450 2010-11-14 23:06:25 +0100 8c59e4da5479758c550d5f689582f03da8d5d917 2010-11-14 22:53:55 +0100 7ee81a2ca43aab9743337c41b5f173d41f8b9ec7 2010-11-14 22:38:40 +0100 e916ed71e7b42b0d0433692cf5d492583cb91f49 2010-11-14 21:05:07 +0100 512d373510f5f7011318c5844e0b1dcbb062264f 2010-10-24 14:14:05 +0200 2d2947318ee4bdc04b70874014a2c3dc032eb553 2010-10-24 14:02:40 +0200 fecedcf18e0775623cf147e07a5caecd517cd1a9 2010-10-24 14:02:20 +0200 5e583161b047449c4ff20475986f686663fce17f 2010-10-24 13:56:07 +0200 19406cc999148fca19988928d26c9f408d53217e 2010-10-24 13:31:32 +0200 6f0c72ee2d8990725948cff2f39c63603037e829 2010-10-24 13:30:59 +0200 1576d09192d19bd941fd0f736210138aa5a3d3bc 2010-10-24 13:29:46 +0200 6a0d674c2700f51bbde2269730b4e4cf49735a16 2010-10-24 13:29:23 +0200 6b1e394b59cbcb801d56923d5ed5aec04790d746 2010-09-22 14:51:37 +0200 f305f13e5599e5909219246a7c50b0852a03f55b 2010-09-19 16:48:00 +0200 3ba1e21b2064d7493074be8e4802de67c689c7b0 2010-09-19 13:10:02 +0200 edecba42d736e6a79cbc7cfae7e448adc6186fb2 2010-09-19 13:00:31 +0200 a2291ffbd464b7222686b1017741916606dc69d5 2010-09-19 12:59:10 +0200 40d1c96222188c75e0ed9741c3490907a2402166 2010-09-19 12:50:15 +0200 b3e71293bea974a399679d5fbba0786e7089109b 2010-09-19 12:49:16 +0200 6d0614c51aee2cce5c4cc7ff5fe332fcac5975bd 2010-09-19 12:46:55 +0200 18fbd08e259c5776b9cdc5c39bf154f8821a6b73 2010-09-19 12:45:01 +0200 e1ed2635fe9d3a0ab7d08b03811340da31d96390 2010-09-19 12:43:48 +0200 3d6eb7a0080d34554766b117f87c50971b220be9 2010-09-19 12:42:20 +0200 dca9bad34862aee680dd16cc0469ecdd7b1cad3d 2010-09-19 12:40:31 +0200 0ad185fe59a5f78d6801325a903bae552f169fb2 2010-09-19 12:40:17 +0200 4a866c1222d006935d79a46bed99d319dfb7c32d 2010-09-19 12:31:52 +0200 2aba8277f4a6c408743e4f4be397b35b0866f6d3 2010-09-19 12:31:28 +0200 2d2dc487b56f4d803339036af1d0b39ba2ab158b 2010-09-19 12:18:43 +0200 bf38f7cdddf54b3597cdc5c937ca5635e153b777 2010-09-19 12:16:00 +0200 ea261b01a3201c7ab6c6fe43e94ad36b0e2fd7c1 2010-09-19 12:13:07 +0200 757e8a1ab8c50bdef9e9e9035a56fb1828f8b3d6 2010-09-19 12:11:03 +0200 a4b3ec3f182d08f5aed524c042f8153013baadbb 2010-09-19 12:05:07 +0200 9a85e20830866b0e8bc66125714cac0f552fe238 2010-09-19 12:04:43 +0200 46b9c3a4c326f148a88b6d2420aebe9530d3470c 2010-09-19 11:54:21 +0200 63913ae03156784a00eedb3d870137512f230c22 2010-09-19 11:51:03 +0200 381e7dbce80ba4d61855963e381fccd6d3b7d716 2010-09-19 11:50:13 +0200 642f5c89983b66794f6c96d9b29f294b2376ce68 2010-09-19 11:42:02 +0200 4b88da43e392e1cc78fff17ffc3f6e79d81519e5 2010-09-19 10:35:21 +0200 beb5daeaa1cf66adde45328d9d1186777d7c8de3 2010-09-19 10:33:53 +0200 666fa45cfed3ba7e62ea8f56d544c29ca26bc08e 2010-09-19 10:32:18 +0200 409a88ec624c3d407cde0d270fb7b7ec67f8e625 2010-09-19 09:56:12 +0200 9a183bccd0c202c18f0073eab4bbaa4e319bdbff 2010-09-19 07:52:48 +0200 831bde6050d36e91144631445bde8f89c30526eb 2010-09-19 00:02:24 +0200 6eb09d55364fc3ff175b010703c91b23db1207e2 2010-09-18 23:32:56 +0200 a2ad3bf8986a8368f82fc89aa22f685e3cc24eac 2010-09-18 23:22:21 +0200 83cf6881d66cf3ff90f4e2bc1e220fcbafe1270b 2010-09-18 23:10:01 +0200 f7eba7e1ee1eb93f6b6c7e91a4f01eb05ad913f8 2010-09-18 23:08:57 +0200 0f011278394e34e8655e7e1cc364eca0a766909f 2010-09-18 23:03:17 +0200 9bb99e81d7af60e0343f0f87b0623f3b44ddd929 2010-09-18 22:46:54 +0200 ac645c973d1b42ae1433817e5057b9eb3300ee47 2010-09-18 15:45:04 +0200 22f536ed0c575700e86763f38851123044dd2dd6 2010-09-18 15:07:32 +0200 05f29b7c220c07c312b2aa6548e2f95bae8162db 2010-09-18 14:36:07 +0200 5cfa3c73c415b1e6aff723b13386959ff2b0674f 2010-09-18 13:11:36 +0200 2cc96743b7cf3994167e930874c9edf8cc5d5b7d 2010-09-18 13:10:26 +0200 51ab1674b94bd88502ead442817060751a4b5017 2010-09-18 10:33:38 +0200 45c530a885e67fc2e478d753932920cb15c6a274 2010-09-13 21:47:00 +0200 b6c5ee39a15564a234e941cecd20effcc352e124 2010-09-12 21:27:57 +0200 b5deaf62621dc7fce2aa62ddf0ce67935d459fe9 2010-08-26 20:48:48 +0200 bdabba7d2fa141f1c359669f4a911720f1879573 2010-08-26 19:59:48 +0200 9cbafd321b59a9d3a3c9761ff75f92f8fed95f5c 2010-08-26 19:50:23 +0200 abd5c7bc2405c4a0fc7f33c4eb4579fb59edcd8b 2010-08-26 19:05:04 +0200 0f43f2462890e76c3a5b94e5168ada44829810c1 2010-08-26 18:52:59 +0200 fe986b2da629701070eb10dc8494b02bef0c28d2 2010-08-23 23:13:29 +0200 52ebff73dafa90849646bcd41389f6ede694186b 2010-08-23 23:09:43 +0200 5485b320a78b44a12b05b4d119abf36855dee056 2010-08-23 23:07:56 +0200 53d109b2639ffaa1c6cdcfb937b2fea6fb552833 2010-08-23 17:42:11 +0200 315e5f9e644653c0020607e76d8f105e770f2f8e 2010-08-23 15:11:23 +0200 241cd8e32ebdc28fc8a42ed9e765e18b15950212 2010-08-23 15:10:58 +0200 cff9d1c349f775e42e43009199ff4c2373b41272 2010-08-23 14:36:13 +0200 acabbe6bb90838fd72789539c90e717992e7c59b 2010-08-23 14:26:48 +0200 db5336cce7f3fa4360ddbc782ea24b1b4ed862b2 2010-08-22 23:49:47 +0200 7629bea5b6ffcab9f9347672267d09d3807b8ad9 2010-08-22 22:03:29 +0200 7565a6b81a7b444621a10dbe66c96fb8df5b46fc 2010-08-22 21:53:24 +0200 1308732708fd125bfe2f7f3f3d16d2219ea2927c 2010-08-22 21:51:21 +0200 f745dcd627b2c1b1443ba2a634c4258629b31afb 2010-08-22 21:50:32 +0200 f4a38ed07366cc38c22a0e1913ae6cefda08941c 2010-08-22 20:42:37 +0200 2639fba954acc3433b78bfb3c8dcfc541b401a2c 2010-08-22 19:12:26 +0200 1bdaf30881057d370a0f772f830ad3eb45c8d4dc 2010-08-22 19:11:39 +0200 04f7c1e38c32a6faa4a0c4ccca1f424903978e6e 2010-08-22 18:58:14 +0200 062f3cf8b131aa5f684af956776221cdfac5ba10 2010-08-22 18:52:32 +0200 9081cbb403f7dcacf547bea7ae7ecd428b894546 2010-08-22 18:44:58 +0200 7107bc5c6f602b1cf8c8ccd5c073b8ef02410dd7 2010-08-22 18:43:16 +0200 2046f7989e50986d1b57f68982961343dc446554 2010-08-22 18:37:41 +0200 7cec326d2364695711560f98b2918a5caed1c683 2010-08-22 12:30:28 +0200 ce7101ab1755c312fa4bb843dea79f81999d4281 2010-08-22 12:11:58 +0200 01f5700bc724668e84f8e5060d12eefc6a7c0d6d 2010-08-22 11:35:52 +0200 8d51f3b91fc08ff1db8ab2d95e18f8cef84ec25d 2010-08-22 10:53:51 +0200 90cf1273a25323a51a9496d58b5bdff5ba1bc128 2010-08-22 10:36:42 +0200 534501434d192fc99aaa1d2d816a6b2e2fab48b2 2010-08-22 10:35:50 +0200 3f0d1fb253eeae4425f9a8fc4a23f8d08bbd950d 2010-08-22 10:12:46 +0200 6efd15417150b40be176c59e57eb0bbb0863c1df 2010-08-21 14:16:02 +0200 827cf34a8cf6d9f3d643d94e3d4ab44ae50eaeec 2010-08-21 14:11:46 +0200 2326b0881180bdf1de442e3a0e062b2098e0293e 2010-08-21 12:04:06 +0200 97a404840d1f342909cbd0f8821417f506019b76 2010-08-17 22:57:13 +0200 ae67396588a43a16e719ae8b6861c085b3547b2e 2010-08-17 22:42:26 +0200 3020210874f5bad659ce2049fe93212f42217fd3 2010-08-17 22:28:02 +0200 aecd87cac282ab85f757fc3fa7975b99d397ff56 2010-08-17 22:14:19 +0200 c53434dae47ddd0dd06daa3bb60f89416c75626f 2010-08-17 22:08:05 +0200 30117454bae3ceee5077137cee2dfeba9857b31e 2010-08-14 11:47:12 +0200 6dbd16e82b2e099493b71276171ed119a86f1bc8 2010-08-14 10:57:58 +0200 0ab21328ee0b32cdb818322048368a60ed665729 2010-08-14 10:46:57 +0200 ac9deb53d85c68d7ff4585fff00a706ddba0fac1 2010-08-13 22:18:15 +0200 2cd9310306d14aa089bde9d61cf184704c8b0bcd 2010-08-13 22:17:26 +0200 48000fd0e6f32ec0ea736844ea5da6fc01db7699 2010-08-13 22:15:51 +0200 9b83e45a0fa75abd04d6e427ea7477101685c8dc 2010-08-13 22:10:51 +0200 0fb29f6679eac4148fdc32fdad1955b0db9b3e19 2010-08-13 22:09:50 +0200 2860d99e23e22f9a1f36dcdd52024675a492a87f 2010-08-13 21:37:46 +0200 4651e714221fa4df66bf537797783249cdd565b7 2010-08-09 13:22:02 +0200 214bfa2fdf80b60f7d82488d43d4b02b5dde63b5 2010-08-09 13:16:01 +0200 777a915c8edc99e3858c6a07c2f56644e6855fcf 2010-08-09 13:12:36 +0200 8099ce8f7e624b853eacca0c9cebdac89f829fbd 2010-08-08 13:16:04 +0200 9ef33ceff744cb41c384e20e84790635081e2067 2010-08-08 08:10:22 +0200 983975cc5ce2bb5cf43ab362e0eb29543d0175d7 2010-08-07 20:50:48 +0200 50e62f7588b715854cab41ccf6f75992bed645bb 2010-08-07 20:27:47 +0200 e1089787793b197662c0481449dbc5a6c09a841b 2010-08-07 20:26:08 +0200 886bbc6eec76290b78ddc28ea3ec0a5800167e7b 2010-08-07 14:51:44 +0200 bb69f050f315462cacef8d9557cf8e78d92fa746 2010-08-07 14:51:14 +0200 ef33312c206ab1f50ebbc4cc821d09deca2bc67f 2010-08-07 14:30:12 +0200 9ed0953ba538a97fef128189c4686aecfcbb4ba7 2010-08-07 14:20:04 +0200 2967dc7b477edf4acc26f4a7ce4fb457b20cf549 2010-08-07 14:18:48 +0200 970603a0e29f2514525f6ad67d8e74351d830fd4 2010-08-05 23:51:02 +0200 ca198c4bfc06d9e89aac770d2e36a9bf9fd16686 2010-08-05 23:33:09 +0200 ca06daa965b8633e351c074ce8bf3185ba8f9b0a 2010-06-17 18:19:32 +0200 dc80d10de7eb5c4e24f8d29c34a7eab52f51826e 2010-06-17 14:57:38 +0200 30884edad2ee04e3219ababf80158ef24c6660d4 2010-06-17 14:56:27 +0200 4a9fa6aea435980875fda4b823a73ea46d25ce3f 2010-06-17 13:50:50 +0200 4f4a4541b97a7c230f991f924484b71f39da701b 2010-06-17 13:49:11 +0200 6ee82a8d62ddf59f93a990359ce959ade1076fc8 2010-06-17 12:39:38 +0200 256c3f3143740792f7f18b8cc6b91c218d0313d8 2010-06-17 12:32:11 +0200 91c408781e68a360de8ab6f70f70015b8150be67 2010-06-14 22:32:21 +0200 8084768736841fe9aa79ae6da18ba2c4c284a3f7 2010-05-02 22:41:43 +0200 fc9b090d6bb765f8c643a920a8aca21ea754cc32 2010-05-02 22:30:30 +0200 e9642100117044e66af5c88828171328959a0cd0 2010-05-02 22:15:10 +0200 a22500102c64c42e2db5b16673f2eee2673a7425 2010-05-02 22:14:27 +0200 23a3a3ff2b3b6a82b2b9e09078dd09b6ed82ac11 2010-05-02 20:48:41 +0200 921c26224650390052a04624833b35f5a2cdda34 2010-04-05 16:21:58 -0400 c42526868085191ca2e089c6394671da85ed4841 2010-04-05 15:35:57 -0400 1f0de3ff78a67911bbeb77d90eb115d884dcf1c5 2010-05-02 20:40:01 +0200 856df189534fea589203cbc74debe6c53b2933e0 2010-04-12 22:35:38 +0200 bf6c26a5a1fe7a8a0f686f672b8285f017f8bf0b 2010-04-12 21:51:07 +0200 2fdba7fd42badc7c86d20cd5bcec0a14e7a176e1 2010-04-09 07:47:39 +0200 39a3da8880a729090d2e1b6d56b361ccee12613c 2010-04-07 18:31:14 +0200 d0769f40f16d20ee09886b8705e723b4a0a41631 2010-02-14 07:18:13 +0100 2c47ed8955d1b2ab43aa68a2d3df5b7b245aa80c 2010-02-13 08:43:25 +0100 5afd2d6f8f4c623bded05bcfdedb2d00c4f58247 2010-02-12 09:13:33 +0100 bb371fc05f5b4c58dc18d62f9163d1450635fe4f 2010-02-11 23:12:05 +0100 d48cc6f1cce26f5de13bd8dbfc85d0d97581a4ad 2010-02-11 22:57:25 +0100 f883b3708318107a1e50def6c43dd78434062d0a 2010-02-11 22:43:49 +0100 75ce9f860c896a72fdfd55cb5bc060781413ba65 2010-02-11 22:41:51 +0100 cf4837ae5cd38b2b7e1db8572d3237a5d71c35ea 2010-02-11 21:45:18 +0100 0997baf2fa682511a2484eff8b95f9da36ece338 2010-02-11 21:36:28 +0100 3fb05d282ab33f66b4da89f5e1138a0a004efde0 2010-02-11 21:18:49 +0100 d476eb3525aad6040a070e592ca4ecaec1ad2c3a 2010-02-11 21:17:23 +0100 e4cc091e185891e020bf0ec1e0849189ee21033d 2010-02-11 10:02:02 +0100 a665b9c8654840487458f1031caf1cf20f9b9fe8 2010-02-11 09:50:49 +0100 91d8a2eb41e417d2142d4b4f3996107facc1aae1 2010-02-11 09:49:39 +0100 d887d0aa8e841c1bd85420eb14f55600ab8214d0 2010-02-10 23:08:47 +0100 45f611a9960b6ed36032eebd8aeba24cfb1c733c 2010-02-10 23:03:03 +0100 7f168e1858f4fea8c14b247f5ef40b4244f94511 2010-02-10 23:02:47 +0100 09c54f758f7480ed7ac1d5acbb6428b9e83775e7 2010-02-10 22:57:27 +0100 00ccc7e3a02ccaf02875c1d056c1ff40777f0c0d 2010-02-10 22:50:05 +0100 1ada264aac33b3cd58ba61d8866c63e26fffab20 2010-02-10 22:46:37 +0100 8b7350da9fc5be8b12552cd1c0c7fe35d9a245d3 2010-02-10 22:38:56 +0100 0b6b3575b981707f308fc3605264d4b6ac5c8707 2010-02-10 22:24:50 +0100 282afff04687c7c58160063ce3f03f5fcda1fb19 2010-01-26 22:39:29 +0100 d048667076ea32630c1f2f478dfdbb924334f3c0 2010-01-25 17:51:40 +0100 b13b95ef169bdd544e3f322ceff32bb4a8a10e18 2010-01-25 13:25:59 +0000 dba35d241916d1a9f0725f67d1ab201c08cb39d4 2009-11-12 15:10:39 +0000 3335b387c7d239f8390913dd4636c70ad9fd1b3a 2009-11-12 14:28:04 +0000 e90102d055afe57b6d157bce47d31c9a4292568b 2009-11-12 14:17:27 +0000 be7dd6979d1159798f9b9c26783b401fa7a66c2c 2009-11-12 14:16:41 +0000 4a42bfcbe1118ef11bad1d6c7687caa6426cc9b2 2009-11-12 14:14:25 +0000 7920a39a379c9f569204b2983693c30e7ba221e9 2009-11-06 11:41:15 +0000 81517281879a368be83648bfd3a19765bc31949c 2009-11-06 11:33:31 +0000 5ff5c73e495bbc35d1d97ebf28b97e98861e4f5a 2009-11-01 22:18:42 +0000 c223441a5d31b638d719178023cc66333db0bc35 2009-10-31 18:06:29 +0000 01e5b681b92cb9b4412de2336c7e71adce32309a 2009-10-31 17:50:31 +0000 c85ba0f4c53247739e2ad7c78ff55d4d827c4407 2009-10-30 20:50:50 +0000 27b6f88cee1e147f0c37911f970f09c817b09f3d 2009-10-30 19:37:10 +0000 8cb525920db59ad9980c71afaca1f3766ab4ebd1 2009-10-30 19:29:45 +0000 9040ba4422b442997ffc75d2ee99fedf57a6b2a8 2009-10-30 19:28:39 +0000 6c2f19cdd2c627d82f9f998a6bec6da066825fb7 2009-10-30 19:21:17 +0000 f1bd27bc249ab3a5a4abafe2225ecb36d9647c00 2009-10-30 19:19:29 +0000 2a804a48e36da03630eed5ee8fc085ffb8f20a46 2009-10-30 19:17:31 +0000 be61e2bed030e001974074d8e6c848cd05fb35bc 2009-07-22 07:44:49 +0000 45423de79effdd4c39f41fcbe702f7d22c0a4c6f 2009-07-22 07:44:01 +0000 2d2d01ad2ec54ba1a73928069a17fe17f09d54a2 2009-07-20 11:13:15 +0000 d16f222c8d446405311c4e223d121373945b997a 2009-07-20 11:04:55 +0000 3064fdee60b09f02136b54890642f782fc27d60f 2009-07-20 09:40:42 +0000 031617cedff3ac8725c70814c6320ce6041092c4 2009-07-13 13:16:00 +0000 0e00af87ab0659f6eb8276cf4a909510fbab87c4 2009-07-13 12:58:19 +0000 6f76e6fd41ab17cc9086c4e8de1dadde365830c2 2009-07-11 15:51:21 +0000 9b759d5191e2a5323b88d7668226fd9ea144b6b4 2009-07-08 19:06:07 +0000 558a2720e0f1714e529c1202f51aa35802b93489 2009-07-08 15:27:10 +0000 1c51f078c784f1e8ae14dcb66d18eb8582157551 2009-07-08 15:25:29 +0000 aaeff6db25783dc857d40adc3ce4401b371f29b6 2009-05-10 14:07:31 +0000 52cb5a25265abe708dac54204b7ead86f9214e77 2009-05-10 14:06:52 +0000 667708101939194ca483f2ce9cdd64f233a08574 2009-05-10 11:31:26 +0000 7823a108a00ba799010137462740c33d34297492 2009-05-10 10:47:06 +0000 b37577197f7e81e9cc5b9c63dcef6888cd5615b4 2009-05-10 10:35:06 +0000 c08941e6112b52cfd39da0c6dad1026d36bb9c68 2009-05-10 10:31:23 +0000 ff1b73253239567914fbec2aae0dff923531fb10 2009-05-10 10:21:38 +0000 7a14276b224cc3ad6b71b2cdb6d73df7e92f0096 2009-05-10 07:52:09 +0000 6929c27acdc9fca9e31af2c66e462f1830fd8432 2009-05-09 11:28:32 +0000 743125307fe74c488eb9f7a03832c5ff0c14c3e4 2009-05-09 10:33:09 +0000 1bf78b8bafef08f5e2ccd0eba94cbca01f81112c 2009-05-09 08:52:58 +0000 0ef45c839810e085129b68891eed2c12f23ed7af 2009-05-06 20:25:40 +0000 99f2a5c66f27cd2acc6b01dbc614c3b2a41898d8 2009-05-06 20:25:03 +0000 06f681345c6e539adb3d239c29251fa2b51f410e 2009-05-06 20:22:39 +0000 2148fae72310871c95e06246256d4cae2ffa2869 2009-05-03 20:00:39 +0000 b3672c4b4ce5baa1ce7fed5021c870bd1f7e5318 2009-05-03 18:17:42 +0000 b263a58e4a48c9627e4c438c264b1a1feef499ca 2009-05-03 18:12:41 +0000 0b29d67d42101f0b86b748b58bf95795e711ef77 2009-05-03 17:54:37 +0000 4da2efa4bf8f8793006e1d7e8b1e2e585fcb2837 2009-04-30 21:08:19 +0000 7515d06b228483c0e1a113f89b3c6cab5bfe9696 2009-04-30 20:59:47 +0000 26ec6614a1afb1a6f643802c73585e34f4bad6b4 2009-04-30 20:55:02 +0000 e8690b575b068e0cf70629e5a331fe6c4da30128 2009-04-30 20:45:28 +0000 0ae4120c904060ba6a97a36195180716de21fcaf 2009-04-30 20:17:54 +0000 2f6b2934a20716c142f75ac1f05c59e4809a2119 2009-04-30 20:16:26 +0000 1ce961cbb4b84486e41b1f2451576ab7ef290379 2009-04-11 08:32:56 +0000 f8810caff2799b98a7ddee39f8229f9eb791ee90 2009-04-11 08:31:59 +0000 e630c1599d674d5fbf15f0949cf978d95205c78f 2009-04-11 08:30:50 +0000 1a6bf04c66fadc651621ac6ddb0de1deaf25fbc5 2009-04-11 08:20:59 +0000 3f242dcc1f22edcdb08897465f569f1bfe3d2c8b 2009-04-11 08:15:08 +0000 e30f19b0424b73d62d0fc5737cf13fa4874c73e7 2009-04-11 07:48:51 +0000 8ca6b70c01d7bf1ca1830b30b239be0753968676 2009-04-11 07:42:03 +0000 de1877c8c9d63252a00aef1178d4072eae7a1c58 2009-04-05 18:09:43 +0000 2166f443d615e21bc6e5465eb09694fa96755fdc 2009-04-05 16:28:42 +0000 b5489dc35a4e0121d90979f5dab2d6605e436bdd 2009-04-05 12:45:29 +0000 da2f3a875b36cb5bb98e537906a2f98e074584dd 2009-02-15 13:03:48 +0000 d24996d8d029ad77e2a0b758b4474c84f1c12bf0 2009-02-15 12:48:11 +0000 684625bf9a1a9d26fcfad6b1db6a7bbd9cc7263c 2009-01-03 21:21:32 +0000 12a0a3d8e8105bcf7cc519838dcecb1060d5709e 2009-01-01 20:15:38 +0000 8ef5bc0d184febeea7b458344dea8f692d7c8009 2009-01-01 19:42:38 +0000 700612900b37fa8d984715ac25da8cb18b16d210 2009-01-01 19:41:22 +0000 79c640bc80ea82d0fada5b01a189ff9daa52ca34 2009-01-01 19:12:03 +0000 c3cdc4e4165e02e300dd533f5af9bb5028c8d404 2009-01-01 18:04:12 +0000 f701ea8fea094e13a87a93287c843a6df1f37f0a 2009-01-01 17:47:47 +0000 ce270f3beb830d8392d99f31be86b94f6823734d 2009-01-01 17:26:30 +0000 ed665b85e8d0968a6ba081fc0bd39ff4c0da22a1 2009-01-01 17:25:19 +0000 c94855dd806310348a894ae04efd078aadb49c44 2009-01-01 17:20:30 +0000 b3860ad4a7f18ed4d27c85fba732370736325aa3 2009-01-01 17:09:32 +0000 20afcd92491b4d33bc4f19b6ebd63c3241bfad20 2009-01-01 17:06:30 +0000 1fee0f787e575f371244570f8b583f042827dfe6 2009-01-01 17:04:21 +0000 a545ccb6241b9907eef201b3c117a0ce6664b324 2009-01-01 17:01:45 +0000 ab500b95e0eef098d3b9daf3921af2a4f2ec81cd 2009-01-01 16:41:15 +0000 32f229d603080e144f01b9f96ba5109d0abd0445 2009-01-01 16:21:27 +0000 7a9d13b116cf3259708fdd9d9c31c3476a13d8b5 2009-01-01 14:57:33 +0000 5030c7856184162115795d290fec8fff6da3469d 2008-12-28 12:44:11 +0000 1b2665163d8cae32e3e485e91e71ca7c5433227f 2008-12-28 12:40:43 +0000 9fbb716966f02b26f788912cd88845ba8c1d64b4 2008-12-28 12:39:50 +0000 d87146681af594494333d80dadfa56db050c61de 2008-12-28 12:37:28 +0000 b0840cfdb26584396c6a8a9668f43e8967839bf2 2008-12-28 12:36:45 +0000 04e99bb9cd15825f4c2902fb597e7cc8d119dd35 2008-12-28 12:26:37 +0000 94114c042f28c2969e6e96dab1f11247fbb071fd 2008-12-28 12:24:34 +0000 3cdd3be89edd4406daf97d9ee022c73e6f25bd98 2008-12-28 12:17:52 +0000 15d1bd65b4520072c53432d751ff88510e181ffb 2008-12-28 12:16:59 +0000 9a994e8c06e4faea75b7ac92fc16abd711e08fbb 2008-12-28 12:09:40 +0000 e4f6f7346c697c53cafba1bc0293894d3ca9c340 2008-12-28 12:09:24 +0000 7f95dce5ef6646ecadadf88e7c3b865c0bd6481a 2008-12-28 11:59:04 +0000 0f9c7274e4352903f6db6ddcd4fc54c12822145b 2008-12-28 11:52:56 +0000 d8e611fd3012c79a543397eed89ea68332d6b8b4 2008-12-28 11:51:44 +0000 7853d8340c0f96a45d68befdd56e104819700852 2008-12-26 22:22:11 +0000 27623d5a923757f1e9a65967499912cb710d7bcf 2008-12-23 23:23:22 +0000 b1b5a237a8ca186f68a175cbb6cc3647f71ca5de 2008-12-19 16:43:06 +0000 768b047b51ac5009e6e1074a74e608cf5e204ee2 2008-12-17 14:30:22 +0000 cea0938649bfbc95b2ed05f67301edfe9a8ae335 2008-12-16 20:37:52 +0000 9548c798d3f8b3bd481fdf71c7163f363e435dff 2008-12-16 20:36:50 +0000 6e0fdfa22eeb4fa4dd8aaf683ccba2237b8abc53 2008-12-16 20:35:56 +0000 42c8d0551fc8637308d492f1d70a5e476004ec3b 2008-12-16 20:34:23 +0000 0d93a978ad858846e5b8aa337bf08a6e3cdb9fa3 2008-12-16 20:33:43 +0000 ffc00d64ee93c8273fe7b75e0c0363ef964946f6 2008-12-16 18:16:52 +0000 607c9181be8765089ea1da6f0cdd21f0143ddd30 2008-12-16 18:16:12 +0000 e4fd26d9ff2721996988cf088afc47dae0b6349b 2008-12-16 18:11:28 +0000 1b27ffe9a904f48720b500f66b1ff9ab06633a4f 2008-12-16 18:10:57 +0000 f833490d67411f77fbd07d57fff0aaaa9fadaf3f 2008-12-16 18:01:54 +0000 e842d927263bd8d2d87d118e9e4da36c19dc88a7 2008-12-16 17:24:26 +0000 42b9376b114fd12507117b3f21e436c0efdd24f1 2008-12-16 16:25:13 +0000 afd89ecbd727c8af25760d6c2ef1ff3fa0b2d888 2008-12-16 16:18:55 +0000 73a52962b097966aa9653e91702559b9bfda2bd2 2008-12-16 16:18:19 +0000 f3f570705fe74c8c86a167a1d3cc3c530148affb 2008-12-16 16:03:39 +0000 b49269498696ef2834ddaa7b1c27d7d655f8d0eb 2008-12-16 14:35:01 +0000 d0ea977217e6f02201d0cf04dcc3a3562a1ef1a7 2008-12-16 14:30:11 +0000 ce67cced118f64e31425736cdb1224ce356f7910 2008-12-16 14:29:50 +0000 22a4bfedaf89c15c7cdf66b38422c0bb1b7a45c8 2008-12-16 14:28:04 +0000 5353ec9a050ed18ecad1386362db1fa48f58f067 2008-12-16 14:14:11 +0000 c377d222ca12f46cb24d2b0b1e7f38c4ccb32487 2008-12-16 10:28:54 +0000 37900fae2d363b8e0ac5eaf8e93f1d3014a6a394 2008-12-16 10:28:28 +0000 c0ab7063491b348f32317fcb63bf7fb68c8d1574 2008-12-16 10:18:01 +0000 680e6a3c66706b6c82ea76090dd1685be45fc84e 2008-12-16 10:17:12 +0000 03855a7b4676376109826ec783447cf913975d0a 2008-12-16 10:16:28 +0000 e0b3a3cde421b18bba072dee4211d6f1ee252a24 2008-12-16 09:40:59 +0000 3da749db59a71057bfd4a24eded8d61418b3411c 2008-12-16 09:21:50 +0000 616f25cfc357d74be9b298d31f03792274a73e0a 2008-12-16 07:07:46 +0000 f5f57be747224fc60d1b0716977fc71e694e8b65 2008-12-15 22:23:25 +0000 3d67776ae5c3e9e0089f4be0b45e0e13cae3d302 2008-12-15 21:17:42 +0000 ea85fe1fd8154ea00ddda19087ea6936df718716 2008-12-15 20:38:20 +0000 6c3570f0fd5c2c5ca398b13f4e96e3869740291d 2008-09-15 02:27:28 +0000 a1b7c141121b14f8bf0dddca0929f0e67fb6f88b 2008-09-06 20:04:08 +0000 1c2921b9f36c278c8530a45e3c7b2a3ee7261c58 2008-09-06 19:50:18 +0000 3e86c3bf1e8e28ef96118731c3326ede2fcc8c3a 2008-09-06 19:30:58 +0000 5d0c3397524947ef91b9ab2a269545ff3db492d8 2008-09-06 19:27:16 +0000 13ff52c9cdf1fa9454cddc26755a5abe0c134e96 2008-09-06 19:13:50 +0000 adc6d6c4c5150076df9980cd84902a5e32d1f2b0 2008-09-06 19:01:03 +0000 73ad91d942b3b03f086a5f33baa594166aeebee9 2008-09-05 21:31:50 +0000 6b3dd855918a556b61de4538423f99be9c0ca177 2008-09-03 19:04:06 +0000 20c14c99b0a5c824ecda1acc7c763dd68c35fdf3 2008-08-31 19:26:08 +0000 906a4f95da58144a2907b4e00885be02420620c0 2008-08-31 13:51:44 +0000 f6b3f0c89892eea5e388d9d3c6e90c3d5da9ac09 2008-08-31 12:52:18 +0000 d08ca0ca87b3ae05fd58c0890f77a9f703db70f6 2008-08-31 12:49:27 +0000 fbc868918cf7e3c2e0983c58502b6b7bd59adabf 2008-08-30 19:43:31 +0000 543018bbb890621af107768a16719ffff62063a2 2008-07-30 20:29:21 +0000 1cfa3425de2713f553aea609f86f005ad6b612b3 2008-07-30 19:33:39 +0000 911549769e8a78da73b937f82a8f563fb50265cd 2008-07-30 18:51:51 +0000 6e078f1c4942437803148fa00d4d3379a82e6b2d 2008-07-30 04:58:13 +0000 9d8097e1dd3a0fd393386694f6b60526bfe5c0e2 2008-07-29 18:21:33 +0000 a09e4a4f0c9d3025c2beab5f40a1533a53f06d00 2007-07-22 15:16:21 +0000 589248058eaa3da98b0ce901024ee7dbf8b08e16 2007-07-22 14:45:12 +0000 9d7ee9d45ffe75d21f02ad2deb61b38afeaf47c4 2007-07-22 12:39:10 +0000 71ec8c36b32d2e7eaca187433292f08478d65845 2007-06-03 18:43:08 +0000 f4405b7674a32246671361a5502dbc19ed07cbc6 2007-06-03 18:24:33 +0000 556f08b08c54409d9ef24ad0cf746c46903081e6 2007-06-03 18:23:32 +0000 16e76b94e500d2354f98e52ffffbdca17ef6d71b 2007-06-03 18:22:08 +0000 cc436d27336930035103fa18289694bc10c9c88b 2007-06-03 16:01:35 +0000 c74c48ecec8eaa8875dfdfa096754878ce691e45 2007-06-03 16:00:44 +0000 3f97934659f9f40279a4225c744beac6404e7bc2 2007-06-03 15:16:01 +0000 146753140c92a0e677ed2da4c5ca508322dcca15 2007-06-03 15:14:30 +0000 a946c5764c793b1d60d1307d131567cb4024c5d4 2007-06-03 14:45:25 +0000 1a53d8b2dcd7a03c3ff1ea31e47f07cc72676d20 2007-05-30 22:27:33 +0000 e555c14cb2f2b3ff3ef4c1d4677fe35118e9bc26 2007-04-22 12:44:03 +0000 2c12221c4a737e212480aa2bee0412057d0216f4 2007-04-22 12:39:27 +0000 8679ed051764f197031d84cc410767c4e1699fc6 2007-04-22 12:30:14 +0000 00780fbaee399f9ddcdafb0979289b566076f93e 2007-04-22 12:17:56 +0000 dd77acbb83f7710386557d360851f27a8933c758 2007-04-21 19:37:40 +0000 889d04fb3522da806217ead84211d21e2d314006 2007-04-16 11:51:32 +0000 1dec7cfdba78915dce12c67f4deeeea03922a8dc 2007-04-16 08:29:59 +0000 c53c37156c9240d00bca43120dbf9f68e32323ee 2007-04-16 08:14:05 +0000 ec6f8388053fe842e5cb326efa4db5af3d0ef431 2007-04-16 08:13:22 +0000 5fd2a61be7a5022ef42fa6b29463838bf5b190f9 2007-04-16 02:00:30 +0000 dd7f0cd9ea3d79a9971e29c91efff9f73680bb11 2007-04-16 01:59:15 +0000 74cac1b559d1c3eb408c329ab0053f59c8169c44 2007-04-16 01:11:42 +0000 217c5f6100762b55cb80d4976a435d153a5264e5 2007-04-16 01:07:10 +0000 a49b4aec010704e0fc3a345ef6fa79b4e6e0c64d 2007-04-15 21:34:46 +0000 5e58ca95316977c5e578565f2460ca2cbebc8a2d 2007-04-14 17:46:51 +0000 df9a626f0a8008265f49aa1bdc9c8c8f85c2863c 2007-02-01 20:55:47 +0000 4a9c7b6fc1b7e718b516fa224c6d8b8e1662efc8 2007-01-31 23:04:05 +0000 e899ee3bf82fd8030ccda09271b395934e39ec80 2007-01-31 23:03:36 +0000 cf3c835487fd55dfa0de9f49bfe8533bf8d201f4 2007-01-26 22:40:05 +0000 043f0537db5d86f264b2c5f84dfac4d91bb4f07d 2007-01-26 22:39:29 +0000 c6d9cb7ba777420e25bd5947492a560492d190a7 2007-01-26 08:10:19 +0000 e0dc9f569cd7bf643452c8f5fab644291456cfdb 2007-01-26 08:09:42 +0000 8213195b860e10c4d0d4d6ba6b76f0eff10926c5 2007-01-26 08:04:51 +0000 8e3fbb31cc805ea4323de49e6bcc8e7c8b30cbbd 2006-12-24 22:31:49 +0000 2512d0af8676d1c20af0789713c3e6448ebabf3d 2006-12-24 22:24:11 +0000 ebe70ab7417db3d394c7412f7af79e14992c1935 2006-12-24 22:22:49 +0000 c567eedcdb0a168cf7a3e023257702070213a7c0 2006-12-24 22:12:55 +0000 e7d68f07ef60209a7bceee47a7db15273fafa5fd 2006-12-13 07:17:34 +0000 69be188f3e6e3b2c287d8ae00b3aee605a676c8d 2006-11-25 11:50:49 +0000 04366302282927d0d46b9ac9cfa8ee77c4c753db 2006-11-24 23:35:32 +0000 c3028d2a9cadd7b58a584effe81235e172848e31 2006-11-24 12:56:50 +0000 ff9169cf7d443e67d1d51afc903c87ed669a0ca1 2006-11-23 20:05:27 +0000 70efc486dcf8b80e23a84772a524427f4288ed31 2006-11-23 19:22:23 +0000 e940c4c4a23b0279a3d2012aafd7ee702c57365f 2006-11-23 19:12:25 +0000 64f441817a6a6d22f1d543b8a3350a7d67ecb434 2006-11-23 19:10:21 +0000 fcc88740384405427031661d64f06b59dd1e2d79 2006-11-23 19:06:27 +0000 c7fa164c1701177624230dafbdac8fb737705432 2006-11-23 19:04:52 +0000 462b66d7e29f8094df805574663bd069a81de971 2006-11-23 19:01:17 +0000 416a6f2379a609f03d2c6557192b56f741c04990 2006-11-23 19:00:57 +0000 cd6dd66b67b1cd9b97c5adaa9094959f49aca080 2006-11-23 18:39:12 +0000 70cdc7a8398f42396088b816f141eadea976ffd2 2006-11-23 18:37:14 +0000 42c46e115fc10f4d933b74687d8bdf6c7dc9fbd4 2006-11-23 18:34:10 +0000 dd1901b918312c328939b4dc8b24a2a8d4f8c783 2006-11-23 18:22:43 +0000 f25a6fb0339b0d94ab38cbb3c3865a024e41d859 2006-11-23 17:51:56 +0000 9cf4a9ec1edef9f9623d673b5cd243a59742da1e 2006-11-23 17:25:29 +0000 3a4e22be7734db799989d7edebc56681935b266c 2006-11-23 17:19:10 +0000 9f40aaf8fda1732d889dd54ff14d232d00469ccf 2006-11-23 16:47:50 +0000 16b027960943e8208719d86a546f1fb16ad7c67e 2006-11-23 16:42:47 +0000 efbd28c6bc3d3561ad6ccd43c598a4a4059669dd 2006-11-23 14:41:29 +0000 4a92c70558e102bcff249376d2396a6478b015c1 2006-11-23 11:58:30 +0000 165734ae2b14cbf8b718687866907e3fe42bfd37 2006-11-23 01:33:53 +0000 46557535213cda9b3c78df71600be205b02d8a4f 2006-11-23 01:33:01 +0000 d72cf4f7bb290be218c8cf7760b1f376024c015d 2006-11-23 01:30:07 +0000 72d6b46688d7cf2f1fce07f6f3c748a3797ec4fc 2006-11-23 01:19:55 +0000 ef6c5dfa43795370819d73eb4df226161e3f8225 2006-11-23 00:28:21 +0000 8739e5d1edbdb91b9d648501490aac5e0e8375d6 2006-11-23 00:27:50 +0000 5e63dda13a86a6464b6b6ae51a18f89c242e00d7 2006-11-23 00:27:23 +0000 2ab2c8a08d73197d0323b98f4416d50223372206 2006-11-23 00:09:10 +0000 b94a6dd1df28629d12c1358cd1260ac4f328f518 2006-11-22 23:56:06 +0000 ec9258eac326bf266b6fed441a992e279b1b8e5d 2006-11-22 23:01:51 +0000 3536e49fc67e0073292045ca49aadc31bf6fe058 2006-11-22 22:49:46 +0000 5a9c273041690504057eb71847bc3f608f1cea6f 2006-11-22 22:38:16 +0000 2ac8410b0a70f67bbde034737ca90c1cd0bce1dc 2006-11-22 22:27:26 +0000 f2ab969f29b5d2f6b6a2c64c62138368ff34ca62 2006-11-22 22:18:57 +0000 34a5c709c98931e50590e4a158bb4c13f566a58b 2006-11-22 22:15:32 +0000 26bd1014b2b792369350817ef7cc0702b3a07ad1 2006-11-22 19:57:47 +0000 22a0f6aa73644caebfac29dfde762b0389bdbb37 2006-11-22 19:56:40 +0000 83dda12c086ff9263bfdbe58bf2e6a899ad0c860 2006-11-22 19:55:55 +0000 923443c9cd9072241293beaf1c94d7bbffabe59f 2006-10-30 23:09:36 +0000 e4529820713217c21319b60ec8ef5b8783782a74 2006-10-30 23:08:52 +0000 7d901252fc5e1a01c4f5f2cae3730dc8e22d66b3 2006-10-30 23:08:07 +0000 fee84e3496a65622608f1e362fa8afaf730e979c 2006-10-19 23:40:01 +0000 503954c425f7ff401c5136377a28db95d769fce2 2006-10-19 23:36:37 +0000 0b945aa60ed38b30d804fc7e8b0e38fc7b5cf03b 2006-10-19 23:28:00 +0000 87250caa0077342347369235752c32c670f0dbcd 2006-10-19 22:54:46 +0000 677d7e4f849a06471e08a7d70e8c5fc815c0e52a 2006-10-19 22:54:18 +0000 475571f05c7711eb2b84e2aefccc665b7a2874df 2006-10-19 22:48:47 +0000 2786879e1803480d4f7da44932b15107a2c1ed0e 2006-10-19 22:37:52 +0000 71dfdb2991e5788329f6ba61f610e26f756edfdd 2006-10-19 22:31:41 +0000 3376329092e514f2d9aed5c6799b8a9b5e8f374d 2006-10-19 21:42:49 +0000 ccad912adb879808f5a489663aa1d57d0c2d5de6 2006-10-19 21:23:32 +0000 ea9c079e5f5fcd8b8f75cf5fca9224cab28b7f6e 2006-10-19 21:19:17 +0000 1ab7b9a1d482d6cfc9563f11b3fda1fd76310643 2006-10-19 19:08:25 +0000 92867832b7a3c309bc83cdb5999103c9438f109b 2006-10-19 18:09:44 +0000 f0832919e4bcdabfd2f1886f106a4351597d205c 2006-10-19 17:53:14 +0000 417ecf074ea675eb0194211fb20ff47e291d5afb 2006-10-19 17:30:17 +0000 06608a9096646c65f029fc3a8f7cfb52971ad26f 2006-10-19 17:28:59 +0000 79fe7ae18727084c27b2b4ddecb4d29ee5c2ae29 2006-10-19 17:28:15 +0000 d4cfbef5634ae80db53005e8c033a274480ebbc4 2006-10-19 17:04:41 +0000 1598dd7ef548d5e20fe5caf9563681c41702e4ff 2006-10-19 15:55:29 +0000 2259d416bedf3f136402c32159c5dcec8c0b11ad 2006-10-19 15:53:49 +0000 5c5035d784a271e2589d20769fffb420d051bfd0 2006-10-18 20:51:14 +0000 3a6566773c0de74df1f3cae72892ef0fa23feedd 2006-10-18 20:42:01 +0000 59c8e31d7fcf5067ffec72d4df9095f34c4aab23 2006-10-18 20:18:50 +0000 303585eee8f4caf6631e40df197d887b7eff398e 2006-10-18 20:17:02 +0000 2654b5037ac03af2453ebc29bbcb3b92f0229d2a 2006-10-18 18:01:43 +0000 71bf82018417fbdee412bab4ba3289aa7350fbc2 2006-10-18 11:27:54 +0000 3bb87e1bdae591df349884bc9c6c264fae7e80d9 2006-10-18 01:53:04 +0000 dfdfc8d079e2615b0fb28b799a9f7f628c0ef20a 2006-10-17 23:06:58 +0000 1df4c98d966c672caedcadb3a7498ca97385e166 2006-10-17 23:05:22 +0000 b809f63540770af162d545db91d79bb5d6785a23 2006-10-17 23:04:13 +0000 f70e3c428b541172e94f31928cdcda5dd01b10b1 2006-10-17 22:57:03 +0000 4d05178dc3af03d2d65dddc9a81b737a5fd00179 2006-10-14 23:54:33 +0000 72ebc25b5c85b949bcd577d534b331530359a65a 2006-10-14 18:46:15 +0000 4683d9ed51ea894b89708b27bf5b7bb35f92ac83 2006-10-14 18:32:19 +0000 dd9df94227ccf781be6fe72c60061cf41c59ff74 2006-10-14 18:28:05 +0000 14454aca79be7d6d5d8d0a50db31adbce68a8b69 2006-10-14 17:39:16 +0000 34080664ac12c64b331e3bbd3c2afaad7374a452 2006-10-14 17:38:25 +0000 5a43ee5e33c12c6d0115b9df7e087fe6edb8629a 2006-10-14 17:37:52 +0000 49d17798af7bb597b45e5179b896ccfccbde6eda 2006-10-14 17:37:23 +0000 683b3776c26ddd7ecc13332cf187075def716f24 2006-10-14 17:36:40 +0000 fb34001a1d3958287ebaef2cfe0d7934a71d367c 2006-10-14 17:29:13 +0000 74a24984f41d0214ea9896f1a2ccc28dda8de3f7 2006-10-14 17:28:46 +0000 9f9876445d7fb6187e42b1cd7774e99a26e05480 2006-10-14 16:55:57 +0000 14667be6a379dbeeb14a9db6ce24dd08c12d8d7c 2006-10-14 16:11:45 +0000 0f602ec3347df3d0a38e29e46466cbf78af50d9a 2006-10-14 16:11:18 +0000 b20b6da7d082c91b060c37e04ebf79795a3a298f 2006-10-14 15:10:34 +0000 26164e36adc09fedb81d16c861ef32780abb5432 2006-10-14 15:09:48 +0000 1dccfc3c76b20982d7ee75b692b2edf2b3d227bb 2006-10-14 14:51:50 +0000 31fe71fcdd4f65d06a323dcc1beae7bd52f6c5c9 2006-10-14 14:50:53 +0000 a636f024a831a6e5a6388c3dab7926522107b36a 2006-10-14 14:50:23 +0000 a2c8c2c58a2af5a9fc0cfb5a2d399f30a29662e7 2006-10-14 14:49:38 +0000 755b538cb146b5a842888a589435e69809a56db4 2006-10-14 01:48:43 +0000 449d24ae830682d72dc044754abe8dc15b2c97eb 2006-10-14 01:33:14 +0000 5001b1e5ea56388ba0d84d5c3342b2fe6ab18a7a 2006-10-14 01:31:45 +0000 76616eb39cab18f8a89c7b696848b65f13e0195a 2006-10-14 01:30:43 +0000 51f781c40dafa78dce34f31ab903f1c6e25673e6 2006-10-14 01:01:25 +0000 f775ed97d207a7c2d233bba760c4fefd386ab95e 2006-10-13 22:47:36 +0000 f0657c692a3f799e9da4afd876e7b998843f2d4a 2006-10-13 12:36:15 +0000 8cd8577fb13927996a291aaa381021d295f08c9f 2006-10-13 12:34:15 +0000 116aad1f76d7b3b7384ab6c3e82387540fb0e753 2006-10-13 12:32:27 +0000 a807c01d17f4bcece49bc0fb0a8c1884a155115c 2006-10-13 00:19:39 +0000 3eec907a31ce92089c1b07469f18c9657a56de85 2006-10-12 23:11:12 +0000 468cd27a4377faba7c2eac8dab6894c417e68966 2006-10-12 23:02:25 +0000 cbd0c5346f486ff27fed1856fb4893c61f743202 2006-10-12 21:53:33 +0000 0b3e33aee4a75b05a08728b7cb7d2f8b444d1640 2006-08-19 12:23:19 +0000 ca7d7602e30208fb7f0f34aa9742e41897b70317 2006-08-19 11:53:59 +0000 a10d7eb25ee62d86adbf648850251da07a2aa77f 2006-08-19 11:51:23 +0000 ea898f19e4cebde5172fab67d5bfb96e9ce3f9aa 2006-08-19 11:12:24 +0000 3c8dcf4c803593988c865b31a8628d71a9798913 2006-08-19 11:11:23 +0000 ef68796417697bdb41088fbc61b57b0e7f15c66b 2006-08-19 10:58:15 +0000 bad81b103d7cfb17563471011e011e79be8ad9cb 2006-08-19 10:50:39 +0000 7de51a61f52adea6022d2d9f6c82e8ff74e2dcac 2006-08-17 12:16:07 +0000 4981041e6e69385805c48d16af25fef5483b1662 2006-08-17 11:50:13 +0000 b79155d713d493ad0ae3966b3c7c589dd39aae62 2006-08-07 21:42:41 +0000 df8e74261cafd52548f5f14b7a82d831f779e4f5 2006-08-07 13:30:26 +0000 0ac2841758e216d4b4d2dec896655c6f8fc4de6a 2006-07-20 20:38:45 +0000 1b5db4835f74d15ad6b53874b498cf105e05a26d 2006-07-16 23:12:00 +0000 03f81d4f2eea018ac208e96038f853592a9d9578 2006-07-16 23:07:48 +0000 a80f4e5dcba916b872b8b3f95ab2f8563583f43c 2006-07-16 23:03:46 +0000 034a8bf8214bccf5ec087b00cee41c4055934c55 2006-04-01 15:15:46 +0000 b69badc3b25c5d1ddcd81f71123ef7bcedfa6b99 2006-03-31 11:18:52 +0000 709496c34c87e96e48b4b1ac6df9a178025a840c 2006-03-30 21:25:47 +0000 7d432d25d0b65cf9e3b48b59a834256312e36c88 2006-03-30 21:25:26 +0000 874767a97458334d8a1c97acef669a3f3df6a92c 2006-03-30 21:21:14 +0000 21525eb838d41f4c508ed7d988f8a739948d97fe 2006-03-30 21:16:04 +0000 80bf47430542e5b2fa845d1f324c0c0d384657c2 2006-03-30 21:12:01 +0000 ae8a3541b3a67d1e23feb9c32c21de3673b7bcc5 2006-03-30 21:08:23 +0000 9dddb389a9c3b70148d157c6df3ade28601068e1 2006-03-30 20:58:28 +0000 23069faad58f3227097d95eadf179aabfa129ac5 2006-03-30 20:05:33 +0000 6e8ccbb3c51295c66ce2145ac0a94849c5684c6a 2006-03-30 19:53:32 +0000 64663f67b004d3be7cc29862bd98ddb7adf415ee 2006-03-02 00:55:44 +0000 f4b1fe507d1f530f9712886bbd48a4f267f8c68e 2006-03-01 12:36:05 +0000 76297ac3c2b802bfcc317bab829cb542ff5a1bae 2006-03-01 12:32:21 +0000 f8808288ae2379a8418165c21cd5e0149e5ab8a0 2006-03-01 12:31:43 +0000 0dee32f7b6812ddbc9f6cb987d0eb3690888ec81 2006-03-01 12:18:51 +0000 4bae7997ee9b6708eb902fef0a8b3a88e6c14ad6 2006-03-01 12:18:18 +0000 20754f9a40b0c6ed5b5aa887da12676bb55096fb 2006-03-01 12:16:52 +0000 af9785ca9b1c7ada312fa374ea482d837356fc25 2006-03-01 12:14:37 +0000 58faa90f78a44eb35e19828cf5ff8e5246f72a55 2006-02-19 17:01:37 +0000 d6ddfef5b1d9f7289e388479862947a9ce8a7277 2006-02-19 11:59:27 +0000 7e61a5b0e3e79017c1e2bfb4ea83a6d71aa2c151 2006-02-19 02:31:55 +0000 09adfbf0b02f6ff1f0ac28148c1a9f2307c39293 2006-02-19 00:28:29 +0000 a34f7794b476d42099cdd352fef59be480c74890 2006-02-19 00:25:06 +0000 2f44a5efbf43e7a185fc3fffecd2b9ecfaa11ef0 2006-02-19 00:21:07 +0000 efc7a2f2d16b11ebc264901d8c4f02b78cb6ef2a 2006-02-18 22:08:24 +0000 c07c11ba8d3ebc75cc2abf14b79de9dda58b140d 2006-02-18 21:50:14 +0000 23ddeb50d25ae1b4132023435ef4211188da129a 2006-02-18 21:48:46 +0000 0646003fa0a0d19e9c02d39981215aa679596ca8 2006-02-18 21:35:38 +0000 bbb12d16377fc28f5d3d82d803f80c2d86e50058 2006-02-18 21:32:10 +0000 c5b0ebdb9574b53c6ca37125ecab6973f99396d9 2006-02-18 21:29:57 +0000 e5c90d782f044549b624cf24948d9ad98a996024 2006-02-18 20:40:50 +0000 dbb9a9d9321e7e24c5892804f0d05a2e69119fd7 2006-02-06 23:50:18 +0000 e68724ac688d4be2570a3a95e93eb4c8151ca80e 2006-02-06 22:39:20 +0000 210e88e14aff7b275641b0452c4de8f70020d47a 2006-02-06 21:38:59 +0000 d35053c2f0e7f7d8672bb997ffe11e6e1cb0b007 2006-02-06 21:37:15 +0000 f0d33047a58d18d489029dc2caeffc1cc530c395 2006-02-06 21:33:38 +0000 be4b6164f280db0eec61c1ed119b666af848f334 2006-02-06 21:18:05 +0000 a40dbd8736e57e3b21f5d2d11867773afe57e89b 2006-02-06 21:04:30 +0000 710045177a071db338eb6612cda5a73d7356f92e 2006-02-06 20:28:13 +0000 e1da62b3b54a6e1ad3f935ed6734ca8be5cfa339 2006-02-06 20:26:15 +0000 a50aaa96d8fcd970b80e490572853738f0780bd7 2006-02-06 20:25:27 +0000 b98ed005b1be754de4779db7948c41799dfc7e08 2006-02-06 19:43:26 +0000 34454ddf1c9684e1dbd2acf432ea07caf2243d2e 2006-02-06 18:39:17 +0000 13996691c16c568fae1cd38bf38e0161f0a63f56 2006-02-06 18:38:44 +0000 d3a64ca4087c0dce7c659dd894686ffc816fbc89 2006-02-06 14:58:22 +0000 edc97f5c9d33b436292b7cc5494a6f18a57331ce 2006-02-06 13:45:33 +0000 53470193e22b41402122fc1c9b306fb3bd85cb8c 2006-02-06 13:43:48 +0000 efb7b88db9b137280105ffab55d2ab7bee7b551d 2006-02-06 13:30:26 +0000 9ccdaa182d6997e9596662b16deb0b0e477d54c8 2006-02-06 13:24:33 +0000 28354286868b8088b2f937f0d18df6f2e4cdb6b4 2006-02-06 13:06:03 +0000 2cff42f0cc106c19f6ffb1343e07859e8550032e 2006-02-05 15:54:08 +0000 cc5bfe25c71b571fd71c3c4103db0a7be9f6df18 2006-02-05 15:44:34 +0000 8b8975fc135dd7a31bbd69d45c16ddf1024975c6 2006-02-05 15:33:52 +0000 5183048698b12d0b535b4b317f35c4a0578ef812 2006-02-05 11:05:29 +0000 9784b14d58c35ddbef97d82d854e528a3f65e5ba 2006-02-05 01:42:30 +0000 9f5ec52f635cc0f08616ba93624d28bed1c15468 2006-02-05 01:03:16 +0000 33e8e5a070cb418af54f32d46ca821deeeb159b1 2006-02-05 01:00:18 +0000 a0acc362be40a19d419c218b729471b4c0e3faee 2006-02-05 00:48:42 +0000 e183639bc3f98bf30b315d4220f633fced8350d2 2006-02-04 23:21:48 +0000 5c772aa7f8877d54dcf875834230b71bea41a7bf 2006-02-04 23:19:45 +0000 99b172b92d4181692b65975b34de938d92334a73 2006-02-04 23:18:33 +0000 dfa799fdf92327d7287dcb58af83c18e0c2484b7 2006-02-04 22:41:07 +0000 17ce2164b30f342f89dac09ef6245054f27d199b 2006-02-04 20:47:09 +0000 b73b5eb1f2c675807116b51d37d8cd43fe356b2c 2006-02-04 20:34:43 +0000 f5f0bf8286e0f7e15b8366f26c5b79b64a8c1bb0 2006-02-04 20:34:12 +0000 6340a607d6c135488bcfaf381bb3a529f5edaf74 2006-02-04 10:00:43 +0000 119a34559b1e08a9ca517e44e3804c8e0f22e6c8 2006-02-03 00:05:14 +0000 1e91469fbf244b9fa202ba40c7a94be438865f46 2006-02-03 00:04:04 +0000 1bac6c89ddf706123dc23e50fc04af908565ae80 2006-02-02 23:55:57 +0000 1e20ca71482ab725dd8f122502245b090f7de053 2006-02-02 23:53:07 +0000 b076d3ebdafcbe7af65531c614daac7692610d3d 2006-02-02 23:51:44 +0000 c2e4c654ae8ab92d2e797c667e16d498c5bb2e6b 2006-02-02 11:33:08 +0000 d751fe9f018964f24493ded70c319d426581d7bb 2006-02-01 01:23:57 +0000 f331e0b199adec8fd6ba9037efa6dd776ff41978 2006-01-29 02:08:28 +0000 d23258f75b65f6f31bcb1949b3bca1f2067aabe5 2006-01-29 02:06:12 +0000 5bb28ccda817aa8cf93696af375f1c85daaa3eb7 2006-01-29 02:04:19 +0000 293a79fd5097f2cac0750e9474a2501a5e90a82a 2006-01-28 22:12:30 +0000 75984ea90e4759a5455c746667e983ee24a2bd77 2006-01-28 22:03:50 +0000 86d16eb9fb27b150906a15f270497b2f55dbacbd 2006-01-28 22:02:04 +0000 c45f498f1060b582efe436a064c22bd602aaa7bb 2006-01-28 21:59:59 +0000 365374eef1fb0bc12626b015b3a5e791c9ee4dea 2006-01-28 21:50:30 +0000 4184061abe7ad699370aad4a8b047dc79847d245 2006-01-28 20:22:45 +0000 7a6ffdf8949b3338141c7c9aa202ff95ba9f5ad0 2006-01-28 20:22:20 +0000 ff78568498a1abdcacc0d82cfa2741a3fa55bc37 2006-01-28 20:21:10 +0000 6722e5fca54ec37a3c97542c104915d3f9318934 2006-01-28 19:40:52 +0000 f892aa696b0bcbb2cef32e53b9e6d0f1dc665224 2006-01-28 19:29:08 +0000 1a9af17a0165e91f722f7d36690712fc8dc1a9f5 2006-01-28 16:53:37 +0000 0aca079e7ecdd0dac841755008977146c774e044 2006-01-28 16:52:32 +0000 1b4ea95cf898440f55e6a75abc2922a9c25b7176 2006-01-28 16:50:00 +0000 ca674d35920d84e5de4587c4d786b91923e6a879 2006-01-27 21:58:55 +0000 b6ac38e2bd70e4aae2d18652ee05dbf98e913750 2006-01-27 21:40:50 +0000 e459f34b88fb83adcf5527668cdc5f2ee9d79477 2006-01-27 21:38:08 +0000 24181ff46676f47d69e1aa5fcf89df625ef24587 2006-01-27 21:36:29 +0000 e2e44f550605ee2396703b26aa575249482035ab 2005-07-01 04:51:58 +0000 cf50c70790ba693bc97aee36d540c9f23dcd3770 2005-06-09 17:40:58 +0000 1b87d89a6d9d47c53aea49d98bc06511a007c653 2005-06-09 17:12:19 +0000 87f7545c6a50e3b84887ecab1d363f19246b85fb 2005-06-09 17:01:56 +0000 093813ebd5794692b9be10818fc03a62abb6c726 2005-06-09 17:01:35 +0000 c199add3ba2a0e4a4691f879549bd26f82599d8b 2005-06-09 17:00:32 +0000 5aa5ae2ec09112eb866e3d4a24e3fe3e982c52cf 2005-06-09 16:57:59 +0000 15e51fe11b4cbd60826dad59baa365ec8419b29c 2005-06-09 16:56:17 +0000 b43fc5d687e70a17428abc9b204021bb4a4a844e 2005-06-09 16:55:57 +0000 45a32afc08fb17f900a036ce87877bb365dd6d90 2005-06-09 16:40:19 +0000 a64d9f632a8e2813cc5de107555b4905a5bc9470 2005-06-09 16:28:47 +0000 438026f99bb99c81277b1373f5db631741b0dea1 2005-06-09 16:27:16 +0000 1ef3513a3ce6b87aae5618b94def59181773b4a3 2005-06-05 15:57:26 +0000 c7209398741453272d72226c2d193045b12c4f06 2005-01-08 12:45:24 +0000 6b192ecdfc28868c0a323f1783c052ac4482a2ef 2004-08-09 19:31:44 +0000 9ebfdf3d73b7ad31dd2badae70e30eeaecd78b74 2004-07-29 22:25:18 +0000 606ef382340d3519ee72609f5da9e442aa0164e1 2004-07-29 22:14:11 +0000 30609c6e20a6b1f8cd21aebc368482e59de79f41 2004-07-29 20:50:35 +0000 f8d0c19e1722a3e16eda9e21d305340d2aecabc3 2004-07-29 20:20:26 +0000 2ad1f63147b857512a3be0609e7318b87f3fdb5c 2004-07-29 19:47:13 +0000 3a8af43ad6f5355ec19317e4d8945af2b3f8cf20 2004-07-12 22:09:42 +0000 2f8408858383015ba1338ce0ee1c4f0cef332836 2004-07-12 22:08:45 +0000 85870ef242ae43ed8321e8954df662189221e619 2004-07-12 22:02:12 +0000 8780446c1f1facb77ff5f00838c9983c3ee8cce9 2004-07-12 22:01:46 +0000 15213e697557717512dbf6f606f6eba95a425add 2004-07-12 21:15:34 +0000 9696c38f7f3a42c62b0a503e67e924a19a667117 2004-07-12 21:13:03 +0000 2e7adfc91a3c1907ab4023d4b206aae934f9895e 2004-07-09 22:12:14 +0000 fc06dc38bb99f3ccf906cfc1bf9a8b5ad8bbaa5e 2004-07-09 21:52:07 +0000 e27d76182fa6b8adce34c6339479f07cb7fd2989 2004-07-09 21:51:24 +0000 259eecde397cac7de70c9674fdcf946a36c3cdb7 2004-07-09 21:49:24 +0000 dde7fb0c741224410b76dbd6308c1173b0ff85c4 2004-07-09 21:48:34 +0000 c2f78e3d150217ba65352bbd6412b4c2d0faf77b 2004-07-09 21:47:15 +0000 4367472eff8f5ad5efacec44dd2fda0717dd3761 2004-07-09 21:44:04 +0000 66f2d2079e8656494ca4c650204385e184eb6ac4 2004-07-09 20:51:34 +0000 5d8c2113714344a21be0c169a1133beb89c9c655 2004-07-09 20:45:49 +0000 b3e9c9d9e1e4af87d82f57d49ee2624e054089e9 2004-04-18 19:08:30 +0000 8d1e114607035ba1e1b812bd94f1efd4b63f5a8b 2004-04-18 19:08:03 +0000 d42a78d1f5607cbc17264eaa3eb21885e991bdfe 2004-04-18 18:49:35 +0000 d37722f46160cbe45856d3943b7b210671f57e44 2004-04-18 18:37:07 +0000 3537d808aca7e279cd078165a4f44f6d8dcf9e33 2004-04-18 18:35:56 +0000 9778ae096beeba3c95e6590772fc9f2c889e42bd 2004-04-18 18:35:37 +0000 1e74aa42299e16ac3d22b1279510c9d5f4f4f90f 2004-04-18 18:34:45 +0000 91e29360302b562a1b2ee285f6f1c4637158b73a 2004-03-08 21:20:25 +0000 c00e7e6e47abe8b1167db4f64aaaaf6071df7748 2004-02-27 23:41:46 +0000 292d2aa2b83ce2ae4cd225dd8e9201e56def603f 2004-02-27 23:34:52 +0000 959fc7d8e387f239cb497537c01a4487994a417d 2004-02-27 23:21:55 +0000 4c8a89493abb87f9e8083ed8d5ce2b4d66e4ec6b 2004-02-27 23:20:34 +0000 ba71337722300ee22438d96a0bde46a9850a0409 2004-02-27 23:18:00 +0000 83484cd4637b336d5dc56c89920a5852c26f3724 2004-02-27 22:57:05 +0000 71df641e062cfab836ecd19c8a83a419fbf9dd10 2004-02-27 18:45:09 +0000 a46de9b176337e211d6dd20900c11e6c35b35202 2004-02-27 18:39:50 +0000 c1ba407a251de7e68631b3392c180f11e065d1ac 2004-02-27 18:37:58 +0000 32b4fee4a985ff9632dada75ed58b2d32271bf49 2004-02-26 22:57:06 +0000 de5b379beb2e2c9c28f099116144d683db9bfd77 2004-02-08 17:26:50 +0000 dbdb29e50a154eb454331dd70eac98da69739c4d 2004-02-08 17:23:30 +0000 dee1c9514b897e7b8d6d514e73b81aa8412fe607 2004-02-08 17:22:26 +0000 c8b996dac4433b0f75994002b545447db39703d1 2004-02-08 17:21:47 +0000 41a4ef5748d6f73d2a4b659da37650116a228028 2004-02-08 16:49:50 +0000 aa47ecfa21a6a57e4d1d98bb8e6972bcb6a19aa8 2004-02-01 21:03:30 +0000 f3ba17cc598514f765a40bf68fab4351be0709d1 2004-01-31 22:53:09 +0000 413db5a95b7bc8e91c4c8180ec4d13b00dc86750 2004-01-31 22:52:38 +0000 332ea16d964bbc87915aa65528f3e2d84d7c8ba9 2004-01-31 22:46:15 +0000 6ca3a39b851cbcbcb051f407d7c6fe5e21fa9c11 2004-01-31 22:37:53 +0000 bb414bd4a7d351b7b6eedb48cb9585a8845d15c0 2004-01-31 22:25:51 +0000 c70dc905a864f3ea3b9de0979f12cbdb6fd23fe2 2004-01-31 22:17:59 +0000 6bdc647903658884e5931ff2c77b307886797f35 2004-01-31 22:17:39 +0000 db75d0115a270946801be71b7f5a455b09b5b131 2004-01-31 21:25:01 +0000 4c5900a4f02212c018a0c4cd44d5cd5320b61dbd 2004-01-31 21:24:32 +0000 98b4d4c7e78d7df21a3fb267866444205d9c901e 2004-01-01 21:31:56 +0000 6120ec64d8925fb61c0db8a19510f29bf2d41a46 2003-12-30 21:05:09 +0000 24c6f23434753b33b8588584ed7c63be7fa14233 2003-12-30 20:50:39 +0000 059056ecde3d90b293d4f3009d035092629233e7 2003-12-30 20:45:00 +0000 87765f85d8654f7b0782a16efc97720f75b57a19 2003-12-30 20:41:45 +0000 f98444989eca6c5289553237136dca0bfa2081e9 2003-12-28 19:29:35 +0000 9b1e7731ac3e80d455f31863458f4681876fb27d 2003-12-28 19:18:28 +0000 d458fa0f01bcdb51c523c6300a4ff1c05363eccd 2003-12-28 19:17:29 +0000 81a5e60f62ab15d55b41c1b039ab5cfdbc843c51 2003-10-30 20:08:11 +0000 6f1a9179411f724469b91b603b239b0a1592afb3 2003-10-30 20:07:40 +0000 fda7b0720f3093020b2b7fcb6ac89cc911eccb99 2003-10-30 20:07:10 +0000 1bd9f2e35b34722d0cc9476a765f4bca1f42a349 2003-10-30 20:00:23 +0000 2c6b86883fa46e42935917a4efd849849b1c6010 2003-10-30 19:56:59 +0000 8fefc22e2992e4493f8b85c8014e48fc91c8538a 2003-10-30 19:55:22 +0000 2c339acf43bb2790f72682381fe15617ef83d0c8 2003-10-30 19:54:17 +0000 1ec5d8c90b109ecf2d7c368434f97698112858c2 2003-10-30 19:52:31 +0000 50b601b90d2bd94bc34a25e8ac8f64d599cddf75 2003-10-30 19:51:09 +0000 72f895916692d488ddec89dd0f0d4e89e33d8179 2003-10-30 19:49:53 +0000 04cabe9f813b9862a5cc6061b920175aaeb1f6bc 2003-10-30 19:25:57 +0000 4375d56b4a62a8675bbaf280450ab42c9d3ce5f4 2003-10-30 19:25:57 +0000 6fd9af870132c6c1032f4ef058160522653d976d 2003-10-30 18:51:23 +0000 f573e621076dd573bb5d46c4415d6be3c3101882 2003-10-29 23:01:50 +0000 113beb5c2ac350970e25560360a68c9eb398e99a 2003-10-29 22:59:51 +0000 87c58ee53026120dd75d1f379198fb4a2a8bf819 2003-10-25 23:06:00 +0000 74ea296f38b626d4173bf7dfa393b3418a7861ad 2003-10-25 22:26:20 +0000 b2d7a19be1efa46b6b0c30cf4d477532278d4c29 2003-10-25 21:33:56 +0000 7a71ef707194ef9b9721a810986479998c0ae1e6 2003-10-25 18:35:49 +0000 baeddd847608eb1fc4c7a3bebbe14e1b0b434036 2003-10-25 18:13:12 +0000 7d0613e239c631b4393f8889ded4de8705eaf109 2003-10-25 18:11:59 +0000 cbca1271f262f42581979ded0bb8b10ab4a4083b 2003-10-25 18:10:15 +0000 30e40b2db88507bf5c565d1ea80ee4bee3586b92 2003-10-25 18:08:53 +0000 0adf2f75734a74b98b2ba9609f918f703ee51100 2003-10-25 18:02:52 +0000 aa8d97ffb50e091f4af888b4351d840a53f01c06 2003-10-12 20:22:23 +0000 6c06aff68fb1c208e75ab62dc2258fc212cb56f7 2003-10-10 18:23:39 +0000 246f95ada04b99cb9a0d2f96ef88e04b6991f118 2003-10-10 18:23:19 +0000 371974bf88cbbc0c78f25a3a07a331b5526631e2 2003-10-10 18:11:28 +0000 49c8c9860601f73183b351797a17a460148bc825 2003-10-10 18:10:20 +0000 29d192952f4a75c44a34b510676790ac30bdf4cd 2003-10-10 18:09:28 +0000 adec0d01108537b453b0f918001a11fc4175e2a3 2003-10-10 17:57:16 +0000 20f70bee7305a4db2409020f3832b6449f8556fd 2003-10-10 17:56:55 +0000 fe5e5e2059f007d2b47c3959f1da8243c81f6a53 2003-10-10 17:50:01 +0000 8dd38feea99184eae21e63872c83b27a882ecf66 2003-10-10 17:48:07 +0000 bf2e42b64ab4273b61b113e896eb973878a62108 2003-10-10 17:47:23 +0000 50aeb83009f9dc1f76f90378c004a6a5c256534e 2003-10-06 20:03:56 +0000 2d03f6315e4508eb22c242a09400bfe1b9b5cf1e 2003-10-05 18:54:38 +0000 748b710a69d0fe824a4006959be9b4ad61e0b5f7 2003-10-05 18:45:53 +0000 cb94a096af8428c4024ddb332c7dbc34ef433713 2003-10-05 18:44:34 +0000 ecb985edf672abc5a7ef9c9643c0d0dc5502b41c 2003-10-05 18:26:07 +0000 65f7300bc005af25eb042fd3a0db36eda7e008f6 2003-10-05 18:25:39 +0000 60ca5098b431512959be41ee92353d8a1ba6cc96 2003-10-05 18:24:58 +0000 ad4102279aa277e7265dbb57d3cb2ae6e3a7b16a 2003-10-05 17:57:28 +0000 d0434f9ab00a890307aee80f7232a61568852a8d 2003-10-05 17:55:57 +0000 6b29c0d8d2e4fa36028328eb30ed49a67b0834ee 2003-10-05 17:11:21 +0000 df3dc0d856c59478de1c17063f84b3221038ffc4 2003-10-05 17:10:39 +0000 29d010bc66957bf7c1500cc9616b1a1ec3339440 2003-10-05 17:09:59 +0000 1939a5ac0635fec851c576a71fbeab90b6c7de21 2003-10-05 15:53:52 +0000 8e5e40619aff45c9bbef5b69d422fdf3b46f86c8 2003-10-05 15:51:23 +0000 cea9f5fdbc1a2c22353c61bb0e077607b2158afa 2003-10-05 15:51:08 +0000 1bbdb2677e28c9f1eac23e49c9a830eaf7e7d87a 2003-10-05 15:50:28 +0000 6332566cb7885c046f48cb8e8bbc584eafb8de31 2003-10-04 21:50:13 +0000 51dc4a585f577bc679577a193ec12fd3e3b7ae81 2003-10-04 21:48:03 +0000 bbc24ecbd22255e9f7f7fc859927c11c85833dd1 2003-10-04 21:47:28 +0000 b538d3d7f502a7f8adbd3d8c8f39e51771de26b4 2003-10-04 21:46:25 +0000 566e28572bc5a782fcef4f20f39560dd5701fbb6 2003-10-04 21:45:37 +0000 7e1966b7757e30807df323c252462ce58b590b26 2003-10-04 21:45:06 +0000 d74ab753fa894839f4c5a3ca1793016668db51d8 2003-10-04 21:09:12 +0000 08b77c5184f62e095cc42fa6b6214aa510ef2a2c 2003-10-04 20:53:04 +0000 1901959bef483af66a41571638a0e2d2337ecd4e 2003-10-04 20:49:08 +0000 e6de63f1644606d855cae00a0b5d6957aa25f4c4 2003-10-04 20:47:06 +0000 21ef1becc865be39828804d028dd3b623efe0c0c 2003-10-04 20:16:45 +0000 100a1b838af7c357fee88590f167bc34a8a470c8 2003-10-04 20:00:03 +0000 dfff0cb22387542a9bdfe0ec3c904572793572ac 2003-10-04 19:55:16 +0000 e3ea216ae3097412f85792d4d258df989978b096 2003-10-04 19:51:48 +0000 bf6c25c4cf672d289a539716741c80c95845a5a0 2003-10-04 19:29:32 +0000 429aa3afc896a05993a357755aa9731c7039242a 2003-10-04 18:59:55 +0000 30e543570160a73e513bfa201c6ba0ba6b1b9012 2003-10-04 18:59:29 +0000 f1492fb1dc8c5ac255eabd747786a2476ff289b3 2003-10-04 18:58:37 +0000 6d749d560381fbe1783bd2e31be8b3fa6c13b2cf 2003-10-04 18:19:03 +0000 aa26052a7525fb587f5cf21bf223bc87d5b945ec 2003-10-04 18:01:55 +0000 d17d6dbf91b23b70a29e6ab8fe198edf5bc1892c 2003-10-04 18:01:35 +0000 13a2831d686480e6dead26c5cc46bf694461b41f 2003-10-04 18:01:07 +0000 fc3cdefd1b0a07cbcf413e7560531ab45de1f56b 2003-10-04 17:54:17 +0000 fae4666ee5d0cb3720491038bb8b347369b62f5b 2003-10-04 17:37:14 +0000 5a2aa918c9afa205b47e59bf69c3de37b042c47e 2003-10-04 17:27:21 +0000 5a245a87472d12cf159c0bb654764b3422822305 2003-10-04 17:24:47 +0000 7d7a42eaa7a0d8a1d1dfaed524a66d703c9671e3 2003-10-04 14:43:44 +0000 8d1d82a525203bb831043d0130b16edceccfb658 2003-10-04 14:39:23 +0000 c175e0dba6860c182e5d5f9a688271ec8d267cfa 2003-10-04 14:38:01 +0000 e51ad8256e60331fbb565f68d605a2dc55393095 2003-10-04 14:35:04 +0000 b732b1c74708849b3f7b4c850612084e3c3f3fdf 2003-10-04 14:33:44 +0000 0fef0c7a080f10c26b6d1af45ac2251fca3a90f2 2003-10-04 14:07:53 +0000 a31d2f88b4932a49c0011d54353669ffd617203c 2003-08-11 20:35:30 +0000 f498f86933acf0656525c5afb8ed3f43a54d76b4 2003-08-11 20:32:17 +0000 1891cc709d71f8f6a957185d4eab5828ea2fa1cf 2003-08-11 20:21:16 +0000 47f7b71b6f6ba4272261187cfd34e4c6779d87d3 2003-08-11 20:03:41 +0000 10ae2ae12014d8c9a8c5d51f676acf620bb67a70 2003-08-11 20:01:23 +0000 21dbff0003caf14e09ed7d795b40ba9ce311a8c7 2003-08-11 19:53:24 +0000 ad8e135de0ba720af8db1d81c1b51b6abbba9b20 2003-08-10 21:41:36 +0000 6cc36c8cfc119fc861989609697a1a01984cfc57 2003-06-09 19:37:56 +0000 7c33dc2a37b828605b729b5aa913fa84203c9b69 2003-06-09 19:33:00 +0000 35b492c6f398aea7bc82d292add2f24cc26a8193 2003-06-09 19:31:11 +0000 2ac3e3daee227f60f118bc0da965355ef4396f25 2003-06-09 19:25:11 +0000 0bebb5381df6052fc7a78c96f035d343a6275432 2003-06-09 19:17:15 +0000 f58a322211cf8c95f938b536ab5a3a524e8123bd 2003-06-09 19:14:10 +0000 28b1dd6b283bfd683de52e10ac0bc941f1c4d78c 2003-06-09 19:06:01 +0000 bfd9c99e7d0bb1bd44a997e8bdfe37be6433a624 2003-06-09 19:05:12 +0000 37fcd66ab2d0b842daaf9b4fca323082fdc63244 2003-06-09 18:38:05 +0000 d83f220cce89bf6ea890e747e2a298e1c736311f 2003-06-09 18:29:32 +0000 147d59af6dfc291790772484b94a0096bf33945a 2003-06-09 18:19:01 +0000 715b9c01a120fd0b6052d80f48499e32db6738c6 2003-06-09 18:16:45 +0000 8d5a24ced345565ef802d1782713d336f404e442 2003-06-09 18:00:13 +0000 2005b37968d7eaaf2c31997fe454e9667dc2e8da 2003-06-09 17:42:43 +0000 e10e51fe2001a5d5745ac702ac07ae6c4b5c3d62 2003-06-09 17:23:51 +0000 21052a55ba9f0f7191c373e4959ae62154a35fe3 2003-06-09 17:22:57 +0000 1481b2223e6292f315596d4896a259ac63200a9e 2003-06-09 16:53:22 +0000 1febce624019fb3305a9a503c671d5818aa51672 2003-06-09 16:52:57 +0000 4491659bea5e676592e7998371bda6f347518839 2003-06-09 16:45:56 +0000 433b30f85c57dd167c1515ebb1f30cb771bcbc9a 2003-06-09 16:04:49 +0000 e439ef5262c1466c6a205d5d3933b62301d9b5ba 2003-06-09 16:00:38 +0000 96542ca6b5eba2a85d1a6415ff380c19cb44e37b 2003-06-09 15:56:59 +0000 a969b12f42eaaeb6cd759620a47fb31ae411dd8a 2003-06-09 15:53:50 +0000 4ea01c5104a0597dd74c934e1e2fb9f1032ac2e5 2003-06-09 15:52:02 +0000 b702aae1c49b8c2bd42b3079d935e30025973e8e 2003-06-09 15:50:47 +0000 9d8d0ac2e0d5f77f31740cd5dc938586d7f59b64 2003-06-09 12:47:29 +0000 de8684c9435f6c2713b4305a9e813910f7d066ff 2003-06-09 12:34:09 +0000 d3ef709456c51628925b29985bb96b2a3b3aeb36 2003-06-09 12:32:00 +0000 1b6c7b44513647de073e418eccb7ffa0fdfb3f4a 2003-06-09 12:26:28 +0000 c2e98e292d7ab0046c563d21b089a2ece7f3345d 2003-06-08 19:47:13 +0000 eb20da006eca11b10eca41c3a39d505d6216cb3b 2003-06-08 19:44:32 +0000 cfff8c3db6b8375664ba5982b869231766cd0e7b 2003-06-08 19:42:06 +0000 3a5666a824b41dcb34b5faf7e1eeb37f91e220fd 2003-06-08 19:05:38 +0000 160daf5b0a94ee0512540bedaaafdaaf88c02b54 2003-03-16 19:21:26 +0000 51fd597e35ea5e4a4e262fbfe31fefc34d14a33c 2003-03-16 19:20:54 +0000 cd9b8e32be3b44ad36c67606b964a85021cfefa9 2003-03-16 19:13:36 +0000 b0b8bd39ed17f078fef641d0cd607360fc3bfc1f 2003-03-16 19:11:15 +0000 0a4ac626c5b84ced2fee0f24fada42499a5fa98f 2003-03-16 19:08:30 +0000 dba658ea1495b1d900886f7bdb69edbefcc4409b 2003-03-16 19:07:50 +0000 f1fe7c7a37405f04528093bbde1f100f94b8c2bb 2003-03-16 18:54:47 +0000 d46da97dc874d78306a975cc618bf57dcd849830 2003-03-16 18:53:00 +0000 f786d968f0a932679922d7e4d78451850d3a22ca 2003-03-16 18:52:10 +0000 f6ca14c3dddafe17ca8730b8e3d0391aeb040192 2003-03-16 18:46:34 +0000 49fe012c8a5f24f80b9cc5dd974e7aee4bc09993 2003-03-16 18:41:36 +0000 a77ad580f2afa4c463a0410169a8014bd3a8f87a 2003-03-16 18:40:47 +0000 31f7934104aaabd735f50347e064294c3ddedfe2 2003-03-16 18:39:33 +0000 51d91513b685dd9ec0fcd30cc4079fc969bbb151 2003-03-16 18:39:07 +0000 a9fe73654ee15cb5d40e3c72289ac8e065726440 2003-03-15 23:03:23 +0000 686bd8c39f6caa84e7b7bff986663914b0d06db9 2003-03-15 22:44:00 +0000 8f9bf8c05e9a591b09e90c607d9841bc40031a2d 2003-03-15 22:35:37 +0000 9dd038c4ed75dd043a3d89b2de1e6cbf778c2dbb 2003-03-15 22:22:11 +0000 f40e7795d634d35d7534c3064c3e65547ce9aa96 2003-03-15 22:05:51 +0000 b959a007165d6350f8cf098394ba0d843bbb3605 2003-03-15 21:57:39 +0000 c3ccda9125e8826da23b8f3db2f75f417efa5882 2003-03-15 21:54:05 +0000 1a4ec4074f1908ac2a87242f121cec4f7e11d33f 2003-03-15 21:40:05 +0000 71b4e5e23cfef785c3136ce09a23e8bd2cdf6d72 2003-03-15 21:38:51 +0000 0a1ef28f5917d25377c807a20005ae72821ab9bf 2003-03-15 21:22:31 +0000 1ead266501ef9dd5876c8324159371478d631ca6 2003-03-15 21:21:37 +0000 7f0fd434c82b98aa8f755dd1cfe7f1020b797d6c 2003-03-15 18:57:22 +0000 29ca502cdad55a53a8250e53b82cfc067df3a217 2003-03-15 18:55:45 +0000 4faa1d0d8dbc6c650521cac94200d75b667db8cc 2003-03-15 18:52:17 +0000 ff666749b632c91991d72284177ff18b3dc8accf 2003-03-15 18:50:17 +0000 78447a5234843621cb65a805dc030d898c3993d3 2003-03-15 18:46:17 +0000 c139b745ac2486a009a59bfc0fe8c7658ccbc456 2003-03-15 18:42:20 +0000 1a9240094e7935378e96699added6f06ec19e56e 2003-01-26 18:16:57 +0000 003a40a10b6df8754bb9a3f9bd519315963923f2 2002-10-22 21:14:21 +0000 1372b96e3bea02a3700b110491e937e11130f2fa 2002-10-22 21:07:53 +0000 05cd435882884f3e30f06f417b0cca3e92824c31 2002-10-22 21:01:30 +0000 63f31b87b3343cf3522ccad8bafe4c8497a13734 2002-10-22 20:58:17 +0000 9864aa0befbb2719007e1f3c540dfd408a20efb9 2002-05-29 11:50:17 +0000 166654a407e89da9cfdcf4bb24875a3b9d9f9d8d 2002-05-29 11:42:12 +0000 ffeb6a4e7b534d6abd594d121489e862333172e1 2002-05-26 23:57:13 +0000 1eef91169c646a21e7263bc6d5bb48d001ad5482 2002-05-26 23:55:41 +0000 6dbf67e913e676d2511841e4d5459b6ae15d72f6 2002-05-26 23:44:08 +0000 04ab5a54489bed14c7f7a4e85d2643a7054ffbf6 2002-04-20 23:05:59 +0000 85b8c11b21829fb99e2472bba2440d956c7a8027 2002-04-07 21:36:04 +0000 99e6d4a2c5a0f52e27d37de41d0d308e074d7828 2002-04-03 22:05:59 +0000 737c9c16bf3fe44ccb4adefafbcbc18072200f4c 2002-04-03 21:57:21 +0000 f860d316ad50db66e7c8b11f1c2f8b6c3a4189d8 2002-04-03 21:50:24 +0000 3e90b1b0dc4ee0d3861823313a99bbc322538425 2002-04-03 21:49:42 +0000 0020c2923f59656ee35166fc8028b2c426935af6 2002-04-03 21:47:41 +0000 63a31d79fc6d7c7db52e7dc427ccaf4cd925b589 2002-04-02 09:10:51 +0000 fc1a1df64b691672233e5f12fdc3cd54c03fd722 2002-04-01 20:37:45 +0000 0eecfb0698d5b2d3c32ffe2c07cd4afaf46a6e10 2002-04-01 20:32:29 +0000 66d52c85c23a0c6a6023895f7e899110d7c88a96 2002-04-01 20:27:15 +0000 79d49ecaba7e3c88bbef553f88e0a6521663b073 2002-04-01 19:48:30 +0000 4723dca546a939ee0ca32e1ff3a5dfe5b138c719 2002-04-01 19:22:27 +0000 ac9cea0097060a9bfd3e9ddfde095f77cf7fb559 2002-04-01 19:18:20 +0000 dab8d2f94899d6e39c5cf44c938dc0a8059af9e2 2002-04-01 19:15:50 +0000 e274821cb6eb77d973e27b59f59b97943b953adb 2002-04-01 19:13:56 +0000 555cd0fa0b6a665e03c782084d352f32b6413874 2002-04-01 19:02:07 +0000 260d502d3f215092759acd12b039edbdc3ab3949 2002-04-01 18:29:30 +0000 fa6fd2b51c6d5df939d38fecf734ca299d5c730d 2002-04-01 17:41:55 +0000 53bb6503b8eec57546e4de732134a3d3d61a6b2a 2002-04-01 16:54:56 +0000 c5bbd756c39c7dd5e41b675c22c0b524d99bb44a 2002-04-01 16:54:35 +0000 58baa1c01679f09f0af22f14782a35dda162e3b9 2002-04-01 16:26:16 +0000 6bc1598eec42365d8a446205cc15a4f6c6506e7d 2002-04-01 16:25:38 +0000 21d56106487fb6bd6ff58af44816c62db9bfd087 2002-04-01 16:24:05 +0000 11c06d8337b1b099f4187e78e8c81ca970158595 2002-04-01 16:22:57 +0000 0de19ee9363788cf619df8531f06d46f1fbc0c30 2002-04-01 15:07:39 +0000 8ecd52b03684752d8e7abe5f66ee531af408619e 2002-04-01 15:06:36 +0000 2d44067315d7d48097b73b5bd4ed0c0387be1f15 2002-04-01 15:05:55 +0000 96721385347ac6fd67a73cc8fa842da7c5cd722c 2002-04-01 14:31:38 +0000 efe7eb078ff6f91c91b24351dab44327caa228c2 2002-04-01 14:14:16 +0000 c342d2f5be81114ae7f917feed10da61fba1944c 2002-04-01 13:58:28 +0000 5809390c1c8d213011b7b546303f6d6f41a1a914 2002-04-01 13:56:33 +0000 091e6cb64a7edd5a78b33f67dd521b6df875cdb7 2002-04-01 13:45:11 +0000 cccc2d44327422f333bcb681e80bb48dc3b04abf 2002-04-01 13:40:05 +0000 7760bb1655f5ebf1bc064df5505767d7331c8fee 2002-04-01 13:05:04 +0000 00fcb45c2d5c2ec3922c7002639c0f234206b863 2002-03-31 23:20:35 +0000 ebeb60ff34b2d0e9b92516d3ecc286dfacf18a18 2002-03-31 23:19:01 +0000 c0cee0002080cc472d22a7aa94218721e6664edb 2002-03-31 23:18:06 +0000 24e12bcb6cca757e5f3daa465f417b474f13c834 2002-03-31 23:17:26 +0000 0de437c433283764435e99e2b84ea5c99a87f23c 2002-03-31 20:36:28 +0000 46eae1ff960b0b478b5999e5ff5e2407af7ee55b 2002-03-30 22:57:47 +0000 fbee507186b1f97b4118f591f9123ded8b1de725 2002-03-30 22:53:42 +0000 535beed99fa962c20a44d008c71c29c7792d2ca9 2002-03-30 22:48:53 +0000 eff13ed57afc8b1503e7eba7934016475d56dccb 2002-03-30 22:46:43 +0000 3bfbb14a2c25231798d178e4b2e3f7e68e35aafa 2002-03-30 22:44:41 +0000 c622a1920a7000bbadd62189deb3ad35da6b7012 2002-03-30 22:38:12 +0000 e4be3e861085640a80c84798d63488f7f3b5e18c 2002-03-30 22:36:31 +0000 b9a01a1c2c200d7f09c6211c48e493b8d9e5c34f 2002-03-30 22:18:20 +0000 046b754c68042bb30bae045c4cd2b391c2f55665 2002-03-30 22:14:53 +0000 fded3cf504d8c90f58e5d8a2c22ceb91b709f9ce 2002-03-30 22:11:59 +0000 4367105bc97b63e80e04710bd94812fe60e87e22 2002-03-30 22:08:45 +0000 8b964abba1db20ba1841733b3097bf8799354cb2 2002-03-30 21:54:38 +0000 8498db577333c2a0400352425c7d7d056e0fc904 2002-03-30 21:50:25 +0000 c955585f2fde7af6887fbc5667cee93efa6e16c0 2002-03-30 21:22:24 +0000 c2a46c957a00fe58599893fde72fd823105e5535 2002-03-30 21:20:28 +0000 4667403ffb5931c37e11603aab6c5abfc7e81c55 2002-03-30 18:56:56 +0000 88cc06dc4f8c15c7e57030011ff680979aa8a1f6 2002-03-30 18:46:59 +0000 d2602b98fd78336821237e8139715ec89f081f82 2002-03-30 18:45:55 +0000 8a497e659b8bbc7cba0dc572ad190466f2686a9c 2002-03-30 18:40:23 +0000 b3b1aae8d0953b451be528cc4e031d8739379b11 2002-03-30 18:38:23 +0000 1761ce206ad349d18b92de2219b0392e7587fec9 2002-03-30 18:36:39 +0000 5612e72b89c85d1f1345a02b5371fdd8d198c5d1 2002-03-30 18:29:23 +0000 1b6121d007a5bf55f917bef7e9865edeb9b1d42b 2002-03-30 18:13:47 +0000 2ef8d9c96b03e2a2cd588b46f90937f9113fd6e5 2002-03-30 17:51:53 +0000 c36b3f4f97e621f270f1adbc8d2c8863390db3a8 2002-03-30 17:44:11 +0000 63e3ebde7bd130c5a0c4e25fd94d7eaacc60ae77 2002-03-30 17:40:03 +0000 8fe742211bc86bc04ea6c9ab1d0edd58bebcb31c 2002-03-30 17:37:29 +0000 eb6a93d64ce597f0bc92d626c11a6f5389184717 2002-03-30 16:50:08 +0000 954d34b033513a218fe2bcc5fe20f4cfbe90c509 2002-03-30 16:48:07 +0000 1990d77f340f421f8818a115c3876ac7c37a3783 2002-03-30 15:36:25 +0000 165cd62a8148d305030a3ed1f9bca7edba0fc7c3 2002-03-30 15:31:30 +0000 6e4e619eba0e075b1707cecc7d4977f5d2648dcd 2002-03-30 15:28:35 +0000 3e2596186b74ab54e24877b63434c41f3ae20c4c 2002-03-30 15:24:45 +0000 79c99882db3014b5d6e68a2cfa52d7f677f6b837 2002-03-30 15:11:29 +0000 d939ecb1d2d84f89aecb5c3954b620fd3d05ec91 2002-03-30 15:10:27 +0000 bb4d77bc1ce2d82c106efb87d02c7eba23683445 2002-03-30 14:53:01 +0000 6ea91ee6ff3866499e444ab216d830e80b1b64ce 2002-03-30 14:49:17 +0000 b5a396d460260fcb9a372986cc6cbacd600f6de4 2002-03-29 23:18:10 +0000 42607e69afec73881ce516ec91ce14669366911c 2002-03-29 23:16:32 +0000 707ecf45c4fcbdc3ca0523b5c7766e9280d38667 2002-03-29 23:04:52 +0000 69b7bbd0660ef249a529564506e9bfda30bb13ea 2002-03-29 23:03:13 +0000 b4dfe4880a8613a0fa38f762849a848ff16669a8 2002-03-29 21:52:43 +0000 22d6f4d364052ce3d8ba576917d8df562ec4163e 2002-03-29 21:51:52 +0000 6b8985f5b13984b463676a3b450e8df7f3d85d8a 2002-03-29 21:50:41 +0000 6023174cb7a299158f6ad3d93b6a02777d6b20b0 2002-03-29 21:47:11 +0000 b01a3e4c03d194320d874d4937cb6ca83388192b 2002-03-29 18:50:33 +0000 c168f95b9eab125dfb94fb273f746191e4199361 2002-03-29 18:34:38 +0000 86a6feb8338de115d2d80e9df42320daf24b87b6 2002-03-29 18:33:55 +0000 868081123ce07f627527bdd64fd2fcf4fdfac4fa 2002-03-29 18:19:24 +0000 2d9dd2410935c3410277b9f7c60dac6ac1acc5ff 2002-03-29 18:18:27 +0000 6ef78c01c467985f2268d29a5995649d9319102c 2002-03-29 18:17:14 +0000 e7a542807e564e441ef54a792ec00ecab25d2ea5 2002-03-29 16:31:20 +0000 1fb1c3cb848b5788a8cbfb540ea82da137ec8459 2002-03-29 16:29:26 +0000 507d3c41a2912e229f130cc6b32a059ba672e1d9 2002-03-29 16:28:54 +0000 9e95c1890c335a39d8525fbc7e6992cf3c8f7ecf 2002-03-29 16:23:08 +0000 63f0df89bc7d5911c9422d7e0e20f3c1740b69a2 2002-03-29 16:12:26 +0000 c54e15c23c7c63ebe422ff2eba30debaab8a01e3 2002-02-12 22:36:51 +0000 d9e1ee4f985762e46691bcf5e82ba3d4b645b121 2002-02-12 22:22:27 +0000 21c101725cbb2b12e3bb3c4d96981fc1f1b3fd58 2002-02-12 22:00:02 +0000 c3ac3e4e38cd0c1f6658490fac2af4e1d1743586 2002-02-12 21:33:01 +0000 8edc69613c5eec03869aeccfc45c8646f38f548e 2002-02-06 23:48:34 +0000 0d79a5127226d6d985ee1ec06f54e3fdc413932a 2002-02-06 23:39:02 +0000 1401e72bb177b1d7fe4ea743db74838da6994a2e 2002-01-30 20:18:38 +0000 be6804debfbb91d9fb61aba904086ab2fe8d04c1 2002-01-29 16:02:34 +0000 16a7531b3fe099c6250fdbf4aaa931a1ac62ae15 2002-01-28 18:22:12 +0000 69cb753ae8dbca7f3fe546d0e49280114e1e32a4 2002-01-28 17:51:24 +0000 d6971cbed0670456d1133a7ab0d6c5466cd77452 2002-01-28 17:50:04 +0000 7a3e3aa0ce8b119ca72a9f44d63bb669c67fc16d 2002-01-28 16:53:50 +0000 8ea4ee8def839cb15a11d3b89e920a650a29a8de 2002-01-28 16:53:33 +0000 c727e01ffc5505b8f28769ce93d7dffbfea162be 2002-01-28 16:49:29 +0000 6239271cb827e8af731f86738cd0871d4124f3dc 2002-01-28 01:06:46 +0000 a71fcedcf52324101ec291c016e20d67bc230c5c 2002-01-28 00:38:16 +0000 55389f8fef361867dcfe3f58c5f0b1d5eda43923 2002-01-28 00:36:54 +0000 4e82a8cc74e0356fe31774cfc892815d9fe8f584 2002-01-27 23:13:16 +0000 748383d405fae5359b9c5f890ff588a9d3199271 2002-01-27 23:12:08 +0000 64dad0a42c7c22a082782b41ec59f4a4a4affb46 2002-01-27 20:59:48 +0000 55db7cd5debc417486c0322c2d6ce8e3294a6176 2002-01-27 20:40:03 +0000 a306c74c0efef660e06fa96105df23849b33ae4d 2002-01-27 20:37:14 +0000 dd44675e6517e38222935aaf51915626f669c73f 2002-01-27 20:34:30 +0000 b6cedb2b5e51938aa68d2c7e0eb6ba88d392e884 2002-01-27 20:27:11 +0000 fcbf8f2158f0b5c9f39723db5ec4799348b438e0 2002-01-27 20:25:20 +0000 b7c61741289a70431dad0545a6356bf569fa66f8 2002-01-27 20:05:53 +0000 afef405a8504e6cd10bfa44f39cd5cc3d90df8d2 2002-01-27 19:56:23 +0000 b713a25444c3575780ad58e737eafe95402ac49c 2002-01-27 19:54:41 +0000 674fdaa3ed95b4febaa2201c5a54c8b4db8da8a5 2002-01-27 18:42:24 +0000 42b3330f7da8b8853203ab937dbc0df4b45c261b 2002-01-27 18:35:33 +0000 635267ae49b0e8a7f6f90f8743bdcaa4ad2faccc 2002-01-27 18:22:52 +0000 eb88ecbbabf65f60a20877ed5b20fd9a46f1d9ac 2002-01-27 18:05:29 +0000 5d5ecd33bbb849a4313fcfc0ba7dd681c0533fa3 2002-01-26 22:32:53 +0000 c0187f8be3cad0d45a7b56260bb47a5e94e35191 2002-01-26 21:55:36 +0000 b22285189805cb9547190b723107d6418c2c0ca8 2002-01-26 21:48:32 +0000 b3198f220e121c1bac828caa8541ba56bea5bf41 2002-01-26 21:16:45 +0000 e4ff75e5d51910ef3576bb704a52ad7141e93d87 2002-01-26 21:08:31 +0000 0665575c8fe326fccefed9bf8715490d711a0c7c 2002-01-26 20:38:09 +0000 55e459e80b782d94e3e77eea2f3710777470821d 2002-01-23 18:09:20 +0000 2372a6c1206fd20537017456bc6f78c364bb2d47 2002-01-23 16:04:14 +0000 fffd328bda86d8180a53c668c4c40034ef2ea5f1 2002-01-23 14:09:09 +0000 80d94964ef0f231c4e0e5cbdcd430460685e73ef 2002-01-23 13:26:11 +0000 3bd327c8db8e83de9d442ff6ef30f8f6f75510bd 2002-01-23 13:25:54 +0000 a2b37e8ac5adea07d52ee0871968f762e708bc7c 2002-01-23 12:44:44 +0000 0d0443d9b41f732b0e8be617fac62b6ba33e2b55 2002-01-23 12:39:53 +0000 e4f91515d3ae22aa43b79c84a85945ee91218d7c 2002-01-23 12:35:24 +0000 c439e01f88549c91c175ad40c45d47ad3761e0ee 2002-01-23 12:28:12 +0000 e6b17eeae47be6f7780dc9d2ab289a02ae562816 2002-01-23 12:24:27 +0000 d97334098af43d6e4c321424a97ae618a0178a88 2002-01-22 22:11:06 +0000 17abc311e8c971a88550861b758fc24f49a56004 2002-01-22 21:35:10 +0000 ef12b53f131a7dc3665dea928c3536a563ba8138 2002-01-22 21:34:41 +0000 8f793817276dbcfc25536dce396429298d236b75 2002-01-22 20:19:30 +0000 e68b708ff4ac75f96fb81a7da57ead617d30b48a 2002-01-22 00:44:26 +0000 53b9a482638ee85f2cb729510050a38a34dcb3ed 2002-01-21 23:16:28 +0000 886b2a9281ef1bb404ea4309d09ee756f423cb9f 2002-01-21 23:01:59 +0000 f7703507d4cbcc868498ec56b9bfa56716e2ab2e 2002-01-21 22:35:37 +0000 d274c54611f4ffc908b769a4eba6a534672a4849 2002-01-21 22:16:19 +0000 a258732bb1675d7142c0e3f25e2107297f42c63b 2002-01-21 16:26:39 +0000 6b7ef120a57e01edbf394618345da8370df84e1d 2002-01-21 16:13:05 +0000 67acc14f0d9d5f96507edeb65881bc4b3fb34d8c 2002-01-21 16:10:20 +0000 232a35f6ba7abd3f365066e967abcc53ee7fc7af 2002-01-21 16:04:48 +0000 8ece8c81a0cada6fbc69870c6608fd8169beab86 2002-01-21 15:49:47 +0000 45455e40d4d18e310d3da0645ddeb9bc231a2e95 2002-01-21 00:49:00 +0000 d2a22a5e9b9fc5cb753414d8ca99b40911a066f9 2002-01-20 23:40:30 +0000 ee38675205c26a154a7b68238730974c6848993c 2002-01-20 23:01:49 +0000 6bdc2d713d9ec37693ec76a44b82a69d487e8400 2002-01-20 22:36:27 +0000 b307973afc4d07997eb31d3920488441251e601f 2002-01-20 20:31:46 +0000 627c1889e085e7f4366d22d9bc8523b6c7884f66 2002-01-20 18:56:39 +0000 7e80c5defa38f085a089ce803096bd6fef9a2f31 2002-01-20 18:53:25 +0000 67ef83b2d60d3b88451fbc9061a1b9de08c8d13a 2002-01-20 18:45:17 +0000 63cb5572a0b8927dd8270608b4027412a6c2691f 2002-01-20 18:44:39 +0000 1a308017f13a12769669021586cfe3f71351746e 2002-01-20 18:06:37 +0000 adb98b60e012ac1f10db1a11e6a716f309b4484a 2002-01-20 18:06:01 +0000 e61f5f5de95b876ff85ce69b80e8593abc4cce22 2002-01-20 17:35:42 +0000 96792a7f21f9a44e020349f850aa70c8b1a26ac7 2002-01-20 17:29:43 +0000 5a61737f3d98609af28a53c0f518be4d983c1aaf 2002-01-20 01:40:06 +0000 e08352e2ad7e6196c69b236a3b9e502f85138af6 2002-01-19 23:20:25 +0000 3e7d631aad3a64d52125677f3bc41e800561951a 2002-01-19 23:19:30 +0000 fadd6cf83d9f917075470fd7275b226ad711ae4d 2002-01-19 23:05:35 +0000 70d391ee15a51dbf45364398b07334e814e95942 2002-01-19 22:37:06 +0000 910a53e1f0a1e58d012949d6b486478d327f36fb 2002-01-19 22:28:18 +0000 625d8c4e7feaca3aeac0ffaa65418e3e4a9977af 2002-01-19 21:51:54 +0000 2c1667efde362dec6efc34d0aadade208d9337e2 2002-01-19 21:50:44 +0000 7c63c553d2c5c51e9ac08e6086e5037f2f895b14 2002-01-19 21:36:04 +0000 4be21d2e461902dbb158e1846f927d2998b25d62 2002-01-19 21:33:52 +0000 5880f30b0a9cd57ebc2991d0f0f1fd1dd3119835 2002-01-19 20:56:32 +0000 8c490fb1ee4e7372198ee40bf008011b18afd020 2002-01-13 18:34:17 +0000 26f4388829214ea020c911595564c72abe664ba7 2002-01-13 18:03:11 +0000 834304b432688ab06f22b94c6e993f02719fa74b 2002-01-13 14:11:21 +0000 e93155993afc30db56ee9dce996b21c88db51513 2002-01-13 01:08:26 +0000 9db916ed2259acaf93b0fd3226673ccc1f3f04da 2002-01-12 22:05:52 +0000 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7859647 ftputil-5.0.3/doc/python3_support.txt0000664000175000017500000001633000000000000016716 0ustar00schwaschwaMaking ftputil Ready for Python 3 ================================= Summary ------- First, here's a summary of my current plans: - The next ftputil version will be 3.0. It will work with all Python versions starting at 2.6. - This ftputil version will change some APIs in ways that will require changes in existing ftputil client code. This may include non-trivial changes where ftputil previously accepted either byte strings or unicode strings but will require unicode strings in the future. Please read on for details, including the reasons for these plans. Current Situation ----------------- Currently ftputil only supports Python 2. Several people have asked for Python 3 support; one entered a ticket on the ftputil project website. I also think that Python 3 becomes more and more widespread and so ftputil should be made compatible with Python 3. Future Development ------------------ Supported Python Versions ~~~~~~~~~~~~~~~~~~~~~~~~~ Although I want to adapt ftputil for use with Python 3, I have no plans to drop Python 2 support in ftputil anytime soon. Python 2 still is much more widely used. Originally the recommended approach for supporting Python 2 and 3 was to have a Python 2 version that could be translated with the `2to3` tool. However, nowadays another approach seems to be more common: supporting Python 2 and 3 with the very same code, without any conversions. From what I heard and read about the topic, I prefer the latter approach. In order to support Python 2 and 3 with the same code, I plan to drop support for Python versions before 2.6. Increasing the minimum Python version to 2.6 makes a common implementation easier because several significant Python 3 features have been backported to Python 2.6. Important examples are: - from __future__ import print_function - from __future__ import unicode_literals - `io` library, simplifying on-the-fly encoding/decoding Incompatibilities With Previous ftputil Versions (2.8 and Earlier) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It seems that most of the semantics of ftputil can be kept. However, the handling of strings in Python 2 and 3 differs in non-trivial ways. Almost all APIs that used to expect byte strings in Python 2, expect unicode strings in Python 3. Actually, the native `str` type in Python 2 is a byte string type, whereas `str` in Python 3 is a unicode strings type. I can imagine two approaches for dealing with strings in the next version of ftputil: 1. Under Python 2, offer an API which is more "natural" for Python 2. Under Python 3, offer an API which is more "natural" for Python 3. Advantage: When run under Python 2, ftputil would behave the same as before. Only few code changes (e. g. import statements) would be necessary. Under Python 3, a more "modern" API could be used. Disadvantage: Implementing two different APIs in the same code base might be extremely difficult. Conditional testing for different Python versions would probably be a mess and error-prone. 2. Use the same API for Python 2 and 3. Advantages: Using the same string types (bytes/unicode) under Python 2 and 3 will make the migration of ftputil itself easier, in particular the unit tests. It will be easier for clients of ftputil to offer support for Python 2 and 3 because they don't have to access ftputil via different APIs that depend on the Python runtime environment. Disadvantage: Code using previous ftputil versions will have to be adapted, possibly in non-trivial ways. I prefer the second approach, using a unified API. Please read this thread on comp.lang.python for some discussion: https://groups.google.com/forum/?fromgroups=#!topic/comp.lang.python/XKof6DpNyH4 In particular, so far I plan the following changes: - Opening remote files will behave as for the `io.open` function, available in Python 2.6 and later (see http://docs.python.org/2/library/io.html#io.open ; this is the same as the built-in `open` function in Python 3). When opening a file for reading in text mode, methods like `read` and `readlines` will return unicode strings. When opening a file for writing in text mode, the `write` and `writelines` methods will only accept unicode strings. Also as documented for `io.open`, files opened in binary mode will use only byte strings in their interfaces. - Traditionally, using the "ascii" mode for file transfers has meant that newline characters on writing were translated to "\r\n" pairs. Versions of ftputil published so far have done the same when a file was opened in text mode. In the future, I'd like to handle the newline conversion as documented for `io.open`. That is, newlines in remote files will be written according to the operating system where the client runs. This is in line with accesses to remote file systems like NFS or CIFS. So ftputil here will introduce a backward incompatibility, but the API will correspond better to the behavior of the usual file system API, which actually is a key idea in the design of ftputil. - Methods which accept and return a string, say `FTPHost.path.abspath`, will return the same string type they've been called with. This is the same behavior as in Python 3. - Some module names are going to change. For example, `ftputil.ftp_error` will become `ftputil.error`. - Translation of file names from/to unicode will use ISO-8859-1, also known as latin-1. For local file system accesses, Python 3 uses the value of `sys.getfilesystemencoding()`, which often will be UTF-8. I think using ISO-8859-1 is the better choice since the `ftplib` in Python 3 also uses this encoding. Using the same encoding in ftputil will make sure that directory and file names created by ftplib can be read by ftputil and vice versa. That said, if you can you should avoid using anything but ASCII characters for directories and files on remote file systems. This advice is the same as for older ftputil versions. ;-) The mapping between actual bytes for directory and file names may be different from previous ftputil versions. On the other hand, this mapping hadn't even be defined previously. The above list contains the (in)compatibility changes that I've been able to think of so far. More issues may surface when I'm making the actual ftputil changes and after the first such version(s) of ftputil will have been published. I assume it was the same when the file system APIs in Python 3 were developed, so please bear with me. :-) If You Really Need Backward Compatibility: Creating an API Adapter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If there's a strong need for an API compatible to ftputil 2.8 and below, someone could write an adapter layer that could/would be used instead of the new ftputil API. I'm open to making simple API changes so that it becomes easier to use custom classes. For example, instead of hardcoding the use of the `ftputil._FTPFile` class in the `FTPHost` class, the `FTPHost` class can define the concrete FTP file class to use as a class attribute. What do you think about all this? Do you think it's reasonable? Have I overlooked something? Are there problems I may not have anticipated? Please give me feedback if there's something on your mind. :-) Best regards, Stefan ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7859647 ftputil-5.0.3/doc/python3_support_outline.txt0000664000175000017500000001324700000000000020461 0ustar00schwaschwaWhat to do for Python 3 support =============================== Improving unit tests Review them before starting coding on them. Will probably remind me of what's tricky. Maybe this is the time (aka chance ;-) ) to clean up. After reading first tests ... I don't remember easily what all these tests were for, but I think I can recall when I look at the code. -> Add docstrings/comments to explain what the idea of each respective test is. Put custom session factory classes for certain tests before these tests the session factories are used in. Tests for ASCII/binary conversions certainly have to change. We're going to use Python's usual line ending normalization. We don't expect `\r` characters to be added during ASCII _reads_. Change certain methods to return the same string type that is passed in. The following list contains only user-visible methods or methods whose API is directly relevant for users. host.listdir(self, path) host.lstat(self, path, _exception_for_missing_path=True) host.stat(self, path, _exception_for_missing_path=True) host.walk(self, top, topdown=True, onerror=None) host.path.abspath(self, path) host.path.basename(path) host.path.commonprefix(path_list) host.path.dirname(path) host.path.exists(path) host.path.getmtime(path) host.path.getsize(path) host.path.isabs(path) host.path.isdir(path) host.path.isfile(path) host.path.islink(path) host.path.join(path1, path2, ...) host.path.normcase(path) host.path.normpath(path) host.path.split(path) host.path.splitdrive(path) host.path.splitext(path) host.path.walk(path, func, arg) parser.ignores_line(self, line) parser.parse_line(self, line, time_shift=0.0) mock_ftplib.cwd(self, path) mock_ftplib.dir(self, *args) mock_ftplib.transfercmd(self, cmd) mock_ftplib.voidcmd(self, cmd) mock_ftplib.voidresp(self) Can we somehow "generalize" unit tests? I. e. we don't want to code the "same string type" logic when testing each method. Which methods of the "normal" file system API accept byte strings, unicode strings or either? If the APIs are systematic, follow them for ftputil. If the APIs aren't systematic, don't support exceptional cases, e. g. only one method in a group of methods accepts byte strings beside unicode strings. Or support the exceptional cases only in a later ftputil version. Normalize cache string type? Does it make sense? What encoding to use to automatically convert? What to do if encoding to byte string isn't possible? (i. e. when the unicode strings contains characters that are invalid in the target encoding) Local files use the value of `sys.getfilesystemencoding()` whereas remote files - in ftplib - use latin1. Is it possible to be consistent? For what kind of "consistency"? Different caches for byte string paths and unicode string paths? File object API Files opened for reading When opened in binary mode, return byte strings. When opened in text mode, return unicode strings. Apply encoding given in `open` on the fly. Use helper classes in `io` module. Files opened for writing When opened in binary mode, accept only byte strings. When opened in text mode, accept only unicode strings. Allow byte strings during a "deprecation period"? No: Do the simplest thing that could possibly work. Might rather confuse people. Might cause surprises when the "deprecation period" is over! Apply encoding given in `open` on the fly. Use helper classes in `io` module. Deal consistently with local and remote files. What does "consistent" mean/involve here? How to deal with line ending conversion? We don't know for certain whether the remote server is a Posix or Windows machine. -> This would suggest to use the line endings of the local host when writing remote files. -> But I don't think this is logical. We would get different remote files depending on the operating system of the client which accesses these files! Always assume Posix or always assume Windows for the remote system? Let it depend on the returned listing format? Too subtle. What to do if we later move to using `MLSD`? Is there a way to specify a desired line ending type when using the `io` module? Yes, there's the `newline` argument in `io.open`. Probably it's better to let ftputil behave as for "regular" file systems. This also holds for remote file systems (NFS, CIFS). So it should be ok to use Python conventions here; mounted remote file systems are treated like local file systems. Provide user and password in `FTPHost` constructor as unicode strings? Probably very important since the server will only see the bytes, and an encoding problem will lead to a refused connection. How does ftplib handle the encoding? Different methods for returning byte strings or unicode strings (similar to `os.getcwd` vs. `os.getcwdu`)? Later! (do the simplest thing that could possibly work) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7859647 ftputil-5.0.3/doc/todo.txt0000664000175000017500000000105200000000000014456 0ustar00schwaschwaPlanned: - Add helper routines `server_time_from_client_time` and `client_time_from_server_time` - Update documentation for ticket 134 - Possibly introduce keyword-only arguments (ticket 130) - Improve caching (ticket 129) - Improve sync code. Ideas for future development: - See ticket system - Use `freezegun` module to speed up long-running test for `upload_if_newer` and `download_if_newer`. Note: It won't be possible, to speed up `test_public_servers.py` with this. - Support `FTPFile.seek` and `FTPFile.tell` as far as it makes sense. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1643211004.1858404 ftputil-5.0.3/doc/whats_new_in_ftputil_3.0.html0000664000175000017500000006006300000000000020461 0ustar00schwaschwa What's new in ftputil 3.0?

What's new in ftputil 3.0?

Version: 3.0
Date: 2013-09-29
Author: Stefan Schwarzer <sschwarzer@sschwarzer.net>

Added support for Python 3

This ftputil release adds support for Python 3.0 and up.

Python 2 and 3 are supported with the same source code. Also, the API including the semantics is the same. As for Python 3 code, in ftputil 3.0 unicode is somewhat preferred over byte strings. On the other hand, in line with the file system APIs of both Python 2 and 3, methods take either byte strings or unicode strings. Methods that take and return strings (for example, FTPHost.path.abspath or FTPHost.listdir), return the same string type they get.

Note

Both Python 2 and 3 have two "string" types where one type represents a sequence of bytes and the other type character (text) data.

Python version Binary type Text type Default string literal type
2 str unicode str (= binary type)
3 bytes str str (= text type)

So both lines of Python have an str type, but in Python 2 it's the byte type and in Python 3 the text type. The str type is also what you get when you write a literal string without any prefixes. For example "Python" is a binary string in Python 2 and a text (unicode) string in Python 3.

If this seems confusing, please read this description in the Python documentation for more details.

Dropped support for Python 2.4 and 2.5

To make it easier to use the same code for Python 2 and 3, I decided to use the Python 3 features backported to Python 2.6. As a consequence, ftputil 3.0 doesn't work with Python 2.4 and 2.5.

Newlines and encoding of remote file content

Traditionally, "text mode" for FTP transfers meant translation to \r\n newlines, even between transfers of Unix clients and Unix servers. Since this presumably most of the time is neither the expected nor the desired behavior, the FTPHost.open method now has the API and semantics of the built-in open function in Python 3. If you want the same API for local files in Python 2.6 and 2.7, you can use the open function from the io module.

Thus, when opening remote files in binary mode, the new API does not accept an encoding argument. On the other hand, opening a file in text mode always implies an encoding step when writing and decoding step when reading files. If the encoding argument isn't specified, it defaults to the value of locale.getpreferredencoding(False).

Also as with Python 3's open builtin, opening a file in binary mode for reading will give you byte string data. If you write to a file opened in binary mode, you must write byte strings. Along the same lines, files opened in text mode will give you unicode strings when read, and require unicode strings to be passed to write operations.

Module and method name changes

In earlier ftputil versions, most module names had a redundant ftp_ prefix. In ftputil 3.0, these prefixes are removed. Of the module names that are part of the public ftputil API, this affects only ftputil.error and ftputil.stat.

In Python 2.2, file became an alias for open, and previous ftputil versions also had an FTPHost.file besides the FTPHost.open method. In Python 3.0, the file builtin was removed and the return values from the built-in open methods are no longer file instances. Along the same lines, ftputil 3.0 also drops the FTPHost.file alias and requires FTPHost.open.

Upload and download modes

The FTPHost methods for downloading and uploading files (download, download_if_newer, upload and upload_if_newer) now always use binary mode; a mode argument is no longer needed or even allowed. Although this behavior makes downloads and uploads slightly less flexible, it should cover almost all use cases.

If you really want to do a transfer involving files opened in text mode, you can still do:

import ftputil.file_transfer

...

with FTPHost.open("source.txt", "r", encoding="UTF-8") as source, \
     FTPHost.open("target.txt", "w", encoding="latin1") as target:
    ftputil.file_transfer.copyfileobj(source, target)

Note that it's not possible anymore to open one file in binary mode and the other file in text mode and transfer data between them with copyfileobj. For example, opening the source in binary mode will read byte strings, but a target file opened in text mode will only allow writing of unicode strings. Then again, I assume that the cases where you want a mixed binary/text mode transfer should be very rare.

Custom parsers receive lines as unicode strings

Custom parsers, as described in the documentation, receive a text line for each directory entry in the methods ignores_line and parse_line. In previous ftputil versions, the line arguments were byte strings; now they're unicode strings.

If you aren't sure what this is about, this may help: If you never used the FTPHost.set_parser method, you can ignore this section. :-)

Porting to ftputil 3.0

  • It's likely that you catch an ftputil exception here and there. In that case, you need to change import ftputil.ftp_error to import ftputil.error and modify the uses of the module accordingly. If you used from ftputil import ftp_error, you can change this to from ftputil import error as ftp_error without changing the code using the module.
  • If you use the download or upload methods, you need to remove the mode argument from the call. If you used something else than "b" for binary mode (which I assume to be unlikely), you'll need to adapt the code that calls the download or upload methods.
  • If you use custom parsers, you'll need to change import ftputil.ftp_stat to import ftputil.stat and adapt your code in the module. Moreover, you might need to change your ignores_line or parse_line calls if they rely on their line argument being a byte string.
  • If you use remote files, especially ones opened in text mode, you may need to change your code to adapt to the changes in newline conversion, encoding and/or string type (see above sections).

Note

In the root directory of the installed ftputil package is a script find_invalid_code.py which, given a start directory as argument, will scan that directory tree for code that may need to be fixed. However, this script uses very simple heuristics, so it may miss some problematic code or list perfectly valid code.

In particular, you may want to change the regular expression string HOST_REGEX for the names you usually use for FTPHost objects.

Questions and answers

The advice to "adapt code to the new string types" is rather vague. Can't you be more specific?

It's difficult to be more specific without knowing your application.

That said, best practices nowadays are:

  • If you're dealing with character data, use unicode strings whenever possible. In Python 2, this means the unicode type and in Python 3 the str type.
  • Whenever you deal with binary data which is actually character data, decode it as soon as possible when reading data. Encode the data as late as possible when writing data.

Yes, I know that's not much more specific.

Why don't you use a "Python 2 API" for Python 2 and a "Python 3 API" for Python 3?

(What's meant here is, for example, that if you opened a remote file as text, the read data could be of byte string type in Python 2 and of unicode type in Python 3. Similarly, under Python 2 a text file opened for writing could accept both byte strings and unicode strings in the write* methods.)

Actually, I had at first thought of implementing this but dropped the idea because it has several problems:

  • Basically, I would have to support two APIs for the same set of methods. I can imagine that some things can be simplified by just using str to convert to the "right" string type automatically, but I assume these opportunities would be rather the exception than the rule. I'd certainly not look forward to maintaining such code.
  • Using two different APIs might require people to change their code if they move from using ftputil 3.x in Python 2 to using it in Python 3.
  • Developers who want to support both Python 2 and 3 with the same source code (as I do now in ftputil) would "inherit" the "dual API" and would have to use different wrapper code depending on the Python version their code is run under.

For these reasons, I ended up choosing the same API semantics for Python 2 and 3.

Why don't you use the six module to be able to support Python 2.4 and 2.5?

There are two reasons:

  • ftputil so far has no dependencies other than the Python standard library, and I think that's a nice feature.
  • Although six makes it easier to support Python 2.4/2.5 and Python 3 at the same time, the resulting code is somewhat awkward. I wanted a code base that feels more like "modern Python"; I wanted to use the Python 3 features backported to Python 2.6 and 2.7.

Why don't you use 2to3 to generate the Python 3 version of ftputil?

I had considered this when I started adapting the ftputil source code for Python 3. On the other hand, although using 2to3 used to be the recommended approach for Python 3 support, even rather large projects have chosen the route of having one code base and using it unmodified for Python 2 and 3.

When I looked into this approach for ftputil 3.0, it became quickly obvious that it would be easier and I found it worked out very well.

././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7869647 ftputil-5.0.3/doc/whats_new_in_ftputil_3.0.txt0000664000175000017500000002545300000000000020340 0ustar00schwaschwaWhat's new in ftputil 3.0? ========================== :Version: 3.0 :Date: 2013-09-29 :Author: Stefan Schwarzer .. contents:: Added support for Python 3 -------------------------- This ftputil release adds support for Python 3.0 and up. Python 2 and 3 are supported with the same source code. Also, the API including the semantics is the same. As for Python 3 code, in ftputil 3.0 unicode is somewhat preferred over byte strings. On the other hand, in line with the file system APIs of both Python 2 and 3, methods take either byte strings or unicode strings. Methods that take and return strings (for example, ``FTPHost.path.abspath`` or ``FTPHost.listdir``), return the same string type they get. .. Note:: Both Python 2 and 3 have two "string" types where one type represents a sequence of bytes and the other type character (text) data. ============== =========== =========== =========================== Python version Binary type Text type Default string literal type ============== =========== =========== =========================== 2 ``str`` ``unicode`` ``str`` (= binary type) 3 ``bytes`` ``str`` ``str`` (= text type) ============== =========== =========== =========================== So both lines of Python have an ``str`` type, but in Python 2 it's the byte type and in Python 3 the text type. The ``str`` type is also what you get when you write a literal string without any prefixes. For example ``"Python"`` is a binary string in Python 2 and a text (unicode) string in Python 3. If this seems confusing, please read `this description`_ in the Python documentation for more details. .. _`this description`: http://docs.python.org/3.0/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit Dropped support for Python 2.4 and 2.5 -------------------------------------- To make it easier to use the same code for Python 2 and 3, I decided to use the Python 3 features backported to Python 2.6. As a consequence, ftputil 3.0 doesn't work with Python 2.4 and 2.5. Newlines and encoding of remote file content -------------------------------------------- Traditionally, "text mode" for FTP transfers meant translation to ``\r\n`` newlines, even between transfers of Unix clients and Unix servers. Since this presumably most of the time is neither the expected nor the desired behavior, the ``FTPHost.open`` method now has the API and semantics of the built-in ``open`` function in Python 3. If you want the same API for *local* files in Python 2.6 and 2.7, you can use the ``open`` function from the ``io`` module. Thus, when opening remote files in *binary* mode, the new API does *not* accept an encoding argument. On the other hand, opening a file in text mode always implies an encoding step when writing and decoding step when reading files. If the ``encoding`` argument isn't specified, it defaults to the value of ``locale.getpreferredencoding(False)``. Also as with Python 3's ``open`` builtin, opening a file in binary mode for reading will give you byte string data. If you write to a file opened in binary mode, you must write byte strings. Along the same lines, files opened in text mode will give you unicode strings when read, and require unicode strings to be passed to write operations. Module and method name changes ------------------------------ In earlier ftputil versions, most module names had a redundant ``ftp_`` prefix. In ftputil 3.0, these prefixes are removed. Of the module names that are part of the public ftputil API, this affects only ``ftputil.error`` and ``ftputil.stat``. In Python 2.2, ``file`` became an alias for ``open``, and previous ftputil versions also had an ``FTPHost.file`` besides the ``FTPHost.open`` method. In Python 3.0, the ``file`` builtin was removed and the return values from the built-in ``open`` methods are no longer ``file`` instances. Along the same lines, ftputil 3.0 also drops the ``FTPHost.file`` alias and requires ``FTPHost.open``. Upload and download modes ------------------------- The ``FTPHost`` methods for downloading and uploading files (``download``, ``download_if_newer``, ``upload`` and ``upload_if_newer``) now always use binary mode; a ``mode`` argument is no longer needed or even allowed. Although this behavior makes downloads and uploads slightly less flexible, it should cover almost all use cases. If you *really* want to do a transfer involving files opened in text mode, you can still do:: import ftputil.file_transfer ... with FTPHost.open("source.txt", "r", encoding="UTF-8") as source, \ FTPHost.open("target.txt", "w", encoding="latin1") as target: ftputil.file_transfer.copyfileobj(source, target) Note that it's not possible anymore to open one file in binary mode and the other file in text mode and transfer data between them with ``copyfileobj``. For example, opening the source in binary mode will read byte strings, but a target file opened in text mode will only allow writing of unicode strings. Then again, I assume that the cases where you want a mixed binary/text mode transfer should be *very* rare. Custom parsers receive lines as unicode strings ----------------------------------------------- Custom parsers, as described in the documentation_, receive a text line for each directory entry in the methods ``ignores_line`` and ``parse_line``. In previous ftputil versions, the ``line`` arguments were byte strings; now they're unicode strings. .. _documentation: http://ftputil.sschwarzer.net/documentation#writing-directory-parsers If you aren't sure what this is about, this may help: If you never used the ``FTPHost.set_parser`` method, you can ignore this section. :-) Porting to ftputil 3.0 ---------------------- - It's likely that you catch an ftputil exception here and there. In that case, you need to change ``import ftputil.ftp_error`` to ``import ftputil.error`` and modify the uses of the module accordingly. If you used ``from ftputil import ftp_error``, you can change this to ``from ftputil import error as ftp_error`` without changing the code using the module. - If you use the download or upload methods, you need to remove the ``mode`` argument from the call. If you used something else than ``"b"`` for binary mode (which I assume to be unlikely), you'll need to adapt the code that calls the download or upload methods. - If you use custom parsers, you'll need to change ``import ftputil.ftp_stat`` to ``import ftputil.stat`` and adapt your code in the module. Moreover, you might need to change your ``ignores_line`` or ``parse_line`` calls if they rely on their ``line`` argument being a byte string. - If you use remote files, especially ones opened in text mode, you may need to change your code to adapt to the changes in newline conversion, encoding and/or string type (see above sections). .. Note:: In the root directory of the installed ftputil package is a script ``find_invalid_code.py`` which, given a start directory as argument, will scan that directory tree for code that may need to be fixed. However, this script uses very simple heuristics, so it may miss some problematic code or list perfectly valid code. In particular, you may want to change the regular expression string ``HOST_REGEX`` for the names you usually use for ``FTPHost`` objects. Questions and answers --------------------- The advice to "adapt code to the new string types" is rather vague. Can't you be more specific? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It's difficult to be more specific without knowing your application. That said, best practices nowadays are: - If you're dealing with character data, use unicode strings whenever possible. In Python 2, this means the ``unicode`` type and in Python 3 the ``str`` type. - Whenever you deal with binary data which is actually character data, decode it as *soon* as possible when *reading* data. Encode the data as *late* as possible when *writing* data. Yes, I know that's not much more specific. Why don't you use a "Python 2 API" for Python 2 and a "Python 3 API" for Python 3? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ (What's meant here is, for example, that if you opened a remote file as text, the read data could be of byte string type in Python 2 and of unicode type in Python 3. Similarly, under Python 2 a text file opened for writing could accept both byte strings and unicode strings in the ``write*`` methods.) Actually, I had at first thought of implementing this but dropped the idea because it has several problems: - Basically, I would have to support two APIs for the same set of methods. I can imagine that some things can be simplified by just using ``str`` to convert to the "right" string type automatically, but I assume these opportunities would be rather the exception than the rule. I'd certainly not look forward to maintaining such code. - Using two different APIs might require people to change their code if they move from using ftputil 3.x in Python 2 to using it in Python 3. - Developers who want to support both Python 2 and 3 with the same source code (as I do now in ftputil) would "inherit" the "dual API" and would have to use different wrapper code depending on the Python version their code is run under. For these reasons, I `ended up`_ choosing the same API semantics for Python 2 and 3. .. _`ended up`: https://groups.google.com/forum/?fromgroups=#!topic/comp.lang.python/XKof6DpNyH4 Why don't you use the six_ module to be able to support Python 2.4 and 2.5? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. _six: https://pypi.python.org/pypi/six/ There are two reasons: - ftputil so far has no dependencies other than the Python standard library, and I think that's a nice feature. - Although ``six`` makes it easier to support Python 2.4/2.5 and Python 3 at the same time, the resulting code is somewhat awkward. I wanted a code base that feels more like "modern Python"; I wanted to use the Python 3 features backported to Python 2.6 and 2.7. Why don't you use 2to3_ to generate the Python 3 version of ftputil? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. _2to3: http://docs.python.org/2/library/2to3.html I had considered this when I started adapting the ftputil source code for Python 3. On the other hand, although using 2to3 used to be the recommended approach for Python 3 support, even `rather large projects`_ have chosen the route of having one code base and using it unmodified for Python 2 and 3. .. _`rather large projects`: https://docs.djangoproject.com/en/dev/topics/python3/ When I looked into this approach for ftputil 3.0, it became quickly obvious that it would be easier and I found it worked out very well. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1643211004.5438335 ftputil-5.0.3/doc/whats_new_in_ftputil_4.0.0.html0000664000175000017500000005046400000000000020624 0ustar00schwaschwa What's new in ftputil 4.0.0?

What's new in ftputil 4.0.0?

Version: 4.0.0
Date: 2020-06-13
Author: Stefan Schwarzer <sschwarzer@sschwarzer.net>

Supported Python versions

Support for Python 2 is dropped. The minimum required Python 3 version is 3.6.

Find more details in Questions and answers.

Path-like objects

Methods that take directory and file names as str or bytes objects now also accept path-like objects.

Time shift handling

ftputil uses the notion of "time shift" to deal with time zone differences between client and server. This is important for the methods upload_if_newer and download_if_newer.

The defintion of "time shift" changed from earlier ftputil versions to ftputil 4.0.0.

Previously, the time shift was defined as

time_used_by_server - local_time_used_by_client

The new definition is

time_used_by_server - UTC

Both definitions have their pros and cons (detailed in the Questions and answers).

Porting

If you don't use any methods that deal with time stamps from the FTP server, you can ignore the time shift redefinition. The affected methods are upload_if_newer, download_if_newer and using the timestamp values from the stat and lstat methods.

Ideally, you have write access on the server in the current directory. In this case you can call synchronize_times:

with ftputil.FTPHost(host, user, password) as ftp_host:
    ftp_host.synchronize_times()
    ...

If using synchronize_times isn't an option, you have to set the time shift explicitly with set_time_shift:

with ftputil.FTPHost(host, user, password) as ftp_host:
    ftp_host.set_time_shift(new_time_shift)
    ...

If you're sure the server in its directory listings uses the same timezone as the client, you can use

with ftputil.FTPHost(host, user, password) as ftp_host:
    ftp_host.set_time_shift(
      round((datetime.datetime.now() - datetime.datetime.utcnow()).seconds, -2)
    )
    ...

This is roughly equivalent to the old ftputil behavior. The only difference is that the new behavior requires that you adapt the time shift value if there's a switch to or from daylight saving time.

If you want to cover such daylight saving time switches as well, you could override time_shift() to return the above value. This will automatically adapt to daylight saving time switches because datetime.datetime.now() will return the time for the changed UTC offset. That said, I think this approach is a hack because it means that calls to set_time_shift will effectively be ignored. So don't take this approach as a recommendation.

You can use this script to scan your code for uses of time_shift() or set_time_shift. However, if you implicitly relied on the old default behavior (time shift is 0.0 if client and server use the same time zone), you'll need additional calls for set_time_shift.

ftputil no longer uses the -a option by default

Earlier ftputil versions by default sent an -a option with the FTP DIR command to include "hidden" directories and files (names starting with a dot) in the listing.

That led to problems when the server didn't understand the option and treated it as a directory or file name.

Therefore, ftputil no longer uses the -a option by default.

Porting

You can enable the old behavior by setting use_list_a_option on the FTPHost instance to True:

with ftputil.FTPHost(host, user, password) as ftp_host:
    ftp_host.use_list_a_option = True
    ...

However, do this only if you're sure the server interprets the option correctly!

makedirs behaves as in Python 3

In Python 2, os.makedirs didn't complain if any directory in the path to create already existed. Since ftputil was originally based on Python 2 behavior, this was also the behavior in FTPHost.makedirs.

Python 3 added an optional argument exist_ok with the default False. With this default, os.makedirs raises an exception if any directory but the last in the path argument exists.

Since Python 2 is used less and less, ftputil 4.0.0 follows the Python 3 semantics.

Porting

If you want the old behavior of FTPHost.makedirs, pass exist_ok=True. Note that there's also an unused mode argument for consistency with the os.makedirs API, so make sure you pass exist_ok as a keyword argument.

You can use this script to scan your code for uses of makedirs().

Questions and answers

Why is Python 2 no longer supported?

Since the start of the year Python 2 officially is no longer maintained and supporting it in combination with Python 3 led to lots of extra work. Therefore, I decided to drop support for Python 2.

Why is the minimum version Python 3.6?

Python 3.4 and older versions are no longer supported by the CPython team.

My plan was to support Python 3.5 since it's not yet end-of-life'd and it may still be used in some LTS Linux/Unix distributions. However, dropping Python 3.5 support made it much easier to implement ticket #119, support for path-like objects. Python 3.6 introduced some infrastructure so that code that used to use only str and bytes paths can now use path-like objects as well. I considered it more important to support path-like objects than Python 3.5. I guess it might have been possible to add support for path-like objects on top of Python 3.5, but it would have been a hassle and Python 3.5 support officially ends in just a few months.

Why a new time shift definition?

Both the old and the new approach have their pros and cons:

Regarding the old approach:

  • Pro: If the server uses the time zone of the client in directory listings, the default time shift of 0.0 will do the right thing.

  • Con: Since the time shift depends on two time zones, it's more difficult to reason about the (ftputil) code and write correct code. In the past, there have been multiple bugs in the time zone / time shift handling in ftputil, some of them occuring only under "interesting" conditions (around daylight saving time changes or year changes).

    Actually, I decided to implement the new approach when I ran into a bug around the last new year change.

Regarding the new approach:

  • Pro: The new behavior works better when the server is set to UTC. This is a sane setting because it avoids that an hour interval is used twice when there's a switch from daylight saving time to "normal" time. There are even more subtle problems with daylight saving time switches.
  • Pro: Since the time shift calculation depends on only one time zone (that of the server), the code in ftputil is easier to reason about and now hopefully more robust.
  • Con: The new approach is backward-incompatible, so users may have to adapt their code.
  • Con: If the server uses the time zone of the client in directory listings, the time shift must be adjusted whenever there's a switch to or from daylight saving time.

Neither of the two approaches is foolproof. For example, timestamps that are older than the last daylight saving time switch may be calculated wrongly because they may use a different time zone than the one currently set by set_time_shift.

././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7869647 ftputil-5.0.3/doc/whats_new_in_ftputil_4.0.0.txt0000664000175000017500000001776400000000000020505 0ustar00schwaschwaWhat's new in ftputil 4.0.0? ============================ :Version: 4.0.0 :Date: 2020-06-13 :Author: Stefan Schwarzer .. contents:: Supported Python versions ------------------------- Support for Python 2 is dropped. The minimum required Python 3 version is 3.6. Find more details in `Questions and answers`_. Path-like objects ----------------- Methods that take directory and file names as ``str`` or ``bytes`` objects now also accept `path-like objects`_. .. _`path-like objects`: https://docs.python.org/3/library/os.html#os.PathLike Time shift handling ------------------- ftputil uses the notion of "time shift" to deal with time zone differences between client and server. This is important for the methods ``upload_if_newer`` and ``download_if_newer``. The defintion of "time shift" changed from earlier ftputil versions to ftputil 4.0.0. Previously, the time shift was defined as time_used_by_server - local_time_used_by_client The new definition is time_used_by_server - `UTC`_ .. _`UTC`: https://en.wikipedia.org/wiki/Coordinated_Universal_Time Both definitions have their pros and cons (detailed in the `Questions and answers`_). Porting ~~~~~~~ If you don't use any methods that deal with time stamps from the FTP server, you can ignore the time shift redefinition. The affected methods are ``upload_if_newer``, ``download_if_newer`` and using the timestamp values from the ``stat`` and ``lstat`` methods. Ideally, you have write access on the server in the current directory. In this case you can call ``synchronize_times``:: with ftputil.FTPHost(host, user, password) as ftp_host: ftp_host.synchronize_times() ... If using ``synchronize_times`` isn't an option, you have to set the time shift explicitly with ``set_time_shift``:: with ftputil.FTPHost(host, user, password) as ftp_host: ftp_host.set_time_shift(new_time_shift) ... *If* you're sure the server in its directory listings uses the same timezone as the client, you can use :: with ftputil.FTPHost(host, user, password) as ftp_host: ftp_host.set_time_shift( round((datetime.datetime.now() - datetime.datetime.utcnow()).seconds, -2) ) ... This is roughly equivalent to the old ftputil behavior. The only difference is that the new behavior requires that you adapt the time shift value if there's a switch to or from daylight saving time. If you want to cover such daylight saving time switches as well, you could override ``time_shift()`` to return the above value. This will automatically adapt to daylight saving time switches because ``datetime.datetime.now()`` will return the time for the changed UTC offset. That said, I think this approach is a hack because it means that calls to ``set_time_shift`` will effectively be ignored. So don't take this approach as a recommendation. You can use `this script`_ to scan your code for uses of ``time_shift()`` or ``set_time_shift``. However, if you implicitly relied on the old default behavior (time shift is 0.0 if client and server use the same time zone), you'll need additional calls for ``set_time_shift``. .. _`this script`: https://ftputil.sschwarzer.net/trac/browser/find_problematic_code.py?format=txt ftputil no longer uses the ``-a`` option by default --------------------------------------------------- Earlier ftputil versions by default sent an ``-a`` option with the FTP ``DIR`` command to include "hidden" directories and files (names starting with a dot) in the listing. That led to problems when the server `didn't understand the option`_ and treated it as a directory or file name. .. _`didn't understand the option`: https://ftputil.sschwarzer.net/trac/ticket/110 Therefore, ftputil no longer uses the ``-a`` option by default. Porting ~~~~~~~ You can enable the old behavior by setting ``use_list_a_option`` on the ``FTPHost`` instance to ``True``:: with ftputil.FTPHost(host, user, password) as ftp_host: ftp_host.use_list_a_option = True ... However, do this only if you're *sure* the server interprets the option correctly! ``makedirs`` behaves as in Python 3 ----------------------------------- In Python 2, ``os.makedirs`` didn't complain if any directory in the path to create already existed. Since ftputil was originally based on Python 2 behavior, this *was* also the behavior in ``FTPHost.makedirs``. Python 3 added an optional argument ``exist_ok`` with the default ``False``. With this default, ``os.makedirs`` raises an exception if any directory but the last in the ``path`` argument exists. Since Python 2 is used less and less, ftputil 4.0.0 follows the Python 3 semantics. Porting ~~~~~~~ If you want the old behavior of ``FTPHost.makedirs``, pass ``exist_ok=True``. Note that there's also an unused ``mode`` argument for consistency with the ``os.makedirs`` API, so make sure you pass ``exist_ok`` as a keyword argument. You can use `this script`_ to scan your code for uses of ``makedirs()``. Questions and answers --------------------- Why is Python 2 no longer supported? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Since the start of the year Python 2 officially is no longer maintained and supporting it in combination with Python 3 led to lots of extra work. Therefore, I decided to drop support for Python 2. Why is the minimum version Python 3.6? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Python 3.4 and older versions are no longer supported by the CPython team. My plan *was* to support Python 3.5 since it's not yet end-of-life'd and it may still be used in some LTS Linux/Unix distributions. However, dropping Python 3.5 support made it much easier to implement `ticket #119`_, support for `path-like objects`_. Python 3.6 introduced some infrastructure so that code that used to use only ``str`` and ``bytes`` paths can now use path-like objects as well. I considered it more important to support path-like objects than Python 3.5. I guess it might have been possible to add support for path-like objects on top of Python 3.5, but it would have been a hassle and Python 3.5 support officially ends in `just a few months`_. .. _`ticket #119`: https://ftputil.sschwarzer.net/trac/ticket/119 .. _`path-like objects`: https://docs.python.org/3/library/os.html#os.PathLike .. _`just a few months`: https://devguide.python.org/#status-of-python-branches Why a new time shift definition? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Both the old and the new approach have their pros and cons: Regarding the old approach: - Pro: *If* the server uses the time zone of the client in directory listings, the default time shift of 0.0 will do the right thing. - Con: Since the time shift depends on *two* time zones, it's more difficult to reason about the (ftputil) code and write correct code. In the past, there have been multiple bugs in the time zone / time shift handling in ftputil, some of them occuring only under "interesting" conditions (around daylight saving time changes or year changes). Actually, I decided to implement the new approach when I ran into a bug around the last new year change. Regarding the new approach: - Pro: The new behavior works better when the server is set to UTC. This is a sane setting because it avoids that an hour interval is used twice when there's a switch from daylight saving time to "normal" time. There are even more subtle problems with daylight saving time switches. - Pro: Since the time shift calculation depends on only one time zone (that of the server), the code in ftputil is easier to reason about and now hopefully more robust. - Con: The new approach is backward-incompatible, so users may have to adapt their code. - Con: *If* the server uses the time zone of the client in directory listings, the time shift must be adjusted whenever there's a switch to or from daylight saving time. Neither of the two approaches is foolproof. For example, timestamps that are older than the last daylight saving time switch may be calculated wrongly because they may use a different time zone than the one currently set by ``set_time_shift``. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1643211004.7538292 ftputil-5.0.3/ftputil/0000775000175000017500000000000000000000000013674 5ustar00schwaschwa././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7869647 ftputil-5.0.3/ftputil/__init__.py0000664000175000017500000000356000000000000016011 0ustar00schwaschwa# Copyright (C) 2002-2018, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. """ ftputil - high-level FTP client library FTPHost objects This class resembles the `os` module's interface to ordinary file systems. In addition, it provides a method `file` which will return file-objects corresponding to remote files. # Example session with ftputil.FTPHost("ftp.domain.com", "me", "secret") as host: print(host.getcwd()) # e. g. "/home/me" host.mkdir("newdir") host.chdir("newdir") with host.open("sourcefile", "r") as source: with host.open("targetfile", "w") as target: host.copyfileobj(source, target) host.remove("targetfile") host.chdir(host.pardir) host.rmdir("newdir") There are also shortcuts for uploads and downloads: host.upload(local_file, remote_file) host.download(remote_file, local_file) Both accept an additional mode parameter. If it is "b", the transfer mode will be for binary files. For even more functionality refer to the documentation in `ftputil.txt` or `ftputil.html`. FTPFile objects `FTPFile` objects are constructed via the `file` method (`open` is an alias) of `FTPHost` objects. `FTPFile` objects support the usual file operations for non-seekable files (`read`, `readline`, `readlines`, `write`, `writelines`, `close`). Note: ftputil currently is not threadsafe. More specifically, you can use different `FTPHost` objects in different threads but not a single `FTPHost` object in different threads. """ from ftputil.host import FTPHost from ftputil.version import __version__ # Apart from `ftputil.error` and `ftputil.stat`, this is the whole # public API of `ftputil`. __all__ = ["FTPHost", "__version__"] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7879648 ftputil-5.0.3/ftputil/error.py0000664000175000017500000001314300000000000015401 0ustar00schwaschwa# Copyright (C) 2003-2021, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. """ ftputil.error - exception classes and wrappers """ # pylint: disable=too-many-ancestors import ftplib import ftputil.path_encoding import ftputil.tool import ftputil.version # You _can_ import these with `from ftputil.error import *`, - but it's _not_ # recommended. __all__ = [ "CommandNotImplementedError", "FTPIOError", "FTPOSError", "InaccessibleLoginDirError", "InternalError", "KeepAliveError", "NoEncodingError", "ParserError", "PermanentError", "RootDirError", "SyncError", "TemporaryError", "TimeShiftError", ] class FTPError(Exception): """ General ftputil error class. """ def __init__(self, *args, original_error=None): super().__init__(*args) # `strerror` self.strerror = "" if original_error is not None: try: self.strerror = str(original_error) except Exception: # Consume all errors. If the `str` call fails, it's more # appropriate to ignore `original_error` than to raise an # exception while instantiating `FTPError`. pass elif args: # Assume the first argument is a string. It may be a byte string # though. try: self.strerror = ftputil.tool.as_str( args[0], ftputil.path_encoding.DEFAULT_ENCODING ) except TypeError: # `args[0]` isn't `str` or `bytes`. pass # `errno` self.errno = None try: self.errno = int(self.strerror[:3]) except ValueError: # `int()` argument couldn't be converted to an integer. pass # `file_name` self.file_name = None def __str__(self): return "{}\nDebugging info: {}".format( self.strerror, ftputil.version.version_info ) # Internal errors are those that have more to do with the inner workings of # ftputil than with errors on the server side. class InternalError(FTPError): """Internal error.""" pass class RootDirError(InternalError): """Raised for generic stat calls on the remote root directory.""" pass class InaccessibleLoginDirError(InternalError): """May be raised if the login directory isn't accessible.""" pass class TimeShiftError(InternalError): """Raised for invalid time shift values.""" pass class ParserError(InternalError): """Raised if a line of a remote directory can't be parsed.""" pass class CacheMissError(InternalError): """Raised if a path isn't found in the cache.""" pass class NoEncodingError(InternalError): """Raised if session instances don't specify an encoding.""" pass # Currently not used class KeepAliveError(InternalError): """Raised if the keep-alive feature failed.""" pass class FTPOSError(FTPError, OSError): """Generic FTP error related to `OSError`.""" pass class TemporaryError(FTPOSError): """Raised for temporary FTP errors (4xx).""" pass class PermanentError(FTPOSError): """Raised for permanent FTP errors (5xx).""" pass class CommandNotImplementedError(PermanentError): """Raised if the server doesn't implement a certain feature (502).""" pass class RecursiveLinksError(PermanentError): """Raised if an infinite link structure is detected.""" pass # Currently not used class SyncError(PermanentError): """Raised for problems specific to syncing directories.""" pass class FtplibErrorToFTPOSError: """ Context manager to convert `ftplib` exceptions to exceptions derived from `FTPOSError`. """ def __enter__(self): pass def __exit__(self, exc_type, exc_value, traceback): if exc_type is None: # No exception return if isinstance(exc_value, ftplib.error_temp): raise TemporaryError( *exc_value.args, original_error=exc_value ) from exc_value elif isinstance(exc_value, ftplib.error_perm): # If `exc_value.args[0]` is present, assume it's a byte or unicode # string. if exc_value.args and ftputil.tool.as_str( exc_value.args[0], ftputil.path_encoding.DEFAULT_ENCODING ).startswith("502"): raise CommandNotImplementedError( *exc_value.args, original_error=exc_value ) from exc_value else: raise PermanentError( *exc_value.args, original_error=exc_value ) from exc_value elif isinstance(exc_value, ftplib.all_errors): raise FTPOSError(*exc_value.args, original_error=exc_value) from exc_value else: raise ftplib_error_to_ftp_os_error = FtplibErrorToFTPOSError() class FTPIOError(FTPError, IOError): """Generic FTP error related to `IOError`.""" pass class FtplibErrorToFTPIOError: """ Context manager to convert `ftplib` exceptions to `FTPIOError` exceptions. """ def __enter__(self): pass def __exit__(self, exc_type, exc_value, traceback): if exc_type is None: # No exception return if isinstance(exc_value, ftplib.all_errors): raise FTPIOError(*exc_value.args, original_error=exc_value) from exc_value else: raise ftplib_error_to_ftp_io_error = FtplibErrorToFTPIOError() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642884893.2582123 ftputil-5.0.3/ftputil/file.py0000664000175000017500000001542600000000000015175 0ustar00schwaschwa# Copyright (C) 2003-2022, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. """ ftputil.file - support for file-like objects on FTP servers """ import ftputil.error # This module shouldn't be used by clients of the ftputil library. __all__ = [] try: import ssl except ImportError: SSLSocket = None else: SSLSocket = ssl.SSLSocket class FTPFile: """ Represents a file-like object associated with an FTP host. File and socket are closed appropriately if the `close` method is called. """ # Set timeout in seconds when closing file connections (see ticket #51). _close_timeout = 5 def __init__(self, host): """Construct the file(-like) object.""" self._host = host # pylint: disable=protected-access self._session = host._session # The file is still closed. self.closed = True self._conn = None self._fobj = None def _open( self, path, mode, buffering=None, encoding=None, errors=None, newline=None, *, rest=None, ): """ Open the remote file with given path name and mode. Contrary to the `open` builtin, this method returns `None`, instead this file object is modified in-place. """ # We use the same arguments as in `open`. # pylint: disable=unused-argument # pylint: disable=too-many-arguments # # Check mode. if mode is None: # This is Python's behavior for local files. raise TypeError("open() argument 2 must be str, not None") if "a" in mode: raise ftputil.error.FTPIOError("append mode not supported") if mode not in ("r", "rb", "rt", "w", "wb", "wt"): raise ftputil.error.FTPIOError("invalid mode '{}'".format(mode)) if "b" in mode and "t" in mode: # Raise a `ValueError` like Python would. raise ValueError("can't have text and binary mode at once") # Convenience variables is_binary_mode = "b" in mode is_read_mode = "r" in mode # `rest` is only allowed for binary mode. if (not is_binary_mode) and (rest is not None): raise ftputil.error.CommandNotImplementedError( "`rest` argument can't be used for text files" ) # Always use binary mode and leave any conversions to Python, # controlled by the arguments to `makefile` below. transfer_type = "I" command = "TYPE {}".format(transfer_type) with ftputil.error.ftplib_error_to_ftp_io_error: self._session.voidcmd(command) # Make transfer command. command_type = "RETR" if is_read_mode else "STOR" command = "{} {}".format(command_type, path) # Get connection and file object. with ftputil.error.ftplib_error_to_ftp_io_error: self._conn = self._session.transfercmd(command, rest) self._fobj = self._conn.makefile( mode, buffering=buffering, encoding=encoding, errors=errors, newline=newline ) # This comes last so that `close` won't try to close `FTPFile` objects # without `_conn` and `_fobj` attributes in case of an error. self.closed = False def __iter__(self): """ Return a file iterator. """ return self def __next__(self): """ Return the next line or raise `StopIteration`, if there are no more. """ # Apply implicit line ending conversion for text files. line = self.readline() if line: return line else: raise StopIteration # # Context manager methods # def __enter__(self): # Return `self`, so it can be accessed as the variable component of the # `with` statement. return self def __exit__(self, exc_type, exc_val, exc_tb): # We don't need the `exc_*` arguments here # pylint: disable=unused-argument self.close() # Be explicit return False # # Other attributes # def __getattr__(self, attr_name): """ Handle requests for attributes unknown to `FTPFile` objects: delegate the requests to the contained file object. """ if attr_name in ( "encoding flush isatty fileno read readline readlines seek tell " "truncate name softspace write writelines".split() ): return getattr(self._fobj, attr_name) raise AttributeError("'FTPFile' object has no attribute '{}'".format(attr_name)) # TODO: Implement `__dir__`? (See # http://docs.python.org/whatsnew/2.6.html#other-language-changes ) def close(self): """ Close the `FTPFile`. """ if self.closed: return # Timeout value to restore, see below. # Statement works only before the try/finally statement, otherwise # Python raises an `UnboundLocalError`. old_timeout = self._session.sock.gettimeout() try: self._fobj.close() self._fobj = None with ftputil.error.ftplib_error_to_ftp_io_error: if (SSLSocket is not None) and isinstance(self._conn, SSLSocket): self._conn.unwrap() self._conn.close() # Set a timeout to prevent waiting until server timeout if we have # a server blocking here like in ticket #51. self._session.sock.settimeout(self._close_timeout) try: with ftputil.error.ftplib_error_to_ftp_io_error: self._session.voidresp() except ftputil.error.FTPIOError as exc: # Ignore some errors, see tickets #51 and #17 at # http://ftputil.sschwarzer.net/trac/ticket/51 and # http://ftputil.sschwarzer.net/trac/ticket/17, respectively. exc = str(exc) error_code = exc[:3] if exc.splitlines()[0] != "timed out" and error_code not in ( "150", "426", "450", "451", ): raise finally: # Restore timeout for socket of `FTPFile`'s `ftplib.FTP` object in # case the connection is reused later. self._session.sock.settimeout(old_timeout) # If something went wrong before, the file is probably defunct and # subsequent calls to `close` won't help either, so we consider the # file closed for practical purposes. self.closed = True def __getstate__(self): raise TypeError("cannot serialize FTPFile object") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642880791.7885559 ftputil-5.0.3/ftputil/file_transfer.py0000664000175000017500000001450500000000000017076 0ustar00schwaschwa# Copyright (C) 2013-2020, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. """ file_transfer.py - upload, download and generic file copy """ import os import ftputil.stat # TODO: Think a bit more about the API before making it public. # Only `chunks` should be used by clients of the ftputil library. Any other # functionality is supposed to be used via `FTPHost` objects. # __all__ = ["chunks"] __all__ = [] # Maximum size of chunk in `FTPHost.copyfileobj` in bytes. MAX_COPY_CHUNK_SIZE = 64 * 1024 class LocalFile: """ Represent a file on the local side which is to be transferred or is already transferred. """ def __init__(self, name, mode): self.name = os.path.abspath(name) self.mode = mode def exists(self): """ Return `True` if the path representing this file exists. Otherwise return `False`. """ return os.path.exists(self.name) def mtime(self): """ Return the timestamp for the last modification in seconds. """ return os.path.getmtime(self.name) def mtime_precision(self): """ Return the precision of the last modification time in seconds. """ # Derived classes might want to use `self`. # pylint: disable=no-self-use # # Assume modification timestamps for local file systems are at least # precise up to a second. return 1.0 def fobj(self): """ Return a file object for the name/path in the constructor. """ return open(self.name, self.mode) class RemoteFile: """ Represent a file on the remote side which is to be transferred or is already transferred. """ def __init__(self, ftp_host, name, mode): self._host = ftp_host self._path = ftp_host.path self.name = self._path.abspath(name) self.mode = mode def exists(self): """ Return `True` if the path representing this file exists. Otherwise return `False`. """ return self._path.exists(self.name) def mtime(self): """ Return the timestamp for the last modification in seconds. """ # Convert to client time zone (see definition of time shift in # docstring of `FTPHost.set_time_shift`). return self._path.getmtime(self.name) def mtime_precision(self): """ Return the precision of the last modification time in seconds. """ # I think using `stat` instead of `lstat` makes more sense here. return self._host.stat(self.name)._st_mtime_precision def fobj(self): """ Return a file object for the name/path in the constructor. """ return self._host.open(self.name, self.mode) def source_is_newer_than_target(source_file, target_file): """ Return `True` if the source is newer than the target, else `False`. Both arguments are `LocalFile` or `RemoteFile` objects. It's assumed that the actual modification time is reported_mtime <= actual_mtime <= reported_mtime + mtime_precision i. e. that the reported mtime is the actual mtime or rounded down (truncated). For the purpose of this test the source is newer than the target if any of the possible actual source modification times is greater than the reported target modification time. In other words: If in doubt, the file should be transferred. This is the only situation where the source is _not_ considered newer than the target: |/////////////////////| possible source mtime |////////| possible target mtime That is, the latest possible actual source modification time is before the first possible actual target modification time. """ if source_file.mtime_precision() is ftputil.stat.UNKNOWN_PRECISION: return True else: return ( source_file.mtime() + source_file.mtime_precision() >= target_file.mtime() ) def chunks(fobj, max_chunk_size=MAX_COPY_CHUNK_SIZE): """ Return an iterator which yields the contents of the file object. For each iteration, at most `max_chunk_size` bytes are read from `fobj` and yielded as a byte string. If the file object is exhausted, then don't yield any more data but stop the iteration, so the client does _not_ get an empty byte string. Any exceptions resulting from reading the file object are passed through to the client. """ while True: chunk = fobj.read(max_chunk_size) if not chunk: break yield chunk def copyfileobj( source_fobj, target_fobj, max_chunk_size=MAX_COPY_CHUNK_SIZE, callback=None ): """ Copy data from file-like object source to file-like object target. """ # Inspired by `shutil.copyfileobj` (I don't use the `shutil` code directly # because it might change) for chunk in chunks(source_fobj, max_chunk_size): target_fobj.write(chunk) if callback is not None: callback(chunk) def copy_file(source_file, target_file, conditional, callback): """ Copy a file from `source_file` to `target_file`. These are `LocalFile` or `RemoteFile` objects. Which of them is a local or a remote file, respectively, is determined by the arguments. If `conditional` is true, the file is only copied if the target doesn't exist or is older than the source. If `conditional` is false, the file is copied unconditionally. Return `True` if the file was copied, else `False`. """ if conditional: # Evaluate condition: The target file either doesn't exist or is older # than the source file. If in doubt (due to imprecise timestamps), # perform the transfer. transfer_condition = not target_file.exists() or source_is_newer_than_target( source_file, target_file ) if not transfer_condition: # We didn't transfer. return False source_fobj = source_file.fobj() try: target_fobj = target_file.fobj() try: copyfileobj(source_fobj, target_fobj, callback=callback) finally: target_fobj.close() finally: source_fobj.close() # Transfer accomplished return True ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7879648 ftputil-5.0.3/ftputil/host.py0000664000175000017500000012651200000000000015232 0ustar00schwaschwa# Copyright (C) 2002-2021, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. """ `FTPHost` is the central class of the `ftputil` library. See `__init__.py` for an example. """ import datetime import errno import ftplib import stat import sys import time import ftputil.error import ftputil.file import ftputil.file_transfer import ftputil.path import ftputil.path_encoding import ftputil.session import ftputil.stat import ftputil.tool __all__ = ["FTPHost"] # The "protected" attributes PyLint talks about aren't intended for clients of # the library. `FTPHost` objects need to use some of these library-internal # attributes though. # pylint: disable=protected-access # For Python versions 3.8 and below, ftputil has implicitly defaulted to # latin-1 encoding. Prefer that behavior for Python 3.9 and up as well instead # of using the encoding that is the default for `ftplib.FTP` in the Python # version. if ftputil.path_encoding.RUNNING_UNDER_PY39_AND_UP: class default_session_factory(ftplib.FTP): def __init__(self, *args, **kwargs): # Python 3.9 defines `encoding` as a keyword-only argument, so test # only for the `encoding` argument in `kwargs`. # # Only use the ftputil default encoding if the caller didn't pass # an encoding. if "encoding" not in kwargs: kwargs["encoding"] = ftputil.path_encoding.DEFAULT_ENCODING super().__init__(*args, **kwargs) else: default_session_factory = ftplib.FTP ##################################################################### # `FTPHost` class with several methods similar to those of `os` class FTPHost: """ FTP host class. """ # Implementation notes: # # Upon every request of a file (`FTPFile` object) a new FTP session is # created (or reused from a cache), leading to a child session of the # `FTPHost` object from which the file is requested. # # This is needed because opening an `FTPFile` will make the local session # object wait for the completion of the transfer. In fact, code like this # would block indefinitely, if the `RETR` request would be made on the # `_session` of the object host: # # host = FTPHost(ftp_server, user, password) # f = host.open("index.html") # host.getcwd() # would block! # # On the other hand, the initially constructed host object will store # references to already established `FTPFile` objects and reuse an # associated connection if its associated `FTPFile` has been closed. def __init__(self, *args, **kwargs): """ Abstract initialization of `FTPHost` object. """ # Store arguments for later operations. self._args = args self._kwargs = kwargs # XXX: Maybe put the following in a `reset` method. # The time shift setting shouldn't be reset though. Make a session # according to these arguments. self._session = self._make_session() # Simulate `os.path`. self.path = ftputil.path._Path(self) # lstat, stat, listdir services. self._stat = ftputil.stat._Stat(self) self.stat_cache = self._stat._lstat_cache self.stat_cache.enable() with ftputil.error.ftplib_error_to_ftp_os_error: current_dir = self._session.pwd() self._cached_current_dir = self.path.normpath( ftputil.tool.as_str_path(current_dir, encoding=self._encoding) ) # Associated `FTPHost` objects for data transfer. self._children = [] # This is only set to something else than `None` if this instance # represents an `FTPFile`. self._file = None # Now opened. self.closed = False # Set curdir, pardir etc. for the remote host. RFC 959 states that this # is, strictly speaking, dependent on the server OS but it seems to # work at least with Unix and Windows servers. self.curdir, self.pardir, self.sep = ".", "..", "/" # Set default time shift (used in `upload_if_newer` and # `download_if_newer`). self._time_shift = 0.0 # Don't use `LIST -a` option by default. If the server doesn't # understand the `-a` option and interprets it as a path, the results # can be surprising. See ticket #110. self.use_list_a_option = False def keep_alive(self): """ Try to keep the connection alive in order to avoid server timeouts. Note that this won't help if the connection has already timed out! In this case, `keep_alive` will raise an `TemporaryError`. (Actually, if you get a server timeout, the error - for a specific connection - will be permanent.) """ # Warning: Don't call this method on `FTPHost` instances which # represent file transfers. This may fail in confusing ways. with ftputil.error.ftplib_error_to_ftp_os_error: # Ignore return value. self._session.pwd() # # Dealing with child sessions and file-like objects (rather low-level) # def _make_session(self): """ Return a new session object according to the current state of this `FTPHost` instance. """ # Don't modify original attributes below. args = self._args[:] kwargs = self._kwargs.copy() # If a session factory has been given on the instantiation of this # `FTPHost` object, use the same factory for this `FTPHost` object's # child sessions. factory = kwargs.pop("session_factory", default_session_factory) with ftputil.error.ftplib_error_to_ftp_os_error: session = factory(*args, **kwargs) if not hasattr(session, "encoding"): raise ftputil.error.NoEncodingError( f"session instance {session!r} must have an `encoding` attribute" ) self._encoding = session.encoding return session def _copy(self): """ Return a copy of this `FTPHost` object. """ # The copy includes a new session factory return value (aka session) # but doesn't copy the state of `self.getcwd()`. return self.__class__(*self._args, **self._kwargs) def _available_child(self): """ Return an available (i. e. one whose `_file` object is closed and doesn't have a timed-out server connection) child (`FTPHost` object) from the pool of children or `None` if there aren't any. """ # TODO: Currently timed-out child sessions aren't removed and may # collect over time. In very busy or long running processes, this might # slow down an application because the same stale child sessions have # to be processed again and again. for host in self._children: # Test for timeouts only after testing for a closed file: # - If a file isn't closed, save time; don't bother to access the # remote server. # - If a file transfer on the child is in progress, requesting the # directory is an invalid operation because of the way the FTP # state machine works (see RFC 959). if host._file.closed: try: host._session.pwd() # Under high load, a 226 status response from a previous # download may arrive too late, so that it's "seen" in the # `pwd` call. For now, skip the potential child session; it # will be considered again when `_available_child` is called # the next time. except ftplib.error_reply: continue # Timed-out sessions raise `error_temp`. except ftplib.error_temp: continue # The server may have closed the connection which may cause # `host._session.getline` to raise an `EOFError` (see ticket # #114). except EOFError: continue # Under high load, there may be a socket read timeout during # the last FTP file `close` (see ticket #112). Note that a # socket timeout is quite different from an FTP session # timeout. except OSError: continue else: # Everything's ok; use this `FTPHost` instance. return host # Be explicit. return None def open( self, path, mode="r", buffering=None, encoding=None, errors=None, newline=None, *, rest=None, ): """ Return an open file(-like) object which is associated with this `FTPHost` object. The arguments `path`, `mode`, `buffering`, `encoding`, `errors` and `newlines` have the same meaning as for `open`. If `rest` is given as an integer, - reading will start at the byte (zero-based) `rest` - writing will overwrite the remote file from byte `rest` This method tries to reuse a child but will generate a new one if none is available. """ # Support the same arguments as `open`. # pylint: disable=too-many-arguments path = ftputil.tool.as_str_path(path, encoding=self._encoding) host = self._available_child() if host is None: host = self._copy() self._children.append(host) host._file = ftputil.file.FTPFile(host) basedir = self.getcwd() # Prepare for changing the directory (see whitespace workaround in # method `_dir`). if host.path.isabs(path): effective_path = path else: effective_path = host.path.join(basedir, path) effective_dir, effective_file = host.path.split(effective_path) try: # This will fail if the directory isn't accessible at all. host.chdir(effective_dir) except ftputil.error.PermanentError: # Similarly to a failed `file` in a local file system, raise an # `IOError`, not an `OSError`. raise ftputil.error.FTPIOError( "remote directory '{}' doesn't " "exist or has insufficient access rights".format(effective_dir) ) host._file._open( effective_file, mode=mode, buffering=buffering, encoding=encoding, errors=errors, newline=newline, rest=rest, ) if "w" in mode: # Invalidate cache entry because size and timestamps will change. self.stat_cache.invalidate(effective_path) return host._file def close(self): """ Close host connection. """ if self.closed: return # Close associated children. for host in self._children: # Children have a `_file` attribute which is an `FTPFile` object. host._file.close() host.close() # Now deal with ourself. try: with ftputil.error.ftplib_error_to_ftp_os_error: self._session.close() finally: # If something went wrong before, the host/session is probably # defunct and subsequent calls to `close` won't help either, so # consider the host/session closed for practical purposes. self.stat_cache.clear() self._children = [] self.closed = True # # Setting a custom directory parser # def set_parser(self, parser): """ Set the parser for extracting stat results from directory listings. The parser interface is described in the documentation, but here are the most important things: - A parser should derive from `ftputil.stat.Parser`. - The parser has to implement two methods, `parse_line` and `ignores_line`. For the latter, there's a probably useful default in the class `ftputil.stat.Parser`. - `parse_line` should try to parse a line of a directory listing and return a `ftputil.stat.StatResult` instance. If parsing isn't possible, raise `ftputil.error.ParserError` with a useful error message. - `ignores_line` should return a true value if the line isn't assumed to contain stat information. """ # The cache contents, if any, probably aren't useful. self.stat_cache.clear() # Set the parser explicitly, don't allow "smart" switching anymore. self._stat._parser = parser self._stat._allow_parser_switching = False # # Time shift adjustment between client (i. e. us) and server # @staticmethod def __rounded_time_shift(time_shift): """ Return the given time shift in seconds, but rounded to 15-minute units. The argument is also assumed to be given in seconds. """ minute = 60.0 # Avoid division by zero below. if time_shift == 0: return 0.0 # Use a positive value for rounding. absolute_time_shift = abs(time_shift) signum = time_shift / absolute_time_shift # Round absolute time shift to 15-minute units. absolute_rounded_time_shift = int( (absolute_time_shift + (7.5 * minute)) / (15.0 * minute) ) * (15.0 * minute) # Return with correct sign. return signum * absolute_rounded_time_shift def __assert_valid_time_shift(self, time_shift): """ Perform sanity checks on the time shift value (given in seconds). If the value is invalid, raise a `TimeShiftError`, else simply return `None`. """ minute = 60.0 # seconds hour = 60.0 * minute absolute_rounded_time_shift = abs(self.__rounded_time_shift(time_shift)) # Test 1: Fail if the absolute time shift is greater than a full day # (24 hours). if absolute_rounded_time_shift > 24 * hour: raise ftputil.error.TimeShiftError( "time shift abs({0:.2f} s) > 1 day".format(time_shift) ) # Test 2: Fail if the deviation between given time shift and 15-minute # units is greater than a certain limit. maximum_deviation = 5 * minute if abs(time_shift - self.__rounded_time_shift(time_shift)) > maximum_deviation: raise ftputil.error.TimeShiftError( "time shift ({0:.2f} s) deviates more than {1:d} s " "from 15-minute units".format(time_shift, int(maximum_deviation)) ) def set_time_shift(self, time_shift): """ Set the time shift value. By (my) definition, the time shift value is the difference of the time zone used in server listings and UTC, i. e. time_shift =def= t_server - utc <=> t_server = utc + time_shift <=> utc = t_server - time_shift The time shift is measured in seconds. """ self.__assert_valid_time_shift(time_shift) old_time_shift = self.time_shift() if time_shift != old_time_shift: # If the time shift changed, all entries in the cache will have # wrong times with respect to the updated time shift, therefore # clear the cache. self.stat_cache.clear() self._time_shift = self.__rounded_time_shift(time_shift) def time_shift(self): """ Return the time shift between FTP server and client. See the docstring of `set_time_shift` for more on this value. """ return self._time_shift def synchronize_times(self): """ Synchronize the local times of FTP client and server. This is necessary to let `upload_if_newer` and `download_if_newer` work correctly. If `synchronize_times` isn't applicable (see below), the time shift can still be set explicitly with `set_time_shift`. This implementation of `synchronize_times` requires _all_ of the following: - The connection between server and client is established. - The client has write access to the directory that is current when `synchronize_times` is called. The common usage pattern of `synchronize_times` is to call it directly after the connection is established. (As can be concluded from the points above, this requires write access to the login directory.) If `synchronize_times` fails, it raises a `TimeShiftError`. """ helper_file_name = "_ftputil_sync_" # Open a dummy file for writing in the current directory on the FTP # host, then close it. try: # May raise `FTPIOError` if directory isn't writable. file_ = self.open(helper_file_name, "w") file_.close() except ftputil.error.FTPIOError: raise ftputil.error.TimeShiftError( """couldn't write helper file in directory '{}'""".format(self.getcwd()) ) # If everything worked up to here it should be possible to stat and # then remove the just-written file. try: server_time = self.path.getmtime(helper_file_name) self.unlink(helper_file_name) except ftputil.error.FTPOSError: # If we got a `TimeShiftError` exception above, we should't come # here: if we didn't get a `TimeShiftError` above, deletion should # be possible. The only reason for an exception I can think of here # is a race condition by removing the helper file or write # permission from the directory or helper file after it has been # written to. raise ftputil.error.TimeShiftError( "could write helper file but not unlink it" ) # Calculate the difference between server and client. now = time.time() time_shift = server_time - now # As the time shift for this host instance isn't set yet, the directory # parser will calculate times one year in the past if the time zone of # the server is east from ours. Thus the time shift will be off by a # year as well (see ticket #55). if time_shift < -360 * 24 * 60 * 60: # Read one year and recalculate the time shift. We don't know how # many days made up that year (it might have been a leap year), so # go the route via `datetime.replace`. server_datetime = datetime.datetime.fromtimestamp( server_time, tz=datetime.timezone.utc ) server_datetime = server_datetime.replace(year=server_datetime.year + 1) time_shift = server_datetime.timestamp() - now self.set_time_shift(time_shift) # # Operations based on file-like objects (rather high-level), like upload # and download # # XXX: This has a different API from `shutil.copyfileobj`, on which this # method is modeled. But I don't think it makes sense to change this method # here because the method is probably rarely used and a change would break # client code. @staticmethod def copyfileobj( source, target, max_chunk_size=ftputil.file_transfer.MAX_COPY_CHUNK_SIZE, callback=None, ): """ Copy data from file-like object `source` to file-like object `target`. """ ftputil.file_transfer.copyfileobj(source, target, max_chunk_size, callback) def _upload_files(self, source_path, target_path): """ Return a `LocalFile` and `RemoteFile` as source and target, respectively. The strings `source_path` and `target_path` are the (absolute or relative) paths of the local and the remote file, respectively. """ source_file = ftputil.file_transfer.LocalFile(source_path, "rb") # Passing `self` (the `FTPHost` instance) here is correct. target_file = ftputil.file_transfer.RemoteFile(self, target_path, "wb") return source_file, target_file def upload(self, source, target, callback=None): """ Upload a file from the local source (name) to the remote target (name). If a callable `callback` is given, it's called after every chunk of transferred data. The chunk size is a constant defined in `file_transfer`. The callback will be called with a single argument, the data chunk that was transferred before the callback was called. """ if source in ["", b""]: raise IOError("path argument `source` is empty") ftputil.tool.raise_for_empty_path(target, path_argument_name="target") target = ftputil.tool.as_str_path(target, encoding=self._encoding) source_file, target_file = self._upload_files(source, target) ftputil.file_transfer.copy_file( source_file, target_file, conditional=False, callback=callback ) def upload_if_newer(self, source, target, callback=None): """ Upload a file only if it's newer than the target on the remote host or if the target file does not exist. See the method `upload` for the meaning of the parameters. If an upload was necessary, return `True`, else return `False`. If a callable `callback` is given, it's called after every chunk of transferred data. The chunk size is a constant defined in `file_transfer`. The callback will be called with a single argument, the data chunk that was transferred before the callback was called. """ ftputil.tool.raise_for_empty_path(source, path_argument_name="source") if target in ["", b""]: raise IOError("path argument `target` is empty") target = ftputil.tool.as_str_path(target, encoding=self._encoding) source_file, target_file = self._upload_files(source, target) return ftputil.file_transfer.copy_file( source_file, target_file, conditional=True, callback=callback ) def _download_files(self, source_path, target_path): """ Return a `RemoteFile` and `LocalFile` as source and target, respectively. The strings `source_path` and `target_path` are the (absolute or relative) paths of the remote and the local file, respectively. """ source_file = ftputil.file_transfer.RemoteFile(self, source_path, "rb") target_file = ftputil.file_transfer.LocalFile(target_path, "wb") return source_file, target_file def download(self, source, target, callback=None): """ Download a file from the remote source (name) to the local target (name). If a callable `callback` is given, it's called after every chunk of transferred data. The chunk size is a constant defined in `file_transfer`. The callback will be called with a single argument, the data chunk that was transferred before the callback was called. """ ftputil.tool.raise_for_empty_path(source, path_argument_name="source") if target in ["", b""]: raise IOError("path argument `target` is empty") source = ftputil.tool.as_str_path(source, encoding=self._encoding) source_file, target_file = self._download_files(source, target) ftputil.file_transfer.copy_file( source_file, target_file, conditional=False, callback=callback ) def download_if_newer(self, source, target, callback=None): """ Download a file only if it's newer than the target on the local host or if the target file does not exist. See the method `download` for the meaning of the parameters. If a download was necessary, return `True`, else return `False`. If a callable `callback` is given, it's called after every chunk of transferred data. The chunk size is a constant defined in `file_transfer`. The callback will be called with a single argument, the data chunk that was transferred before the callback was called. """ if source in ["", b""]: raise IOError("path argument `source` is empty") ftputil.tool.raise_for_empty_path(target, path_argument_name="target") source = ftputil.tool.as_str_path(source, encoding=self._encoding) source_file, target_file = self._download_files(source, target) return ftputil.file_transfer.copy_file( source_file, target_file, conditional=True, callback=callback ) # # Helper methods to descend into a directory before executing a command # def _check_inaccessible_login_directory(self): """ Raise an `InaccessibleLoginDirError` exception if we can't change to the login directory. This test is only reliable if the current directory is the login directory. """ presumable_login_dir = self.getcwd() # Bail out with an internal error rather than modify the current # directory without hope of restoration. try: self.chdir(presumable_login_dir) except ftputil.error.PermanentError: raise ftputil.error.InaccessibleLoginDirError( "directory '{}' is not accessible".format(presumable_login_dir) ) def _robust_ftp_command(self, command, path, descend_deeply=False): """ Run an FTP command on a path. The return value of the method is the return value of the command. If `descend_deeply` is true (the default is false), descend deeply, i. e. change the directory to the end of the path. """ # If we can't change to the yet-current directory, the code below won't # work (see below), so in this case rather raise an exception than # giving wrong results. self._check_inaccessible_login_directory() # Some FTP servers don't behave as expected if the directory portion of # the path contains whitespace; some even yield strange results if the # command isn't executed in the current directory. Therefore, change to # the directory which contains the item to run the command on and # invoke the command just there. # # Remember old working directory. old_dir = self.getcwd() try: if descend_deeply: # Invoke the command in (not: on) the deepest directory. self.chdir(path) # Workaround for some servers that give recursive listings when # called with a dot as path; see issue #33, # http://ftputil.sschwarzer.net/trac/ticket/33 return command(self, "") else: # Invoke the command in the "next to last" directory. head, tail = self.path.split(path) self.chdir(head) return command(self, tail) finally: self.chdir(old_dir) # # Miscellaneous utility methods resembling functions in `os` # def getcwd(self): """ Return the current directory path. """ return self._cached_current_dir def chdir(self, path): """ Change the directory on the host to `path`. """ path = ftputil.tool.as_str_path(path, encoding=self._encoding) with ftputil.error.ftplib_error_to_ftp_os_error: self._session.cwd(path) # The path given as the argument is relative to the old current # directory, therefore join them. self._cached_current_dir = self.path.normpath( self.path.join(self._cached_current_dir, path) ) # Ignore unused argument `mode` # pylint: disable=unused-argument def mkdir(self, path, mode=None): """ Make the directory path on the remote host. The argument `mode` is ignored and only "supported" for similarity with `os.mkdir`. """ ftputil.tool.raise_for_empty_path(path) path = ftputil.tool.as_str_path(path, encoding=self._encoding) def command(self, path): """Callback function.""" with ftputil.error.ftplib_error_to_ftp_os_error: self._session.mkd(path) self._robust_ftp_command(command, path) # TODO: The virtual directory support doesn't have unit tests yet because # the mocking most likely would be quite complicated. The tests should be # added when mainly the `mock` library is used instead of the mock code in # `test.mock_ftplib`. # # Ignore unused argument `mode` # pylint: disable=unused-argument def makedirs(self, path, mode=None, exist_ok=False): """ Make the directory `path`, but also make not yet existing intermediate directories, like `os.makedirs`. The value of `mode` is only accepted for compatibility with `os.makedirs` but otherwise ignored. If `exist_ok` is `False` (the default) and the leaf directory exists, raise a `PermanentError` with `errno` 17. """ ftputil.tool.raise_for_empty_path(path) path = ftputil.tool.as_str_path(path, encoding=self._encoding) path = self.path.abspath(path) directories = path.split(self.sep) old_dir = self.getcwd() try: # Try to build the directory chain from the "uppermost" to the # "lowermost" directory. for index in range(1, len(directories)): # Re-insert the separator which got lost by using `path.split`. next_directory = self.sep + self.path.join(*directories[: index + 1]) # If we have "virtual directories" (see #86), just listing the # parent directory won't tell us if a directory actually # exists. So try to change into the directory. try: self.chdir(next_directory) except ftputil.error.PermanentError: # Directory presumably doesn't exist. try: self.mkdir(next_directory) except ftputil.error.PermanentError: # Find out the cause of the error. Re-raise the # exception only if the directory didn't exist already, # else something went _really_ wrong, e. g. there's a # regular file with the name of the directory. if not self.path.isdir(next_directory): raise else: # Directory exists. If we are at the last directory # component and `exist_ok` is `False`, this is an error. if (index == len(directories) - 1) and (not exist_ok): # Before PEP 3151, if `exist_ok` is `False`, trying to # create an existing directory in the local file system # results in an `OSError` with `errno.EEXIST, so # emulate this also for FTP. ftp_os_error = ftputil.error.PermanentError( "path {!r} exists".format(path) ) ftp_os_error.errno = errno.EEXIST raise ftp_os_error finally: self.chdir(old_dir) def rmdir(self, path): """ Remove the _empty_ directory `path` on the remote host. Compatibility note: Previous versions of ftputil could possibly delete non-empty directories as well, - if the server allowed it. This is no longer supported. """ ftputil.tool.raise_for_empty_path(path) path = ftputil.tool.as_str_path(path, encoding=self._encoding) path = self.path.abspath(path) if self.listdir(path): raise ftputil.error.PermanentError("directory '{}' not empty".format(path)) # XXX: How does `rmd` work with links? def command(self, path): """Callback function.""" with ftputil.error.ftplib_error_to_ftp_os_error: self._session.rmd(path) self._robust_ftp_command(command, path) self.stat_cache.invalidate(path) def remove(self, path): """ Remove the file or link given by `path`. Raise a `PermanentError` if the path doesn't exist, but maybe raise other exceptions depending on the state of the server (e. g. timeout). """ ftputil.tool.raise_for_empty_path(path) path = ftputil.tool.as_str_path(path, encoding=self._encoding) path = self.path.abspath(path) # Though `isfile` includes also links to files, `islink` is needed to # include links to directories. if ( self.path.isfile(path) or self.path.islink(path) or not self.path.exists(path) ): # If the path doesn't exist, let the removal command trigger an # exception with a more appropriate error message. def command(self, path): """Callback function.""" with ftputil.error.ftplib_error_to_ftp_os_error: self._session.delete(path) self._robust_ftp_command(command, path) else: raise ftputil.error.PermanentError( "remove/unlink can only delete files and links, " "not directories" ) self.stat_cache.invalidate(path) unlink = remove def rmtree(self, path, ignore_errors=False, onerror=None): """ Remove the given remote, possibly non-empty, directory tree. The interface of this method is rather complex, in favor of compatibility with `shutil.rmtree`. If `ignore_errors` is set to a true value, errors are ignored. If `ignore_errors` is a false value _and_ `onerror` isn't set, all exceptions occurring during the tree iteration and processing are raised. These exceptions are all of type `PermanentError`. To distinguish between error situations, pass in a callable for `onerror`. This callable must accept three arguments: `func`, `path` and `exc_info`. `func` is a bound method object, _for example_ `your_host_object.listdir`. `path` is the path that was the recent argument of the respective method (`listdir`, `remove`, `rmdir`). `exc_info` is the exception info as it's got from `sys.exc_info`. Implementation note: The code is copied from `shutil.rmtree` in Python 2.4 and adapted to ftputil. """ ftputil.tool.raise_for_empty_path(path) path = ftputil.tool.as_str_path(path, encoding=self._encoding) # The following code is an adapted version of Python 2.4's # `shutil.rmtree` function. if ignore_errors: def new_onerror(*args): """Do nothing.""" # pylint: disable=unused-argument pass elif onerror is None: def new_onerror(*args): """Re-raise exception.""" # pylint: disable=misplaced-bare-raise, unused-argument raise else: new_onerror = onerror names = [] try: names = self.listdir(path) except ftputil.error.PermanentError: new_onerror(self.listdir, path, sys.exc_info()) for name in names: full_name = self.path.join(path, name) try: mode = self.lstat(full_name).st_mode except ftputil.error.PermanentError: mode = 0 if stat.S_ISDIR(mode): self.rmtree(full_name, ignore_errors, new_onerror) else: try: self.remove(full_name) except ftputil.error.PermanentError: new_onerror(self.remove, full_name, sys.exc_info()) try: self.rmdir(path) except ftputil.error.FTPOSError: new_onerror(self.rmdir, path, sys.exc_info()) def rename(self, source, target): """ Rename the source on the FTP host to target. """ ftputil.tool.raise_for_empty_path(source, path_argument_name="source") ftputil.tool.raise_for_empty_path(target, path_argument_name="target") source = ftputil.tool.as_str_path(source, encoding=self._encoding) target = ftputil.tool.as_str_path(target, encoding=self._encoding) # The following code is in spirit similar to the code in the method # `_robust_ftp_command`, though we do _not_ do _everything_ imaginable. self._check_inaccessible_login_directory() source_head, source_tail = self.path.split(source) target_head, target_tail = self.path.split(target) paths_contain_whitespace = (" " in source_head) or (" " in target_head) if paths_contain_whitespace and source_head == target_head: # Both items are in the same directory. old_dir = self.getcwd() try: self.chdir(source_head) with ftputil.error.ftplib_error_to_ftp_os_error: self._session.rename(source_tail, target_tail) finally: self.chdir(old_dir) else: # Use straightforward command. with ftputil.error.ftplib_error_to_ftp_os_error: self._session.rename(source, target) # XXX: One could argue to put this method into the `_Stat` class, but I # refrained from that because then `_Stat` would have to know about # `FTPHost`'s `_session` attribute and in turn about `_session`'s `dir` # method. def _dir(self, path): """ Return a directory listing as made by FTP's `LIST` command as a list of strings. """ # Don't use `self.path.isdir` in this method because that would cause a # call of `(l)stat` and thus a call to `_dir`, so we would end up with # an infinite recursion. def _FTPHost_dir_command(self, path): """Callback function.""" lines = [] def callback(line): """Callback function.""" lines.append(ftputil.tool.as_str(line, encoding=self._encoding)) with ftputil.error.ftplib_error_to_ftp_os_error: if self.use_list_a_option: self._session.dir("-a", path, callback) else: self._session.dir(path, callback) return lines lines = self._robust_ftp_command( _FTPHost_dir_command, path, descend_deeply=True ) return lines # The `listdir`, `lstat` and `stat` methods don't use `_robust_ftp_command` # because they implicitly already use `_dir` which actually uses # `_robust_ftp_command`. def listdir(self, path): """ Return a list of directories, files etc. in the directory named `path`. If the directory listing from the server can't be parsed with any of the available parsers raise a `ParserError`. """ ftputil.tool.raise_for_empty_path(path) original_path = path path = ftputil.tool.as_str_path(path, encoding=self._encoding) items = self._stat._listdir(path) return [ ftputil.tool.same_string_type_as(original_path, item, self._encoding) for item in items ] def lstat(self, path, _exception_for_missing_path=True): """ Return an object similar to that returned by `os.lstat`. If the directory listing from the server can't be parsed with any of the available parsers, raise a `ParserError`. If the directory _can_ be parsed and the `path` is _not_ found, raise a `PermanentError`. (`_exception_for_missing_path` is an implementation aid and _not_ intended for use by ftputil clients.) """ ftputil.tool.raise_for_empty_path(path) path = ftputil.tool.as_str_path(path, encoding=self._encoding) return self._stat._lstat(path, _exception_for_missing_path) def stat(self, path, _exception_for_missing_path=True): """ Return info from a "stat" call on `path`. If the directory containing `path` can't be parsed, raise a `ParserError`. If the directory containing `path` can be parsed but the `path` can't be found, raise a `PermanentError`. Also raise a `PermanentError` if there's an endless (cyclic) chain of symbolic links "behind" the `path`. (`_exception_for_missing_path` is an implementation aid and _not_ intended for use by ftputil clients.) """ ftputil.tool.raise_for_empty_path(path) path = ftputil.tool.as_str_path(path, encoding=self._encoding) return self._stat._stat(path, _exception_for_missing_path) def walk(self, top, topdown=True, onerror=None, followlinks=False): """ Iterate over directory tree and return a tuple (dirpath, dirnames, filenames) on each iteration, like the `os.walk` function (see https://docs.python.org/library/os.html#os.walk ). """ ftputil.tool.raise_for_empty_path(top, path_argument_name="top") top = ftputil.tool.as_str_path(top, encoding=self._encoding) # The following code is copied from `os.walk` in Python 2.4 and adapted # to ftputil. try: names = self.listdir(top) except ftputil.error.FTPOSError as err: if onerror is not None: onerror(err) return dirs, nondirs = [], [] for name in names: if self.path.isdir(self.path.join(top, name)): dirs.append(name) else: nondirs.append(name) if topdown: yield top, dirs, nondirs for name in dirs: path = self.path.join(top, name) if followlinks or not self.path.islink(path): yield from self.walk(path, topdown, onerror, followlinks) if not topdown: yield top, dirs, nondirs def chmod(self, path, mode): """ Change the mode of a remote `path` (a string) to the integer `mode`. This integer uses the same bits as the mode value returned by the `stat` and `lstat` commands. If something goes wrong, raise a `TemporaryError` or a `PermanentError`, according to the status code returned by the server. In particular, a non-existent path usually causes a `PermanentError`. """ ftputil.tool.raise_for_empty_path(path) path = ftputil.tool.as_str_path(path, encoding=self._encoding) path = self.path.abspath(path) def command(self, path): """Callback function.""" with ftputil.error.ftplib_error_to_ftp_os_error: self._session.voidcmd("SITE CHMOD 0{0:o} {1}".format(mode, path)) self._robust_ftp_command(command, path) self.stat_cache.invalidate(path) def __getstate__(self): raise TypeError("cannot serialize FTPHost object") # # Context manager methods # def __enter__(self): # Return `self`, so it can be accessed as the variable component of the # `with` statement. return self def __exit__(self, exc_type, exc_val, exc_tb): # We don't need the `exc_*` arguments here. # pylint: disable=unused-argument self.close() # Be explicit. return False ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7889647 ftputil-5.0.3/ftputil/lrucache.py0000664000175000017500000002337500000000000016046 0ustar00schwaschwa# lrucache.py -- a simple LRU (Least-Recently-Used) cache class # Copyright 2004 Evan Prodromou # # Copyright 2009-2018 Stefan Schwarzer # (some changes to the original version) # Licensed under the Academic Free License 2.1 # Licensed for ftputil under the revised BSD license # with permission by the author, Evan Prodromou. Many # thanks, Evan! :-) # # The original file is available at # http://pypi.python.org/pypi/lrucache/0.2 . # arch-tag: LRU cache main module """a simple LRU (Least-Recently-Used) cache module This module provides very simple LRU (Least-Recently-Used) cache functionality. An *in-memory cache* is useful for storing the results of an 'expensive' process (one that takes a lot of time or resources) for later re-use. Typical examples are accessing data from the filesystem, a database, or a network location. If you know you'll need to re-read the data again, it can help to keep it in a cache. You *can* use a Python dictionary as a cache for some purposes. However, if the results you're caching are large, or you have a lot of possible results, this can be impractical memory-wise. An *LRU cache*, on the other hand, only keeps _some_ of the results in memory, which keeps you from overusing resources. The cache is bounded by a maximum size; if you try to add more values to the cache, it will automatically discard the values that you haven't read or written to in the longest time. In other words, the least-recently-used items are discarded. [1]_ .. [1]: 'Discarded' here means 'removed from the cache'. """ import time # The suffix after the hyphen denotes modifications by the # ftputil project with respect to the original version. __version__ = "0.2-15" __all__ = ["CacheKeyError", "LRUCache", "DEFAULT_SIZE"] __docformat__ = "reStructuredText en" # Default size of a new LRUCache object, if no 'size' argument is given. DEFAULT_SIZE = 16 class CacheKeyError(KeyError): """Error raised when cache requests fail. When a cache record is accessed which no longer exists (or never did), this error is raised. To avoid it, you may want to check for the existence of a cache record before reading or deleting it. """ pass class LRUCache: """Least-Recently-Used (LRU) cache. Instances of this class provide a least-recently-used (LRU) cache. They emulate a Python mapping type. You can use an LRU cache more or less like a Python dictionary, with the exception that objects you put into the cache may be discarded before you take them out. Some example usage:: cache = LRUCache(32) # new cache cache['foo'] = get_file_contents('foo') # or whatever if 'foo' in cache: # if it's still in cache... # use cached version contents = cache['foo'] else: # recalculate contents = get_file_contents('foo') # store in cache for next time cache['foo'] = contents print(cache.size) # Maximum size print(len(cache)) # 0 <= len(cache) <= cache.size cache.size = 10 # Auto-shrink on size assignment for i in range(50): # note: larger than cache size cache[i] = i if 0 not in cache: print('Zero was discarded.') if 42 in cache: del cache[42] # Manual deletion for j in cache: # iterate (in LRU order) print(j, cache[j]) # iterator produces keys, not values """ class _Node: """Record of a cached value. Not for public consumption.""" def __init__(self, key, obj, timestamp, sort_key): object.__init__(self) self.key = key self.obj = obj self.atime = timestamp self.mtime = self.atime self._sort_key = sort_key def __lt__(self, other): # Seems to be preferred over `__cmp__`, at least in newer # Python versions. Uses only around 60 % of the time # with respect to `__cmp__`. # pylint: disable=protected-access return self._sort_key < other._sort_key def __repr__(self): return "<%s %s => %s (%s)>" % ( self.__class__, self.key, self.obj, time.asctime(time.localtime(self.atime)), ) def __init__(self, size=DEFAULT_SIZE): """Init the `LRUCache` object. `size` is the initial _maximum_ size of the cache. The size can be changed by setting the `size` attribute. """ self.clear() # Maximum size of the cache. If more than 'size' elements are # added to the cache, the least-recently-used ones will be # discarded. This assignment implicitly checks the size value. self.size = size def clear(self): """Clear the cache, removing all elements. The `size` attribute of the cache isn't modified. """ # pylint: disable=attribute-defined-outside-init self.__heap = [] self.__dict = {} self.__counter = 0 def _sort_key(self): """Return a new integer value upon every call. Cache nodes need a monotonically increasing time indicator. `time.time()` and `time.clock()` don't guarantee this in a platform-independent way. See http://ftputil.sschwarzer.net/trac/ticket/32 for details. """ self.__counter += 1 return self.__counter def __len__(self): """Return _current_ number of cache entries. This may be different from the value of the `size` attribute. """ return len(self.__heap) def __contains__(self, key): """Return `True` if the item denoted by `key` is in the cache.""" return key in self.__dict def __setitem__(self, key, obj): """Store item `obj` in the cache under the key `key`. If the number of elements after the addition of a new key would exceed the maximum cache size, the least recently used item in the cache is "forgotten". """ heap = self.__heap dict_ = self.__dict if key in dict_: node = dict_[key] # Update node object in-place. node.obj = obj node.atime = time.time() node.mtime = node.atime # pylint: disable=protected-access node._sort_key = self._sort_key() else: # The size of the heap can be at most the value of # `self.size` because `__setattr__` decreases the cache # size if the new size value is smaller; so we don't # need a loop _here_. if len(heap) == self.size: lru_node = min(heap) heap.remove(lru_node) del dict_[lru_node.key] node = self._Node(key, obj, time.time(), self._sort_key()) dict_[key] = node heap.append(node) def __getitem__(self, key): """Return the item stored under `key` key. If no such key is present in the cache, raise a `CacheKeyError`. """ if not key in self.__dict: raise CacheKeyError(key) else: node = self.__dict[key] # Update node object in-place. node.atime = time.time() # pylint: disable=protected-access node._sort_key = self._sort_key() return node.obj def __delitem__(self, key): """Delete the item stored under `key` key. If no such key is present in the cache, raise a `CacheKeyError`. """ if not key in self.__dict: raise CacheKeyError(key) else: node = self.__dict[key] self.__heap.remove(node) del self.__dict[key] return node.obj def __iter__(self): """Iterate over the cache, from the least to the most recently accessed item. """ self.__heap.sort() for node in self.__heap: yield node.key def __setattr__(self, name, value): """If the name of the attribute is "size", set the _maximum_ size of the cache to the supplied value. """ object.__setattr__(self, name, value) # Automagically shrink heap on resize. if name == "size": size = value if not isinstance(size, int): raise TypeError("cache size (%r) must be an integer" % size) if size <= 0: raise ValueError("cache size (%d) must be positive" % size) heap = self.__heap dict_ = self.__dict # Do we need to remove anything at all? if len(heap) <= self.size: return # Remove enough nodes to reach the new size. heap.sort() node_count_to_remove = len(heap) - self.size for node in heap[:node_count_to_remove]: del dict_[node.key] del heap[:node_count_to_remove] def __repr__(self): return "<%s (%d elements)>" % (str(self.__class__), len(self.__heap)) def mtime(self, key): """Return the last modification time for the cache record with key. May be useful for cache instances where the stored values can get "stale", such as caching file or network resource contents. """ if not key in self.__dict: raise CacheKeyError(key) else: node = self.__dict[key] return node.mtime if __name__ == "__main__": cache = LRUCache(25) print(cache) for i in range(50): cache[i] = str(i) print(cache) if 46 in cache: del cache[46] print(cache) cache.size = 10 print(cache) cache[46] = "46" print(cache) print(len(cache)) for c in cache: print(c) print(cache) print(cache.mtime(46)) for c in cache: print(c) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7889647 ftputil-5.0.3/ftputil/path.py0000664000175000017500000001762300000000000015213 0ustar00schwaschwa# Copyright (C) 2003-2021, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. """ ftputil.path - simulate `os.path` for FTP servers """ import os import posixpath import stat import ftputil.error import ftputil.tool # The `_Path` class shouldn't be used directly by clients of the ftputil # library. __all__ = [] class _Path: """ Support class resembling `os.path`, accessible from the `FTPHost` object, e. g. as `FTPHost().path.abspath(path)`. Hint: substitute `os` with the `FTPHost` object. """ # `_Path` needs to provide all methods of `os.path`. # pylint: disable=too-many-instance-attributes def __init__(self, host): self._host = host self._encoding = host._encoding # Delegate these methods to the `posixpath` module because they don't # need file system access but work on the path strings (possibly # extracted from `PathLike` objects). # pylint: disable=invalid-name pp = posixpath self.basename = pp.basename self.commonprefix = pp.commonprefix self.dirname = pp.dirname self.isabs = pp.isabs self.join = pp.join self.normcase = pp.normcase self.normpath = pp.normpath self.split = pp.split self.splitdrive = pp.splitdrive self.splitext = pp.splitext def abspath(self, path): """ Return an absolute path. """ # Don't use `raise_for_empty_path` here since Python itself doesn't # raise an exception and just returns the current directory. original_path = path path = ftputil.tool.as_str_path(path, encoding=self._encoding) if not self.isabs(path): path = self.join(self._host.getcwd(), path) return ftputil.tool.same_string_type_as( os.fspath(original_path), self.normpath(path), self._encoding ) def exists(self, path): """ Return true if the path exists. """ if path in ["", b""]: return False try: lstat_result = self._host.lstat(path, _exception_for_missing_path=False) return lstat_result is not None except ftputil.error.RootDirError: return True def getmtime(self, path): """ Return the timestamp for the last modification for `path` as a float. This will raise `PermanentError` if the path doesn't exist, but maybe other exceptions depending on the state of the server (e. g. timeout). """ ftputil.tool.raise_for_empty_path(path) return self._host.stat(path).st_mtime def getsize(self, path): """ Return the size of the `path` item as an integer. This will raise `PermanentError` if the path doesn't exist, but maybe raise other exceptions depending on the state of the server (e. g. timeout). """ ftputil.tool.raise_for_empty_path(path) return self._host.stat(path).st_size # Check whether a path is a regular file/dir/link. For the first two cases # follow links (like in `os.path`). # # Implementation note: The previous implementations simply called `stat` or # `lstat` and returned `False` if they ended with raising a # `PermanentError`. That exception usually used to signal a missing path. # This approach has the problem, however, that exceptions caused by code # earlier in `lstat` are obscured by the exception handling in `isfile`, # `isdir` and `islink`. def _is_file_system_entity(self, path, dir_or_file): """ Return `True` if `path` represents the file system entity described by `dir_or_file` ("dir" or "file"). Return `False` if `path` isn't a directory or file, respectively or if `path` leads to an infinite chain of links. """ assert dir_or_file in ["dir", "file"] # Consider differences between directories and files. if dir_or_file == "dir": should_look_for_dir = True stat_function = stat.S_ISDIR else: should_look_for_dir = False stat_function = stat.S_ISREG # path = ftputil.tool.as_str_path(path, encoding=self._encoding) # Workaround if we can't go up from the current directory. The result # from `getcwd` should already be normalized. if self.normpath(path) == self._host.getcwd(): return should_look_for_dir try: stat_result = self._host.stat(path, _exception_for_missing_path=False) except ftputil.error.RecursiveLinksError: return False except ftputil.error.RootDirError: return should_look_for_dir else: if stat_result is None: # Non-existent path return False else: return stat_function(stat_result.st_mode) def isdir(self, path): """ Return true if the `path` exists and corresponds to a directory (no link). A non-existing path does _not_ cause a `PermanentError`, instead return `False`. """ if path in ["", b""]: return False return self._is_file_system_entity(path, "dir") def isfile(self, path): """ Return true if the `path` exists and corresponds to a regular file (no link). A non-existing path does _not_ cause a `PermanentError`, instead return `False`. """ if path in ["", b""]: return False return self._is_file_system_entity(path, "file") def islink(self, path): """ Return true if the `path` exists and is a link. A non-existing path does _not_ cause a `PermanentError`, instead return `False`. """ path = ftputil.tool.as_str_path(path, encoding=self._encoding) if path == "": return False try: lstat_result = self._host.lstat(path, _exception_for_missing_path=False) except ftputil.error.RootDirError: return False else: if lstat_result is None: # Non-existent path return False else: return stat.S_ISLNK(lstat_result.st_mode) def walk(self, top, func, arg): """ Directory tree walk with callback function. For each directory in the directory tree rooted at top (including top itself, but excluding "." and ".."), call func(arg, dirname, fnames). dirname is the name of the directory, and fnames a list of the names of the files and subdirectories in dirname (excluding "." and ".."). func may modify the fnames list in-place (e.g. via del or slice assignment), and walk will only recurse into the subdirectories whose names remain in fnames; this can be used to implement a filter, or to impose a specific order of visiting. No semantics are defined for, or required of, arg, beyond that arg is always passed to func. It can be used, e.g., to pass a filename pattern, or a mutable object designed to accumulate statistics. Passing None for arg is common. """ ftputil.tool.raise_for_empty_path(top, path_argument_name="top") top = ftputil.tool.as_str_path(top, encoding=self._encoding) # This code (and the above documentation) is taken from `posixpath.py`, # with slight modifications. try: names = self._host.listdir(top) except OSError: return func(arg, top, names) for name in names: name = self.join(top, name) try: stat_result = self._host.lstat(name) except OSError: continue if stat.S_ISDIR(stat_result[stat.ST_MODE]): self.walk(name, func, arg) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7909646 ftputil-5.0.3/ftputil/path_encoding.py0000664000175000017500000000176500000000000017061 0ustar00schwaschwa# Copyright (C) 2021, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. """ Compatibility code for Python versions <= 3.8 vs. >= 3.9 Python 3.9 changed the default encoding for FTP remote paths from latin-1 to UTF-8 which causes a few headaches for ftputil. """ import sys __all__ = ["DEFAULT_ENCODING", "RUNNING_UNDER_PY39_AND_UP", "FTPLIB_DEFAULT_ENCODING"] RUNNING_UNDER_PY39_AND_UP = (sys.version_info.major, sys.version_info.minor) >= (3, 9) # FTP path default encoding for Python 3.8 and below FTPLIB_PY38_ENCODING = "latin-1" # FTP path default encoding for Python 3.9 and above FTPLIB_PY39_ENCODING = "utf-8" # Default encoding for ftputil. Stay compatible to the behavior of former # ftputil versions and Python <= 3.8. DEFAULT_ENCODING = FTPLIB_PY38_ENCODING if RUNNING_UNDER_PY39_AND_UP: FTPLIB_DEFAULT_ENCODING = FTPLIB_PY39_ENCODING else: FTPLIB_DEFAULT_ENCODING = FTPLIB_PY38_ENCODING ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7909646 ftputil-5.0.3/ftputil/session.py0000664000175000017500000001217700000000000015741 0ustar00schwaschwa# Copyright (C) 2014-2021, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. """ Session factory factory (the two "factory" are intentional :-) ) for ftputil. """ import ftplib import ftputil.tool __all__ = ["session_factory"] # In a way, it would be appropriate to call this function # `session_factory_factory`, but that's cumbersome to use. Think of the # function returning a session factory and the shorter name should be fine. def session_factory( base_class=ftplib.FTP, port=21, use_passive_mode=None, *, encrypt_data_channel=True, encoding=None, debug_level=None, ): """ Create and return a session factory according to the keyword arguments. base_class: Base class to use for the session class (e. g. `ftplib.FTP_TLS` or `M2Crypto.ftpslib.FTP_TLS`, the default is `ftplib.FTP`). port: Port number (integer) for the command channel (default 21). If you don't know what "command channel" means, use the default or use what the provider gave you as "the FTP port". use_passive_mode: If `True`, explicitly use passive mode. If `False`, explicitly don't use passive mode. If `None` (default), let the `base_class` decide whether it wants to use active or passive mode. encrypt_data_channel: If `True` (the default), call the `prot_p` method of the base class if it has the method. If `False` or `None` (`None` is the default), don't call the method. encoding: Encoding (str) to use for directory and file paths, or `None`. Unicode (`str`) paths will be encoded with this encoding. Bytes paths are assumed to be in this encoding. The default (equivalent to passing `None`) is to use the default encoding of the `base_class` argument. Note that this encoding has changed from Python 3.8 to 3.9. In Python 3.8 and lower, the default path encoding is "latin-1"; in Python 3.9, the default path encoding is "utf-8". Therefore, if you want an encoding that's independent of the Python version, pass an explicit `encoding`. Using a non-`None` `encoding` is only supported if `base_class` is `ftplib.FTP` or a subclass of it. debug_level: Debug level (integer) to be set on a session instance. The default is `None`, meaning no debugging output. This function should work for the base classes `ftplib.FTP`, `ftplib.FTP_TLS`. Other base classes should work if they use the same API as `ftplib.FTP`. Usage example: my_session_factory = session_factory( base_class=ftplib.FTP_TLS, use_passive_mode=True, encrypt_data_channel=True) with ftputil.FTPHost(host, user, password, session_factory=my_session_factory) as host: ... """ if not isinstance(base_class, type): raise ValueError(f"`base_class` must be a class, but is {base_class!r}") if (encoding is not None) and (not issubclass(base_class, ftplib.FTP)): raise ValueError( f"`encoding` is only supported for `ftplib.FTP` and subclasses, " f"but base class is {base_class!r}" ) class Session(base_class): """ Session factory class created by `session_factory`. """ # In Python 3.8 and below, the `encoding` class attribute was never # documented, but setting it is the only way to set a custom encoding # for remote file system paths. Since we set the encoding on the class # level, all instances created from this class will share this # encoding. That's ok because the user asked for a specific encoding of # the _factory_ when calling `session_factory`. # # Python 3.9 is the first Python version to have a documented way to # set a custom encoding (per instance). # # XXX: The following heuristic doesn't cover the case that we run under # Python 3.8 or earlier _and_ have a base class with an `encoding` # argument. Also, the heuristic will fail if we run under Python 3.9, # but have a base class that overrides the constructor so that it # doesn't support the `encoding` argument anymore. def __init__(self, host, user, password): if ( encoding is not None ) and ftputil.path_encoding.RUNNING_UNDER_PY39_AND_UP: super().__init__(encoding=encoding) else: super().__init__() self.connect(host, port) if debug_level is not None: self.set_debuglevel(debug_level) self.login(user, password) # `set_pasv` can be called with `True` (causing passive mode) or # `False` (causing active mode). if use_passive_mode is not None: self.set_pasv(use_passive_mode) if encrypt_data_channel and hasattr(base_class, "prot_p"): self.prot_p() if (encoding is not None) and not ftputil.path_encoding.RUNNING_UNDER_PY39_AND_UP: Session.encoding = encoding return Session ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7919645 ftputil-5.0.3/ftputil/stat.py0000664000175000017500000010155000000000000015223 0ustar00schwaschwa# Copyright (C) 2002-2020, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. """ ftputil.stat - stat result, parsers, and FTP stat'ing for `ftputil` """ import datetime import math import re import stat import ftputil.error import ftputil.stat_cache # These can be used to write custom parsers. __all__ = ["StatResult", "Parser", "UnixParser", "MSParser"] # Datetime precision values in seconds. MINUTE_PRECISION = 60 DAY_PRECISION = 24 * 60 * 60 UNKNOWN_PRECISION = None class StatResult(tuple): """ Support class resembling a tuple like that returned from `os.(l)stat`. """ _index_mapping = { "st_mode": 0, "st_ino": 1, "st_dev": 2, "st_nlink": 3, "st_uid": 4, "st_gid": 5, "st_size": 6, "st_atime": 7, "st_mtime": 8, "st_ctime": 9, "_st_name": 10, "_st_target": 11, } def __init__(self, sequence): # Don't call `__init__` via `super`. Construction from a sequence is # implicitly handled by `tuple.__new__`, not `tuple.__init__`. # pylint: disable=super-init-not-called # # Use `sequence` parameter to remain compatible to `__new__` interface. # pylint: disable=unused-argument # # These may be overwritten in a `Parser.parse_line` method. self._st_name = "" self._st_target = None self._st_mtime_precision = UNKNOWN_PRECISION def __getattr__(self, attr_name): if attr_name in self._index_mapping: return self[self._index_mapping[attr_name]] else: raise AttributeError( "'StatResult' object has no attribute '{}'".format(attr_name) ) def __repr__(self): # "Invert" `_index_mapping` so that we can look up the names for the # tuple indices. index_to_name = dict((v, k) for k, v in self._index_mapping.items()) argument_strings = [] for index, item in enumerate(self): argument_strings.append("{}={!r}".format(index_to_name[index], item)) return "{}({})".format(type(self).__name__, ", ".join(argument_strings)) # # FTP directory parsers # class Parser: """ Represent a parser for directory lines. Parsers for specific directory formats inherit from this class. """ # Map month abbreviations to month numbers. _month_numbers = { "jan": 1, "feb": 2, "mar": 3, "apr": 4, "may": 5, "jun": 6, "jul": 7, "aug": 8, "sep": 9, "oct": 10, "nov": 11, "dec": 12, } _total_regex = re.compile(r"^total\s+\d+") def ignores_line(self, line): """ Return a true value if the line should be ignored, i. e. is assumed to _not_ contain actual directory/file/link data. A typical example are summary lines like "total 23" which are emitted by some FTP servers. If the line should be used to extract stat data from it, return a false value. """ # Ignore empty lines stemming from only a line break. if not line.strip(): # Yes, ignore the line if it's empty. return True # Either a `_SRE_Match` instance or `None` match = self._total_regex.search(line) return bool(match) def parse_line(self, line, time_shift=0.0): """ Return a `StatResult` object as derived from the string `line`. The parser code to use depends on the directory format the FTP server delivers (also see examples at end of file). If the given text line can't be parsed, raise a `ParserError`. For the definition of `time_shift` see the docstring of `FTPHost.set_time_shift` in `ftputil.py`. Not all parsers use the `time_shift` parameter. """ raise NotImplementedError("must be defined by subclass") # # Helper methods for parts of a directory listing line # def parse_unix_mode(self, mode_string): """ Return an integer from the `mode_string`, compatible with the `st_mode` value in stat results. Such a mode string may look like "drwxr-xr-x". If the mode string can't be parsed, raise an `ftputil.error.ParserError`. """ # Allow derived classes to make use of `self`. # pylint: disable=no-self-use if len(mode_string) != 10: raise ftputil.error.ParserError( "invalid mode string '{}'".format(mode_string) ) st_mode = 0 # TODO: Add support for "S" and sticky bit ("t", "T"). for bit in mode_string[1:10]: bit = bit != "-" st_mode = (st_mode << 1) + bit if mode_string[3] == "s": st_mode = st_mode | stat.S_ISUID if mode_string[6] == "s": st_mode = st_mode | stat.S_ISGID file_type_to_mode = { "b": stat.S_IFBLK, "c": stat.S_IFCHR, "d": stat.S_IFDIR, "l": stat.S_IFLNK, "p": stat.S_IFIFO, "s": stat.S_IFSOCK, "-": stat.S_IFREG, # Ignore types which `ls` can't make sense of (assuming the FTP # server returns listings like `ls` does). "?": 0, } file_type = mode_string[0] if file_type in file_type_to_mode: st_mode = st_mode | file_type_to_mode[file_type] else: raise ftputil.error.ParserError( "unknown file type character '{}'".format(file_type) ) return st_mode # pylint: disable=no-self-use def _as_int(self, int_string, int_description): """ Return `int_string` converted to an integer. If it can't be converted, raise a `ParserError`, using `int_description` in the error message. For example, if the integer value is a day, pass "day" for `int_description`. """ try: return int(int_string) except ValueError: raise ftputil.error.ParserError( "non-integer {} value {!r}".format(int_description, int_string) ) @staticmethod def _datetime(year, month, day, hour, minute, second): """ Return UTC `datetime.datetime` object for the given year, month, day, hour, minute and second. If there are invalid values, for example minute > 59, raise a `ParserError`. """ try: return datetime.datetime( year, month, day, hour, minute, second, tzinfo=datetime.timezone.utc ) except ValueError: invalid_datetime = ( f"{year:04d}-{month:02d}-{day:02d} " f"{hour:02d}:{minute:02d}:{second:02d}" ) raise ftputil.error.ParserError( "invalid datetime {0!r}".format(invalid_datetime) ) def parse_unix_time( self, month_abbreviation, day, year_or_time, time_shift, with_precision=False ): """ Return a floating point number, like from `time.mktime`, by parsing the string arguments `month_abbreviation`, `day` and `year_or_time`. The parameter `time_shift` is the difference "time on server" - "time on client" and is available as the `time_shift` parameter in the `parse_line` interface. If `with_precision` is true return a two-element tuple consisting of the floating point number as described in the previous paragraph and the precision of the time in seconds. The default is `False` for backward compatibility with custom parsers. The precision value takes into account that, for example, a time string like "May 26 2005" has only a precision of one day. This information is important for the `upload_if_newer` and `download_if_newer` methods in the `FTPHost` class. Times in Unix-style directory listings typically have one of these formats: - "Nov 23 02:33" (month name, day of month, time) - "May 26 2005" (month name, day of month, year) If this method can't make sense of the given arguments, it raises an `ftputil.error.ParserError`. """ try: month = self._month_numbers[month_abbreviation.lower()] except KeyError: raise ftputil.error.ParserError( "invalid month abbreviation {0!r}".format(month_abbreviation) ) day = self._as_int(day, "day") year_is_known = ":" not in year_or_time if year_is_known: # `year_or_time` is really a year. st_mtime_precision = DAY_PRECISION server_year, hour, minute = self._as_int(year_or_time, "year"), 0, 0 else: # `year_or_time` is a time hh:mm. st_mtime_precision = MINUTE_PRECISION hour, minute = year_or_time.split(":") year, hour, minute = ( None, self._as_int(hour, "hour"), self._as_int(minute, "minute"), ) # First assume the year of the directory/file is the current year. server_now = datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc ) + datetime.timedelta(seconds=time_shift) server_year = server_now.year # If the server datetime derived from this year seems to be in the # future, subtract one year. # # Things to consider: # # Since the server time will be rounded down to full minutes, apply # the same truncation to the presumed current server time. # # Due to possible small errors in the time setting of the server # (not only because of the parsing), there will always be a small # time window in which we can't be sure whether the parsed time is # in the future or not. Make a mistake of one second and the time # is off one year. # # To resolve this conflict, if in doubt assume that a directory or # file has just been created, not that it is by chance to the # minute one year old. # # Hence, add a small time difference that must be exceeded in order # to assume the time is in the future. This time difference is # arbitrary, but we have to assume _some_ value. if self._datetime( server_year, month, day, hour, minute, 0 ) > server_now.replace(second=0) + datetime.timedelta(seconds=120): server_year -= 1 # The time shift is the time difference the server is ahead. So to get # back to client time (UTC), subtract the time shift. The calculation # is supposed to be the same for negative time shifts; in this case we # subtract a negative time shift, i. e. add the absolute value of the # time shift to the server date time. server_utc_datetime = self._datetime( server_year, month, day, hour, minute, 0 ) - datetime.timedelta(seconds=time_shift) st_mtime = server_utc_datetime.timestamp() # If we had a datetime before the epoch, the resulting value 0.0 # doesn't tell us anything about the precision. if st_mtime < 0.0: st_mtime_precision = UNKNOWN_PRECISION st_mtime = 0.0 # if with_precision: return st_mtime, st_mtime_precision else: return st_mtime def parse_ms_time(self, date, time_, time_shift, with_precision=False): """ Return a floating point number, like from `time.mktime`, by parsing the string arguments `date` and `time_`. The parameter `time_shift` is the difference "time on server" - "time on client" and can be set as the `time_shift` parameter in the `parse_line` interface. If `with_precision` is true return a two-element tuple consisting of the floating point number as described in the previous paragraph and the precision of the time in seconds. The default is `False` for backward compatibility with custom parsers. The precision value takes into account that, for example, a time string like "10-23-2001 03:25PM" has only a precision of one minute. This information is important for the `upload_if_newer` and `download_if_newer` methods in the `FTPHost` class. Usually, the returned precision is `MINUTE_PRECISION`, except when the date is before the epoch, in which case the returned `st_mtime` value is set to 0.0 and the precision to `UNKNOWN_PRECISION`. Times in MS-style directory listings typically have the format "10-23-01 03:25PM" (month-day_of_month-two_digit_year, hour:minute, am/pm). If this method can't make sense of the given arguments, it raises an `ftputil.error.ParserError`. """ # Derived classes might want to use `self`. # pylint: disable=no-self-use # # Derived classes may need access to `time_shift`. # pylint: disable=unused-argument # # For the time being, I don't add a `with_precision` parameter as in # the MS parser because the precision for the DOS format is always a # minute and can be set in `MSParser.parse_line`. Should you find # yourself needing support for `with_precision` for a derived class, # please send a mail (see ftputil.txt/html). month, day, year = [ self._as_int(part, "year/month/day") for part in date.split("-") ] if year >= 1000: # We have a four-digit year, so no need for heuristics. pass elif year >= 70: year = 1900 + year else: year = 2000 + year try: hour, minute, am_pm = time_[0:2], time_[3:5], time_[5] except IndexError: raise ftputil.error.ParserError("invalid time string '{}'".format(time_)) hour, minute = (self._as_int(hour, "hour"), self._as_int(minute, "minute")) if hour == 12 and am_pm == "A": hour = 0 if hour != 12 and am_pm == "P": hour += 12 server_datetime = self._datetime(year, month, day, hour, minute, 0) client_datetime = server_datetime - datetime.timedelta(seconds=time_shift) st_mtime = client_datetime.timestamp() if st_mtime < 0.0: st_mtime_precision = UNKNOWN_PRECISION st_mtime = 0.0 else: st_mtime_precision = MINUTE_PRECISION if with_precision: return st_mtime, st_mtime_precision else: return st_mtime class UnixParser(Parser): """ `Parser` class for Unix-specific directory format. """ @staticmethod def _split_line(line): """ Split a line in metadata, nlink, user, group, size, month, day, year_or_time and name and return the result as an nine-element list of these values. If the name is a link, it will be encoded as a string "link_name -> link_target". """ # This method encapsulates the recognition of an unusual Unix format # variant (see ticket http://ftputil.sschwarzer.net/trac/ticket/12 ). line_parts = line.split() FIELD_COUNT_WITHOUT_USERID = 8 FIELD_COUNT_WITH_USERID = FIELD_COUNT_WITHOUT_USERID + 1 if len(line_parts) < FIELD_COUNT_WITHOUT_USERID: # No known Unix-style format raise ftputil.error.ParserError("line '{}' can't be parsed".format(line)) # If we have a valid format (either with or without user id field), the # field with index 5 is either the month abbreviation or a day. try: int(line_parts[5]) except ValueError: # Month abbreviation, "invalid literal for int" line_parts = line.split(None, FIELD_COUNT_WITH_USERID - 1) else: # Day line_parts = line.split(None, FIELD_COUNT_WITHOUT_USERID - 1) USER_FIELD_INDEX = 2 line_parts.insert(USER_FIELD_INDEX, None) return line_parts def parse_line(self, line, time_shift=0.0): """ Return a `StatResult` instance corresponding to the given text line. The `time_shift` value is needed to determine to which year a datetime without an explicit year belongs. If the line can't be parsed, raise a `ParserError`. """ # The local variables are rather simple. # pylint: disable=too-many-locals try: ( mode_string, nlink, user, group, size, month, day, year_or_time, name, ) = self._split_line(line) # We can get a `ValueError` here if the name is blank (see ticket #69). # This is a strange use case, but at least we should raise the # exception the docstring mentions. except ValueError as exc: raise ftputil.error.ParserError(str(exc)) # st_mode st_mode = self.parse_unix_mode(mode_string) # st_ino, st_dev, st_nlink, st_uid, st_gid, st_size, st_atime st_ino = None st_dev = None st_nlink = int(nlink) st_uid = user st_gid = group st_size = int(size) st_atime = None # st_mtime st_mtime, st_mtime_precision = self.parse_unix_time( month, day, year_or_time, time_shift, with_precision=True ) # st_ctime st_ctime = None # st_name if name.count(" -> ") > 1: # If we have more than one arrow we can't tell where the link name # ends and the target name starts. raise ftputil.error.ParserError( '''name '{}' contains more than one "->"'''.format(name) ) elif name.count(" -> ") == 1: st_name, st_target = name.split(" -> ") else: st_name, st_target = name, None stat_result = StatResult( ( st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size, st_atime, st_mtime, st_ctime, ) ) # These attributes are kind of "half-official". I'm not sure whether # they should be used by ftputil client code. # pylint: disable=protected-access stat_result._st_mtime_precision = st_mtime_precision stat_result._st_name = st_name stat_result._st_target = st_target return stat_result class MSParser(Parser): """ `Parser` class for MS-specific directory format. """ def parse_line(self, line, time_shift=0.0): """ Return a `StatResult` instance corresponding to the given text line from a FTP server which emits "Microsoft format" (see end of file). If the line can't be parsed, raise a `ParserError`. The parameter `time_shift` isn't used in this method but is listed for compatibility with the base class. """ # The local variables are rather simple. # pylint: disable=too-many-locals try: date, time_, dir_or_size, name = line.split(None, 3) except ValueError: # "unpack list of wrong size" raise ftputil.error.ParserError("line '{}' can't be parsed".format(line)) # st_mode # Default to read access only; in fact, we can't tell. st_mode = 0o400 if dir_or_size == "": st_mode = st_mode | stat.S_IFDIR else: st_mode = st_mode | stat.S_IFREG # st_ino, st_dev, st_nlink, st_uid, st_gid st_ino = None st_dev = None st_nlink = None st_uid = None st_gid = None # st_size if dir_or_size != "": try: st_size = int(dir_or_size) except ValueError: raise ftputil.error.ParserError("invalid size {}".format(dir_or_size)) else: st_size = None # st_atime st_atime = None # st_mtime st_mtime, st_mtime_precision = self.parse_ms_time( date, time_, time_shift, with_precision=True ) # st_ctime st_ctime = None stat_result = StatResult( ( st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size, st_atime, st_mtime, st_ctime, ) ) # These attributes are kind of "half-official". I'm not sure whether # they should be used by ftputil client code. # pylint: disable=protected-access # _st_name and _st_target stat_result._st_name = name stat_result._st_target = None # mtime precision in seconds stat_result._st_mtime_precision = st_mtime_precision return stat_result # # Stat'ing operations for files on an FTP server # class _Stat: """ Methods for stat'ing directories, links and regular files. """ # pylint: disable=protected-access def __init__(self, host): self._host = host self._path = host.path # Use the Unix directory parser by default. self._parser = UnixParser() # Allow one chance to switch to another parser if the default doesn't # work. self._allow_parser_switching = True # Cache only lstat results. `stat` works locally on `lstat` results. self._lstat_cache = ftputil.stat_cache.StatCache() def _host_dir(self, path): """ Return a list of lines, as fetched by FTP's `LIST` command, when applied to `path`. """ return self._host._dir(path) def _stat_results_from_dir(self, path): """ Yield stat results extracted from the directory listing `path`. Omit the special entries for the directory itself and its parent directory. """ lines = self._host_dir(path) # `cache` is the "high-level" `StatCache` object whereas `cache._cache` # is the "low-level" `LRUCache` object. cache = self._lstat_cache # Auto-grow cache if the cache up to now can't hold as many entries as # there are in the directory `path`. if cache._enabled and len(lines) >= cache._cache.size: new_size = int(math.ceil(1.1 * len(lines))) cache.resize(new_size) # Yield stat results from lines. for line in lines: if self._parser.ignores_line(line): continue # Although for a `listdir` call we're only interested in the names, # use the `time_shift` parameter to store the correct timestamp # values in the cache. stat_result = self._parser.parse_line(line, self._host.time_shift()) # Skip entries "." and "..". if stat_result._st_name in [self._host.curdir, self._host.pardir]: continue loop_path = self._path.join(path, stat_result._st_name) # No-op if cache is disabled. cache[loop_path] = stat_result yield stat_result # The methods `listdir`, `lstat` and `stat` come in two variants. The # methods `_real_listdir`, `_real_lstat` and `_real_stat` use the currently # set parser to get the directories/files of the requested directory, the # lstat result or the stat result, respectively. # # Additionally, we have the methods `_listdir`, `_lstat` and `_stat`, which # wrap the above `_real_*` methods. _For example_, `_listdir` calls # `_real_listdir`. If `_real_listdir` can't parse the directory lines and a # parser hasn't been fixed yet, `_listdir` switches to the MS parser and # calls `_real_listdir` again. # # There are two important conditions to watch out for: # # - If the user explicitly set a different parser with # `FTPHost.set_parser`, parser switching is disabled after that and # `_listdir` etc. only call "their" method once with the fixed parser. # # - A `_real_*` call will fail if there's no directory line at all in the # given directory. In that case, we can't tell whether the default parser # was appropriate or not. Hence parser switching will still be allowed # until we encounter a directory that has directories/files/links in it. def _real_listdir(self, path): """ Return a list of directories, files etc. in the directory named `path`. Like `os.listdir` the returned list elements have the type of the path argument. If the directory listing from the server can't be parsed, raise a `ParserError`. """ # We _can't_ put this check into `FTPHost._dir`; see its docstring. path = self._path.abspath(path) # `listdir` should only be allowed for directories and links to them. if not self._path.isdir(path): raise ftputil.error.PermanentError( "550 {}: no such directory or wrong directory parser used".format(path) ) # Set up for `for` loop. names = [] for stat_result in self._stat_results_from_dir(path): st_name = stat_result._st_name names.append(st_name) return names def _real_lstat(self, path, _exception_for_missing_path=True): """ Return an object similar to that returned by `os.lstat`. If the directory listing from the server can't be parsed, raise a `ParserError`. If the directory can be parsed and the `path` is not found, raise a `PermanentError`. That means that if the directory containing `path` can't be parsed we get a `ParserError`, independent on the presence of `path` on the server. (`_exception_for_missing_path` is an implementation aid and _not_ intended for use by ftputil clients.) """ path = self._path.abspath(path) # If the path is in the cache, return the lstat result. if path in self._lstat_cache: return self._lstat_cache[path] # Note: (l)stat works by going one directory up and parsing the output # of an FTP `LIST` command. Unfortunately, it is not possible to do # this for the root directory `/`. if path == "/": raise ftputil.error.RootDirError("can't stat remote root directory") dirname, basename = self._path.split(path) # If even the directory doesn't exist and we don't want the exception, # treat it the same as if the path wasn't found in the directory's # contents (compare below). The use of `isdir` here causes a recursion # but that should be ok because that will at the latest stop when we've # gotten to the root directory. if not self._path.isdir(dirname) and not _exception_for_missing_path: return None # Loop through all lines of the directory listing. We probably won't # need all lines for the particular path but we want to collect as many # stat results in the cache as possible. lstat_result_for_path = None # FIXME: Here we try to list the contents of `dirname` even though the # above `isdir` call might/could have shown that the directory doesn't # exist. This may be related to ticket #108. That said, we may need to # consider virtual directories here (see tickets #86 / #87). for stat_result in self._stat_results_from_dir(dirname): # Needed to work without cache or with disabled cache. if stat_result._st_name == basename: lstat_result_for_path = stat_result if lstat_result_for_path is not None: return lstat_result_for_path # Path was not found during the loop. if _exception_for_missing_path: # TODO: Use FTP `LIST` command on the file to implicitly use the # usual status code of the server for missing files (450 vs. 550). raise ftputil.error.PermanentError( "550 {}: no such file or directory".format(path) ) else: # Be explicit. Returning `None` is a signal for # `_Path.exists/isfile/isdir/islink` that the path was not found. # If we would raise an exception, there would be no distinction # between a missing path or a more severe error in the code above. return None def _real_stat(self, path, _exception_for_missing_path=True): """ Return info from a "stat" call on `path`. If the directory containing `path` can't be parsed, raise a `ParserError`. If the listing can be parsed but the `path` can't be found, raise a `PermanentError`. Also raise a `PermanentError` if there's an endless (cyclic) chain of symbolic links "behind" the `path`. (`_exception_for_missing_path` is an implementation aid and _not_ intended for use by ftputil clients.) """ # Save for error message. original_path = path # Most code in this method is used to detect recursive link structures. visited_paths = set() while True: # Stat the link if it is one, else the file/directory. lstat_result = self._real_lstat(path, _exception_for_missing_path) if lstat_result is None: return None # If the file is not a link, the `stat` result is the same as the # `lstat` result. if not stat.S_ISLNK(lstat_result.st_mode): return lstat_result # If we stat'ed a link, calculate a normalized path for the file # the link points to. dirname, _ = self._path.split(path) path = self._path.join(dirname, lstat_result._st_target) path = self._path.abspath(self._path.normpath(path)) # Check for cyclic structure. if path in visited_paths: # We had seen this path already. raise ftputil.error.RecursiveLinksError( "recursive link structure detected for remote path '{}'".format( original_path ) ) # Remember the path we have encountered. visited_paths.add(path) def __call_with_parser_retry(self, method, *args, **kwargs): """ Call `method` with the `args` and `kwargs` once. If that results in a `ParserError` and only one parser has been used yet, try the other parser. If that still fails, propagate the `ParserError`. """ # Do _not_ set `_allow_parser_switching` in a `finally` clause! This # would cause a `PermanentError` due to a not-found file in an empty # directory to finally establish the parser - which is wrong. try: result = method(*args, **kwargs) # If a `listdir` call didn't find anything, we can't say anything # about the usefulness of the parser. if (method is not self._real_listdir) and result: self._allow_parser_switching = False return result except ftputil.error.ParserError: if self._allow_parser_switching: self._allow_parser_switching = False self._parser = MSParser() return method(*args, **kwargs) else: raise # Client code should never use these methods, but only the corresponding # methods without the leading underscore in the `FTPHost` class. def _listdir(self, path): """ Return a list of items in `path`. Raise a `PermanentError` if the path doesn't exist, but maybe raise other exceptions depending on the state of the server (e. g. timeout). """ return self.__call_with_parser_retry(self._real_listdir, path) def _lstat(self, path, _exception_for_missing_path=True): """ Return a `StatResult` without following links. Raise a `PermanentError` if the path doesn't exist, but maybe raise other exceptions depending on the state of the server (e. g. timeout). """ return self.__call_with_parser_retry( self._real_lstat, path, _exception_for_missing_path ) def _stat(self, path, _exception_for_missing_path=True): """ Return a `StatResult` with following links. Raise a `PermanentError` if the path doesn't exist, but maybe raise other exceptions depending on the state of the server (e. g. timeout). """ return self.__call_with_parser_retry( self._real_stat, path, _exception_for_missing_path ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7919645 ftputil-5.0.3/ftputil/stat_cache.py0000664000175000017500000001302700000000000016347 0ustar00schwaschwa# Copyright (C) 2006-2018, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. """ ftp_stat_cache.py - cache for (l)stat data """ import time import ftputil.error import ftputil.lrucache # This module shouldn't be used by clients of the ftputil library. __all__ = [] class StatCache: """ Implement an LRU (least-recently-used) cache. `StatCache` objects have an attribute `max_age`. After this duration after _setting_ it a cache entry will expire. For example, if you code my_cache = StatCache() my_cache.max_age = 10 my_cache["/home"] = ... the value my_cache["/home"] can be retrieved for 10 seconds. After that, the entry will be treated as if it had never been in the cache and should be fetched again from the remote host. Note that the `__len__` method does no age tests and thus may include some or many already expired entries. """ # Default number of cache entries _DEFAULT_CACHE_SIZE = 5000 def __init__(self): # Can be reset with method `resize` self._cache = ftputil.lrucache.LRUCache(self._DEFAULT_CACHE_SIZE) # Never expire self.max_age = None self.enable() def enable(self): """ Enable storage of stat results. """ self._enabled = True def disable(self): """ Disable the cache. Further storage attempts with `__setitem__` won't have any visible effect. Disabling the cache only effects new storage attempts. Values stored before calling `disable` can still be retrieved unless disturbed by a `resize` command or normal cache expiration. """ # `_enabled` is set via calling `enable` in the constructor. # pylint: disable=attribute-defined-outside-init self._enabled = False def resize(self, new_size): """ Set number of cache entries to the integer `new_size`. If the new size is smaller than the current cache size, relatively long-unused elements will be removed. """ self._cache.size = new_size def _age(self, path): """ Return the age of a cache entry for `path` in seconds. If the path isn't in the cache, raise a `CacheMissError`. """ try: return time.time() - self._cache.mtime(path) except ftputil.lrucache.CacheKeyError: raise ftputil.error.CacheMissError( "no entry for path {} in cache".format(path) ) def clear(self): """ Clear (invalidate) all cache entries. """ self._cache.clear() def invalidate(self, path): """ Invalidate the cache entry for the absolute `path` if present. After that, the stat result data for `path` can no longer be retrieved, as if it had never been stored. If no stat result for `path` is in the cache, do _not_ raise an exception. """ # XXX: To be 100 % sure, this should be `host.sep`, but I don't want to # introduce a reference to the `FTPHost` object for only that purpose. assert path.startswith("/"), "{} must be an absolute path".format(path) try: del self._cache[path] except ftputil.lrucache.CacheKeyError: # Ignore errors pass def __getitem__(self, path): """ Return the stat entry for the `path`. If there's no stored stat entry or the cache is disabled, raise `CacheMissError`. """ if not self._enabled: raise ftputil.error.CacheMissError("cache is disabled") # Possibly raise a `CacheMissError` in `_age` if (self.max_age is not None) and (self._age(path) > self.max_age): self.invalidate(path) raise ftputil.error.CacheMissError( "entry for path {} has expired".format(path) ) else: # XXX: I don't know if this may raise a `CacheMissError` in case of # race conditions. I prefer robust code. try: return self._cache[path] except ftputil.lrucache.CacheKeyError: raise ftputil.error.CacheMissError( "entry for path {} not found".format(path) ) def __setitem__(self, path, stat_result): """ Put the stat data for the absolute `path` into the cache, unless it's disabled. """ assert path.startswith("/") if not self._enabled: return self._cache[path] = stat_result def __contains__(self, path): """ Support for the `in` operator. Return a true value, if data for `path` is in the cache, else return a false value. """ try: # Implicitly do an age test which may raise `CacheMissError`. self[path] except ftputil.error.CacheMissError: return False else: return True # # The following methods are only intended for debugging! # def __len__(self): """ Return the number of entries in the cache. Note that this may include some (or many) expired entries. """ return len(self._cache) def __str__(self): """ Return a string representation of the cache contents. """ lines = [] for key in sorted(self._cache): lines.append("{}: {}".format(key, self[key])) return "\n".join(lines) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7919645 ftputil-5.0.3/ftputil/sync.py0000664000175000017500000001335400000000000015230 0ustar00schwaschwa# Copyright (C) 2007-2018, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. """ Tools for syncing combinations of local and remote directories. *** WARNING: This is an unfinished in-development version! """ # Sync combinations: # - remote -> local (download) # - local -> remote (upload) # - remote -> remote # - local -> local (maybe implicitly possible due to design, but not targeted) import os import shutil from ftputil import FTPHost import ftputil.error __all__ = ["FTPHost", "LocalHost", "Syncer"] # Used for copying file objects; value is 64 KB. CHUNK_SIZE = 64 * 1024 class LocalHost: """ Provide an API for local directories and files so we can use the same code as for `FTPHost` instances. """ def open(self, path, mode): """ Return a Python file object for file name `path`, opened in mode `mode`. """ # This is the built-in `open` function, not `os.open`! return open(path, mode) def time_shift(self): """ Return the time shift value (see methods `set_time_shift` and `time_shift` in class `FTPHost` for a definition). By definition, the value is zero for local file systems. """ return 0.0 def __getattr__(self, attr): return getattr(os, attr) class Syncer: """ Control synchronization between combinations of local and remote directories and files. """ def __init__(self, source, target): """ Init the `FTPSyncer` instance. Each of `source` and `target` is either an `FTPHost` or a `LocalHost` object. The source and target directories, resp. have to be set with the `chdir` command before passing them in. The semantics is so that the items under the source directory will show up under the target directory after the synchronization (unless there's an error). """ self._source = source self._target = target def _mkdir(self, target_dir): """ Try to create the target directory `target_dir`. If it already exists, don't do anything. If the directory is present but it's actually a file, raise a `SyncError`. """ # TODO: Handle setting of target mtime according to source mtime # (beware of rootdir anomalies; try to handle them as well). if self._target.path.isfile(target_dir): raise ftputil.error.SyncError( "target dir '{}' is actually a file".format(target_dir) ) # Deliberately use an `isdir` test instead of `try/except`. The latter # approach might mask other errors we want to see, e. g. insufficient # permissions. if not self._target.path.isdir(target_dir): self._target.mkdir(target_dir) def _sync_file(self, source_file, target_file): # XXX: This duplicates code from `FTPHost._copyfileobj`. Maybe # implement the upload and download methods in terms of `_sync_file`, # or maybe not? # TODO: Handle `IOError`s # TODO: Handle conditional copy # TODO: Handle setting of target mtime according to source mtime # (beware of rootdir anomalies; try to handle them as well). source = self._source.open(source_file, "rb") try: target = self._target.open(target_file, "wb") try: shutil.copyfileobj(source, target, length=CHUNK_SIZE) finally: target.close() finally: source.close() def _fix_sep_for_target(self, path): """ Return the string `path` with appropriate path separators for the target file system. """ return path.replace(self._source.sep, self._target.sep) def _sync_tree(self, source_dir, target_dir): """ Synchronize the source and the target directory tree by updating the target to match the source as far as possible. Current limitations: - _don't_ delete items which are on the target path but not on the source path - files are always copied, the modification timestamps are not compared - all files are copied in binary mode, never in ASCII/text mode - incomplete error handling """ self._mkdir(target_dir) for dirpath, dir_names, file_names in self._source.walk(source_dir): for dir_name in dir_names: inner_source_dir = self._source.path.join(dirpath, dir_name) inner_target_dir = inner_source_dir.replace(source_dir, target_dir, 1) inner_target_dir = self._fix_sep_for_target(inner_target_dir) self._mkdir(inner_target_dir) for file_name in file_names: source_file = self._source.path.join(dirpath, file_name) target_file = source_file.replace(source_dir, target_dir, 1) target_file = self._fix_sep_for_target(target_file) self._sync_file(source_file, target_file) def sync(self, source_path, target_path): """ Synchronize `source_path` and `target_path` (both are strings, each denoting a directory or file path), i. e. update the target path so that it's a copy of the source path. This method handles both directory trees and single files. """ # TODO: Handle making of missing intermediate directories. source_path = self._source.path.abspath(source_path) target_path = self._target.path.abspath(target_path) if self._source.path.isfile(source_path): self._sync_file(source_path, target_path) else: self._sync_tree(source_path, target_path) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7919645 ftputil-5.0.3/ftputil/tool.py0000664000175000017500000000470400000000000015230 0ustar00schwaschwa# Copyright (C) 2013-2021, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. """ tool.py - helper code """ import os __all__ = ["same_string_type_as", "as_str", "as_str_path", "raise_for_empty_path"] def same_string_type_as(type_source, string, encoding): """ Return a string of the same type as `type_source` with the content from `string`. If the `type_source` and `string` don't have the same type, use `encoding` to encode or decode, whatever operation is needed. """ if isinstance(type_source, bytes) and isinstance(string, str): return string.encode(encoding) elif isinstance(type_source, str) and isinstance(string, bytes): return string.decode(encoding) else: return string def as_str(string, encoding): """ Return the argument `string` converted to a unicode string if it's a `bytes` object. Otherwise just return the string. If a conversion is necessary, use `encoding`. If `string` is neither `str` nor `bytes`, raise a `TypeError`. """ if isinstance(string, bytes): return string.decode(encoding) elif isinstance(string, str): return string else: raise TypeError("`as_str` argument must be `bytes` or `str`") def as_str_path(path, encoding): """ Return the argument `path` converted to a unicode string if it's a `bytes` object. Otherwise just return the string. If a conversion is necessary, use `encoding`. Instead of passing a `bytes` or `str` object for `path`, you can pass a `PathLike` object that can be converted to a `bytes` or `str` object. If the `path` can't be converted to a `bytes` or `str`, a `TypeError` is raised. """ path = os.fspath(path) return as_str(path, encoding) def raise_for_empty_path(path, path_argument_name="path"): """ Raise an exception of class `exception_class` if `path` is an empty string (text or bytes). """ # Avoid cyclic import. import ftputil.error # Don't handle `pathlib.Path("")`. This immediately results in `Path(".")`, # so we can't detect it anyway. Regarding bytes, `Path(b"")` results in a # `TypeError`. if path in ["", b""]: if path_argument_name is None: message = "path argument is empty" else: message = f"path argument `{path_argument_name}` is empty" raise ftputil.error.FTPIOError(message) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1643211003.3438568 ftputil-5.0.3/ftputil/version.py0000664000175000017500000000105700000000000015736 0ustar00schwaschwa# Copyright (C) 2006-2018, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. """ Provide version information about ftputil and the runtime environment. """ import sys # ftputil version number; substituted by `make patch` __version__ = "5.0.3" _ftputil_version = __version__ _python_version = sys.version.split()[0] _python_platform = sys.platform version_info = "ftputil {}, Python {} ({})".format( _ftputil_version, _python_version, _python_platform ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7939646 ftputil-5.0.3/setup.py0000775000175000017500000000533200000000000013725 0ustar00schwaschwa#! /usr/bin/env python # Copyright (C) 2003-2020, Stefan Schwarzer # See the file LICENSE for licensing terms. """ setup.py - installation script for Python distutils """ import os import sys from distutils import core _name = "ftputil" _package = "ftputil" _version = open("VERSION").read().strip() doc_files = [ os.path.join("doc", name) for name in [ "ftputil.txt", "ftputil.html", "whats_new_in_ftputil_3.0.txt", "whats_new_in_ftputil_3.0.html", "README.txt", ] ] doc_files_are_present = all((os.path.exists(doc_file) for doc_file in doc_files)) if "install" in sys.argv[1:] and not doc_files_are_present: print("One or more of the HTML documentation files are missing.") print("Please generate them with `make docs`.") sys.exit(1) core.setup( # Installation data name=_name, version=_version, packages=[_package], package_dir={_package: _package}, data_files=[("doc/ftputil", doc_files)], # Metadata author="Stefan Schwarzer", author_email="sschwarzer@sschwarzer.net", url="https://ftputil.sschwarzer.net/", description="High-level FTP client library (virtual file system and more)", keywords="FTP, client, library, virtual file system", license="Open source (revised BSD license)", platforms=["Pure Python"], # See https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires python_requires=">=3.6", long_description="""\ ftputil is a high-level FTP client library for the Python programming language. ftputil implements a virtual file system for accessing FTP servers, that is, it can generate file-like objects for remote files. The library supports many functions similar to those in the os, os.path and shutil modules. ftputil has convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones.""", download_url="http://ftputil.sschwarzer.net/trac/attachment/wiki/Download/" "{}-{}.tar.gz?format=raw".format(_name, _version), classifiers=[ # Commented-out for beta release "Development Status :: 5 - Production/Stable", #"Development Status :: 4 - Beta", "Environment :: Other Environment", "Intended Audience :: Developers", "Intended Audience :: System Administrators", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Topic :: Internet :: File Transfer Protocol (FTP)", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Filesystems", ], ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1643211004.7548294 ftputil-5.0.3/test/0000775000175000017500000000000000000000000013164 5ustar00schwaschwa././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7939646 ftputil-5.0.3/test/__init__.py0000664000175000017500000000000000000000000015263 0ustar00schwaschwa././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7939646 ftputil-5.0.3/test/scripted_session.py0000664000175000017500000002255300000000000017125 0ustar00schwaschwa# Copyright (C) 2018-2021, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. import sys import unittest.mock import ftputil.path_encoding __all__ = ["Call", "factory"] class Call: def __init__(self, method_name, *, args=None, kwargs=None, result=None): self.method_name = method_name self.result = result self.args = args self.kwargs = kwargs def __repr__(self): return ( "{0.__class__.__name__}(" "method_name={0.method_name!r}, " "result={0.result!r}, " "args={0.args!r}, " "kwargs={0.kwargs!r})".format(self) ) def check_call(self, method_name, args=None, kwargs=None): # TODO: Mention printing in the docstring. # TODO: Describe how the comparison is made. """ Check the method name, args and kwargs from this `Call` object against the method name, args and kwargs from the system under test. Raise an `AssertionError` if there's a mismatch. """ print( " Call from session script: {} | {!r} | {!r}".format( self.method_name, self.args, self.kwargs ) ) print( " Call from system under test: {} | {!r} | {!r}".format( method_name, args, kwargs ) ) def compare(value_name, script_value, sut_value): if script_value is not None: try: assert script_value == sut_value except AssertionError: print( " Mismatch for `{}`: {!r} != {!r}".format( value_name, script_value, sut_value ) ) raise compare("method_name", self.method_name, method_name) compare("args", self.args, args) compare("kwargs", self.kwargs, kwargs) @staticmethod def _is_exception_class(obj): """ Return `True` if `obj` is an exception class, else `False`. """ try: return issubclass(obj, Exception) except TypeError: # TypeError: issubclass() arg 1 must be a class return False def __call__(self): """ Simulate call, returning the result or raising the exception. """ if isinstance(self.result, Exception) or self._is_exception_class(self.result): raise self.result else: return self.result class ScriptedSession: """ "Scripted" `ftplib.FTP`-like class for testing. To avoid actual input/output over sockets or files, specify the values that should be returned by the class's methods. The class is instantiated with a `script` argument. This is a list of `Call` objects where each object specifies the name of the `ftplib.FTP` method that is expected to be called and what the method should return. If the value is an exception, it will be raised, not returned. In case the method returns a socket (like `transfercmd`), the return value to be specified in the `Call` instance is the content of the underlying socket file. The advantage of the approach of this class over the use of `unittest.mock.Mock` objects is that the sequence of calls is clearly visible. With `Mock` objects, the developer must keep in mind all the calls when specifying return values or side effects for the mock methods. """ # Class-level counter to enumerate `ScriptedSession`s. This makes it # possible to make the output even more compact. Additionally, it's easier # to distinguish numbers like 1, 2, etc. than hexadecimal ids. _session_count = 0 encoding = ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING @classmethod def reset_session_count(cls): cls._session_count = 0 def __init__(self, script): self.script = script # `File.close` accesses the session `sock` object to set and reset the # timeout. `sock` itself is never _called_ though, so it doesn't make # sense to create a `sock` _call_. self.sock = unittest.mock.Mock(name="socket_attribute") # Index into `script`, the list of `Call` objects self._call_index = 0 self.__class__._session_count += 1 self._session_count = self.__class__._session_count # Always expect an entry for the constructor. init_call = self._next_script_call("__init__") # The constructor isn't supposed to return anything. The only reason to # call it here is to raise an exception if that was specified in the # `script`. init_call() def __str__(self): return "{} {}".format(self.__class__.__name__, self._session_count) def _next_script_call(self, requested_attribute): """ Return next `Call` object. """ print(self, "in `_next_script_call`") try: call = self.script[self._call_index] except IndexError: print(" *** Ran out of `Call` objects for this session {!r}".format(self)) print(" Requested attribute was {!r}".format(requested_attribute)) raise self._call_index += 1 print(self, f"next call: {call!r}") return call def __getattr__(self, attribute_name): script_call = self._next_script_call(attribute_name) def dummy_method(*args, **kwargs): print(self, "in `__getattr__`") script_call.check_call(attribute_name, args, kwargs) return script_call() return dummy_method # ---------------------------------------------------------------------- # `ftplib.FTP` methods that shouldn't be executed with the default # processing in `__getattr__` def dir(self, path, callback): """ Call the `callback` for each line in the multiline string `call.result`. """ script_call = self._next_script_call("dir") # Check only the path. This requires that the corresponding `Call` # object also solely specifies the path as `args`. script_call.check_call("dir", (path,), None) # Give `dir` the chance to raise an exception if one was specified in # the `Call`'s `result` argument. call_result = script_call() for line in call_result.splitlines(): callback(line) def ntransfercmd(self, cmd, rest=None): """ Simulate the `ftplib.FTP.ntransfercmd` call. `ntransfercmd` returns a tuple of a socket and a size argument. The `result` value given when constructing an `ntransfercmd` call specifies an `io.TextIO` or `io.BytesIO` value to be used as the `Socket.makefile` result. """ script_call = self._next_script_call("ntransfercmd") script_call.check_call("ntransfercmd", (cmd, rest), None) # Give `ntransfercmd` the chance to raise an exception if one was # specified in the `Call`'s `result` argument. call_result = script_call() mock_socket = unittest.mock.Mock(name="socket") mock_socket.makefile.return_value = call_result # Return `None` for size. The docstring of `ftplib.FTP.ntransfercmd` # says that's a possibility. # TODO: Use a sensible `size` value later if it turns out we need it. return mock_socket, None def transfercmd(self, cmd, rest=None): """ Simulate the `ftplib.FTP.transfercmd` call. `transfercmd` returns a socket. The `result` value given when constructing an `transfercmd` call specifies an `io.TextIO` or `io.BytesIO` value to be used as the `Socket.makefile` result. """ script_call = self._next_script_call("transfercmd") script_call.check_call("transfercmd", (cmd, rest), None) # Give `transfercmd` the chance to raise an exception if one was # specified in the `Call`'s `result` argument. call_result = script_call() mock_socket = unittest.mock.Mock(name="socket") mock_socket.makefile.return_value = call_result return mock_socket class MultisessionFactory: """ Return a session factory using the scripted data from the given "scripts" for each consecutive call ("creation") of a factory. Example: host = ftputil.FTPHost(host, user, password, session_factory=scripted_session.factory(script1, script2)) When the `session_factory` is "instantiated" for the first time by `FTPHost._make_session`, the factory object will use the behavior described by the script `script1`. When the `session_factory` is "instantiated" a second time, the factory object will use the behavior described by the script `script2`. """ def __init__(self, *scripts): ScriptedSession.reset_session_count() self._scripts = iter(scripts) self.scripted_sessions = [] def __call__(self, host, user, password): """ Call the factory. This is equivalent to the constructor of the session (e. g. `ftplib.FTP` in a real application). """ script = next(self._scripts) scripted_session = ScriptedSession(script) self.scripted_sessions.append(scripted_session) return scripted_session factory = MultisessionFactory ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7939646 ftputil-5.0.3/test/test_base.py0000664000175000017500000000664300000000000015520 0ustar00schwaschwa# Copyright (C) 2003-2019, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. import io import ftputil # Since `io.BytesIO` and `io.StringIO` are built-in, they can't be patched with # `unittest.mock.patch`. However, derived classes can be mocked. Mocking is # useful to test the arguments of `write` calls, i. e. whether the expected # data was written. class MockableBytesIO(io.BytesIO): pass class MockableStringIO(io.StringIO): pass # Factory to produce `FTPHost`-like classes from a given `FTPHost` class and # (usually) a given `MockSession` class. def ftp_host_factory(session_factory, ftp_host_class=ftputil.FTPHost): return ftp_host_class( "dummy_host", "dummy_user", "dummy_password", session_factory=session_factory ) def dir_line( mode_string="-r--r--r--", nlink=1, user="dummy_user", group="dummy_group", size=512, date_=None, datetime_=None, name="dummy_name", link_target=None, ): """ Return a line as it would be returned by an FTP `DIR` invocation. Some values are handled specially: - One of `date_` or `datetime_` must be given, the other must be `None`. If `date_` is given, it must be a `datetime.date` object. The timestamp in the `DIR` line is formatted like "Apr 22 2019", with the concrete value taken from the `date_` object. If `datetime_` is given, it must be a `datetime.datetime` object. The timestamp in the `DIR` line is formatted like "Apr 22 16:50", with the concrete value taken from the `datetime_` object. Timezone information in the `datetime_` object is ignored. - If `link_target` is left at the default `None`, the name part is the value from the `name` argument. If `link_target` isn't `None`, the name part of the `DIR` line is formatted as "name -> link_target". Note that the spacing between the parts of the line isn't necessarily exactly what you'd get from an FTP server because the parser in ftputil doesn't take the exact amount of spaces into account, so the `DIR` lines don't have to be that accurate. Examples: # Result: # "drwxr-xr-x 2 dummy_user dummy_group 182 Apr 22 16:50 file_name" line = dir_line(mode_string="-rw-rw-r--", nlink=2, size=182, datetime=datetime.datetime.now(), name="file_name") # Result: # "drwxr-xr-x 1 dummy_user dummy_group 512 Apr 22 2019 dir_name -> dir_target" line = dir_line(mode_string="drwxr-xr-x", date=datetime.date.today(), name="dir_name", link_target="dir_target") """ # Date or datetime. We must have exactly one of `date_` and `datetime_` # set. The other value must be `None`. assert [date_, datetime_].count( None ) == 1, "specify exactly one of `date_` and `datetime_`" if date_: datetime_string = date_.strftime("%b %d %Y") else: datetime_string = datetime_.strftime("%b %d %H:%M") # Name, possibly with link target if not link_target: name_string = name else: name_string = "{} -> {}".format(name, link_target) # return "{} {} {} {} {} {} {}".format( mode_string, nlink, user, group, size, datetime_string, name_string ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7939646 ftputil-5.0.3/test/test_error.py0000664000175000017500000000434300000000000015732 0ustar00schwaschwa# Copyright (C) 2002-2020, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. import ftplib import socket import pytest import ftputil.error class TestFTPErrorArguments: """ The `*Error` constructors should accept either a byte string or a unicode string. """ def test_bytestring_argument(self): # An umlaut as latin-1 character io_error = ftputil.error.FTPIOError(b"\xe4") os_error = ftputil.error.FTPOSError(b"\xe4") def test_unicode_argument(self): # An umlaut as unicode character io_error = ftputil.error.FTPIOError("\xe4") os_error = ftputil.error.FTPOSError("\xe4") class TestErrorConversion: def callee(self): raise ftplib.error_perm() def test_ftplib_error_to_ftp_os_error(self): """ Ensure the `ftplib` exception isn't used as `FTPOSError` argument. """ with pytest.raises(ftputil.error.FTPOSError) as exc_info: with ftputil.error.ftplib_error_to_ftp_os_error: self.callee() exc = exc_info.value assert not (exc.args and isinstance(exc.args[0], ftplib.error_perm)) del exc_info def test_ftplib_error_to_ftp_io_error(self): """ Ensure the `ftplib` exception isn't used as `FTPIOError` argument. """ with pytest.raises(ftputil.error.FTPIOError) as exc_info: with ftputil.error.ftplib_error_to_ftp_io_error: self.callee() exc = exc_info.value assert not (exc.args and isinstance(exc.args[0], ftplib.error_perm)) del exc_info def test_error_message_reuse(self): """ Test if the error message string is retained if the caught exception has more than one element in `args`. """ # See ticket #76. with pytest.raises(ftputil.error.FTPOSError) as exc_info: # Format "host:port" doesn't work. The use here is intentional. host = ftputil.FTPHost("localhost:21", "", "") exc = exc_info.value assert isinstance(exc.__cause__, socket.gaierror) assert exc.__cause__.errno == socket.EAI_NONAME del exc_info ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7949646 ftputil-5.0.3/test/test_file.py0000664000175000017500000005460500000000000015526 0ustar00schwaschwa# Copyright (C) 2002-2019, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. import ftplib import io import unittest import pytest import ftputil.error from test import scripted_session from test import test_base Call = scripted_session.Call # Test data for use in text/binary read/write tests. TEXT_TEST_DATA = " söme line\r\nänother line\n almost done\n" BINARY_TEST_DATA = TEXT_TEST_DATA.encode("UTF-8") class TestFileOperations: """Test operations with file-like objects.""" def test_inaccessible_dir(self): """ Test whether opening a file at an invalid location fails. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("STOR inaccessible", None), result=ftplib.error_perm, ), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with pytest.raises(ftputil.error.FTPIOError): host.open("/inaccessible", "w") def test_caching_of_children(self): """ Test whether `FTPFile` cache of `FTPHost` object works. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file1_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call("transfercmd", args=("STOR path1", None), result=io.StringIO("")), Call("voidresp"), # Open a file again while reusing the child object and with it its # `_session` attribute (the `ftplib.FTP` object). Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call("transfercmd", args=("STOR path1", None), result=io.StringIO("")), Call("voidresp"), Call("close"), ] file2_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call("transfercmd", result=io.StringIO(""), args=("STOR path2", None)), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory( host_script, file1_script, file2_script ) with test_base.ftp_host_factory(multisession_factory) as host: assert len(host._children) == 0 path1 = "path1" path2 = "path2" # Open one file and inspect cache of available children. file1 = host.open(path1, "w") child1 = host._children[0] assert len(host._children) == 1 assert not child1._file.closed # Open another file. file2 = host.open(path2, "w") child2 = host._children[1] assert len(host._children) == 2 assert not child2._file.closed # Close first file. file1.close() assert len(host._children) == 2 assert child1._file.closed assert not child2._file.closed # Re-open first child's file. file1 = host.open(path1, "w") child1_1 = file1._host # Check if it's reused. assert child1 is child1_1 assert not child1._file.closed assert not child2._file.closed # Close second file. file2.close() assert child2._file.closed def test_write_to_directory(self): """ Test whether attempting to write to a directory fails. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("STOR some_directory", None), result=ftplib.error_perm, ), # Because of the exception, `voidresp` isn't called. Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with pytest.raises(ftputil.error.FTPIOError): host.open("/some_directory", "w") def test_mode_not_given(self): """ If the mode isn't given, a file should be opened in text read mode. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("RETR some_file", None), result=io.StringIO(TEXT_TEST_DATA), ), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with host.open("some_file") as fobj: data = fobj.read() assert data == TEXT_TEST_DATA def test_mode_is_none(self): """ If the mode is passed as `None`, a `TypeError` should be raised. (This is Python's behavior for local files.) """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with pytest.raises(TypeError): with host.open("some_file", None) as fobj: pass def test_binary_read(self): """ Read data from a binary file. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("RETR some_file", None), result=io.BytesIO(BINARY_TEST_DATA), ), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with host.open("some_file", "rb") as fobj: data = fobj.read() assert data == BINARY_TEST_DATA def test_binary_write(self): """ Write binary data with `write`. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("STOR some_file", None), result=test_base.MockableBytesIO(b""), ), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with unittest.mock.patch("test.test_base.MockableBytesIO.write") as write_mock: with test_base.ftp_host_factory(multisession_factory) as host: with host.open("some_file", "wb") as output: output.write(BINARY_TEST_DATA) write_mock.assert_called_with(BINARY_TEST_DATA) def test_text_read(self): """ Read text with plain `read`. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("RETR dummy", None), # Use the same `newline` as `host.open` call. The default for # `StringIO`'s `newline` differs from the default for `open` # and `socket.makefile`. result=io.StringIO(TEXT_TEST_DATA, newline=None), ), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with host.open("dummy", "r") as input_: data = input_.read(0) assert data == "" data = input_.read(3) assert data == " sö" # Specifically check the behavior around the line ending # character. data = input_.read(7) assert data == "me line" data = input_.read(1) assert data == "\n" data = input_.read() assert data == "änother line\n almost done\n" data = input_.read() assert data == "" def test_text_write(self): """ Write text with `write`. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("STOR dummy", None), # Use the same `newline` as `host.open` call. The default for # `StringIO`'s `newline` differs from the default for `open` # and `socket.makefile`. result=test_base.MockableStringIO(newline="\r\n"), ), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with unittest.mock.patch("test.test_base.MockableStringIO.write") as write_mock: with test_base.ftp_host_factory(multisession_factory) as host: with host.open("dummy", "w", newline="\r\n") as output: output.write(TEXT_TEST_DATA) write_mock.assert_called_with(TEXT_TEST_DATA) def test_text_writelines(self): """ Write text with `writelines`. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("STOR dummy", None), # Use the same `newline` as `host.open` call. The default for # `StringIO`'s `newline` differs from the default for `open` # and `socket.makefile`. result=test_base.MockableStringIO(newline="\r\n"), ), Call("voidresp"), Call("close"), ] data = [" söme line\r\n", "änother line\n", " almost done\n"] print("=== data:", data) backup_data = data[:] multisession_factory = scripted_session.factory(host_script, file_script) with unittest.mock.patch( "test.test_base.MockableStringIO.writelines" ) as write_mock: with test_base.ftp_host_factory(multisession_factory) as host: with host.open("dummy", "w", newline="\r\n") as output: output.writelines(data) write_mock.assert_called_with(data) # Ensure that the original data was not modified. assert data == backup_data def test_binary_readline(self): """ Read binary data with `readline`. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("RETR dummy", None), result=io.BytesIO(BINARY_TEST_DATA), ), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with host.open("dummy", "rb") as input_: data = input_.readline(3) assert data == b" s\xc3" data = input_.readline(9) assert data == b"\xb6me line\r" # 30 = at most 30 bytes data = input_.readline(30) assert data == b"\n" data = input_.readline() assert data == b"\xc3\xa4nother line\n" data = input_.readline() assert data == b" almost done\n" data = input_.readline() assert data == b"" def test_text_readline(self): """ Read text with `readline`. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("RETR dummy", None), # Use the same `newline` as `host.open` call. The default for # `StringIO`'s `newline` differs from the default for `open` # and `socket.makefile`. result=io.StringIO(TEXT_TEST_DATA, newline=None), ), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with host.open("dummy", "r") as input_: data = input_.readline(3) assert data == " sö" data = input_.readline(7) assert data == "me line" data = input_.readline(10) # Does implicit newline conversion. assert data == "\n" # 30 = at most 30 bytes data = input_.readline(30) assert data == "änother line\n" data = input_.readline() assert data == " almost done\n" data = input_.readline() assert data == "" def test_text_readlines(self): """ Read text with `readlines`. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("RETR dummy", None), # Use the same `newline` as `host.open` call. The default for # `StringIO`'s `newline` differs from the default for `open` # and `socket.makefile`. result=io.StringIO(TEXT_TEST_DATA, newline=None), ), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with host.open("dummy", "r") as input_: data = input_.read(3) assert data == " sö" data = input_.readlines() assert data == ["me line\n", "änother line\n", " almost done\n"] input_.close() def test_binary_iterator(self): """ Test iterator interface of `FTPFile` objects for binary files. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("RETR dummy", None), result=io.BytesIO(BINARY_TEST_DATA), ), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with host.open("dummy", "rb") as input_: input_iterator = iter(input_) assert next(input_iterator) == b" s\xc3\xb6me line\r\n" # The last two lines don't have a `\r`. assert next(input_iterator) == b"\xc3\xa4nother line\n" assert next(input_iterator) == b" almost done\n" with pytest.raises(StopIteration): input_iterator.__next__() def test_text_iterator(self): """ Test iterator interface of `FTPFile` objects for text files. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("RETR dummy", None), # Use the same `newline` as `host.open` call. The default for # `StringIO`'s `newline` differs from the default for `open` # and `socket.makefile`. result=io.StringIO(TEXT_TEST_DATA, newline=None), ), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with host.open("dummy", "r") as input_: input_iterator = iter(input_) assert next(input_iterator) == " söme line\n" assert next(input_iterator) == "änother line\n" assert next(input_iterator) == " almost done\n" with pytest.raises(StopIteration): input_iterator.__next__() def test_read_unknown_file(self): """ Test whether reading a file which isn't there fails. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call("transfercmd", args=("RETR notthere", None), result=ftplib.error_perm), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with pytest.raises(ftputil.error.FTPIOError): with host.open("notthere", "r") as input_: pass class TestAvailableChild: def _failing_pwd(self, exception_class): """ Return a function that will be used instead of the `session.pwd` and will raise the exception `exception_to_raise`. """ def new_pwd(): raise exception_class("") return new_pwd def _test_with_pwd_error(self, exception_class): """ Test if reusing a child session fails because of `child_host._session.pwd` raising an exception of type `exception_class`. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] first_file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call("transfercmd", args=("RETR dummy1", None), result=io.StringIO("")), Call("voidresp"), # This `pwd` is executed from `FTPHost._available_child`. Call("pwd", result=exception_class), Call("close"), ] second_file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call("transfercmd", args=("RETR dummy2", None), result=io.StringIO("")), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory( host_script, first_file_script, second_file_script ) with test_base.ftp_host_factory(multisession_factory) as host: # Implicitly create a child session. with host.open("/dummy1") as _: pass assert len(host._children) == 1 # Try to create a new file. Since `pwd` in # `FTPHost._available_child` raises an exception, a new child # session should be created. with host.open("/dummy2") as _: pass assert len(host._children) == 2 def test_pwd_with_error_temp(self): """ Test if an `error_temp` in `_session.pwd` skips the child session. """ self._test_with_pwd_error(ftplib.error_temp) def test_pwd_with_error_reply(self): """ Test if an `error_reply` in `_session.pwd` skips the child session. """ self._test_with_pwd_error(ftplib.error_reply) def test_pwd_with_OSError(self): """ Test if an `OSError` in `_session.pwd` skips the child session. """ self._test_with_pwd_error(OSError) def test_pwd_with_EOFError(self): """ Test if an `EOFError` in `_session.pwd` skips the child session. """ self._test_with_pwd_error(EOFError) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7949646 ftputil-5.0.3/test/test_file_transfer.py0000664000175000017500000001370400000000000017425 0ustar00schwaschwa# Copyright (C) 2010-2020, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. import datetime import io import random import pytest import ftputil.file_transfer import ftputil.stat from test import test_base from test import scripted_session Call = scripted_session.Call class MockFile: """ Class compatible with `LocalFile` and `RemoteFile`. """ def __init__(self, mtime, mtime_precision): self._mtime = mtime self._mtime_precision = mtime_precision def mtime(self): return self._mtime def mtime_precision(self): return self._mtime_precision class TestRemoteFile: def test_time_shift_subtracted_only_once(self): """ Test whether the time shift value is subtracted from the initial server timestamp only once. This subtraction happens in `stat._Stat.parse_unix_time`, so it must _not_ be done a second time in `file_transfer.RemoteFile`. """ utcnow = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) # 3 hours time_shift = 3 * 60 * 60 dir_line = test_base.dir_line( datetime_=utcnow + datetime.timedelta(seconds=time_shift), name="dummy_name" ) script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.set_time_shift(3 * 60 * 60) remote_file = ftputil.file_transfer.RemoteFile(host, "dummy_name", 0o644) remote_mtime = remote_file.mtime() # The remote mtime should be corrected by the time shift, so the # calculated UTC time is the same as for the client. The 60.0 (seconds) # is the timestamp precision. assert remote_mtime <= utcnow.timestamp() <= remote_mtime + 60.0 class TestTimestampComparison: def test_source_is_newer_than_target(self): """ Test whether the source is newer than the target, i. e. if the file should be transferred. """ # Define some time units/precisions. second = 1.0 minute = 60 * second hour = 60 * minute day = 24 * hour unknown = ftputil.stat.UNKNOWN_PRECISION # Define input arguments; modification datetimes are in seconds. Fields # are source datetime/precision, target datetime/precision, expected # comparison result. file_data = [ # Non-overlapping modification datetimes/precisions (1000.0, second, 900.0, second, True), (900.0, second, 1000.0, second, False), # Equal modification datetimes/precisions (if in doubt, transfer) (1000.0, second, 1000.0, second, True), # Just touching intervals (1000.0, second, 1000.0 + second, minute, True), (1000.0 + second, minute, 1000.0, second, True), # Other overlapping intervals (10000.0 - 0.5 * hour, hour, 10000.0, day, True), (10000.0 + 0.5 * hour, hour, 10000.0, day, True), (10000.0 + 0.2 * hour, 0.2 * hour, 10000.0, hour, True), (10000.0 - 0.2 * hour, 2 * hour, 10000.0, hour, True), # Unknown precision (1000.0, unknown, 1000.0, second, True), (1000.0, second, 1000.0, unknown, True), (1000.0, unknown, 1000.0, unknown, True), ] for ( source_mtime, source_mtime_precision, target_mtime, target_mtime_precision, expected_result, ) in file_data: source_file = MockFile(source_mtime, source_mtime_precision) target_file = MockFile(target_mtime, target_mtime_precision) result = ftputil.file_transfer.source_is_newer_than_target( source_file, target_file ) assert result == expected_result class FailingStringIO(io.BytesIO): """ Mock class to test whether exceptions are passed on. """ # Kind of nonsense; we just want to see this exception raised. expected_exception = IndexError def read(self, count): raise self.expected_exception class TestChunkwiseTransfer: def _random_string(self, count): """ Return a `BytesIO` object containing `count` "random" bytes. """ ints = (random.randint(0, 255) for i in range(count)) return bytes(ints) def test_chunkwise_transfer_without_remainder(self): """ Check if we get four chunks with 256 Bytes each. """ data = self._random_string(1024) fobj = io.BytesIO(data) chunks = list(ftputil.file_transfer.chunks(fobj, 256)) assert len(chunks) == 4 assert chunks[0] == data[:256] assert chunks[1] == data[256:512] assert chunks[2] == data[512:768] assert chunks[3] == data[768:1024] def test_chunkwise_transfer_with_remainder(self): """ Check if we get three chunks with 256 Bytes and one with 253. """ data = self._random_string(1021) fobj = io.BytesIO(data) chunks = list(ftputil.file_transfer.chunks(fobj, 256)) assert len(chunks) == 4 assert chunks[0] == data[:256] assert chunks[1] == data[256:512] assert chunks[2] == data[512:768] assert chunks[3] == data[768:1021] def test_chunkwise_transfer_with_exception(self): """ Check if we see the exception raised during reading. """ data = self._random_string(1024) fobj = FailingStringIO(data) iterator = ftputil.file_transfer.chunks(fobj, 256) with pytest.raises(FailingStringIO.expected_exception): next(iterator) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7959645 ftputil-5.0.3/test/test_host.py0000664000175000017500000013633300000000000015563 0ustar00schwaschwa# Copyright (C) 2002-2021, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. import datetime import errno import ftplib import io import itertools import os import pickle import posixpath import random import time import unittest import unittest.mock import warnings import pytest import ftputil import ftputil.error import ftputil.file import ftputil.path_encoding import ftputil.tool import ftputil.stat from test import test_base import test.scripted_session as scripted_session Call = scripted_session.Call # # Helper function to generate random data # def binary_data(): """ Return a bytes object of length 10000, consisting of bytes from a pool of integer numbers in the range 0..255. """ pool = list(range(0, 256)) size = 10000 integer_list = [random.choice(pool) for i in range(size)] return bytes(integer_list) def as_bytes(string, encoding=ftputil.path_encoding.DEFAULT_ENCODING): return string.encode(encoding) # # Test cases # # For Python < 3.9, the `default_session_factory` is just `ftplib.FTP`. It's # not worth it to test the factory then. @pytest.mark.skipif( not ftputil.path_encoding.RUNNING_UNDER_PY39_AND_UP, reason="tests apply only to Python 3.9 and up", ) class TestDefaultSessionFactory: def test_ftplib_FTP_subclass(self): """ Test if the default factory is a subclass of `ftplib.FTP`. """ assert issubclass(ftputil.host.default_session_factory, ftplib.FTP) def _test_extra_arguments(self, args=None, kwargs=None, expected_kwargs=None): """ Test if `ftputil.FTPHost` accepts additional positional and keyword arguments, which are then passed to the session factory. """ if args is None: args = () if kwargs is None: kwargs = {} if expected_kwargs is None: expected_kwargs = kwargs # Since our test server listens on a non-default port, we can't use the # session factory directly. We have to mock `ftplib.FTP` which is used # by ftputil's `default_session_factory`. with unittest.mock.patch("ftplib.FTP.__init__") as ftp_mock: # Prevent `TypeError` when Python checks the `__init__` result. ftp_mock.return_value = None session = ftputil.host.default_session_factory( "localhost", "ftptest", "dummy", *args, **kwargs ) assert len(ftp_mock.call_args_list) == 1 assert ( # Don't compare the `self` argument. It changes for every test # run. ftp_mock.call_args.args == ("localhost", "ftptest", "dummy") + args ) assert ftp_mock.call_args.kwargs == expected_kwargs def test_extra_positional_arguments(self): """ Test if extra positional arguments are passed to the `ftplib.FTP` constructor. """ expected_kwargs = {"encoding": ftputil.path_encoding.DEFAULT_ENCODING} # `acct`, `timeout` self._test_extra_arguments( args=("", 1.0), kwargs={}, expected_kwargs=expected_kwargs ) def test_extra_keyword_arguments(self): """ Test if extra keyword arguments are passed to the `ftplib.FTP` constructor. """ kwargs = {"timeout": 1.0, "source_address": None} expected_kwargs = kwargs.copy() expected_kwargs["encoding"] = ftputil.path_encoding.DEFAULT_ENCODING self._test_extra_arguments(kwargs=kwargs, expected_kwargs=expected_kwargs) def test_custom_encoding(self): """ Test if a custom encoding is passed to the base class constructor when running under Python 3.9 and up. """ kwargs = {"timeout": 1.0, "source_address": None, "encoding": "latin-2"} self._test_extra_arguments(kwargs=kwargs) class TestConstructor: """ Test initialization of `FTPHost` objects. """ def test_open_and_close(self): """ Test if opening and closing an `FTPHost` object works as expected. """ script = [Call("__init__"), Call("pwd", result="/"), Call("close")] host = test_base.ftp_host_factory(scripted_session.factory(script)) host.close() assert host.closed is True assert host._children == [] def test_invalid_login(self): """ Login to invalid host must fail. """ script = [Call("__init__", result=ftplib.error_perm), Call("pwd", result="/")] with pytest.raises(ftputil.error.FTPOSError): test_base.ftp_host_factory(scripted_session.factory(script)) def test_pwd_normalization(self): """ Test if the stored current directory is normalized. """ script = [ Call("__init__"), # Deliberately return the current working directory with a trailing # slash to test if it's removed when stored in the `FTPHost` # instance. Call("pwd", result="/home/"), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: assert host.getcwd() == "/home" def test_missing_encoding_attribute(self): """ Test if a missing `encoding` attribute on the session instance leads to a `NoEncodingError`. """ class InvalidSessionError: pass with pytest.raises(ftputil.error.NoEncodingError): _ = ftputil.host.FTPHost(session_factory=InvalidSessionError) class TestKeepAlive: def test_succeeding_keep_alive(self): """ Assume the connection is still alive. """ script = [ Call("__init__"), Call("pwd", result="/"), # `pwd` due to `keep_alive` call. Call("pwd", result="/"), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.keep_alive() def test_failing_keep_alive(self): """ Assume the connection has timed out, so `keep_alive` fails. """ script = [ Call("__init__"), Call("pwd", result="/home"), # Simulate failing `pwd` call after the server closed the # connection due to a session timeout. Call("pwd", result=ftplib.error_temp), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: with pytest.raises(ftputil.error.TemporaryError): host.keep_alive() class TestSetParser: class TrivialParser(ftputil.stat.Parser): """ An instance of this parser always returns the same result from its `parse_line` method. This is all we need to check if ftputil uses the set parser. No actual parsing code is required here. """ def __init__(self): # We can't use `os.stat("/home")` directly because we later need # the object's `_st_name` attribute, which we can't set on a # `os.stat` stat value. default_stat_result = ftputil.stat.StatResult(os.stat("/home")) default_stat_result._st_name = "home" self.default_stat_result = default_stat_result def parse_line(self, line, time_shift=0.0): return self.default_stat_result def test_set_parser(self): """ Test if the selected parser is used. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", result="drwxr-xr-x 2 45854 200 512 May 4 2000 home", ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: assert host._stat._allow_parser_switching is True trivial_parser = TestSetParser.TrivialParser() host.set_parser(trivial_parser) stat_result = host.stat("/home") assert stat_result == trivial_parser.default_stat_result assert host._stat._allow_parser_switching is False class TestCommandNotImplementedError: def test_command_not_implemented_error(self): """ Test if we get the anticipated exception if a command isn't implemented by the server. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # `FTPHost.chmod` only raises a `CommandNotImplementedError` when # the exception text of the `ftplib.error_perm` starts with "502". Call( "voidcmd", result=ftplib.error_perm("502 command not implemented"), args=("SITE CHMOD 0644 nonexistent",), ), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # `FTPHost.chmod` only raises a `CommandNotImplementedError` when # the exception text of the `ftplib.error_perm` starts with "502". Call( "voidcmd", result=ftplib.error_perm("502 command not implemented"), args=("SITE CHMOD 0644 nonexistent",), ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: with pytest.raises(ftputil.error.CommandNotImplementedError): host.chmod("nonexistent", 0o644) # `CommandNotImplementedError` is a subclass of `PermanentError`. with pytest.raises(ftputil.error.PermanentError): host.chmod("nonexistent", 0o644) class TestRecursiveListingForDotAsPath: """ These tests are for issue #33, see http://ftputil.sschwarzer.net/trac/ticket/33 . """ def test_plain_listing(self): """ If an empty string is passed to `FTPHost._dir` it should be passed to `session.dir` unmodified. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=(".",)), # Check that the empty string is passed on to `session.dir`. Call("dir", args=("",), result="non-recursive listing"), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: lines = host._dir(host.curdir) assert lines[0] == "non-recursive listing" def test_empty_string_instead_of_dot_workaround(self): """ If `FTPHost.listdir` is called with a dot as argument, the underlying `session.dir` should _not_ be called with the dot as argument, but with an empty string. """ dir_result = ( "total 10\n" "lrwxrwxrwx 1 staff 7 Aug 13 2003 bin -> usr/bin\n" "d--x--x--x 2 staff 512 Sep 24 2000 dev\n" "d--x--x--x 3 staff 512 Sep 25 2000 etc\n" "dr-xr-xr-x 3 staff 512 Oct 3 2000 pub\n" "d--x--x--x 5 staff 512 Oct 3 2000 usr\n" ) script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_result), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: files = host.listdir(host.curdir) assert files == ["bin", "dev", "etc", "pub", "usr"] class TestTimeShift: # Helper mock class that frees us from setting up complicated session # scripts for the remote calls. class _Path: def split(self, path): return posixpath.split(path) def set_mtime(self, mtime): self._mtime = mtime def getmtime(self, file_name): return self._mtime def join(self, *args): return posixpath.join(*args) def normpath(self, path): return posixpath.normpath(path) def isabs(self, path): return posixpath.isabs(path) def abspath(self, path): return "/_ftputil_sync_" # Needed for `isdir` in `FTPHost.remove` def isfile(self, path): return True def test_rounded_time_shift(self): """ Test if time shift is rounded correctly. """ script = [Call("__init__"), Call("pwd", result="/"), Call("close")] multisession_factory = scripted_session.factory(script) with test_base.ftp_host_factory(multisession_factory) as host: # Use private bound method. rounded_time_shift = host._FTPHost__rounded_time_shift # Pairs consisting of original value and expected result test_data = [ (0, 0), (0.1, 0), (-0.1, 0), (1500, 1800), (-1500, -1800), (1800, 1800), (-1800, -1800), (2000, 1800), (-2000, -1800), (5 * 3600 - 100, 5 * 3600), (-5 * 3600 + 100, -5 * 3600), ] for time_shift, expected_time_shift in test_data: calculated_time_shift = rounded_time_shift(time_shift) assert calculated_time_shift == expected_time_shift def test_assert_valid_time_shift(self): """ Test time shift sanity checks. """ script = [Call("__init__"), Call("pwd", result="/"), Call("close")] multisession_factory = scripted_session.factory(script) with test_base.ftp_host_factory(multisession_factory) as host: # Use private bound method. assert_time_shift = host._FTPHost__assert_valid_time_shift # Valid time shifts test_data = [23 * 3600, -23 * 3600, 3600 + 30, -3600 + 30] for time_shift in test_data: assert assert_time_shift(time_shift) is None # Invalid time shift (exceeds one day) with pytest.raises(ftputil.error.TimeShiftError): assert_time_shift(25 * 3600) with pytest.raises(ftputil.error.TimeShiftError): assert_time_shift(-25 * 3600) # Invalid time shift (too large deviation from 15-minute units is # unacceptable) with pytest.raises(ftputil.error.TimeShiftError): assert_time_shift(8 * 60) with pytest.raises(ftputil.error.TimeShiftError): assert_time_shift(-3600 - 8 * 60) def test_synchronize_times(self): """ Test time synchronization with server. """ host_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("delete", args=("_ftputil_sync_",)), Call("cwd", args=("/",)), Call("close"), ] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("STOR _ftputil_sync_", None), result=io.BytesIO() ), Call("voidresp"), Call("close"), ] # Valid time shifts test_data = [ (60 * 60 + 30, 60 * 60), (60 * 60 - 100, 60 * 60), (30 * 60 + 100, 30 * 60), (45 * 60 - 100, 45 * 60), ] for measured_time_shift, expected_time_shift in test_data: # Use a new `BytesIO` object to avoid exception # `ValueError: I/O operation on closed file`. file_script[4] = Call( "transfercmd", result=io.BytesIO(), args=("STOR _ftputil_sync_", None) ) multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: host.path = self._Path() host.path.set_mtime(time.time() + measured_time_shift) host.synchronize_times() assert host.time_shift() == expected_time_shift # Invalid time shifts measured_time_shifts = [60 * 60 + 8 * 60, 45 * 60 - 6 * 60] for measured_time_shift in measured_time_shifts: # Use a new `BytesIO` object to avoid exception # `ValueError: I/O operation on closed file`. file_script[4] = Call( "transfercmd", result=io.BytesIO(), args=("STOR _ftputil_sync_", None) ) multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: host.path = self._Path() host.path.set_mtime(time.time() + measured_time_shift) with pytest.raises(ftputil.error.TimeShiftError): host.synchronize_times() def test_synchronize_times_for_server_in_east(self): """ Test for timestamp correction (see ticket #55). """ host_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("delete", args=("_ftputil_sync_",)), Call("cwd", args=("/",)), Call("close"), ] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("STOR _ftputil_sync_", None), result=io.BytesIO() ), Call("voidresp", args=()), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(session_factory=multisession_factory) as host: host.path = self._Path() # Set this explicitly to emphasize the problem. host.set_time_shift(0.0) hour = 60 * 60 # This could be any negative time shift. presumed_time_shift = -6 * hour # Set `mtime` to simulate a server east of us. # In case the `time_shift` value for this host instance is 0.0 # (as is to be expected before the time shift is determined), the # directory parser (more specifically # `ftputil.stat.Parser.parse_unix_time`) will return a time which # is a year too far in the past. The `synchronize_times` method # needs to deal with this and add the year "back". I don't think # this is a bug in `parse_unix_time` because the method should work # once the time shift is set correctly. client_time = datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc ) presumed_server_time = client_time.replace( year=client_time.year - 1 ) + datetime.timedelta(seconds=presumed_time_shift) host.path.set_mtime(presumed_server_time.timestamp()) host.synchronize_times() assert host.time_shift() == presumed_time_shift class TestUploadAndDownload: """ Test upload and download. """ def test_download(self, tmp_path): """ Test mode download. """ remote_file_name = "dummy_name" remote_file_content = b"dummy_content" local_target = tmp_path / "test_target" host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("RETR {}".format(remote_file_name), None), result=io.BytesIO(remote_file_content), ), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) # Download with test_base.ftp_host_factory(multisession_factory) as host: host.download(remote_file_name, str(local_target)) # Verify expected operations on mock socket as done in `FTPFile.close`. # We expect one `gettimeout` and two `settimeout` calls. file_session = multisession_factory.scripted_sessions[1] file_session.sock.gettimeout.assert_called_once_with() assert len(file_session.sock.settimeout.call_args_list) == 2 assert file_session.sock.settimeout.call_args_list[0] == ( (ftputil.file.FTPFile._close_timeout,), {}, ) assert file_session.sock.settimeout.call_args_list[1] == ( (file_session.sock.gettimeout(),), {}, ) assert local_target.read_bytes() == remote_file_content def test_conditional_upload_without_upload(self, tmp_path): """ If the target file is newer, no upload should happen. """ local_source = tmp_path / "test_source" data = binary_data() local_source.write_bytes(data) dir_result = test_base.dir_line( mode_string="-rw-r--r--", date_=datetime.date.today() + datetime.timedelta(days=1), name="newer", ) script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_result), Call("cwd", args=("/",)), Call("close"), ] # Target is newer, so don't upload. # # This not only tests the return value, but also if a transfer # happened. If an upload was tried, our test framework would complain # about a missing scripted session for the `FTPFile` host. multisession_factory = scripted_session.factory(script) with test_base.ftp_host_factory(multisession_factory) as host: flag = host.upload_if_newer(str(local_source), "/newer") assert flag is False def test_conditional_upload_with_upload(self, tmp_path): """ If the target file is older or doesn't exist, the source file should be uploaded. """ local_source = tmp_path / "test_source" file_content = b"dummy_content" local_source.write_bytes(file_content) remote_file_name = "dummy_name" dir_result = test_base.dir_line( mode_string="-rw-r--r--", date_=datetime.date.today() - datetime.timedelta(days=1), name="older", ) host_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_result), Call("cwd", args=("/",)), Call("close"), ] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call( "transfercmd", args=("STOR older", None), result=test_base.MockableBytesIO(), ), Call("voidresp"), Call("close"), ] # Target is older, so upload. multisession_factory = scripted_session.factory(host_script, file_script) with unittest.mock.patch("test.test_base.MockableBytesIO.write") as write_mock: with test_base.ftp_host_factory(multisession_factory) as host: flag = host.upload_if_newer(str(local_source), "/older") write_mock.assert_called_with(file_content) assert flag is True # Target doesn't exist, so upload. # Use correct file name for this test. file_script[4] = Call( "transfercmd", args=("STOR notthere", None), result=test_base.MockableBytesIO(), ) multisession_factory = scripted_session.factory(host_script, file_script) with unittest.mock.patch("test.test_base.MockableBytesIO.write") as write_mock: with test_base.ftp_host_factory(multisession_factory) as host: flag = host.upload_if_newer(str(local_source), "/notthere") write_mock.assert_called_with(file_content) assert flag is True def test_conditional_download_without_target(self, tmp_path): """ Test conditional binary mode download when no target file exists. """ local_target = tmp_path / "test_target" data = binary_data() # Target does not exist, so download. # There isn't a `dir` call to compare the datetimes of the remote and # the target file because the local `exists` call for the local target # returns `False` and the datetime comparison therefore isn't done. host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call("transfercmd", args=("RETR newer", None), result=io.BytesIO(data)), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: flag = host.download_if_newer("/newer", str(local_target)) assert flag is True assert local_target.read_bytes() == data def test_conditional_download_with_older_target(self, tmp_path): """ Test conditional binary mode download with newer source file. """ local_target = tmp_path / "test_target" # Make sure file exists for the timestamp comparison. local_target.touch() data = binary_data() # Target is older, so download. # Use a date in the future. That isn't realistic, but for the purpose # of the test it's an easy way to make sure the source file is newer # than the target file. dir_result = test_base.dir_line( mode_string="-rw-r--r--", date_=datetime.date.today() + datetime.timedelta(days=1), name="newer", ) host_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_result), Call("cwd", args=("/",)), Call("close"), ] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call("transfercmd", args=("RETR newer", None), result=io.BytesIO(data)), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: flag = host.download_if_newer("/newer", str(local_target)) assert flag is True assert local_target.read_bytes() == data def test_conditional_download_with_newer_target(self, tmp_path): """ Test conditional binary mode download with older source file. """ local_target = tmp_path / "test_target" # Make sure file exists for timestamp comparison. local_target.touch() data = binary_data() # Use date in the past, so the target file is newer and no download # happens. dir_result = test_base.dir_line( mode_string="-rw-r--r--", date_=datetime.date.today() - datetime.timedelta(days=1), name="newer", ) host_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_result), Call("cwd", args=("/",)), Call("close"), ] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call("transfercmd", args=("RETR newer", None), result=io.BytesIO(data)), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: flag = host.download_if_newer("/newer", str(local_target)) assert flag is False class TestMakedirs: def test_exist_ok_false(self): """ If `exist_ok` is `False` or not specified, an existing leaf directory should lead to a `PermanentError` with `errno` set to 17. """ # No `exist_ok` specified script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/part1",)), Call("cwd", args=("/part1/part2",)), Call("cwd", args=("/",)), Call("close"), ] multisession_factory = scripted_session.factory(script) with test_base.ftp_host_factory(session_factory=multisession_factory) as host: with pytest.raises(ftputil.error.PermanentError) as exc_info: host.makedirs("/part1/part2") assert isinstance(exc_info.value, ftputil.error.PermanentError) assert exc_info.value.errno == errno.EEXIST # `exist_ok` explicitly set to `False` script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/part1",)), Call("cwd", args=("/part1/part2",)), Call("cwd", args=("/",)), Call("close"), ] multisession_factory = scripted_session.factory(script) with test_base.ftp_host_factory(session_factory=multisession_factory) as host: with pytest.raises(ftputil.error.PermanentError) as exc_info: host.makedirs("/part1/part2", exist_ok=False) assert isinstance(exc_info.value, ftputil.error.PermanentError) assert exc_info.value.errno == errno.EEXIST def test_exist_ok_true(self): """ If `exist_ok` is `True`, an existing leaf directory should _not_ lead to an exception. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/part1",)), Call("cwd", args=("/part1/part2",)), Call("cwd", args=("/",)), Call("close"), ] multisession_factory = scripted_session.factory(script) with test_base.ftp_host_factory(session_factory=multisession_factory) as host: host.makedirs("/part1/part2", exist_ok=True) class TestAcceptEitherUnicodeOrBytes: """ Test whether certain `FTPHost` methods accept either unicode or byte strings for the path(s). """ def test_upload(self): """ Test whether `upload` accepts either unicode or bytes. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call("transfercmd", args=("STOR target", None), result=io.BytesIO()), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) # The source file needs to be present in the current directory. with test_base.ftp_host_factory(multisession_factory) as host: host.upload("Makefile", "target") # Create new `BytesIO` object. file_script[4] = Call( "transfercmd", args=("STOR target", None), result=io.BytesIO() ) multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: host.upload("Makefile", as_bytes("target")) def test_download(self, tmp_path): """ Test whether `download` accepts either unicode or bytes. """ local_target = tmp_path / "local_target" host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call("transfercmd", args=("RETR source", None), result=io.BytesIO()), Call("voidresp"), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) # The source file needs to be present in the current directory. with test_base.ftp_host_factory(multisession_factory) as host: host.download("source", str(local_target)) # Create new `BytesIO` object. file_script[4] = Call( "transfercmd", args=("RETR source", None), result=io.BytesIO() ) multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: host.download(as_bytes("source"), str(local_target)) def test_rename(self): """ Test whether `rename` accepts either unicode or bytes. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("rename", args=("/ä", "/ä")), Call("close"), ] # It's possible to mix argument types, as for `os.rename`. path_as_str = "/ä" path_as_bytes = as_bytes( path_as_str, ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING ) paths = [path_as_str, path_as_bytes] for source_path, target_path in itertools.product(paths, paths): # Uses ftplib default encoding session_factory = scripted_session.factory(script) with test_base.ftp_host_factory(session_factory) as host: host.rename(source_path, target_path) def test_listdir(self): """ Test whether `listdir` accepts either unicode or bytes. """ top_level_dir_line = test_base.dir_line( mode_string="drwxr-xr-x", date_=datetime.date.today(), name="ä" ) dir_line1 = test_base.dir_line( mode_string="-rw-r--r--", date_=datetime.date.today(), name="ö" ) dir_line2 = test_base.dir_line( mode_string="-rw-r--r--", date_=datetime.date.today(), name="o" ) dir_result = dir_line1 + "\n" + dir_line2 script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=top_level_dir_line), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/ä",)), Call("dir", args=("",), result=dir_result), Call("cwd", args=("/",)), Call("close"), ] # Unicode session_factory = scripted_session.factory(script) with test_base.ftp_host_factory(session_factory) as host: items = host.listdir("ä") assert items == ["ö", "o"] # Bytes session_factory = scripted_session.factory(script) with test_base.ftp_host_factory(session_factory) as host: items = host.listdir( as_bytes("ä", ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING) ) assert items == [ as_bytes("ö", ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING), as_bytes("o"), ] def test_chmod(self): """ Test whether `chmod` accepts either unicode or bytes. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("voidcmd", args=("SITE CHMOD 0755 ä",)), Call("cwd", args=("/",)), Call("close"), ] path = "/ä" # Unicode session_factory = scripted_session.factory(script) with test_base.ftp_host_factory(session_factory) as host: host.chmod(path, 0o755) # Bytes session_factory = scripted_session.factory(script) with test_base.ftp_host_factory(session_factory) as host: host.chmod( as_bytes(path, ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING), 0o755 ) def _test_method_with_single_path_argument(self, method_name, path, script): # Unicode session_factory = scripted_session.factory(script) with test_base.ftp_host_factory(session_factory) as host: method = getattr(host, method_name) method(path) # Bytes session_factory = scripted_session.factory(script) with test_base.ftp_host_factory(session_factory) as host: method = getattr(host, method_name) method(as_bytes(path, ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING)) def test_chdir(self): """ Test whether `chdir` accepts either unicode or bytes. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/ö",)), Call("close"), ] self._test_method_with_single_path_argument("chdir", "/ö", script) def test_mkdir(self): """ Test whether `mkdir` accepts either unicode or bytes. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("mkd", args=("ä",)), Call("cwd", args=("/",)), Call("close"), ] self._test_method_with_single_path_argument("mkdir", "/ä", script) def test_makedirs(self): """ Test whether `makedirs` accepts either unicode or bytes. """ script = [ Call("__init__"), Call("pwd", result="/"), # To deal with ticket #86 (virtual directories), `makedirs` tries # to change into each directory and if it exists (changing doesn't # raise an exception), doesn't try to create it. That's why you # don't see an `mkd` calls here despite originally having a # `makedirs` call. Call("cwd", args=("/ä",)), # If `exist_ok` is `False` (which is the default), the leaf # directory to make must not exist. In other words, the `chdir` # call is `makedirs` must fail with a permanent error. Call("cwd", args=("/ä/ö",), result=ftplib.error_perm), Call("cwd", args=("/ä",)), Call("cwd", args=("/ä",)), Call("mkd", args=("ö",)), # From `isdir` call Call("cwd", args=("/ä",)), Call("cwd", args=("/",)), Call("close"), ] self._test_method_with_single_path_argument("makedirs", "/ä/ö", script) def test_rmdir(self): """ Test whether `rmdir` accepts either unicode or bytes. """ dir_line = test_base.dir_line( mode_string="drwxr-xr-x", date_=datetime.date.today(), name="empty_ä" ) # Since the session script isn't at all obvious, I checked it with a # debugger and added comments on some of the calls that happen during # the `rmdir` call. # # `_robust_ftp_command` descends one directory at a time (see ticket # #11) and restores the original directory in the end, which results in # at least four calls on the FTP session object (`cwd`, `cwd`, actual # method, `cwd`). It would be great if all the roundtrips to the server # could be reduced. script = [ # `FTPHost` initialization Call("__init__"), Call("pwd", result="/"), # `host.rmdir("/empty_ä")` # `host.listdir("/empty_ä")` # `host._stat._listdir("/empty_ä")` # `host._stat.__call_with_parser_retry("/empty_ä")` # `host._stat._real_listdir("/empty_ä")` # `host.path.isdir("/empty_ä")` Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), # `host.path.isdir` end # `host._stat._stat_results_from_dir("/empty_ä")` Call("cwd", args=("/",)), Call("cwd", args=("/empty_ä",)), Call("dir", args=("",), result=""), Call("cwd", args=("/",)), # `host._stat._stat_results_from_dir("/empty_ä")` end # `host._session.rmd` in `host._robust_ftp_command` # `host._check_inaccessible_login_directory()` Call("cwd", args=("/",)), # `host.chdir(head)` ("/") Call("cwd", args=("/",)), # `host.rmd(tail)` ("empty_ä") Call("rmd", args=("empty_ä",)), # `host.chdir(old_dir)` ("/") Call("cwd", args=("/",)), # Call("close"), ] empty_directory_as_required_by_rmdir = "/empty_ä" self._test_method_with_single_path_argument( "rmdir", empty_directory_as_required_by_rmdir, script ) def test_remove(self): """ Test whether `remove` accepts either unicode or bytes. """ dir_line = test_base.dir_line( mode_string="-rw-r--r--", date_=datetime.date.today(), name="ö" ) script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("delete", args=("ö",)), Call("cwd", args=("/",)), Call("close"), ] self._test_method_with_single_path_argument("remove", "/ö", script) def test_rmtree(self): """ Test whether `rmtree` accepts either unicode or bytes. """ dir_line = test_base.dir_line( mode_string="drwxr-xr-x", date_=datetime.date.today(), name="empty_ä" ) script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # Recursive `listdir` # Check parent (root) directory. Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/empty_ä",)), # Child directory (inside `empty_ä`) Call("dir", args=("",), result=""), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/empty_ä",)), # Recursive `rmdir` (repeated `cwd` calls because of # `_robust_ftp_command`) Call("dir", result="", args=("",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("rmd", args=("empty_ä",)), Call("cwd", args=("/",)), Call("close"), ] empty_directory_as_required_by_rmtree = "/empty_ä" self._test_method_with_single_path_argument( "rmtree", empty_directory_as_required_by_rmtree, script ) def test_lstat(self): """ Test whether `lstat` accepts either unicode or bytes. """ dir_line = test_base.dir_line( mode_string="-rw-r--r--", date_=datetime.date.today(), name="ä" ) script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), Call("close"), ] self._test_method_with_single_path_argument("lstat", "/ä", script) def test_stat(self): """ Test whether `stat` accepts either unicode or bytes. """ dir_line = test_base.dir_line( mode_string="-rw-r--r--", date_=datetime.date.today(), name="ä" ) script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), Call("close"), ] self._test_method_with_single_path_argument("stat", "/ä", script) def test_walk(self): """ Test whether `walk` accepts either unicode or bytes. """ dir_line = test_base.dir_line( mode_string="-rw-r--r--", date_=datetime.date.today(), name="ä" ) script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), Call("close"), ] # We're not interested in the return value of `walk`. # Unicode session_factory = scripted_session.factory(script) with test_base.ftp_host_factory(session_factory) as host: result = list(host.walk("/ä")) # Bytes session_factory = scripted_session.factory(script) with test_base.ftp_host_factory(session_factory) as host: result = list( host.walk(as_bytes("/ä", ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING)) ) class TestFailingPickling: def test_failing_pickling(self): """ Test if pickling (intentionally) isn't supported. """ host_script = [Call("__init__"), Call("pwd", result="/"), Call("close")] file_script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("voidcmd", args=("TYPE I",)), Call("transfercmd", args=("RETR test", None), result=io.BytesIO()), Call("voidresp", args=()), Call("close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with pytest.raises(TypeError): pickle.dumps(host) with host.open("/test") as file_obj: with pytest.raises(TypeError): pickle.dumps(file_obj) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7959645 ftputil-5.0.3/test/test_path.py0000664000175000017500000006213100000000000015534 0ustar00schwaschwa# Copyright (C) 2003-2021, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. import datetime import ftplib import functools import time import pytest import ftputil import ftputil.error import ftputil.path_encoding import ftputil.tool from test import test_base from test import scripted_session Call = scripted_session.Call def as_bytes(string, encoding=ftputil.path_encoding.DEFAULT_ENCODING): return string.encode(encoding) class TestPath: """Test operations in `FTPHost.path`.""" # TODO: Add unit tests for changes for ticket #113 (commits [b4c9b089b6b8] # and [4027740cdd2d]). def test_regular_isdir_isfile_islink(self): """ Test regular `FTPHost._Path.isdir/isfile/islink`. """ # Test a path which isn't there. script = [ Call("__init__"), Call("pwd", result="/"), # `isdir` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=""), Call("cwd", args=("/",)), # `isfile` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=""), Call("cwd", args=("/",)), # `islink` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=""), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() assert not host.path.isdir("notthere") assert not host.path.isfile("notthere") assert not host.path.islink("notthere") # This checks additional code (see ticket #66). script = [ Call("__init__"), Call("pwd", result="/"), # `isdir` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=""), Call("cwd", args=("/",)), # `isfile` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=""), Call("cwd", args=("/",)), # `islink` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=""), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() assert not host.path.isdir("/notthere/notthere") assert not host.path.isfile("/notthere/notthere") assert not host.path.islink("/notthere/notthere") # Test a directory. test_dir = "/some_dir" dir_line = test_base.dir_line( mode_string="dr-xr-xr-x", datetime_=datetime.datetime.now(), name=test_dir.lstrip("/"), ) script = [ Call("__init__"), Call("pwd", result="/"), # `isdir` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), # `isfile` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), # `islink` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() assert host.path.isdir(test_dir) assert not host.path.isfile(test_dir) assert not host.path.islink(test_dir) # Test a file. test_file = "/some_file" dir_line = test_base.dir_line( datetime_=datetime.datetime.now(), name=test_file.lstrip("/") ) script = [ Call("__init__"), Call("pwd", result="/"), # `isdir` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), # `isfile` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), # `islink` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() assert not host.path.isdir(test_file) assert host.path.isfile(test_file) assert not host.path.islink(test_file) # Test a link. Since the link target doesn't exist, neither # `isdir` nor `isfile` return `True`. test_link = "/some_link" dir_line = test_base.dir_line( mode_string="lrwxrwxrwx", datetime_=datetime.datetime.now(), name=test_link.lstrip("/"), link_target="nonexistent", ) script = [ Call("__init__"), Call("pwd", result="/"), # `isdir` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), # Look for `/some_link` Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # Look for `/nonexistent` Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), # `isfile` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), # Look for `/some_link` Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # Look for `/nonexistent` Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), # `islink` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), # Look for `/some_link`. `islink` doesn't try to dereference # the link. Call("dir", args=("",), result=dir_line), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() assert not host.path.isdir(test_link) assert not host.path.isfile(test_link) assert host.path.islink(test_link) def test_workaround_for_spaces(self): """ Test whether the workaround for space-containing paths is used. """ # Test a file name containing spaces. test_file = "/home/dir with spaces/file with spaces" dir_line1 = test_base.dir_line( mode_string="dr-xr-xr-x", datetime_=datetime.datetime.now(), name="home" ) dir_line2 = test_base.dir_line( mode_string="dr-xr-xr-x", datetime_=datetime.datetime.now(), name="dir with spaces", ) dir_line3 = test_base.dir_line( mode_string="-r--r--r--", datetime_=datetime.datetime.now(), name="file with spaces", ) script = [ Call("__init__"), Call("pwd", result="/"), # `isdir` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line1), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/home",)), Call("dir", args=("",), result=dir_line2), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/home/dir with spaces",)), Call("dir", args=("",), result=dir_line3), Call("cwd", args=("/",)), # `isfile` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line1), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/home",)), Call("dir", args=("",), result=dir_line2), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/home/dir with spaces",)), Call("dir", args=("",), result=dir_line3), Call("cwd", args=("/",)), # `islink` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line1), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/home",)), Call("dir", args=("",), result=dir_line2), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/home/dir with spaces",)), Call("dir", args=("",), result=dir_line3), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() assert not host.path.isdir(test_file) assert host.path.isfile(test_file) assert not host.path.islink(test_file) def test_inaccessible_home_directory_and_whitespace_workaround(self): """ Test combination of inaccessible home directory + whitespace in path. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", result=ftplib.error_perm), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: with pytest.raises(ftputil.error.InaccessibleLoginDirError): host._dir("/home dir") def test_isdir_isfile_islink_with_dir_failure(self): """ Test failing `FTPHost._Path.isdir/isfile/islink` because of failing `_dir` call. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=ftplib.error_perm), Call("cwd", args=("/",)), Call("close"), ] FTPOSError = ftputil.error.FTPOSError # Test if exceptions are propagated. with test_base.ftp_host_factory(scripted_session.factory(script)) as host: with pytest.raises(FTPOSError): host.path.isdir("index.html") with test_base.ftp_host_factory(scripted_session.factory(script)) as host: with pytest.raises(FTPOSError): host.path.isfile("index.html") with test_base.ftp_host_factory(scripted_session.factory(script)) as host: with pytest.raises(FTPOSError): host.path.islink("index.html") def test_isdir_isfile_with_infinite_link_chain(self): """ Test if `isdir` and `isfile` return `False` if they encounter an infinite link chain. """ # `/home/bad_link` links to `/home/subdir/bad_link`, which links back # to `/home/bad_link` etc. dir_line1 = test_base.dir_line( mode_string="dr-xr-xr-x", datetime_=datetime.datetime.now(), name="home" ) dir_line2 = test_base.dir_line( mode_string="lrwxrwxrwx", datetime_=datetime.datetime.now(), name="bad_link", link_target="subdir/bad_link", ) dir_line3 = test_base.dir_line( mode_string="dr-xr-xr-x", datetime_=datetime.datetime.now(), name="subdir" ) dir_line4 = test_base.dir_line( mode_string="lrwxrwxrwx", datetime_=datetime.datetime.now(), name="bad_link", link_target="/home/bad_link", ) script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line1), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/home",)), Call("dir", args=("",), result=dir_line2 + "\n" + dir_line3), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/home/subdir",)), Call("dir", args=("",), result=dir_line4), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: assert host.path.isdir("/home/bad_link") is False with test_base.ftp_host_factory(scripted_session.factory(script)) as host: assert host.path.isfile("/home/bad_link") is False def test_exists(self): """ Test `FTPHost.path.exists`. """ # Regular use of `exists` dir_line1 = test_base.dir_line( datetime_=datetime.datetime.now(), name="some_file" ) script = [ Call("__init__"), Call("pwd", result="/"), # `exists("some_file")` Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line1), Call("cwd", args=("/",)), # `exists("notthere")` Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=dir_line1), Call("cwd", args=("/",)), # `exists` with failing `dir` call Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=ftplib.error_perm), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() assert host.path.exists("some_file") assert not host.path.exists("notthere") # Test if exceptions are propagated. with pytest.raises(ftputil.error.FTPOSError): host.path.exists("some_file") class TestAcceptEitherBytesOrStr: # Use path arguments directly path_converter = staticmethod(lambda path: path) def _test_method_string_types(self, method, path): expected_type = type(path) path_converter = self.path_converter assert isinstance(method(path_converter(path)), expected_type) def test_methods_that_take_and_return_one_string(self): """ Test whether the same string type as for the argument is returned. """ method_names = [ "abspath", "basename", "dirname", "join", "normcase", "normpath", ] script = [Call("__init__"), Call("pwd", result="/"), Call("close")] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: for method_name in method_names: method = getattr(host.path, method_name) self._test_method_string_types(method, "/") self._test_method_string_types(method, ".") self._test_method_string_types(method, b"/") self._test_method_string_types(method, b".") def test_methods_that_take_a_string_and_return_a_bool(self): """ Test whether the methods accept byte and unicode strings. """ path_converter = self.path_converter script = [ Call("__init__"), Call("pwd", result="/"), # `exists` test 1 Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result=test_base.dir_line(name="ä", datetime_=datetime.datetime.now()), ), Call("cwd", args=("/",)), # `exists` test 2 Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result=test_base.dir_line(name="ä", datetime_=datetime.datetime.now()), ), Call("cwd", args=("/",)), # `isdir` test 1 Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result=test_base.dir_line( mode_string="dr-xr-xr-x", name="ä", datetime_=datetime.datetime.now(), ), ), Call("cwd", args=("/",)), # `isdir` test 2 Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result=test_base.dir_line( mode_string="dr-xr-xr-x", name="ä", datetime_=datetime.datetime.now(), ), ), Call("cwd", args=("/",)), # `isfile` test 1 Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result=test_base.dir_line(name="ö", datetime_=datetime.datetime.now()), ), Call("cwd", args=("/",)), # `isfile` test 2 Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result=test_base.dir_line(name="ö", datetime_=datetime.datetime.now()), ), Call("cwd", args=("/",)), # `islink` test 1 Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result=test_base.dir_line( mode_string="lrwxrwxrwx", name="ü", datetime_=datetime.datetime.now(), link_target="unimportant", ), ), Call("cwd", args=("/",)), # `islink` test 2 Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result=test_base.dir_line( mode_string="lrwxrwxrwx", name="ü", datetime_=datetime.datetime.now(), link_target="unimportant", ), ), Call("cwd", args=("/",)), Call("close"), ] custom_as_bytes = functools.partial( as_bytes, encoding=ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING ) with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() # `isabs` assert not host.path.isabs("ä") assert not host.path.isabs(path_converter(custom_as_bytes("ä"))) # `exists` assert host.path.exists(path_converter("ä")) assert host.path.exists(path_converter(custom_as_bytes("ä"))) # `isdir`, `isfile`, `islink` assert host.path.isdir(path_converter("ä")) assert host.path.isdir(path_converter(custom_as_bytes("ä"))) assert host.path.isfile(path_converter("ö")) assert host.path.isfile(path_converter(custom_as_bytes("ö"))) assert host.path.islink(path_converter("ü")) assert host.path.islink(path_converter(custom_as_bytes("ü"))) def test_getmtime(self): """ Test whether `FTPHost.path.getmtime` accepts byte and unicode paths. """ path_converter = self.path_converter now = datetime.datetime.utcnow() script = [ Call("__init__"), Call("pwd", result="/"), # `getmtime` call 1 Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=test_base.dir_line(name="ä", datetime_=now)), Call("cwd", args=("/",)), # `getmtime` call 2 Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=test_base.dir_line(name="ä", datetime_=now)), Call("cwd", args=("/",)), Call("close"), ] expected_mtime = now.timestamp() # We don't care about the _exact_ time, so don't bother with timezone # differences. Instead, do a simple sanity check. day = 24 * 60 * 60 # seconds mtime_makes_sense = ( lambda mtime: expected_mtime - day <= mtime <= expected_mtime + day ) with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() assert mtime_makes_sense(host.path.getmtime(path_converter(("ä")))) assert mtime_makes_sense( host.path.getmtime( path_converter( as_bytes("ä", ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING) ) ) ) def test_getsize(self): """ Test whether `FTPHost.path.getsize` accepts byte and unicode paths. """ path_converter = self.path_converter now = datetime.datetime.now() script = [ Call("__init__"), Call("pwd", result="/"), # `getsize` call 1 Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result=test_base.dir_line(name="ä", size=512, datetime_=now), ), Call("cwd", args=("/",)), # `getsize` call 2 Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result=test_base.dir_line(name="ä", size=512, datetime_=now), ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() assert host.path.getsize(path_converter("ä")) == 512 assert ( host.path.getsize( path_converter( as_bytes("ä", ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING) ) ) == 512 ) def test_walk(self): """ Test whether `FTPHost.path.walk` accepts bytes and unicode paths. """ path_converter = self.path_converter now = datetime.datetime.now() script = [ Call("__init__"), Call("pwd", result="/"), # `walk` call 1 Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result=test_base.dir_line( mode_string="dr-xr-xr-x", name="ä", size=512, datetime_=now ), ), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/ä",)), # Assume directory `ä` is empty. Call("dir", args=("",), result=""), Call("cwd", args=("/",)), # `walk` call 2 Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result=test_base.dir_line( mode_string="dr-xr-xr-x", name="ä", size=512, datetime_=now ), ), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/ä",)), # Assume directory `ä` is empty. Call("dir", args=("",), result=""), Call("cwd", args=("/",)), Call("close"), ] def noop(arg, top, names): del names[:] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() host.path.walk(path_converter("ä"), func=noop, arg=None) host.path.walk( path_converter( as_bytes("ä", ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING) ), func=noop, arg=None, ) class Path: def __init__(self, path): self.path = path def __fspath__(self): return self.path class TestAcceptEitherBytesOrStrFromPath(TestAcceptEitherBytesOrStr): # Take path arguments from `Path(...)` objects path_converter = Path ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7959645 ftputil-5.0.3/test/test_public_servers.py0000664000175000017500000001573700000000000017641 0ustar00schwaschwa# Copyright (C) 2009-2018, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. import os import socket import subprocess import pytest import ftputil import test def email_address(): """ Return the email address used to identify the client to an FTP server. If the hostname is "warpy", use my (Stefan's) email address, else try to use the content of the `$EMAIL` environment variable. If that doesn't exist, use a dummy address. """ hostname = socket.gethostname() if hostname == "warpy": email = "sschwarzer@sschwarzer.net" else: dummy_address = "anonymous@example.com" email = os.environ.get("EMAIL", dummy_address) if not email: # Environment variable exists but content is an empty string email = dummy_address return email EMAIL = email_address() def ftp_client_listing(server, directory): """ Log into the FTP server `server` using the command line client, then change to the `directory` and retrieve a listing with "dir". Return the list of items found as an `os.listdir` would return it. """ # The `-n` option prevents an auto-login. ftp_popen = subprocess.Popen( ["ftp", "-n", server], stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True, ) commands = ["user anonymous {}".format(EMAIL), "dir", "bye"] if directory: # Change to this directory before calling "dir". commands.insert(1, "cd {}".format(directory)) input_ = "\n".join(commands) stdout, unused_stderr = ftp_popen.communicate(input_) # Collect the directory/file names from the listing's text names = [] for line in stdout.strip().split("\n"): if line.startswith("total ") or line.startswith("Trying "): continue parts = line.split() if parts[-2] == "->": # Most likely a link name = parts[-3] else: name = parts[-1] names.append(name) # Remove entries for current and parent directory since they aren't # included in the result of `FTPHost.listdir` either. names = [name for name in names if name not in (".", "..")] return names class TestPublicServers: """ Get directory listings from various public FTP servers with a command line client and ftputil and compare both. An important aspect is to test different "spellings" of the same directory. For example, to list the root directory which is usually set after login, use "" (nothing), ".", "/", "/.", "./.", "././", "..", "../.", "../.." etc. The command line client `ftp` has to be in the path. """ # Implementation note: # # I (Stefan) implement the code so it works with Ubuntu's client. Other # clients may work or not. If you have problems testing some other client, # please send me a (small) patch. Keep in mind that I don't plan supporting # as many FTP obscure commandline clients as servers. ;-) # List of pairs with server name and a directory "guaranteed to exist" # under the login directory which is assumed to be the root directory. servers = [ # Posix format ("ftp.de.debian.org", "debian"), # ("ftp.gnome.org", "pub"), ("ftp.heanet.ie", "pub"), ("ftp.heise.de", "pub"), # DOS/Microsoft format # Do you know any FTP servers that use Microsoft format? # `ftp.microsoft.com` doesn't seem to be reachable anymore. ] # This data structure contains the initial directories "." and "DIR" (which # will be replaced by a valid directory name for each server). The list # after the initial directory contains paths that will be queried after # changing into the initial directory. All items in these lists are # actually supposed to yield the same directory contents. paths_table = [ ( ".", [ ".", "/", "/.", "./.", "././", "..", "../.", "../..", "DIR/..", "/DIR/../.", "/DIR/../..", ], ), ("DIR", [".", "/DIR", "/DIR/", "../DIR", "../../DIR"]), ] def inner_test_server(self, server, initial_directory, paths): """ Test one server for one initial directory. Connect to the server `server`; if the string argument `initial_directory` has a true value, change to this directory. Then iterate over all strings in the sequence `paths`, comparing the results of a listdir call with the listing from the command line client. """ canonical_names = ftp_client_listing(server, initial_directory) host = ftputil.FTPHost(server, "anonymous", EMAIL) try: host.chdir(initial_directory) for path in paths: path = path.replace("DIR", initial_directory) # Make sure that we don't recycle directory entries, i. e. # really repeatedly retrieve the directory contents (shouldn't # happen anyway with the current implementation). host.stat_cache.clear() names = host.listdir(path) # Filter out "hidden" names since the FTP command line client # won't include them in its listing either. names = [ name for name in names if not ( name.startswith(".") or # The login directory of `ftp.microsoft.com` contains # this "hidden" entry that ftputil finds but not the # FTP command line client. name == "mscomtest" ) ] failure_message = "For server {}, directory {}: {} != {}".format( server, initial_directory, names, canonical_names ) assert names == canonical_names, failure_message finally: host.close() @pytest.mark.slow_test def test_servers(self): """ Test all servers in `self.servers`. For each server, get the listings for the login directory and one other directory which is known to exist. Use different "spellings" to retrieve each list via ftputil and compare with the results gotten with the command line client. """ for server, actual_initial_directory in self.servers: print("=== server:", server) for initial_directory, paths in self.paths_table: initial_directory = initial_directory.replace( "DIR", actual_initial_directory ) print(server, initial_directory) self.inner_test_server(server, initial_directory, paths) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7959645 ftputil-5.0.3/test/test_real_ftp.py0000664000175000017500000011036700000000000016401 0ustar00schwaschwa# Copyright (C) 2003-2021, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. # Execute tests on a real FTP server (other tests use mock code). # # These tests write some files and directories on the local client and the # remote server. You'll need write access in the login directory. These tests # can take a few minutes because they have to wait to test the timezone # calculation. import datetime import ftplib import functools import gc import operator import os import pathlib import time import stat import pytest import ftputil.error import ftputil.file_transfer import ftputil.path_encoding import ftputil.session import ftputil.stat_cache import test def expected_time_shift(): """ Return the expected time shift in seconds. Unfortunately, the calculation may depend on the timezone of the server, i. e. the timezone used in directory listings coming from the server. So, depending on your test environment, you may need to change this function for your environment. _If_ you need an adapted `expected_time_shift`, please contact me (my e-mail address is in the ftputil documentation). In my particular case, I use Pure-FTPd as FTP server for the integration tests. At some point, it returned listings in the local timezone of the server, later it used UTC time, and now it uses the local timezone again. I wasn't able to find out why or how I can control this. """ raw_time_shift = (datetime.datetime.now() - datetime.datetime.utcnow()).seconds # To be safe, round the above value to units of 900 s (1/4 hours). return round(raw_time_shift / 900.0) * 900 # The containerized PureFTPd seems to use UTC. EXPECTED_TIME_SHIFT = 0.0 # Set when starting container PORT = 2121 DEFAULT_SESSION_FACTORY = ftputil.session.session_factory( port=PORT, encrypt_data_channel=False ) class Cleaner: """ This class helps remove directories and files which might otherwise be left behind if a test fails in unexpected ways. """ def __init__(self, host): # The test class (probably `RealFTPTest`) and the helper # class share the same `FTPHost` object. self._host = host self._ftp_items = [] def add_dir(self, path): """ Schedule a directory with path `path` for removal. """ self._ftp_items.append(("d", self._host.path.abspath(path))) def add_file(self, path): """ Schedule a file with path `path` for removal. """ self._ftp_items.append(("f", self._host.path.abspath(path))) def clean(self): """ Remove the directories and files previously remembered. The removal works in reverse order of the scheduling with `add_dir` and `add_file`. Errors due to a removal are ignored. """ self._host.chdir("/") for type_, path in reversed(self._ftp_items): try: if type_ == "d": # If something goes wrong in `rmtree` we might leave a mess # behind. self._host.rmtree(path) elif type_ == "f": # Minor mess if `remove` fails self._host.remove(path) except ftputil.error.FTPError: pass class RealFTPTest: def setup_method(self, method): # Server, username, password. self.login_data = ("localhost", "ftptest", "dummy") self.host = ftputil.FTPHost( *self.login_data, session_factory=DEFAULT_SESSION_FACTORY ) self.cleaner = Cleaner(self.host) def teardown_method(self, method): self.cleaner.clean() self.host.close() # # Helper methods # def make_remote_file(self, path): """Create a file on the FTP host.""" self.cleaner.add_file(path) with self.host.open(path, "wb") as file_: # Write something. Otherwise the FTP server might not update the # time of last modification if the file existed before. file_.write(b"\n") def make_local_file(self): """ Create a file on the local host (= on the client side). """ with open("_local_file_", "wb") as fobj: fobj.write(b"abc\x12\x34def\t") class TestMkdir(RealFTPTest): def test_mkdir_rmdir(self): host = self.host dir_name = "_testdir_" file_name = host.path.join(dir_name, "_nonempty_") self.cleaner.add_dir(dir_name) # Make dir and check if the directory is there. host.mkdir(dir_name) files = host.listdir(host.curdir) assert dir_name in files # Try to remove a non-empty directory. self.cleaner.add_file(file_name) non_empty = host.open(file_name, "w") non_empty.close() with pytest.raises(ftputil.error.PermanentError): host.rmdir(dir_name) # Remove file. host.unlink(file_name) # `remove` on a directory should fail. try: try: host.remove(dir_name) except ftputil.error.PermanentError as exc: assert str(exc).startswith("remove/unlink can only delete files") else: pytest.fail("we shouldn't have come here") finally: # Delete empty directory. host.rmdir(dir_name) files = host.listdir(host.curdir) assert dir_name not in files def test_makedirs_without_existing_dirs(self): host = self.host # No `_dir1_` yet assert "_dir1_" not in host.listdir(host.curdir) # Vanilla case, all should go well. host.makedirs("_dir1_/dir2/dir3/dir4") self.cleaner.add_dir("_dir1_") # Check host. assert host.path.isdir("_dir1_") assert host.path.isdir("_dir1_/dir2") assert host.path.isdir("_dir1_/dir2/dir3") assert host.path.isdir("_dir1_/dir2/dir3/dir4") def test_makedirs_from_non_root_directory(self): # This is a testcase for issue #22, see # http://ftputil.sschwarzer.net/trac/ticket/22 . host = self.host # No `_dir1_` and `_dir2_` yet assert "_dir1_" not in host.listdir(host.curdir) assert "_dir2_" not in host.listdir(host.curdir) # Part 1: Try to make directories starting from `_dir1_` and # change to non-root directory. self.cleaner.add_dir("_dir1_") host.mkdir("_dir1_") host.chdir("_dir1_") host.makedirs("_dir2_/_dir3_") # Test for expected directory hierarchy. assert host.path.isdir("/_dir1_") assert host.path.isdir("/_dir1_/_dir2_") assert host.path.isdir("/_dir1_/_dir2_/_dir3_") assert not host.path.isdir("/_dir1_/_dir1_") # Remove all but the directory we're in. host.rmdir("/_dir1_/_dir2_/_dir3_") host.rmdir("/_dir1_/_dir2_") # Part 2: Try to make directories starting from root. self.cleaner.add_dir("/_dir2_") host.makedirs("/_dir2_/_dir3_") # Test for expected directory hierarchy assert host.path.isdir("/_dir2_") assert host.path.isdir("/_dir2_/_dir3_") assert not host.path.isdir("/_dir1_/_dir2_") def test_makedirs_of_existing_directory(self): host = self.host # The (chrooted) login directory host.makedirs("/", exist_ok=True) def test_makedirs_with_file_in_the_way(self): host = self.host self.cleaner.add_dir("_dir1_") host.mkdir("_dir1_") self.make_remote_file("_dir1_/file1") # Try it. with pytest.raises(ftputil.error.PermanentError): host.makedirs("_dir1_/file1") with pytest.raises(ftputil.error.PermanentError): host.makedirs("_dir1_/file1/dir2") def test_makedirs_with_existing_directory(self): host = self.host self.cleaner.add_dir("_dir1_") host.mkdir("_dir1_") host.makedirs("_dir1_/dir2") # Check assert host.path.isdir("_dir1_") assert host.path.isdir("_dir1_/dir2") def test_makedirs_in_non_writable_directory(self): host = self.host # Preparation: `rootdir1` exists but is only writable by root. with pytest.raises(ftputil.error.PermanentError): host.makedirs("rootdir1/dir2") def test_makedirs_with_writable_directory_at_end(self): host = self.host self.cleaner.add_dir("rootdir2/dir2") # Preparation: `rootdir2` exists but is only writable by root. `dir2` # is writable by regular ftp users. Both directories below should work. host.makedirs("rootdir2/dir2", exist_ok=True) host.makedirs("rootdir2/dir2/dir3") class TestRemoval(RealFTPTest): def test_rmtree_without_error_handler(self): host = self.host # Build a tree. self.cleaner.add_dir("_dir1_") host.makedirs("_dir1_/dir2") self.make_remote_file("_dir1_/file1") self.make_remote_file("_dir1_/file2") self.make_remote_file("_dir1_/dir2/file3") self.make_remote_file("_dir1_/dir2/file4") # Try to remove a _file_ with `rmtree`. with pytest.raises(ftputil.error.PermanentError): host.rmtree("_dir1_/file2") # Remove `dir2`. host.rmtree("_dir1_/dir2") assert not host.path.exists("_dir1_/dir2") assert host.path.exists("_dir1_/file2") # Re-create `dir2` and remove `_dir1_`. host.mkdir("_dir1_/dir2") self.make_remote_file("_dir1_/dir2/file3") self.make_remote_file("_dir1_/dir2/file4") host.rmtree("_dir1_") assert not host.path.exists("_dir1_") def test_rmtree_with_error_handler(self): host = self.host self.cleaner.add_dir("_dir1_") host.mkdir("_dir1_") self.make_remote_file("_dir1_/file1") # Prepare error "handler" log = [] def error_handler(*args): log.append(args) # Try to remove a file as root "directory". host.rmtree("_dir1_/file1", ignore_errors=True, onerror=error_handler) assert log == [] host.rmtree("_dir1_/file1", ignore_errors=False, onerror=error_handler) assert log[0][0] == host.listdir assert log[0][1] == "_dir1_/file1" assert log[1][0] == host.rmdir assert log[1][1] == "_dir1_/file1" host.rmtree("_dir1_") # Try to remove a non-existent directory. del log[:] host.rmtree("_dir1_", ignore_errors=False, onerror=error_handler) assert log[0][0] == host.listdir assert log[0][1] == "_dir1_" assert log[1][0] == host.rmdir assert log[1][1] == "_dir1_" def test_remove_non_existent_item(self): host = self.host with pytest.raises(ftputil.error.PermanentError): host.remove("nonexistent") def test_remove_existing_file(self): self.cleaner.add_file("_testfile_") self.make_remote_file("_testfile_") host = self.host assert host.path.isfile("_testfile_") host.remove("_testfile_") assert not host.path.exists("_testfile_") class TestWalk(RealFTPTest): """ Walk the directory tree walk_test ├── dir1 │   ├── dir11 │   └── dir12 │   ├── dir123 │   │   └── file1234 │   ├── file121 │   └── file122 ├── dir2 ├── dir3 │   ├── dir31 │   ├── dir32 -> ../dir1/dir12/dir123 │   ├── file31 │   └── file32 └── file4 and check if the results are the expected ones. """ def _walk_test(self, expected_result, **walk_kwargs): """ Walk the directory and test results. """ # Collect data using `walk`. actual_result = [] for items in self.host.walk(**walk_kwargs): actual_result.append(items) # Compare with expected results. assert len(actual_result) == len(expected_result) for index, _ in enumerate(actual_result): assert actual_result[index] == expected_result[index] def test_walk_topdown(self): # Preparation: build tree in directory `walk_test`. expected_result = [ ("walk_test", ["dir1", "dir2", "dir3"], ["file4"]), # ("walk_test/dir1", ["dir11", "dir12"], []), # ("walk_test/dir1/dir11", [], []), # ("walk_test/dir1/dir12", ["dir123"], ["file121", "file122"]), # ("walk_test/dir1/dir12/dir123", [], ["file1234"]), # ("walk_test/dir2", [], []), # ("walk_test/dir3", ["dir31", "dir32"], ["file31", "file32"]), # ("walk_test/dir3/dir31", [], []), ] self._walk_test(expected_result, top="walk_test") def test_walk_depth_first(self): # Preparation: build tree in directory `walk_test` expected_result = [ ("walk_test/dir1/dir11", [], []), # ("walk_test/dir1/dir12/dir123", [], ["file1234"]), # ("walk_test/dir1/dir12", ["dir123"], ["file121", "file122"]), # ("walk_test/dir1", ["dir11", "dir12"], []), # ("walk_test/dir2", [], []), # ("walk_test/dir3/dir31", [], []), # ("walk_test/dir3", ["dir31", "dir32"], ["file31", "file32"]), # ("walk_test", ["dir1", "dir2", "dir3"], ["file4"]), ] self._walk_test(expected_result, top="walk_test", topdown=False) def test_walk_following_links(self): # Preparation: build tree in directory `walk_test`. expected_result = [ ("walk_test", ["dir1", "dir2", "dir3"], ["file4"]), # ("walk_test/dir1", ["dir11", "dir12"], []), # ("walk_test/dir1/dir11", [], []), # ("walk_test/dir1/dir12", ["dir123"], ["file121", "file122"]), # ("walk_test/dir1/dir12/dir123", [], ["file1234"]), # ("walk_test/dir2", [], []), # ("walk_test/dir3", ["dir31", "dir32"], ["file31", "file32"]), # ("walk_test/dir3/dir31", [], []), # ("walk_test/dir3/dir32", [], ["file1234"]), ] self._walk_test(expected_result, top="walk_test", followlinks=True) class TestRename(RealFTPTest): def test_rename(self): host = self.host # Make sure the target of the renaming operation is removed. self.cleaner.add_file("_testfile2_") self.make_remote_file("_testfile1_") host.rename(pathlib.Path("_testfile1_"), "_testfile2_") assert not host.path.exists("_testfile1_") assert host.path.exists(pathlib.Path("_testfile2_")) def test_rename_with_spaces_in_directory(self): host = self.host dir_name = "_dir with spaces_" self.cleaner.add_dir(dir_name) host.mkdir(pathlib.Path(dir_name)) self.make_remote_file(dir_name + "/testfile1") host.rename(dir_name + "/testfile1", pathlib.Path(dir_name + "/testfile2")) assert not host.path.exists(dir_name + "/testfile1") assert host.path.exists(dir_name + "/testfile2") class TestStat(RealFTPTest): def test_stat(self): host = self.host dir_name = "_testdir_" file_name = host.path.join(dir_name, "_nonempty_") # Make a directory and a file in it. self.cleaner.add_dir(dir_name) host.mkdir(dir_name) with host.open(file_name, "wb") as fobj: fobj.write(b"abc\x12\x34def\t") # Do some stats # - dir dir_stat = host.stat(dir_name) assert isinstance(dir_stat._st_name, str) assert host.listdir(dir_name) == ["_nonempty_"] assert host.path.isdir(dir_name) assert not host.path.isfile(dir_name) assert not host.path.islink(dir_name) # - file file_stat = host.stat(file_name) assert isinstance(file_stat._st_name, str) assert not host.path.isdir(file_name) assert host.path.isfile(file_name) assert not host.path.islink(file_name) assert host.path.getsize(file_name) == 9 # - file's modification time host.synchronize_times() # The returned server mtime is supposed to be converted to UTC, so # there should be only a small difference between server and client # time. Arbitrarily allow two minutes here to account for limited time # precision from parsing the directory. server_mtime = host.path.getmtime(file_name) client_mtime = ( datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).timestamp() ) assert not (client_mtime - server_mtime > 120) def test_issomething_for_nonexistent_directory(self): host = self.host # Check if we get the right results if even the containing directory # doesn't exist (see ticket #66). nonexistent_path = "/nonexistent/nonexistent" assert not host.path.isdir(nonexistent_path) assert not host.path.isfile(nonexistent_path) assert not host.path.islink(nonexistent_path) def test_special_broken_link(self): # Test for ticket #39. host = self.host broken_link_name = os.path.join("dir_with_broken_link", "nonexistent") assert host.lstat(broken_link_name)._st_target == "../nonexistent/nonexistent" assert not host.path.isdir(broken_link_name) assert not host.path.isfile(broken_link_name) assert host.path.islink(broken_link_name) def test_concurrent_access(self): self.make_remote_file("_testfile_") with ftputil.FTPHost( *self.login_data, session_factory=DEFAULT_SESSION_FACTORY ) as host1: with ftputil.FTPHost( *self.login_data, session_factory=DEFAULT_SESSION_FACTORY ) as host2: stat_result1 = host1.stat("_testfile_") stat_result2 = host2.stat("_testfile_") assert stat_result1 == stat_result2 host2.remove("_testfile_") # Can still get the result via `host1` stat_result1 = host1.stat("_testfile_") assert stat_result1 == stat_result2 # Stat'ing on `host2` gives an exception. with pytest.raises(ftputil.error.PermanentError): host2.stat("_testfile_") # Stat'ing on `host1` after invalidation absolute_path = host1.path.join(host1.getcwd(), "_testfile_") host1.stat_cache.invalidate(absolute_path) with pytest.raises(ftputil.error.PermanentError): host1.stat("_testfile_") def test_cache_auto_resizing(self): """ Test if the cache is resized appropriately. """ host = self.host cache = host.stat_cache._cache # Make sure the cache size isn't adjusted towards smaller values. unused_entries = host.listdir("walk_test") assert cache.size == ftputil.stat_cache.StatCache._DEFAULT_CACHE_SIZE # Make the cache very small initially and see if it gets resized. cache.size = 2 entries = host.listdir("walk_test") # The adjusted cache size should be larger or equal to the number of # items in `walk_test` and its parent directory. The latter is read # implicitly upon `listdir`'s `isdir` call. expected_min_cache_size = max(len(host.listdir(host.curdir)), len(entries)) assert cache.size >= expected_min_cache_size class TestUploadAndDownload(RealFTPTest): """ Test upload and download (including time shift test). """ def test_time_shift(self): self.host.synchronize_times() assert self.host.time_shift() == EXPECTED_TIME_SHIFT @pytest.mark.slow_test def test_upload(self): host = self.host host.synchronize_times() local_file = "_local_file_" remote_file = "_remote_file_" # Make local file to upload. self.make_local_file() # Wait, else small time differences between client and server actually # could trigger the update. time.sleep(65) try: self.cleaner.add_file(remote_file) host.upload(local_file, remote_file) # Retry; shouldn't be uploaded uploaded = host.upload_if_newer(local_file, remote_file) assert uploaded is False # Rewrite the local file. self.make_local_file() # Retry; should be uploaded now uploaded = host.upload_if_newer(local_file, remote_file) assert uploaded is True finally: # Clean up os.unlink(local_file) @pytest.mark.slow_test def test_download(self): host = self.host host.synchronize_times() local_file = "_local_file_" remote_file = "_remote_file_" # Make a remote file. self.make_remote_file(remote_file) # File should be downloaded as it's not present yet. downloaded = host.download_if_newer(remote_file, local_file) assert downloaded is True try: # If the remote file, taking the datetime precision into account, # _might_ be newer, the file will be downloaded again. To prevent # this, wait a bit over a minute (the remote precision), then # "touch" the local file. time.sleep(65) # Create empty file. with open(local_file, "w") as fobj: pass # Local file is present and newer, so shouldn't download. downloaded = host.download_if_newer(remote_file, local_file) assert downloaded is False # Re-make the remote file. self.make_remote_file(remote_file) # Local file is present but possibly older (taking the possible # deviation because of the precision into account), so should # download. downloaded = host.download_if_newer(remote_file, local_file) assert downloaded is True finally: # Clean up. os.unlink(local_file) def test_callback_with_transfer(self): host = self.host FILE_NAME = "large_file" # Default chunk size as in `FTPHost.copyfileobj` MAX_COPY_CHUNK_SIZE = ftputil.file_transfer.MAX_COPY_CHUNK_SIZE file_size = host.path.getsize(FILE_NAME) chunk_count, _ = divmod(file_size, MAX_COPY_CHUNK_SIZE) # Add one chunk for remainder. chunk_count += 1 # Define a callback that just collects all data passed to it. transferred_chunks_list = [] def test_callback(chunk): transferred_chunks_list.append(chunk) try: host.download(FILE_NAME, FILE_NAME, callback=test_callback) # Construct a list of data chunks we expect. expected_chunks_list = [] with open(FILE_NAME, "rb") as downloaded_fobj: while True: chunk = downloaded_fobj.read(MAX_COPY_CHUNK_SIZE) if not chunk: break expected_chunks_list.append(chunk) # Examine data collected by callback function. assert len(transferred_chunks_list) == chunk_count assert transferred_chunks_list == expected_chunks_list finally: os.unlink(FILE_NAME) class TestFTPFiles(RealFTPTest): def test_only_closed_children(self): REMOTE_FILE_NAME = "CONTENTS" host = self.host with host.open(REMOTE_FILE_NAME, "rb") as file_obj1: # Create empty file and close it. with host.open(REMOTE_FILE_NAME, "rb") as file_obj2: pass # This should re-use the second child because the first isn't # closed but the second is. with host.open(REMOTE_FILE_NAME, "rb") as file_obj: assert len(host._children) == 2 assert file_obj._host is host._children[1] def test_no_timed_out_children(self): REMOTE_FILE_NAME = "CONTENTS" host = self.host # Implicitly create child host object. with host.open(REMOTE_FILE_NAME, "rb") as file_obj1: pass # Monkey-patch file to simulate an FTP server timeout below. def timed_out_pwd(): raise ftplib.error_temp("simulated timeout") file_obj1._host._session.pwd = timed_out_pwd # Try to get a file - which shouldn't be the timed-out file. with host.open(REMOTE_FILE_NAME, "rb") as file_obj2: assert file_obj1 is not file_obj2 # Re-use closed and not timed-out child session. with host.open(REMOTE_FILE_NAME, "rb") as file_obj3: pass assert file_obj2 is file_obj3 def test_no_delayed_226_children(self): REMOTE_FILE_NAME = "CONTENTS" host = self.host # Implicitly create child host object. with host.open(REMOTE_FILE_NAME, "rb") as file_obj1: pass # Monkey-patch file to simulate an FTP server timeout below. def timed_out_pwd(): raise ftplib.error_reply("delayed 226 reply") file_obj1._host._session.pwd = timed_out_pwd # Try to get a file - which shouldn't be the timed-out file. with host.open(REMOTE_FILE_NAME, "rb") as file_obj2: assert file_obj1 is not file_obj2 # Re-use closed and not timed-out child session. with host.open(REMOTE_FILE_NAME, "rb") as file_obj3: pass assert file_obj2 is file_obj3 class TestChmod(RealFTPTest): def assert_mode(self, path, expected_mode): """ Return an integer containing the allowed bits in the mode change command. The `FTPHost` object to test against is `self.host`. """ full_mode = self.host.stat(path).st_mode # Remove flags we can't set via `chmod`. Allowed flags according to # Python documentation: https://docs.python.org/library/stat.html allowed_flags = [ stat.S_ISUID, stat.S_ISGID, stat.S_ENFMT, stat.S_ISVTX, stat.S_IREAD, stat.S_IWRITE, stat.S_IEXEC, stat.S_IRWXU, stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR, stat.S_IRWXG, stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP, stat.S_IRWXO, stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH, ] allowed_mask = functools.reduce(operator.or_, allowed_flags) mode = full_mode & allowed_mask assert mode == expected_mode, "mode {0:o} != {1:o}".format(mode, expected_mode) def test_chmod_existing_directory(self): host = self.host host.mkdir("_test dir_") self.cleaner.add_dir("_test dir_") # Set/get mode of a directory. host.chmod("_test dir_", 0o757) self.assert_mode("_test dir_", 0o757) # Set/get mode in nested directory. host.mkdir("_test dir_/nested_dir") self.cleaner.add_dir("_test dir_/nested_dir") host.chmod("_test dir_/nested_dir", 0o757) self.assert_mode("_test dir_/nested_dir", 0o757) def test_chmod_existing_file(self): host = self.host host.mkdir("_test dir_") self.cleaner.add_dir("_test dir_") # Set/get mode on a file. file_name = host.path.join("_test dir_", "_testfile_") self.make_remote_file(file_name) host.chmod(file_name, 0o646) self.assert_mode(file_name, 0o646) def test_chmod_nonexistent_path(self): # Set/get mode of a non-existing item. with pytest.raises(ftputil.error.PermanentError): self.host.chmod("nonexistent", 0o757) def test_cache_invalidation(self): host = self.host host.mkdir("_test dir_") self.cleaner.add_dir("_test dir_") # Make sure the mode is in the cache. unused_stat_result = host.stat("_test dir_") # Set/get mode of the directory. host.chmod("_test dir_", 0o757) self.assert_mode("_test dir_", 0o757) # Set/get mode on a file. file_name = host.path.join("_test dir_", "_testfile_") self.make_remote_file(file_name) # Make sure the mode is in the cache. unused_stat_result = host.stat(file_name) host.chmod(file_name, 0o646) self.assert_mode(file_name, 0o646) class TestRestArgument(RealFTPTest): TEST_FILE_NAME = "rest_test" def setup_method(self, method): super().setup_method(method) # Write test file. with self.host.open(self.TEST_FILE_NAME, "wb") as fobj: fobj.write(b"abcdefghijkl") self.cleaner.add_file(self.TEST_FILE_NAME) def test_for_reading(self): """ If a `rest` argument is passed to `open`, the following read operation should start at the byte given by `rest`. """ with self.host.open(self.TEST_FILE_NAME, "rb", rest=3) as fobj: data = fobj.read() assert data == b"defghijkl" def test_for_writing(self): """ If a `rest` argument is passed to `open`, the following write operation should start writing at the byte given by `rest`. """ with self.host.open(self.TEST_FILE_NAME, "wb", rest=3) as fobj: fobj.write(b"123") with self.host.open(self.TEST_FILE_NAME, "rb") as fobj: data = fobj.read() assert data == b"abc123" def test_invalid_read_from_text_file(self): """ If the `rest` argument is used for reading from a text file, a `CommandNotImplementedError` should be raised. """ with pytest.raises(ftputil.error.CommandNotImplementedError): self.host.open(self.TEST_FILE_NAME, "r", rest=3) def test_invalid_write_to_text_file(self): """ If the `rest` argument is used for reading from a text file, a `CommandNotImplementedError` should be raised. """ with pytest.raises(ftputil.error.CommandNotImplementedError): self.host.open(self.TEST_FILE_NAME, "w", rest=3) # There are no tests for reading and writing beyond the end of a file. For # example, if the remote file is 10 bytes long and # `open(remote_file, "rb", rest=100)` is used, the server may return an # error status code or not. # # The server I use for testing returns a 554 status when attempting to # _read_ beyond the end of the file. On the other hand, if attempting to # _write_ beyond the end of the file, the server accepts the request, but # starts writing after the end of the file, i. e. appends to the file. # # Instead of expecting certain responses that may differ between server # implementations, I leave the bahavior for too large `rest` arguments # undefined. In practice, this shouldn't be a problem because the `rest` # argument should only be used for error recovery, and in this case a valid # byte count for the `rest` argument should be known. class TestOther(RealFTPTest): def test_open_for_reading(self): # Test for issues #17 and #51, # http://ftputil.sschwarzer.net/trac/ticket/17 and # http://ftputil.sschwarzer.net/trac/ticket/51 . file1 = self.host.open("large_file", "rb") time.sleep(1) # Depending on the FTP server, this might return a status code # unexpected by `ftplib` or block the socket connection until a # server-side timeout. file1.close() def test_subsequent_reading(self): # Open a file for reading. with self.host.open("CONTENTS", "rb") as file1: pass # Make sure that there are no problems if the connection is reused. with self.host.open("CONTENTS", "rb") as file2: pass assert file1._session is file2._session def test_names_with_spaces(self): # Test if directories and files with spaces in their names can be used. host = self.host assert host.path.isdir("dir with spaces") assert host.listdir("dir with spaces") == [ "second dir", "some file", "some_file", ] assert host.path.isdir("dir with spaces/second dir") assert host.path.isfile("dir with spaces/some_file") assert host.path.isfile("dir with spaces/some file") def test_synchronize_times_without_write_access(self): """ Test failing synchronization because of non-writable directory. """ host = self.host # This isn't writable by the ftp account the tests are run under. host.chdir("rootdir1") with pytest.raises(ftputil.error.TimeShiftError): host.synchronize_times() def test_encoding(self): """ Test setting the path encoding. """ for encoding in ["latin-1", "UTF-8"]: factory = ftputil.session.session_factory(port=PORT, encoding=encoding) session = factory(*self.login_data) assert session.encoding == encoding def test_listdir_with_non_ascii_byte_string(self): """ `listdir` should accept byte strings with non-ASCII characters and return non-ASCII characters in directory or file names. """ host = self.host path = "äbc".encode("UTF-8") names = host.listdir(path) assert names[0] == b"file1" assert names[1] == "file1_ö".encode("UTF-8") def test_listdir_with_non_ascii_unicode_string(self): """ `listdir` should accept unicode strings with non-ASCII characters and return non-ASCII characters in directory or file names. """ host = self.host # `ftplib` under Python 3 only works correctly if the unicode strings # are decoded from the `ftplib` default encoding. For Python 3.9 and up # the `encode`/`decode` combination is a no-op. path = "äbc".encode("UTF-8").decode( ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING ) names = host.listdir(path) assert names[0] == "file1" assert names[1] == "file1_ö".encode("UTF-8").decode( ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING ) @pytest.mark.skipif( ftputil.path_encoding.RUNNING_UNDER_PY39_AND_UP, reason="test applies only to `FTPHost` objects using 'latin-1' path encoding", ) def test_path_with_non_latin1_unicode_string_below_python_3_9(self): """ ftputil operations shouldn't accept file paths with non-latin1 characters. """ # Use some musical symbols. These are certainly not latin1. ;-) path = "𝄞𝄢" # `UnicodeEncodeError` is also the exception that `ftplib` raises if it # gets a non-latin1 path. with pytest.raises(UnicodeEncodeError): self.host.mkdir(path) def test_list_a_option(self): # For this test to pass, the server must _not_ list "hidden" files by # default but instead only when the `LIST` `-a` option is used. host = self.host assert not host.use_list_a_option directory_entries = host.listdir(host.curdir) assert ".hidden" not in directory_entries # Switch on showing of hidden paths. host.use_list_a_option = True directory_entries = host.listdir(host.curdir) assert ".hidden" in directory_entries def _make_objects_to_be_garbage_collected(self): for _ in range(10): with ftputil.FTPHost( *self.login_data, session_factory=DEFAULT_SESSION_FACTORY ) as host: for _ in range(10): unused_stat_result = host.stat("CONTENTS") with host.open("CONTENTS") as fobj: unused_data = fobj.read() def test_garbage_collection(self): """Test whether there are cycles which prevent garbage collection.""" gc.collect() objects_before_test = len(gc.garbage) self._make_objects_to_be_garbage_collected() gc.collect() objects_after_test = len(gc.garbage) assert not objects_after_test - objects_before_test ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7959645 ftputil-5.0.3/test/test_session.py0000664000175000017500000001245600000000000016270 0ustar00schwaschwa# Copyright (C) 2014-2021, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. """ Unit tests for session factory helpers. """ import ftplib import sys import ftputil.path_encoding import ftputil.session import ftputil.tool # Inherit from `ftplib.FTP` to get past the subclass check in # `ftputil.session.session_factory`. class MockSession(ftplib.FTP): """ Mock session base class to determine if all expected calls have happened. """ encoding = ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING def __init__(self, encoding=None): self.calls = [] if encoding is not None: self.encoding = encoding def add_call(self, *args): self.calls.append(args) def connect(self, host, port): self.add_call("connect", host, port) def set_debuglevel(self, value): self.add_call("set_debuglevel", value) def login(self, user, password): self.add_call("login", user, password) def set_pasv(self, flag): self.add_call("set_pasv", flag) class EncryptedMockSession(MockSession): def auth_tls(self): self.add_call("auth_tls") def prot_p(self): self.add_call("prot_p") class TestSessionFactory: """ Test if session factories created by `ftputil.session.session_factory` trigger the expected calls. """ def test_defaults(self): """ Test defaults (apart from base class). """ factory = ftputil.session.session_factory(base_class=MockSession) session = factory("host", "user", "password") assert session.calls == [("connect", "host", 21), ("login", "user", "password")] def test_different_port(self): """ Test setting the command channel port with `port`. """ factory = ftputil.session.session_factory(base_class=MockSession, port=2121) session = factory("host", "user", "password") assert session.calls == [ ("connect", "host", 2121), ("login", "user", "password"), ] def test_use_passive_mode(self): """ Test explicitly setting passive/active mode with `use_passive_mode`. """ # Passive mode factory = ftputil.session.session_factory( base_class=MockSession, use_passive_mode=True ) session = factory("host", "user", "password") assert session.calls == [ ("connect", "host", 21), ("login", "user", "password"), ("set_pasv", True), ] # Active mode factory = ftputil.session.session_factory( base_class=MockSession, use_passive_mode=False ) session = factory("host", "user", "password") assert session.calls == [ ("connect", "host", 21), ("login", "user", "password"), ("set_pasv", False), ] def test_encrypt_data_channel(self): """ Test request to call `prot_p` with `encrypt_data_channel`. """ # With encrypted data channel (default for encrypted session). factory = ftputil.session.session_factory(base_class=EncryptedMockSession) session = factory("host", "user", "password") assert session.calls == [ ("connect", "host", 21), ("login", "user", "password"), ("prot_p",), ] # factory = ftputil.session.session_factory( base_class=EncryptedMockSession, encrypt_data_channel=True ) session = factory("host", "user", "password") assert session.calls == [ ("connect", "host", 21), ("login", "user", "password"), ("prot_p",), ] # Without encrypted data channel. factory = ftputil.session.session_factory( base_class=EncryptedMockSession, encrypt_data_channel=False ) session = factory("host", "user", "password") assert session.calls == [("connect", "host", 21), ("login", "user", "password")] def test_encoding(self): """ Test setting the default encoding and a custom encoding. """ # Default encoding factory = ftputil.session.session_factory( base_class=MockSession, ) session = factory("host", "user", "password") assert session.calls == [ ("connect", "host", 21), ("login", "user", "password"), ] assert session.encoding == ftputil.path_encoding.FTPLIB_DEFAULT_ENCODING # Custom encoding factory = ftputil.session.session_factory( base_class=MockSession, encoding="UTF-8", ) session = factory("host", "user", "password") assert session.calls == [ ("connect", "host", 21), ("login", "user", "password"), ] assert session.encoding == "UTF-8" def test_debug_level(self): """ Test setting the debug level on the session. """ factory = ftputil.session.session_factory(base_class=MockSession, debug_level=1) session = factory("host", "user", "password") assert session.calls == [ ("connect", "host", 21), ("set_debuglevel", 1), ("login", "user", "password"), ] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7959645 ftputil-5.0.3/test/test_stat.py0000664000175000017500000011412600000000000015555 0ustar00schwaschwa# Copyright (C) 2003-2019, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. import datetime import ftplib import stat import time import freezegun import pytest import ftputil import ftputil.error import ftputil.stat from ftputil.stat import MINUTE_PRECISION, DAY_PRECISION, UNKNOWN_PRECISION from test import test_base from test import scripted_session Call = scripted_session.Call # Special value to handle special case of datetimes before the epoch. EPOCH = time.gmtime(0)[:6] def stat_tuple_to_seconds(t): """ Return a float number representing the UTC timestamp from the six-element tuple `t`. """ assert len(t) == 6, "need a six-element tuple (year, month, day, hour, min, sec)" # Do _not_ apply `time.mktime` to the `EPOCH` value below. On some # platforms (e. g. Windows) this might cause an `OverflowError`. if t == EPOCH: return 0.0 else: return datetime.datetime(*t, tzinfo=datetime.timezone.utc).timestamp() class TestParsers: # # Helper methods # def _test_valid_lines(self, parser_class, lines, expected_stat_results): parser = parser_class() for line, expected_stat_result in zip(lines, expected_stat_results): # Convert to list to compare with the list `expected_stat_results`. parse_result = parser.parse_line(line, time_shift=5 * 60 * 60) stat_result = list(parse_result) + [ parse_result._st_mtime_precision, parse_result._st_name, parse_result._st_target, ] # Convert time tuple to seconds. expected_stat_result[8] = stat_tuple_to_seconds(expected_stat_result[8]) # Compare lists. assert stat_result == expected_stat_result def _test_invalid_lines(self, parser_class, lines): parser = parser_class() for line in lines: with pytest.raises(ftputil.error.ParserError): parser.parse_line(line) def _expected_year(self): """ Return the expected year for the second line in the listing in `test_valid_unix_lines`. """ # If in this year it's after Dec 19, 23:11, use the current year, else # use the previous year. This datetime value corresponds to the # hard-coded value in the string lists below. client_datetime = datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc ) server_datetime_candidate = client_datetime.replace( month=12, day=19, hour=23, minute=11, second=0 ) if server_datetime_candidate > client_datetime: return server_datetime_candidate.year - 1 else: return server_datetime_candidate.year # # Unix parser # def test_valid_unix_lines(self): lines = [ "drwxr-sr-x 2 45854 200 512 May 4 2000 " "chemeng link -> chemeng target", # The year value for this line will change with the actual time. "-rw-r--r-- 1 45854 200 4604 Dec 19 23:11 index.html", "drwxr-sr-x 2 45854 200 512 Jan 01 2000 os2", "---------- 2 45854 200 512 May 29 2000 some_file", "lrwxrwxrwx 2 45854 200 512 May 29 2000 osup -> " "../os2", ] # Note that the time shift is also subtracted from the datetimes that # have only day precision, i. e. a year but no time. expected_stat_results = [ [ 17901, None, None, 2, "45854", "200", 512, None, (2000, 5, 3, 19, 0, 0), None, DAY_PRECISION, "chemeng link", "chemeng target", ], [ 33188, None, None, 1, "45854", "200", 4604, None, (self._expected_year(), 12, 19, 18, 11, 0), None, MINUTE_PRECISION, "index.html", None, ], [ 17901, None, None, 2, "45854", "200", 512, None, (1999, 12, 31, 19, 0, 0), None, DAY_PRECISION, "os2", None, ], [ 32768, None, None, 2, "45854", "200", 512, None, (2000, 5, 28, 19, 0, 0), None, DAY_PRECISION, "some_file", None, ], [ 41471, None, None, 2, "45854", "200", 512, None, (2000, 5, 28, 19, 0, 0), None, DAY_PRECISION, "osup", "../os2", ], ] self._test_valid_lines(ftputil.stat.UnixParser, lines, expected_stat_results) def test_alternative_unix_format(self): # See http://ftputil.sschwarzer.net/trac/ticket/12 for a description # for the need for an alternative format. lines = [ "drwxr-sr-x 2 200 512 May 4 2000 " "chemeng link -> chemeng target", # The year value for this line will change with the actual time. "-rw-r--r-- 1 200 4604 Dec 19 23:11 index.html", "drwxr-sr-x 2 200 512 May 29 2000 os2", "lrwxrwxrwx 2 200 512 May 29 2000 osup -> ../os2", ] expected_stat_results = [ [ 17901, None, None, 2, None, "200", 512, None, (2000, 5, 3, 19, 0, 0), None, DAY_PRECISION, "chemeng link", "chemeng target", ], [ 33188, None, None, 1, None, "200", 4604, None, (self._expected_year(), 12, 19, 18, 11, 0), None, MINUTE_PRECISION, "index.html", None, ], [ 17901, None, None, 2, None, "200", 512, None, (2000, 5, 28, 19, 0, 0), None, DAY_PRECISION, "os2", None, ], [ 41471, None, None, 2, None, "200", 512, None, (2000, 5, 28, 19, 0, 0), None, DAY_PRECISION, "osup", "../os2", ], ] self._test_valid_lines(ftputil.stat.UnixParser, lines, expected_stat_results) def test_pre_epoch_times_for_unix(self): # See http://ftputil.sschwarzer.net/trac/ticket/83 . # `mirrors.ibiblio.org` returns dates before the "epoch" that cause an # `OverflowError` in `mktime` on some platforms, e. g. Windows. lines = [ "-rw-r--r-- 1 45854 200 4604 May 4 1968 index.html", "-rw-r--r-- 1 45854 200 4604 Dec 31 1969 index.html", "-rw-r--r-- 1 45854 200 4604 May 4 1800 index.html", ] expected_stat_result = [ 33188, None, None, 1, "45854", "200", 4604, None, EPOCH, None, UNKNOWN_PRECISION, "index.html", None, ] # Make shallow copies to avoid converting the time tuple more than once # in _test_valid_lines`. expected_stat_results = [ expected_stat_result[:], expected_stat_result[:], expected_stat_result[:], ] self._test_valid_lines(ftputil.stat.UnixParser, lines, expected_stat_results) def test_invalid_unix_lines(self): lines = [ # Not intended to be parsed. Should have been filtered out by # `ignores_line`. "total 14", # Invalid month abbreviation "drwxr-sr-x 2 45854 200 512 Max 4 2000 chemeng", # Year value isn't an integer "drwxr-sr-x 2 45854 200 512 May 4 abcd chemeng", # Day value isn't an integer "drwxr-sr-x 2 45854 200 512 May ab 2000 chemeng", # Hour value isn't an integer "-rw-r--r-- 1 45854 200 4604 Dec 19 ab:11 index.html", # Minute value isn't an integer "-rw-r--r-- 1 45854 200 4604 Dec 19 23:ab index.html", # Day value too large "drwxr-sr-x 2 45854 200 512 May 32 2000 chemeng", # Ditto, for time instead of year "drwxr-sr-x 2 45854 200 512 May 32 11:22 chemeng", # Incomplete mode "drwxr-sr- 2 45854 200 512 May 4 2000 chemeng", # Invalid first letter in mode "xrwxr-sr-x 2 45854 200 512 May 4 2000 chemeng", # Ditto, plus invalid size value "xrwxr-sr-x 2 45854 200 51x May 4 2000 chemeng", # Is this `os1 -> os2` pointing to `os3`, or `os1` pointing to # `os2 -> os3` or the plain name `os1 -> os2 -> os3`? We don't # know, so we consider the line invalid. "drwxr-sr-x 2 45854 200 512 May 29 2000 " "os1 -> os2 -> os3", # Missing name "-rwxr-sr-x 2 45854 200 51x May 4 2000 ", ] self._test_invalid_lines(ftputil.stat.UnixParser, lines) # # Microsoft parser # def test_valid_ms_lines_two_digit_year(self): lines = [ "07-27-01 11:16AM Test", "10-23-95 03:25PM WindowsXP", "07-17-00 02:08PM 12266720 test.exe", "07-17-09 12:08AM 12266720 test.exe", "07-17-09 12:08PM 12266720 test.exe", ] expected_stat_results = [ [ 16640, None, None, None, None, None, None, None, (2001, 7, 27, 6, 16, 0), None, MINUTE_PRECISION, "Test", None, ], [ 16640, None, None, None, None, None, None, None, (1995, 10, 23, 10, 25, 0), None, MINUTE_PRECISION, "WindowsXP", None, ], [ 33024, None, None, None, None, None, 12266720, None, (2000, 7, 17, 9, 8, 0), None, MINUTE_PRECISION, "test.exe", None, ], [ 33024, None, None, None, None, None, 12266720, None, (2009, 7, 16, 19, 8, 0), None, MINUTE_PRECISION, "test.exe", None, ], [ 33024, None, None, None, None, None, 12266720, None, (2009, 7, 17, 7, 8, 0), None, MINUTE_PRECISION, "test.exe", None, ], ] self._test_valid_lines(ftputil.stat.MSParser, lines, expected_stat_results) def test_valid_ms_lines_four_digit_year(self): # See http://ftputil.sschwarzer.net/trac/ticket/67 lines = [ "10-19-2012 03:13PM SYNCDEST", "10-19-2012 03:13PM SYNCSOURCE", "10-19-1968 03:13PM SYNC", ] expected_stat_results = [ [ 16640, None, None, None, None, None, None, None, (2012, 10, 19, 10, 13, 0), None, MINUTE_PRECISION, "SYNCDEST", None, ], [ 16640, None, None, None, None, None, None, None, (2012, 10, 19, 10, 13, 0), None, MINUTE_PRECISION, "SYNCSOURCE", None, ], [ 16640, None, None, None, None, None, None, None, EPOCH, None, UNKNOWN_PRECISION, "SYNC", None, ], ] self._test_valid_lines(ftputil.stat.MSParser, lines, expected_stat_results) def test_invalid_ms_lines(self): lines = [ # Neither "" nor a size present "07-27-01 11:16AM Test", # "AM"/"PM" missing "07-17-00 02:08 12266720 test.exe", # Year not an int "07-17-ab 02:08AM 12266720 test.exe", # Month not an int "ab-17-00 02:08AM 12266720 test.exe", # Day not an int "07-ab-00 02:08AM 12266720 test.exe", # Hour not an int "07-17-00 ab:08AM 12266720 test.exe", # Invalid size value "07-17-00 02:08AM 1226672x test.exe", ] self._test_invalid_lines(ftputil.stat.MSParser, lines) # # The following code checks if the decision logic in the Unix line parser # for determining the year works. # def dir_line(self, datetime_): """ Return a directory line as from a Unix FTP server. Most of the contents are fixed, but the timestamp is made from `time_float` (seconds since the epoch, as from `time.time()`). """ line_template = "-rw-r--r-- 1 45854 200 4604 {} index.html" datetime_string = datetime_.strftime("%b %d %H:%M") return line_template.format(datetime_string) def assert_equal_times(self, time1, time2): """ Check if both times (seconds since the epoch) are equal. For the purpose of this test, two times are "equal" if they differ no more than one minute from each other. """ abs_difference = abs(time1 - time2) assert abs_difference <= 60.0, "Difference is %s seconds" % abs_difference def _test_time_shift(self, supposed_time_shift, deviation=0.0): """ Check if the stat parser considers the time shift value correctly. `deviation` is the difference between the actual time shift and the supposed time shift, which is rounded to full hours. """ script = [Call("__init__"), Call("pwd", result="/"), Call("close")] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() # Explicitly use Unix format parser here. host._stat._parser = ftputil.stat.UnixParser() host.set_time_shift(supposed_time_shift) server_time = datetime.datetime.utcnow().replace( tzinfo=datetime.timezone.utc ) + datetime.timedelta(seconds=supposed_time_shift + deviation) stat_result = host._stat._parser.parse_line( self.dir_line(server_time), host.time_shift() ) # We expect `st_mtime` in UTC. self.assert_equal_times( stat_result.st_mtime, ( server_time # Convert back to client time. - datetime.timedelta(seconds=supposed_time_shift) ).timestamp(), ) def test_time_shifts(self): """ Test correct year depending on time shift value. """ # 1. test: Client and server share the same time (UTC). This is true if # the directory listing from the server is in UTC. self._test_time_shift(0.0) # 2. test: Server is three hours ahead of client self._test_time_shift(3 * 60 * 60) # Ditto, but with client and server in different years. See ticket #131. with freezegun.freeze_time("2019-12-31 22:37"): self._test_time_shift(3 * 60 * 60) # 3. test: Client is three hours ahead of server self._test_time_shift(-3 * 60 * 60) # 4. test: Server is supposed to be three hours ahead, but is ahead # three hours and one minute self._test_time_shift(3 * 60 * 60, 60) # 5. test: Server is supposed to be three hours ahead, but is ahead # three hours minus one minute self._test_time_shift(3 * 60 * 60, -60) # 6. test: Client is supposed to be three hours ahead, but is ahead # three hours and one minute self._test_time_shift(-3 * 60 * 60, -60) # 7. test: Client is supposed to be three hours ahead, but is ahead # three hours minus one minute self._test_time_shift(-3 * 60 * 60, 60) class TestLstatAndStat: """ Test `FTPHost.lstat` and `FTPHost.stat` (test currently only implemented for Unix server format). """ def test_repr(self): """ Test if the `repr` result looks like a named tuple. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result="drwxr-sr-x 2 45854 200 512 May 4 2000 foo", ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() stat_result = host.stat("/foo") expected_result = ( "StatResult(st_mode=17901, st_ino=None, st_dev=None, " "st_nlink=2, st_uid='45854', st_gid='200', st_size=512, " "st_atime=None, st_mtime=957398400.0, st_ctime=None)" ) assert repr(stat_result) == expected_result def test_failing_lstat(self): """ Test whether `lstat` fails for a nonexistent path. """ # Directory with presumed file item doesn't exist. script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=""), Call("cwd", args=("/",)), # See FIXME comment in `ftputil.stat._Stat._real_lstat` Call("cwd", args=("/",)), Call("cwd", args=("/notthere",), result=ftplib.error_perm), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() with pytest.raises(ftputil.error.PermanentError): host.lstat("/notthere/irrelevant") # Directory exists, but not the file system item in the directory. script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result=test_base.dir_line( mode_string="dr-xr-xr-x", datetime_=datetime.datetime.now(), name="some_dir", ), ), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/some_dir",)), Call("dir", args=("",), result=""), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() with pytest.raises(ftputil.error.PermanentError): host.lstat("/some_dir/notthere") def test_lstat_for_root(self): """ Test `lstat` for `/` . Note: `(l)stat` works by going one directory up and parsing the output of an FTP `LIST` command. Unfortunately, it's not possible to do this for the root directory `/`. """ script = [Call("__init__"), Call("pwd", result="/"), Call("close")] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() with pytest.raises(ftputil.error.RootDirError) as exc_info: host.lstat("/") # `RootDirError` is "outside" the `FTPOSError` hierarchy. assert not isinstance(exc_info.value, ftputil.error.FTPOSError) del exc_info def test_lstat_one_unix_file(self): """ Test `lstat` for a file described in Unix-style format. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result="-rw-r--r-- 1 45854 200 4604 Jan 19 23:11 some_file", ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() stat_result = host.lstat("/some_file") assert oct(stat_result.st_mode) == "0o100644" assert stat_result.st_size == 4604 assert stat_result._st_mtime_precision == 60 def test_lstat_one_ms_file(self): """ Test `lstat` for a file described in DOS-style format. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # First try with unix parser, but this parser can't parse this # line. Call( "dir", args=("",), result="07-17-00 02:08PM 12266720 some_file", ), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # Now try with MS parser. Call( "dir", args=("",), result="07-17-00 02:08PM 12266720 some_file", ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() stat_result = host.lstat("/some_file") assert stat_result._st_name == "some_file" assert stat_result._st_mtime_precision == 60 def test_lstat_one_unix_dir(self): """ Test `lstat` for a directory described in Unix-style format. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result="drwxr-sr-x 6 45854 200 512 Sep 20 1999 some_dir", ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() stat_result = host.lstat("/some_dir") assert oct(stat_result.st_mode) == "0o42755" assert stat_result.st_ino is None assert stat_result.st_dev is None assert stat_result.st_nlink == 6 assert stat_result.st_uid == "45854" assert stat_result.st_gid == "200" assert stat_result.st_size == 512 assert stat_result.st_atime is None assert stat_result.st_mtime == stat_tuple_to_seconds((1999, 9, 20, 0, 0, 0)) assert stat_result.st_ctime is None assert stat_result._st_mtime_precision == 24 * 60 * 60 assert stat_result == ( 17901, None, None, 6, "45854", "200", 512, None, stat_tuple_to_seconds((1999, 9, 20, 0, 0, 0)), None, ) def test_lstat_one_ms_dir(self): """ Test `lstat` for a directory described in DOS-style format. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # First try with unix parser, but this parser can't parse this # line. Call( "dir", args=("",), result="10-23-01 03:25PM some_dir", ), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # Now try with MS parser. Call( "dir", args=("",), result="10-23-01 03:25PM some_dir", ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() stat_result = host.lstat("/some_dir") assert stat_result._st_mtime_precision == 60 def test_lstat_via_stat_module(self): """ Test `lstat` indirectly via `stat` module. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result="drwxr-sr-x 2 45854 200 512 May 4 2000 some_dir", ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() stat_result = host.lstat("/some_dir") assert stat.S_ISDIR(stat_result.st_mode) def test_stat_following_link(self): """ Test `stat` when invoked on a link. """ # Simple link script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result="lrwxrwxrwx 1 45854 200 21 Jan 19 2002 link -> link_target", ), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result="-rw-r--r-- 1 45854 200 4604 Jan 19 23:11 link_target", ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() stat_result = host.stat("/link") assert stat_result.st_size == 4604 # Link pointing to a link dir_lines = ( "lrwxrwxrwx 1 45854 200 7 Jan 19 2002 link_link -> link\n" "lrwxrwxrwx 1 45854 200 14 Jan 19 2002 link -> link_target\n" "-rw-r--r-- 1 45854 200 4604 Jan 19 23:11 link_target" ) # Note that only one `dir` call would be needed in case of an enabled # cache. script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # Look up `/link_link`. Call("dir", args=("",), result=dir_lines), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # Look up `/link`. Call("dir", args=("",), result=dir_lines), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # Look up `/link_target`. Call("dir", args=("",), result=dir_lines), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() stat_result = host.stat("/link_link") assert stat_result.st_size == 4604 # Recursive link structure dir_lines = ( "lrwxrwxrwx 1 45854 200 7 Jan 19 2002 bad_link1 -> bad_link2\n" "lrwxrwxrwx 1 45854 200 14 Jan 19 2002 bad_link2 -> bad_link1" ) script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # This dir finds the `bad_link1` name requested in the `stat` call. Call("dir", args=("",), result=dir_lines), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # Look up link target `bad_link2`. Call("dir", args=("",), result=dir_lines), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), # FIXME: `stat` looks up the link target pointed to by `bad_link2`, # which is `bad_link1`. Only here ftputil notices the recursive # link chain. Obviously the start of the link chain hadn't been # stored in `visited_paths` (see also ticket #108). Call("dir", args=("",), result=dir_lines), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() with pytest.raises(ftputil.error.PermanentError): host.stat("bad_link1") # # Test automatic switching of Unix/MS parsers # def test_parser_switching_with_permanent_error(self): """ Test non-switching of parser format with `PermanentError`. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result="10-23-01 03:25PM home" ), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result="10-23-01 03:25PM home" ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() assert host._stat._allow_parser_switching is True # With these directory contents, we get a `ParserError` for the # Unix parser first, so `_allow_parser_switching` can be switched # off no matter whether we got a `PermanentError` afterward or not. with pytest.raises(ftputil.error.PermanentError): host.lstat("/nonexistent") assert host._stat._allow_parser_switching is False def test_parser_switching_default_to_unix(self): """ Test non-switching of parser format; stay with Unix. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result="-rw-r--r-- 1 45854 200 4604 Jan 19 23:11 some_file", ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() assert host._stat._allow_parser_switching is True assert host._stat._allow_parser_switching is True assert isinstance(host._stat._parser, ftputil.stat.UnixParser) stat_result = host.lstat("some_file") # The Unix parser worked, so keep it. assert isinstance(host._stat._parser, ftputil.stat.UnixParser) assert host._stat._allow_parser_switching is False def test_parser_switching_to_ms(self): """ Test switching of parser from Unix to MS format. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result="07-17-00 02:08PM 12266720 some_file", ), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result="07-17-00 02:08PM 12266720 some_file", ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() assert host._stat._allow_parser_switching is True assert isinstance(host._stat._parser, ftputil.stat.UnixParser) # Parsing the directory `/` with the Unix parser fails, so switch # to the MS parser. stat_result = host.lstat("/some_file") assert isinstance(host._stat._parser, ftputil.stat.MSParser) assert host._stat._allow_parser_switching is False assert stat_result._st_name == "some_file" assert stat_result.st_size == 12266720 def test_parser_switching_regarding_empty_dir(self): """Test switching of parser if a directory is empty.""" script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=""), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() assert host._stat._allow_parser_switching is True # When the directory we're looking into doesn't give us any lines # we can't decide whether the first parser worked, because it # wasn't applied. So keep the parser for now. result = host.listdir("/") assert result == [] assert host._stat._allow_parser_switching is True assert isinstance(host._stat._parser, ftputil.stat.UnixParser) class TestListdir: """ Test `FTPHost.listdir`. """ def test_failing_listdir(self): """ Test failing `FTPHost.listdir`. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call("dir", args=("",), result=""), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() with pytest.raises(ftputil.error.PermanentError): host.listdir("notthere") def test_succeeding_listdir(self): """ Test succeeding `FTPHost.listdir`. """ script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result="drwxr-sr-x 2 45854 200 512 Jan 3 17:17 download\n" "drwxr-sr-x 2 45854 200 512 Jul 30 17:14 dir with spaces\n" "lrwxrwxrwx 2 45854 200 6 May 29 2000 link -> ../link_target\n" "-rw-r--r-- 1 45854 200 4604 Jan 19 23:11 index.html", ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: host.stat_cache.disable() remote_file_list = host.listdir(".") # Do we have all expected "files"? assert len(remote_file_list) == 4 expected_names = ["download", "dir with spaces", "link", "index.html"] for name in expected_names: assert name in remote_file_list assert len(host.stat_cache) == 0 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7959645 ftputil-5.0.3/test/test_stat_cache.py0000664000175000017500000001010500000000000016670 0ustar00schwaschwa# Copyright (C) 2006-2018, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. import time import pytest import ftputil.error import ftputil.stat_cache from test import scripted_session from test import test_base Call = scripted_session.Call class TestStatCache: def setup_method(self, method): self.cache = ftputil.stat_cache.StatCache() def test_get_set(self): with pytest.raises(ftputil.error.CacheMissError): self.cache.__getitem__("/path") self.cache["/path"] = "test" assert self.cache["/path"] == "test" def test_invalidate(self): # Don't raise a `CacheMissError` for missing paths self.cache.invalidate("/path") self.cache["/path"] = "test" self.cache.invalidate("/path") assert len(self.cache) == 0 def test_clear(self): self.cache["/path1"] = "test1" self.cache["/path2"] = "test2" self.cache.clear() assert len(self.cache) == 0 def test_contains(self): self.cache["/path1"] = "test1" assert "/path1" in self.cache assert "/path2" not in self.cache def test_len(self): assert len(self.cache) == 0 self.cache["/path1"] = "test1" self.cache["/path2"] = "test2" assert len(self.cache) == 2 def test_resize(self): self.cache.resize(100) # Don't grow the cache beyond it's set size. for i in range(150): self.cache["/{0:d}".format(i)] = i assert len(self.cache) == 100 def test_max_age1(self): """ Set expiration after setting a cache item. """ self.cache["/path1"] = "test1" # Expire after one second self.cache.max_age = 1 time.sleep(0.5) # Should still be present assert self.cache["/path1"] == "test1" time.sleep(0.6) # Should have expired (_setting_ the cache counts) with pytest.raises(ftputil.error.CacheMissError): self.cache.__getitem__("/path1") def test_max_age2(self): """ Set expiration before setting a cache item. """ # Expire after one second self.cache.max_age = 1 self.cache["/path1"] = "test1" time.sleep(0.5) # Should still be present assert self.cache["/path1"] == "test1" time.sleep(0.6) # Should have expired (_setting_ the cache counts) with pytest.raises(ftputil.error.CacheMissError): self.cache.__getitem__("/path1") def test_disabled(self): self.cache["/path1"] = "test1" self.cache.disable() self.cache["/path2"] = "test2" with pytest.raises(ftputil.error.CacheMissError): self.cache.__getitem__("/path1") with pytest.raises(ftputil.error.CacheMissError): self.cache.__getitem__("/path2") assert len(self.cache) == 1 # Don't raise a `CacheMissError` for missing paths. self.cache.invalidate("/path2") def test_cache_size_zero(self): script = [ Call("__init__"), Call("pwd", result="/"), Call("cwd", args=("/",)), Call("cwd", args=("/",)), Call( "dir", args=("",), result="drwxr-sr-x 2 45854 200 512 Jan 3 17:17 download\n" "drwxr-sr-x 2 45854 200 512 Jul 30 17:14 dir with spaces\n" "lrwxrwxrwx 2 45854 200 6 May 29 2000 link -> ../link_target\n" "-rw-r--r-- 1 45854 200 4604 Jan 19 23:11 index.html", ), Call("cwd", args=("/",)), Call("close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: with pytest.raises(ValueError): host.stat_cache.resize(0) # If bug #38 was present, this would raise an `IndexError`. items = host.listdir(host.curdir) assert items == ["download", "dir with spaces", "link", "index.html"] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7959645 ftputil-5.0.3/test/test_sync.py0000664000175000017500000000636700000000000015565 0ustar00schwaschwa# Copyright (C) 2007-2021, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. import io import ntpath import os import shutil import ftputil import ftputil.path_encoding import ftputil.sync # Assume the test subdirectories are or will be in the current directory. TEST_ROOT = os.getcwd() class TestLocalToLocal: def setup_method(self, method): if not os.path.exists("test_empty"): os.mkdir("test_empty") if os.path.exists("test_target"): shutil.rmtree("test_target") os.mkdir("test_target") def test_sync_empty_dir(self): source = ftputil.sync.LocalHost() target = ftputil.sync.LocalHost() syncer = ftputil.sync.Syncer(source, target) source_dir = os.path.join(TEST_ROOT, "test_empty") target_dir = os.path.join(TEST_ROOT, "test_target") syncer.sync(source_dir, target_dir) def test_source_with_and_target_without_slash(self): source = ftputil.sync.LocalHost() target = ftputil.sync.LocalHost() syncer = ftputil.sync.Syncer(source, target) source_dir = os.path.join(TEST_ROOT, "test_source/") target_dir = os.path.join(TEST_ROOT, "test_target") syncer.sync(source_dir, target_dir) # Helper classes for `TestUploadFromWindows` class LocalWindowsHost(ftputil.sync.LocalHost): def __init__(self): self.path = ntpath self.sep = "\\" def open(self, path, mode): # Just return a dummy file object. return io.StringIO("") def walk(self, root): """ Return a list of tuples as `os.walk`, but use tuples as if the directory structure was dir1 dir11 file1 file2 where is the string passed in as `root`. """ join = ntpath.join return [ (root, [join(root, "dir1")], []), (join(root, "dir1"), ["dir11"], ["file1", "file2"]), ] class DummyFTPSession: def pwd(self): return "/" class DummyFTPPath: def abspath(self, path): # Don't care here if the path is absolute or not. return path def isdir(self, path): return "dir" in path def isfile(self, path): return "file" in path class ArgumentCheckingFTPHost(ftputil.FTPHost): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.path = DummyFTPPath() def _make_session(self, *args, **kwargs): self._encoding = ftputil.path_encoding.DEFAULT_ENCODING return DummyFTPSession() def mkdir(self, path): assert "\\" not in path def open(self, path, mode): assert "\\" not in path return io.StringIO("") class TestUploadFromWindows: def test_no_mixed_separators(self): source = LocalWindowsHost() target = ArgumentCheckingFTPHost() local_root = ntpath.join("some", "directory") syncer = ftputil.sync.Syncer(source, target) # If the following call raises any `AssertionError`s, the test # framework will catch them and show them. syncer.sync(local_root, "not_used_by_ArgumentCheckingFTPHost") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7959645 ftputil-5.0.3/test/test_tool.py0000664000175000017500000000426400000000000015560 0ustar00schwaschwa# Copyright (C) 2013-2021, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. import os import pytest import ftputil.path_encoding import ftputil.tool DEFAULT_ENCODING = ftputil.path_encoding.DEFAULT_ENCODING class Path(os.PathLike): """ Helper class for `TestSameStringTypeAs` """ def __init__(self, type_source): self._type_source = type_source def __fspath__(self): return self._type_source same_string_type_as = ftputil.tool.same_string_type_as class TestSameStringTypeAs: @staticmethod def _test_string(type_source, string, expected_result): """ Check if the result from `tool.same_string_type_as(type_source, string)` is the same as `expected_result`. `type_source` must be a `bytes` or `str` object. """ result = ftputil.tool.same_string_type_as(type_source, path) assert result == expected_result def test_to_bytes(self): assert same_string_type_as(b"abc", "def", encoding=DEFAULT_ENCODING) == b"def" def test_to_str(self): assert same_string_type_as("abc", b"def", encoding=DEFAULT_ENCODING) == "def" def test_both_bytes_type(self): assert same_string_type_as(b"abc", b"def", encoding=DEFAULT_ENCODING) == b"def" def test_both_str_type(self): assert same_string_type_as("abc", "def", encoding=DEFAULT_ENCODING) == "def" as_str = ftputil.tool.as_str as_str_path = ftputil.tool.as_str_path class TestAsStr: def test_from_bytes(self): assert as_str(b"abc", encoding=DEFAULT_ENCODING) == "abc" assert as_str_path(b"abc", encoding=DEFAULT_ENCODING) == "abc" def test_from_str(self): assert as_str("abc", encoding=DEFAULT_ENCODING) == "abc" assert as_str_path("abc", encoding=DEFAULT_ENCODING) == "abc" def test_from_bytes_path(self): assert as_str_path(Path(b"abc"), encoding=DEFAULT_ENCODING) == "abc" def test_from_str_path(self): assert as_str_path(Path("abc"), encoding=DEFAULT_ENCODING) == "abc" def test_type_error(self): with pytest.raises(TypeError): as_str(1, encoding=DEFAULT_ENCODING) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1642269185.7969646 ftputil-5.0.3/test/test_with_statement.py0000664000175000017500000001527000000000000017641 0ustar00schwaschwa# Copyright (C) 2008-2019, Stefan Schwarzer # and ftputil contributors (see `doc/contributors.txt`) # See the file LICENSE for licensing terms. import ftplib import io import pytest import ftputil.error import test.scripted_session as scripted_session from test import test_base Call = scripted_session.Call # Exception raised by client code, i. e. code using ftputil. Used to test the # behavior in case of client exceptions. class ClientCodeException(Exception): pass # # Test cases # class TestHostContextManager: def test_normal_operation(self): """ If an `FTPHost` instance is created, it should be closed by the host context manager. """ script = [ Call(method_name="__init__"), Call(method_name="pwd", result="/"), Call(method_name="close"), ] with test_base.ftp_host_factory(scripted_session.factory(script)) as host: assert host.closed is False assert host.closed is True def test_ftputil_exception(self): """ If an `ftplib.FTP` method raises an exception, it should be caught by the host context manager and the host object should be closed. """ script = [ # Since `__init__` raises an exception, `pwd` isn't called. # However, `close` is called via the context manager. Call(method_name="__init__", result=ftplib.error_perm), Call(method_name="close"), ] with pytest.raises(ftputil.error.FTPOSError): with test_base.ftp_host_factory(scripted_session.factory(script)) as host: pass # We arrived here, that's fine. Because the `FTPHost` object wasn't # successfully constructed, the assignment to `host` shouldn't have # happened. assert "host" not in locals() def test_client_code_exception(self): """ If client code raises an exception in the context manager block, the host object should be closed by the context manager. """ script = [ Call(method_name="__init__"), Call(method_name="pwd", result="/"), Call(method_name="close"), ] try: with test_base.ftp_host_factory(scripted_session.factory(script)) as host: assert host.closed is False raise ClientCodeException() except ClientCodeException: assert host.closed is True else: pytest.fail("`ClientCodeException` not raised") class TestFileContextManager: def test_normal_operation(self): """ If an `FTPFile` object is created in the `FTPFile` context manager, the context manager should close the file at the end of the `with` block. """ host_script = [ Call(method_name="__init__"), Call(method_name="pwd", result="/"), Call(method_name="close"), ] file_script = [ Call(method_name="__init__"), Call(method_name="pwd", result="/"), Call(method_name="cwd", result=None, args=("/",)), Call(method_name="voidcmd", result=None, args=("TYPE I",)), Call( method_name="transfercmd", result=io.StringIO("line 1\nline 2"), args=("RETR dummy", None), ), Call(method_name="voidresp", result=None), Call(method_name="close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with host.open("dummy", "r") as fobj: assert fobj.closed is False data = fobj.readline() assert data == "line 1\n" assert fobj.closed is False assert fobj.closed is True def test_ftputil_exception(self): """ If an `ftplib.FTP` method raises an exception, the `FTPFile` context manager should try to close the file. """ host_script = [ Call(method_name="__init__"), Call(method_name="pwd", result="/"), Call(method_name="close"), ] file_script = [ Call(method_name="__init__"), Call(method_name="pwd", result="/"), Call(method_name="cwd", result=None, args=("/",)), Call(method_name="voidcmd", result=None, args=("TYPE I",)), # Raise exception. `voidresp` therefore won't be called, but # `close` will be called by the context manager. Call( method_name="transfercmd", result=ftplib.error_perm, args=("STOR inaccessible", None), ), # Call(method_name="voidresp", result=None), Call(method_name="close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: with pytest.raises(ftputil.error.FTPIOError): # This should fail. with host.open("/inaccessible", "w") as fobj: pass # The file construction shouldn't have succeeded, so `fobj` should # be absent from the local namespace. assert "fobj" not in locals() def test_client_code_exception(self): """ If client code raises an exception in the `FTPFile` context manager block, the file object should be closed by the context manager. """ host_script = [ Call(method_name="__init__"), Call(method_name="pwd", result="/"), Call(method_name="close"), ] file_script = [ Call(method_name="__init__"), Call(method_name="pwd", result="/"), Call(method_name="cwd", result=None, args=("/",)), Call(method_name="voidcmd", result=None, args=("TYPE I",)), Call( method_name="transfercmd", result=io.BytesIO(b""), args=("RETR dummy", None), ), Call(method_name="voidresp", result=None), Call(method_name="close"), ] multisession_factory = scripted_session.factory(host_script, file_script) with test_base.ftp_host_factory(multisession_factory) as host: try: with host.open("dummy", "r") as fobj: assert fobj.closed is False raise ClientCodeException() except ClientCodeException: assert fobj.closed is True else: pytest.fail("`ClientCodeException` not raised")