././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1619698712.2686799 multivolumefile-0.2.3/0000755000175100001640000000000000000000000015540 5ustar00runnerdocker00000000000000././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1619698712.26468 multivolumefile-0.2.3/.github/0000755000175100001640000000000000000000000017100 5ustar00runnerdocker00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/.github/FUNDING.yml0000644000175100001640000000124400000000000020716 0ustar00runnerdocker00000000000000# These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: miurahr issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1619698712.26468 multivolumefile-0.2.3/.github/ISSUE_TEMPLATE/0000755000175100001640000000000000000000000021263 5ustar00runnerdocker00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/.github/ISSUE_TEMPLATE/bug_report.md0000644000175100001640000000127200000000000023757 0ustar00runnerdocker00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **Related issue** (if exist) **To Reproduce** Steps to reproduce the behavior: **Expected behavior** A clear and concise description of what you expected to happen. **Environment (please complete the following information):** - OS: [e.g. Windows 10, Ubuntu Linux 18.04.01] - Python [e.g. 3.8.1] - py7zr version: [e.g. v0.5b1, commit #123456 on master] **Test data(please attach in the report):** A minimum test data to reproduce your problem. **Additional context** Add any other context about the problem here. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/.github/ISSUE_TEMPLATE/feature_request.md0000644000175100001640000000112300000000000025005 0ustar00runnerdocker00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1619698712.26468 multivolumefile-0.2.3/.github/workflows/0000755000175100001640000000000000000000000021135 5ustar00runnerdocker00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/.github/workflows/publish-to-test-pypi.yml0000644000175100001640000000230200000000000025677 0ustar00runnerdocker00000000000000name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI on: push jobs: build-n-publish: name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI runs-on: ubuntu-18.04 steps: - uses: actions/checkout@master with: fetch-depth: 20 - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Set up Python 3.8 uses: actions/setup-python@v1 with: python-version: 3.8 - name: Install pep517 and twine run: python -m pip install pep517 twine --user - name: Build a binary wheel and a source tarball run: python -m pep517.build --source --binary --out-dir dist/ ./ - name: twine check run: python -m twine check dist/* - name: Publish distribution 📦 to Test PyPI if: startsWith(github.event.ref, 'refs/heads/master') uses: pypa/gh-action-pypi-publish@master with: password: ${{ secrets.testpypi_password }} repository_url: https://test.pypi.org/legacy/ - name: Publish distribution 📦 to PyPI if: startsWith(github.event.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@master with: password: ${{ secrets.pypi_password }} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/.github/workflows/release-note.yml0000644000175100001640000000136300000000000024246 0ustar00runnerdocker00000000000000# Trigger the workflow on milestone events on: milestone: types: [closed] name: Milestone Closure jobs: create-release-notes: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Create Release Notes uses: docker://decathlon/release-notes-generator-action:2.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} OUTPUT_FOLDER: temp_release_notes USE_MILESTONE_TITLE: "true" - name: Upload Release Notes to Wiki uses: docker://decathlon/wiki-page-creator-action:latest env: ACTION_MAIL: miurahr@linux.com ACTION_NAME: miurahr GH_PAT: ${{ secrets.GH_PAT }} MD_FOLDER: temp_release_notes OWNER: miurahr REPO_NAME: py7zr ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/.github/workflows/run-tox-tests.yml0000644000175100001640000000250600000000000024437 0ustar00runnerdocker00000000000000name: Run Tox tests on: push jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest] python-version: [3.8] name: Test on Python ${{ matrix.python-version }} steps: - uses: actions/checkout@v2 - name: Setup python uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} architecture: x64 - name: Install dependencies run: | pip install -U pip pip install tox tox-gh-actions coveralls - name: Test project with tox run: | tox coveralls env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} COVERALLS_PARALLEL: true COVERALLS_FLAG_NAME: py-${{ matrix.python-version }}-${{ matrix.os }} PYTEST_ADDOPTS: --cov-config=pyproject.toml --cov --cov-append finish: runs-on: ubuntu-latest name: finish parallel build needs: build steps: - name: Tell Coveralls that the parallel build is finished run: | curl -k \ https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN \ -d "payload[build_num]=$GITHUB_SHA&payload[status]=done" env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/.gitignore0000644000175100001640000000410300000000000017526 0ustar00runnerdocker00000000000000# Created by .ignore support plugin (hsz.mobi) ### Python template # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # IDE .idea ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/Changelog.rst0000644000175100001640000000412500000000000020163 0ustar00runnerdocker00000000000000========= ChangeLog ========= All notable changes to this project will be documented in this file. `Unreleased`_ ============= Added ----- Changed ------- Fixed ----- Deprecated ---------- Removed ------- Security -------- `v0.2.3`_ ========= Added ----- * implement readall() Chnaged ------- * lint with black `v0.2.2`_ ========= Added ----- * Add py.typed file for type hinting. `v0.2.1`_ ========= Added ----- * Add `name` property that indicate basename of volumes * Add `stat()` that return `stat_result` which has as mostly same methods as `os.stat_result` class except for platform dependent methods. `v0.2.0`_ ========= Added ----- * Type hint information bundled. Fixed ----- * Seek() returns current position. Changed ------- * Explanation of unsupported methods an modes in README `v0.1.4`_ ========= Fixed ----- * Fix append mode bug. `v0.1.3`_ ========= Fixed ----- * Fix added volume size become wrong `v0.1.2`_ ========= Fixed ----- * Fix append mode (#1) `v0.1.1`_ ========= Fixed ----- * Fin NotImplementedError when writing boudning of target files `v0.1.0`_ ========= * ***API changed*** Added ----- * Add mode 'x', 'xb' and 'xt' * Add mode 'a', 'ab' and 'at' * Support flush() Changed ------- * Change API: file argument of 'r' and 'rb' now single basename instead of list of files `v0.0.5`_ ========= * Support context manager * Support read functions. .. History links .. _Unreleased: https://github.com/miurahr/py7zr/compare/v0.2.2...HEAD .. _v0.2.2: https://github.com/miurahr/py7zr/compare/v0.2.1...v0.2.2 .. _v0.2.1: https://github.com/miurahr/py7zr/compare/v0.2.0...v0.2.1 .. _v0.2.0: https://github.com/miurahr/py7zr/compare/v0.1.4...v0.2.0 .. _v0.1.4: https://github.com/miurahr/py7zr/compare/v0.1.3...v0.1.4 .. _v0.1.3: https://github.com/miurahr/py7zr/compare/v0.1.2...v0.1.3 .. _v0.1.2: https://github.com/miurahr/py7zr/compare/v0.1.1...v0.1.2 .. _v0.1.1: https://github.com/miurahr/py7zr/compare/v0.1.0...v0.1.1 .. _v0.1.0: https://github.com/miurahr/py7zr/compare/v0.0.5...v0.1.0 .. _v0.0.5: https://github.com/miurahr/py7zr/compare/v0.0.1...v0.0.5 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/LICENSE0000644000175100001640000006364200000000000016560 0ustar00runnerdocker00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/MANIFEST.in0000644000175100001640000000022500000000000017275 0ustar00runnerdocker00000000000000include *.py include *.pyi include *.rst include LICENSE include tox.ini include py.typed recursive-include tests *.py recursive-include tests *.00? ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1619698712.2686799 multivolumefile-0.2.3/PKG-INFO0000644000175100001640000001644600000000000016650 0ustar00runnerdocker00000000000000Metadata-Version: 2.1 Name: multivolumefile Version: 0.2.3 Summary: multi volume file wrapper library Home-page: https://github.com/miurahr/multivolume Author: Hiroshi Miura Author-email: miurahr@linux.com License: LGPL-2.1+ Description: =============== multivolumefile =============== .. image:: https://coveralls.io/repos/github/miurahr/multivolume/badge.svg?branch=master :target: https://coveralls.io/github/miurahr/multivolume?branch=master .. image:: https://github.com/miurahr/multivolume/workflows/Run%20Tox%20tests/badge.svg :target: https://github.com/miurahr/multivolume/actions MultiVolumefile is a python library to provide a file-object wrapping multiple files as virtually like as a single file. It inherit io.RawIOBase class and support some of its standard methods. See API details at `python library reference`_ .. _`python library reference`: https://docs.python.org/3/library/io.html Status ====== multivolumefile module is under active development and considered as ***Alpha*** state. It is not good idea to use it on production systems, but it may work well in a limited range of usage. Please check limitations and passed test cases. Install ======= You can install it as usual public libraries, you can use pip command ``` pip install multivolumefile ``` You are also able to add it to your setup.py/cfg as dependency. Usages ------ - For reading multi-volume files, that has names `archive.7z.0001`, `archive.7z.0002` and so on, you can call multivolumefile as follows; .. code-block:: with multivolumefile.open('archive.7z', 'rb') as vol: data = vol.read(100) vol.seek(500) - For writing multi-volue files, that has names `archive.7z.0001`, `archive.7z.0002` and so on, you can call multivolumefile as follows; .. code-block:: data = b'abcdefg' with multivolumefile.open('archive.7z', 'wb') as vol: size = vol.write(data) vol.seek(0) you will see file `archive.7z.001` are written. Limitations and known issues ============================ - fileno() is not supported and when call it, you will get RuntimeError exception. - There are several non-implemented functions such as truncate() and writeline() that will raise NotimplementedError - There are several non-implemented functions such as readlines(), readline() and readall(). - Text mode is not implemented. - ***Caution***: When globbing existent volumes, it glob all files other than 4-digit extensions, it may break your data. Contribution ============ You are welcome to contribute the project, as usual on github projects, Pull-Requests are always welcome. License ======= Multivolume is licensed under GNU Lesser General Public license version 2.1 or later. ========= ChangeLog ========= All notable changes to this project will be documented in this file. `Unreleased`_ ============= Added ----- Changed ------- Fixed ----- Deprecated ---------- Removed ------- Security -------- `v0.2.3`_ ========= Added ----- * implement readall() Chnaged ------- * lint with black `v0.2.2`_ ========= Added ----- * Add py.typed file for type hinting. `v0.2.1`_ ========= Added ----- * Add `name` property that indicate basename of volumes * Add `stat()` that return `stat_result` which has as mostly same methods as `os.stat_result` class except for platform dependent methods. `v0.2.0`_ ========= Added ----- * Type hint information bundled. Fixed ----- * Seek() returns current position. Changed ------- * Explanation of unsupported methods an modes in README `v0.1.4`_ ========= Fixed ----- * Fix append mode bug. `v0.1.3`_ ========= Fixed ----- * Fix added volume size become wrong `v0.1.2`_ ========= Fixed ----- * Fix append mode (#1) `v0.1.1`_ ========= Fixed ----- * Fin NotImplementedError when writing boudning of target files `v0.1.0`_ ========= * ***API changed*** Added ----- * Add mode 'x', 'xb' and 'xt' * Add mode 'a', 'ab' and 'at' * Support flush() Changed ------- * Change API: file argument of 'r' and 'rb' now single basename instead of list of files `v0.0.5`_ ========= * Support context manager * Support read functions. .. History links .. _Unreleased: https://github.com/miurahr/py7zr/compare/v0.2.2...HEAD .. _v0.2.2: https://github.com/miurahr/py7zr/compare/v0.2.1...v0.2.2 .. _v0.2.1: https://github.com/miurahr/py7zr/compare/v0.2.0...v0.2.1 .. _v0.2.0: https://github.com/miurahr/py7zr/compare/v0.1.4...v0.2.0 .. _v0.1.4: https://github.com/miurahr/py7zr/compare/v0.1.3...v0.1.4 .. _v0.1.3: https://github.com/miurahr/py7zr/compare/v0.1.2...v0.1.3 .. _v0.1.2: https://github.com/miurahr/py7zr/compare/v0.1.1...v0.1.2 .. _v0.1.1: https://github.com/miurahr/py7zr/compare/v0.1.0...v0.1.1 .. _v0.1.0: https://github.com/miurahr/py7zr/compare/v0.0.5...v0.1.0 .. _v0.0.5: https://github.com/miurahr/py7zr/compare/v0.0.1...v0.0.5 Keywords: multivolume,file Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Console Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+) Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >=3.6 Description-Content-Type: text/x-rst Provides-Extra: test Provides-Extra: type Provides-Extra: check ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/README.rst0000644000175100001640000000467100000000000017237 0ustar00runnerdocker00000000000000=============== multivolumefile =============== .. image:: https://coveralls.io/repos/github/miurahr/multivolume/badge.svg?branch=master :target: https://coveralls.io/github/miurahr/multivolume?branch=master .. image:: https://github.com/miurahr/multivolume/workflows/Run%20Tox%20tests/badge.svg :target: https://github.com/miurahr/multivolume/actions MultiVolumefile is a python library to provide a file-object wrapping multiple files as virtually like as a single file. It inherit io.RawIOBase class and support some of its standard methods. See API details at `python library reference`_ .. _`python library reference`: https://docs.python.org/3/library/io.html Status ====== multivolumefile module is under active development and considered as ***Alpha*** state. It is not good idea to use it on production systems, but it may work well in a limited range of usage. Please check limitations and passed test cases. Install ======= You can install it as usual public libraries, you can use pip command ``` pip install multivolumefile ``` You are also able to add it to your setup.py/cfg as dependency. Usages ------ - For reading multi-volume files, that has names `archive.7z.0001`, `archive.7z.0002` and so on, you can call multivolumefile as follows; .. code-block:: with multivolumefile.open('archive.7z', 'rb') as vol: data = vol.read(100) vol.seek(500) - For writing multi-volue files, that has names `archive.7z.0001`, `archive.7z.0002` and so on, you can call multivolumefile as follows; .. code-block:: data = b'abcdefg' with multivolumefile.open('archive.7z', 'wb') as vol: size = vol.write(data) vol.seek(0) you will see file `archive.7z.001` are written. Limitations and known issues ============================ - fileno() is not supported and when call it, you will get RuntimeError exception. - There are several non-implemented functions such as truncate() and writeline() that will raise NotimplementedError - There are several non-implemented functions such as readlines(), readline() and readall(). - Text mode is not implemented. - ***Caution***: When globbing existent volumes, it glob all files other than 4-digit extensions, it may break your data. Contribution ============ You are welcome to contribute the project, as usual on github projects, Pull-Requests are always welcome. License ======= Multivolume is licensed under GNU Lesser General Public license version 2.1 or later. ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1619698712.26468 multivolumefile-0.2.3/multivolumefile/0000755000175100001640000000000000000000000020762 5ustar00runnerdocker00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/multivolumefile/__init__.py0000644000175100001640000002455300000000000023104 0ustar00runnerdocker00000000000000#!/usr/bin/env python # # multi-volume file library # Copyright (C) 2020 Hiroshi Miura # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # import contextlib import io import os import pathlib from mmap import mmap from typing import Any, Container, List, Optional, Union from .stat import stat_result __all__ = ["stat_result", "open", "MultiVolume"] BLOCKSIZE = 16384 def open(name: Union[pathlib.Path, str], mode=None, volume=None) -> io.RawIOBase: return MultiVolume(name, mode=mode, volume=volume) class _FileInfo: def __init__(self, filename, stat, size): self.filename = filename self.stat = stat self.size = size class MultiVolume(io.RawIOBase, contextlib.AbstractContextManager): def __init__( self, basename: Union[pathlib.Path, str], mode: Optional[str] = "r", *, volume: Optional[int] = None, ext_digits: Optional[int] = 4, hex: Optional[bool] = False, ext_start: Optional[int] = 1 ): self._mode = mode self._closed = False self._files = [] # type: List[object] self._fileinfo = [] # type: List[_FileInfo] self._position = 0 self._positions = [] self._digits = ext_digits self._start = ext_start self._hex = hex self.name = str(basename) if mode in ["rb", "r", "rt"]: self._init_reader(basename) elif mode in ["wb", "w", "wt", "xb", "x", "xt", "ab", "a", "at"]: if volume is None: self._volume_size = 10 * 1024 * 1024 # set default to 10MBytes else: self._volume_size = volume self._init_writer(basename) else: raise NotImplementedError def _glob_files(self, basename): if isinstance(basename, str): basename = pathlib.Path(basename) files = basename.parent.glob(basename.name + ".*") return sorted(files) def _init_reader(self, basename): pos = 0 self._positions.append(pos) filenames = self._glob_files(basename) for name in filenames: stat = os.stat(name) size = os.stat(name).st_size self._fileinfo.append(_FileInfo(name, stat, size)) self._files.append(io.open(name, mode=self._mode)) pos += size self._positions.append(pos) def _init_writer(self, basename): if isinstance(basename, str): basename = pathlib.Path(basename) ext = ".{num:0{ext_digit}d}".format(num=self._start, ext_digit=self._digits) target = basename.with_name(basename.name + ext) if target.exists(): if self._mode in ["x", "xb", "xt"]: raise FileExistsError elif self._mode in ["w", "wb", "wt"]: file = io.open(target, mode=self._mode) self._files.append(file) file.truncate(0) stat = os.stat(target) self._fileinfo.append(_FileInfo(target, stat, self._volume_size)) self._positions = [0, self._volume_size] elif self._mode in ["a", "ab", "at"]: filenames = self._glob_files(basename) if self._mode == "ab": mode = "rb" else: mode = "r" pos = 0 size = 0 self._positions = [0] for i in range(len(filenames)): file = io.open(filenames[i], mode) self._files.append(file) stat = filenames[i].stat() size = stat.st_size self._fileinfo.append(_FileInfo(filenames[i], stat, size)) pos += size self._positions.append(pos) self._position = pos # last file if size >= self._volume_size: self._add_volume() else: self._files[-1].close() self._files[-1] = io.open( filenames[len(filenames) - 1], mode=self._mode ) else: raise NotImplementedError else: file = io.open(target, mode=self._mode) self._files.append(file) self._fileinfo.append(_FileInfo(target, os.stat(target), self._volume_size)) self._positions = [0, self._volume_size] def _current_index(self): for i in range(len(self._positions) - 1): if self._positions[i] <= self._position < self._positions[i + 1]: pos = self._files[i].tell() offset = self._position - self._positions[i] if pos != offset: self._files[i].seek(offset, io.SEEK_SET) return i return len(self._files) - 1 def read(self, size: int = -1) -> bytes: if size == -1: return self.readall() current = self._current_index() file = self._files[current] data = file.read(size) self._position += len(data) return data def readall(self) -> bytes: result = b"" data = self.read(BLOCKSIZE) while len(data) > 0: result += data data = self.read(BLOCKSIZE) return result def readinto(self, b: Union[bytearray, memoryview, Container[Any], mmap]) -> int: size = len(b) data = self.read(size) b[: len(data)] = data return len(data) def write( self, b: Union[bytes, bytearray, memoryview, Container[Any], mmap] ) -> None: current = self._current_index() file = self._files[current] pos = file.tell() if pos + len(b) > self._volume_size: file.write(b[: self._volume_size - pos]) self._position += self._volume_size - pos if current == len(self._files) - 1: self._add_volume() file = self._files[current + 1] file.seek(0) self.write(b[self._volume_size - pos :]) # recursive call else: file.write(b) self._position += len(b) def _add_volume(self): num = len(self._fileinfo) + self._start - 1 if self._hex: last = self._fileinfo[-1].filename last_ext = ".{num:0{ext_digit}x}".format(num=num, ext_digit=self._digits) assert last.suffix.endswith(last_ext) next_ext = ".{num:0{ext_digit}x}".format( num=num + 1, ext_digit=self._digits ) next = last.with_suffix(next_ext) else: last = self._fileinfo[-1].filename last_ext = ".{num:0{ext_digit}d}".format(num=num, ext_digit=self._digits) assert last.suffix.endswith(last_ext) next_ext = ".{num:0{ext_digit}d}".format( num=num + 1, ext_digit=self._digits ) next = last.with_suffix(next_ext) self._files.append(io.open(next, self._mode)) stat = os.stat(next) self._fileinfo.append(_FileInfo(next, stat, self._volume_size)) pos = self._positions[-1] if pos != self._position: self._positions[-1] = self._position self._positions.append(self._positions[-1] + self._volume_size) def close(self) -> None: if self._closed: return self._closed = True for file in self._files: file.close() @property def closed(self) -> bool: return self._closed def fileno(self) -> int: """ fileno() is incompatible with other implementations. multivolume handle multiple file object so we cannot return single fd. """ raise RuntimeError("fileno() is not supported.") def flush(self) -> None: if self._closed: return if self._mode == "wb" or self._mode == "w": for file in self._files: file.flush() def isatty(self) -> bool: return False def readable(self) -> bool: if self._closed: return False return all([f.readable() for f in self._files]) def readline(self, size: Optional[int] = -1) -> bytes: raise NotImplementedError def readlines(self, hint: int = -1) -> List[bytes]: raise NotImplementedError def seek(self, offset: int, whence: Optional[int] = io.SEEK_SET) -> int: if whence == io.SEEK_SET: target = offset elif whence == io.SEEK_CUR: target = self._position + offset else: target = self._positions[-1] + offset self._position = target i = len(self._files) - 1 while i > 0 and target < self._positions[i]: i -= 1 file = self._files[i] file.seek(target - self._positions[i], io.SEEK_SET) return self._position def seekable(self) -> bool: if self._mode in ["ab", "at", "a"]: return False else: return all([f.seekable() for f in self._files]) def tell(self) -> int: return self._position def truncate(self, size: Optional[int] = None) -> int: raise NotImplementedError def writable(self) -> bool: return self._mode in ["wb", "w", "wt", "x", "xb", "xt", "ab", "a", "at"] def writelines(self, lines): raise NotImplementedError def stat(self) -> stat_result: totalsize = 0 for fi in self._fileinfo: totalsize += fi.size return stat_result(self._fileinfo[0].stat, totalsize) def __del__(self): # FIXME pass def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/multivolumefile/__init__.pyi0000644000175100001640000000337000000000000023247 0ustar00runnerdocker00000000000000import contextlib import io import pathlib from mmap import mmap from typing import Any, Container, List, Optional, Union def open( name: Union[pathlib.Path, str], mode: Any = ..., volume: Any = ... ) -> io.RawIOBase: ... class _FileInfo: filename: Any = ... size: Any = ... def __init__(self, filename: Any, size: Any) -> None: ... class MultiVolume(io.RawIOBase, contextlib.AbstractContextManager): def __init__( self, basename: Union[pathlib.Path, str], mode: Optional[str] = ..., *, volume: Optional[int] = ..., ext_digits: Optional[int] = ..., hex: Optional[bool] = ..., ext_start: Optional[int] = ... ) -> None: ... def read(self, size: int = ...) -> bytes: ... def readall(self) -> bytes: ... def readinto( self, b: Union[bytearray, memoryview, Container[Any], mmap] ) -> int: ... def write( self, b: Union[bytes, bytearray, memoryview, Container[Any], mmap] ) -> None: ... def close(self) -> None: ... @property def closed(self) -> bool: ... def fileno(self) -> int: ... def flush(self) -> None: ... def isatty(self) -> bool: ... def readable(self) -> bool: ... def readline(self, size: Optional[int] = ...) -> bytes: ... def readlines(self, hint: int = ...) -> List[bytes]: ... def seek(self, offset: int, whence: Optional[int] = ...) -> int: ... def seekable(self) -> bool: ... def tell(self) -> int: ... def truncate(self, size: Optional[int] = ...) -> int: ... def writable(self) -> bool: ... def writelines(self, lines: Any) -> None: ... def __del__(self) -> None: ... def __enter__(self): ... def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: ... ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/multivolumefile/py.typed0000644000175100001640000000000000000000000022447 0ustar00runnerdocker00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/multivolumefile/stat.py0000644000175100001640000000202300000000000022304 0ustar00runnerdocker00000000000000class stat_result: def __init__(self, stat, size): self._stat = stat self._size = size @property def st_size(self): return self._size @property def st_mode(self): return self._stat.st_mode @property def st_ino(self): return 0 @property def st_dev(self): return self._stat.st_dev @property def st_nlink(self): return self._stat.st_nlink @property def st_uid(self): return self._stat.st_uid @property def st_gid(self): return self._stat.st_gid @property def st_atime(self): return self._stat.st_atime @property def st_ctime(self): return self._stat.st_ctime @property def st_mtime(self): return self._stat.st_mtime @property def st_atime_ns(self): return self._stat.st_atime_ns @property def st_ctime_ns(self): return self._stat.st_ctime_ns @property def st_mtime_ns(self): return self._stat.st_mtime_ns ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1619698712.26468 multivolumefile-0.2.3/multivolumefile.egg-info/0000755000175100001640000000000000000000000022454 5ustar00runnerdocker00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698711.0 multivolumefile-0.2.3/multivolumefile.egg-info/PKG-INFO0000644000175100001640000001644600000000000023564 0ustar00runnerdocker00000000000000Metadata-Version: 2.1 Name: multivolumefile Version: 0.2.3 Summary: multi volume file wrapper library Home-page: https://github.com/miurahr/multivolume Author: Hiroshi Miura Author-email: miurahr@linux.com License: LGPL-2.1+ Description: =============== multivolumefile =============== .. image:: https://coveralls.io/repos/github/miurahr/multivolume/badge.svg?branch=master :target: https://coveralls.io/github/miurahr/multivolume?branch=master .. image:: https://github.com/miurahr/multivolume/workflows/Run%20Tox%20tests/badge.svg :target: https://github.com/miurahr/multivolume/actions MultiVolumefile is a python library to provide a file-object wrapping multiple files as virtually like as a single file. It inherit io.RawIOBase class and support some of its standard methods. See API details at `python library reference`_ .. _`python library reference`: https://docs.python.org/3/library/io.html Status ====== multivolumefile module is under active development and considered as ***Alpha*** state. It is not good idea to use it on production systems, but it may work well in a limited range of usage. Please check limitations and passed test cases. Install ======= You can install it as usual public libraries, you can use pip command ``` pip install multivolumefile ``` You are also able to add it to your setup.py/cfg as dependency. Usages ------ - For reading multi-volume files, that has names `archive.7z.0001`, `archive.7z.0002` and so on, you can call multivolumefile as follows; .. code-block:: with multivolumefile.open('archive.7z', 'rb') as vol: data = vol.read(100) vol.seek(500) - For writing multi-volue files, that has names `archive.7z.0001`, `archive.7z.0002` and so on, you can call multivolumefile as follows; .. code-block:: data = b'abcdefg' with multivolumefile.open('archive.7z', 'wb') as vol: size = vol.write(data) vol.seek(0) you will see file `archive.7z.001` are written. Limitations and known issues ============================ - fileno() is not supported and when call it, you will get RuntimeError exception. - There are several non-implemented functions such as truncate() and writeline() that will raise NotimplementedError - There are several non-implemented functions such as readlines(), readline() and readall(). - Text mode is not implemented. - ***Caution***: When globbing existent volumes, it glob all files other than 4-digit extensions, it may break your data. Contribution ============ You are welcome to contribute the project, as usual on github projects, Pull-Requests are always welcome. License ======= Multivolume is licensed under GNU Lesser General Public license version 2.1 or later. ========= ChangeLog ========= All notable changes to this project will be documented in this file. `Unreleased`_ ============= Added ----- Changed ------- Fixed ----- Deprecated ---------- Removed ------- Security -------- `v0.2.3`_ ========= Added ----- * implement readall() Chnaged ------- * lint with black `v0.2.2`_ ========= Added ----- * Add py.typed file for type hinting. `v0.2.1`_ ========= Added ----- * Add `name` property that indicate basename of volumes * Add `stat()` that return `stat_result` which has as mostly same methods as `os.stat_result` class except for platform dependent methods. `v0.2.0`_ ========= Added ----- * Type hint information bundled. Fixed ----- * Seek() returns current position. Changed ------- * Explanation of unsupported methods an modes in README `v0.1.4`_ ========= Fixed ----- * Fix append mode bug. `v0.1.3`_ ========= Fixed ----- * Fix added volume size become wrong `v0.1.2`_ ========= Fixed ----- * Fix append mode (#1) `v0.1.1`_ ========= Fixed ----- * Fin NotImplementedError when writing boudning of target files `v0.1.0`_ ========= * ***API changed*** Added ----- * Add mode 'x', 'xb' and 'xt' * Add mode 'a', 'ab' and 'at' * Support flush() Changed ------- * Change API: file argument of 'r' and 'rb' now single basename instead of list of files `v0.0.5`_ ========= * Support context manager * Support read functions. .. History links .. _Unreleased: https://github.com/miurahr/py7zr/compare/v0.2.2...HEAD .. _v0.2.2: https://github.com/miurahr/py7zr/compare/v0.2.1...v0.2.2 .. _v0.2.1: https://github.com/miurahr/py7zr/compare/v0.2.0...v0.2.1 .. _v0.2.0: https://github.com/miurahr/py7zr/compare/v0.1.4...v0.2.0 .. _v0.1.4: https://github.com/miurahr/py7zr/compare/v0.1.3...v0.1.4 .. _v0.1.3: https://github.com/miurahr/py7zr/compare/v0.1.2...v0.1.3 .. _v0.1.2: https://github.com/miurahr/py7zr/compare/v0.1.1...v0.1.2 .. _v0.1.1: https://github.com/miurahr/py7zr/compare/v0.1.0...v0.1.1 .. _v0.1.0: https://github.com/miurahr/py7zr/compare/v0.0.5...v0.1.0 .. _v0.0.5: https://github.com/miurahr/py7zr/compare/v0.0.1...v0.0.5 Keywords: multivolume,file Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Console Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+) Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Python: >=3.6 Description-Content-Type: text/x-rst Provides-Extra: test Provides-Extra: type Provides-Extra: check ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698712.0 multivolumefile-0.2.3/multivolumefile.egg-info/SOURCES.txt0000644000175100001640000000130500000000000024337 0ustar00runnerdocker00000000000000.gitignore Changelog.rst LICENSE MANIFEST.in README.rst pyproject.toml setup.cfg setup.py tox.ini .github/FUNDING.yml .github/ISSUE_TEMPLATE/bug_report.md .github/ISSUE_TEMPLATE/feature_request.md .github/workflows/publish-to-test-pypi.yml .github/workflows/release-note.yml .github/workflows/run-tox-tests.yml multivolumefile/__init__.py multivolumefile/__init__.pyi multivolumefile/py.typed multivolumefile/stat.py multivolumefile.egg-info/PKG-INFO multivolumefile.egg-info/SOURCES.txt multivolumefile.egg-info/dependency_links.txt multivolumefile.egg-info/requires.txt multivolumefile.egg-info/top_level.txt tests/test_fuzzer.py tests/test_multivolume.py tests/data/archive.7z.001 tests/data/archive.7z.002././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698711.0 multivolumefile-0.2.3/multivolumefile.egg-info/dependency_links.txt0000644000175100001640000000000100000000000026522 0ustar00runnerdocker00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698711.0 multivolumefile-0.2.3/multivolumefile.egg-info/requires.txt0000644000175100001640000000031200000000000025050 0ustar00runnerdocker00000000000000 [check] check-manifest flake8 flake8-black readme-renderer pygments isort>=5.0.3 twine [test] pytest pytest-cov pyannotate coverage[toml]>=5.2 coveralls>=2.1.1 hypothesis [type] mypy mypy_extensions ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698711.0 multivolumefile-0.2.3/multivolumefile.egg-info/top_level.txt0000644000175100001640000000002000000000000025176 0ustar00runnerdocker00000000000000multivolumefile ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/pyproject.toml0000644000175100001640000000062300000000000020455 0ustar00runnerdocker00000000000000[build-system] requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.5.0"] build-backend = "setuptools.build_meta" [tools.setuptools_scm] local_scheme = "no-local-version" [tool.coverage.run] branch = true parallel = true source = ["multivolumefile"] [tool.coverage.report] show_missing = true exclude_lines = ["if __name__ == .__main__.:", "pragma: no-cover", "@abstract", "def __repr__"] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1619698712.2686799 multivolumefile-0.2.3/setup.cfg0000644000175100001640000000251100000000000017360 0ustar00runnerdocker00000000000000[flake8] max-line-length = 125 [bdist_wheel] universal = 0 [metadata] name = multivolumefile description = multi volume file wrapper library long_description = file: README.rst, Changelog.rst long_description_content_type = text/x-rst keywords = multivolume, file license = LGPL-2.1+ author = Hiroshi Miura author_email = miurahr@linux.com url = https://github.com/miurahr/multivolume classifiers = Development Status :: 3 - Alpha Environment :: Console License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+) Operating System :: MacOS :: MacOS X Operating System :: Microsoft :: Windows Operating System :: POSIX Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3 :: Only Topic :: Software Development :: Libraries :: Python Modules [options] python_requires = >= 3.6 setup_requires = setuptools-scm>=3.5.0 setuptools>=42.0 packages = find: [options.package_data] multivolumefile = py.typed, *.pyi [options.extras_require] test = pytest pytest-cov pyannotate coverage[toml]>=5.2 coveralls>=2.1.1 hypothesis type = mypy mypy_extensions check = check-manifest flake8 flake8-black readme-renderer pygments isort>=5.0.3 twine [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/setup.py0000644000175100001640000000016100000000000017250 0ustar00runnerdocker00000000000000#!/usr/bin/env python from setuptools import setup setup(use_scm_version={"local_scheme": "no-local-version"}) ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1619698712.26468 multivolumefile-0.2.3/tests/0000755000175100001640000000000000000000000016702 5ustar00runnerdocker00000000000000././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1619698712.26468 multivolumefile-0.2.3/tests/data/0000755000175100001640000000000000000000000017613 5ustar00runnerdocker00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/tests/data/archive.7z.0010000644000175100001640000006065000000000000022024 0ustar00runnerdocker000000000000007z'̙2-$i#~fi]%7 B\@l d{ݙbؔI8=?cHJ;m:[ˉf0)2D;ǓP.zז440P1ĩz#VNoh@ (Dce= ,#ڢ7 gghES[P;0tYt.N. wY` J6Og_}7Ȍ/ʣ`!uh`9KXL ;>2c7x 'gHcl "+8iVb>FNMg F(a*H=_{| 5C>}"bZ)DvndڞM/uϒo>HQH[@&g=傜]˓;]sK*)%? ty.,{_ jug0_辰'{v0v]GIdrNҞ3Z&6 ~=Xex/ Ve5P3Y<ز?mNAf ROX'&´q ,#}D@{8x'p 6p1V2j:E/[aUu XXkPi*0R903e&P,g(d._3?ѾA}[E5oro#](E iPPvEp" extt;ӈ0:.j֟(+sxkxq՟ yoϏc4qN"L4D+=5s9 UL# ߻"!UC[]]鎹'OO Ms}{ 9f/pΫS2tqG4<>t~b7 j@K%K#_KFk,ƒ/H7g 7VD?uQ8Q#~!4lx[;-iЍ { eʗ,:l|+8֊UJp/).28im#ۥi9Xp:Z&>͝K /fh̆Dt_;>ȤWs:PHIVVo%8I= '"اW௨3NHf˵"-&" Ͳ O~[ Mf*^x_S0x 5LBz…B)5,$߀ϋ#*jg_)v2X7w#=7|3)V&-II;fЩ+Jٷ12  \QQ4.xX4n}k`m`|茜O· Y.V/\ 1+ue;XE7j jEko7ܴ7ReGSKH^Xb;g<2ѡbNJF1QcL΄l2Kszg+GH-0cBhu|WI$s4L+'wGHǁK˂Nƒ 'xT"YGi̳کHMȭl0AJ2\m(ݓ N׏"_Vf {lO{ad ՑO ѵm+GẺ̈́V= ;ox ZuzcQeJ-RILDAd֬r1nM*mqg 3)}=ݬJ_IƓNeA qG4VOl)_ltm!]_Go l"*}訹T\6g1G{fh8uO"ZkƥCjfdNL:09˳\z;37,҄9~fB=Dٖspu~۾$RICЩ?{6 Olyqc;{wncFL4b8⢍Rۣ/!2@%:|oGeӖ_HHYtwk1B$kB+yz>d&3Y92qᲃ]A8b[7: 0z!34#L+$4ux ʉ`Iŕ)no}<3>qm3O>$ED *!*xJ1}tL9nC띯.13ajUqPoUon ow&`TRD˘! +fej8E8!_cϬaAH`_LD04E<TXǻ/rՅ`b1c ¾uKHaLSm n C:,Jad!E[ M+Wҩtz6"[J~Uxd=3`Va| H^%E+/;=!uCntZx提LHx6fTƇTLHFF?tES%B&֣J;rK'ּ_B䜙i90x/*m؅x\O\<&Ea -X)@QAc}+zpFJOuwݞ1o,$\̅&ʀoz 40JWc{_FwwOyϿd V|rMՁ.rW|(w. z-Υ+"0e4du/Stx1tgܬ˧)o4Zh@p‚`ԎtukJF Ə`yv)Sy!ɋCgttAqY|&?b/,iz2N4!Y)SAc}E$ Q5aNh^ђ-I' OFd;ef7L/P~BFݏ"* Plimo)OvfSHehm؏J4y4O vVE13995h[B-ݝ=~c ܱyBS! rn  nLB|V\4Sک*$ TF~@S`.骅n-忊+/q_?4PP^cMMEYwo&da]kz ㈶dyaHۤߵ``CӋ8 kmH=;]4ZsHeW\illo^OuQA|xor#]۔pnHI C<%.^xe96Wa^Sj &>-I0o> .PZ,GvѦʕwX@2\"6`k%gN^4=fXy+FY.G+֛_aCKpVt|Ll^ݸ| +zI[ (BieS\7mQM/ke1x=-Y_[ Ss-#Q K0BKh/=k݌v)e3Tt|&dɕ2+̶|(I3y}vO:g1 H}f@P/B0'0y|%7½Gl&;lneTJxҺlD}w81KgpFW?^}8-n"U3!Ac ^,=QOC)qs'ВIN 9j$͓(]GЬ!SD{qGʬS[}io~a_[)d/,,0i?W+CÇjږ1#M@V_- WV^eվK? $nyݮB-5܀t0MImX@P׮h2yG{[1lОLTv%[]i5+ stA(DRr~+ewG蜺O\PDO~ԏ;z 9A'<5'4ǓW,wlGiEKY#~ZD`rAהG iLOFB+P^d(Iф:Ghc/ZаʗtC\b.}3o`<\f_j\vwj*Z' %-J'eK tU|5V$Wd3ކ='Zr= '︗߽FY!Jzq=;Bx?!NY(ܧ&o&,,ˮ(4*V1=Ѿ?$ONZB3ICؿuSi f4:u`!( Bڕ\!'3{K>J)`5ғUH) P0 IcV- JN<~$J- SI1&/0lNGT椴\+*8KQ7O[Kc<ᡗ iO"{w§3CObTfKoCr8*7/Zg&U?!U19cl6v=p:JH|Fۦ@0K+jc E#=O@F8?nJZ6qzՑ.6d8UPʳy #Gị*&IOWCv}k^r`)EG2]*t3%s(Qk─.F5"VɰS?ur/_qV41>%Jz xyr+9))bƓbǨ}f6c ָJ!+ry~8RfP!HV*⻠pYXZҵEs U#^K16%z{hE^11iz=2V-%,$dao`)ᴂ8>HELZĈ{*2N!#\v< .f{[|B{?:mU!hlzf>Y~'ڛi/u_(ۇvnbM Qʣٞ;foS1387s݁o ;veL.+q] kc+H˅/}|\nmծ3 ͖GZ܊׵ 5jb'-(AX#Pn s uQNe OE׀aDb r1(u6Z-dM1O&iy UNn$'OBߝToK`ݵ2FN_. Vh{ϞcWRHøUwmI/3=نZ}j.Nfee>@o*+*84j) D 1E6e7CsBt!ZD("1{N0"[n k5v;W:GA7aFə4p?(<9tn W1BF4QlUQ40]M(dσE>[wu-LC%ȴZ)jck?/>S\g zP&H Y0$nhA8 ڣ )& r3>waLwfcbqb7&ZymmcF.P>qe^L8>> N( %\ :`E4%#:}Thm %'P`!5)}?~< is<>mGOBZ3kxifW{fI6,q0嗩a(>dQH$Mg* ejd_=rNFw[{@:_bȥxv¶9)?`deRx.ygA&68:%co!;4ݾp)elBԀ,]Ox`Y[]U3*mUY2m^Y־ö9; Xy0+ h$YmшςmnmЃ8k\fU{NԜd x"uNO#qN#40]3Z!s#m#Z9.;!#ܝ["Sݪ7P//pJSTP`@L6Y5io R`̎7R!rA|tݭz)+cN$->VaTSI,>q;HdޤGi/7\f MڒE9ix'ADo څXwt8vz4χYm4X6kr+jM3|"> Z}?=/B QW(#)S p/C)D{p268s dY T{]R^V}:x>!>+^e#"'g Yhݑw}5R\.y>c(~ʀђs<MmqF{缻Q PRL71)GJ՟Pf!Pr4#E.]}^,o?tZ}tjcjQ=R GcwS%tHb~\7[r\:2t9dv8I V́ȹ{Ԋc?k|QMԅYU-;Y']O^v;( BG eT]Z6uJ"&A3J)t\gW\DjDc`kS{~@͊|cvdtQp?b_g޿͝Fueiځϳ}Jݨ ՚#,z~Ī)nÖ: p?<tA` ó6Kz}Ψ @{&lN V5vxjNB5<3=BY^yvun9v*ڱx7U`}ufV4\ .3nX1C#MlQ'Ljrx&$T6q+*X${)ÜEa!]R>L89Zsn zDk9dXf̔f%^OFS'夯cUyJZ;BgWʘFX253f8)* Wf ȆgCXS);ߩms+.eXSj(mC.gEk `Vá?N&diBm9\&˥b7~#?3evgp!f[kK9uωǁzűiS}RONTtZ 28Tr\0j M$7UH'f={ 74UVŴcW؜['ΥdL`Tjx14 -Ҟ6MH랕g=F*`zdj,@&l3%DYk. \Aa469qut#KyȎp5NY"ὤҕúGmrH&IO@,ߦmzpBDYdKtIQ&+ 3kx|UqofOHsw]ãd8^EIC# J61xQ4/N6=5@s0:A`!"ͿTXGE=UĀ*I?4vt|BJV9A=1 M P%(SQ00[&two:/xFa{r8CPJfsjhمQ!_gc-L>ʾwZg'Ӆi#lM⟝FmJ+P:AzمE5/!'!1;3H=,x2SHAUHj2 q%0 4]~ [ \dH2(C M"<(A#ЪJq4.զ K; Ȅ]v38$a'y,Ywrmx`ď/~8Aϭ8>N RY1ݬj {?'^/0?:xI#~gU 1Sz۾ W%t(ywx-0v: mH%͠ik*=|A85 Kc͚a >TyIN6R6Ouhj,׮Vo˿ƤR憴 ǖ:Jo:ga+{P-;ٛR]>&8-Gr͡O۩60"&o@=3~N lбGӪ+i:6kP:7ҷDzul ~8?i$NxU<;*J>=(ARe0b+V5jmk 2knB2')Z LeӿIє+ {`i'NΒ%xVPz"2œr6/!1]j/RdGp^j;s X|/TNy\ώA1B^٠L8fu燣"ߩ~D odc#$^>IZȑdu}*z*1#Fլ15BTpJBEmi{4qIoqp! 7dpGfX7SdSHH`!>v'y]A3{{ƊUK3 pN aN- S'?E$1ܣ^=Ζ+둵;s8+!+!dngH-:GM aݚ2kʜe͒ !x~` QgCHih~^*iCzƗ"!fTFJf)C EluH)<1'kҹ  {?𕸙Udޒ5h5[u^a (. MIr"(m| '^۸#aQQp5uuO=ٓZ+}t+tx> 6oZ-M- OVhp&S&i\/hoG̫<=~e,y;%|9#AÐ|~;@2 3JsjnhyBKQpL 5ʐTbf<^?/`q$4Q"B.2v0!fqE7x8`o)zZ?{Lz2NƠ lkmJb ]*2?Dш:ɳG`^4` ې *04 8T CNZ뱝N~G&g™h(`_nŚo6fj~= o^'w9B9jX8XjTl X"/veQ\umV>N5%=0^^JSTK+K2nTF;}G :E7QRo[18B=:0) UGw`7N#? <õ!Hѣq~&OzЇ_,`\__j+塋9p?o 'khx`;N}0QVp޹c˙UC$GWG7UqV}і{da3aW j#d\`]LZ@ȃh %>.f9)tJ`!gk9Ng%ז6lY5 DN'cI8x _CA* ij-ه!uՠ_fAѲ/9/G:u .EQZGtU -$KGpw{ìF-\ bΛqe7]eg,[(ק*R*\3)ʴc9us>*D8_d孍 tC!Vkqj#|ms ȳeuI&\B <è`KKoL&3;i}R5Uhg '5f_s*_i-{+1=j}KO#7) 9x\ mv0zIALt|hTLJf}VpvUK[,8\xIq{9lR~=gM^Dé=tr6aXP+,HTy}Y"Ҩ\5̩Y-:ɿ}2T1})s/ y'ᡲ" :<JE.h/W L SKyVO%O:ɟlamNq}|?)lIq7`f84 a(Pnv;Xf,ƅh̴M(!SMN#Uڗ0?@]8zƋaCҰ&u{4@&`ie73^oחH>Y9JnIlad;|!;y"7ɛXER:l`$0l+тdˑ<ȤۂX!qwRsigWioRaLolhZ2#Pȇ5+O pxW4i8?K0suRs[SAw$\u+9,m/^hp'SFiA-b4D)MAO8\4#l{n1Rtui34WϵR0')l{HY &"vDbӨ6sP]' C)=wܿjb8 5xsX,kAsPTⵜ:^50'fiNr [3,r[JMNQT4YF,e/ w϶~%IrY$ksco3@䌘+,Bx۟6;,K iM[l|9p֒>MzikPZo=26* <Ԧ)!Gv6ɵ;iTRP.u(RUp ؇% m4Z|wq2w  \2a Mr ?XDcث_;@*ɸҜ ? $[# }=gqd1ܿZTK4nOd#WM=>Tf|?@E c@~qZYKw0LF%7m;2%Gk>2ڏ]!F[h"+Kd# -|UP5D}`% Q2_[c@'r7VwVu}%WgMq?JpSNw8\%fKnIꪐK;?;@8ñӦn-֚A\i,Yu((PZ=]5 qŁȡTѾ_+ }S9?EZo@qξ rTѺI:Gp+U,?(LM UT%I6<Ҥ S2ã ~fo kra|C,d.(htcy!7Y8/rʧ#nN"E5pL`]eN J@H&lG)ȶG칐9"֫z E՛ G\FEjO P@P3>HiBifV8}Kк qȋN|NL6H.RӢ8cl3ճL'vE_M~,Ggx =.&O0Ei3]01&RذKf  L-jv&xYiB| ߎ=w\;.y/wdڮۿ]#=ѠIbjfj$6m WuƮjoFwL!^ݧ*g^J\F#$PQP /FD(W-z.xpLz }uRZ :O 9;RW9qf>hٙGA3Ga@Ib$Q :MHbq,*ޭ.F&;/ĚtR'7Xۻ|6VB9{F\+/.j,ϠWlgfGpj=J.)KzepbòhcǎEm9Z_͒Dd.dV<^fǭ4 b[/+.9-bԪJ1pM»=$eEsS&Iʨb `@qzG^J#kȠ wJojw6g>iu,JQ^ftj_Sz[1i,GQ/nUW/*nܚGQm[a#jmJ FZwhc XRś'AW7t3t~|#bXF', '{D>ڽ&0ᵊUzP*1:֊k2=MS̀e8bz3Cu 6Ց=3ӿSڀ/T#,?ͥ2JQ7SMfY){w;39,=(ب8L~-& ۂ*9}!yhTVOVŁ}u{&^[Q~WD:(oӿ?YXY.V(GOp}XDEdqrAJ& ^l+Lw^g6ҊCZHowA|PDH 85\ +d5Hˆ9ƯS0XZW-{U%(!iZ\Uݯ"ޏo/.+H Z„)R)Yk 7-$^%x/Ta9QglSnwWee%][u*B Vik-&^Xfrռ6v,$+ X -zJoR::b&dLY%^DF !uu8Ka/Tk6U|6zjEOa2Ev5V ilGYrSEapttl>k~t6M+E:(!%g)Z X*$;uAom uRe␁zZs5Cƽ%nt!wv0; 57q۠CO`?AS- ~voV3I#F.(/I۶,k۱C>:MGfVv^2/s0@O`UwVdΓIW3sUz%\l.v혝 oV^S,&+Zhr&kρ5ϋ,#%q޳j(>.f2 OtWaAa#u@s/' w6>}Bg̒&Tt;}~rY51LVd]fb۪Uйs>5mx*XeaF7R UEڌ_1cA׵ܡB$G /O%AJTF ۪V'WjZ7#[6D0̝gZԒP)^̽6MƱ-ʞkO xD7 t!ErBN4VwWؖC (sE?W[ϘavJ@AyUZr1@Kw"H*̾W܀ky׻ g|mX )Ib: i8K3M44k(;C@87c%P:%FuW+0g0bɺ]P~ևeTw֣߶z.l5/;0bTWTgW %C ҊZK 1~jNc#ԝʺ}W  [.ablG5pa kC!2W5q~K$2 aybqb=Z2pt ! SQsLh4sSA|'&~T?vLdCD45gxpTz}hg'3Tcsj39uFu,Y_e2cZp3xN ԁذ U>TAfOr4k56̊?߶mt95v$ :PIwRak^c/}f" Qi\|A"Le ,oG1Ms+xierz/_[3HHMnhKҴt+[\Vyچ{۫ȱ,'ΩZA[,Uƨ=zܺd 2,\#!W1}ֺ?TB OLA\czSz;":nDQHHȓ yȒ!L׃=*DQ9hAO;Չ't:Kg\(VQ XP|ef|hJptE {ms+Mp{2lO{c4uҮzif}H< M_(a^ziV*uFƭ閸v 3I$ k}&alDüBDze¾i'ӓTU:ƃ@yb,JJap 'NODA\ =#yf3ؚv;wxs Pro{m<@3%vox8]ƫ= 'uv0*#?N)# 1/p)vnICNQ8MSD[M'Ǵ[eCn\:9 TEBv{4o Ć[+֯yqp, EǝSQ>RC {^ʙn;nb9YV+һ;j<'9"!hi@A!(x, Zf;!_QTG~|a~ޗޟ{)NQRO`s GYݻ^M5Ake=WRݚOyٝ!:%g|vS AoB=)J醾w)_iVSbtZ4UH3ӷR?L(ӊmjvyt_ЮD?\ [a݁&DP&KGnj|>$;0\ `,VC40ǓgK6_ߪ.`댑|,=E*&pԥMZYӘ;|z"P QQWz+P_cowq^MXqtm[^Zƕ4ݾg5ATzĮa6CZB̌ZfƽG hA9އ4fMU1H%whQ:G 0?=ʾChW4~Y \+A𗟛Yk؛w)or׻DbskKc(Μl\xY. ~&"BwmBe.ԁ ɘ:*CH]p8bi~rGI)j:;W~KqO?`D톳jLhLh'mgDVo{ЎY+(Poi&/ҁu~C/)Q.B#dJh%dTsb^ ҔTR$.IdX+e»4bC5+[3tmڅ׷d K=/%ؿXIw 9/):LHqq[, 1DvmY\3@ z۾=tŏ!MUuK q_k~ зf6+RízL+eͬA^>bws݇rmEX)VKCKT G1uX{!M_=5!9.N/OBHbЙxC|r"P/jy;淇&%C`WPq%>SMT6yFQ;+]q2DDowO&FwqP Ю|[i2k9t>3Yw'ұvngF?uĮ1B{<9,u*݃&Zb {̼ Yg*Y/41paջ(8E ."mبpW'53;\Nxɽv|ԩ=Ӝ:CKhyyqy_1L'mj= nE+P2gL@DT75;=r]QhJwK=[N>{E|nZެZuJ}aDV &Th!X&w{@ 22a]p8jn̕<~f]lf)48xD-yگ OAI]Hg b=zO,:Fڍz}jQ 2?s!.a%HJ%G;F,S/-%+C̆ȦJ6c"vGJ+- ktlE\JZT̑IP|BU?iݙg V2,[){-f2w=1sݻsOghO Dpo t_fsI'xj߁d׊sr+)X {BqiGx`b!GnfDUͿM ىM2J(eftt K#!" \J: ~f@s QbM!.ެ*hR/,R ڕ9 Ict w0Qfbߜl޽c5[t:f9+כR87v:}arf)|oRCNkp? ϐV5睒ɝp3p! }vճ'+:4୶[pD[!A6 ';C ㊵Vh7"ITK*i wbJdI*1t(쁁zmsŒg+\pR>-,>PU̶O-P{Y@{97@p@ 9|6$.v+!=֠3(ǬNJPY4qmI-;wf"$͵S8B7B1]sq724f}Kf[qֻLۓ4QVJZٰӣTvXE(eoCV8v 7vbަ3.ΫWb*o11K&`H@Kۨ2Rfa 2rN2Q(X1{ϕk۔ۚyd"G孒R!5b羅3_HCLo@Te#* Ɣ.JqG:K!^Uf/SLž.t"rY47 yʠ+Ţ*ns]pxF+*g`W1TR1S7/uhKq\O`$OahS^ Y&i1DnqkhQ.lh#07rr>'BbIf|Ft=@`.7o)vc[R[E:8Ƅ¡㒈zq_z`ܒQȤ&yv*¸r3fs$ă/HooI-_6)e6JؕFiENj5w  8>ȕv8KyH`ˡzȢnВٌjw"!=v~<8A[0Px $zˀYVA) gd \[hlha.| ESei%ehxp=FzU3RMa m:v<6 (Ѡ |߂Ĵ`xT 3}#<]aҥO~a&Y4:#K-Z62ց͋J5R[U0{)0FE:&3[tlR;ZCV2Nŵ@%A ;ߺ[ sj~M@r >&/3X(?GN%ѓ󏔜B#ԧ,fMU}*A{V @tMW3 ߪ'9σl)yjrzh&6,J7\ٶƗB ^gP1iXJ-C 7r ~u&*Zٜw4šݧw [4x/0: ׌]CZ9['ZHUK؏ H^&٭G/+#Ćabkefd[rS6 RA9uAPhq[bTdDl:(qrt{M/RM ] j<>O9 yn|%8`fOY"ɰ2lC=rʝIi)J%ˠR{2ڻA4۩k-d cd\n t{Y}#ȢxA\"ju4Yjf̖gGPba<`!*:g=jƳ( Q?WRfab^qaLZm!M|QhL.S M͵#ŀd*N"<<p]lT%IN=s|$f `aym).ݩ&D9Py'.sCs}ve>S*YJo4N%06b$GM)0ZqQ v` -D:$@mMrLAXr&R yӾ6)y,ؓxiT?w@XE2f!Fea8TW=DOav/enjRJkIF&;]PܞWd5:)B29jdS X:/<`4X`VrO\#Z/bX*9@XT;X/L5AA]qBDH0zvk^W#邠Ss#/ک4V,$$ LDX_RY/K#wĘ8C<ԍR 8uWZ,3M#F+ ΡD,T.&S,hGjuhT g5eDބ%/ϗkJ8m c$E G+ևmO:ԘgF tZ17m6{R {'O VaoI.q骏AU~b&yWu .VLBe5{jw؝wZ80'Vxu٢# Iqu|q8˅F]сf|!>7Rx[kz7J@z@O"t>6BY4^ @'9߷[VWrAIDOq/aT]&p/DN+S嫼NΡc@HC)|W1mg&I =,F2f,QT\cf :K,n_ܐWP4a|Hq/BbCқgVem"Nث==kk ˣ1r͍He5--8'2Vr]Lwxi2(3̻JѸX"w{@QOks fm!'5L$wM"O)x۞\ v0Jg#MзWHƙi$Fp%ypU)Oǹ45:A$QQlY^Dx|vŢLAĚKE12/uuEPI"Y;DYgC3w<]fNZ|&LyP9#Lnu]u$?@zA WhX)I$;"GdxXxv ZOѻW= +qdowH3'hPd $ .@k"ߝqWYG:סCN az<@o3R, l%xWة˓j`օ ^3?.ɵ"7 \S x3|cSD[1TJu2`b;e!>fn.NVAϒK_[bjl"7@rPKq8ro=BHBlm1eaw3߉4fO/ZtM=`>N'h?]b |#+ڑɖLPIJ){K=NYٟV9DG>~giGDź[l(َM/#E*#0Org;~4\9*9_>ֺE:\{#`okãP훍`:d_J~l!4+g =uҚi#o hO~ B:k>z  a 1OBS^qC Q>1{Y/R jq K p rhf9!sב0EME)'hUĎ$Y:[΋aOC/W$1 f90|8<L ?1հ`PpTSucq`2qUK`r!C%k[C,B{crah8$]q ٷWj(7g~ZaN֪zҒ^sCۣʝIh_>I+㰓Nu6/G?d4M<"EQZv5~/1w:|tp#iCzB  V` j$ ~ar 9rn 6Qܠ'i5y-ٺlt4|t>4BOW^,/O#SLH[Zь)?ß7dNR3[$d!^yRȄ|L~]xrEn7N됂ԽjSݿo hATĆⱦGF-M,+/ǀ(ShC;)鼳N5hMgHw9'{2ŻeE7zˊŢA"NEk*;<5!`COa\Ⱥx+V*KNg>'zX|t %}ґ[$|^M`/ެ;+f")&KLeJ2B!]=ylrw6C7 \|>JИ-8.1v^FvoDAv\Jf@t9OD:= RB$81ʆ#FvE ۰Ϝ I@ޔŋ%ךCY_ǂ卖V8;p(K]JY$JP&Dm9_l hOTE7nc3[?%%& .ȏeAT#]Tjn@)xm$N{2t){( *m3X/ӲƻW<(> nzxJ.Gg"2e%AW<ou]ރ[v geJQs Ud @&~vޒ͍˚dr tCzD$;`?^%v A"dU[QBĬ' OF?Wa\OQ((ͪ9.g-ig !֪u(<7_<8Ѥ]&Q+ `n3O*O|꙱+Ra#Zӹ ~u,Lp #f`(40ѱar:z9;Dt]MnffVPmz&mfY܄ Ŕ2p9;k!)|ŊyXO =i؇˖VUjd,b $>4;A1_Zw$ m"& .K6:5]hST)^t]qCGzj6yaPa1]e9'x^%oPw^qRM <um{1dv9:ϐr:X7$}0k$hB| ,!|lb`U8Yvn1IkcУ+OH1+Fhsd] Fǵ7R:Sf[|c{RyR7IԲk}Ur-pCƝtsPm~r5d IqBQXE˔[ 5wKY5"b_7US(MC(-+_Xv償(W$xc+Gѷѝr͝iԕQw>جR#KTkӟqv>?׍*@w+moӒJ{'My,爲yk1b U}ʴG\DH#}|w'|C%|3۸E[VW%6Ttܹo 2drY)֓vw|-X=Y >M<,/-&&98ՄB'?q>@]'CyOȚhdZ_1ҭeGL~[Z_Kz}&O"D[j;uI`('kB^hp;E+'x_mա1a7BPRlH]"'*Qy'Skg`'_SxZj!͜{)moXMfw#r6P, n+#zFHZ *]n,m|d  >N2ߌ`0@y#VMUsqՒnB> ̓ߝ@K$[W)/9DV:R1P4$X8ˤ \Ō1ReE饊km rZ^I:H1#u FÂ3.?XA̴>bΟuyM~u %0d?P7BTTPyI|vZij89 wvD3Xbh,kb#EԪnAq?.?6lE3-XsJ+2/.]@$cnn,(n~$leR | $/sX^'えz<Yj(? } 4r e]+hY7glzMs8O fAQHB@Yڪ~:"Ef7dYE(H&,:* ]x\EVYH,u貐$5piNnsr2Q}؏zBA^*%Q _\E6,sU [ 8:Ysmգ[)?Z&Z{[ddΧp9Z84tEˢ}aA9zI,tSgСue+ ҠBd F!J 7Yz禎0ګr,fB&T+o61ġgW&i䡚qYY=`UGa ]u} Мɐ2w$cbu鰨P U=^B8ʮIbʯWR8S?]C }:c-Lu6"[%F%Fa)p?_ fqIyGIM`پx^uZL[+F&T2NƌvGԞ)#ĝk w,-hz U{|T5dFJDm |/YPTx8 ^qrr+-+xwQm.B=Hw]3oSfLEm`:|0 i[l5scE rl Ļi iauyDaco{{"kS֜²'=Xn<:6LO6DX l0#78 UZ'"],4v'j!cK(;̡-MG~ 5nA x۵&yV tu+JY3Xѻ )$ UT=UԡN:zYv3J8[-HQr\#7O gYe b;ECW?%η=&y—' Mq*uq68ieO.ݗb x^j^dHT~i^nTl,ƁO[pE5QGyq4\Qw ÌhwV>y)0鴿!2lp t;w6f2X2@i]"A3T[%tDɊDnbg|CwBJg* Jq[][^ Q2u!c{, C] 1߾MV\wKQX"G|"0xRhi0$,үd& `"6"PJA~A#M*T_;!P|PLŏErРLkMV 4PN8:ʉe)Ga)5q4I?/.iCF!e;sUAI vlƤs]Nm4|{!Tzy;dtS@j=nW=B8p6i{.ؘ}`"Ov[MOj}AF*(1pFKN`5jɫWzo@>yɯ[VZF4N; 9KLZ2NfDG(bJ+v_~ӯҀfξ|)   4JK9^v}7 $uΎ- ~oxX-ȩW39/51۱/OO7 wIpJu2P0)̋ȣA{՗lDS.A߶P/'< ^rQ@K: Too,/̯h2`9n4?eɯiʫSG1 MMϪWJrkt |%_HX_Uԃ'-!g\W XpOoGX/ q^("U}T!*r/dvN> oǟ r42+!Ϩa׊3,om{js',sm ;)akIj&gS@̻()گV{s0 Ȃ 7:ޏ;[LOR-'sk@X~\_W. %݁0N|JIqL!6ҵ{X+,iac8.sCgAh=(ÛuK$Pk.EEUkw\ dBQ:,ьP2 ~G9Z[(uIkZCMibt &HJ0&ͷXk-lPYh!H! gtH>CCiqKSEz(1\d8@Cƙ\*nbKTү( _Zvㅵ`'Wt`m2OMSH<תZ˘IG@ACF:_Uy]`؊EH!0`lml=ԍNN&Kn)3oœ;\u# PdKh-= gQ(+IK(#$zI0MylVSgtvprԳXF Ǩ7*gjEdC9!>iq&T80q!̔sɹyTUp\,.X3kV:_T E>2kh:|}Z_U\}棢u^g%)pѵ]sy_^"pD#~EԩtS@wmZJɕbF(&=w땛#ɐN|X|K =atlb],f&NE8Jwn\#ж$E-1 _/dUU#f*?碏O |2MtdSUe rQ*{yT0r+r lq,ѣV@He-eV.nX 3}Co,hu棅▞av"cGɲ09%D#e9P#XJWA"-ȫpk  _xJ&vHͪB GM<}IspsB뒚nT3DEcq;½Fq3Aen\WbEоxύ7{ߓۺ9Q;3EU"hQyXʹxtU'4*aA5o1;DyfвɦV/e c ;t}{8 ZUi'/OY(CF5Au4v/{b Y§ȫ#gюOヽw~v #NƼd?BoO;Qʗ\tjJru2.uDD_QoҞCn?*l.,r܆PA-}HE+X cv%!{J:KJtVVK;t|o$ͮWȰ&IASLH8^E*UzdU#ɸ2dBUYzejAk,ʹ1ALj#ufqUªݟS$ >bzQwCV~#qQKUWa$kP}6Q[~eM1oMMQJl|`v~rkt\(.lV,?L_,7jڶ))n>ă!pϖ7TJ OO}HRzQI MJuq6p$617Yz_Dů¯ GqC/ͱeu^xUTY]fߜɟO>MQ ŕRN,j9L\YX9=Z¡૩gD% Eտ$ݴ_ o>U@h#'̸Sw)(>eFci᤭oL<7+<}„ ؛CƠR$١EëYOH6$Ők~hb3z2Od>.iX KlJzAҧ=^cJgyueOti 6| .Yd]7K!OEFP_>5޳IfKӻy{f0dWC]5tE,(6M=&lu?x AVA1an-HLD~ʻ ftE*&:׭Ҋd0z El)R1];X.Z*A$jN[ S3rt n8CDwskPΈmts;jazݢϕI113bM͗XKK.PY3^ɴV,*GEjw>}VMZxy|/z5)H3 dwPU迃Ҹm,{ϯԖغJDUv}GXI/;T(R38# >ٸ۴VX㲪Tq1Db|!R%o>OƉ8bI7)-N:)-IO挲$Q^/Bm~k)Ђz懲l{Y/(-pg&_;O5"KeR4 }WPN$\A\|k-!‰)aֻv%^pBK*.SK/@[f\iS똺ڌɆlzsX7'Xs~n9m~"~ Z摰25lP@թO V2i'HfY3n;&U7iO_$ aR[\> SuH @+L[bU701P(ΞKiɏ]/y_T%Zp~nuJ6)ugcLUcw/!ن6eu@ʮKα.?"[G$_k;98oaRwsU 7C7b'yE$q0rwU?‹m؉5Lb#g} "䉢"}?0IQ)ױQObQf[λٙ\vRYx78%69Fs^C┃֛ qI#+' ֈ1=A) EBtVwThw)4xh%, {/I!im+A_\b?ʑ'8bבˉ8#ڞbwVB#3V*LVy'ߗs)1gy;d`",rWo=uߟf13pV粵kc!gy〟SġZXd6XO-ȼB]]-CeU_ MaL Fw &],;^GN6Tw rn\(8b_ tL%7 0ЎQ%5.Fmk,TL Ʀ!56(S '-n jw}GۺL9ߝVj~!j6;]Hl۾ '6.;yMuL)b0jXM/'6E+ݻrez( Bt!xA#< slIV̈*v\| #Zf]OkNu@6'Uへt_gII' rE͙},v 췽<.* dbJh9nȂaV\Gqa)KYmXЄRA#KMϯ2 VZG8ʫAFKi> wm̐Ӈ*Ьzdrߋ$!f1L[nhC=4c]x^j*3UMOQ!(i9z)<^?0O>|"QLu,%ֹ2NNڔ][d D+A2ˌY&~j8JK.[6YPo21|Z{'Llh\'E#\JފJ@uY0KtrNzQ6}~븖^ܪuL#{\?H0D_zO#7`|7 3d&ͥ/٥X`B]Fa67BJ۽|`Qd]' a =R{yNۓn-E;hZ3qMѦ@&' ~ψc͘Vew&Vpw*:hQ@nv0 * VQpe/27-f)_.K}>n"K!ov(Hǵe&+Q6[ru=Hu,zW"ae%XU#CЬlUNEZ5DȎp|mK܌fԣI#\Lh-8sov"1Cʼn1kOfJ74Ö2ߤ;n6M.@eW~k }/f~]cL7Dh. َz;`sJg-\4*&iaHZ2֓t9n "GIyWK ;_+)jW"#rYB0<)-ϜT,xu@c]Y`@ET^g7uW4YTrEfĬm|=sfI@/x7 @ US${c'R@Rߜ) MRGiCZ+*1-٨A*'$%U4e MEehe1O2$oc;zwAlNd;"(N]G$b+J>nY0c9CqU 6|]aY\@ )|)oyFMO*xC (ц`F=:)79Fp~ P͡:fSXih$:BƜK<5[ T:F *~Mvօ|}Pƣ:'TɜRWˍ:D4$Wz\W Jv9 ySR<"!CXZ+Y~Ϗ%S(ҨSF1noŢ[ @7jX__Ҟՙ2zgCϡ_ɊVrPic؞Huiq +\SӚtQzf]Ц`Ӏ7M)#8e4CdW&<% 3P5|=h8?ogy &46־ EJ @AT iUvY+cF5Ts):4RB9}o| NĨdg޶|l%%0þf Os5=)ʽ mKkmHHN5 &L0JBB&e,3We+Î/j^yXpk} J*/:6y~XmȏR_ Լg.Gq5UmQjm?*ZH |6Ϟ~ORC%z@$NoHF>ok+t`2FIMoΦs1˥}PЁX9|LJ=&cU&ڲXv(ơHhuT[|懡a: !Cb9LVTEF53-NA D Ρ,\V>SX,/ğUSpMqb.ZJ2ESbx3azj `1W}{A7AT6ǀ3BTn:}̧;eQa  v4ݦ]2RWRK)psw1\Zmܥfeu#hKMKiF?~\yN@B:h'䎂5[61Y5k>6lzXxJJ7=OH)5 ;19% ɡtO 3 "i`?n,@YBJK?~3}(Kюb)H| <&דCϾqFg?1u]7B5`8Ww=_oƧ>d .׀yT%U/҅OϷkQ!7҃ x{A|Ta7E֊jZ(] |rb!o'/~I?m*,Ԥoeu+QKԡc:*{z-iG~kR"{c cdĐ}ĤA`YӷkEbC<^ է] y |w5Cv.<Vni+,uoE2 r2KuӽX ׽(|YI:>0FaܱVgSmk"Ngp(Þr,Sfm׏pvOۇWBO>-(p=-nJ(Ҁ4J2x.VXl?+Ur),{:Ct BW-dV1 w6)=jӴ[-e=Eq% -U:VIot^n\pZ;[Uf=tId0vl{.=T?˰j7j\U $^vӃI~d. {\۞I:פI~>+p_ d^)a4[:?k{(VF$cjHTtV4L;VS ]C\F&V}N;^C5^ t"\Hn_tS RSL^ĒI1E­E@!c'DM7rQx^ɚ0.s߁lP&=zG^OlkѸ {9X/ }/ 6ȲU-$\xl ;~8ͰzZ`CjL bPQ=߃8`Q$'Tf6Ru{πKG^O;^ZQM,sF+$q}p̙4mڢ2x˻k[TӁ:vmEД`h]+to3Os|_V&AA/c[+/IMZzsNP0Ž%G=kEJBIg >!] Gbu G^Z$m:ljm?j^xkG. 9$lm:onX ߻& ?rw.fPOClSGJFQgSkXlPtk5(:&{|uOw P(7Y)9Fzy}{BKԦ&350EHU*.ہE8bޯ`ZK?kNF@=-k 81%xߠ؂i>Ƹ0[YT|QAl `ρ vTҡZwEf xrPIhiO~ Eg t^6=*t5͏pv_)2JQJrC2)}q'Vfu+VF84vER"Z.Q(Z92Fy:nζpþL_?( A`ב;.XuH$"!օ}\Sp!);2@;5 Zܼv!*oj;C0bp^E !B9|袦Yg&qNE:So7@JxKL58;OA= [K)7O‚D$aPymʻb^Y,W@ALQh(g*ZC߰ T>uf^KX= OS"m>Wb<\pusE_#p:!OSQ^ّ Z 2)54,DB-zD]o dOt1iTȲotƣTy,`o>7bR՗-)YY} _ȇ;򆙃%P֢<|KYBl4zV,͒,>}<Ӷ O+_Q}5"汀@vo3d$x KJaPPYBوE[|GqMC7 ~h{b: Rv}gdJdTO ߋX.~GfN׌T"_V M/lˋI0[.% D]: >cC9=$-xo\TȡmE6AO /lE:NS?{ 1gPd(ǻʨCMV10BlޢIjմܜ@>&1wǹ,`2 ՟]`mmFMO݉= -dVVĶ@?2uߦ^a3sb q8>\ACz/G,Ƅ|Gtzwb!93p!*o3E-ErmI>IH^P8Hɾ?ߟ7{J:|C+v mHγ%=_EoK  T~y;Eظmihf'/F™+2)/2ոĔAr[o@ޛY]cDM48  ,͇\:r?~>7^3po.Sx&8 2' ]tJH`&,S9>y=xNj-}brjNr #hLOd-\ͬPE3CBvi襯H Ytã^E*'8Slb:YB9jr\d0*^+܄ (,k4&h}iJ CaLDD0xji(w2 @!tؙ>ƼHD'u$,Ot:xhl8a( >`%~IO!q"D_v9O5#n!Q6,k^h{ҁ?6U#CF׾k›f[U4RWm1*yBt}C5J \B\-wH@ClhAq~rucO'-MF;y)SKpl`>"BUx$I 2[}|:gfαtrM6S!7pa*Iߞ8fl,s"ud/(WW^VQp;ٖBwt5y6\qu.}|k`t{ *ŚQX`d4enagXT[Ȯ mg 0):͐!uj.Fl\]i"3}Dd`LdL󌈂%=.5c\J\" efݥ^ނև,S-¿r%O HZh\C e~^-2p08d+Rf P~s1_X[4aVDRަl]6!Amy;_2A8E D|HGoG,p16MäLi8{+WD!H(TCuE.qTO6C&/]]x&„^\me}&r拵F[:)RFVX(H۹(a<(I!^n;|ff31HU4Sd;EcGv:e ]<05&͂ m"N6-m21 dnؠQj: >Ժe&ʯhה<}흇!_Wc$fyM)s?@K+}jgx#")ضy!QUF:UF,Y5ze.eh`i_ݿ)fIW9%+6O!F5;F05zfaY*w@Qw\ɮ2+Hi~il%dn[鴵|0PuTpt+6qKp1:_{6*SKy ʅ,(x׫X'J7SޣDHIsI3\?*_akg$o )ߊc@Or1]Y#7T L^8QRv~Ŗ1w ZHk*6G[k  #] Wʤ7././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/tests/test_fuzzer.py0000644000175100001640000000354500000000000021647 0ustar00runnerdocker00000000000000import io import os import pathlib import sys from hypothesis import given from hypothesis import strategies as st from pytest import fixture import multivolumefile as MV counter = 0 @fixture(scope="session") def basepath(tmp_path_factory): return tmp_path_factory.getbasetemp() def numbered_tempdir(base: pathlib.Path, common, suffix): global counter counter += 1 tmpdir = base.joinpath(common).joinpath("{0}{1}".format(suffix, counter)) os.makedirs(tmpdir, exist_ok=True) return tmpdir @given( obj=st.binary(min_size=10, max_size=10 << 10), volume=st.integers(min_value=5, max_value=1 << 10), ) def test_fuzzer_once(obj, volume, basepath): target = numbered_tempdir(basepath, "test_fuzzer", "once").joinpath("target.bin") with MV.open(target, mode="wb", volume=volume) as f: f.write(obj) with MV.open(target, mode="rb") as f: result = f.read() assert result == obj @given( obj=st.binary(min_size=10, max_size=10 << 10), volume=st.integers(min_value=5, max_value=1 << 10), wblock=st.integers(min_value=5, max_value=5 << 9), rblock=st.integers(min_value=5, max_value=5 << 9), ) def test_fuzzer_block(obj, volume, wblock, rblock, basepath): target = numbered_tempdir(basepath, "test_fuzzer", "block").joinpath("target.bin") src = io.BytesIO(obj) result = b"" with MV.open(target, mode="wb", volume=volume) as f: data = src.read(wblock) while len(data) > 0: f.write(data) data = src.read(wblock) with MV.open(target, mode="rb") as f: data = f.read(rblock) while len(data) > 0: result += data data = f.read(rblock) assert result == obj if __name__ == "__main__": import atheris # type: ignore # noqa atheris.Setup(sys.argv, test_fuzzer_block.hypothesis.fuzz_one_input) atheris.Fuzz() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/tests/test_multivolume.py0000644000175100001640000002732400000000000022705 0ustar00runnerdocker00000000000000import binascii import hashlib import os import shutil import pytest import multivolumefile as MV BLOCKSIZE = 8192 testdata_path = os.path.join(os.path.dirname(__file__), "data") os.umask(0o022) def test_read_check_sha1(): target = os.path.join(testdata_path, "archive.7z") mv = MV.open(target, mode="rb") sha = hashlib.sha256() data = mv.read(BLOCKSIZE) while len(data) > 0: sha.update(data) data = mv.read(BLOCKSIZE) mv.close() assert sha.digest() == binascii.unhexlify( "e0cfeb8010d105d0e2d56d2f94d2b786ead352cef5bca579cb7689d08b3614d1" ) def test_read_seek(): target = os.path.join(testdata_path, "archive.7z") mv = MV.open(target, mode="rb") assert mv.isatty() is False assert mv.seekable() mv.seek(40000) pos = mv.tell() assert pos == 40000 mv.seek(-1000, os.SEEK_CUR) pos = mv.tell() assert pos == 39000 mv.seek(-2337, os.SEEK_END) pos = mv.tell() assert pos == 50000 mv.close() assert mv.readable() is False mv.flush() def test_read_context(): target = os.path.join(testdata_path, "archive.7z") with MV.open(target, mode="rb") as mv: assert mv.readable() mv.seek(24900) data = mv.read(200) assert len(data) == 100 data = mv.read(200) assert len(data) == 200 def test_readinto(): target = os.path.join(testdata_path, "archive.7z") b = bytearray(200) with MV.open(target, mode="rb") as mv: mv.seek(24900) size = mv.readinto(b) assert size == 100 def test_read_boundary(): target = os.path.join(testdata_path, "archive.7z") with MV.open(target, mode="rb") as mv: mv.seek(24999) b = mv.read(200) assert len(b) == 1 def test_readinto_boundary(): target = os.path.join(testdata_path, "archive.7z") b = bytearray(200) with MV.open(target, mode="rb") as mv: mv.seek(25000) size = mv.readinto(b) assert size == 200 sha = hashlib.sha256(b) assert ( sha.digest() == b"6\xbc\x92\n\xa53tY\x97\xaa`!\xf3\xa7\xaa\xc3\\V\xac\xc1p\x97\xb0\xf5M\xc0)\x12\x0eD$\x9a" ) def test_readline(): target = os.path.join(testdata_path, "archive.7z") with pytest.raises(NotImplementedError): with MV.open(target, mode="rb") as mv: mv.readline() def test_readlines(): target = os.path.join(testdata_path, "archive.7z") with pytest.raises(NotImplementedError): with MV.open(target, mode="rb") as mv: mv.readlines() def test_readall(): target = os.path.join(testdata_path, "archive.7z") with MV.open(target, mode="rb") as mv: b = mv.readall() sha = hashlib.sha256(b) assert sha.digest() == binascii.unhexlify( "e0cfeb8010d105d0e2d56d2f94d2b786ead352cef5bca579cb7689d08b3614d1" ) def test_write(tmp_path): target = tmp_path.joinpath("target.7z") with MV.open(target, mode="wb", volume=10240) as volume: assert volume.writable() with open(os.path.join(testdata_path, "archive.7z.001"), "rb") as r: data = r.read(BLOCKSIZE) while len(data) > 0: volume.write(data) data = r.read(BLOCKSIZE) with open(os.path.join(testdata_path, "archive.7z.002"), "rb") as r: data = r.read(BLOCKSIZE) while len(data) > 0: volume.write(data) data = r.read(BLOCKSIZE) assert volume.seekable() volume.seek(0) volume.seek(51000) volume.flush() created = tmp_path.joinpath("target.7z.0001") assert created.exists() assert created.stat().st_size == 10240 def test_write_default_volume(tmp_path): target = tmp_path.joinpath("target.7z").as_posix() with MV.open(target, mode="wb") as volume: with open(os.path.join(testdata_path, "archive.7z.001"), "rb") as r: data = r.read(BLOCKSIZE) while len(data) > 0: volume.write(data) data = r.read(BLOCKSIZE) with open(os.path.join(testdata_path, "archive.7z.002"), "rb") as r: data = r.read(BLOCKSIZE) while len(data) > 0: volume.write(data) data = r.read(BLOCKSIZE) assert volume.seekable() volume.seek(0) volume.seek(51000) volume.flush() created = tmp_path.joinpath("target.7z.0001") assert created.exists() assert created.stat().st_size == 52337 def test_write_posixpath(tmp_path): target = tmp_path.joinpath("target.7z").as_posix() with MV.open(target, mode="wb", volume=10240) as volume: assert volume.writable() with open(os.path.join(testdata_path, "archive.7z.001"), "rb") as r: data = r.read(BLOCKSIZE) while len(data) > 0: volume.write(data) data = r.read(BLOCKSIZE) with open(os.path.join(testdata_path, "archive.7z.002"), "rb") as r: data = r.read(BLOCKSIZE) while len(data) > 0: volume.write(data) data = r.read(BLOCKSIZE) assert volume.seekable() volume.seek(0) volume.seek(51000) volume.flush() created = tmp_path.joinpath("target.7z.0001") assert created.exists() assert created.stat().st_size == 10240 def test_write_boundary(tmp_path): target = tmp_path.joinpath("target.7z") with MV.open(target, mode="wb", volume=10240) as volume: assert volume.writable() with open(os.path.join(testdata_path, "archive.7z.001"), "rb") as r: data = r.read(10000) volume.write(data) data = r.read(250) volume.write(data) volume.flush() created = tmp_path.joinpath("target.7z.0002") assert created.exists() assert created.stat().st_size == 10 def test_write_exist(tmp_path): target = tmp_path.joinpath("target.7z") target_volume = tmp_path.joinpath("target.7z.0001") shutil.copyfile(os.path.join(testdata_path, "archive.7z.001"), target_volume) # with MV.open(target, mode="wb", volume=10240) as volume: assert volume.writable() with open(os.path.join(testdata_path, "archive.7z.001"), "rb") as r: data = r.read(10000) volume.write(data) volume.flush() created = tmp_path.joinpath("target.7z.0001") assert created.stat().st_size == 10000 def test_exclusive_write(tmp_path): target = tmp_path.joinpath("target.7z") with MV.open(target, mode="xb", volume=10240) as volume: assert volume.writable() with open(os.path.join(testdata_path, "archive.7z.001"), "rb") as r: data = r.read(BLOCKSIZE) while len(data) > 0: volume.write(data) data = r.read(BLOCKSIZE) with open(os.path.join(testdata_path, "archive.7z.002"), "rb") as r: data = r.read(BLOCKSIZE) while len(data) > 0: volume.write(data) data = r.read(BLOCKSIZE) assert volume.seekable() volume.seek(0) volume.seek(51000) volume.flush() created = tmp_path.joinpath("target.7z.0001") assert created.exists() assert created.stat().st_size == 10240 def test_exclusive_write_exist(tmp_path): target = tmp_path.joinpath("target.7z") target_volume = tmp_path.joinpath("target.7z.0001") shutil.copyfile(os.path.join(testdata_path, "archive.7z.001"), target_volume) with pytest.raises(FileExistsError): MV.open(target, "x") def test_write_append1(tmp_path): target_volume = tmp_path.joinpath("target.7z.0001") with target_volume.open(mode="wb") as target: with open(os.path.join(testdata_path, "archive.7z.001"), "rb") as src: target.write(src.read(1000)) # target = tmp_path.joinpath("target.7z") with MV.open(target, mode="ab", volume=10240) as volume: assert volume.seekable() is False with open(os.path.join(testdata_path, "archive.7z.001"), "rb") as src: src.seek(1000) volume.write(src.read(24000)) with open(os.path.join(testdata_path, "archive.7z.002"), "rb") as r: data = r.read(BLOCKSIZE) while len(data) > 0: volume.write(data) data = r.read(BLOCKSIZE) volume.flush() # existed = tmp_path.joinpath("target.7z.0001") assert existed.exists() assert existed.stat().st_size == 10240 created2 = tmp_path.joinpath("target.7z.0002") assert created2.exists() assert created2.stat().st_size == 10240 created6 = tmp_path.joinpath("target.7z.0006") assert created6.exists() assert created6.stat().st_size == 1137 def test_write_append2(tmp_path): target = tmp_path.joinpath("target.7z") target_volume = tmp_path.joinpath("target.7z.0001") shutil.copyfile(os.path.join(testdata_path, "archive.7z.001"), target_volume) # with MV.open(target, mode="ab", volume=10240) as volume: with open(os.path.join(testdata_path, "archive.7z.002"), "rb") as r: data = r.read(BLOCKSIZE) while len(data) > 0: volume.write(data) data = r.read(BLOCKSIZE) volume.flush() # existed = tmp_path.joinpath("target.7z.0001") assert existed.exists() assert existed.stat().st_size == 25000 created2 = tmp_path.joinpath("target.7z.0002") assert created2.exists() assert created2.stat().st_size == 10240 created4 = tmp_path.joinpath("target.7z.0004") assert created4.exists() assert created4.stat().st_size == 6857 # 52337 - 25000 - 10240 * 2 def test_read_double_close(): target = os.path.join(testdata_path, "archive.7z") mv = MV.open(target, mode="rb") assert not mv.closed mv.close() assert mv.closed mv.close() def test_read_fileno(): target = os.path.join(testdata_path, "archive.7z") mv = MV.open(target, mode="rb") with pytest.raises(RuntimeError): mv.fileno() mv.close() def test_write_short_digits(tmp_path): target = tmp_path.joinpath("target.7z") with MV.MultiVolume( target, mode="wb", volume=10240, ext_digits=3, ext_start=0 ) as volume: assert volume.writable() with open(os.path.join(testdata_path, "archive.7z.001"), "rb") as r: data = r.read(10000) volume.write(data) data = r.read(250) volume.write(data) volume.flush() created = tmp_path.joinpath("target.7z.001") assert created.exists() assert created.stat().st_size == 10 def test_write_hex_digits(tmp_path): target = tmp_path.joinpath("target.7z") with MV.MultiVolume( target, mode="wb", volume=800, hex=True, ext_digits=3, ext_start=0 ) as volume: assert volume.writable() with open(os.path.join(testdata_path, "archive.7z.001"), "rb") as r: for _ in range(5): data = r.read(2000) volume.write(data) data = r.read(250) volume.write(data) volume.flush() created = tmp_path.joinpath("target.7z.00c") assert created.exists() assert created.stat().st_size == 650 def test_unsupported_open_mode(tmp_path): target = tmp_path.joinpath("target.7z") with pytest.raises(NotImplementedError): with MV.MultiVolume(target, mode="qb", volume=800): pass def test_stat(): target = os.path.join(testdata_path, "archive.7z") first_target = os.path.join(testdata_path, "archive.7z.001") with MV.open(target, mode="rb") as mv: st = mv.stat() assert st.st_size == 52337 assert st.st_mtime == os.stat(first_target).st_mtime assert st.st_mtime_ns == os.stat(first_target).st_mtime_ns assert st.st_dev == os.stat(first_target).st_dev assert st.st_ino == 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619698699.0 multivolumefile-0.2.3/tox.ini0000644000175100001640000000251300000000000017054 0ustar00runnerdocker00000000000000[tox] envlist = check, mypy, py38, report [testenv] passenv = TRAVIS TRAVIS_* APPVEYOR APPVEYOR_* GITHUB_* PYTEST_ADDOPTS COVERALLS_* extras = test commands = python -m pytest -vv depends = py38: clean, check report: py38 [flake8] extend-ignore = E203, W503 ignore = F841 [pytest] basepython = py38: python3.8 python_files = tests/test_*.py timeout = 480 [testenv:check] basepython = python3.8 extras = check ignore_errors=true commands = check-manifest {toxinidir} flake8 multivolumefile tests setup.py isort --quiet --check-only --diff multivolumefile tests setup.py [testenv:mypy] basepython = python3.8 extras = type commands = mypy multivolumefile/ [isort] known_first_party = multivolumefile known_third_party = docutils,flake8,pyannotate_runtime,pytest,pytz,setuptools,sphinx,yaml multi_line_output = 3 include_trailing_comma = True force_grid_wrap = 0 use_parentheses = True ensure_newline_before_comments = True line_length = 125 [testenv:clean] deps = coverage[toml]>=5.2 skip_install = true commands = coverage erase [testenv:coveralls] deps = coveralls skip_install = true commands = coveralls [] [testenv:report] basepython = python3.8 deps = coverage[toml]>=5.2 skip_install = true commands = coverage report coverage html -d build/htmlcov [gh-actions] python = 3.8: py38, check