Show More
The requested changes are too big and content was truncated. Show full diff
@@ -0,0 +1,127 b'' | |||||
|
1 | ==================== | |||
|
2 | Mercurial Automation | |||
|
3 | ==================== | |||
|
4 | ||||
|
5 | This directory contains code and utilities for building and testing Mercurial | |||
|
6 | on remote machines. | |||
|
7 | ||||
|
8 | The ``automation.py`` Script | |||
|
9 | ============================ | |||
|
10 | ||||
|
11 | ``automation.py`` is an executable Python script (requires Python 3.5+) | |||
|
12 | that serves as a driver to common automation tasks. | |||
|
13 | ||||
|
14 | When executed, the script will *bootstrap* a virtualenv in | |||
|
15 | ``<source-root>/build/venv-automation`` then re-execute itself using | |||
|
16 | that virtualenv. So there is no need for the caller to have a virtualenv | |||
|
17 | explicitly activated. This virtualenv will be populated with various | |||
|
18 | dependencies (as defined by the ``requirements.txt`` file). | |||
|
19 | ||||
|
20 | To see what you can do with this script, simply run it:: | |||
|
21 | ||||
|
22 | $ ./automation.py | |||
|
23 | ||||
|
24 | Local State | |||
|
25 | =========== | |||
|
26 | ||||
|
27 | By default, local state required to interact with remote servers is stored | |||
|
28 | in the ``~/.hgautomation`` directory. | |||
|
29 | ||||
|
30 | We attempt to limit persistent state to this directory. Even when | |||
|
31 | performing tasks that may have side-effects, we try to limit those | |||
|
32 | side-effects so they don't impact the local system. e.g. when we SSH | |||
|
33 | into a remote machine, we create a temporary directory for the SSH | |||
|
34 | config so the user's known hosts file isn't updated. | |||
|
35 | ||||
|
36 | AWS Integration | |||
|
37 | =============== | |||
|
38 | ||||
|
39 | Various automation tasks integrate with AWS to provide access to | |||
|
40 | resources such as EC2 instances for generic compute. | |||
|
41 | ||||
|
42 | This obviously requires an AWS account and credentials to work. | |||
|
43 | ||||
|
44 | We use the ``boto3`` library for interacting with AWS APIs. We do not employ | |||
|
45 | any special functionality for telling ``boto3`` where to find AWS credentials. See | |||
|
46 | https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html | |||
|
47 | for how ``boto3`` works. Once you have configured your environment such | |||
|
48 | that ``boto3`` can find credentials, interaction with AWS should *just work*. | |||
|
49 | ||||
|
50 | .. hint:: | |||
|
51 | ||||
|
52 | Typically you have a ``~/.aws/credentials`` file containing AWS | |||
|
53 | credentials. If you manage multiple credentials, you can override which | |||
|
54 | *profile* to use at run-time by setting the ``AWS_PROFILE`` environment | |||
|
55 | variable. | |||
|
56 | ||||
|
57 | Resource Management | |||
|
58 | ------------------- | |||
|
59 | ||||
|
60 | Depending on the task being performed, various AWS services will be accessed. | |||
|
61 | This of course requires AWS credentials with permissions to access these | |||
|
62 | services. | |||
|
63 | ||||
|
64 | The following AWS services can be accessed by automation tasks: | |||
|
65 | ||||
|
66 | * EC2 | |||
|
67 | * IAM | |||
|
68 | * Simple Systems Manager (SSM) | |||
|
69 | ||||
|
70 | Various resources will also be created as part of performing various tasks. | |||
|
71 | This also requires various permissions. | |||
|
72 | ||||
|
73 | The following AWS resources can be created by automation tasks: | |||
|
74 | ||||
|
75 | * EC2 key pairs | |||
|
76 | * EC2 security groups | |||
|
77 | * EC2 instances | |||
|
78 | * IAM roles and instance profiles | |||
|
79 | * SSM command invocations | |||
|
80 | ||||
|
81 | When possible, we prefix resource names with ``hg-`` so they can easily | |||
|
82 | be identified as belonging to Mercurial. | |||
|
83 | ||||
|
84 | .. important:: | |||
|
85 | ||||
|
86 | We currently assume that AWS accounts utilized by *us* are single | |||
|
87 | tenancy. Attempts to have discrete users of ``automation.py`` (including | |||
|
88 | sharing credentials across machines) using the same AWS account can result | |||
|
89 | in them interfering with each other and things breaking. | |||
|
90 | ||||
|
91 | Cost of Operation | |||
|
92 | ----------------- | |||
|
93 | ||||
|
94 | ``automation.py`` tries to be frugal with regards to utilization of remote | |||
|
95 | resources. Persistent remote resources are minimized in order to keep costs | |||
|
96 | in check. For example, EC2 instances are often ephemeral and only live as long | |||
|
97 | as the operation being performed. | |||
|
98 | ||||
|
99 | Under normal operation, recurring costs are limited to: | |||
|
100 | ||||
|
101 | * Storage costs for AMI / EBS snapshots. This should be just a few pennies | |||
|
102 | per month. | |||
|
103 | ||||
|
104 | When running EC2 instances, you'll be billed accordingly. By default, we | |||
|
105 | use *small* instances, like ``t3.medium``. This instance type costs ~$0.07 per | |||
|
106 | hour. | |||
|
107 | ||||
|
108 | .. note:: | |||
|
109 | ||||
|
110 | When running Windows EC2 instances, AWS bills at the full hourly cost, even | |||
|
111 | if the instance doesn't run for a full hour (per-second billing doesn't | |||
|
112 | apply to Windows AMIs). | |||
|
113 | ||||
|
114 | Managing Remote Resources | |||
|
115 | ------------------------- | |||
|
116 | ||||
|
117 | Occassionally, there may be an error purging a temporary resource. Or you | |||
|
118 | may wish to forcefully purge remote state. Commands can be invoked to manually | |||
|
119 | purge remote resources. | |||
|
120 | ||||
|
121 | To terminate all EC2 instances that we manage:: | |||
|
122 | ||||
|
123 | $ automation.py terminate-ec2-instances | |||
|
124 | ||||
|
125 | To purge all EC2 resources that we manage:: | |||
|
126 | ||||
|
127 | $ automation.py purge-ec2-resources |
@@ -0,0 +1,70 b'' | |||||
|
1 | #!/usr/bin/env python3 | |||
|
2 | # | |||
|
3 | # automation.py - Perform tasks on remote machines | |||
|
4 | # | |||
|
5 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
6 | # | |||
|
7 | # This software may be used and distributed according to the terms of the | |||
|
8 | # GNU General Public License version 2 or any later version. | |||
|
9 | ||||
|
10 | import os | |||
|
11 | import pathlib | |||
|
12 | import subprocess | |||
|
13 | import sys | |||
|
14 | import venv | |||
|
15 | ||||
|
16 | ||||
|
17 | HERE = pathlib.Path(os.path.abspath(__file__)).parent | |||
|
18 | REQUIREMENTS_TXT = HERE / 'requirements.txt' | |||
|
19 | SOURCE_DIR = HERE.parent.parent | |||
|
20 | VENV = SOURCE_DIR / 'build' / 'venv-automation' | |||
|
21 | ||||
|
22 | ||||
|
23 | def bootstrap(): | |||
|
24 | venv_created = not VENV.exists() | |||
|
25 | ||||
|
26 | VENV.parent.mkdir(exist_ok=True) | |||
|
27 | ||||
|
28 | venv.create(VENV, with_pip=True) | |||
|
29 | ||||
|
30 | if os.name == 'nt': | |||
|
31 | venv_bin = VENV / 'Scripts' | |||
|
32 | pip = venv_bin / 'pip.exe' | |||
|
33 | python = venv_bin / 'python.exe' | |||
|
34 | else: | |||
|
35 | venv_bin = VENV / 'bin' | |||
|
36 | pip = venv_bin / 'pip' | |||
|
37 | python = venv_bin / 'python' | |||
|
38 | ||||
|
39 | args = [str(pip), 'install', '-r', str(REQUIREMENTS_TXT), | |||
|
40 | '--disable-pip-version-check'] | |||
|
41 | ||||
|
42 | if not venv_created: | |||
|
43 | args.append('-q') | |||
|
44 | ||||
|
45 | subprocess.run(args, check=True) | |||
|
46 | ||||
|
47 | os.environ['HGAUTOMATION_BOOTSTRAPPED'] = '1' | |||
|
48 | os.environ['PATH'] = '%s%s%s' % ( | |||
|
49 | venv_bin, os.pathsep, os.environ['PATH']) | |||
|
50 | ||||
|
51 | subprocess.run([str(python), __file__] + sys.argv[1:], check=True) | |||
|
52 | ||||
|
53 | ||||
|
54 | def run(): | |||
|
55 | import hgautomation.cli as cli | |||
|
56 | ||||
|
57 | # Need to strip off main Python executable. | |||
|
58 | cli.main() | |||
|
59 | ||||
|
60 | ||||
|
61 | if __name__ == '__main__': | |||
|
62 | try: | |||
|
63 | if 'HGAUTOMATION_BOOTSTRAPPED' not in os.environ: | |||
|
64 | bootstrap() | |||
|
65 | else: | |||
|
66 | run() | |||
|
67 | except subprocess.CalledProcessError as e: | |||
|
68 | sys.exit(e.returncode) | |||
|
69 | except KeyboardInterrupt: | |||
|
70 | sys.exit(1) |
@@ -0,0 +1,59 b'' | |||||
|
1 | # __init__.py - High-level automation interfaces | |||
|
2 | # | |||
|
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | ||||
|
8 | # no-check-code because Python 3 native. | |||
|
9 | ||||
|
10 | import pathlib | |||
|
11 | import secrets | |||
|
12 | ||||
|
13 | from .aws import ( | |||
|
14 | AWSConnection, | |||
|
15 | ) | |||
|
16 | ||||
|
17 | ||||
|
18 | class HGAutomation: | |||
|
19 | """High-level interface for Mercurial automation. | |||
|
20 | ||||
|
21 | Holds global state, provides access to other primitives, etc. | |||
|
22 | """ | |||
|
23 | ||||
|
24 | def __init__(self, state_path: pathlib.Path): | |||
|
25 | self.state_path = state_path | |||
|
26 | ||||
|
27 | state_path.mkdir(exist_ok=True) | |||
|
28 | ||||
|
29 | def default_password(self): | |||
|
30 | """Obtain the default password to use for remote machines. | |||
|
31 | ||||
|
32 | A new password will be generated if one is not stored. | |||
|
33 | """ | |||
|
34 | p = self.state_path / 'default-password' | |||
|
35 | ||||
|
36 | try: | |||
|
37 | with p.open('r', encoding='ascii') as fh: | |||
|
38 | data = fh.read().strip() | |||
|
39 | ||||
|
40 | if data: | |||
|
41 | return data | |||
|
42 | ||||
|
43 | except FileNotFoundError: | |||
|
44 | pass | |||
|
45 | ||||
|
46 | password = secrets.token_urlsafe(24) | |||
|
47 | ||||
|
48 | with p.open('w', encoding='ascii') as fh: | |||
|
49 | fh.write(password) | |||
|
50 | fh.write('\n') | |||
|
51 | ||||
|
52 | p.chmod(0o0600) | |||
|
53 | ||||
|
54 | return password | |||
|
55 | ||||
|
56 | def aws_connection(self, region: str): | |||
|
57 | """Obtain an AWSConnection instance bound to a specific region.""" | |||
|
58 | ||||
|
59 | return AWSConnection(self, region) |
This diff has been collapsed as it changes many lines, (879 lines changed) Show them Hide them | |||||
@@ -0,0 +1,879 b'' | |||||
|
1 | # aws.py - Automation code for Amazon Web Services | |||
|
2 | # | |||
|
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | ||||
|
8 | # no-check-code because Python 3 native. | |||
|
9 | ||||
|
10 | import contextlib | |||
|
11 | import copy | |||
|
12 | import hashlib | |||
|
13 | import json | |||
|
14 | import os | |||
|
15 | import pathlib | |||
|
16 | import subprocess | |||
|
17 | import time | |||
|
18 | ||||
|
19 | import boto3 | |||
|
20 | import botocore.exceptions | |||
|
21 | ||||
|
22 | from .winrm import ( | |||
|
23 | run_powershell, | |||
|
24 | wait_for_winrm, | |||
|
25 | ) | |||
|
26 | ||||
|
27 | ||||
|
28 | SOURCE_ROOT = pathlib.Path(os.path.abspath(__file__)).parent.parent.parent.parent | |||
|
29 | ||||
|
30 | INSTALL_WINDOWS_DEPENDENCIES = (SOURCE_ROOT / 'contrib' / | |||
|
31 | 'install-windows-dependencies.ps1') | |||
|
32 | ||||
|
33 | ||||
|
34 | KEY_PAIRS = { | |||
|
35 | 'automation', | |||
|
36 | } | |||
|
37 | ||||
|
38 | ||||
|
39 | SECURITY_GROUPS = { | |||
|
40 | 'windows-dev-1': { | |||
|
41 | 'description': 'Mercurial Windows instances that perform build automation', | |||
|
42 | 'ingress': [ | |||
|
43 | { | |||
|
44 | 'FromPort': 22, | |||
|
45 | 'ToPort': 22, | |||
|
46 | 'IpProtocol': 'tcp', | |||
|
47 | 'IpRanges': [ | |||
|
48 | { | |||
|
49 | 'CidrIp': '0.0.0.0/0', | |||
|
50 | 'Description': 'SSH from entire Internet', | |||
|
51 | }, | |||
|
52 | ], | |||
|
53 | }, | |||
|
54 | { | |||
|
55 | 'FromPort': 3389, | |||
|
56 | 'ToPort': 3389, | |||
|
57 | 'IpProtocol': 'tcp', | |||
|
58 | 'IpRanges': [ | |||
|
59 | { | |||
|
60 | 'CidrIp': '0.0.0.0/0', | |||
|
61 | 'Description': 'RDP from entire Internet', | |||
|
62 | }, | |||
|
63 | ], | |||
|
64 | ||||
|
65 | }, | |||
|
66 | { | |||
|
67 | 'FromPort': 5985, | |||
|
68 | 'ToPort': 5986, | |||
|
69 | 'IpProtocol': 'tcp', | |||
|
70 | 'IpRanges': [ | |||
|
71 | { | |||
|
72 | 'CidrIp': '0.0.0.0/0', | |||
|
73 | 'Description': 'PowerShell Remoting (Windows Remote Management)', | |||
|
74 | }, | |||
|
75 | ], | |||
|
76 | } | |||
|
77 | ], | |||
|
78 | }, | |||
|
79 | } | |||
|
80 | ||||
|
81 | ||||
|
82 | IAM_ROLES = { | |||
|
83 | 'ephemeral-ec2-role-1': { | |||
|
84 | 'description': 'Mercurial temporary EC2 instances', | |||
|
85 | 'policy_arns': [ | |||
|
86 | 'arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM', | |||
|
87 | ], | |||
|
88 | }, | |||
|
89 | } | |||
|
90 | ||||
|
91 | ||||
|
92 | ASSUME_ROLE_POLICY_DOCUMENT = ''' | |||
|
93 | { | |||
|
94 | "Version": "2012-10-17", | |||
|
95 | "Statement": [ | |||
|
96 | { | |||
|
97 | "Effect": "Allow", | |||
|
98 | "Principal": { | |||
|
99 | "Service": "ec2.amazonaws.com" | |||
|
100 | }, | |||
|
101 | "Action": "sts:AssumeRole" | |||
|
102 | } | |||
|
103 | ] | |||
|
104 | } | |||
|
105 | '''.strip() | |||
|
106 | ||||
|
107 | ||||
|
108 | IAM_INSTANCE_PROFILES = { | |||
|
109 | 'ephemeral-ec2-1': { | |||
|
110 | 'roles': [ | |||
|
111 | 'ephemeral-ec2-role-1', | |||
|
112 | ], | |||
|
113 | } | |||
|
114 | } | |||
|
115 | ||||
|
116 | ||||
|
117 | # User Data for Windows EC2 instance. Mainly used to set the password | |||
|
118 | # and configure WinRM. | |||
|
119 | # Inspired by the User Data script used by Packer | |||
|
120 | # (from https://www.packer.io/intro/getting-started/build-image.html). | |||
|
121 | WINDOWS_USER_DATA = r''' | |||
|
122 | <powershell> | |||
|
123 | ||||
|
124 | # TODO enable this once we figure out what is failing. | |||
|
125 | #$ErrorActionPreference = "stop" | |||
|
126 | ||||
|
127 | # Set administrator password | |||
|
128 | net user Administrator "%s" | |||
|
129 | wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE | |||
|
130 | ||||
|
131 | # First, make sure WinRM can't be connected to | |||
|
132 | netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block | |||
|
133 | ||||
|
134 | # Delete any existing WinRM listeners | |||
|
135 | winrm delete winrm/config/listener?Address=*+Transport=HTTP 2>$Null | |||
|
136 | winrm delete winrm/config/listener?Address=*+Transport=HTTPS 2>$Null | |||
|
137 | ||||
|
138 | # Create a new WinRM listener and configure | |||
|
139 | winrm create winrm/config/listener?Address=*+Transport=HTTP | |||
|
140 | winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="0"}' | |||
|
141 | winrm set winrm/config '@{MaxTimeoutms="7200000"}' | |||
|
142 | winrm set winrm/config/service '@{AllowUnencrypted="true"}' | |||
|
143 | winrm set winrm/config/service '@{MaxConcurrentOperationsPerUser="12000"}' | |||
|
144 | winrm set winrm/config/service/auth '@{Basic="true"}' | |||
|
145 | winrm set winrm/config/client/auth '@{Basic="true"}' | |||
|
146 | ||||
|
147 | # Configure UAC to allow privilege elevation in remote shells | |||
|
148 | $Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' | |||
|
149 | $Setting = 'LocalAccountTokenFilterPolicy' | |||
|
150 | Set-ItemProperty -Path $Key -Name $Setting -Value 1 -Force | |||
|
151 | ||||
|
152 | # Configure and restart the WinRM Service; Enable the required firewall exception | |||
|
153 | Stop-Service -Name WinRM | |||
|
154 | Set-Service -Name WinRM -StartupType Automatic | |||
|
155 | netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any | |||
|
156 | Start-Service -Name WinRM | |||
|
157 | ||||
|
158 | # Disable firewall on private network interfaces so prompts don't appear. | |||
|
159 | Set-NetFirewallProfile -Name private -Enabled false | |||
|
160 | </powershell> | |||
|
161 | '''.lstrip() | |||
|
162 | ||||
|
163 | ||||
|
164 | WINDOWS_BOOTSTRAP_POWERSHELL = ''' | |||
|
165 | Write-Output "installing PowerShell dependencies" | |||
|
166 | Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | |||
|
167 | Set-PSRepository -Name PSGallery -InstallationPolicy Trusted | |||
|
168 | Install-Module -Name OpenSSHUtils -RequiredVersion 0.0.2.0 | |||
|
169 | ||||
|
170 | Write-Output "installing OpenSSL server" | |||
|
171 | Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 | |||
|
172 | # Various tools will attempt to use older versions of .NET. So we enable | |||
|
173 | # the feature that provides them so it doesn't have to be auto-enabled | |||
|
174 | # later. | |||
|
175 | Write-Output "enabling .NET Framework feature" | |||
|
176 | Install-WindowsFeature -Name Net-Framework-Core | |||
|
177 | ''' | |||
|
178 | ||||
|
179 | ||||
|
180 | class AWSConnection: | |||
|
181 | """Manages the state of a connection with AWS.""" | |||
|
182 | ||||
|
183 | def __init__(self, automation, region: str): | |||
|
184 | self.automation = automation | |||
|
185 | self.local_state_path = automation.state_path | |||
|
186 | ||||
|
187 | self.prefix = 'hg-' | |||
|
188 | ||||
|
189 | self.session = boto3.session.Session(region_name=region) | |||
|
190 | self.ec2client = self.session.client('ec2') | |||
|
191 | self.ec2resource = self.session.resource('ec2') | |||
|
192 | self.iamclient = self.session.client('iam') | |||
|
193 | self.iamresource = self.session.resource('iam') | |||
|
194 | ||||
|
195 | ensure_key_pairs(automation.state_path, self.ec2resource) | |||
|
196 | ||||
|
197 | self.security_groups = ensure_security_groups(self.ec2resource) | |||
|
198 | ensure_iam_state(self.iamresource) | |||
|
199 | ||||
|
200 | def key_pair_path_private(self, name): | |||
|
201 | """Path to a key pair private key file.""" | |||
|
202 | return self.local_state_path / 'keys' / ('keypair-%s' % name) | |||
|
203 | ||||
|
204 | def key_pair_path_public(self, name): | |||
|
205 | return self.local_state_path / 'keys' / ('keypair-%s.pub' % name) | |||
|
206 | ||||
|
207 | ||||
|
208 | def rsa_key_fingerprint(p: pathlib.Path): | |||
|
209 | """Compute the fingerprint of an RSA private key.""" | |||
|
210 | ||||
|
211 | # TODO use rsa package. | |||
|
212 | res = subprocess.run( | |||
|
213 | ['openssl', 'pkcs8', '-in', str(p), '-nocrypt', '-topk8', | |||
|
214 | '-outform', 'DER'], | |||
|
215 | capture_output=True, | |||
|
216 | check=True) | |||
|
217 | ||||
|
218 | sha1 = hashlib.sha1(res.stdout).hexdigest() | |||
|
219 | return ':'.join(a + b for a, b in zip(sha1[::2], sha1[1::2])) | |||
|
220 | ||||
|
221 | ||||
|
222 | def ensure_key_pairs(state_path: pathlib.Path, ec2resource, prefix='hg-'): | |||
|
223 | remote_existing = {} | |||
|
224 | ||||
|
225 | for kpi in ec2resource.key_pairs.all(): | |||
|
226 | if kpi.name.startswith(prefix): | |||
|
227 | remote_existing[kpi.name[len(prefix):]] = kpi.key_fingerprint | |||
|
228 | ||||
|
229 | # Validate that we have these keys locally. | |||
|
230 | key_path = state_path / 'keys' | |||
|
231 | key_path.mkdir(exist_ok=True, mode=0o700) | |||
|
232 | ||||
|
233 | def remove_remote(name): | |||
|
234 | print('deleting key pair %s' % name) | |||
|
235 | key = ec2resource.KeyPair(name) | |||
|
236 | key.delete() | |||
|
237 | ||||
|
238 | def remove_local(name): | |||
|
239 | pub_full = key_path / ('keypair-%s.pub' % name) | |||
|
240 | priv_full = key_path / ('keypair-%s' % name) | |||
|
241 | ||||
|
242 | print('removing %s' % pub_full) | |||
|
243 | pub_full.unlink() | |||
|
244 | print('removing %s' % priv_full) | |||
|
245 | priv_full.unlink() | |||
|
246 | ||||
|
247 | local_existing = {} | |||
|
248 | ||||
|
249 | for f in sorted(os.listdir(key_path)): | |||
|
250 | if not f.startswith('keypair-') or not f.endswith('.pub'): | |||
|
251 | continue | |||
|
252 | ||||
|
253 | name = f[len('keypair-'):-len('.pub')] | |||
|
254 | ||||
|
255 | pub_full = key_path / f | |||
|
256 | priv_full = key_path / ('keypair-%s' % name) | |||
|
257 | ||||
|
258 | with open(pub_full, 'r', encoding='ascii') as fh: | |||
|
259 | data = fh.read() | |||
|
260 | ||||
|
261 | if not data.startswith('ssh-rsa '): | |||
|
262 | print('unexpected format for key pair file: %s; removing' % | |||
|
263 | pub_full) | |||
|
264 | pub_full.unlink() | |||
|
265 | priv_full.unlink() | |||
|
266 | continue | |||
|
267 | ||||
|
268 | local_existing[name] = rsa_key_fingerprint(priv_full) | |||
|
269 | ||||
|
270 | for name in sorted(set(remote_existing) | set(local_existing)): | |||
|
271 | if name not in local_existing: | |||
|
272 | actual = '%s%s' % (prefix, name) | |||
|
273 | print('remote key %s does not exist locally' % name) | |||
|
274 | remove_remote(actual) | |||
|
275 | del remote_existing[name] | |||
|
276 | ||||
|
277 | elif name not in remote_existing: | |||
|
278 | print('local key %s does not exist remotely' % name) | |||
|
279 | remove_local(name) | |||
|
280 | del local_existing[name] | |||
|
281 | ||||
|
282 | elif remote_existing[name] != local_existing[name]: | |||
|
283 | print('key fingerprint mismatch for %s; ' | |||
|
284 | 'removing from local and remote' % name) | |||
|
285 | remove_local(name) | |||
|
286 | remove_remote('%s%s' % (prefix, name)) | |||
|
287 | del local_existing[name] | |||
|
288 | del remote_existing[name] | |||
|
289 | ||||
|
290 | missing = KEY_PAIRS - set(remote_existing) | |||
|
291 | ||||
|
292 | for name in sorted(missing): | |||
|
293 | actual = '%s%s' % (prefix, name) | |||
|
294 | print('creating key pair %s' % actual) | |||
|
295 | ||||
|
296 | priv_full = key_path / ('keypair-%s' % name) | |||
|
297 | pub_full = key_path / ('keypair-%s.pub' % name) | |||
|
298 | ||||
|
299 | kp = ec2resource.create_key_pair(KeyName=actual) | |||
|
300 | ||||
|
301 | with priv_full.open('w', encoding='ascii') as fh: | |||
|
302 | fh.write(kp.key_material) | |||
|
303 | fh.write('\n') | |||
|
304 | ||||
|
305 | priv_full.chmod(0o0600) | |||
|
306 | ||||
|
307 | # SSH public key can be extracted via `ssh-keygen`. | |||
|
308 | with pub_full.open('w', encoding='ascii') as fh: | |||
|
309 | subprocess.run( | |||
|
310 | ['ssh-keygen', '-y', '-f', str(priv_full)], | |||
|
311 | stdout=fh, | |||
|
312 | check=True) | |||
|
313 | ||||
|
314 | pub_full.chmod(0o0600) | |||
|
315 | ||||
|
316 | ||||
|
317 | def delete_instance_profile(profile): | |||
|
318 | for role in profile.roles: | |||
|
319 | print('removing role %s from instance profile %s' % (role.name, | |||
|
320 | profile.name)) | |||
|
321 | profile.remove_role(RoleName=role.name) | |||
|
322 | ||||
|
323 | print('deleting instance profile %s' % profile.name) | |||
|
324 | profile.delete() | |||
|
325 | ||||
|
326 | ||||
|
327 | def ensure_iam_state(iamresource, prefix='hg-'): | |||
|
328 | """Ensure IAM state is in sync with our canonical definition.""" | |||
|
329 | ||||
|
330 | remote_profiles = {} | |||
|
331 | ||||
|
332 | for profile in iamresource.instance_profiles.all(): | |||
|
333 | if profile.name.startswith(prefix): | |||
|
334 | remote_profiles[profile.name[len(prefix):]] = profile | |||
|
335 | ||||
|
336 | for name in sorted(set(remote_profiles) - set(IAM_INSTANCE_PROFILES)): | |||
|
337 | delete_instance_profile(remote_profiles[name]) | |||
|
338 | del remote_profiles[name] | |||
|
339 | ||||
|
340 | remote_roles = {} | |||
|
341 | ||||
|
342 | for role in iamresource.roles.all(): | |||
|
343 | if role.name.startswith(prefix): | |||
|
344 | remote_roles[role.name[len(prefix):]] = role | |||
|
345 | ||||
|
346 | for name in sorted(set(remote_roles) - set(IAM_ROLES)): | |||
|
347 | role = remote_roles[name] | |||
|
348 | ||||
|
349 | print('removing role %s' % role.name) | |||
|
350 | role.delete() | |||
|
351 | del remote_roles[name] | |||
|
352 | ||||
|
353 | # We've purged remote state that doesn't belong. Create missing | |||
|
354 | # instance profiles and roles. | |||
|
355 | for name in sorted(set(IAM_INSTANCE_PROFILES) - set(remote_profiles)): | |||
|
356 | actual = '%s%s' % (prefix, name) | |||
|
357 | print('creating IAM instance profile %s' % actual) | |||
|
358 | ||||
|
359 | profile = iamresource.create_instance_profile( | |||
|
360 | InstanceProfileName=actual) | |||
|
361 | remote_profiles[name] = profile | |||
|
362 | ||||
|
363 | for name in sorted(set(IAM_ROLES) - set(remote_roles)): | |||
|
364 | entry = IAM_ROLES[name] | |||
|
365 | ||||
|
366 | actual = '%s%s' % (prefix, name) | |||
|
367 | print('creating IAM role %s' % actual) | |||
|
368 | ||||
|
369 | role = iamresource.create_role( | |||
|
370 | RoleName=actual, | |||
|
371 | Description=entry['description'], | |||
|
372 | AssumeRolePolicyDocument=ASSUME_ROLE_POLICY_DOCUMENT, | |||
|
373 | ) | |||
|
374 | ||||
|
375 | remote_roles[name] = role | |||
|
376 | ||||
|
377 | for arn in entry['policy_arns']: | |||
|
378 | print('attaching policy %s to %s' % (arn, role.name)) | |||
|
379 | role.attach_policy(PolicyArn=arn) | |||
|
380 | ||||
|
381 | # Now reconcile state of profiles. | |||
|
382 | for name, meta in sorted(IAM_INSTANCE_PROFILES.items()): | |||
|
383 | profile = remote_profiles[name] | |||
|
384 | wanted = {'%s%s' % (prefix, role) for role in meta['roles']} | |||
|
385 | have = {role.name for role in profile.roles} | |||
|
386 | ||||
|
387 | for role in sorted(have - wanted): | |||
|
388 | print('removing role %s from %s' % (role, profile.name)) | |||
|
389 | profile.remove_role(RoleName=role) | |||
|
390 | ||||
|
391 | for role in sorted(wanted - have): | |||
|
392 | print('adding role %s to %s' % (role, profile.name)) | |||
|
393 | profile.add_role(RoleName=role) | |||
|
394 | ||||
|
395 | ||||
|
396 | def find_windows_server_2019_image(ec2resource): | |||
|
397 | """Find the Amazon published Windows Server 2019 base image.""" | |||
|
398 | ||||
|
399 | images = ec2resource.images.filter( | |||
|
400 | Filters=[ | |||
|
401 | { | |||
|
402 | 'Name': 'owner-alias', | |||
|
403 | 'Values': ['amazon'], | |||
|
404 | }, | |||
|
405 | { | |||
|
406 | 'Name': 'state', | |||
|
407 | 'Values': ['available'], | |||
|
408 | }, | |||
|
409 | { | |||
|
410 | 'Name': 'image-type', | |||
|
411 | 'Values': ['machine'], | |||
|
412 | }, | |||
|
413 | { | |||
|
414 | 'Name': 'name', | |||
|
415 | 'Values': ['Windows_Server-2019-English-Full-Base-2019.02.13'], | |||
|
416 | }, | |||
|
417 | ]) | |||
|
418 | ||||
|
419 | for image in images: | |||
|
420 | return image | |||
|
421 | ||||
|
422 | raise Exception('unable to find Windows Server 2019 image') | |||
|
423 | ||||
|
424 | ||||
|
425 | def ensure_security_groups(ec2resource, prefix='hg-'): | |||
|
426 | """Ensure all necessary Mercurial security groups are present. | |||
|
427 | ||||
|
428 | All security groups are prefixed with ``hg-`` by default. Any security | |||
|
429 | groups having this prefix but aren't in our list are deleted. | |||
|
430 | """ | |||
|
431 | existing = {} | |||
|
432 | ||||
|
433 | for group in ec2resource.security_groups.all(): | |||
|
434 | if group.group_name.startswith(prefix): | |||
|
435 | existing[group.group_name[len(prefix):]] = group | |||
|
436 | ||||
|
437 | purge = set(existing) - set(SECURITY_GROUPS) | |||
|
438 | ||||
|
439 | for name in sorted(purge): | |||
|
440 | group = existing[name] | |||
|
441 | print('removing legacy security group: %s' % group.group_name) | |||
|
442 | group.delete() | |||
|
443 | ||||
|
444 | security_groups = {} | |||
|
445 | ||||
|
446 | for name, group in sorted(SECURITY_GROUPS.items()): | |||
|
447 | if name in existing: | |||
|
448 | security_groups[name] = existing[name] | |||
|
449 | continue | |||
|
450 | ||||
|
451 | actual = '%s%s' % (prefix, name) | |||
|
452 | print('adding security group %s' % actual) | |||
|
453 | ||||
|
454 | group_res = ec2resource.create_security_group( | |||
|
455 | Description=group['description'], | |||
|
456 | GroupName=actual, | |||
|
457 | ) | |||
|
458 | ||||
|
459 | group_res.authorize_ingress( | |||
|
460 | IpPermissions=group['ingress'], | |||
|
461 | ) | |||
|
462 | ||||
|
463 | security_groups[name] = group_res | |||
|
464 | ||||
|
465 | return security_groups | |||
|
466 | ||||
|
467 | ||||
|
468 | def terminate_ec2_instances(ec2resource, prefix='hg-'): | |||
|
469 | """Terminate all EC2 instances managed by us.""" | |||
|
470 | waiting = [] | |||
|
471 | ||||
|
472 | for instance in ec2resource.instances.all(): | |||
|
473 | if instance.state['Name'] == 'terminated': | |||
|
474 | continue | |||
|
475 | ||||
|
476 | for tag in instance.tags or []: | |||
|
477 | if tag['Key'] == 'Name' and tag['Value'].startswith(prefix): | |||
|
478 | print('terminating %s' % instance.id) | |||
|
479 | instance.terminate() | |||
|
480 | waiting.append(instance) | |||
|
481 | ||||
|
482 | for instance in waiting: | |||
|
483 | instance.wait_until_terminated() | |||
|
484 | ||||
|
485 | ||||
|
486 | def remove_resources(c, prefix='hg-'): | |||
|
487 | """Purge all of our resources in this EC2 region.""" | |||
|
488 | ec2resource = c.ec2resource | |||
|
489 | iamresource = c.iamresource | |||
|
490 | ||||
|
491 | terminate_ec2_instances(ec2resource, prefix=prefix) | |||
|
492 | ||||
|
493 | for image in ec2resource.images.all(): | |||
|
494 | if image.name.startswith(prefix): | |||
|
495 | remove_ami(ec2resource, image) | |||
|
496 | ||||
|
497 | for group in ec2resource.security_groups.all(): | |||
|
498 | if group.group_name.startswith(prefix): | |||
|
499 | print('removing security group %s' % group.group_name) | |||
|
500 | group.delete() | |||
|
501 | ||||
|
502 | for profile in iamresource.instance_profiles.all(): | |||
|
503 | if profile.name.startswith(prefix): | |||
|
504 | delete_instance_profile(profile) | |||
|
505 | ||||
|
506 | for role in iamresource.roles.all(): | |||
|
507 | if role.name.startswith(prefix): | |||
|
508 | print('removing role %s' % role.name) | |||
|
509 | role.delete() | |||
|
510 | ||||
|
511 | ||||
|
512 | def wait_for_ip_addresses(instances): | |||
|
513 | """Wait for the public IP addresses of an iterable of instances.""" | |||
|
514 | for instance in instances: | |||
|
515 | while True: | |||
|
516 | if not instance.public_ip_address: | |||
|
517 | time.sleep(2) | |||
|
518 | instance.reload() | |||
|
519 | continue | |||
|
520 | ||||
|
521 | print('public IP address for %s: %s' % ( | |||
|
522 | instance.id, instance.public_ip_address)) | |||
|
523 | break | |||
|
524 | ||||
|
525 | ||||
|
526 | def remove_ami(ec2resource, image): | |||
|
527 | """Remove an AMI and its underlying snapshots.""" | |||
|
528 | snapshots = [] | |||
|
529 | ||||
|
530 | for device in image.block_device_mappings: | |||
|
531 | if 'Ebs' in device: | |||
|
532 | snapshots.append(ec2resource.Snapshot(device['Ebs']['SnapshotId'])) | |||
|
533 | ||||
|
534 | print('deregistering %s' % image.id) | |||
|
535 | image.deregister() | |||
|
536 | ||||
|
537 | for snapshot in snapshots: | |||
|
538 | print('deleting snapshot %s' % snapshot.id) | |||
|
539 | snapshot.delete() | |||
|
540 | ||||
|
541 | ||||
|
542 | def wait_for_ssm(ssmclient, instances): | |||
|
543 | """Wait for SSM to come online for an iterable of instance IDs.""" | |||
|
544 | while True: | |||
|
545 | res = ssmclient.describe_instance_information( | |||
|
546 | Filters=[ | |||
|
547 | { | |||
|
548 | 'Key': 'InstanceIds', | |||
|
549 | 'Values': [i.id for i in instances], | |||
|
550 | }, | |||
|
551 | ], | |||
|
552 | ) | |||
|
553 | ||||
|
554 | available = len(res['InstanceInformationList']) | |||
|
555 | wanted = len(instances) | |||
|
556 | ||||
|
557 | print('%d/%d instances available in SSM' % (available, wanted)) | |||
|
558 | ||||
|
559 | if available == wanted: | |||
|
560 | return | |||
|
561 | ||||
|
562 | time.sleep(2) | |||
|
563 | ||||
|
564 | ||||
|
565 | def run_ssm_command(ssmclient, instances, document_name, parameters): | |||
|
566 | """Run a PowerShell script on an EC2 instance.""" | |||
|
567 | ||||
|
568 | res = ssmclient.send_command( | |||
|
569 | InstanceIds=[i.id for i in instances], | |||
|
570 | DocumentName=document_name, | |||
|
571 | Parameters=parameters, | |||
|
572 | CloudWatchOutputConfig={ | |||
|
573 | 'CloudWatchOutputEnabled': True, | |||
|
574 | }, | |||
|
575 | ) | |||
|
576 | ||||
|
577 | command_id = res['Command']['CommandId'] | |||
|
578 | ||||
|
579 | for instance in instances: | |||
|
580 | while True: | |||
|
581 | try: | |||
|
582 | res = ssmclient.get_command_invocation( | |||
|
583 | CommandId=command_id, | |||
|
584 | InstanceId=instance.id, | |||
|
585 | ) | |||
|
586 | except botocore.exceptions.ClientError as e: | |||
|
587 | if e.response['Error']['Code'] == 'InvocationDoesNotExist': | |||
|
588 | print('could not find SSM command invocation; waiting') | |||
|
589 | time.sleep(1) | |||
|
590 | continue | |||
|
591 | else: | |||
|
592 | raise | |||
|
593 | ||||
|
594 | if res['Status'] == 'Success': | |||
|
595 | break | |||
|
596 | elif res['Status'] in ('Pending', 'InProgress', 'Delayed'): | |||
|
597 | time.sleep(2) | |||
|
598 | else: | |||
|
599 | raise Exception('command failed on %s: %s' % ( | |||
|
600 | instance.id, res['Status'])) | |||
|
601 | ||||
|
602 | ||||
|
603 | @contextlib.contextmanager | |||
|
604 | def temporary_ec2_instances(ec2resource, config): | |||
|
605 | """Create temporary EC2 instances. | |||
|
606 | ||||
|
607 | This is a proxy to ``ec2client.run_instances(**config)`` that takes care of | |||
|
608 | managing the lifecycle of the instances. | |||
|
609 | ||||
|
610 | When the context manager exits, the instances are terminated. | |||
|
611 | ||||
|
612 | The context manager evaluates to the list of data structures | |||
|
613 | describing each created instance. The instances may not be available | |||
|
614 | for work immediately: it is up to the caller to wait for the instance | |||
|
615 | to start responding. | |||
|
616 | """ | |||
|
617 | ||||
|
618 | ids = None | |||
|
619 | ||||
|
620 | try: | |||
|
621 | res = ec2resource.create_instances(**config) | |||
|
622 | ||||
|
623 | ids = [i.id for i in res] | |||
|
624 | print('started instances: %s' % ' '.join(ids)) | |||
|
625 | ||||
|
626 | yield res | |||
|
627 | finally: | |||
|
628 | if ids: | |||
|
629 | print('terminating instances: %s' % ' '.join(ids)) | |||
|
630 | for instance in res: | |||
|
631 | instance.terminate() | |||
|
632 | print('terminated %d instances' % len(ids)) | |||
|
633 | ||||
|
634 | ||||
|
635 | @contextlib.contextmanager | |||
|
636 | def create_temp_windows_ec2_instances(c: AWSConnection, config): | |||
|
637 | """Create temporary Windows EC2 instances. | |||
|
638 | ||||
|
639 | This is a higher-level wrapper around ``create_temp_ec2_instances()`` that | |||
|
640 | configures the Windows instance for Windows Remote Management. The emitted | |||
|
641 | instances will have a ``winrm_client`` attribute containing a | |||
|
642 | ``pypsrp.client.Client`` instance bound to the instance. | |||
|
643 | """ | |||
|
644 | if 'IamInstanceProfile' in config: | |||
|
645 | raise ValueError('IamInstanceProfile cannot be provided in config') | |||
|
646 | if 'UserData' in config: | |||
|
647 | raise ValueError('UserData cannot be provided in config') | |||
|
648 | ||||
|
649 | password = c.automation.default_password() | |||
|
650 | ||||
|
651 | config = copy.deepcopy(config) | |||
|
652 | config['IamInstanceProfile'] = { | |||
|
653 | 'Name': 'hg-ephemeral-ec2-1', | |||
|
654 | } | |||
|
655 | config.setdefault('TagSpecifications', []).append({ | |||
|
656 | 'ResourceType': 'instance', | |||
|
657 | 'Tags': [{'Key': 'Name', 'Value': 'hg-temp-windows'}], | |||
|
658 | }) | |||
|
659 | config['UserData'] = WINDOWS_USER_DATA % password | |||
|
660 | ||||
|
661 | with temporary_ec2_instances(c.ec2resource, config) as instances: | |||
|
662 | wait_for_ip_addresses(instances) | |||
|
663 | ||||
|
664 | print('waiting for Windows Remote Management service...') | |||
|
665 | ||||
|
666 | for instance in instances: | |||
|
667 | client = wait_for_winrm(instance.public_ip_address, 'Administrator', password) | |||
|
668 | print('established WinRM connection to %s' % instance.id) | |||
|
669 | instance.winrm_client = client | |||
|
670 | ||||
|
671 | yield instances | |||
|
672 | ||||
|
673 | ||||
|
674 | def ensure_windows_dev_ami(c: AWSConnection, prefix='hg-'): | |||
|
675 | """Ensure Windows Development AMI is available and up-to-date. | |||
|
676 | ||||
|
677 | If necessary, a modern AMI will be built by starting a temporary EC2 | |||
|
678 | instance and bootstrapping it. | |||
|
679 | ||||
|
680 | Obsolete AMIs will be deleted so there is only a single AMI having the | |||
|
681 | desired name. | |||
|
682 | ||||
|
683 | Returns an ``ec2.Image`` of either an existing AMI or a newly-built | |||
|
684 | one. | |||
|
685 | """ | |||
|
686 | ec2client = c.ec2client | |||
|
687 | ec2resource = c.ec2resource | |||
|
688 | ssmclient = c.session.client('ssm') | |||
|
689 | ||||
|
690 | name = '%s%s' % (prefix, 'windows-dev') | |||
|
691 | ||||
|
692 | config = { | |||
|
693 | 'BlockDeviceMappings': [ | |||
|
694 | { | |||
|
695 | 'DeviceName': '/dev/sda1', | |||
|
696 | 'Ebs': { | |||
|
697 | 'DeleteOnTermination': True, | |||
|
698 | 'VolumeSize': 32, | |||
|
699 | 'VolumeType': 'gp2', | |||
|
700 | }, | |||
|
701 | } | |||
|
702 | ], | |||
|
703 | 'ImageId': find_windows_server_2019_image(ec2resource).id, | |||
|
704 | 'InstanceInitiatedShutdownBehavior': 'stop', | |||
|
705 | 'InstanceType': 't3.medium', | |||
|
706 | 'KeyName': '%sautomation' % prefix, | |||
|
707 | 'MaxCount': 1, | |||
|
708 | 'MinCount': 1, | |||
|
709 | 'SecurityGroupIds': [c.security_groups['windows-dev-1'].id], | |||
|
710 | } | |||
|
711 | ||||
|
712 | commands = [ | |||
|
713 | # Need to start the service so sshd_config is generated. | |||
|
714 | 'Start-Service sshd', | |||
|
715 | 'Write-Output "modifying sshd_config"', | |||
|
716 | r'$content = Get-Content C:\ProgramData\ssh\sshd_config', | |||
|
717 | '$content = $content -replace "Match Group administrators","" -replace "AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys",""', | |||
|
718 | r'$content | Set-Content C:\ProgramData\ssh\sshd_config', | |||
|
719 | 'Import-Module OpenSSHUtils', | |||
|
720 | r'Repair-SshdConfigPermission C:\ProgramData\ssh\sshd_config -Confirm:$false', | |||
|
721 | 'Restart-Service sshd', | |||
|
722 | 'Write-Output "installing OpenSSL client"', | |||
|
723 | 'Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0', | |||
|
724 | 'Set-Service -Name sshd -StartupType "Automatic"', | |||
|
725 | 'Write-Output "OpenSSH server running"', | |||
|
726 | ] | |||
|
727 | ||||
|
728 | with INSTALL_WINDOWS_DEPENDENCIES.open('r', encoding='utf-8') as fh: | |||
|
729 | commands.extend(l.rstrip() for l in fh) | |||
|
730 | ||||
|
731 | # Disable Windows Defender when bootstrapping because it just slows | |||
|
732 | # things down. | |||
|
733 | commands.insert(0, 'Set-MpPreference -DisableRealtimeMonitoring $true') | |||
|
734 | commands.append('Set-MpPreference -DisableRealtimeMonitoring $false') | |||
|
735 | ||||
|
736 | # Compute a deterministic fingerprint to determine whether image needs | |||
|
737 | # to be regenerated. | |||
|
738 | fingerprint = { | |||
|
739 | 'instance_config': config, | |||
|
740 | 'user_data': WINDOWS_USER_DATA, | |||
|
741 | 'initial_bootstrap': WINDOWS_BOOTSTRAP_POWERSHELL, | |||
|
742 | 'bootstrap_commands': commands, | |||
|
743 | } | |||
|
744 | ||||
|
745 | fingerprint = json.dumps(fingerprint, sort_keys=True) | |||
|
746 | fingerprint = hashlib.sha256(fingerprint.encode('utf-8')).hexdigest() | |||
|
747 | ||||
|
748 | # Find existing AMIs with this name and delete the ones that are invalid. | |||
|
749 | # Store a reference to a good image so it can be returned one the | |||
|
750 | # image state is reconciled. | |||
|
751 | images = ec2resource.images.filter( | |||
|
752 | Filters=[{'Name': 'name', 'Values': [name]}]) | |||
|
753 | ||||
|
754 | existing_image = None | |||
|
755 | ||||
|
756 | for image in images: | |||
|
757 | if image.tags is None: | |||
|
758 | print('image %s for %s lacks required tags; removing' % ( | |||
|
759 | image.id, image.name)) | |||
|
760 | remove_ami(ec2resource, image) | |||
|
761 | else: | |||
|
762 | tags = {t['Key']: t['Value'] for t in image.tags} | |||
|
763 | ||||
|
764 | if tags.get('HGIMAGEFINGERPRINT') == fingerprint: | |||
|
765 | existing_image = image | |||
|
766 | else: | |||
|
767 | print('image %s for %s has wrong fingerprint; removing' % ( | |||
|
768 | image.id, image.name)) | |||
|
769 | remove_ami(ec2resource, image) | |||
|
770 | ||||
|
771 | if existing_image: | |||
|
772 | return existing_image | |||
|
773 | ||||
|
774 | print('no suitable Windows development image found; creating one...') | |||
|
775 | ||||
|
776 | with create_temp_windows_ec2_instances(c, config) as instances: | |||
|
777 | assert len(instances) == 1 | |||
|
778 | instance = instances[0] | |||
|
779 | ||||
|
780 | wait_for_ssm(ssmclient, [instance]) | |||
|
781 | ||||
|
782 | # On first boot, install various Windows updates. | |||
|
783 | # We would ideally use PowerShell Remoting for this. However, there are | |||
|
784 | # trust issues that make it difficult to invoke Windows Update | |||
|
785 | # remotely. So we use SSM, which has a mechanism for running Windows | |||
|
786 | # Update. | |||
|
787 | print('installing Windows features...') | |||
|
788 | run_ssm_command( | |||
|
789 | ssmclient, | |||
|
790 | [instance], | |||
|
791 | 'AWS-RunPowerShellScript', | |||
|
792 | { | |||
|
793 | 'commands': WINDOWS_BOOTSTRAP_POWERSHELL.split('\n'), | |||
|
794 | }, | |||
|
795 | ) | |||
|
796 | ||||
|
797 | # Reboot so all updates are fully applied. | |||
|
798 | print('rebooting instance %s' % instance.id) | |||
|
799 | ec2client.reboot_instances(InstanceIds=[instance.id]) | |||
|
800 | ||||
|
801 | time.sleep(15) | |||
|
802 | ||||
|
803 | print('waiting for Windows Remote Management to come back...') | |||
|
804 | client = wait_for_winrm(instance.public_ip_address, 'Administrator', | |||
|
805 | c.automation.default_password()) | |||
|
806 | print('established WinRM connection to %s' % instance.id) | |||
|
807 | instance.winrm_client = client | |||
|
808 | ||||
|
809 | print('bootstrapping instance...') | |||
|
810 | run_powershell(instance.winrm_client, '\n'.join(commands)) | |||
|
811 | ||||
|
812 | print('bootstrap completed; stopping %s to create image' % instance.id) | |||
|
813 | instance.stop() | |||
|
814 | ||||
|
815 | ec2client.get_waiter('instance_stopped').wait( | |||
|
816 | InstanceIds=[instance.id], | |||
|
817 | WaiterConfig={ | |||
|
818 | 'Delay': 5, | |||
|
819 | }) | |||
|
820 | print('%s is stopped' % instance.id) | |||
|
821 | ||||
|
822 | image = instance.create_image( | |||
|
823 | Name=name, | |||
|
824 | Description='Mercurial Windows development environment', | |||
|
825 | ) | |||
|
826 | ||||
|
827 | image.create_tags(Tags=[ | |||
|
828 | { | |||
|
829 | 'Key': 'HGIMAGEFINGERPRINT', | |||
|
830 | 'Value': fingerprint, | |||
|
831 | }, | |||
|
832 | ]) | |||
|
833 | ||||
|
834 | print('waiting for image %s' % image.id) | |||
|
835 | ||||
|
836 | ec2client.get_waiter('image_available').wait( | |||
|
837 | ImageIds=[image.id], | |||
|
838 | ) | |||
|
839 | ||||
|
840 | print('image %s available as %s' % (image.id, image.name)) | |||
|
841 | ||||
|
842 | return image | |||
|
843 | ||||
|
844 | ||||
|
845 | @contextlib.contextmanager | |||
|
846 | def temporary_windows_dev_instances(c: AWSConnection, image, instance_type, | |||
|
847 | prefix='hg-', disable_antivirus=False): | |||
|
848 | """Create a temporary Windows development EC2 instance. | |||
|
849 | ||||
|
850 | Context manager resolves to the list of ``EC2.Instance`` that were created. | |||
|
851 | """ | |||
|
852 | config = { | |||
|
853 | 'BlockDeviceMappings': [ | |||
|
854 | { | |||
|
855 | 'DeviceName': '/dev/sda1', | |||
|
856 | 'Ebs': { | |||
|
857 | 'DeleteOnTermination': True, | |||
|
858 | 'VolumeSize': 32, | |||
|
859 | 'VolumeType': 'gp2', | |||
|
860 | }, | |||
|
861 | } | |||
|
862 | ], | |||
|
863 | 'ImageId': image.id, | |||
|
864 | 'InstanceInitiatedShutdownBehavior': 'stop', | |||
|
865 | 'InstanceType': instance_type, | |||
|
866 | 'KeyName': '%sautomation' % prefix, | |||
|
867 | 'MaxCount': 1, | |||
|
868 | 'MinCount': 1, | |||
|
869 | 'SecurityGroupIds': [c.security_groups['windows-dev-1'].id], | |||
|
870 | } | |||
|
871 | ||||
|
872 | with create_temp_windows_ec2_instances(c, config) as instances: | |||
|
873 | if disable_antivirus: | |||
|
874 | for instance in instances: | |||
|
875 | run_powershell( | |||
|
876 | instance.winrm_client, | |||
|
877 | 'Set-MpPreference -DisableRealtimeMonitoring $true') | |||
|
878 | ||||
|
879 | yield instances |
@@ -0,0 +1,273 b'' | |||||
|
1 | # cli.py - Command line interface for automation | |||
|
2 | # | |||
|
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | ||||
|
8 | # no-check-code because Python 3 native. | |||
|
9 | ||||
|
10 | import argparse | |||
|
11 | import os | |||
|
12 | import pathlib | |||
|
13 | ||||
|
14 | from . import ( | |||
|
15 | aws, | |||
|
16 | HGAutomation, | |||
|
17 | windows, | |||
|
18 | ) | |||
|
19 | ||||
|
20 | ||||
|
21 | SOURCE_ROOT = pathlib.Path(os.path.abspath(__file__)).parent.parent.parent.parent | |||
|
22 | DIST_PATH = SOURCE_ROOT / 'dist' | |||
|
23 | ||||
|
24 | ||||
|
25 | def bootstrap_windows_dev(hga: HGAutomation, aws_region): | |||
|
26 | c = hga.aws_connection(aws_region) | |||
|
27 | image = aws.ensure_windows_dev_ami(c) | |||
|
28 | print('Windows development AMI available as %s' % image.id) | |||
|
29 | ||||
|
30 | ||||
|
31 | def build_inno(hga: HGAutomation, aws_region, arch, revision, version): | |||
|
32 | c = hga.aws_connection(aws_region) | |||
|
33 | image = aws.ensure_windows_dev_ami(c) | |||
|
34 | DIST_PATH.mkdir(exist_ok=True) | |||
|
35 | ||||
|
36 | with aws.temporary_windows_dev_instances(c, image, 't3.medium') as insts: | |||
|
37 | instance = insts[0] | |||
|
38 | ||||
|
39 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) | |||
|
40 | ||||
|
41 | for a in arch: | |||
|
42 | windows.build_inno_installer(instance.winrm_client, a, | |||
|
43 | DIST_PATH, | |||
|
44 | version=version) | |||
|
45 | ||||
|
46 | ||||
|
47 | def build_wix(hga: HGAutomation, aws_region, arch, revision, version): | |||
|
48 | c = hga.aws_connection(aws_region) | |||
|
49 | image = aws.ensure_windows_dev_ami(c) | |||
|
50 | DIST_PATH.mkdir(exist_ok=True) | |||
|
51 | ||||
|
52 | with aws.temporary_windows_dev_instances(c, image, 't3.medium') as insts: | |||
|
53 | instance = insts[0] | |||
|
54 | ||||
|
55 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) | |||
|
56 | ||||
|
57 | for a in arch: | |||
|
58 | windows.build_wix_installer(instance.winrm_client, a, | |||
|
59 | DIST_PATH, version=version) | |||
|
60 | ||||
|
61 | ||||
|
62 | def build_windows_wheel(hga: HGAutomation, aws_region, arch, revision): | |||
|
63 | c = hga.aws_connection(aws_region) | |||
|
64 | image = aws.ensure_windows_dev_ami(c) | |||
|
65 | DIST_PATH.mkdir(exist_ok=True) | |||
|
66 | ||||
|
67 | with aws.temporary_windows_dev_instances(c, image, 't3.medium') as insts: | |||
|
68 | instance = insts[0] | |||
|
69 | ||||
|
70 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) | |||
|
71 | ||||
|
72 | for a in arch: | |||
|
73 | windows.build_wheel(instance.winrm_client, a, DIST_PATH) | |||
|
74 | ||||
|
75 | ||||
|
76 | def build_all_windows_packages(hga: HGAutomation, aws_region, revision): | |||
|
77 | c = hga.aws_connection(aws_region) | |||
|
78 | image = aws.ensure_windows_dev_ami(c) | |||
|
79 | DIST_PATH.mkdir(exist_ok=True) | |||
|
80 | ||||
|
81 | with aws.temporary_windows_dev_instances(c, image, 't3.medium') as insts: | |||
|
82 | instance = insts[0] | |||
|
83 | ||||
|
84 | winrm_client = instance.winrm_client | |||
|
85 | ||||
|
86 | windows.synchronize_hg(SOURCE_ROOT, revision, instance) | |||
|
87 | ||||
|
88 | for arch in ('x86', 'x64'): | |||
|
89 | windows.purge_hg(winrm_client) | |||
|
90 | windows.build_wheel(winrm_client, arch, DIST_PATH) | |||
|
91 | windows.purge_hg(winrm_client) | |||
|
92 | windows.build_inno_installer(winrm_client, arch, DIST_PATH) | |||
|
93 | windows.purge_hg(winrm_client) | |||
|
94 | windows.build_wix_installer(winrm_client, arch, DIST_PATH) | |||
|
95 | ||||
|
96 | ||||
|
97 | def terminate_ec2_instances(hga: HGAutomation, aws_region): | |||
|
98 | c = hga.aws_connection(aws_region) | |||
|
99 | aws.terminate_ec2_instances(c.ec2resource) | |||
|
100 | ||||
|
101 | ||||
|
102 | def purge_ec2_resources(hga: HGAutomation, aws_region): | |||
|
103 | c = hga.aws_connection(aws_region) | |||
|
104 | aws.remove_resources(c) | |||
|
105 | ||||
|
106 | ||||
|
107 | def run_tests_windows(hga: HGAutomation, aws_region, instance_type, | |||
|
108 | python_version, arch, test_flags): | |||
|
109 | c = hga.aws_connection(aws_region) | |||
|
110 | image = aws.ensure_windows_dev_ami(c) | |||
|
111 | ||||
|
112 | with aws.temporary_windows_dev_instances(c, image, instance_type, | |||
|
113 | disable_antivirus=True) as insts: | |||
|
114 | instance = insts[0] | |||
|
115 | ||||
|
116 | windows.synchronize_hg(SOURCE_ROOT, '.', instance) | |||
|
117 | windows.run_tests(instance.winrm_client, python_version, arch, | |||
|
118 | test_flags) | |||
|
119 | ||||
|
120 | ||||
|
121 | def get_parser(): | |||
|
122 | parser = argparse.ArgumentParser() | |||
|
123 | ||||
|
124 | parser.add_argument( | |||
|
125 | '--state-path', | |||
|
126 | default='~/.hgautomation', | |||
|
127 | help='Path for local state files', | |||
|
128 | ) | |||
|
129 | parser.add_argument( | |||
|
130 | '--aws-region', | |||
|
131 | help='AWS region to use', | |||
|
132 | default='us-west-1', | |||
|
133 | ) | |||
|
134 | ||||
|
135 | subparsers = parser.add_subparsers() | |||
|
136 | ||||
|
137 | sp = subparsers.add_parser( | |||
|
138 | 'bootstrap-windows-dev', | |||
|
139 | help='Bootstrap the Windows development environment', | |||
|
140 | ) | |||
|
141 | sp.set_defaults(func=bootstrap_windows_dev) | |||
|
142 | ||||
|
143 | sp = subparsers.add_parser( | |||
|
144 | 'build-all-windows-packages', | |||
|
145 | help='Build all Windows packages', | |||
|
146 | ) | |||
|
147 | sp.add_argument( | |||
|
148 | '--revision', | |||
|
149 | help='Mercurial revision to build', | |||
|
150 | default='.', | |||
|
151 | ) | |||
|
152 | sp.set_defaults(func=build_all_windows_packages) | |||
|
153 | ||||
|
154 | sp = subparsers.add_parser( | |||
|
155 | 'build-inno', | |||
|
156 | help='Build Inno Setup installer(s)', | |||
|
157 | ) | |||
|
158 | sp.add_argument( | |||
|
159 | '--arch', | |||
|
160 | help='Architecture to build for', | |||
|
161 | choices={'x86', 'x64'}, | |||
|
162 | nargs='*', | |||
|
163 | default=['x64'], | |||
|
164 | ) | |||
|
165 | sp.add_argument( | |||
|
166 | '--revision', | |||
|
167 | help='Mercurial revision to build', | |||
|
168 | default='.', | |||
|
169 | ) | |||
|
170 | sp.add_argument( | |||
|
171 | '--version', | |||
|
172 | help='Mercurial version string to use in installer', | |||
|
173 | ) | |||
|
174 | sp.set_defaults(func=build_inno) | |||
|
175 | ||||
|
176 | sp = subparsers.add_parser( | |||
|
177 | 'build-windows-wheel', | |||
|
178 | help='Build Windows wheel(s)', | |||
|
179 | ) | |||
|
180 | sp.add_argument( | |||
|
181 | '--arch', | |||
|
182 | help='Architecture to build for', | |||
|
183 | choices={'x86', 'x64'}, | |||
|
184 | nargs='*', | |||
|
185 | default=['x64'], | |||
|
186 | ) | |||
|
187 | sp.add_argument( | |||
|
188 | '--revision', | |||
|
189 | help='Mercurial revision to build', | |||
|
190 | default='.', | |||
|
191 | ) | |||
|
192 | sp.set_defaults(func=build_windows_wheel) | |||
|
193 | ||||
|
194 | sp = subparsers.add_parser( | |||
|
195 | 'build-wix', | |||
|
196 | help='Build WiX installer(s)' | |||
|
197 | ) | |||
|
198 | sp.add_argument( | |||
|
199 | '--arch', | |||
|
200 | help='Architecture to build for', | |||
|
201 | choices={'x86', 'x64'}, | |||
|
202 | nargs='*', | |||
|
203 | default=['x64'], | |||
|
204 | ) | |||
|
205 | sp.add_argument( | |||
|
206 | '--revision', | |||
|
207 | help='Mercurial revision to build', | |||
|
208 | default='.', | |||
|
209 | ) | |||
|
210 | sp.add_argument( | |||
|
211 | '--version', | |||
|
212 | help='Mercurial version string to use in installer', | |||
|
213 | ) | |||
|
214 | sp.set_defaults(func=build_wix) | |||
|
215 | ||||
|
216 | sp = subparsers.add_parser( | |||
|
217 | 'terminate-ec2-instances', | |||
|
218 | help='Terminate all active EC2 instances managed by us', | |||
|
219 | ) | |||
|
220 | sp.set_defaults(func=terminate_ec2_instances) | |||
|
221 | ||||
|
222 | sp = subparsers.add_parser( | |||
|
223 | 'purge-ec2-resources', | |||
|
224 | help='Purge all EC2 resources managed by us', | |||
|
225 | ) | |||
|
226 | sp.set_defaults(func=purge_ec2_resources) | |||
|
227 | ||||
|
228 | sp = subparsers.add_parser( | |||
|
229 | 'run-tests-windows', | |||
|
230 | help='Run tests on Windows', | |||
|
231 | ) | |||
|
232 | sp.add_argument( | |||
|
233 | '--instance-type', | |||
|
234 | help='EC2 instance type to use', | |||
|
235 | default='t3.medium', | |||
|
236 | ) | |||
|
237 | sp.add_argument( | |||
|
238 | '--python-version', | |||
|
239 | help='Python version to use', | |||
|
240 | choices={'2.7', '3.5', '3.6', '3.7', '3.8'}, | |||
|
241 | default='2.7', | |||
|
242 | ) | |||
|
243 | sp.add_argument( | |||
|
244 | '--arch', | |||
|
245 | help='Architecture to test', | |||
|
246 | choices={'x86', 'x64'}, | |||
|
247 | default='x64', | |||
|
248 | ) | |||
|
249 | sp.add_argument( | |||
|
250 | '--test-flags', | |||
|
251 | help='Extra command line flags to pass to run-tests.py', | |||
|
252 | ) | |||
|
253 | sp.set_defaults(func=run_tests_windows) | |||
|
254 | ||||
|
255 | return parser | |||
|
256 | ||||
|
257 | ||||
|
258 | def main(): | |||
|
259 | parser = get_parser() | |||
|
260 | args = parser.parse_args() | |||
|
261 | ||||
|
262 | local_state_path = pathlib.Path(os.path.expanduser(args.state_path)) | |||
|
263 | automation = HGAutomation(local_state_path) | |||
|
264 | ||||
|
265 | if not hasattr(args, 'func'): | |||
|
266 | parser.print_help() | |||
|
267 | return | |||
|
268 | ||||
|
269 | kwargs = dict(vars(args)) | |||
|
270 | del kwargs['func'] | |||
|
271 | del kwargs['state_path'] | |||
|
272 | ||||
|
273 | args.func(automation, **kwargs) |
@@ -0,0 +1,287 b'' | |||||
|
1 | # windows.py - Automation specific to Windows | |||
|
2 | # | |||
|
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | ||||
|
8 | # no-check-code because Python 3 native. | |||
|
9 | ||||
|
10 | import os | |||
|
11 | import pathlib | |||
|
12 | import re | |||
|
13 | import subprocess | |||
|
14 | import tempfile | |||
|
15 | ||||
|
16 | from .winrm import ( | |||
|
17 | run_powershell, | |||
|
18 | ) | |||
|
19 | ||||
|
20 | ||||
|
21 | # PowerShell commands to activate a Visual Studio 2008 environment. | |||
|
22 | # This is essentially a port of vcvarsall.bat to PowerShell. | |||
|
23 | ACTIVATE_VC9_AMD64 = r''' | |||
|
24 | Write-Output "activating Visual Studio 2008 environment for AMD64" | |||
|
25 | $root = "$env:LOCALAPPDATA\Programs\Common\Microsoft\Visual C++ for Python\9.0" | |||
|
26 | $Env:VCINSTALLDIR = "${root}\VC\" | |||
|
27 | $Env:WindowsSdkDir = "${root}\WinSDK\" | |||
|
28 | $Env:PATH = "${root}\VC\Bin\amd64;${root}\WinSDK\Bin\x64;${root}\WinSDK\Bin;$Env:PATH" | |||
|
29 | $Env:INCLUDE = "${root}\VC\Include;${root}\WinSDK\Include;$Env:PATH" | |||
|
30 | $Env:LIB = "${root}\VC\Lib\amd64;${root}\WinSDK\Lib\x64;$Env:LIB" | |||
|
31 | $Env:LIBPATH = "${root}\VC\Lib\amd64;${root}\WinSDK\Lib\x64;$Env:LIBPATH" | |||
|
32 | '''.lstrip() | |||
|
33 | ||||
|
34 | ACTIVATE_VC9_X86 = r''' | |||
|
35 | Write-Output "activating Visual Studio 2008 environment for x86" | |||
|
36 | $root = "$env:LOCALAPPDATA\Programs\Common\Microsoft\Visual C++ for Python\9.0" | |||
|
37 | $Env:VCINSTALLDIR = "${root}\VC\" | |||
|
38 | $Env:WindowsSdkDir = "${root}\WinSDK\" | |||
|
39 | $Env:PATH = "${root}\VC\Bin;${root}\WinSDK\Bin;$Env:PATH" | |||
|
40 | $Env:INCLUDE = "${root}\VC\Include;${root}\WinSDK\Include;$Env:INCLUDE" | |||
|
41 | $Env:LIB = "${root}\VC\Lib;${root}\WinSDK\Lib;$Env:LIB" | |||
|
42 | $Env:LIBPATH = "${root}\VC\lib;${root}\WinSDK\Lib:$Env:LIBPATH" | |||
|
43 | '''.lstrip() | |||
|
44 | ||||
|
45 | HG_PURGE = r''' | |||
|
46 | $Env:PATH = "C:\hgdev\venv-bootstrap\Scripts;$Env:PATH" | |||
|
47 | Set-Location C:\hgdev\src | |||
|
48 | hg.exe --config extensions.purge= purge --all | |||
|
49 | if ($LASTEXITCODE -ne 0) { | |||
|
50 | throw "process exited non-0: $LASTEXITCODE" | |||
|
51 | } | |||
|
52 | Write-Output "purged Mercurial repo" | |||
|
53 | ''' | |||
|
54 | ||||
|
55 | HG_UPDATE_CLEAN = r''' | |||
|
56 | $Env:PATH = "C:\hgdev\venv-bootstrap\Scripts;$Env:PATH" | |||
|
57 | Set-Location C:\hgdev\src | |||
|
58 | hg.exe --config extensions.purge= purge --all | |||
|
59 | if ($LASTEXITCODE -ne 0) {{ | |||
|
60 | throw "process exited non-0: $LASTEXITCODE" | |||
|
61 | }} | |||
|
62 | hg.exe update -C {revision} | |||
|
63 | if ($LASTEXITCODE -ne 0) {{ | |||
|
64 | throw "process exited non-0: $LASTEXITCODE" | |||
|
65 | }} | |||
|
66 | hg.exe log -r . | |||
|
67 | Write-Output "updated Mercurial working directory to {revision}" | |||
|
68 | '''.lstrip() | |||
|
69 | ||||
|
70 | BUILD_INNO = r''' | |||
|
71 | Set-Location C:\hgdev\src | |||
|
72 | $python = "C:\hgdev\python27-{arch}\python.exe" | |||
|
73 | C:\hgdev\python37-x64\python.exe contrib\packaging\inno\build.py --python $python | |||
|
74 | if ($LASTEXITCODE -ne 0) {{ | |||
|
75 | throw "process exited non-0: $LASTEXITCODE" | |||
|
76 | }} | |||
|
77 | '''.lstrip() | |||
|
78 | ||||
|
79 | BUILD_WHEEL = r''' | |||
|
80 | Set-Location C:\hgdev\src | |||
|
81 | C:\hgdev\python27-{arch}\Scripts\pip.exe wheel --wheel-dir dist . | |||
|
82 | if ($LASTEXITCODE -ne 0) {{ | |||
|
83 | throw "process exited non-0: $LASTEXITCODE" | |||
|
84 | }} | |||
|
85 | ''' | |||
|
86 | ||||
|
87 | BUILD_WIX = r''' | |||
|
88 | Set-Location C:\hgdev\src | |||
|
89 | $python = "C:\hgdev\python27-{arch}\python.exe" | |||
|
90 | C:\hgdev\python37-x64\python.exe contrib\packaging\wix\build.py --python $python {extra_args} | |||
|
91 | if ($LASTEXITCODE -ne 0) {{ | |||
|
92 | throw "process exited non-0: $LASTEXITCODE" | |||
|
93 | }} | |||
|
94 | ''' | |||
|
95 | ||||
|
96 | RUN_TESTS = r''' | |||
|
97 | C:\hgdev\MinGW\msys\1.0\bin\sh.exe --login -c "cd /c/hgdev/src/tests && /c/hgdev/{python_path}/python.exe run-tests.py {test_flags}" | |||
|
98 | if ($LASTEXITCODE -ne 0) {{ | |||
|
99 | throw "process exited non-0: $LASTEXITCODE" | |||
|
100 | }} | |||
|
101 | ''' | |||
|
102 | ||||
|
103 | ||||
|
104 | def get_vc_prefix(arch): | |||
|
105 | if arch == 'x86': | |||
|
106 | return ACTIVATE_VC9_X86 | |||
|
107 | elif arch == 'x64': | |||
|
108 | return ACTIVATE_VC9_AMD64 | |||
|
109 | else: | |||
|
110 | raise ValueError('illegal arch: %s; must be x86 or x64' % arch) | |||
|
111 | ||||
|
112 | ||||
|
113 | def fix_authorized_keys_permissions(winrm_client, path): | |||
|
114 | commands = [ | |||
|
115 | '$ErrorActionPreference = "Stop"', | |||
|
116 | 'Repair-AuthorizedKeyPermission -FilePath %s -Confirm:$false' % path, | |||
|
117 | r'icacls %s /remove:g "NT Service\sshd"' % path, | |||
|
118 | ] | |||
|
119 | ||||
|
120 | run_powershell(winrm_client, '\n'.join(commands)) | |||
|
121 | ||||
|
122 | ||||
|
123 | def synchronize_hg(hg_repo: pathlib.Path, revision: str, ec2_instance): | |||
|
124 | """Synchronize local Mercurial repo to remote EC2 instance.""" | |||
|
125 | ||||
|
126 | winrm_client = ec2_instance.winrm_client | |||
|
127 | ||||
|
128 | with tempfile.TemporaryDirectory() as temp_dir: | |||
|
129 | temp_dir = pathlib.Path(temp_dir) | |||
|
130 | ||||
|
131 | ssh_dir = temp_dir / '.ssh' | |||
|
132 | ssh_dir.mkdir() | |||
|
133 | ssh_dir.chmod(0o0700) | |||
|
134 | ||||
|
135 | # Generate SSH key to use for communication. | |||
|
136 | subprocess.run([ | |||
|
137 | 'ssh-keygen', '-t', 'rsa', '-b', '4096', '-N', '', | |||
|
138 | '-f', str(ssh_dir / 'id_rsa')], | |||
|
139 | check=True, capture_output=True) | |||
|
140 | ||||
|
141 | # Add it to ~/.ssh/authorized_keys on remote. | |||
|
142 | # This assumes the file doesn't already exist. | |||
|
143 | authorized_keys = r'c:\Users\Administrator\.ssh\authorized_keys' | |||
|
144 | winrm_client.execute_cmd(r'mkdir c:\Users\Administrator\.ssh') | |||
|
145 | winrm_client.copy(str(ssh_dir / 'id_rsa.pub'), authorized_keys) | |||
|
146 | fix_authorized_keys_permissions(winrm_client, authorized_keys) | |||
|
147 | ||||
|
148 | public_ip = ec2_instance.public_ip_address | |||
|
149 | ||||
|
150 | ssh_config = temp_dir / '.ssh' / 'config' | |||
|
151 | ||||
|
152 | with open(ssh_config, 'w', encoding='utf-8') as fh: | |||
|
153 | fh.write('Host %s\n' % public_ip) | |||
|
154 | fh.write(' User Administrator\n') | |||
|
155 | fh.write(' StrictHostKeyChecking no\n') | |||
|
156 | fh.write(' UserKnownHostsFile %s\n' % (ssh_dir / 'known_hosts')) | |||
|
157 | fh.write(' IdentityFile %s\n' % (ssh_dir / 'id_rsa')) | |||
|
158 | ||||
|
159 | env = dict(os.environ) | |||
|
160 | env['HGPLAIN'] = '1' | |||
|
161 | env['HGENCODING'] = 'utf-8' | |||
|
162 | ||||
|
163 | hg_bin = hg_repo / 'hg' | |||
|
164 | ||||
|
165 | res = subprocess.run( | |||
|
166 | ['python2.7', str(hg_bin), 'log', '-r', revision, '-T', '{node}'], | |||
|
167 | cwd=str(hg_repo), env=env, check=True, capture_output=True) | |||
|
168 | ||||
|
169 | full_revision = res.stdout.decode('ascii') | |||
|
170 | ||||
|
171 | args = [ | |||
|
172 | 'python2.7', hg_bin, | |||
|
173 | '--config', 'ui.ssh=ssh -F %s' % ssh_config, | |||
|
174 | '--config', 'ui.remotecmd=c:/hgdev/venv-bootstrap/Scripts/hg.exe', | |||
|
175 | 'push', '-r', full_revision, 'ssh://%s/c:/hgdev/src' % public_ip, | |||
|
176 | ] | |||
|
177 | ||||
|
178 | subprocess.run(args, cwd=str(hg_repo), env=env, check=True) | |||
|
179 | ||||
|
180 | run_powershell(winrm_client, | |||
|
181 | HG_UPDATE_CLEAN.format(revision=full_revision)) | |||
|
182 | ||||
|
183 | # TODO detect dirty local working directory and synchronize accordingly. | |||
|
184 | ||||
|
185 | ||||
|
186 | def purge_hg(winrm_client): | |||
|
187 | """Purge the Mercurial source repository on an EC2 instance.""" | |||
|
188 | run_powershell(winrm_client, HG_PURGE) | |||
|
189 | ||||
|
190 | ||||
|
191 | def find_latest_dist(winrm_client, pattern): | |||
|
192 | """Find path to newest file in dist/ directory matching a pattern.""" | |||
|
193 | ||||
|
194 | res = winrm_client.execute_ps( | |||
|
195 | r'$v = Get-ChildItem -Path C:\hgdev\src\dist -Filter "%s" ' | |||
|
196 | '| Sort-Object LastWriteTime -Descending ' | |||
|
197 | '| Select-Object -First 1\n' | |||
|
198 | '$v.name' % pattern | |||
|
199 | ) | |||
|
200 | return res[0] | |||
|
201 | ||||
|
202 | ||||
|
203 | def copy_latest_dist(winrm_client, pattern, dest_path): | |||
|
204 | """Copy latest file matching pattern in dist/ directory. | |||
|
205 | ||||
|
206 | Given a WinRM client and a file pattern, find the latest file on the remote | |||
|
207 | matching that pattern and copy it to the ``dest_path`` directory on the | |||
|
208 | local machine. | |||
|
209 | """ | |||
|
210 | latest = find_latest_dist(winrm_client, pattern) | |||
|
211 | source = r'C:\hgdev\src\dist\%s' % latest | |||
|
212 | dest = dest_path / latest | |||
|
213 | print('copying %s to %s' % (source, dest)) | |||
|
214 | winrm_client.fetch(source, str(dest)) | |||
|
215 | ||||
|
216 | ||||
|
217 | def build_inno_installer(winrm_client, arch: str, dest_path: pathlib.Path, | |||
|
218 | version=None): | |||
|
219 | """Build the Inno Setup installer on a remote machine. | |||
|
220 | ||||
|
221 | Using a WinRM client, remote commands are executed to build | |||
|
222 | a Mercurial Inno Setup installer. | |||
|
223 | """ | |||
|
224 | print('building Inno Setup installer for %s' % arch) | |||
|
225 | ||||
|
226 | extra_args = [] | |||
|
227 | if version: | |||
|
228 | extra_args.extend(['--version', version]) | |||
|
229 | ||||
|
230 | ps = get_vc_prefix(arch) + BUILD_INNO.format(arch=arch, | |||
|
231 | extra_args=' '.join(extra_args)) | |||
|
232 | run_powershell(winrm_client, ps) | |||
|
233 | copy_latest_dist(winrm_client, '*.exe', dest_path) | |||
|
234 | ||||
|
235 | ||||
|
236 | def build_wheel(winrm_client, arch: str, dest_path: pathlib.Path): | |||
|
237 | """Build Python wheels on a remote machine. | |||
|
238 | ||||
|
239 | Using a WinRM client, remote commands are executed to build a Python wheel | |||
|
240 | for Mercurial. | |||
|
241 | """ | |||
|
242 | print('Building Windows wheel for %s' % arch) | |||
|
243 | ps = get_vc_prefix(arch) + BUILD_WHEEL.format(arch=arch) | |||
|
244 | run_powershell(winrm_client, ps) | |||
|
245 | copy_latest_dist(winrm_client, '*.whl', dest_path) | |||
|
246 | ||||
|
247 | ||||
|
248 | def build_wix_installer(winrm_client, arch: str, dest_path: pathlib.Path, | |||
|
249 | version=None): | |||
|
250 | """Build the WiX installer on a remote machine. | |||
|
251 | ||||
|
252 | Using a WinRM client, remote commands are executed to build a WiX installer. | |||
|
253 | """ | |||
|
254 | print('Building WiX installer for %s' % arch) | |||
|
255 | extra_args = [] | |||
|
256 | if version: | |||
|
257 | extra_args.extend(['--version', version]) | |||
|
258 | ||||
|
259 | ps = get_vc_prefix(arch) + BUILD_WIX.format(arch=arch, | |||
|
260 | extra_args=' '.join(extra_args)) | |||
|
261 | run_powershell(winrm_client, ps) | |||
|
262 | copy_latest_dist(winrm_client, '*.msi', dest_path) | |||
|
263 | ||||
|
264 | ||||
|
265 | def run_tests(winrm_client, python_version, arch, test_flags=''): | |||
|
266 | """Run tests on a remote Windows machine. | |||
|
267 | ||||
|
268 | ``python_version`` is a ``X.Y`` string like ``2.7`` or ``3.7``. | |||
|
269 | ``arch`` is ``x86`` or ``x64``. | |||
|
270 | ``test_flags`` is a str representing extra arguments to pass to | |||
|
271 | ``run-tests.py``. | |||
|
272 | """ | |||
|
273 | if not re.match(r'\d\.\d', python_version): | |||
|
274 | raise ValueError(r'python_version must be \d.\d; got %s' % | |||
|
275 | python_version) | |||
|
276 | ||||
|
277 | if arch not in ('x86', 'x64'): | |||
|
278 | raise ValueError('arch must be x86 or x64; got %s' % arch) | |||
|
279 | ||||
|
280 | python_path = 'python%s-%s' % (python_version.replace('.', ''), arch) | |||
|
281 | ||||
|
282 | ps = RUN_TESTS.format( | |||
|
283 | python_path=python_path, | |||
|
284 | test_flags=test_flags or '', | |||
|
285 | ) | |||
|
286 | ||||
|
287 | run_powershell(winrm_client, ps) |
@@ -0,0 +1,82 b'' | |||||
|
1 | # winrm.py - Interact with Windows Remote Management (WinRM) | |||
|
2 | # | |||
|
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | ||||
|
8 | # no-check-code because Python 3 native. | |||
|
9 | ||||
|
10 | import logging | |||
|
11 | import pprint | |||
|
12 | import time | |||
|
13 | ||||
|
14 | from pypsrp.client import ( | |||
|
15 | Client, | |||
|
16 | ) | |||
|
17 | from pypsrp.powershell import ( | |||
|
18 | PowerShell, | |||
|
19 | PSInvocationState, | |||
|
20 | RunspacePool, | |||
|
21 | ) | |||
|
22 | import requests.exceptions | |||
|
23 | ||||
|
24 | ||||
|
25 | logger = logging.getLogger(__name__) | |||
|
26 | ||||
|
27 | ||||
|
28 | def wait_for_winrm(host, username, password, timeout=120, ssl=False): | |||
|
29 | """Wait for the Windows Remoting (WinRM) service to become available. | |||
|
30 | ||||
|
31 | Returns a ``psrpclient.Client`` instance. | |||
|
32 | """ | |||
|
33 | ||||
|
34 | end_time = time.time() + timeout | |||
|
35 | ||||
|
36 | while True: | |||
|
37 | try: | |||
|
38 | client = Client(host, username=username, password=password, | |||
|
39 | ssl=ssl, connection_timeout=5) | |||
|
40 | client.execute_cmd('echo "hello world"') | |||
|
41 | return client | |||
|
42 | except requests.exceptions.ConnectionError: | |||
|
43 | if time.time() >= end_time: | |||
|
44 | raise | |||
|
45 | ||||
|
46 | time.sleep(1) | |||
|
47 | ||||
|
48 | ||||
|
49 | def format_object(o): | |||
|
50 | if isinstance(o, str): | |||
|
51 | return o | |||
|
52 | ||||
|
53 | try: | |||
|
54 | o = str(o) | |||
|
55 | except TypeError: | |||
|
56 | o = pprint.pformat(o.extended_properties) | |||
|
57 | ||||
|
58 | return o | |||
|
59 | ||||
|
60 | ||||
|
61 | def run_powershell(client, script): | |||
|
62 | with RunspacePool(client.wsman) as pool: | |||
|
63 | ps = PowerShell(pool) | |||
|
64 | ps.add_script(script) | |||
|
65 | ||||
|
66 | ps.begin_invoke() | |||
|
67 | ||||
|
68 | while ps.state == PSInvocationState.RUNNING: | |||
|
69 | ps.poll_invoke() | |||
|
70 | for o in ps.output: | |||
|
71 | print(format_object(o)) | |||
|
72 | ||||
|
73 | ps.output[:] = [] | |||
|
74 | ||||
|
75 | ps.end_invoke() | |||
|
76 | ||||
|
77 | for o in ps.output: | |||
|
78 | print(format_object(o)) | |||
|
79 | ||||
|
80 | if ps.state == PSInvocationState.FAILED: | |||
|
81 | raise Exception('PowerShell execution failed: %s' % | |||
|
82 | ' '.join(map(format_object, ps.streams.error))) |
@@ -0,0 +1,119 b'' | |||||
|
1 | # | |||
|
2 | # This file is autogenerated by pip-compile | |||
|
3 | # To update, run: | |||
|
4 | # | |||
|
5 | # pip-compile -U --generate-hashes --output-file contrib/automation/requirements.txt contrib/automation/requirements.txt.in | |||
|
6 | # | |||
|
7 | asn1crypto==0.24.0 \ | |||
|
8 | --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ | |||
|
9 | --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 \ | |||
|
10 | # via cryptography | |||
|
11 | boto3==1.9.111 \ | |||
|
12 | --hash=sha256:06414c75d1f62af7d04fd652b38d1e4fd3cfd6b35bad978466af88e2aaecd00d \ | |||
|
13 | --hash=sha256:f3b77dff382374773d02411fa47ee408f4f503aeebd837fd9dc9ed8635bc5e8e | |||
|
14 | botocore==1.12.111 \ | |||
|
15 | --hash=sha256:6af473c52d5e3e7ff82de5334e9fee96b2d5ec2df5d78bc00cd9937e2573a7a8 \ | |||
|
16 | --hash=sha256:9f5123c7be704b17aeacae99b5842ab17bda1f799dd29134de8c70e0a50a45d7 \ | |||
|
17 | # via boto3, s3transfer | |||
|
18 | certifi==2019.3.9 \ | |||
|
19 | --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ | |||
|
20 | --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae \ | |||
|
21 | # via requests | |||
|
22 | cffi==1.12.2 \ | |||
|
23 | --hash=sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f \ | |||
|
24 | --hash=sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11 \ | |||
|
25 | --hash=sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d \ | |||
|
26 | --hash=sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891 \ | |||
|
27 | --hash=sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf \ | |||
|
28 | --hash=sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c \ | |||
|
29 | --hash=sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed \ | |||
|
30 | --hash=sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b \ | |||
|
31 | --hash=sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a \ | |||
|
32 | --hash=sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585 \ | |||
|
33 | --hash=sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea \ | |||
|
34 | --hash=sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f \ | |||
|
35 | --hash=sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33 \ | |||
|
36 | --hash=sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145 \ | |||
|
37 | --hash=sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a \ | |||
|
38 | --hash=sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3 \ | |||
|
39 | --hash=sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f \ | |||
|
40 | --hash=sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd \ | |||
|
41 | --hash=sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804 \ | |||
|
42 | --hash=sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d \ | |||
|
43 | --hash=sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92 \ | |||
|
44 | --hash=sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f \ | |||
|
45 | --hash=sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84 \ | |||
|
46 | --hash=sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb \ | |||
|
47 | --hash=sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7 \ | |||
|
48 | --hash=sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 \ | |||
|
49 | --hash=sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35 \ | |||
|
50 | --hash=sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889 \ | |||
|
51 | # via cryptography | |||
|
52 | chardet==3.0.4 \ | |||
|
53 | --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ | |||
|
54 | --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \ | |||
|
55 | # via requests | |||
|
56 | cryptography==2.6.1 \ | |||
|
57 | --hash=sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1 \ | |||
|
58 | --hash=sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705 \ | |||
|
59 | --hash=sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6 \ | |||
|
60 | --hash=sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1 \ | |||
|
61 | --hash=sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8 \ | |||
|
62 | --hash=sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151 \ | |||
|
63 | --hash=sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d \ | |||
|
64 | --hash=sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659 \ | |||
|
65 | --hash=sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537 \ | |||
|
66 | --hash=sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e \ | |||
|
67 | --hash=sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb \ | |||
|
68 | --hash=sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c \ | |||
|
69 | --hash=sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9 \ | |||
|
70 | --hash=sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5 \ | |||
|
71 | --hash=sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad \ | |||
|
72 | --hash=sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a \ | |||
|
73 | --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ | |||
|
74 | --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ | |||
|
75 | --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 \ | |||
|
76 | # via pypsrp | |||
|
77 | docutils==0.14 \ | |||
|
78 | --hash=sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6 \ | |||
|
79 | --hash=sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274 \ | |||
|
80 | --hash=sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6 \ | |||
|
81 | # via botocore | |||
|
82 | idna==2.8 \ | |||
|
83 | --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ | |||
|
84 | --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c \ | |||
|
85 | # via requests | |||
|
86 | jmespath==0.9.4 \ | |||
|
87 | --hash=sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6 \ | |||
|
88 | --hash=sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c \ | |||
|
89 | # via boto3, botocore | |||
|
90 | ntlm-auth==1.2.0 \ | |||
|
91 | --hash=sha256:7bc02a3fbdfee7275d3dc20fce8028ed8eb6d32364637f28be9e9ae9160c6d5c \ | |||
|
92 | --hash=sha256:9b13eaf88f16a831637d75236a93d60c0049536715aafbf8190ba58a590b023e \ | |||
|
93 | # via pypsrp | |||
|
94 | pycparser==2.19 \ | |||
|
95 | --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 \ | |||
|
96 | # via cffi | |||
|
97 | pypsrp==0.3.1 \ | |||
|
98 | --hash=sha256:309853380fe086090a03cc6662a778ee69b1cae355ae4a932859034fd76e9d0b \ | |||
|
99 | --hash=sha256:90f946254f547dc3493cea8493c819ab87e152a755797c93aa2668678ba8ae85 | |||
|
100 | python-dateutil==2.8.0 \ | |||
|
101 | --hash=sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb \ | |||
|
102 | --hash=sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e \ | |||
|
103 | # via botocore | |||
|
104 | requests==2.21.0 \ | |||
|
105 | --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ | |||
|
106 | --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b \ | |||
|
107 | # via pypsrp | |||
|
108 | s3transfer==0.2.0 \ | |||
|
109 | --hash=sha256:7b9ad3213bff7d357f888e0fab5101b56fa1a0548ee77d121c3a3dbfbef4cb2e \ | |||
|
110 | --hash=sha256:f23d5cb7d862b104401d9021fc82e5fa0e0cf57b7660a1331425aab0c691d021 \ | |||
|
111 | # via boto3 | |||
|
112 | six==1.12.0 \ | |||
|
113 | --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ | |||
|
114 | --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 \ | |||
|
115 | # via cryptography, pypsrp, python-dateutil | |||
|
116 | urllib3==1.24.1 \ | |||
|
117 | --hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \ | |||
|
118 | --hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22 \ | |||
|
119 | # via botocore, requests |
@@ -0,0 +1,200 b'' | |||||
|
1 | # install-dependencies.ps1 - Install Windows dependencies for building Mercurial | |||
|
2 | # | |||
|
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | ||||
|
8 | # This script can be used to bootstrap a Mercurial build environment on | |||
|
9 | # Windows. | |||
|
10 | # | |||
|
11 | # The script makes a lot of assumptions about how things should work. | |||
|
12 | # For example, the install location of Python is hardcoded to c:\hgdev\*. | |||
|
13 | # | |||
|
14 | # The script should be executed from a PowerShell with elevated privileges | |||
|
15 | # if you don't want to see a UAC prompt for various installers. | |||
|
16 | # | |||
|
17 | # The script is tested on Windows 10 and Windows Server 2019 (in EC2). | |||
|
18 | ||||
|
19 | $VS_BUILD_TOOLS_URL = "https://download.visualstudio.microsoft.com/download/pr/a1603c02-8a66-4b83-b821-811e3610a7c4/aa2db8bb39e0cbd23e9940d8951e0bc3/vs_buildtools.exe" | |||
|
20 | $VS_BUILD_TOOLS_SHA256 = "911E292B8E6E5F46CBC17003BDCD2D27A70E616E8D5E6E69D5D489A605CAA139" | |||
|
21 | ||||
|
22 | $VC9_PYTHON_URL = "https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-B59FBF6D907B/VCForPython27.msi" | |||
|
23 | $VC9_PYTHON_SHA256 = "070474db76a2e625513a5835df4595df9324d820f9cc97eab2a596dcbc2f5cbf" | |||
|
24 | ||||
|
25 | $PYTHON27_x64_URL = "https://www.python.org/ftp/python/2.7.16/python-2.7.16.amd64.msi" | |||
|
26 | $PYTHON27_x64_SHA256 = "7c0f45993019152d46041a7db4b947b919558fdb7a8f67bcd0535bc98d42b603" | |||
|
27 | $PYTHON27_X86_URL = "https://www.python.org/ftp/python/2.7.16/python-2.7.16.msi" | |||
|
28 | $PYTHON27_X86_SHA256 = "d57dc3e1ba490aee856c28b4915d09e3f49442461e46e481bc6b2d18207831d7" | |||
|
29 | ||||
|
30 | $PYTHON35_x86_URL = "https://www.python.org/ftp/python/3.5.4/python-3.5.4.exe" | |||
|
31 | $PYTHON35_x86_SHA256 = "F27C2D67FD9688E4970F3BFF799BB9D722A0D6C2C13B04848E1F7D620B524B0E" | |||
|
32 | $PYTHON35_x64_URL = "https://www.python.org/ftp/python/3.5.4/python-3.5.4-amd64.exe" | |||
|
33 | $PYTHON35_x64_SHA256 = "9B7741CC32357573A77D2EE64987717E527628C38FD7EAF3E2AACA853D45A1EE" | |||
|
34 | ||||
|
35 | $PYTHON36_x86_URL = "https://www.python.org/ftp/python/3.6.8/python-3.6.8.exe" | |||
|
36 | $PYTHON36_x86_SHA256 = "89871D432BC06E4630D7B64CB1A8451E53C80E68DE29029976B12AAD7DBFA5A0" | |||
|
37 | $PYTHON36_x64_URL = "https://www.python.org/ftp/python/3.6.8/python-3.6.8-amd64.exe" | |||
|
38 | $PYTHON36_x64_SHA256 = "96088A58B7C43BC83B84E6B67F15E8706C614023DD64F9A5A14E81FF824ADADC" | |||
|
39 | ||||
|
40 | $PYTHON37_x86_URL = "https://www.python.org/ftp/python/3.7.2/python-3.7.2.exe" | |||
|
41 | $PYTHON37_x86_SHA256 = "8BACE330FB409E428B04EEEE083DD9CA7F6C754366D07E23B3853891D8F8C3D0" | |||
|
42 | $PYTHON37_x64_URL = "https://www.python.org/ftp/python/3.7.2/python-3.7.2-amd64.exe" | |||
|
43 | $PYTHON37_x64_SHA256 = "0FE2A696F5A3E481FED795EF6896ED99157BCEF273EF3C4A96F2905CBDB3AA13" | |||
|
44 | ||||
|
45 | $PYTHON38_x86_URL = "https://www.python.org/ftp/python/3.8.0/python-3.8.0a2.exe" | |||
|
46 | $PYTHON38_x86_SHA256 = "013A7DDD317679FE51223DE627688CFCB2F0F1128FD25A987F846AEB476D3FEF" | |||
|
47 | $PYTHON38_x64_URL = "https://www.python.org/ftp/python/3.8.0/python-3.8.0a2-amd64.exe" | |||
|
48 | $PYTHON38_X64_SHA256 = "560BC6D1A76BCD6D544AC650709F3892956890753CDCF9CE67E3D7302D76FB41" | |||
|
49 | ||||
|
50 | # PIP 19.0.3. | |||
|
51 | $PIP_URL = "https://github.com/pypa/get-pip/raw/fee32c376da1ff6496a798986d7939cd51e1644f/get-pip.py" | |||
|
52 | $PIP_SHA256 = "efe99298f3fbb1f56201ce6b81d2658067d2f7d7dfc2d412e0d3cacc9a397c61" | |||
|
53 | ||||
|
54 | $VIRTUALENV_URL = "https://files.pythonhosted.org/packages/37/db/89d6b043b22052109da35416abc3c397655e4bd3cff031446ba02b9654fa/virtualenv-16.4.3.tar.gz" | |||
|
55 | $VIRTUALENV_SHA256 = "984d7e607b0a5d1329425dd8845bd971b957424b5ba664729fab51ab8c11bc39" | |||
|
56 | ||||
|
57 | $INNO_SETUP_URL = "http://files.jrsoftware.org/is/5/innosetup-5.6.1-unicode.exe" | |||
|
58 | $INNO_SETUP_SHA256 = "27D49E9BC769E9D1B214C153011978DB90DC01C2ACD1DDCD9ED7B3FE3B96B538" | |||
|
59 | ||||
|
60 | $MINGW_BIN_URL = "https://osdn.net/frs/redir.php?m=constant&f=mingw%2F68260%2Fmingw-get-0.6.3-mingw32-pre-20170905-1-bin.zip" | |||
|
61 | $MINGW_BIN_SHA256 = "2AB8EFD7C7D1FC8EAF8B2FA4DA4EEF8F3E47768284C021599BC7435839A046DF" | |||
|
62 | ||||
|
63 | $MERCURIAL_WHEEL_FILENAME = "mercurial-4.9-cp27-cp27m-win_amd64.whl" | |||
|
64 | $MERCURIAL_WHEEL_URL = "https://files.pythonhosted.org/packages/fe/e8/b872d53dfbbf986bdc46af0b30f580b227fb59bddd2587152a55e205b0cc/$MERCURIAL_WHEEL_FILENAME" | |||
|
65 | $MERCURIAL_WHEEL_SHA256 = "218cc2e7c3f1d535007febbb03351663897edf27df0e57d6842e3b686492b429" | |||
|
66 | ||||
|
67 | # Writing progress slows down downloads substantially. So disable it. | |||
|
68 | $progressPreference = 'silentlyContinue' | |||
|
69 | ||||
|
70 | function Secure-Download($url, $path, $sha256) { | |||
|
71 | if (Test-Path -Path $path) { | |||
|
72 | Get-FileHash -Path $path -Algorithm SHA256 -OutVariable hash | |||
|
73 | ||||
|
74 | if ($hash.Hash -eq $sha256) { | |||
|
75 | Write-Output "SHA256 of $path verified as $sha256" | |||
|
76 | return | |||
|
77 | } | |||
|
78 | ||||
|
79 | Write-Output "hash mismatch on $path; downloading again" | |||
|
80 | } | |||
|
81 | ||||
|
82 | Write-Output "downloading $url to $path" | |||
|
83 | Invoke-WebRequest -Uri $url -OutFile $path | |||
|
84 | Get-FileHash -Path $path -Algorithm SHA256 -OutVariable hash | |||
|
85 | ||||
|
86 | if ($hash.Hash -ne $sha256) { | |||
|
87 | Remove-Item -Path $path | |||
|
88 | throw "hash mismatch when downloading $url; got $($hash.Hash), expected $sha256" | |||
|
89 | } | |||
|
90 | } | |||
|
91 | ||||
|
92 | function Invoke-Process($path, $arguments) { | |||
|
93 | $p = Start-Process -FilePath $path -ArgumentList $arguments -Wait -PassThru -WindowStyle Hidden | |||
|
94 | ||||
|
95 | if ($p.ExitCode -ne 0) { | |||
|
96 | throw "process exited non-0: $($p.ExitCode)" | |||
|
97 | } | |||
|
98 | } | |||
|
99 | ||||
|
100 | function Install-Python3($name, $installer, $dest, $pip) { | |||
|
101 | Write-Output "installing $name" | |||
|
102 | ||||
|
103 | # We hit this when running the script as part of Simple Systems Manager in | |||
|
104 | # EC2. The Python 3 installer doesn't seem to like per-user installs | |||
|
105 | # when running as the SYSTEM user. So enable global installs if executed in | |||
|
106 | # this mode. | |||
|
107 | if ($env:USERPROFILE -eq "C:\Windows\system32\config\systemprofile") { | |||
|
108 | Write-Output "running with SYSTEM account; installing for all users" | |||
|
109 | $allusers = "1" | |||
|
110 | } | |||
|
111 | else { | |||
|
112 | $allusers = "0" | |||
|
113 | } | |||
|
114 | ||||
|
115 | Invoke-Process $installer "/quiet TargetDir=${dest} InstallAllUsers=${allusers} AssociateFiles=0 CompileAll=0 PrependPath=0 Include_doc=0 Include_launcher=0 InstallLauncherAllUsers=0 Include_pip=0 Include_test=0" | |||
|
116 | Invoke-Process ${dest}\python.exe $pip | |||
|
117 | } | |||
|
118 | ||||
|
119 | function Install-Dependencies($prefix) { | |||
|
120 | if (!(Test-Path -Path $prefix\assets)) { | |||
|
121 | New-Item -Path $prefix\assets -ItemType Directory | |||
|
122 | } | |||
|
123 | ||||
|
124 | $pip = "${prefix}\assets\get-pip.py" | |||
|
125 | ||||
|
126 | Secure-Download $VC9_PYTHON_URL ${prefix}\assets\VCForPython27.msi $VC9_PYTHON_SHA256 | |||
|
127 | Secure-Download $PYTHON27_x86_URL ${prefix}\assets\python27-x86.msi $PYTHON27_x86_SHA256 | |||
|
128 | Secure-Download $PYTHON27_x64_URL ${prefix}\assets\python27-x64.msi $PYTHON27_x64_SHA256 | |||
|
129 | Secure-Download $PYTHON35_x86_URL ${prefix}\assets\python35-x86.exe $PYTHON35_x86_SHA256 | |||
|
130 | Secure-Download $PYTHON35_x64_URL ${prefix}\assets\python35-x64.exe $PYTHON35_x64_SHA256 | |||
|
131 | Secure-Download $PYTHON36_x86_URL ${prefix}\assets\python36-x86.exe $PYTHON36_x86_SHA256 | |||
|
132 | Secure-Download $PYTHON36_x64_URL ${prefix}\assets\python36-x64.exe $PYTHON36_x64_SHA256 | |||
|
133 | Secure-Download $PYTHON37_x86_URL ${prefix}\assets\python37-x86.exe $PYTHON37_x86_SHA256 | |||
|
134 | Secure-Download $PYTHON37_x64_URL ${prefix}\assets\python37-x64.exe $PYTHON37_x64_SHA256 | |||
|
135 | Secure-Download $PYTHON38_x86_URL ${prefix}\assets\python38-x86.exe $PYTHON38_x86_SHA256 | |||
|
136 | Secure-Download $PYTHON38_x64_URL ${prefix}\assets\python38-x64.exe $PYTHON38_x64_SHA256 | |||
|
137 | Secure-Download $PIP_URL ${pip} $PIP_SHA256 | |||
|
138 | Secure-Download $VIRTUALENV_URL ${prefix}\assets\virtualenv.tar.gz $VIRTUALENV_SHA256 | |||
|
139 | Secure-Download $VS_BUILD_TOOLS_URL ${prefix}\assets\vs_buildtools.exe $VS_BUILD_TOOLS_SHA256 | |||
|
140 | Secure-Download $INNO_SETUP_URL ${prefix}\assets\InnoSetup.exe $INNO_SETUP_SHA256 | |||
|
141 | Secure-Download $MINGW_BIN_URL ${prefix}\assets\mingw-get-bin.zip $MINGW_BIN_SHA256 | |||
|
142 | Secure-Download $MERCURIAL_WHEEL_URL ${prefix}\assets\${MERCURIAL_WHEEL_FILENAME} $MERCURIAL_WHEEL_SHA256 | |||
|
143 | ||||
|
144 | Write-Output "installing Python 2.7 32-bit" | |||
|
145 | Invoke-Process msiexec.exe "/i ${prefix}\assets\python27-x86.msi /l* ${prefix}\assets\python27-x86.log /q TARGETDIR=${prefix}\python27-x86 ALLUSERS=" | |||
|
146 | Invoke-Process ${prefix}\python27-x86\python.exe ${prefix}\assets\get-pip.py | |||
|
147 | Invoke-Process ${prefix}\python27-x86\Scripts\pip.exe "install ${prefix}\assets\virtualenv.tar.gz" | |||
|
148 | ||||
|
149 | Write-Output "installing Python 2.7 64-bit" | |||
|
150 | Invoke-Process msiexec.exe "/i ${prefix}\assets\python27-x64.msi /l* ${prefix}\assets\python27-x64.log /q TARGETDIR=${prefix}\python27-x64 ALLUSERS=" | |||
|
151 | Invoke-Process ${prefix}\python27-x64\python.exe ${prefix}\assets\get-pip.py | |||
|
152 | Invoke-Process ${prefix}\python27-x64\Scripts\pip.exe "install ${prefix}\assets\virtualenv.tar.gz" | |||
|
153 | ||||
|
154 | Install-Python3 "Python 3.5 32-bit" ${prefix}\assets\python35-x86.exe ${prefix}\python35-x86 ${pip} | |||
|
155 | Install-Python3 "Python 3.5 64-bit" ${prefix}\assets\python35-x64.exe ${prefix}\python35-x64 ${pip} | |||
|
156 | Install-Python3 "Python 3.6 32-bit" ${prefix}\assets\python36-x86.exe ${prefix}\python36-x86 ${pip} | |||
|
157 | Install-Python3 "Python 3.6 64-bit" ${prefix}\assets\python36-x64.exe ${prefix}\python36-x64 ${pip} | |||
|
158 | Install-Python3 "Python 3.7 32-bit" ${prefix}\assets\python37-x86.exe ${prefix}\python37-x86 ${pip} | |||
|
159 | Install-Python3 "Python 3.7 64-bit" ${prefix}\assets\python37-x64.exe ${prefix}\python37-x64 ${pip} | |||
|
160 | Install-Python3 "Python 3.8 32-bit" ${prefix}\assets\python38-x86.exe ${prefix}\python38-x86 ${pip} | |||
|
161 | Install-Python3 "Python 3.8 64-bit" ${prefix}\assets\python38-x64.exe ${prefix}\python38-x64 ${pip} | |||
|
162 | ||||
|
163 | Write-Output "installing Visual Studio 2017 Build Tools and SDKs" | |||
|
164 | Invoke-Process ${prefix}\assets\vs_buildtools.exe "--quiet --wait --norestart --nocache --channelUri https://aka.ms/vs/15/release/channel --add Microsoft.VisualStudio.Workload.MSBuildTools --add Microsoft.VisualStudio.Component.Windows10SDK.17763 --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.Windows10SDK --add Microsoft.VisualStudio.Component.VC.140" | |||
|
165 | ||||
|
166 | Write-Output "installing Visual C++ 9.0 for Python 2.7" | |||
|
167 | Invoke-Process msiexec.exe "/i ${prefix}\assets\VCForPython27.msi /l* ${prefix}\assets\VCForPython27.log /q" | |||
|
168 | ||||
|
169 | Write-Output "installing Inno Setup" | |||
|
170 | Invoke-Process ${prefix}\assets\InnoSetup.exe "/SP- /VERYSILENT /SUPPRESSMSGBOXES" | |||
|
171 | ||||
|
172 | Write-Output "extracting MinGW base archive" | |||
|
173 | Expand-Archive -Path ${prefix}\assets\mingw-get-bin.zip -DestinationPath "${prefix}\MinGW" -Force | |||
|
174 | ||||
|
175 | Write-Output "updating MinGW package catalogs" | |||
|
176 | Invoke-Process ${prefix}\MinGW\bin\mingw-get.exe "update" | |||
|
177 | ||||
|
178 | Write-Output "installing MinGW packages" | |||
|
179 | Invoke-Process ${prefix}\MinGW\bin\mingw-get.exe "install msys-base msys-coreutils msys-diffutils msys-unzip" | |||
|
180 | ||||
|
181 | # Construct a virtualenv useful for bootstrapping. It conveniently contains a | |||
|
182 | # Mercurial install. | |||
|
183 | Write-Output "creating bootstrap virtualenv with Mercurial" | |||
|
184 | Invoke-Process "$prefix\python27-x64\Scripts\virtualenv.exe" "${prefix}\venv-bootstrap" | |||
|
185 | Invoke-Process "${prefix}\venv-bootstrap\Scripts\pip.exe" "install ${prefix}\assets\${MERCURIAL_WHEEL_FILENAME}" | |||
|
186 | } | |||
|
187 | ||||
|
188 | function Clone-Mercurial-Repo($prefix, $repo_url, $dest) { | |||
|
189 | Write-Output "cloning $repo_url to $dest" | |||
|
190 | # TODO Figure out why CA verification isn't working in EC2 and remove | |||
|
191 | # --insecure. | |||
|
192 | Invoke-Process "${prefix}\venv-bootstrap\Scripts\hg.exe" "clone --insecure $repo_url $dest" | |||
|
193 | ||||
|
194 | # Mark repo as non-publishing by default for convenience. | |||
|
195 | Add-Content -Path "$dest\.hg\hgrc" -Value "`n[phases]`npublish = false" | |||
|
196 | } | |||
|
197 | ||||
|
198 | $prefix = "c:\hgdev" | |||
|
199 | Install-Dependencies $prefix | |||
|
200 | Clone-Mercurial-Repo $prefix "https://www.mercurial-scm.org/repo/hg" $prefix\src |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 |
@@ -0,0 +1,175 b'' | |||||
|
1 | # downloads.py - Code for downloading dependencies. | |||
|
2 | # | |||
|
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | ||||
|
8 | # no-check-code because Python 3 native. | |||
|
9 | ||||
|
10 | import gzip | |||
|
11 | import hashlib | |||
|
12 | import pathlib | |||
|
13 | import urllib.request | |||
|
14 | ||||
|
15 | ||||
|
16 | DOWNLOADS = { | |||
|
17 | 'gettext': { | |||
|
18 | 'url': 'https://versaweb.dl.sourceforge.net/project/gnuwin32/gettext/0.14.4/gettext-0.14.4-bin.zip', | |||
|
19 | 'size': 1606131, | |||
|
20 | 'sha256': '60b9ef26bc5cceef036f0424e542106cf158352b2677f43a01affd6d82a1d641', | |||
|
21 | 'version': '0.14.4', | |||
|
22 | }, | |||
|
23 | 'gettext-dep': { | |||
|
24 | 'url': 'https://versaweb.dl.sourceforge.net/project/gnuwin32/gettext/0.14.4/gettext-0.14.4-dep.zip', | |||
|
25 | 'size': 715086, | |||
|
26 | 'sha256': '411f94974492fd2ecf52590cb05b1023530aec67e64154a88b1e4ebcd9c28588', | |||
|
27 | }, | |||
|
28 | 'py2exe': { | |||
|
29 | 'url': 'https://versaweb.dl.sourceforge.net/project/py2exe/py2exe/0.6.9/py2exe-0.6.9.zip', | |||
|
30 | 'size': 149687, | |||
|
31 | 'sha256': '6bd383312e7d33eef2e43a5f236f9445e4f3e0f6b16333c6f183ed445c44ddbd', | |||
|
32 | 'version': '0.6.9', | |||
|
33 | }, | |||
|
34 | # The VC9 CRT merge modules aren't readily available on most systems because | |||
|
35 | # they are only installed as part of a full Visual Studio 2008 install. | |||
|
36 | # While we could potentially extract them from a Visual Studio 2008 | |||
|
37 | # installer, it is easier to just fetch them from a known URL. | |||
|
38 | 'vc9-crt-x86-msm': { | |||
|
39 | 'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/Microsoft_VC90_CRT_x86.msm', | |||
|
40 | 'size': 615424, | |||
|
41 | 'sha256': '837e887ef31b332feb58156f429389de345cb94504228bb9a523c25a9dd3d75e', | |||
|
42 | }, | |||
|
43 | 'vc9-crt-x86-msm-policy': { | |||
|
44 | 'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/policy_9_0_Microsoft_VC90_CRT_x86.msm', | |||
|
45 | 'size': 71168, | |||
|
46 | 'sha256': '3fbcf92e3801a0757f36c5e8d304e134a68d5cafd197a6df7734ae3e8825c940', | |||
|
47 | }, | |||
|
48 | 'vc9-crt-x64-msm': { | |||
|
49 | 'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/Microsoft_VC90_CRT_x86_x64.msm', | |||
|
50 | 'size': 662528, | |||
|
51 | 'sha256': '50d9639b5ad4844a2285269c7551bf5157ec636e32396ddcc6f7ec5bce487a7c', | |||
|
52 | }, | |||
|
53 | 'vc9-crt-x64-msm-policy': { | |||
|
54 | 'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/policy_9_0_Microsoft_VC90_CRT_x86_x64.msm', | |||
|
55 | 'size': 71168, | |||
|
56 | 'sha256': '0550ea1929b21239134ad3a678c944ba0f05f11087117b6cf0833e7110686486', | |||
|
57 | }, | |||
|
58 | 'virtualenv': { | |||
|
59 | 'url': 'https://files.pythonhosted.org/packages/37/db/89d6b043b22052109da35416abc3c397655e4bd3cff031446ba02b9654fa/virtualenv-16.4.3.tar.gz', | |||
|
60 | 'size': 3713208, | |||
|
61 | 'sha256': '984d7e607b0a5d1329425dd8845bd971b957424b5ba664729fab51ab8c11bc39', | |||
|
62 | 'version': '16.4.3', | |||
|
63 | }, | |||
|
64 | 'wix': { | |||
|
65 | 'url': 'https://github.com/wixtoolset/wix3/releases/download/wix3111rtm/wix311-binaries.zip', | |||
|
66 | 'size': 34358269, | |||
|
67 | 'sha256': '37f0a533b0978a454efb5dc3bd3598becf9660aaf4287e55bf68ca6b527d051d', | |||
|
68 | 'version': '3.11.1', | |||
|
69 | }, | |||
|
70 | } | |||
|
71 | ||||
|
72 | ||||
|
73 | def hash_path(p: pathlib.Path): | |||
|
74 | h = hashlib.sha256() | |||
|
75 | ||||
|
76 | with p.open('rb') as fh: | |||
|
77 | while True: | |||
|
78 | chunk = fh.read(65536) | |||
|
79 | if not chunk: | |||
|
80 | break | |||
|
81 | ||||
|
82 | h.update(chunk) | |||
|
83 | ||||
|
84 | return h.hexdigest() | |||
|
85 | ||||
|
86 | ||||
|
87 | class IntegrityError(Exception): | |||
|
88 | """Represents an integrity error when downloading a URL.""" | |||
|
89 | ||||
|
90 | ||||
|
91 | def secure_download_stream(url, size, sha256): | |||
|
92 | """Securely download a URL to a stream of chunks. | |||
|
93 | ||||
|
94 | If the integrity of the download fails, an IntegrityError is | |||
|
95 | raised. | |||
|
96 | """ | |||
|
97 | h = hashlib.sha256() | |||
|
98 | length = 0 | |||
|
99 | ||||
|
100 | with urllib.request.urlopen(url) as fh: | |||
|
101 | if not url.endswith('.gz') and fh.info().get('Content-Encoding') == 'gzip': | |||
|
102 | fh = gzip.GzipFile(fileobj=fh) | |||
|
103 | ||||
|
104 | while True: | |||
|
105 | chunk = fh.read(65536) | |||
|
106 | if not chunk: | |||
|
107 | break | |||
|
108 | ||||
|
109 | h.update(chunk) | |||
|
110 | length += len(chunk) | |||
|
111 | ||||
|
112 | yield chunk | |||
|
113 | ||||
|
114 | digest = h.hexdigest() | |||
|
115 | ||||
|
116 | if length != size: | |||
|
117 | raise IntegrityError('size mismatch on %s: wanted %d; got %d' % ( | |||
|
118 | url, size, length)) | |||
|
119 | ||||
|
120 | if digest != sha256: | |||
|
121 | raise IntegrityError('sha256 mismatch on %s: wanted %s; got %s' % ( | |||
|
122 | url, sha256, digest)) | |||
|
123 | ||||
|
124 | ||||
|
125 | def download_to_path(url: str, path: pathlib.Path, size: int, sha256: str): | |||
|
126 | """Download a URL to a filesystem path, possibly with verification.""" | |||
|
127 | ||||
|
128 | # We download to a temporary file and rename at the end so there's | |||
|
129 | # no chance of the final file being partially written or containing | |||
|
130 | # bad data. | |||
|
131 | print('downloading %s to %s' % (url, path)) | |||
|
132 | ||||
|
133 | if path.exists(): | |||
|
134 | good = True | |||
|
135 | ||||
|
136 | if path.stat().st_size != size: | |||
|
137 | print('existing file size is wrong; removing') | |||
|
138 | good = False | |||
|
139 | ||||
|
140 | if good: | |||
|
141 | if hash_path(path) != sha256: | |||
|
142 | print('existing file hash is wrong; removing') | |||
|
143 | good = False | |||
|
144 | ||||
|
145 | if good: | |||
|
146 | print('%s exists and passes integrity checks' % path) | |||
|
147 | return | |||
|
148 | ||||
|
149 | path.unlink() | |||
|
150 | ||||
|
151 | tmp = path.with_name('%s.tmp' % path.name) | |||
|
152 | ||||
|
153 | try: | |||
|
154 | with tmp.open('wb') as fh: | |||
|
155 | for chunk in secure_download_stream(url, size, sha256): | |||
|
156 | fh.write(chunk) | |||
|
157 | except IntegrityError: | |||
|
158 | tmp.unlink() | |||
|
159 | raise | |||
|
160 | ||||
|
161 | tmp.rename(path) | |||
|
162 | print('successfully downloaded %s' % url) | |||
|
163 | ||||
|
164 | ||||
|
165 | def download_entry(name: dict, dest_path: pathlib.Path, local_name=None) -> pathlib.Path: | |||
|
166 | entry = DOWNLOADS[name] | |||
|
167 | ||||
|
168 | url = entry['url'] | |||
|
169 | ||||
|
170 | local_name = local_name or url[url.rindex('/') + 1:] | |||
|
171 | ||||
|
172 | local_path = dest_path / local_name | |||
|
173 | download_to_path(url, local_path, entry['size'], entry['sha256']) | |||
|
174 | ||||
|
175 | return local_path, entry |
@@ -0,0 +1,78 b'' | |||||
|
1 | # inno.py - Inno Setup functionality. | |||
|
2 | # | |||
|
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | ||||
|
8 | # no-check-code because Python 3 native. | |||
|
9 | ||||
|
10 | import os | |||
|
11 | import pathlib | |||
|
12 | import shutil | |||
|
13 | import subprocess | |||
|
14 | ||||
|
15 | from .py2exe import ( | |||
|
16 | build_py2exe, | |||
|
17 | ) | |||
|
18 | from .util import ( | |||
|
19 | find_vc_runtime_files, | |||
|
20 | ) | |||
|
21 | ||||
|
22 | ||||
|
23 | EXTRA_PACKAGES = { | |||
|
24 | 'dulwich', | |||
|
25 | 'keyring', | |||
|
26 | 'pygments', | |||
|
27 | 'win32ctypes', | |||
|
28 | } | |||
|
29 | ||||
|
30 | ||||
|
31 | def build(source_dir: pathlib.Path, build_dir: pathlib.Path, | |||
|
32 | python_exe: pathlib.Path, iscc_exe: pathlib.Path, | |||
|
33 | version=None): | |||
|
34 | """Build the Inno installer. | |||
|
35 | ||||
|
36 | Build files will be placed in ``build_dir``. | |||
|
37 | ||||
|
38 | py2exe's setup.py doesn't use setuptools. It doesn't have modern logic | |||
|
39 | for finding the Python 2.7 toolchain. So, we require the environment | |||
|
40 | to already be configured with an active toolchain. | |||
|
41 | """ | |||
|
42 | if not iscc_exe.exists(): | |||
|
43 | raise Exception('%s does not exist' % iscc_exe) | |||
|
44 | ||||
|
45 | vc_x64 = r'\x64' in os.environ.get('LIB', '') | |||
|
46 | ||||
|
47 | requirements_txt = (source_dir / 'contrib' / 'packaging' / | |||
|
48 | 'inno' / 'requirements.txt') | |||
|
49 | ||||
|
50 | build_py2exe(source_dir, build_dir, python_exe, 'inno', | |||
|
51 | requirements_txt, extra_packages=EXTRA_PACKAGES) | |||
|
52 | ||||
|
53 | # hg.exe depends on VC9 runtime DLLs. Copy those into place. | |||
|
54 | for f in find_vc_runtime_files(vc_x64): | |||
|
55 | if f.name.endswith('.manifest'): | |||
|
56 | basename = 'Microsoft.VC90.CRT.manifest' | |||
|
57 | else: | |||
|
58 | basename = f.name | |||
|
59 | ||||
|
60 | dest_path = source_dir / 'dist' / basename | |||
|
61 | ||||
|
62 | print('copying %s to %s' % (f, dest_path)) | |||
|
63 | shutil.copyfile(f, dest_path) | |||
|
64 | ||||
|
65 | print('creating installer') | |||
|
66 | ||||
|
67 | args = [str(iscc_exe)] | |||
|
68 | ||||
|
69 | if vc_x64: | |||
|
70 | args.append('/dARCH=x64') | |||
|
71 | ||||
|
72 | if version: | |||
|
73 | args.append('/dVERSION=%s' % version) | |||
|
74 | ||||
|
75 | args.append('/Odist') | |||
|
76 | args.append('contrib/packaging/inno/mercurial.iss') | |||
|
77 | ||||
|
78 | subprocess.run(args, cwd=str(source_dir), check=True) |
@@ -0,0 +1,150 b'' | |||||
|
1 | # py2exe.py - Functionality for performing py2exe builds. | |||
|
2 | # | |||
|
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | ||||
|
8 | # no-check-code because Python 3 native. | |||
|
9 | ||||
|
10 | import os | |||
|
11 | import pathlib | |||
|
12 | import subprocess | |||
|
13 | ||||
|
14 | from .downloads import ( | |||
|
15 | download_entry, | |||
|
16 | ) | |||
|
17 | from .util import ( | |||
|
18 | extract_tar_to_directory, | |||
|
19 | extract_zip_to_directory, | |||
|
20 | python_exe_info, | |||
|
21 | ) | |||
|
22 | ||||
|
23 | ||||
|
24 | def build_py2exe(source_dir: pathlib.Path, build_dir: pathlib.Path, | |||
|
25 | python_exe: pathlib.Path, build_name: str, | |||
|
26 | venv_requirements_txt: pathlib.Path, | |||
|
27 | extra_packages=None, extra_excludes=None, | |||
|
28 | extra_dll_excludes=None, | |||
|
29 | extra_packages_script=None): | |||
|
30 | """Build Mercurial with py2exe. | |||
|
31 | ||||
|
32 | Build files will be placed in ``build_dir``. | |||
|
33 | ||||
|
34 | py2exe's setup.py doesn't use setuptools. It doesn't have modern logic | |||
|
35 | for finding the Python 2.7 toolchain. So, we require the environment | |||
|
36 | to already be configured with an active toolchain. | |||
|
37 | """ | |||
|
38 | if 'VCINSTALLDIR' not in os.environ: | |||
|
39 | raise Exception('not running from a Visual C++ build environment; ' | |||
|
40 | 'execute the "Visual C++ <version> Command Prompt" ' | |||
|
41 | 'application shortcut or a vcsvarsall.bat file') | |||
|
42 | ||||
|
43 | # Identity x86/x64 and validate the environment matches the Python | |||
|
44 | # architecture. | |||
|
45 | vc_x64 = r'\x64' in os.environ['LIB'] | |||
|
46 | ||||
|
47 | py_info = python_exe_info(python_exe) | |||
|
48 | ||||
|
49 | if vc_x64: | |||
|
50 | if py_info['arch'] != '64bit': | |||
|
51 | raise Exception('architecture mismatch: Visual C++ environment ' | |||
|
52 | 'is configured for 64-bit but Python is 32-bit') | |||
|
53 | else: | |||
|
54 | if py_info['arch'] != '32bit': | |||
|
55 | raise Exception('architecture mismatch: Visual C++ environment ' | |||
|
56 | 'is configured for 32-bit but Python is 64-bit') | |||
|
57 | ||||
|
58 | if py_info['py3']: | |||
|
59 | raise Exception('Only Python 2 is currently supported') | |||
|
60 | ||||
|
61 | build_dir.mkdir(exist_ok=True) | |||
|
62 | ||||
|
63 | gettext_pkg, gettext_entry = download_entry('gettext', build_dir) | |||
|
64 | gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0] | |||
|
65 | virtualenv_pkg, virtualenv_entry = download_entry('virtualenv', build_dir) | |||
|
66 | py2exe_pkg, py2exe_entry = download_entry('py2exe', build_dir) | |||
|
67 | ||||
|
68 | venv_path = build_dir / ('venv-%s-%s' % (build_name, | |||
|
69 | 'x64' if vc_x64 else 'x86')) | |||
|
70 | ||||
|
71 | gettext_root = build_dir / ( | |||
|
72 | 'gettext-win-%s' % gettext_entry['version']) | |||
|
73 | ||||
|
74 | if not gettext_root.exists(): | |||
|
75 | extract_zip_to_directory(gettext_pkg, gettext_root) | |||
|
76 | extract_zip_to_directory(gettext_dep_pkg, gettext_root) | |||
|
77 | ||||
|
78 | # This assumes Python 2. We don't need virtualenv on Python 3. | |||
|
79 | virtualenv_src_path = build_dir / ( | |||
|
80 | 'virtualenv-%s' % virtualenv_entry['version']) | |||
|
81 | virtualenv_py = virtualenv_src_path / 'virtualenv.py' | |||
|
82 | ||||
|
83 | if not virtualenv_src_path.exists(): | |||
|
84 | extract_tar_to_directory(virtualenv_pkg, build_dir) | |||
|
85 | ||||
|
86 | py2exe_source_path = build_dir / ('py2exe-%s' % py2exe_entry['version']) | |||
|
87 | ||||
|
88 | if not py2exe_source_path.exists(): | |||
|
89 | extract_zip_to_directory(py2exe_pkg, build_dir) | |||
|
90 | ||||
|
91 | if not venv_path.exists(): | |||
|
92 | print('creating virtualenv with dependencies') | |||
|
93 | subprocess.run( | |||
|
94 | [str(python_exe), str(virtualenv_py), str(venv_path)], | |||
|
95 | check=True) | |||
|
96 | ||||
|
97 | venv_python = venv_path / 'Scripts' / 'python.exe' | |||
|
98 | venv_pip = venv_path / 'Scripts' / 'pip.exe' | |||
|
99 | ||||
|
100 | subprocess.run([str(venv_pip), 'install', '-r', str(venv_requirements_txt)], | |||
|
101 | check=True) | |||
|
102 | ||||
|
103 | # Force distutils to use VC++ settings from environment, which was | |||
|
104 | # validated above. | |||
|
105 | env = dict(os.environ) | |||
|
106 | env['DISTUTILS_USE_SDK'] = '1' | |||
|
107 | env['MSSdk'] = '1' | |||
|
108 | ||||
|
109 | if extra_packages_script: | |||
|
110 | more_packages = set(subprocess.check_output( | |||
|
111 | extra_packages_script, | |||
|
112 | cwd=build_dir).split(b'\0')[-1].strip().decode('utf-8').splitlines()) | |||
|
113 | if more_packages: | |||
|
114 | if not extra_packages: | |||
|
115 | extra_packages = more_packages | |||
|
116 | else: | |||
|
117 | extra_packages |= more_packages | |||
|
118 | ||||
|
119 | if extra_packages: | |||
|
120 | env['HG_PY2EXE_EXTRA_PACKAGES'] = ' '.join(sorted(extra_packages)) | |||
|
121 | hgext3rd_extras = sorted( | |||
|
122 | e for e in extra_packages if e.startswith('hgext3rd.')) | |||
|
123 | if hgext3rd_extras: | |||
|
124 | env['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'] = ' '.join(hgext3rd_extras) | |||
|
125 | if extra_excludes: | |||
|
126 | env['HG_PY2EXE_EXTRA_EXCLUDES'] = ' '.join(sorted(extra_excludes)) | |||
|
127 | if extra_dll_excludes: | |||
|
128 | env['HG_PY2EXE_EXTRA_DLL_EXCLUDES'] = ' '.join( | |||
|
129 | sorted(extra_dll_excludes)) | |||
|
130 | ||||
|
131 | py2exe_py_path = venv_path / 'Lib' / 'site-packages' / 'py2exe' | |||
|
132 | if not py2exe_py_path.exists(): | |||
|
133 | print('building py2exe') | |||
|
134 | subprocess.run([str(venv_python), 'setup.py', 'install'], | |||
|
135 | cwd=py2exe_source_path, | |||
|
136 | env=env, | |||
|
137 | check=True) | |||
|
138 | ||||
|
139 | # Register location of msgfmt and other binaries. | |||
|
140 | env['PATH'] = '%s%s%s' % ( | |||
|
141 | env['PATH'], os.pathsep, str(gettext_root / 'bin')) | |||
|
142 | ||||
|
143 | print('building Mercurial') | |||
|
144 | subprocess.run( | |||
|
145 | [str(venv_python), 'setup.py', | |||
|
146 | 'py2exe', | |||
|
147 | 'build_doc', '--html'], | |||
|
148 | cwd=str(source_dir), | |||
|
149 | env=env, | |||
|
150 | check=True) |
@@ -0,0 +1,155 b'' | |||||
|
1 | # util.py - Common packaging utility code. | |||
|
2 | # | |||
|
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | ||||
|
8 | # no-check-code because Python 3 native. | |||
|
9 | ||||
|
10 | import distutils.version | |||
|
11 | import getpass | |||
|
12 | import os | |||
|
13 | import pathlib | |||
|
14 | import subprocess | |||
|
15 | import tarfile | |||
|
16 | import zipfile | |||
|
17 | ||||
|
18 | ||||
|
19 | def extract_tar_to_directory(source: pathlib.Path, dest: pathlib.Path): | |||
|
20 | with tarfile.open(source, 'r') as tf: | |||
|
21 | tf.extractall(dest) | |||
|
22 | ||||
|
23 | ||||
|
24 | def extract_zip_to_directory(source: pathlib.Path, dest: pathlib.Path): | |||
|
25 | with zipfile.ZipFile(source, 'r') as zf: | |||
|
26 | zf.extractall(dest) | |||
|
27 | ||||
|
28 | ||||
|
29 | def find_vc_runtime_files(x64=False): | |||
|
30 | """Finds Visual C++ Runtime DLLs to include in distribution.""" | |||
|
31 | winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS' | |||
|
32 | ||||
|
33 | prefix = 'amd64' if x64 else 'x86' | |||
|
34 | ||||
|
35 | candidates = sorted(p for p in os.listdir(winsxs) | |||
|
36 | if p.lower().startswith('%s_microsoft.vc90.crt_' % prefix)) | |||
|
37 | ||||
|
38 | for p in candidates: | |||
|
39 | print('found candidate VC runtime: %s' % p) | |||
|
40 | ||||
|
41 | # Take the newest version. | |||
|
42 | version = candidates[-1] | |||
|
43 | ||||
|
44 | d = winsxs / version | |||
|
45 | ||||
|
46 | return [ | |||
|
47 | d / 'msvcm90.dll', | |||
|
48 | d / 'msvcp90.dll', | |||
|
49 | d / 'msvcr90.dll', | |||
|
50 | winsxs / 'Manifests' / ('%s.manifest' % version), | |||
|
51 | ] | |||
|
52 | ||||
|
53 | ||||
|
54 | def windows_10_sdk_info(): | |||
|
55 | """Resolves information about the Windows 10 SDK.""" | |||
|
56 | ||||
|
57 | base = pathlib.Path(os.environ['ProgramFiles(x86)']) / 'Windows Kits' / '10' | |||
|
58 | ||||
|
59 | if not base.is_dir(): | |||
|
60 | raise Exception('unable to find Windows 10 SDK at %s' % base) | |||
|
61 | ||||
|
62 | # Find the latest version. | |||
|
63 | bin_base = base / 'bin' | |||
|
64 | ||||
|
65 | versions = [v for v in os.listdir(bin_base) if v.startswith('10.')] | |||
|
66 | version = sorted(versions, reverse=True)[0] | |||
|
67 | ||||
|
68 | bin_version = bin_base / version | |||
|
69 | ||||
|
70 | return { | |||
|
71 | 'root': base, | |||
|
72 | 'version': version, | |||
|
73 | 'bin_root': bin_version, | |||
|
74 | 'bin_x86': bin_version / 'x86', | |||
|
75 | 'bin_x64': bin_version / 'x64' | |||
|
76 | } | |||
|
77 | ||||
|
78 | ||||
|
79 | def find_signtool(): | |||
|
80 | """Find signtool.exe from the Windows SDK.""" | |||
|
81 | sdk = windows_10_sdk_info() | |||
|
82 | ||||
|
83 | for key in ('bin_x64', 'bin_x86'): | |||
|
84 | p = sdk[key] / 'signtool.exe' | |||
|
85 | ||||
|
86 | if p.exists(): | |||
|
87 | return p | |||
|
88 | ||||
|
89 | raise Exception('could not find signtool.exe in Windows 10 SDK') | |||
|
90 | ||||
|
91 | ||||
|
92 | def sign_with_signtool(file_path, description, subject_name=None, | |||
|
93 | cert_path=None, cert_password=None, | |||
|
94 | timestamp_url=None): | |||
|
95 | """Digitally sign a file with signtool.exe. | |||
|
96 | ||||
|
97 | ``file_path`` is file to sign. | |||
|
98 | ``description`` is text that goes in the signature. | |||
|
99 | ||||
|
100 | The signing certificate can be specified by ``cert_path`` or | |||
|
101 | ``subject_name``. These correspond to the ``/f`` and ``/n`` arguments | |||
|
102 | to signtool.exe, respectively. | |||
|
103 | ||||
|
104 | The certificate password can be specified via ``cert_password``. If | |||
|
105 | not provided, you will be prompted for the password. | |||
|
106 | ||||
|
107 | ``timestamp_url`` is the URL of a RFC 3161 timestamp server (``/tr`` | |||
|
108 | argument to signtool.exe). | |||
|
109 | """ | |||
|
110 | if cert_path and subject_name: | |||
|
111 | raise ValueError('cannot specify both cert_path and subject_name') | |||
|
112 | ||||
|
113 | while cert_path and not cert_password: | |||
|
114 | cert_password = getpass.getpass('password for %s: ' % cert_path) | |||
|
115 | ||||
|
116 | args = [ | |||
|
117 | str(find_signtool()), 'sign', | |||
|
118 | '/v', | |||
|
119 | '/fd', 'sha256', | |||
|
120 | '/d', description, | |||
|
121 | ] | |||
|
122 | ||||
|
123 | if cert_path: | |||
|
124 | args.extend(['/f', str(cert_path), '/p', cert_password]) | |||
|
125 | elif subject_name: | |||
|
126 | args.extend(['/n', subject_name]) | |||
|
127 | ||||
|
128 | if timestamp_url: | |||
|
129 | args.extend(['/tr', timestamp_url, '/td', 'sha256']) | |||
|
130 | ||||
|
131 | args.append(str(file_path)) | |||
|
132 | ||||
|
133 | print('signing %s' % file_path) | |||
|
134 | subprocess.run(args, check=True) | |||
|
135 | ||||
|
136 | ||||
|
137 | PRINT_PYTHON_INFO = ''' | |||
|
138 | import platform; print("%s:%s" % (platform.architecture()[0], platform.python_version())) | |||
|
139 | '''.strip() | |||
|
140 | ||||
|
141 | ||||
|
142 | def python_exe_info(python_exe: pathlib.Path): | |||
|
143 | """Obtain information about a Python executable.""" | |||
|
144 | ||||
|
145 | res = subprocess.check_output([str(python_exe), '-c', PRINT_PYTHON_INFO]) | |||
|
146 | ||||
|
147 | arch, version = res.decode('utf-8').split(':') | |||
|
148 | ||||
|
149 | version = distutils.version.LooseVersion(version) | |||
|
150 | ||||
|
151 | return { | |||
|
152 | 'arch': arch, | |||
|
153 | 'version': version, | |||
|
154 | 'py3': version >= distutils.version.LooseVersion('3'), | |||
|
155 | } |
@@ -0,0 +1,327 b'' | |||||
|
1 | # wix.py - WiX installer functionality | |||
|
2 | # | |||
|
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | ||||
|
8 | # no-check-code because Python 3 native. | |||
|
9 | ||||
|
10 | import os | |||
|
11 | import pathlib | |||
|
12 | import re | |||
|
13 | import subprocess | |||
|
14 | import tempfile | |||
|
15 | import typing | |||
|
16 | import xml.dom.minidom | |||
|
17 | ||||
|
18 | from .downloads import ( | |||
|
19 | download_entry, | |||
|
20 | ) | |||
|
21 | from .py2exe import ( | |||
|
22 | build_py2exe, | |||
|
23 | ) | |||
|
24 | from .util import ( | |||
|
25 | extract_zip_to_directory, | |||
|
26 | sign_with_signtool, | |||
|
27 | ) | |||
|
28 | ||||
|
29 | ||||
|
30 | SUPPORT_WXS = [ | |||
|
31 | ('contrib.wxs', r'contrib'), | |||
|
32 | ('dist.wxs', r'dist'), | |||
|
33 | ('doc.wxs', r'doc'), | |||
|
34 | ('help.wxs', r'mercurial\help'), | |||
|
35 | ('i18n.wxs', r'i18n'), | |||
|
36 | ('locale.wxs', r'mercurial\locale'), | |||
|
37 | ('templates.wxs', r'mercurial\templates'), | |||
|
38 | ] | |||
|
39 | ||||
|
40 | ||||
|
41 | EXTRA_PACKAGES = { | |||
|
42 | 'distutils', | |||
|
43 | 'pygments', | |||
|
44 | } | |||
|
45 | ||||
|
46 | ||||
|
47 | def find_version(source_dir: pathlib.Path): | |||
|
48 | version_py = source_dir / 'mercurial' / '__version__.py' | |||
|
49 | ||||
|
50 | with version_py.open('r', encoding='utf-8') as fh: | |||
|
51 | source = fh.read().strip() | |||
|
52 | ||||
|
53 | m = re.search('version = b"(.*)"', source) | |||
|
54 | return m.group(1) | |||
|
55 | ||||
|
56 | ||||
|
57 | def normalize_version(version): | |||
|
58 | """Normalize Mercurial version string so WiX accepts it. | |||
|
59 | ||||
|
60 | Version strings have to be numeric X.Y.Z. | |||
|
61 | """ | |||
|
62 | ||||
|
63 | if '+' in version: | |||
|
64 | version, extra = version.split('+', 1) | |||
|
65 | else: | |||
|
66 | extra = None | |||
|
67 | ||||
|
68 | # 4.9rc0 | |||
|
69 | if version[:-1].endswith('rc'): | |||
|
70 | version = version[:-3] | |||
|
71 | ||||
|
72 | versions = [int(v) for v in version.split('.')] | |||
|
73 | while len(versions) < 3: | |||
|
74 | versions.append(0) | |||
|
75 | ||||
|
76 | major, minor, build = versions[:3] | |||
|
77 | ||||
|
78 | if extra: | |||
|
79 | # <commit count>-<hash>+<date> | |||
|
80 | build = int(extra.split('-')[0]) | |||
|
81 | ||||
|
82 | return '.'.join('%d' % x for x in (major, minor, build)) | |||
|
83 | ||||
|
84 | ||||
|
85 | def ensure_vc90_merge_modules(build_dir): | |||
|
86 | x86 = ( | |||
|
87 | download_entry('vc9-crt-x86-msm', build_dir, | |||
|
88 | local_name='microsoft.vcxx.crt.x86_msm.msm')[0], | |||
|
89 | download_entry('vc9-crt-x86-msm-policy', build_dir, | |||
|
90 | local_name='policy.x.xx.microsoft.vcxx.crt.x86_msm.msm')[0] | |||
|
91 | ) | |||
|
92 | ||||
|
93 | x64 = ( | |||
|
94 | download_entry('vc9-crt-x64-msm', build_dir, | |||
|
95 | local_name='microsoft.vcxx.crt.x64_msm.msm')[0], | |||
|
96 | download_entry('vc9-crt-x64-msm-policy', build_dir, | |||
|
97 | local_name='policy.x.xx.microsoft.vcxx.crt.x64_msm.msm')[0] | |||
|
98 | ) | |||
|
99 | return { | |||
|
100 | 'x86': x86, | |||
|
101 | 'x64': x64, | |||
|
102 | } | |||
|
103 | ||||
|
104 | ||||
|
105 | def run_candle(wix, cwd, wxs, source_dir, defines=None): | |||
|
106 | args = [ | |||
|
107 | str(wix / 'candle.exe'), | |||
|
108 | '-nologo', | |||
|
109 | str(wxs), | |||
|
110 | '-dSourceDir=%s' % source_dir, | |||
|
111 | ] | |||
|
112 | ||||
|
113 | if defines: | |||
|
114 | args.extend('-d%s=%s' % define for define in sorted(defines.items())) | |||
|
115 | ||||
|
116 | subprocess.run(args, cwd=str(cwd), check=True) | |||
|
117 | ||||
|
118 | ||||
|
119 | def make_post_build_signing_fn(name, subject_name=None, cert_path=None, | |||
|
120 | cert_password=None, timestamp_url=None): | |||
|
121 | """Create a callable that will use signtool to sign hg.exe.""" | |||
|
122 | ||||
|
123 | def post_build_sign(source_dir, build_dir, dist_dir, version): | |||
|
124 | description = '%s %s' % (name, version) | |||
|
125 | ||||
|
126 | sign_with_signtool(dist_dir / 'hg.exe', description, | |||
|
127 | subject_name=subject_name, cert_path=cert_path, | |||
|
128 | cert_password=cert_password, | |||
|
129 | timestamp_url=timestamp_url) | |||
|
130 | ||||
|
131 | return post_build_sign | |||
|
132 | ||||
|
133 | ||||
|
134 | LIBRARIES_XML = ''' | |||
|
135 | <?xml version="1.0" encoding="utf-8"?> | |||
|
136 | <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> | |||
|
137 | ||||
|
138 | <?include {wix_dir}/guids.wxi ?> | |||
|
139 | <?include {wix_dir}/defines.wxi ?> | |||
|
140 | ||||
|
141 | <Fragment> | |||
|
142 | <DirectoryRef Id="INSTALLDIR" FileSource="$(var.SourceDir)"> | |||
|
143 | <Directory Id="libdir" Name="lib" FileSource="$(var.SourceDir)/lib"> | |||
|
144 | <Component Id="libOutput" Guid="$(var.lib.guid)" Win64='$(var.IsX64)'> | |||
|
145 | </Component> | |||
|
146 | </Directory> | |||
|
147 | </DirectoryRef> | |||
|
148 | </Fragment> | |||
|
149 | </Wix> | |||
|
150 | '''.lstrip() | |||
|
151 | ||||
|
152 | ||||
|
153 | def make_libraries_xml(wix_dir: pathlib.Path, dist_dir: pathlib.Path): | |||
|
154 | """Make XML data for library components WXS.""" | |||
|
155 | # We can't use ElementTree because it doesn't handle the | |||
|
156 | # <?include ?> directives. | |||
|
157 | doc = xml.dom.minidom.parseString( | |||
|
158 | LIBRARIES_XML.format(wix_dir=str(wix_dir))) | |||
|
159 | ||||
|
160 | component = doc.getElementsByTagName('Component')[0] | |||
|
161 | ||||
|
162 | f = doc.createElement('File') | |||
|
163 | f.setAttribute('Name', 'library.zip') | |||
|
164 | f.setAttribute('KeyPath', 'yes') | |||
|
165 | component.appendChild(f) | |||
|
166 | ||||
|
167 | lib_dir = dist_dir / 'lib' | |||
|
168 | ||||
|
169 | for p in sorted(lib_dir.iterdir()): | |||
|
170 | if not p.name.endswith(('.dll', '.pyd')): | |||
|
171 | continue | |||
|
172 | ||||
|
173 | f = doc.createElement('File') | |||
|
174 | f.setAttribute('Name', p.name) | |||
|
175 | component.appendChild(f) | |||
|
176 | ||||
|
177 | return doc.toprettyxml() | |||
|
178 | ||||
|
179 | ||||
|
180 | def build_installer(source_dir: pathlib.Path, python_exe: pathlib.Path, | |||
|
181 | msi_name='mercurial', version=None, post_build_fn=None, | |||
|
182 | extra_packages_script=None, | |||
|
183 | extra_wxs:typing.Optional[typing.Dict[str,str]]=None, | |||
|
184 | extra_features:typing.Optional[typing.List[str]]=None): | |||
|
185 | """Build a WiX MSI installer. | |||
|
186 | ||||
|
187 | ``source_dir`` is the path to the Mercurial source tree to use. | |||
|
188 | ``arch`` is the target architecture. either ``x86`` or ``x64``. | |||
|
189 | ``python_exe`` is the path to the Python executable to use/bundle. | |||
|
190 | ``version`` is the Mercurial version string. If not defined, | |||
|
191 | ``mercurial/__version__.py`` will be consulted. | |||
|
192 | ``post_build_fn`` is a callable that will be called after building | |||
|
193 | Mercurial but before invoking WiX. It can be used to e.g. facilitate | |||
|
194 | signing. It is passed the paths to the Mercurial source, build, and | |||
|
195 | dist directories and the resolved Mercurial version. | |||
|
196 | ``extra_packages_script`` is a command to be run to inject extra packages | |||
|
197 | into the py2exe binary. It should stage packages into the virtualenv and | |||
|
198 | print a null byte followed by a newline-separated list of packages that | |||
|
199 | should be included in the exe. | |||
|
200 | ``extra_wxs`` is a dict of {wxs_name: working_dir_for_wxs_build}. | |||
|
201 | ``extra_features`` is a list of additional named Features to include in | |||
|
202 | the build. These must match Feature names in one of the wxs scripts. | |||
|
203 | """ | |||
|
204 | arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86' | |||
|
205 | ||||
|
206 | hg_build_dir = source_dir / 'build' | |||
|
207 | dist_dir = source_dir / 'dist' | |||
|
208 | wix_dir = source_dir / 'contrib' / 'packaging' / 'wix' | |||
|
209 | ||||
|
210 | requirements_txt = wix_dir / 'requirements.txt' | |||
|
211 | ||||
|
212 | build_py2exe(source_dir, hg_build_dir, | |||
|
213 | python_exe, 'wix', requirements_txt, | |||
|
214 | extra_packages=EXTRA_PACKAGES, | |||
|
215 | extra_packages_script=extra_packages_script) | |||
|
216 | ||||
|
217 | version = version or normalize_version(find_version(source_dir)) | |||
|
218 | print('using version string: %s' % version) | |||
|
219 | ||||
|
220 | if post_build_fn: | |||
|
221 | post_build_fn(source_dir, hg_build_dir, dist_dir, version) | |||
|
222 | ||||
|
223 | build_dir = hg_build_dir / ('wix-%s' % arch) | |||
|
224 | ||||
|
225 | build_dir.mkdir(exist_ok=True) | |||
|
226 | ||||
|
227 | wix_pkg, wix_entry = download_entry('wix', hg_build_dir) | |||
|
228 | wix_path = hg_build_dir / ('wix-%s' % wix_entry['version']) | |||
|
229 | ||||
|
230 | if not wix_path.exists(): | |||
|
231 | extract_zip_to_directory(wix_pkg, wix_path) | |||
|
232 | ||||
|
233 | ensure_vc90_merge_modules(hg_build_dir) | |||
|
234 | ||||
|
235 | source_build_rel = pathlib.Path(os.path.relpath(source_dir, build_dir)) | |||
|
236 | ||||
|
237 | defines = {'Platform': arch} | |||
|
238 | ||||
|
239 | for wxs, rel_path in SUPPORT_WXS: | |||
|
240 | wxs = wix_dir / wxs | |||
|
241 | wxs_source_dir = source_dir / rel_path | |||
|
242 | run_candle(wix_path, build_dir, wxs, wxs_source_dir, defines=defines) | |||
|
243 | ||||
|
244 | for source, rel_path in sorted((extra_wxs or {}).items()): | |||
|
245 | run_candle(wix_path, build_dir, source, rel_path, defines=defines) | |||
|
246 | ||||
|
247 | # candle.exe doesn't like when we have an open handle on the file. | |||
|
248 | # So use TemporaryDirectory() instead of NamedTemporaryFile(). | |||
|
249 | with tempfile.TemporaryDirectory() as td: | |||
|
250 | td = pathlib.Path(td) | |||
|
251 | ||||
|
252 | tf = td / 'library.wxs' | |||
|
253 | with tf.open('w') as fh: | |||
|
254 | fh.write(make_libraries_xml(wix_dir, dist_dir)) | |||
|
255 | ||||
|
256 | run_candle(wix_path, build_dir, tf, dist_dir, defines=defines) | |||
|
257 | ||||
|
258 | source = wix_dir / 'mercurial.wxs' | |||
|
259 | defines['Version'] = version | |||
|
260 | defines['Comments'] = 'Installs Mercurial version %s' % version | |||
|
261 | defines['VCRedistSrcDir'] = str(hg_build_dir) | |||
|
262 | if extra_features: | |||
|
263 | assert all(';' not in f for f in extra_features) | |||
|
264 | defines['MercurialExtraFeatures'] = ';'.join(extra_features) | |||
|
265 | ||||
|
266 | run_candle(wix_path, build_dir, source, source_build_rel, defines=defines) | |||
|
267 | ||||
|
268 | msi_path = source_dir / 'dist' / ( | |||
|
269 | '%s-%s-%s.msi' % (msi_name, version, arch)) | |||
|
270 | ||||
|
271 | args = [ | |||
|
272 | str(wix_path / 'light.exe'), | |||
|
273 | '-nologo', | |||
|
274 | '-ext', 'WixUIExtension', | |||
|
275 | '-sw1076', | |||
|
276 | '-spdb', | |||
|
277 | '-o', str(msi_path), | |||
|
278 | ] | |||
|
279 | ||||
|
280 | for source, rel_path in SUPPORT_WXS: | |||
|
281 | assert source.endswith('.wxs') | |||
|
282 | args.append(str(build_dir / ('%s.wixobj' % source[:-4]))) | |||
|
283 | ||||
|
284 | for source, rel_path in sorted((extra_wxs or {}).items()): | |||
|
285 | assert source.endswith('.wxs') | |||
|
286 | source = os.path.basename(source) | |||
|
287 | args.append(str(build_dir / ('%s.wixobj' % source[:-4]))) | |||
|
288 | ||||
|
289 | args.extend([ | |||
|
290 | str(build_dir / 'library.wixobj'), | |||
|
291 | str(build_dir / 'mercurial.wixobj'), | |||
|
292 | ]) | |||
|
293 | ||||
|
294 | subprocess.run(args, cwd=str(source_dir), check=True) | |||
|
295 | ||||
|
296 | print('%s created' % msi_path) | |||
|
297 | ||||
|
298 | return { | |||
|
299 | 'msi_path': msi_path, | |||
|
300 | } | |||
|
301 | ||||
|
302 | ||||
|
303 | def build_signed_installer(source_dir: pathlib.Path, python_exe: pathlib.Path, | |||
|
304 | name: str, version=None, subject_name=None, | |||
|
305 | cert_path=None, cert_password=None, | |||
|
306 | timestamp_url=None, extra_packages_script=None, | |||
|
307 | extra_wxs=None, extra_features=None): | |||
|
308 | """Build an installer with signed executables.""" | |||
|
309 | ||||
|
310 | post_build_fn = make_post_build_signing_fn( | |||
|
311 | name, | |||
|
312 | subject_name=subject_name, | |||
|
313 | cert_path=cert_path, | |||
|
314 | cert_password=cert_password, | |||
|
315 | timestamp_url=timestamp_url) | |||
|
316 | ||||
|
317 | info = build_installer(source_dir, python_exe=python_exe, | |||
|
318 | msi_name=name.lower(), version=version, | |||
|
319 | post_build_fn=post_build_fn, | |||
|
320 | extra_packages_script=extra_packages_script, | |||
|
321 | extra_wxs=extra_wxs, extra_features=extra_features) | |||
|
322 | ||||
|
323 | description = '%s %s' % (name, version) | |||
|
324 | ||||
|
325 | sign_with_signtool(info['msi_path'], description, | |||
|
326 | subject_name=subject_name, cert_path=cert_path, | |||
|
327 | cert_password=cert_password, timestamp_url=timestamp_url) |
@@ -0,0 +1,51 b'' | |||||
|
1 | #!/usr/bin/env python3 | |||
|
2 | # build.py - Inno installer build script. | |||
|
3 | # | |||
|
4 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
5 | # | |||
|
6 | # This software may be used and distributed according to the terms of the | |||
|
7 | # GNU General Public License version 2 or any later version. | |||
|
8 | ||||
|
9 | # This script automates the building of the Inno MSI installer for Mercurial. | |||
|
10 | ||||
|
11 | # no-check-code because Python 3 native. | |||
|
12 | ||||
|
13 | import argparse | |||
|
14 | import os | |||
|
15 | import pathlib | |||
|
16 | import sys | |||
|
17 | ||||
|
18 | ||||
|
19 | if __name__ == '__main__': | |||
|
20 | parser = argparse.ArgumentParser() | |||
|
21 | ||||
|
22 | parser.add_argument('--python', | |||
|
23 | required=True, | |||
|
24 | help='path to python.exe to use') | |||
|
25 | parser.add_argument('--iscc', | |||
|
26 | help='path to iscc.exe to use') | |||
|
27 | parser.add_argument('--version', | |||
|
28 | help='Mercurial version string to use ' | |||
|
29 | '(detected from __version__.py if not defined') | |||
|
30 | ||||
|
31 | args = parser.parse_args() | |||
|
32 | ||||
|
33 | if not os.path.isabs(args.python): | |||
|
34 | raise Exception('--python arg must be an absolute path') | |||
|
35 | ||||
|
36 | if args.iscc: | |||
|
37 | iscc = pathlib.Path(args.iscc) | |||
|
38 | else: | |||
|
39 | iscc = (pathlib.Path(os.environ['ProgramFiles(x86)']) / 'Inno Setup 5' / | |||
|
40 | 'ISCC.exe') | |||
|
41 | ||||
|
42 | here = pathlib.Path(os.path.abspath(os.path.dirname(__file__))) | |||
|
43 | source_dir = here.parent.parent.parent | |||
|
44 | build_dir = source_dir / 'build' | |||
|
45 | ||||
|
46 | sys.path.insert(0, str(source_dir / 'contrib' / 'packaging')) | |||
|
47 | ||||
|
48 | from hgpackaging.inno import build | |||
|
49 | ||||
|
50 | build(source_dir, build_dir, pathlib.Path(args.python), iscc, | |||
|
51 | version=args.version) |
@@ -0,0 +1,219 b'' | |||||
|
1 | // ---------------------------------------------------------------------------- | |||
|
2 | // | |||
|
3 | // Inno Setup Ver: 5.4.2 | |||
|
4 | // Script Version: 1.4.2 | |||
|
5 | // Author: Jared Breland <jbreland@legroom.net> | |||
|
6 | // Homepage: http://www.legroom.net/software | |||
|
7 | // License: GNU Lesser General Public License (LGPL), version 3 | |||
|
8 | // http://www.gnu.org/licenses/lgpl.html | |||
|
9 | // | |||
|
10 | // Script Function: | |||
|
11 | // Allow modification of environmental path directly from Inno Setup installers | |||
|
12 | // | |||
|
13 | // Instructions: | |||
|
14 | // Copy modpath.iss to the same directory as your setup script | |||
|
15 | // | |||
|
16 | // Add this statement to your [Setup] section | |||
|
17 | // ChangesEnvironment=true | |||
|
18 | // | |||
|
19 | // Add this statement to your [Tasks] section | |||
|
20 | // You can change the Description or Flags | |||
|
21 | // You can change the Name, but it must match the ModPathName setting below | |||
|
22 | // Name: modifypath; Description: &Add application directory to your environmental path; Flags: unchecked | |||
|
23 | // | |||
|
24 | // Add the following to the end of your [Code] section | |||
|
25 | // ModPathName defines the name of the task defined above | |||
|
26 | // ModPathType defines whether the 'user' or 'system' path will be modified; | |||
|
27 | // this will default to user if anything other than system is set | |||
|
28 | // setArrayLength must specify the total number of dirs to be added | |||
|
29 | // Result[0] contains first directory, Result[1] contains second, etc. | |||
|
30 | // const | |||
|
31 | // ModPathName = 'modifypath'; | |||
|
32 | // ModPathType = 'user'; | |||
|
33 | // | |||
|
34 | // function ModPathDir(): TArrayOfString; | |||
|
35 | // begin | |||
|
36 | // setArrayLength(Result, 1); | |||
|
37 | // Result[0] := ExpandConstant('{app}'); | |||
|
38 | // end; | |||
|
39 | // #include "modpath.iss" | |||
|
40 | // ---------------------------------------------------------------------------- | |||
|
41 | ||||
|
42 | procedure ModPath(); | |||
|
43 | var | |||
|
44 | oldpath: String; | |||
|
45 | newpath: String; | |||
|
46 | updatepath: Boolean; | |||
|
47 | pathArr: TArrayOfString; | |||
|
48 | aExecFile: String; | |||
|
49 | aExecArr: TArrayOfString; | |||
|
50 | i, d: Integer; | |||
|
51 | pathdir: TArrayOfString; | |||
|
52 | regroot: Integer; | |||
|
53 | regpath: String; | |||
|
54 | ||||
|
55 | begin | |||
|
56 | // Get constants from main script and adjust behavior accordingly | |||
|
57 | // ModPathType MUST be 'system' or 'user'; force 'user' if invalid | |||
|
58 | if ModPathType = 'system' then begin | |||
|
59 | regroot := HKEY_LOCAL_MACHINE; | |||
|
60 | regpath := 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'; | |||
|
61 | end else begin | |||
|
62 | regroot := HKEY_CURRENT_USER; | |||
|
63 | regpath := 'Environment'; | |||
|
64 | end; | |||
|
65 | ||||
|
66 | // Get array of new directories and act on each individually | |||
|
67 | pathdir := ModPathDir(); | |||
|
68 | for d := 0 to GetArrayLength(pathdir)-1 do begin | |||
|
69 | updatepath := true; | |||
|
70 | ||||
|
71 | // Modify WinNT path | |||
|
72 | if UsingWinNT() = true then begin | |||
|
73 | ||||
|
74 | // Get current path, split into an array | |||
|
75 | RegQueryStringValue(regroot, regpath, 'Path', oldpath); | |||
|
76 | oldpath := oldpath + ';'; | |||
|
77 | i := 0; | |||
|
78 | ||||
|
79 | while (Pos(';', oldpath) > 0) do begin | |||
|
80 | SetArrayLength(pathArr, i+1); | |||
|
81 | pathArr[i] := Copy(oldpath, 0, Pos(';', oldpath)-1); | |||
|
82 | oldpath := Copy(oldpath, Pos(';', oldpath)+1, Length(oldpath)); | |||
|
83 | i := i + 1; | |||
|
84 | ||||
|
85 | // Check if current directory matches app dir | |||
|
86 | if pathdir[d] = pathArr[i-1] then begin | |||
|
87 | // if uninstalling, remove dir from path | |||
|
88 | if IsUninstaller() = true then begin | |||
|
89 | continue; | |||
|
90 | // if installing, flag that dir already exists in path | |||
|
91 | end else begin | |||
|
92 | updatepath := false; | |||
|
93 | end; | |||
|
94 | end; | |||
|
95 | ||||
|
96 | // Add current directory to new path | |||
|
97 | if i = 1 then begin | |||
|
98 | newpath := pathArr[i-1]; | |||
|
99 | end else begin | |||
|
100 | newpath := newpath + ';' + pathArr[i-1]; | |||
|
101 | end; | |||
|
102 | end; | |||
|
103 | ||||
|
104 | // Append app dir to path if not already included | |||
|
105 | if (IsUninstaller() = false) AND (updatepath = true) then | |||
|
106 | newpath := newpath + ';' + pathdir[d]; | |||
|
107 | ||||
|
108 | // Write new path | |||
|
109 | RegWriteStringValue(regroot, regpath, 'Path', newpath); | |||
|
110 | ||||
|
111 | // Modify Win9x path | |||
|
112 | end else begin | |||
|
113 | ||||
|
114 | // Convert to shortened dirname | |||
|
115 | pathdir[d] := GetShortName(pathdir[d]); | |||
|
116 | ||||
|
117 | // If autoexec.bat exists, check if app dir already exists in path | |||
|
118 | aExecFile := 'C:\AUTOEXEC.BAT'; | |||
|
119 | if FileExists(aExecFile) then begin | |||
|
120 | LoadStringsFromFile(aExecFile, aExecArr); | |||
|
121 | for i := 0 to GetArrayLength(aExecArr)-1 do begin | |||
|
122 | if IsUninstaller() = false then begin | |||
|
123 | // If app dir already exists while installing, skip add | |||
|
124 | if (Pos(pathdir[d], aExecArr[i]) > 0) then | |||
|
125 | updatepath := false; | |||
|
126 | break; | |||
|
127 | end else begin | |||
|
128 | // If app dir exists and = what we originally set, then delete at uninstall | |||
|
129 | if aExecArr[i] = 'SET PATH=%PATH%;' + pathdir[d] then | |||
|
130 | aExecArr[i] := ''; | |||
|
131 | end; | |||
|
132 | end; | |||
|
133 | end; | |||
|
134 | ||||
|
135 | // If app dir not found, or autoexec.bat didn't exist, then (create and) append to current path | |||
|
136 | if (IsUninstaller() = false) AND (updatepath = true) then begin | |||
|
137 | SaveStringToFile(aExecFile, #13#10 + 'SET PATH=%PATH%;' + pathdir[d], True); | |||
|
138 | ||||
|
139 | // If uninstalling, write the full autoexec out | |||
|
140 | end else begin | |||
|
141 | SaveStringsToFile(aExecFile, aExecArr, False); | |||
|
142 | end; | |||
|
143 | end; | |||
|
144 | end; | |||
|
145 | end; | |||
|
146 | ||||
|
147 | // Split a string into an array using passed delimeter | |||
|
148 | procedure MPExplode(var Dest: TArrayOfString; Text: String; Separator: String); | |||
|
149 | var | |||
|
150 | i: Integer; | |||
|
151 | begin | |||
|
152 | i := 0; | |||
|
153 | repeat | |||
|
154 | SetArrayLength(Dest, i+1); | |||
|
155 | if Pos(Separator,Text) > 0 then begin | |||
|
156 | Dest[i] := Copy(Text, 1, Pos(Separator, Text)-1); | |||
|
157 | Text := Copy(Text, Pos(Separator,Text) + Length(Separator), Length(Text)); | |||
|
158 | i := i + 1; | |||
|
159 | end else begin | |||
|
160 | Dest[i] := Text; | |||
|
161 | Text := ''; | |||
|
162 | end; | |||
|
163 | until Length(Text)=0; | |||
|
164 | end; | |||
|
165 | ||||
|
166 | ||||
|
167 | procedure CurStepChanged(CurStep: TSetupStep); | |||
|
168 | var | |||
|
169 | taskname: String; | |||
|
170 | begin | |||
|
171 | taskname := ModPathName; | |||
|
172 | if CurStep = ssPostInstall then | |||
|
173 | if IsTaskSelected(taskname) then | |||
|
174 | ModPath(); | |||
|
175 | end; | |||
|
176 | ||||
|
177 | procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); | |||
|
178 | var | |||
|
179 | aSelectedTasks: TArrayOfString; | |||
|
180 | i: Integer; | |||
|
181 | taskname: String; | |||
|
182 | regpath: String; | |||
|
183 | regstring: String; | |||
|
184 | appid: String; | |||
|
185 | begin | |||
|
186 | // only run during actual uninstall | |||
|
187 | if CurUninstallStep = usUninstall then begin | |||
|
188 | // get list of selected tasks saved in registry at install time | |||
|
189 | appid := '{#emit SetupSetting("AppId")}'; | |||
|
190 | if appid = '' then appid := '{#emit SetupSetting("AppName")}'; | |||
|
191 | regpath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\'+appid+'_is1'); | |||
|
192 | RegQueryStringValue(HKLM, regpath, 'Inno Setup: Selected Tasks', regstring); | |||
|
193 | if regstring = '' then RegQueryStringValue(HKCU, regpath, 'Inno Setup: Selected Tasks', regstring); | |||
|
194 | ||||
|
195 | // check each task; if matches modpath taskname, trigger patch removal | |||
|
196 | if regstring <> '' then begin | |||
|
197 | taskname := ModPathName; | |||
|
198 | MPExplode(aSelectedTasks, regstring, ','); | |||
|
199 | if GetArrayLength(aSelectedTasks) > 0 then begin | |||
|
200 | for i := 0 to GetArrayLength(aSelectedTasks)-1 do begin | |||
|
201 | if comparetext(aSelectedTasks[i], taskname) = 0 then | |||
|
202 | ModPath(); | |||
|
203 | end; | |||
|
204 | end; | |||
|
205 | end; | |||
|
206 | end; | |||
|
207 | end; | |||
|
208 | ||||
|
209 | function NeedRestart(): Boolean; | |||
|
210 | var | |||
|
211 | taskname: String; | |||
|
212 | begin | |||
|
213 | taskname := ModPathName; | |||
|
214 | if IsTaskSelected(taskname) and not UsingWinNT() then begin | |||
|
215 | Result := True; | |||
|
216 | end else begin | |||
|
217 | Result := False; | |||
|
218 | end; | |||
|
219 | end; |
@@ -0,0 +1,38 b'' | |||||
|
1 | # | |||
|
2 | # This file is autogenerated by pip-compile | |||
|
3 | # To update, run: | |||
|
4 | # | |||
|
5 | # pip-compile --generate-hashes contrib/packaging/inno/requirements.txt.in -o contrib/packaging/inno/requirements.txt | |||
|
6 | # | |||
|
7 | certifi==2018.11.29 \ | |||
|
8 | --hash=sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7 \ | |||
|
9 | --hash=sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033 \ | |||
|
10 | # via dulwich | |||
|
11 | configparser==3.7.3 \ | |||
|
12 | --hash=sha256:27594cf4fc279f321974061ac69164aaebd2749af962ac8686b20503ac0bcf2d \ | |||
|
13 | --hash=sha256:9d51fe0a382f05b6b117c5e601fc219fede4a8c71703324af3f7d883aef476a3 \ | |||
|
14 | # via entrypoints | |||
|
15 | docutils==0.14 \ | |||
|
16 | --hash=sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6 \ | |||
|
17 | --hash=sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274 \ | |||
|
18 | --hash=sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6 | |||
|
19 | dulwich==0.19.11 \ | |||
|
20 | --hash=sha256:afbe070f6899357e33f63f3f3696e601731fef66c64a489dea1bc9f539f4a725 | |||
|
21 | entrypoints==0.3 \ | |||
|
22 | --hash=sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19 \ | |||
|
23 | --hash=sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451 \ | |||
|
24 | # via keyring | |||
|
25 | keyring==18.0.0 \ | |||
|
26 | --hash=sha256:12833d2b05d2055e0e25931184af9cd6a738f320a2264853cabbd8a3a0f0b65d \ | |||
|
27 | --hash=sha256:ca33f5ccc542b9ffaa196ee9a33488069e5e7eac77d5b81969f8a3ce74d0230c | |||
|
28 | pygments==2.3.1 \ | |||
|
29 | --hash=sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a \ | |||
|
30 | --hash=sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d | |||
|
31 | pywin32-ctypes==0.2.0 \ | |||
|
32 | --hash=sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942 \ | |||
|
33 | --hash=sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98 \ | |||
|
34 | # via keyring | |||
|
35 | urllib3==1.24.1 \ | |||
|
36 | --hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \ | |||
|
37 | --hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22 \ | |||
|
38 | # via dulwich |
@@ -0,0 +1,84 b'' | |||||
|
1 | #!/usr/bin/env python3 | |||
|
2 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
3 | # | |||
|
4 | # This software may be used and distributed according to the terms of the | |||
|
5 | # GNU General Public License version 2 or any later version. | |||
|
6 | ||||
|
7 | # no-check-code because Python 3 native. | |||
|
8 | ||||
|
9 | """Code to build Mercurial WiX installer.""" | |||
|
10 | ||||
|
11 | import argparse | |||
|
12 | import os | |||
|
13 | import pathlib | |||
|
14 | import sys | |||
|
15 | ||||
|
16 | ||||
|
17 | if __name__ == '__main__': | |||
|
18 | parser = argparse.ArgumentParser() | |||
|
19 | ||||
|
20 | parser.add_argument('--name', | |||
|
21 | help='Application name', | |||
|
22 | default='Mercurial') | |||
|
23 | parser.add_argument('--python', | |||
|
24 | help='Path to Python executable to use', | |||
|
25 | required=True) | |||
|
26 | parser.add_argument('--sign-sn', | |||
|
27 | help='Subject name (or fragment thereof) of certificate ' | |||
|
28 | 'to use for signing') | |||
|
29 | parser.add_argument('--sign-cert', | |||
|
30 | help='Path to certificate to use for signing') | |||
|
31 | parser.add_argument('--sign-password', | |||
|
32 | help='Password for signing certificate') | |||
|
33 | parser.add_argument('--sign-timestamp-url', | |||
|
34 | help='URL of timestamp server to use for signing') | |||
|
35 | parser.add_argument('--version', | |||
|
36 | help='Version string to use') | |||
|
37 | parser.add_argument('--extra-packages-script', | |||
|
38 | help=('Script to execute to include extra packages in ' | |||
|
39 | 'py2exe binary.')) | |||
|
40 | parser.add_argument('--extra-wxs', | |||
|
41 | help='CSV of path_to_wxs_file=working_dir_for_wxs_file') | |||
|
42 | parser.add_argument('--extra-features', | |||
|
43 | help=('CSV of extra feature names to include ' | |||
|
44 | 'in the installer from the extra wxs files')) | |||
|
45 | ||||
|
46 | args = parser.parse_args() | |||
|
47 | ||||
|
48 | here = pathlib.Path(os.path.abspath(os.path.dirname(__file__))) | |||
|
49 | source_dir = here.parent.parent.parent | |||
|
50 | ||||
|
51 | sys.path.insert(0, str(source_dir / 'contrib' / 'packaging')) | |||
|
52 | ||||
|
53 | from hgpackaging.wix import ( | |||
|
54 | build_installer, | |||
|
55 | build_signed_installer, | |||
|
56 | ) | |||
|
57 | ||||
|
58 | fn = build_installer | |||
|
59 | kwargs = { | |||
|
60 | 'source_dir': source_dir, | |||
|
61 | 'python_exe': pathlib.Path(args.python), | |||
|
62 | 'version': args.version, | |||
|
63 | } | |||
|
64 | ||||
|
65 | if not os.path.isabs(args.python): | |||
|
66 | raise Exception('--python arg must be an absolute path') | |||
|
67 | ||||
|
68 | if args.extra_packages_script: | |||
|
69 | kwargs['extra_packages_script'] = args.extra_packages_script | |||
|
70 | if args.extra_wxs: | |||
|
71 | kwargs['extra_wxs'] = dict( | |||
|
72 | thing.split("=") for thing in args.extra_wxs.split(',')) | |||
|
73 | if args.extra_features: | |||
|
74 | kwargs['extra_features'] = args.extra_features.split(',') | |||
|
75 | ||||
|
76 | if args.sign_sn or args.sign_cert: | |||
|
77 | fn = build_signed_installer | |||
|
78 | kwargs['name'] = args.name | |||
|
79 | kwargs['subject_name'] = args.sign_sn | |||
|
80 | kwargs['cert_path'] = args.sign_cert | |||
|
81 | kwargs['cert_password'] = args.sign_password | |||
|
82 | kwargs['timestamp_url'] = args.sign_timestamp_url | |||
|
83 | ||||
|
84 | fn(**kwargs) |
@@ -0,0 +1,13 b'' | |||||
|
1 | # | |||
|
2 | # This file is autogenerated by pip-compile | |||
|
3 | # To update, run: | |||
|
4 | # | |||
|
5 | # pip-compile --generate-hashes contrib/packaging/wix/requirements.txt.in -o contrib/packaging/wix/requirements.txt | |||
|
6 | # | |||
|
7 | docutils==0.14 \ | |||
|
8 | --hash=sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6 \ | |||
|
9 | --hash=sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274 \ | |||
|
10 | --hash=sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6 | |||
|
11 | pygments==2.3.1 \ | |||
|
12 | --hash=sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a \ | |||
|
13 | --hash=sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100755 |
|
NO CONTENT: new file 100755 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
@@ -5,7 +5,7 b'' | |||||
5 | # % make PREFIX=/opt/ install |
|
5 | # % make PREFIX=/opt/ install | |
6 |
|
6 | |||
7 | export PREFIX=/usr/local |
|
7 | export PREFIX=/usr/local | |
8 | PYTHON=python |
|
8 | PYTHON?=python | |
9 | $(eval HGROOT := $(shell pwd)) |
|
9 | $(eval HGROOT := $(shell pwd)) | |
10 | HGPYTHONS ?= $(HGROOT)/build/pythons |
|
10 | HGPYTHONS ?= $(HGROOT)/build/pythons | |
11 | PURE= |
|
11 | PURE= |
@@ -47,3 +47,6 b' parents(20000)' | |||||
47 | # The one below is used by rebase |
|
47 | # The one below is used by rebase | |
48 | (children(ancestor(tip~5, tip)) and ::(tip~5)):: |
|
48 | (children(ancestor(tip~5, tip)) and ::(tip~5)):: | |
49 | heads(commonancestors(last(head(), 2))) |
|
49 | heads(commonancestors(last(head(), 2))) | |
|
50 | heads(-10000:-1) | |||
|
51 | roots(-10000:-1) | |||
|
52 | only(max(head()), min(head())) |
@@ -25,7 +25,7 b' def reducetest(a, b):' | |||||
25 |
|
25 | |||
26 | try: |
|
26 | try: | |
27 | test1(a, b) |
|
27 | test1(a, b) | |
28 |
except Exception |
|
28 | except Exception: | |
29 | reductions += 1 |
|
29 | reductions += 1 | |
30 | tries = 0 |
|
30 | tries = 0 | |
31 | a = a2 |
|
31 | a = a2 |
@@ -40,6 +40,8 b' try:' | |||||
40 | except ImportError: |
|
40 | except ImportError: | |
41 | re2 = None |
|
41 | re2 = None | |
42 |
|
42 | |||
|
43 | import testparseutil | |||
|
44 | ||||
43 | def compilere(pat, multiline=False): |
|
45 | def compilere(pat, multiline=False): | |
44 | if multiline: |
|
46 | if multiline: | |
45 | pat = '(?m)' + pat |
|
47 | pat = '(?m)' + pat | |
@@ -231,8 +233,10 b' utestfilters = [' | |||||
231 | (r"( +)(#([^!][^\n]*\S)?)", repcomment), |
|
233 | (r"( +)(#([^!][^\n]*\S)?)", repcomment), | |
232 | ] |
|
234 | ] | |
233 |
|
235 | |||
234 | pypats = [ |
|
236 | # common patterns to check *.py | |
|
237 | commonpypats = [ | |||
235 | [ |
|
238 | [ | |
|
239 | (r'\\$', 'Use () to wrap long lines in Python, not \\'), | |||
236 | (r'^\s*def\s*\w+\s*\(.*,\s*\(', |
|
240 | (r'^\s*def\s*\w+\s*\(.*,\s*\(', | |
237 | "tuple parameter unpacking not available in Python 3+"), |
|
241 | "tuple parameter unpacking not available in Python 3+"), | |
238 | (r'lambda\s*\(.*,.*\)', |
|
242 | (r'lambda\s*\(.*,.*\)', | |
@@ -261,7 +265,6 b' pypats = [' | |||||
261 | # a pass at the same indent level, which is bogus |
|
265 | # a pass at the same indent level, which is bogus | |
262 | r'(?P=indent)pass[ \t\n#]' |
|
266 | r'(?P=indent)pass[ \t\n#]' | |
263 | ), 'omit superfluous pass'), |
|
267 | ), 'omit superfluous pass'), | |
264 | (r'.{81}', "line too long"), |
|
|||
265 | (r'[^\n]\Z', "no trailing newline"), |
|
268 | (r'[^\n]\Z', "no trailing newline"), | |
266 | (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"), |
|
269 | (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"), | |
267 | # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=', |
|
270 | # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=', | |
@@ -299,7 +302,6 b' pypats = [' | |||||
299 | "wrong whitespace around ="), |
|
302 | "wrong whitespace around ="), | |
300 | (r'\([^()]*( =[^=]|[^<>!=]= )', |
|
303 | (r'\([^()]*( =[^=]|[^<>!=]= )', | |
301 | "no whitespace around = for named parameters"), |
|
304 | "no whitespace around = for named parameters"), | |
302 | (r'raise Exception', "don't raise generic exceptions"), |
|
|||
303 | (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$', |
|
305 | (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$', | |
304 | "don't use old-style two-argument raise, use Exception(message)"), |
|
306 | "don't use old-style two-argument raise, use Exception(message)"), | |
305 | (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"), |
|
307 | (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"), | |
@@ -315,21 +317,12 b' pypats = [' | |||||
315 | "use opener.read() instead"), |
|
317 | "use opener.read() instead"), | |
316 | (r'opener\([^)]*\).write\(', |
|
318 | (r'opener\([^)]*\).write\(', | |
317 | "use opener.write() instead"), |
|
319 | "use opener.write() instead"), | |
318 | (r'[\s\(](open|file)\([^)]*\)\.read\(', |
|
|||
319 | "use util.readfile() instead"), |
|
|||
320 | (r'[\s\(](open|file)\([^)]*\)\.write\(', |
|
|||
321 | "use util.writefile() instead"), |
|
|||
322 | (r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))', |
|
|||
323 | "always assign an opened file to a variable, and close it afterwards"), |
|
|||
324 | (r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))', |
|
|||
325 | "always assign an opened file to a variable, and close it afterwards"), |
|
|||
326 | (r'(?i)descend[e]nt', "the proper spelling is descendAnt"), |
|
320 | (r'(?i)descend[e]nt', "the proper spelling is descendAnt"), | |
327 | (r'\.debug\(\_', "don't mark debug messages for translation"), |
|
321 | (r'\.debug\(\_', "don't mark debug messages for translation"), | |
328 | (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"), |
|
322 | (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"), | |
329 | (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'), |
|
323 | (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'), | |
330 | (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,', |
|
324 | (r'^\s*except\s([^\(,]+|\([^\)]+\))\s*,', | |
331 | 'legacy exception syntax; use "as" instead of ","'), |
|
325 | 'legacy exception syntax; use "as" instead of ","'), | |
332 | (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"), |
|
|||
333 | (r'release\(.*wlock, .*lock\)', "wrong lock release order"), |
|
326 | (r'release\(.*wlock, .*lock\)', "wrong lock release order"), | |
334 | (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"), |
|
327 | (r'\bdef\s+__bool__\b', "__bool__ should be __nonzero__ in Python 2"), | |
335 | (r'os\.path\.join\(.*, *(""|\'\')\)', |
|
328 | (r'os\.path\.join\(.*, *(""|\'\')\)', | |
@@ -339,7 +332,6 b' pypats = [' | |||||
339 | (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"), |
|
332 | (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"), | |
340 | (r'\butil\.Abort\b', "directly use error.Abort"), |
|
333 | (r'\butil\.Abort\b', "directly use error.Abort"), | |
341 | (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"), |
|
334 | (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"), | |
342 | (r'^import atexit', "don't use atexit, use ui.atexit"), |
|
|||
343 | (r'^import Queue', "don't use Queue, use pycompat.queue.Queue + " |
|
335 | (r'^import Queue', "don't use Queue, use pycompat.queue.Queue + " | |
344 | "pycompat.queue.Empty"), |
|
336 | "pycompat.queue.Empty"), | |
345 | (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"), |
|
337 | (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"), | |
@@ -358,6 +350,34 b' pypats = [' | |||||
358 | "don't convert rev to node before passing to revision(nodeorrev)"), |
|
350 | "don't convert rev to node before passing to revision(nodeorrev)"), | |
359 | (r'platform\.system\(\)', "don't use platform.system(), use pycompat"), |
|
351 | (r'platform\.system\(\)', "don't use platform.system(), use pycompat"), | |
360 |
|
352 | |||
|
353 | ], | |||
|
354 | # warnings | |||
|
355 | [ | |||
|
356 | ] | |||
|
357 | ] | |||
|
358 | ||||
|
359 | # patterns to check normal *.py files | |||
|
360 | pypats = [ | |||
|
361 | [ | |||
|
362 | # Ideally, these should be placed in "commonpypats" for | |||
|
363 | # consistency of coding rules in Mercurial source tree. | |||
|
364 | # But on the other hand, these are not so seriously required for | |||
|
365 | # python code fragments embedded in test scripts. Fixing test | |||
|
366 | # scripts for these patterns requires many changes, and has less | |||
|
367 | # profit than effort. | |||
|
368 | (r'.{81}', "line too long"), | |||
|
369 | (r'raise Exception', "don't raise generic exceptions"), | |||
|
370 | (r'[\s\(](open|file)\([^)]*\)\.read\(', | |||
|
371 | "use util.readfile() instead"), | |||
|
372 | (r'[\s\(](open|file)\([^)]*\)\.write\(', | |||
|
373 | "use util.writefile() instead"), | |||
|
374 | (r'^[\s\(]*(open(er)?|file)\([^)]*\)(?!\.close\(\))', | |||
|
375 | "always assign an opened file to a variable, and close it afterwards"), | |||
|
376 | (r'[\s\(](open|file)\([^)]*\)\.(?!close\(\))', | |||
|
377 | "always assign an opened file to a variable, and close it afterwards"), | |||
|
378 | (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"), | |||
|
379 | (r'^import atexit', "don't use atexit, use ui.atexit"), | |||
|
380 | ||||
361 | # rules depending on implementation of repquote() |
|
381 | # rules depending on implementation of repquote() | |
362 | (r' x+[xpqo%APM][\'"]\n\s+[\'"]x', |
|
382 | (r' x+[xpqo%APM][\'"]\n\s+[\'"]x', | |
363 | 'string join across lines with no space'), |
|
383 | 'string join across lines with no space'), | |
@@ -376,21 +396,35 b' pypats = [' | |||||
376 | # because _preparepats forcibly adds "\n" into [^...], |
|
396 | # because _preparepats forcibly adds "\n" into [^...], | |
377 | # even though this regexp wants match it against "\n")''', |
|
397 | # even though this regexp wants match it against "\n")''', | |
378 | "missing _() in ui message (use () to hide false-positives)"), |
|
398 | "missing _() in ui message (use () to hide false-positives)"), | |
379 | ], |
|
399 | ] + commonpypats[0], | |
380 | # warnings |
|
400 | # warnings | |
381 | [ |
|
401 | [ | |
382 | # rules depending on implementation of repquote() |
|
402 | # rules depending on implementation of repquote() | |
383 | (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"), |
|
403 | (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"), | |
384 | ] |
|
404 | ] + commonpypats[1] | |
385 | ] |
|
405 | ] | |
386 |
|
406 | |||
387 | pyfilters = [ |
|
407 | # patterns to check *.py for embedded ones in test script | |
|
408 | embeddedpypats = [ | |||
|
409 | [ | |||
|
410 | ] + commonpypats[0], | |||
|
411 | # warnings | |||
|
412 | [ | |||
|
413 | ] + commonpypats[1] | |||
|
414 | ] | |||
|
415 | ||||
|
416 | # common filters to convert *.py | |||
|
417 | commonpyfilters = [ | |||
388 | (r"""(?msx)(?P<comment>\#.*?$)| |
|
418 | (r"""(?msx)(?P<comment>\#.*?$)| | |
389 | ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!"))) |
|
419 | ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!"))) | |
390 | (?P<text>(([^\\]|\\.)*?)) |
|
420 | (?P<text>(([^\\]|\\.)*?)) | |
391 | (?P=quote))""", reppython), |
|
421 | (?P=quote))""", reppython), | |
392 | ] |
|
422 | ] | |
393 |
|
423 | |||
|
424 | # filters to convert normal *.py files | |||
|
425 | pyfilters = [ | |||
|
426 | ] + commonpyfilters | |||
|
427 | ||||
394 | # non-filter patterns |
|
428 | # non-filter patterns | |
395 | pynfpats = [ |
|
429 | pynfpats = [ | |
396 | [ |
|
430 | [ | |
@@ -403,6 +437,10 b' pynfpats = [' | |||||
403 | [], |
|
437 | [], | |
404 | ] |
|
438 | ] | |
405 |
|
439 | |||
|
440 | # filters to convert *.py for embedded ones in test script | |||
|
441 | embeddedpyfilters = [ | |||
|
442 | ] + commonpyfilters | |||
|
443 | ||||
406 | # extension non-filter patterns |
|
444 | # extension non-filter patterns | |
407 | pyextnfpats = [ |
|
445 | pyextnfpats = [ | |
408 | [(r'^"""\n?[A-Z]', "don't capitalize docstring title")], |
|
446 | [(r'^"""\n?[A-Z]', "don't capitalize docstring title")], | |
@@ -414,7 +452,7 b' txtfilters = []' | |||||
414 |
|
452 | |||
415 | txtpats = [ |
|
453 | txtpats = [ | |
416 | [ |
|
454 | [ | |
417 | ('\s$', 'trailing whitespace'), |
|
455 | (r'\s$', 'trailing whitespace'), | |
418 | ('.. note::[ \n][^\n]', 'add two newlines after note::') |
|
456 | ('.. note::[ \n][^\n]', 'add two newlines after note::') | |
419 | ], |
|
457 | ], | |
420 | [] |
|
458 | [] | |
@@ -537,9 +575,17 b' checks = [' | |||||
537 | allfilesfilters, allfilespats), |
|
575 | allfilesfilters, allfilespats), | |
538 | ] |
|
576 | ] | |
539 |
|
577 | |||
|
578 | # (desc, | |||
|
579 | # func to pick up embedded code fragments, | |||
|
580 | # list of patterns to convert target files | |||
|
581 | # list of patterns to detect errors/warnings) | |||
|
582 | embeddedchecks = [ | |||
|
583 | ('embedded python', | |||
|
584 | testparseutil.pyembedded, embeddedpyfilters, embeddedpypats) | |||
|
585 | ] | |||
|
586 | ||||
540 | def _preparepats(): |
|
587 | def _preparepats(): | |
541 | for c in checks: |
|
588 | def preparefailandwarn(failandwarn): | |
542 | failandwarn = c[-1] |
|
|||
543 | for pats in failandwarn: |
|
589 | for pats in failandwarn: | |
544 | for i, pseq in enumerate(pats): |
|
590 | for i, pseq in enumerate(pats): | |
545 | # fix-up regexes for multi-line searches |
|
591 | # fix-up regexes for multi-line searches | |
@@ -553,10 +599,19 b' def _preparepats():' | |||||
553 | p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p) |
|
599 | p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p) | |
554 |
|
600 | |||
555 | pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:] |
|
601 | pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:] | |
556 | filters = c[3] |
|
602 | ||
|
603 | def preparefilters(filters): | |||
557 | for i, flt in enumerate(filters): |
|
604 | for i, flt in enumerate(filters): | |
558 | filters[i] = re.compile(flt[0]), flt[1] |
|
605 | filters[i] = re.compile(flt[0]), flt[1] | |
559 |
|
606 | |||
|
607 | for cs in (checks, embeddedchecks): | |||
|
608 | for c in cs: | |||
|
609 | failandwarn = c[-1] | |||
|
610 | preparefailandwarn(failandwarn) | |||
|
611 | ||||
|
612 | filters = c[-2] | |||
|
613 | preparefilters(filters) | |||
|
614 | ||||
560 | class norepeatlogger(object): |
|
615 | class norepeatlogger(object): | |
561 | def __init__(self): |
|
616 | def __init__(self): | |
562 | self._lastseen = None |
|
617 | self._lastseen = None | |
@@ -604,13 +659,12 b' def checkfile(f, logfunc=_defaultlogger.' | |||||
604 |
|
659 | |||
605 | return True if no error is found, False otherwise. |
|
660 | return True if no error is found, False otherwise. | |
606 | """ |
|
661 | """ | |
607 | blamecache = None |
|
|||
608 | result = True |
|
662 | result = True | |
609 |
|
663 | |||
610 | try: |
|
664 | try: | |
611 | with opentext(f) as fp: |
|
665 | with opentext(f) as fp: | |
612 | try: |
|
666 | try: | |
613 |
pre |
|
667 | pre = fp.read() | |
614 | except UnicodeDecodeError as e: |
|
668 | except UnicodeDecodeError as e: | |
615 | print("%s while reading %s" % (e, f)) |
|
669 | print("%s while reading %s" % (e, f)) | |
616 | return result |
|
670 | return result | |
@@ -618,11 +672,12 b' def checkfile(f, logfunc=_defaultlogger.' | |||||
618 | print("Skipping %s, %s" % (f, str(e).split(':', 1)[0])) |
|
672 | print("Skipping %s, %s" % (f, str(e).split(':', 1)[0])) | |
619 | return result |
|
673 | return result | |
620 |
|
674 | |||
|
675 | # context information shared while single checkfile() invocation | |||
|
676 | context = {'blamecache': None} | |||
|
677 | ||||
621 | for name, match, magic, filters, pats in checks: |
|
678 | for name, match, magic, filters, pats in checks: | |
622 | post = pre # discard filtering result of previous check |
|
|||
623 | if debug: |
|
679 | if debug: | |
624 | print(name, f) |
|
680 | print(name, f) | |
625 | fc = 0 |
|
|||
626 | if not (re.match(match, f) or (magic and re.search(magic, pre))): |
|
681 | if not (re.match(match, f) or (magic and re.search(magic, pre))): | |
627 | if debug: |
|
682 | if debug: | |
628 | print("Skipping %s for %s it doesn't match %s" % ( |
|
683 | print("Skipping %s for %s it doesn't match %s" % ( | |
@@ -637,6 +692,74 b' def checkfile(f, logfunc=_defaultlogger.' | |||||
637 | # tests/test-check-code.t |
|
692 | # tests/test-check-code.t | |
638 | print("Skipping %s it has no-che?k-code (glob)" % f) |
|
693 | print("Skipping %s it has no-che?k-code (glob)" % f) | |
639 | return "Skip" # skip checking this file |
|
694 | return "Skip" # skip checking this file | |
|
695 | ||||
|
696 | fc = _checkfiledata(name, f, pre, filters, pats, context, | |||
|
697 | logfunc, maxerr, warnings, blame, debug, lineno) | |||
|
698 | if fc: | |||
|
699 | result = False | |||
|
700 | ||||
|
701 | if f.endswith('.t') and "no-" "check-code" not in pre: | |||
|
702 | if debug: | |||
|
703 | print("Checking embedded code in %s" % (f)) | |||
|
704 | ||||
|
705 | prelines = pre.splitlines() | |||
|
706 | embeddederros = [] | |||
|
707 | for name, embedded, filters, pats in embeddedchecks: | |||
|
708 | # "reset curmax at each repetition" treats maxerr as "max | |||
|
709 | # nubmer of errors in an actual file per entry of | |||
|
710 | # (embedded)checks" | |||
|
711 | curmaxerr = maxerr | |||
|
712 | ||||
|
713 | for found in embedded(f, prelines, embeddederros): | |||
|
714 | filename, starts, ends, code = found | |||
|
715 | fc = _checkfiledata(name, f, code, filters, pats, context, | |||
|
716 | logfunc, curmaxerr, warnings, blame, debug, | |||
|
717 | lineno, offset=starts - 1) | |||
|
718 | if fc: | |||
|
719 | result = False | |||
|
720 | if curmaxerr: | |||
|
721 | if fc >= curmaxerr: | |||
|
722 | break | |||
|
723 | curmaxerr -= fc | |||
|
724 | ||||
|
725 | return result | |||
|
726 | ||||
|
727 | def _checkfiledata(name, f, filedata, filters, pats, context, | |||
|
728 | logfunc, maxerr, warnings, blame, debug, lineno, | |||
|
729 | offset=None): | |||
|
730 | """Execute actual error check for file data | |||
|
731 | ||||
|
732 | :name: of the checking category | |||
|
733 | :f: filepath | |||
|
734 | :filedata: content of a file | |||
|
735 | :filters: to be applied before checking | |||
|
736 | :pats: to detect errors | |||
|
737 | :context: a dict of information shared while single checkfile() invocation | |||
|
738 | Valid keys: 'blamecache'. | |||
|
739 | :logfunc: function used to report error | |||
|
740 | logfunc(filename, linenumber, linecontent, errormessage) | |||
|
741 | :maxerr: number of error to display before aborting, or False to | |||
|
742 | report all errors | |||
|
743 | :warnings: whether warning level checks should be applied | |||
|
744 | :blame: whether blame information should be displayed at error reporting | |||
|
745 | :debug: whether debug information should be displayed | |||
|
746 | :lineno: whether lineno should be displayed at error reporting | |||
|
747 | :offset: line number offset of 'filedata' in 'f' for checking | |||
|
748 | an embedded code fragment, or None (offset=0 is different | |||
|
749 | from offset=None) | |||
|
750 | ||||
|
751 | returns number of detected errors. | |||
|
752 | """ | |||
|
753 | blamecache = context['blamecache'] | |||
|
754 | if offset is None: | |||
|
755 | lineoffset = 0 | |||
|
756 | else: | |||
|
757 | lineoffset = offset | |||
|
758 | ||||
|
759 | fc = 0 | |||
|
760 | pre = post = filedata | |||
|
761 | ||||
|
762 | if True: # TODO: get rid of this redundant 'if' block | |||
640 | for p, r in filters: |
|
763 | for p, r in filters: | |
641 | post = re.sub(p, r, post) |
|
764 | post = re.sub(p, r, post) | |
642 | nerrs = len(pats[0]) # nerr elements are errors |
|
765 | nerrs = len(pats[0]) # nerr elements are errors | |
@@ -679,20 +802,30 b' def checkfile(f, logfunc=_defaultlogger.' | |||||
679 | if ignore and re.search(ignore, l, re.MULTILINE): |
|
802 | if ignore and re.search(ignore, l, re.MULTILINE): | |
680 | if debug: |
|
803 | if debug: | |
681 | print("Skipping %s for %s:%s (ignore pattern)" % ( |
|
804 | print("Skipping %s for %s:%s (ignore pattern)" % ( | |
682 | name, f, n)) |
|
805 | name, f, (n + lineoffset))) | |
683 | continue |
|
806 | continue | |
684 | bd = "" |
|
807 | bd = "" | |
685 | if blame: |
|
808 | if blame: | |
686 | bd = 'working directory' |
|
809 | bd = 'working directory' | |
687 |
if |
|
810 | if blamecache is None: | |
688 | blamecache = getblame(f) |
|
811 | blamecache = getblame(f) | |
689 |
|
|
812 | context['blamecache'] = blamecache | |
690 |
|
|
813 | if (n + lineoffset) < len(blamecache): | |
691 |
|
|
814 | bl, bu, br = blamecache[(n + lineoffset)] | |
|
815 | if offset is None and bl == l: | |||
692 | bd = '%s@%s' % (bu, br) |
|
816 | bd = '%s@%s' % (bu, br) | |
|
817 | elif offset is not None and bl.endswith(l): | |||
|
818 | # "offset is not None" means "checking | |||
|
819 | # embedded code fragment". In this case, | |||
|
820 | # "l" does not have information about the | |||
|
821 | # beginning of an *original* line in the | |||
|
822 | # file (e.g. ' > '). | |||
|
823 | # Therefore, use "str.endswith()", and | |||
|
824 | # show "maybe" for a little loose | |||
|
825 | # examination. | |||
|
826 | bd = '%s@%s, maybe' % (bu, br) | |||
693 |
|
827 | |||
694 | errors.append((f, lineno and n + 1, l, msg, bd)) |
|
828 | errors.append((f, lineno and (n + lineoffset + 1), l, msg, bd)) | |
695 | result = False |
|
|||
696 |
|
829 | |||
697 | errors.sort() |
|
830 | errors.sort() | |
698 | for e in errors: |
|
831 | for e in errors: | |
@@ -702,7 +835,7 b' def checkfile(f, logfunc=_defaultlogger.' | |||||
702 | print(" (too many errors, giving up)") |
|
835 | print(" (too many errors, giving up)") | |
703 | break |
|
836 | break | |
704 |
|
837 | |||
705 |
return |
|
838 | return fc | |
706 |
|
839 | |||
707 | def main(): |
|
840 | def main(): | |
708 | parser = optparse.OptionParser("%prog [options] [files | -]") |
|
841 | parser = optparse.OptionParser("%prog [options] [files | -]") |
@@ -47,7 +47,7 b' errors = [' | |||||
47 | "adds a function with foo_bar naming"), |
|
47 | "adds a function with foo_bar naming"), | |
48 | ] |
|
48 | ] | |
49 |
|
49 | |||
50 | word = re.compile('\S') |
|
50 | word = re.compile(r'\S') | |
51 | def nonempty(first, second): |
|
51 | def nonempty(first, second): | |
52 | if word.search(first): |
|
52 | if word.search(first): | |
53 | return first |
|
53 | return first |
@@ -25,7 +25,7 b" configre = re.compile(br'''" | |||||
25 | (?:default=)?(?P<default>\S+?))? |
|
25 | (?:default=)?(?P<default>\S+?))? | |
26 | \)''', re.VERBOSE | re.MULTILINE) |
|
26 | \)''', re.VERBOSE | re.MULTILINE) | |
27 |
|
27 | |||
28 | configwithre = re.compile(b''' |
|
28 | configwithre = re.compile(br''' | |
29 | ui\.config(?P<ctype>with)\( |
|
29 | ui\.config(?P<ctype>with)\( | |
30 | # First argument is callback function. This doesn't parse robustly |
|
30 | # First argument is callback function. This doesn't parse robustly | |
31 | # if it is e.g. a function call. |
|
31 | # if it is e.g. a function call. | |
@@ -61,10 +61,10 b' def main(args):' | |||||
61 | linenum += 1 |
|
61 | linenum += 1 | |
62 |
|
62 | |||
63 | # check topic-like bits |
|
63 | # check topic-like bits | |
64 | m = re.match(b'\s*``(\S+)``', l) |
|
64 | m = re.match(br'\s*``(\S+)``', l) | |
65 | if m: |
|
65 | if m: | |
66 | prevname = m.group(1) |
|
66 | prevname = m.group(1) | |
67 | if re.match(b'^\s*-+$', l): |
|
67 | if re.match(br'^\s*-+$', l): | |
68 | sect = prevname |
|
68 | sect = prevname | |
69 | prevname = b'' |
|
69 | prevname = b'' | |
70 |
|
70 |
@@ -14,6 +14,7 b' import importlib' | |||||
14 | import os |
|
14 | import os | |
15 | import sys |
|
15 | import sys | |
16 | import traceback |
|
16 | import traceback | |
|
17 | import warnings | |||
17 |
|
18 | |||
18 | def check_compat_py2(f): |
|
19 | def check_compat_py2(f): | |
19 | """Check Python 3 compatibility for a file with Python 2""" |
|
20 | """Check Python 3 compatibility for a file with Python 2""" | |
@@ -45,7 +46,7 b' def check_compat_py3(f):' | |||||
45 | content = fh.read() |
|
46 | content = fh.read() | |
46 |
|
47 | |||
47 | try: |
|
48 | try: | |
48 | ast.parse(content) |
|
49 | ast.parse(content, filename=f) | |
49 | except SyntaxError as e: |
|
50 | except SyntaxError as e: | |
50 | print('%s: invalid syntax: %s' % (f, e)) |
|
51 | print('%s: invalid syntax: %s' % (f, e)) | |
51 | return |
|
52 | return | |
@@ -91,6 +92,11 b" if __name__ == '__main__':" | |||||
91 | fn = check_compat_py3 |
|
92 | fn = check_compat_py3 | |
92 |
|
93 | |||
93 | for f in sys.argv[1:]: |
|
94 | for f in sys.argv[1:]: | |
94 | fn(f) |
|
95 | with warnings.catch_warnings(record=True) as warns: | |
|
96 | fn(f) | |||
|
97 | ||||
|
98 | for w in warns: | |||
|
99 | print(warnings.formatwarning(w.message, w.category, | |||
|
100 | w.filename, w.lineno).rstrip()) | |||
95 |
|
101 | |||
96 | sys.exit(0) |
|
102 | sys.exit(0) |
@@ -84,8 +84,9 b' static void initcontext(context_t *ctx)' | |||||
84 |
|
84 | |||
85 | static void enlargecontext(context_t *ctx, size_t newsize) |
|
85 | static void enlargecontext(context_t *ctx, size_t newsize) | |
86 | { |
|
86 | { | |
87 | if (newsize <= ctx->maxdatasize) |
|
87 | if (newsize <= ctx->maxdatasize) { | |
88 | return; |
|
88 | return; | |
|
89 | } | |||
89 |
|
90 | |||
90 | newsize = defaultdatasize * |
|
91 | newsize = defaultdatasize * | |
91 | ((newsize + defaultdatasize - 1) / defaultdatasize); |
|
92 | ((newsize + defaultdatasize - 1) / defaultdatasize); | |
@@ -117,22 +118,25 b' static void readchannel(hgclient_t *hgc)' | |||||
117 |
|
118 | |||
118 | uint32_t datasize_n; |
|
119 | uint32_t datasize_n; | |
119 | rsize = recv(hgc->sockfd, &datasize_n, sizeof(datasize_n), 0); |
|
120 | rsize = recv(hgc->sockfd, &datasize_n, sizeof(datasize_n), 0); | |
120 | if (rsize != sizeof(datasize_n)) |
|
121 | if (rsize != sizeof(datasize_n)) { | |
121 | abortmsg("failed to read data size"); |
|
122 | abortmsg("failed to read data size"); | |
|
123 | } | |||
122 |
|
124 | |||
123 | /* datasize denotes the maximum size to write if input request */ |
|
125 | /* datasize denotes the maximum size to write if input request */ | |
124 | hgc->ctx.datasize = ntohl(datasize_n); |
|
126 | hgc->ctx.datasize = ntohl(datasize_n); | |
125 | enlargecontext(&hgc->ctx, hgc->ctx.datasize); |
|
127 | enlargecontext(&hgc->ctx, hgc->ctx.datasize); | |
126 |
|
128 | |||
127 | if (isupper(hgc->ctx.ch) && hgc->ctx.ch != 'S') |
|
129 | if (isupper(hgc->ctx.ch) && hgc->ctx.ch != 'S') { | |
128 | return; /* assumes input request */ |
|
130 | return; /* assumes input request */ | |
|
131 | } | |||
129 |
|
132 | |||
130 | size_t cursize = 0; |
|
133 | size_t cursize = 0; | |
131 | while (cursize < hgc->ctx.datasize) { |
|
134 | while (cursize < hgc->ctx.datasize) { | |
132 | rsize = recv(hgc->sockfd, hgc->ctx.data + cursize, |
|
135 | rsize = recv(hgc->sockfd, hgc->ctx.data + cursize, | |
133 | hgc->ctx.datasize - cursize, 0); |
|
136 | hgc->ctx.datasize - cursize, 0); | |
134 | if (rsize < 1) |
|
137 | if (rsize < 1) { | |
135 | abortmsg("failed to read data block"); |
|
138 | abortmsg("failed to read data block"); | |
|
139 | } | |||
136 | cursize += rsize; |
|
140 | cursize += rsize; | |
137 | } |
|
141 | } | |
138 | } |
|
142 | } | |
@@ -143,8 +147,9 b' static void sendall(int sockfd, const vo' | |||||
143 | const char *const endp = p + datasize; |
|
147 | const char *const endp = p + datasize; | |
144 | while (p < endp) { |
|
148 | while (p < endp) { | |
145 | ssize_t r = send(sockfd, p, endp - p, 0); |
|
149 | ssize_t r = send(sockfd, p, endp - p, 0); | |
146 | if (r < 0) |
|
150 | if (r < 0) { | |
147 | abortmsgerrno("cannot communicate"); |
|
151 | abortmsgerrno("cannot communicate"); | |
|
152 | } | |||
148 | p += r; |
|
153 | p += r; | |
149 | } |
|
154 | } | |
150 | } |
|
155 | } | |
@@ -186,8 +191,9 b' static void packcmdargs(context_t *ctx, ' | |||||
186 | ctx->datasize += n; |
|
191 | ctx->datasize += n; | |
187 | } |
|
192 | } | |
188 |
|
193 | |||
189 | if (ctx->datasize > 0) |
|
194 | if (ctx->datasize > 0) { | |
190 | --ctx->datasize; /* strip last '\0' */ |
|
195 | --ctx->datasize; /* strip last '\0' */ | |
|
196 | } | |||
191 | } |
|
197 | } | |
192 |
|
198 | |||
193 | /* Extract '\0'-separated list of args to new buffer, terminated by NULL */ |
|
199 | /* Extract '\0'-separated list of args to new buffer, terminated by NULL */ | |
@@ -205,8 +211,9 b' static const char **unpackcmdargsnul(con' | |||||
205 | args[nargs] = s; |
|
211 | args[nargs] = s; | |
206 | nargs++; |
|
212 | nargs++; | |
207 | s = memchr(s, '\0', e - s); |
|
213 | s = memchr(s, '\0', e - s); | |
208 | if (!s) |
|
214 | if (!s) { | |
209 | break; |
|
215 | break; | |
|
216 | } | |||
210 | s++; |
|
217 | s++; | |
211 | } |
|
218 | } | |
212 | args[nargs] = NULL; |
|
219 | args[nargs] = NULL; | |
@@ -225,8 +232,9 b' static void handlereadrequest(hgclient_t' | |||||
225 | static void handlereadlinerequest(hgclient_t *hgc) |
|
232 | static void handlereadlinerequest(hgclient_t *hgc) | |
226 | { |
|
233 | { | |
227 | context_t *ctx = &hgc->ctx; |
|
234 | context_t *ctx = &hgc->ctx; | |
228 | if (!fgets(ctx->data, ctx->datasize, stdin)) |
|
235 | if (!fgets(ctx->data, ctx->datasize, stdin)) { | |
229 | ctx->data[0] = '\0'; |
|
236 | ctx->data[0] = '\0'; | |
|
237 | } | |||
230 | ctx->datasize = strlen(ctx->data); |
|
238 | ctx->datasize = strlen(ctx->data); | |
231 | writeblock(hgc); |
|
239 | writeblock(hgc); | |
232 | } |
|
240 | } | |
@@ -239,8 +247,9 b' static void handlesystemrequest(hgclient' | |||||
239 | ctx->data[ctx->datasize] = '\0'; /* terminate last string */ |
|
247 | ctx->data[ctx->datasize] = '\0'; /* terminate last string */ | |
240 |
|
248 | |||
241 | const char **args = unpackcmdargsnul(ctx); |
|
249 | const char **args = unpackcmdargsnul(ctx); | |
242 | if (!args[0] || !args[1] || !args[2]) |
|
250 | if (!args[0] || !args[1] || !args[2]) { | |
243 | abortmsg("missing type or command or cwd in system request"); |
|
251 | abortmsg("missing type or command or cwd in system request"); | |
|
252 | } | |||
244 | if (strcmp(args[0], "system") == 0) { |
|
253 | if (strcmp(args[0], "system") == 0) { | |
245 | debugmsg("run '%s' at '%s'", args[1], args[2]); |
|
254 | debugmsg("run '%s' at '%s'", args[1], args[2]); | |
246 | int32_t r = runshellcmd(args[1], args + 3, args[2]); |
|
255 | int32_t r = runshellcmd(args[1], args + 3, args[2]); | |
@@ -252,8 +261,9 b' static void handlesystemrequest(hgclient' | |||||
252 | writeblock(hgc); |
|
261 | writeblock(hgc); | |
253 | } else if (strcmp(args[0], "pager") == 0) { |
|
262 | } else if (strcmp(args[0], "pager") == 0) { | |
254 | setuppager(args[1], args + 3); |
|
263 | setuppager(args[1], args + 3); | |
255 | if (hgc->capflags & CAP_ATTACHIO) |
|
264 | if (hgc->capflags & CAP_ATTACHIO) { | |
256 | attachio(hgc); |
|
265 | attachio(hgc); | |
|
266 | } | |||
257 | /* unblock the server */ |
|
267 | /* unblock the server */ | |
258 | static const char emptycmd[] = "\n"; |
|
268 | static const char emptycmd[] = "\n"; | |
259 | sendall(hgc->sockfd, emptycmd, sizeof(emptycmd) - 1); |
|
269 | sendall(hgc->sockfd, emptycmd, sizeof(emptycmd) - 1); | |
@@ -296,9 +306,10 b' static void handleresponse(hgclient_t *h' | |||||
296 | handlesystemrequest(hgc); |
|
306 | handlesystemrequest(hgc); | |
297 | break; |
|
307 | break; | |
298 | default: |
|
308 | default: | |
299 | if (isupper(ctx->ch)) |
|
309 | if (isupper(ctx->ch)) { | |
300 | abortmsg("cannot handle response (ch = %c)", |
|
310 | abortmsg("cannot handle response (ch = %c)", | |
301 | ctx->ch); |
|
311 | ctx->ch); | |
|
312 | } | |||
302 | } |
|
313 | } | |
303 | } |
|
314 | } | |
304 | } |
|
315 | } | |
@@ -308,8 +319,9 b' static unsigned int parsecapabilities(co' | |||||
308 | unsigned int flags = 0; |
|
319 | unsigned int flags = 0; | |
309 | while (s < e) { |
|
320 | while (s < e) { | |
310 | const char *t = strchr(s, ' '); |
|
321 | const char *t = strchr(s, ' '); | |
311 | if (!t || t > e) |
|
322 | if (!t || t > e) { | |
312 | t = e; |
|
323 | t = e; | |
|
324 | } | |||
313 | const cappair_t *cap; |
|
325 | const cappair_t *cap; | |
314 | for (cap = captable; cap->flag; ++cap) { |
|
326 | for (cap = captable; cap->flag; ++cap) { | |
315 | size_t n = t - s; |
|
327 | size_t n = t - s; | |
@@ -346,11 +358,13 b' static void readhello(hgclient_t *hgc)' | |||||
346 | const char *const dataend = ctx->data + ctx->datasize; |
|
358 | const char *const dataend = ctx->data + ctx->datasize; | |
347 | while (s < dataend) { |
|
359 | while (s < dataend) { | |
348 | const char *t = strchr(s, ':'); |
|
360 | const char *t = strchr(s, ':'); | |
349 | if (!t || t[1] != ' ') |
|
361 | if (!t || t[1] != ' ') { | |
350 | break; |
|
362 | break; | |
|
363 | } | |||
351 | const char *u = strchr(t + 2, '\n'); |
|
364 | const char *u = strchr(t + 2, '\n'); | |
352 | if (!u) |
|
365 | if (!u) { | |
353 | u = dataend; |
|
366 | u = dataend; | |
|
367 | } | |||
354 | if (strncmp(s, "capabilities:", t - s + 1) == 0) { |
|
368 | if (strncmp(s, "capabilities:", t - s + 1) == 0) { | |
355 | hgc->capflags = parsecapabilities(t + 2, u); |
|
369 | hgc->capflags = parsecapabilities(t + 2, u); | |
356 | } else if (strncmp(s, "pgid:", t - s + 1) == 0) { |
|
370 | } else if (strncmp(s, "pgid:", t - s + 1) == 0) { | |
@@ -367,8 +381,9 b' static void updateprocname(hgclient_t *h' | |||||
367 | { |
|
381 | { | |
368 | int r = snprintf(hgc->ctx.data, hgc->ctx.maxdatasize, "chg[worker/%d]", |
|
382 | int r = snprintf(hgc->ctx.data, hgc->ctx.maxdatasize, "chg[worker/%d]", | |
369 | (int)getpid()); |
|
383 | (int)getpid()); | |
370 | if (r < 0 || (size_t)r >= hgc->ctx.maxdatasize) |
|
384 | if (r < 0 || (size_t)r >= hgc->ctx.maxdatasize) { | |
371 | abortmsg("insufficient buffer to write procname (r = %d)", r); |
|
385 | abortmsg("insufficient buffer to write procname (r = %d)", r); | |
|
386 | } | |||
372 | hgc->ctx.datasize = (size_t)r; |
|
387 | hgc->ctx.datasize = (size_t)r; | |
373 | writeblockrequest(hgc, "setprocname"); |
|
388 | writeblockrequest(hgc, "setprocname"); | |
374 | } |
|
389 | } | |
@@ -380,8 +395,9 b' static void attachio(hgclient_t *hgc)' | |||||
380 | sendall(hgc->sockfd, chcmd, sizeof(chcmd) - 1); |
|
395 | sendall(hgc->sockfd, chcmd, sizeof(chcmd) - 1); | |
381 | readchannel(hgc); |
|
396 | readchannel(hgc); | |
382 | context_t *ctx = &hgc->ctx; |
|
397 | context_t *ctx = &hgc->ctx; | |
383 | if (ctx->ch != 'I') |
|
398 | if (ctx->ch != 'I') { | |
384 | abortmsg("unexpected response for attachio (ch = %c)", ctx->ch); |
|
399 | abortmsg("unexpected response for attachio (ch = %c)", ctx->ch); | |
|
400 | } | |||
385 |
|
401 | |||
386 | static const int fds[3] = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; |
|
402 | static const int fds[3] = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; | |
387 | struct msghdr msgh; |
|
403 | struct msghdr msgh; | |
@@ -399,23 +415,27 b' static void attachio(hgclient_t *hgc)' | |||||
399 | memcpy(CMSG_DATA(cmsg), fds, sizeof(fds)); |
|
415 | memcpy(CMSG_DATA(cmsg), fds, sizeof(fds)); | |
400 | msgh.msg_controllen = cmsg->cmsg_len; |
|
416 | msgh.msg_controllen = cmsg->cmsg_len; | |
401 | ssize_t r = sendmsg(hgc->sockfd, &msgh, 0); |
|
417 | ssize_t r = sendmsg(hgc->sockfd, &msgh, 0); | |
402 | if (r < 0) |
|
418 | if (r < 0) { | |
403 | abortmsgerrno("sendmsg failed"); |
|
419 | abortmsgerrno("sendmsg failed"); | |
|
420 | } | |||
404 |
|
421 | |||
405 | handleresponse(hgc); |
|
422 | handleresponse(hgc); | |
406 | int32_t n; |
|
423 | int32_t n; | |
407 | if (ctx->datasize != sizeof(n)) |
|
424 | if (ctx->datasize != sizeof(n)) { | |
408 | abortmsg("unexpected size of attachio result"); |
|
425 | abortmsg("unexpected size of attachio result"); | |
|
426 | } | |||
409 | memcpy(&n, ctx->data, sizeof(n)); |
|
427 | memcpy(&n, ctx->data, sizeof(n)); | |
410 | n = ntohl(n); |
|
428 | n = ntohl(n); | |
411 | if (n != sizeof(fds) / sizeof(fds[0])) |
|
429 | if (n != sizeof(fds) / sizeof(fds[0])) { | |
412 | abortmsg("failed to send fds (n = %d)", n); |
|
430 | abortmsg("failed to send fds (n = %d)", n); | |
|
431 | } | |||
413 | } |
|
432 | } | |
414 |
|
433 | |||
415 | static void chdirtocwd(hgclient_t *hgc) |
|
434 | static void chdirtocwd(hgclient_t *hgc) | |
416 | { |
|
435 | { | |
417 | if (!getcwd(hgc->ctx.data, hgc->ctx.maxdatasize)) |
|
436 | if (!getcwd(hgc->ctx.data, hgc->ctx.maxdatasize)) { | |
418 | abortmsgerrno("failed to getcwd"); |
|
437 | abortmsgerrno("failed to getcwd"); | |
|
438 | } | |||
419 | hgc->ctx.datasize = strlen(hgc->ctx.data); |
|
439 | hgc->ctx.datasize = strlen(hgc->ctx.data); | |
420 | writeblockrequest(hgc, "chdir"); |
|
440 | writeblockrequest(hgc, "chdir"); | |
421 | } |
|
441 | } | |
@@ -440,8 +460,9 b' static void forwardumask(hgclient_t *hgc' | |||||
440 | hgclient_t *hgc_open(const char *sockname) |
|
460 | hgclient_t *hgc_open(const char *sockname) | |
441 | { |
|
461 | { | |
442 | int fd = socket(AF_UNIX, SOCK_STREAM, 0); |
|
462 | int fd = socket(AF_UNIX, SOCK_STREAM, 0); | |
443 | if (fd < 0) |
|
463 | if (fd < 0) { | |
444 | abortmsgerrno("cannot create socket"); |
|
464 | abortmsgerrno("cannot create socket"); | |
|
465 | } | |||
445 |
|
466 | |||
446 | /* don't keep fd on fork(), so that it can be closed when the parent |
|
467 | /* don't keep fd on fork(), so that it can be closed when the parent | |
447 | * process get terminated. */ |
|
468 | * process get terminated. */ | |
@@ -456,34 +477,39 b' hgclient_t *hgc_open(const char *socknam' | |||||
456 | { |
|
477 | { | |
457 | const char *split = strrchr(sockname, '/'); |
|
478 | const char *split = strrchr(sockname, '/'); | |
458 | if (split && split != sockname) { |
|
479 | if (split && split != sockname) { | |
459 | if (split[1] == '\0') |
|
480 | if (split[1] == '\0') { | |
460 | abortmsg("sockname cannot end with a slash"); |
|
481 | abortmsg("sockname cannot end with a slash"); | |
|
482 | } | |||
461 | size_t len = split - sockname; |
|
483 | size_t len = split - sockname; | |
462 | char sockdir[len + 1]; |
|
484 | char sockdir[len + 1]; | |
463 | memcpy(sockdir, sockname, len); |
|
485 | memcpy(sockdir, sockname, len); | |
464 | sockdir[len] = '\0'; |
|
486 | sockdir[len] = '\0'; | |
465 |
|
487 | |||
466 | bakfd = open(".", O_DIRECTORY); |
|
488 | bakfd = open(".", O_DIRECTORY); | |
467 | if (bakfd == -1) |
|
489 | if (bakfd == -1) { | |
468 | abortmsgerrno("cannot open cwd"); |
|
490 | abortmsgerrno("cannot open cwd"); | |
|
491 | } | |||
469 |
|
492 | |||
470 | int r = chdir(sockdir); |
|
493 | int r = chdir(sockdir); | |
471 | if (r != 0) |
|
494 | if (r != 0) { | |
472 | abortmsgerrno("cannot chdir %s", sockdir); |
|
495 | abortmsgerrno("cannot chdir %s", sockdir); | |
|
496 | } | |||
473 |
|
497 | |||
474 | basename = split + 1; |
|
498 | basename = split + 1; | |
475 | } |
|
499 | } | |
476 | } |
|
500 | } | |
477 | if (strlen(basename) >= sizeof(addr.sun_path)) |
|
501 | if (strlen(basename) >= sizeof(addr.sun_path)) { | |
478 | abortmsg("sockname is too long: %s", basename); |
|
502 | abortmsg("sockname is too long: %s", basename); | |
|
503 | } | |||
479 | strncpy(addr.sun_path, basename, sizeof(addr.sun_path)); |
|
504 | strncpy(addr.sun_path, basename, sizeof(addr.sun_path)); | |
480 | addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; |
|
505 | addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; | |
481 |
|
506 | |||
482 | /* real connect */ |
|
507 | /* real connect */ | |
483 | int r = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); |
|
508 | int r = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); | |
484 | if (r < 0) { |
|
509 | if (r < 0) { | |
485 | if (errno != ENOENT && errno != ECONNREFUSED) |
|
510 | if (errno != ENOENT && errno != ECONNREFUSED) { | |
486 | abortmsgerrno("cannot connect to %s", sockname); |
|
511 | abortmsgerrno("cannot connect to %s", sockname); | |
|
512 | } | |||
487 | } |
|
513 | } | |
488 | if (bakfd != -1) { |
|
514 | if (bakfd != -1) { | |
489 | fchdirx(bakfd); |
|
515 | fchdirx(bakfd); | |
@@ -501,16 +527,21 b' hgclient_t *hgc_open(const char *socknam' | |||||
501 | initcontext(&hgc->ctx); |
|
527 | initcontext(&hgc->ctx); | |
502 |
|
528 | |||
503 | readhello(hgc); |
|
529 | readhello(hgc); | |
504 | if (!(hgc->capflags & CAP_RUNCOMMAND)) |
|
530 | if (!(hgc->capflags & CAP_RUNCOMMAND)) { | |
505 | abortmsg("insufficient capability: runcommand"); |
|
531 | abortmsg("insufficient capability: runcommand"); | |
506 | if (hgc->capflags & CAP_SETPROCNAME) |
|
532 | } | |
|
533 | if (hgc->capflags & CAP_SETPROCNAME) { | |||
507 | updateprocname(hgc); |
|
534 | updateprocname(hgc); | |
508 | if (hgc->capflags & CAP_ATTACHIO) |
|
535 | } | |
|
536 | if (hgc->capflags & CAP_ATTACHIO) { | |||
509 | attachio(hgc); |
|
537 | attachio(hgc); | |
510 | if (hgc->capflags & CAP_CHDIR) |
|
538 | } | |
|
539 | if (hgc->capflags & CAP_CHDIR) { | |||
511 | chdirtocwd(hgc); |
|
540 | chdirtocwd(hgc); | |
512 | if (hgc->capflags & CAP_SETUMASK2) |
|
541 | } | |
|
542 | if (hgc->capflags & CAP_SETUMASK2) { | |||
513 | forwardumask(hgc); |
|
543 | forwardumask(hgc); | |
|
544 | } | |||
514 |
|
545 | |||
515 | return hgc; |
|
546 | return hgc; | |
516 | } |
|
547 | } | |
@@ -555,16 +586,18 b' const char **hgc_validate(hgclient_t *hg' | |||||
555 | size_t argsize) |
|
586 | size_t argsize) | |
556 | { |
|
587 | { | |
557 | assert(hgc); |
|
588 | assert(hgc); | |
558 | if (!(hgc->capflags & CAP_VALIDATE)) |
|
589 | if (!(hgc->capflags & CAP_VALIDATE)) { | |
559 | return NULL; |
|
590 | return NULL; | |
|
591 | } | |||
560 |
|
592 | |||
561 | packcmdargs(&hgc->ctx, args, argsize); |
|
593 | packcmdargs(&hgc->ctx, args, argsize); | |
562 | writeblockrequest(hgc, "validate"); |
|
594 | writeblockrequest(hgc, "validate"); | |
563 | handleresponse(hgc); |
|
595 | handleresponse(hgc); | |
564 |
|
596 | |||
565 | /* the server returns '\0' if it can handle our request */ |
|
597 | /* the server returns '\0' if it can handle our request */ | |
566 | if (hgc->ctx.datasize <= 1) |
|
598 | if (hgc->ctx.datasize <= 1) { | |
567 | return NULL; |
|
599 | return NULL; | |
|
600 | } | |||
568 |
|
601 | |||
569 | /* make sure the buffer is '\0' terminated */ |
|
602 | /* make sure the buffer is '\0' terminated */ | |
570 | enlargecontext(&hgc->ctx, hgc->ctx.datasize + 1); |
|
603 | enlargecontext(&hgc->ctx, hgc->ctx.datasize + 1); | |
@@ -599,8 +632,9 b' int hgc_runcommand(hgclient_t *hgc, cons' | |||||
599 | void hgc_attachio(hgclient_t *hgc) |
|
632 | void hgc_attachio(hgclient_t *hgc) | |
600 | { |
|
633 | { | |
601 | assert(hgc); |
|
634 | assert(hgc); | |
602 | if (!(hgc->capflags & CAP_ATTACHIO)) |
|
635 | if (!(hgc->capflags & CAP_ATTACHIO)) { | |
603 | return; |
|
636 | return; | |
|
637 | } | |||
604 | attachio(hgc); |
|
638 | attachio(hgc); | |
605 | } |
|
639 | } | |
606 |
|
640 | |||
@@ -613,8 +647,9 b' void hgc_attachio(hgclient_t *hgc)' | |||||
613 | void hgc_setenv(hgclient_t *hgc, const char *const envp[]) |
|
647 | void hgc_setenv(hgclient_t *hgc, const char *const envp[]) | |
614 | { |
|
648 | { | |
615 | assert(hgc && envp); |
|
649 | assert(hgc && envp); | |
616 | if (!(hgc->capflags & CAP_SETENV)) |
|
650 | if (!(hgc->capflags & CAP_SETENV)) { | |
617 | return; |
|
651 | return; | |
|
652 | } | |||
618 | packcmdargs(&hgc->ctx, envp, /*argsize*/ -1); |
|
653 | packcmdargs(&hgc->ctx, envp, /*argsize*/ -1); | |
619 | writeblockrequest(hgc, "setenv"); |
|
654 | writeblockrequest(hgc, "setenv"); | |
620 | } |
|
655 | } |
@@ -25,8 +25,9 b' static pid_t peerpid = 0;' | |||||
25 | static void forwardsignal(int sig) |
|
25 | static void forwardsignal(int sig) | |
26 | { |
|
26 | { | |
27 | assert(peerpid > 0); |
|
27 | assert(peerpid > 0); | |
28 | if (kill(peerpid, sig) < 0) |
|
28 | if (kill(peerpid, sig) < 0) { | |
29 | abortmsgerrno("cannot kill %d", peerpid); |
|
29 | abortmsgerrno("cannot kill %d", peerpid); | |
|
30 | } | |||
30 | debugmsg("forward signal %d", sig); |
|
31 | debugmsg("forward signal %d", sig); | |
31 | } |
|
32 | } | |
32 |
|
33 | |||
@@ -34,8 +35,9 b' static void forwardsignaltogroup(int sig' | |||||
34 | { |
|
35 | { | |
35 | /* prefer kill(-pgid, sig), fallback to pid if pgid is invalid */ |
|
36 | /* prefer kill(-pgid, sig), fallback to pid if pgid is invalid */ | |
36 | pid_t killpid = peerpgid > 1 ? -peerpgid : peerpid; |
|
37 | pid_t killpid = peerpgid > 1 ? -peerpgid : peerpid; | |
37 | if (kill(killpid, sig) < 0) |
|
38 | if (kill(killpid, sig) < 0) { | |
38 | abortmsgerrno("cannot kill %d", killpid); |
|
39 | abortmsgerrno("cannot kill %d", killpid); | |
|
40 | } | |||
39 | debugmsg("forward signal %d to %d", sig, killpid); |
|
41 | debugmsg("forward signal %d to %d", sig, killpid); | |
40 | } |
|
42 | } | |
41 |
|
43 | |||
@@ -43,28 +45,36 b' static void handlestopsignal(int sig)' | |||||
43 | { |
|
45 | { | |
44 | sigset_t unblockset, oldset; |
|
46 | sigset_t unblockset, oldset; | |
45 | struct sigaction sa, oldsa; |
|
47 | struct sigaction sa, oldsa; | |
46 | if (sigemptyset(&unblockset) < 0) |
|
48 | if (sigemptyset(&unblockset) < 0) { | |
47 | goto error; |
|
49 | goto error; | |
48 | if (sigaddset(&unblockset, sig) < 0) |
|
50 | } | |
|
51 | if (sigaddset(&unblockset, sig) < 0) { | |||
49 | goto error; |
|
52 | goto error; | |
|
53 | } | |||
50 | memset(&sa, 0, sizeof(sa)); |
|
54 | memset(&sa, 0, sizeof(sa)); | |
51 | sa.sa_handler = SIG_DFL; |
|
55 | sa.sa_handler = SIG_DFL; | |
52 | sa.sa_flags = SA_RESTART; |
|
56 | sa.sa_flags = SA_RESTART; | |
53 | if (sigemptyset(&sa.sa_mask) < 0) |
|
57 | if (sigemptyset(&sa.sa_mask) < 0) { | |
54 | goto error; |
|
58 | goto error; | |
|
59 | } | |||
55 |
|
60 | |||
56 | forwardsignal(sig); |
|
61 | forwardsignal(sig); | |
57 | if (raise(sig) < 0) /* resend to self */ |
|
62 | if (raise(sig) < 0) { /* resend to self */ | |
58 | goto error; |
|
63 | goto error; | |
59 | if (sigaction(sig, &sa, &oldsa) < 0) |
|
64 | } | |
|
65 | if (sigaction(sig, &sa, &oldsa) < 0) { | |||
60 | goto error; |
|
66 | goto error; | |
61 | if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0) |
|
67 | } | |
|
68 | if (sigprocmask(SIG_UNBLOCK, &unblockset, &oldset) < 0) { | |||
62 | goto error; |
|
69 | goto error; | |
|
70 | } | |||
63 | /* resent signal will be handled before sigprocmask() returns */ |
|
71 | /* resent signal will be handled before sigprocmask() returns */ | |
64 | if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0) |
|
72 | if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0) { | |
65 | goto error; |
|
73 | goto error; | |
66 | if (sigaction(sig, &oldsa, NULL) < 0) |
|
74 | } | |
|
75 | if (sigaction(sig, &oldsa, NULL) < 0) { | |||
67 | goto error; |
|
76 | goto error; | |
|
77 | } | |||
68 | return; |
|
78 | return; | |
69 |
|
79 | |||
70 | error: |
|
80 | error: | |
@@ -73,19 +83,22 b' error:' | |||||
73 |
|
83 | |||
74 | static void handlechildsignal(int sig UNUSED_) |
|
84 | static void handlechildsignal(int sig UNUSED_) | |
75 | { |
|
85 | { | |
76 | if (peerpid == 0 || pagerpid == 0) |
|
86 | if (peerpid == 0 || pagerpid == 0) { | |
77 | return; |
|
87 | return; | |
|
88 | } | |||
78 | /* if pager exits, notify the server with SIGPIPE immediately. |
|
89 | /* if pager exits, notify the server with SIGPIPE immediately. | |
79 | * otherwise the server won't get SIGPIPE if it does not write |
|
90 | * otherwise the server won't get SIGPIPE if it does not write | |
80 | * anything. (issue5278) */ |
|
91 | * anything. (issue5278) */ | |
81 | if (waitpid(pagerpid, NULL, WNOHANG) == pagerpid) |
|
92 | if (waitpid(pagerpid, NULL, WNOHANG) == pagerpid) { | |
82 | kill(peerpid, SIGPIPE); |
|
93 | kill(peerpid, SIGPIPE); | |
|
94 | } | |||
83 | } |
|
95 | } | |
84 |
|
96 | |||
85 | void setupsignalhandler(pid_t pid, pid_t pgid) |
|
97 | void setupsignalhandler(pid_t pid, pid_t pgid) | |
86 | { |
|
98 | { | |
87 | if (pid <= 0) |
|
99 | if (pid <= 0) { | |
88 | return; |
|
100 | return; | |
|
101 | } | |||
89 | peerpid = pid; |
|
102 | peerpid = pid; | |
90 | peerpgid = (pgid <= 1 ? 0 : pgid); |
|
103 | peerpgid = (pgid <= 1 ? 0 : pgid); | |
91 |
|
104 | |||
@@ -98,42 +111,52 b' void setupsignalhandler(pid_t pid, pid_t' | |||||
98 | * - SIGINT: usually generated by the terminal */ |
|
111 | * - SIGINT: usually generated by the terminal */ | |
99 | sa.sa_handler = forwardsignaltogroup; |
|
112 | sa.sa_handler = forwardsignaltogroup; | |
100 | sa.sa_flags = SA_RESTART; |
|
113 | sa.sa_flags = SA_RESTART; | |
101 | if (sigemptyset(&sa.sa_mask) < 0) |
|
114 | if (sigemptyset(&sa.sa_mask) < 0) { | |
|
115 | goto error; | |||
|
116 | } | |||
|
117 | if (sigaction(SIGHUP, &sa, NULL) < 0) { | |||
102 | goto error; |
|
118 | goto error; | |
103 | if (sigaction(SIGHUP, &sa, NULL) < 0) |
|
119 | } | |
|
120 | if (sigaction(SIGINT, &sa, NULL) < 0) { | |||
104 | goto error; |
|
121 | goto error; | |
105 | if (sigaction(SIGINT, &sa, NULL) < 0) |
|
122 | } | |
106 | goto error; |
|
|||
107 |
|
123 | |||
108 | /* terminate frontend by double SIGTERM in case of server freeze */ |
|
124 | /* terminate frontend by double SIGTERM in case of server freeze */ | |
109 | sa.sa_handler = forwardsignal; |
|
125 | sa.sa_handler = forwardsignal; | |
110 | sa.sa_flags |= SA_RESETHAND; |
|
126 | sa.sa_flags |= SA_RESETHAND; | |
111 | if (sigaction(SIGTERM, &sa, NULL) < 0) |
|
127 | if (sigaction(SIGTERM, &sa, NULL) < 0) { | |
112 | goto error; |
|
128 | goto error; | |
|
129 | } | |||
113 |
|
130 | |||
114 | /* notify the worker about window resize events */ |
|
131 | /* notify the worker about window resize events */ | |
115 | sa.sa_flags = SA_RESTART; |
|
132 | sa.sa_flags = SA_RESTART; | |
116 | if (sigaction(SIGWINCH, &sa, NULL) < 0) |
|
133 | if (sigaction(SIGWINCH, &sa, NULL) < 0) { | |
117 | goto error; |
|
134 | goto error; | |
|
135 | } | |||
118 | /* forward user-defined signals */ |
|
136 | /* forward user-defined signals */ | |
119 | if (sigaction(SIGUSR1, &sa, NULL) < 0) |
|
137 | if (sigaction(SIGUSR1, &sa, NULL) < 0) { | |
120 | goto error; |
|
138 | goto error; | |
121 | if (sigaction(SIGUSR2, &sa, NULL) < 0) |
|
139 | } | |
|
140 | if (sigaction(SIGUSR2, &sa, NULL) < 0) { | |||
122 | goto error; |
|
141 | goto error; | |
|
142 | } | |||
123 | /* propagate job control requests to worker */ |
|
143 | /* propagate job control requests to worker */ | |
124 | sa.sa_handler = forwardsignal; |
|
144 | sa.sa_handler = forwardsignal; | |
125 | sa.sa_flags = SA_RESTART; |
|
145 | sa.sa_flags = SA_RESTART; | |
126 | if (sigaction(SIGCONT, &sa, NULL) < 0) |
|
146 | if (sigaction(SIGCONT, &sa, NULL) < 0) { | |
127 | goto error; |
|
147 | goto error; | |
|
148 | } | |||
128 | sa.sa_handler = handlestopsignal; |
|
149 | sa.sa_handler = handlestopsignal; | |
129 | sa.sa_flags = SA_RESTART; |
|
150 | sa.sa_flags = SA_RESTART; | |
130 | if (sigaction(SIGTSTP, &sa, NULL) < 0) |
|
151 | if (sigaction(SIGTSTP, &sa, NULL) < 0) { | |
131 | goto error; |
|
152 | goto error; | |
|
153 | } | |||
132 | /* get notified when pager exits */ |
|
154 | /* get notified when pager exits */ | |
133 | sa.sa_handler = handlechildsignal; |
|
155 | sa.sa_handler = handlechildsignal; | |
134 | sa.sa_flags = SA_RESTART; |
|
156 | sa.sa_flags = SA_RESTART; | |
135 | if (sigaction(SIGCHLD, &sa, NULL) < 0) |
|
157 | if (sigaction(SIGCHLD, &sa, NULL) < 0) { | |
136 | goto error; |
|
158 | goto error; | |
|
159 | } | |||
137 |
|
160 | |||
138 | return; |
|
161 | return; | |
139 |
|
162 | |||
@@ -147,26 +170,34 b' void restoresignalhandler(void)' | |||||
147 | memset(&sa, 0, sizeof(sa)); |
|
170 | memset(&sa, 0, sizeof(sa)); | |
148 | sa.sa_handler = SIG_DFL; |
|
171 | sa.sa_handler = SIG_DFL; | |
149 | sa.sa_flags = SA_RESTART; |
|
172 | sa.sa_flags = SA_RESTART; | |
150 | if (sigemptyset(&sa.sa_mask) < 0) |
|
173 | if (sigemptyset(&sa.sa_mask) < 0) { | |
151 | goto error; |
|
174 | goto error; | |
|
175 | } | |||
152 |
|
176 | |||
153 | if (sigaction(SIGHUP, &sa, NULL) < 0) |
|
177 | if (sigaction(SIGHUP, &sa, NULL) < 0) { | |
154 | goto error; |
|
178 | goto error; | |
155 | if (sigaction(SIGTERM, &sa, NULL) < 0) |
|
179 | } | |
|
180 | if (sigaction(SIGTERM, &sa, NULL) < 0) { | |||
156 | goto error; |
|
181 | goto error; | |
157 | if (sigaction(SIGWINCH, &sa, NULL) < 0) |
|
182 | } | |
|
183 | if (sigaction(SIGWINCH, &sa, NULL) < 0) { | |||
158 | goto error; |
|
184 | goto error; | |
159 | if (sigaction(SIGCONT, &sa, NULL) < 0) |
|
185 | } | |
|
186 | if (sigaction(SIGCONT, &sa, NULL) < 0) { | |||
160 | goto error; |
|
187 | goto error; | |
161 | if (sigaction(SIGTSTP, &sa, NULL) < 0) |
|
188 | } | |
|
189 | if (sigaction(SIGTSTP, &sa, NULL) < 0) { | |||
162 | goto error; |
|
190 | goto error; | |
163 | if (sigaction(SIGCHLD, &sa, NULL) < 0) |
|
191 | } | |
|
192 | if (sigaction(SIGCHLD, &sa, NULL) < 0) { | |||
164 | goto error; |
|
193 | goto error; | |
|
194 | } | |||
165 |
|
195 | |||
166 | /* ignore Ctrl+C while shutting down to make pager exits cleanly */ |
|
196 | /* ignore Ctrl+C while shutting down to make pager exits cleanly */ | |
167 | sa.sa_handler = SIG_IGN; |
|
197 | sa.sa_handler = SIG_IGN; | |
168 | if (sigaction(SIGINT, &sa, NULL) < 0) |
|
198 | if (sigaction(SIGINT, &sa, NULL) < 0) { | |
169 | goto error; |
|
199 | goto error; | |
|
200 | } | |||
170 |
|
201 | |||
171 | peerpid = 0; |
|
202 | peerpid = 0; | |
172 | return; |
|
203 | return; | |
@@ -180,22 +211,27 b' error:' | |||||
180 | pid_t setuppager(const char *pagercmd, const char *envp[]) |
|
211 | pid_t setuppager(const char *pagercmd, const char *envp[]) | |
181 | { |
|
212 | { | |
182 | assert(pagerpid == 0); |
|
213 | assert(pagerpid == 0); | |
183 | if (!pagercmd) |
|
214 | if (!pagercmd) { | |
184 | return 0; |
|
215 | return 0; | |
|
216 | } | |||
185 |
|
217 | |||
186 | int pipefds[2]; |
|
218 | int pipefds[2]; | |
187 | if (pipe(pipefds) < 0) |
|
219 | if (pipe(pipefds) < 0) { | |
188 | return 0; |
|
220 | return 0; | |
|
221 | } | |||
189 | pid_t pid = fork(); |
|
222 | pid_t pid = fork(); | |
190 | if (pid < 0) |
|
223 | if (pid < 0) { | |
191 | goto error; |
|
224 | goto error; | |
|
225 | } | |||
192 | if (pid > 0) { |
|
226 | if (pid > 0) { | |
193 | close(pipefds[0]); |
|
227 | close(pipefds[0]); | |
194 | if (dup2(pipefds[1], fileno(stdout)) < 0) |
|
228 | if (dup2(pipefds[1], fileno(stdout)) < 0) { | |
195 | goto error; |
|
229 | goto error; | |
|
230 | } | |||
196 | if (isatty(fileno(stderr))) { |
|
231 | if (isatty(fileno(stderr))) { | |
197 | if (dup2(pipefds[1], fileno(stderr)) < 0) |
|
232 | if (dup2(pipefds[1], fileno(stderr)) < 0) { | |
198 | goto error; |
|
233 | goto error; | |
|
234 | } | |||
199 | } |
|
235 | } | |
200 | close(pipefds[1]); |
|
236 | close(pipefds[1]); | |
201 | pagerpid = pid; |
|
237 | pagerpid = pid; | |
@@ -222,16 +258,18 b' error:' | |||||
222 |
|
258 | |||
223 | void waitpager(void) |
|
259 | void waitpager(void) | |
224 | { |
|
260 | { | |
225 | if (pagerpid == 0) |
|
261 | if (pagerpid == 0) { | |
226 | return; |
|
262 | return; | |
|
263 | } | |||
227 |
|
264 | |||
228 | /* close output streams to notify the pager its input ends */ |
|
265 | /* close output streams to notify the pager its input ends */ | |
229 | fclose(stdout); |
|
266 | fclose(stdout); | |
230 | fclose(stderr); |
|
267 | fclose(stderr); | |
231 | while (1) { |
|
268 | while (1) { | |
232 | pid_t ret = waitpid(pagerpid, NULL, 0); |
|
269 | pid_t ret = waitpid(pagerpid, NULL, 0); | |
233 | if (ret == -1 && errno == EINTR) |
|
270 | if (ret == -1 && errno == EINTR) { | |
234 | continue; |
|
271 | continue; | |
|
272 | } | |||
235 | break; |
|
273 | break; | |
236 | } |
|
274 | } | |
237 | } |
|
275 | } |
@@ -25,8 +25,9 b' static int colorenabled = 0;' | |||||
25 |
|
25 | |||
26 | static inline void fsetcolor(FILE *fp, const char *code) |
|
26 | static inline void fsetcolor(FILE *fp, const char *code) | |
27 | { |
|
27 | { | |
28 | if (!colorenabled) |
|
28 | if (!colorenabled) { | |
29 | return; |
|
29 | return; | |
|
30 | } | |||
30 | fprintf(fp, "\033[%sm", code); |
|
31 | fprintf(fp, "\033[%sm", code); | |
31 | } |
|
32 | } | |
32 |
|
33 | |||
@@ -35,8 +36,9 b' static void vabortmsgerrno(int no, const' | |||||
35 | fsetcolor(stderr, "1;31"); |
|
36 | fsetcolor(stderr, "1;31"); | |
36 | fputs("chg: abort: ", stderr); |
|
37 | fputs("chg: abort: ", stderr); | |
37 | vfprintf(stderr, fmt, args); |
|
38 | vfprintf(stderr, fmt, args); | |
38 | if (no != 0) |
|
39 | if (no != 0) { | |
39 | fprintf(stderr, " (errno = %d, %s)", no, strerror(no)); |
|
40 | fprintf(stderr, " (errno = %d, %s)", no, strerror(no)); | |
|
41 | } | |||
40 | fsetcolor(stderr, ""); |
|
42 | fsetcolor(stderr, ""); | |
41 | fputc('\n', stderr); |
|
43 | fputc('\n', stderr); | |
42 | exit(255); |
|
44 | exit(255); | |
@@ -82,8 +84,9 b' void enabledebugmsg(void)' | |||||
82 |
|
84 | |||
83 | void debugmsg(const char *fmt, ...) |
|
85 | void debugmsg(const char *fmt, ...) | |
84 | { |
|
86 | { | |
85 | if (!debugmsgenabled) |
|
87 | if (!debugmsgenabled) { | |
86 | return; |
|
88 | return; | |
|
89 | } | |||
87 |
|
90 | |||
88 | va_list args; |
|
91 | va_list args; | |
89 | va_start(args, fmt); |
|
92 | va_start(args, fmt); | |
@@ -98,32 +101,37 b' void debugmsg(const char *fmt, ...)' | |||||
98 | void fchdirx(int dirfd) |
|
101 | void fchdirx(int dirfd) | |
99 | { |
|
102 | { | |
100 | int r = fchdir(dirfd); |
|
103 | int r = fchdir(dirfd); | |
101 | if (r == -1) |
|
104 | if (r == -1) { | |
102 | abortmsgerrno("failed to fchdir"); |
|
105 | abortmsgerrno("failed to fchdir"); | |
|
106 | } | |||
103 | } |
|
107 | } | |
104 |
|
108 | |||
105 | void fsetcloexec(int fd) |
|
109 | void fsetcloexec(int fd) | |
106 | { |
|
110 | { | |
107 | int flags = fcntl(fd, F_GETFD); |
|
111 | int flags = fcntl(fd, F_GETFD); | |
108 | if (flags < 0) |
|
112 | if (flags < 0) { | |
109 | abortmsgerrno("cannot get flags of fd %d", fd); |
|
113 | abortmsgerrno("cannot get flags of fd %d", fd); | |
110 | if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) |
|
114 | } | |
|
115 | if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) { | |||
111 | abortmsgerrno("cannot set flags of fd %d", fd); |
|
116 | abortmsgerrno("cannot set flags of fd %d", fd); | |
|
117 | } | |||
112 | } |
|
118 | } | |
113 |
|
119 | |||
114 | void *mallocx(size_t size) |
|
120 | void *mallocx(size_t size) | |
115 | { |
|
121 | { | |
116 | void *result = malloc(size); |
|
122 | void *result = malloc(size); | |
117 | if (!result) |
|
123 | if (!result) { | |
118 | abortmsg("failed to malloc"); |
|
124 | abortmsg("failed to malloc"); | |
|
125 | } | |||
119 | return result; |
|
126 | return result; | |
120 | } |
|
127 | } | |
121 |
|
128 | |||
122 | void *reallocx(void *ptr, size_t size) |
|
129 | void *reallocx(void *ptr, size_t size) | |
123 | { |
|
130 | { | |
124 | void *result = realloc(ptr, size); |
|
131 | void *result = realloc(ptr, size); | |
125 | if (!result) |
|
132 | if (!result) { | |
126 | abortmsg("failed to realloc"); |
|
133 | abortmsg("failed to realloc"); | |
|
134 | } | |||
127 | return result; |
|
135 | return result; | |
128 | } |
|
136 | } | |
129 |
|
137 | |||
@@ -144,30 +152,37 b' int runshellcmd(const char *cmd, const c' | |||||
144 | memset(&newsa, 0, sizeof(newsa)); |
|
152 | memset(&newsa, 0, sizeof(newsa)); | |
145 | newsa.sa_handler = SIG_IGN; |
|
153 | newsa.sa_handler = SIG_IGN; | |
146 | newsa.sa_flags = 0; |
|
154 | newsa.sa_flags = 0; | |
147 | if (sigemptyset(&newsa.sa_mask) < 0) |
|
155 | if (sigemptyset(&newsa.sa_mask) < 0) { | |
148 | goto done; |
|
156 | goto done; | |
149 | if (sigaction(SIGINT, &newsa, &oldsaint) < 0) |
|
157 | } | |
|
158 | if (sigaction(SIGINT, &newsa, &oldsaint) < 0) { | |||
150 | goto done; |
|
159 | goto done; | |
|
160 | } | |||
151 | doneflags |= F_SIGINT; |
|
161 | doneflags |= F_SIGINT; | |
152 | if (sigaction(SIGQUIT, &newsa, &oldsaquit) < 0) |
|
162 | if (sigaction(SIGQUIT, &newsa, &oldsaquit) < 0) { | |
153 | goto done; |
|
163 | goto done; | |
|
164 | } | |||
154 | doneflags |= F_SIGQUIT; |
|
165 | doneflags |= F_SIGQUIT; | |
155 |
|
166 | |||
156 | if (sigaddset(&newsa.sa_mask, SIGCHLD) < 0) |
|
167 | if (sigaddset(&newsa.sa_mask, SIGCHLD) < 0) { | |
157 | goto done; |
|
168 | goto done; | |
158 | if (sigprocmask(SIG_BLOCK, &newsa.sa_mask, &oldmask) < 0) |
|
169 | } | |
|
170 | if (sigprocmask(SIG_BLOCK, &newsa.sa_mask, &oldmask) < 0) { | |||
159 | goto done; |
|
171 | goto done; | |
|
172 | } | |||
160 | doneflags |= F_SIGMASK; |
|
173 | doneflags |= F_SIGMASK; | |
161 |
|
174 | |||
162 | pid_t pid = fork(); |
|
175 | pid_t pid = fork(); | |
163 | if (pid < 0) |
|
176 | if (pid < 0) { | |
164 | goto done; |
|
177 | goto done; | |
|
178 | } | |||
165 | if (pid == 0) { |
|
179 | if (pid == 0) { | |
166 | sigaction(SIGINT, &oldsaint, NULL); |
|
180 | sigaction(SIGINT, &oldsaint, NULL); | |
167 | sigaction(SIGQUIT, &oldsaquit, NULL); |
|
181 | sigaction(SIGQUIT, &oldsaquit, NULL); | |
168 | sigprocmask(SIG_SETMASK, &oldmask, NULL); |
|
182 | sigprocmask(SIG_SETMASK, &oldmask, NULL); | |
169 | if (cwd && chdir(cwd) < 0) |
|
183 | if (cwd && chdir(cwd) < 0) { | |
170 | _exit(127); |
|
184 | _exit(127); | |
|
185 | } | |||
171 | const char *argv[] = {"sh", "-c", cmd, NULL}; |
|
186 | const char *argv[] = {"sh", "-c", cmd, NULL}; | |
172 | if (envp) { |
|
187 | if (envp) { | |
173 | execve("/bin/sh", (char **)argv, (char **)envp); |
|
188 | execve("/bin/sh", (char **)argv, (char **)envp); | |
@@ -176,25 +191,32 b' int runshellcmd(const char *cmd, const c' | |||||
176 | } |
|
191 | } | |
177 | _exit(127); |
|
192 | _exit(127); | |
178 | } else { |
|
193 | } else { | |
179 | if (waitpid(pid, &status, 0) < 0) |
|
194 | if (waitpid(pid, &status, 0) < 0) { | |
180 | goto done; |
|
195 | goto done; | |
|
196 | } | |||
181 | doneflags |= F_WAITPID; |
|
197 | doneflags |= F_WAITPID; | |
182 | } |
|
198 | } | |
183 |
|
199 | |||
184 | done: |
|
200 | done: | |
185 | if (doneflags & F_SIGINT) |
|
201 | if (doneflags & F_SIGINT) { | |
186 | sigaction(SIGINT, &oldsaint, NULL); |
|
202 | sigaction(SIGINT, &oldsaint, NULL); | |
187 | if (doneflags & F_SIGQUIT) |
|
203 | } | |
|
204 | if (doneflags & F_SIGQUIT) { | |||
188 | sigaction(SIGQUIT, &oldsaquit, NULL); |
|
205 | sigaction(SIGQUIT, &oldsaquit, NULL); | |
189 | if (doneflags & F_SIGMASK) |
|
206 | } | |
|
207 | if (doneflags & F_SIGMASK) { | |||
190 | sigprocmask(SIG_SETMASK, &oldmask, NULL); |
|
208 | sigprocmask(SIG_SETMASK, &oldmask, NULL); | |
|
209 | } | |||
191 |
|
210 | |||
192 | /* no way to report other errors, use 127 (= shell termination) */ |
|
211 | /* no way to report other errors, use 127 (= shell termination) */ | |
193 | if (!(doneflags & F_WAITPID)) |
|
212 | if (!(doneflags & F_WAITPID)) { | |
194 | return 127; |
|
213 | return 127; | |
195 | if (WIFEXITED(status)) |
|
214 | } | |
|
215 | if (WIFEXITED(status)) { | |||
196 | return WEXITSTATUS(status); |
|
216 | return WEXITSTATUS(status); | |
197 | if (WIFSIGNALED(status)) |
|
217 | } | |
|
218 | if (WIFSIGNALED(status)) { | |||
198 | return -WTERMSIG(status); |
|
219 | return -WTERMSIG(status); | |
|
220 | } | |||
199 | return 127; |
|
221 | return 127; | |
200 | } |
|
222 | } |
@@ -62,6 +62,11 b' contrib/python-zstandard/zstd/compress/z' | |||||
62 | contrib/python-zstandard/zstd/compress/zstd_opt.c |
|
62 | contrib/python-zstandard/zstd/compress/zstd_opt.c | |
63 | contrib/python-zstandard/zstd/compress/zstd_opt.h |
|
63 | contrib/python-zstandard/zstd/compress/zstd_opt.h | |
64 | contrib/python-zstandard/zstd/decompress/huf_decompress.c |
|
64 | contrib/python-zstandard/zstd/decompress/huf_decompress.c | |
|
65 | contrib/python-zstandard/zstd/decompress/zstd_ddict.c | |||
|
66 | contrib/python-zstandard/zstd/decompress/zstd_ddict.h | |||
|
67 | contrib/python-zstandard/zstd/decompress/zstd_decompress_block.c | |||
|
68 | contrib/python-zstandard/zstd/decompress/zstd_decompress_block.h | |||
|
69 | contrib/python-zstandard/zstd/decompress/zstd_decompress_internal.h | |||
65 | contrib/python-zstandard/zstd/decompress/zstd_decompress.c |
|
70 | contrib/python-zstandard/zstd/decompress/zstd_decompress.c | |
66 | contrib/python-zstandard/zstd/deprecated/zbuff_common.c |
|
71 | contrib/python-zstandard/zstd/deprecated/zbuff_common.c | |
67 | contrib/python-zstandard/zstd/deprecated/zbuff_compress.c |
|
72 | contrib/python-zstandard/zstd/deprecated/zbuff_compress.c |
@@ -7,6 +7,7 b' import mercurial' | |||||
7 | import sys |
|
7 | import sys | |
8 | from mercurial import ( |
|
8 | from mercurial import ( | |
9 | demandimport, |
|
9 | demandimport, | |
|
10 | pycompat, | |||
10 | registrar, |
|
11 | registrar, | |
11 | ) |
|
12 | ) | |
12 |
|
13 | |||
@@ -32,28 +33,30 b' def ipdb(ui, repo, msg, **opts):' | |||||
32 |
|
33 | |||
33 | IPython.embed() |
|
34 | IPython.embed() | |
34 |
|
35 | |||
35 | @command('debugshell|dbsh', []) |
|
36 | @command(b'debugshell|dbsh', []) | |
36 | def debugshell(ui, repo, **opts): |
|
37 | def debugshell(ui, repo, **opts): | |
37 |
bannermsg = "loaded repo : %s\n" |
|
38 | bannermsg = ("loaded repo : %s\n" | |
38 | "using source: %s" % (repo.root, |
|
39 | "using source: %s" % (pycompat.sysstr(repo.root), | |
39 | mercurial.__path__[0]) |
|
40 | mercurial.__path__[0])) | |
40 |
|
41 | |||
41 | pdbmap = { |
|
42 | pdbmap = { | |
42 | 'pdb' : 'code', |
|
43 | 'pdb' : 'code', | |
43 | 'ipdb' : 'IPython' |
|
44 | 'ipdb' : 'IPython' | |
44 | } |
|
45 | } | |
45 |
|
46 | |||
46 | debugger = ui.config("ui", "debugger") |
|
47 | debugger = ui.config(b"ui", b"debugger") | |
47 | if not debugger: |
|
48 | if not debugger: | |
48 | debugger = 'pdb' |
|
49 | debugger = 'pdb' | |
|
50 | else: | |||
|
51 | debugger = pycompat.sysstr(debugger) | |||
49 |
|
52 | |||
50 | # if IPython doesn't exist, fallback to code.interact |
|
53 | # if IPython doesn't exist, fallback to code.interact | |
51 | try: |
|
54 | try: | |
52 | with demandimport.deactivated(): |
|
55 | with demandimport.deactivated(): | |
53 | __import__(pdbmap[debugger]) |
|
56 | __import__(pdbmap[debugger]) | |
54 | except ImportError: |
|
57 | except ImportError: | |
55 | ui.warn(("%s debugger specified but %s module was not found\n") |
|
58 | ui.warn((b"%s debugger specified but %s module was not found\n") | |
56 | % (debugger, pdbmap[debugger])) |
|
59 | % (debugger, pdbmap[debugger])) | |
57 | debugger = 'pdb' |
|
60 | debugger = b'pdb' | |
58 |
|
61 | |||
59 | getattr(sys.modules[__name__], debugger)(ui, repo, bannermsg, **opts) |
|
62 | getattr(sys.modules[__name__], debugger)(ui, repo, bannermsg, **opts) |
@@ -20,11 +20,19 b' try:' | |||||
20 | lm = lazymanifest(mdata) |
|
20 | lm = lazymanifest(mdata) | |
21 | # iterate the whole thing, which causes the code to fully parse |
|
21 | # iterate the whole thing, which causes the code to fully parse | |
22 | # every line in the manifest |
|
22 | # every line in the manifest | |
23 |
|
|
23 | for e, _, _ in lm.iterentries(): | |
|
24 | # also exercise __getitem__ et al | |||
|
25 | lm[e] | |||
|
26 | e in lm | |||
|
27 | (e + 'nope') in lm | |||
24 | lm[b'xyzzy'] = (b'\0' * 20, 'x') |
|
28 | lm[b'xyzzy'] = (b'\0' * 20, 'x') | |
25 | # do an insert, text should change |
|
29 | # do an insert, text should change | |
26 | assert lm.text() != mdata, "insert should change text and didn't: %r %r" % (lm.text(), mdata) |
|
30 | assert lm.text() != mdata, "insert should change text and didn't: %r %r" % (lm.text(), mdata) | |
|
31 | cloned = lm.filtercopy(lambda x: x != 'xyzzy') | |||
|
32 | assert cloned.text() == mdata, 'cloned text should equal mdata' | |||
|
33 | cloned.diff(lm) | |||
27 | del lm[b'xyzzy'] |
|
34 | del lm[b'xyzzy'] | |
|
35 | cloned.diff(lm) | |||
28 | # should be back to the same |
|
36 | # should be back to the same | |
29 | assert lm.text() == mdata, "delete should have restored text but didn't: %r %r" % (lm.text(), mdata) |
|
37 | assert lm.text() == mdata, "delete should have restored text but didn't: %r %r" % (lm.text(), mdata) | |
30 | except Exception as e: |
|
38 | except Exception as e: | |
@@ -39,6 +47,11 b' except Exception as e:' | |||||
39 |
|
47 | |||
40 | int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) |
|
48 | int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) | |
41 | { |
|
49 | { | |
|
50 | // Don't allow fuzzer inputs larger than 100k, since we'll just bog | |||
|
51 | // down and not accomplish much. | |||
|
52 | if (Size > 100000) { | |||
|
53 | return 0; | |||
|
54 | } | |||
42 | PyObject *mtext = |
|
55 | PyObject *mtext = | |
43 | PyBytes_FromStringAndSize((const char *)Data, (Py_ssize_t)Size); |
|
56 | PyBytes_FromStringAndSize((const char *)Data, (Py_ssize_t)Size); | |
44 | PyObject *locals = PyDict_New(); |
|
57 | PyObject *locals = PyDict_New(); |
@@ -19,6 +19,11 b' from parsers import parse_index2' | |||||
19 | for inline in (True, False): |
|
19 | for inline in (True, False): | |
20 | try: |
|
20 | try: | |
21 | index, cache = parse_index2(data, inline) |
|
21 | index, cache = parse_index2(data, inline) | |
|
22 | index.slicechunktodensity(list(range(len(index))), 0.5, 262144) | |||
|
23 | for rev in range(len(index)): | |||
|
24 | node = index[rev][7] | |||
|
25 | partial = index.shortest(node) | |||
|
26 | index.partialmatch(node[:partial]) | |||
22 | except Exception as e: |
|
27 | except Exception as e: | |
23 | pass |
|
28 | pass | |
24 | # uncomment this print if you're editing this Python code |
|
29 | # uncomment this print if you're editing this Python code | |
@@ -31,6 +36,11 b' for inline in (True, False):' | |||||
31 |
|
36 | |||
32 | int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) |
|
37 | int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) | |
33 | { |
|
38 | { | |
|
39 | // Don't allow fuzzer inputs larger than 60k, since we'll just bog | |||
|
40 | // down and not accomplish much. | |||
|
41 | if (Size > 60000) { | |||
|
42 | return 0; | |||
|
43 | } | |||
34 | PyObject *text = |
|
44 | PyObject *text = | |
35 | PyBytes_FromStringAndSize((const char *)Data, (Py_ssize_t)Size); |
|
45 | PyBytes_FromStringAndSize((const char *)Data, (Py_ssize_t)Size); | |
36 | PyObject *locals = PyDict_New(); |
|
46 | PyObject *locals = PyDict_New(); |
@@ -53,4 +53,45 b'' | |||||
53 | (setq mode-name "hg-test") |
|
53 | (setq mode-name "hg-test") | |
54 | (run-hooks 'hg-test-mode-hook)) |
|
54 | (run-hooks 'hg-test-mode-hook)) | |
55 |
|
55 | |||
|
56 | (with-eval-after-load "compile" | |||
|
57 | ;; Link to Python sources in tracebacks in .t failures. | |||
|
58 | (add-to-list 'compilation-error-regexp-alist-alist | |||
|
59 | '(hg-test-output-python-tb | |||
|
60 | "^\\+ +File ['\"]\\([^'\"]+\\)['\"], line \\([0-9]+\\)," 1 2)) | |||
|
61 | (add-to-list 'compilation-error-regexp-alist 'hg-test-output-python-tb) | |||
|
62 | ;; Link to source files in test-check-code.t violations. | |||
|
63 | (add-to-list 'compilation-error-regexp-alist-alist | |||
|
64 | '(hg-test-check-code-output | |||
|
65 | "\\+ \\([^:\n]+\\):\\([0-9]+\\):$" 1 2)) | |||
|
66 | (add-to-list 'compilation-error-regexp-alist 'hg-test-check-code-output)) | |||
|
67 | ||||
|
68 | (defun hg-test-mode--test-one-error-line-regexp (test) | |||
|
69 | (erase-buffer) | |||
|
70 | (setq compilation-locs (make-hash-table)) | |||
|
71 | (insert (car test)) | |||
|
72 | (compilation-parse-errors (point-min) (point-max)) | |||
|
73 | (let ((msg (get-text-property 1 'compilation-message))) | |||
|
74 | (should msg) | |||
|
75 | (let ((loc (compilation--message->loc msg)) | |||
|
76 | (line (nth 1 test)) | |||
|
77 | (file (nth 2 test))) | |||
|
78 | (should (equal (compilation--loc->line loc) line)) | |||
|
79 | (should (equal (caar (compilation--loc->file-struct loc)) file))) | |||
|
80 | msg)) | |||
|
81 | ||||
|
82 | (require 'ert) | |||
|
83 | (ert-deftest hg-test-mode--compilation-mode-support () | |||
|
84 | "Test hg-specific compilation-mode regular expressions" | |||
|
85 | (require 'compile) | |||
|
86 | (with-temp-buffer | |||
|
87 | (font-lock-mode -1) | |||
|
88 | (mapc 'hg-test-mode--test-one-error-line-regexp | |||
|
89 | '( | |||
|
90 | ("+ contrib/debugshell.py:37:" 37 "contrib/debugshell.py") | |||
|
91 | ("+ File \"/tmp/hg/mercurial/commands.py\", line 3115, in help_" | |||
|
92 | 3115 "/tmp/hg/mercurial/commands.py") | |||
|
93 | ("+ File \"mercurial/dispatch.py\", line 225, in dispatch" | |||
|
94 | 225 "mercurial/dispatch.py"))))) | |||
|
95 | ||||
|
96 | ||||
56 | (provide 'hg-test-mode) |
|
97 | (provide 'hg-test-mode) |
@@ -76,7 +76,7 b' def build_docker_image(dockerfile: pathl' | |||||
76 | p.communicate(input=dockerfile) |
|
76 | p.communicate(input=dockerfile) | |
77 | if p.returncode: |
|
77 | if p.returncode: | |
78 | raise subprocess.CalledProcessException( |
|
78 | raise subprocess.CalledProcessException( | |
79 |
p.returncode, 'failed to build docker image: %s %s' |
|
79 | p.returncode, 'failed to build docker image: %s %s' | |
80 | % (p.stdout, p.stderr)) |
|
80 | % (p.stdout, p.stderr)) | |
81 |
|
81 | |||
82 | def command_build(args): |
|
82 | def command_build(args): |
@@ -5,7 +5,7 b'' | |||||
5 | #define FileHandle |
|
5 | #define FileHandle | |
6 | #define FileLine |
|
6 | #define FileLine | |
7 | #define VERSION = "unknown" |
|
7 | #define VERSION = "unknown" | |
8 | #if FileHandle = FileOpen(SourcePath + "\..\..\mercurial\__version__.py") |
|
8 | #if FileHandle = FileOpen(SourcePath + "\..\..\..\mercurial\__version__.py") | |
9 | #expr FileLine = FileRead(FileHandle) |
|
9 | #expr FileLine = FileRead(FileHandle) | |
10 | #expr FileLine = FileRead(FileHandle) |
|
10 | #expr FileLine = FileRead(FileHandle) | |
11 | #define VERSION = Copy(FileLine, Pos('"', FileLine)+1, Len(FileLine)-Pos('"', FileLine)-1) |
|
11 | #define VERSION = Copy(FileLine, Pos('"', FileLine)+1, Len(FileLine)-Pos('"', FileLine)-1) | |
@@ -43,7 +43,7 b' AppUpdatesURL=https://mercurial-scm.org/' | |||||
43 | AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3} |
|
43 | AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3} | |
44 | AppContact=mercurial@mercurial-scm.org |
|
44 | AppContact=mercurial@mercurial-scm.org | |
45 | DefaultDirName={pf}\Mercurial |
|
45 | DefaultDirName={pf}\Mercurial | |
46 | SourceDir=..\.. |
|
46 | SourceDir=..\..\.. | |
47 | VersionInfoDescription=Mercurial distributed SCM (version {#VERSION}) |
|
47 | VersionInfoDescription=Mercurial distributed SCM (version {#VERSION}) | |
48 | VersionInfoCopyright=Copyright 2005-2019 Matt Mackall and others |
|
48 | VersionInfoCopyright=Copyright 2005-2019 Matt Mackall and others | |
49 | VersionInfoCompany=Matt Mackall and others |
|
49 | VersionInfoCompany=Matt Mackall and others | |
@@ -53,6 +53,7 b' SetupIconFile=contrib\\win32\\mercurial.ic' | |||||
53 | AllowNoIcons=true |
|
53 | AllowNoIcons=true | |
54 | DefaultGroupName=Mercurial |
|
54 | DefaultGroupName=Mercurial | |
55 | PrivilegesRequired=none |
|
55 | PrivilegesRequired=none | |
|
56 | ChangesEnvironment=true | |||
56 |
|
57 | |||
57 | [Files] |
|
58 | [Files] | |
58 | Source: contrib\mercurial.el; DestDir: {app}/Contrib |
|
59 | Source: contrib\mercurial.el; DestDir: {app}/Contrib | |
@@ -70,17 +71,12 b' Source: contrib\\hgweb.wsgi; DestDir: {ap' | |||||
70 | Source: contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme |
|
71 | Source: contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme | |
71 | Source: contrib\win32\postinstall.txt; DestDir: {app}; DestName: ReleaseNotes.txt |
|
72 | Source: contrib\win32\postinstall.txt; DestDir: {app}; DestName: ReleaseNotes.txt | |
72 | Source: dist\hg.exe; DestDir: {app}; AfterInstall: Touch('{app}\hg.exe.local') |
|
73 | Source: dist\hg.exe; DestDir: {app}; AfterInstall: Touch('{app}\hg.exe.local') | |
73 | #if ARCH == "x64" |
|
|||
74 | Source: dist\lib\*.dll; Destdir: {app}\lib |
|
74 | Source: dist\lib\*.dll; Destdir: {app}\lib | |
75 | Source: dist\lib\*.pyd; Destdir: {app}\lib |
|
75 | Source: dist\lib\*.pyd; Destdir: {app}\lib | |
76 | #else |
|
|||
77 | Source: dist\w9xpopen.exe; DestDir: {app} |
|
|||
78 | #endif |
|
|||
79 | Source: dist\python*.dll; Destdir: {app}; Flags: skipifsourcedoesntexist |
|
76 | Source: dist\python*.dll; Destdir: {app}; Flags: skipifsourcedoesntexist | |
80 | Source: dist\msvc*.dll; DestDir: {app}; Flags: skipifsourcedoesntexist |
|
77 | Source: dist\msvc*.dll; DestDir: {app}; Flags: skipifsourcedoesntexist | |
81 | Source: dist\Microsoft.VC*.CRT.manifest; DestDir: {app}; Flags: skipifsourcedoesntexist |
|
78 | Source: dist\Microsoft.VC*.CRT.manifest; DestDir: {app}; Flags: skipifsourcedoesntexist | |
82 | Source: dist\lib\library.zip; DestDir: {app}\lib |
|
79 | Source: dist\lib\library.zip; DestDir: {app}\lib | |
83 | Source: dist\add_path.exe; DestDir: {app} |
|
|||
84 | Source: doc\*.html; DestDir: {app}\Docs |
|
80 | Source: doc\*.html; DestDir: {app}\Docs | |
85 | Source: doc\style.css; DestDir: {app}\Docs |
|
81 | Source: doc\style.css; DestDir: {app}\Docs | |
86 | Source: mercurial\help\*.txt; DestDir: {app}\help |
|
82 | Source: mercurial\help\*.txt; DestDir: {app}\help | |
@@ -107,14 +103,22 b' Name: {group}\\Mercurial Configuration Fi' | |||||
107 | Name: {group}\Mercurial Ignore Files; Filename: {app}\Docs\hgignore.5.html |
|
103 | Name: {group}\Mercurial Ignore Files; Filename: {app}\Docs\hgignore.5.html | |
108 | Name: {group}\Mercurial Web Site; Filename: {app}\Mercurial.url |
|
104 | Name: {group}\Mercurial Web Site; Filename: {app}\Mercurial.url | |
109 |
|
105 | |||
110 | [Run] |
|
106 | [Tasks] | |
111 |
|
|
107 | Name: modifypath; Description: Add the installation path to the search path; Flags: unchecked | |
112 |
|
||||
113 | [UninstallRun] |
|
|||
114 | Filename: "{app}\add_path.exe"; Parameters: "/del {app}" |
|
|||
115 |
|
108 | |||
116 | [Code] |
|
109 | [Code] | |
117 | procedure Touch(fn: String); |
|
110 | procedure Touch(fn: String); | |
118 | begin |
|
111 | begin | |
119 | SaveStringToFile(ExpandConstant(fn), '', False); |
|
112 | SaveStringToFile(ExpandConstant(fn), '', False); | |
120 | end; |
|
113 | end; | |
|
114 | ||||
|
115 | const | |||
|
116 | ModPathName = 'modifypath'; | |||
|
117 | ModPathType = 'user'; | |||
|
118 | ||||
|
119 | function ModPathDir(): TArrayOfString; | |||
|
120 | begin | |||
|
121 | setArrayLength(Result, 1) | |||
|
122 | Result[0] := ExpandConstant('{app}'); | |||
|
123 | end; | |||
|
124 | #include "modpath.iss" |
@@ -1,130 +1,61 b'' | |||||
1 | The standalone Windows installer for Mercurial is built in a somewhat |
|
1 | Requirements | |
2 | jury-rigged fashion. |
|
2 | ============ | |
3 |
|
3 | |||
4 | It has the following prerequisites. Ensure to take the packages |
|
4 | Building the Inno installer requires a Windows machine. | |
5 | matching the mercurial version you want to build (32-bit or 64-bit). |
|
|||
6 |
|
5 | |||
7 | Python 2.6 for Windows |
|
6 | The following system dependencies must be installed: | |
8 | http://www.python.org/download/releases/ |
|
|||
9 |
|
7 | |||
10 | A compiler: |
|
8 | * Python 2.7 (download from https://www.python.org/downloads/) | |
11 | either MinGW |
|
9 | * Microsoft Visual C++ Compiler for Python 2.7 | |
12 | http://www.mingw.org/ |
|
10 | (https://www.microsoft.com/en-us/download/details.aspx?id=44266) | |
13 | or Microsoft Visual C++ 2008 SP1 Express Edition |
|
11 | * Inno Setup (http://jrsoftware.org/isdl.php) version 5.4 or newer. | |
14 | http://www.microsoft.com/express/Downloads/Download-2008.aspx |
|
12 | Be sure to install the optional Inno Setup Preprocessor feature, | |
15 |
|
13 | which is required. | ||
16 | Python for Windows Extensions |
|
14 | * Python 3.5+ (to run the ``build.py`` script) | |
17 | http://sourceforge.net/projects/pywin32/ |
|
|||
18 |
|
||||
19 | mfc71.dll (just download, don't install; not needed for Python 2.6) |
|
|||
20 | http://starship.python.net/crew/mhammond/win32/ |
|
|||
21 |
|
||||
22 | Visual C++ 2008 redistributable package (needed for >= Python 2.6 or if you compile with MSVC) |
|
|||
23 | for 32-bit: |
|
|||
24 | http://www.microsoft.com/downloads/details.aspx?FamilyID=9b2da534-3e03-4391-8a4d-074b9f2bc1bf |
|
|||
25 | for 64-bit: |
|
|||
26 | http://www.microsoft.com/downloads/details.aspx?familyid=bd2a6171-e2d6-4230-b809-9a8d7548c1b6 |
|
|||
27 |
|
15 | |||
28 | The py2exe distutils extension |
|
16 | Building | |
29 | http://sourceforge.net/projects/py2exe/ |
|
17 | ======== | |
30 |
|
||||
31 | GnuWin32 gettext utility (if you want to build translations) |
|
|||
32 | http://gnuwin32.sourceforge.net/packages/gettext.htm |
|
|||
33 |
|
||||
34 | Inno Setup |
|
|||
35 | http://www.jrsoftware.org/isdl.php#qsp |
|
|||
36 |
|
||||
37 | Get and install ispack-5.3.10.exe or later (includes Inno Setup Processor), |
|
|||
38 | which is necessary to package Mercurial. |
|
|||
39 |
|
||||
40 | ISTool - optional |
|
|||
41 | http://www.istool.org/default.aspx/ |
|
|||
42 |
|
18 | |||
43 | add_path (you need only add_path.exe in the zip file) |
|
19 | The ``build.py`` script automates the process of producing an | |
44 | http://www.barisione.org/apps.html#add_path |
|
20 | Inno installer. It manages fetching and configuring the | |
45 |
|
21 | non-system dependencies (such as py2exe, gettext, and various | ||
46 | Docutils |
|
22 | Python packages). | |
47 | http://docutils.sourceforge.net/ |
|
|||
48 |
|
23 | |||
49 | CA Certs file |
|
24 | The script requires an activated ``Visual C++ 2008`` command prompt. | |
50 | http://curl.haxx.se/ca/cacert.pem |
|
25 | A shortcut to such a prompt was installed with ``Microsoft Visual C++ | |
51 |
|
26 | Compiler for Python 2.7``. From your Start Menu, look for | ||
52 | And, of course, Mercurial itself. |
|
27 | ``Microsoft Visual C++ Compiler Package for Python 2.7`` then launch | |
53 |
|
28 | either ``Visual C++ 2008 32-bit Command Prompt`` or | ||
54 | Once you have all this installed and built, clone a copy of the |
|
29 | ``Visual C++ 2008 64-bit Command Prompt``. | |
55 | Mercurial repository you want to package, and name the repo |
|
|||
56 | C:\hg\hg-release. |
|
|||
57 |
|
||||
58 | In a shell, build a standalone copy of the hg.exe program. |
|
|||
59 |
|
30 | |||
60 | Building instructions for MinGW: |
|
31 | From the prompt, change to the Mercurial source directory. e.g. | |
61 | python setup.py build -c mingw32 |
|
32 | ``cd c:\src\hg``. | |
62 | python setup.py py2exe -b 2 |
|
33 | ||
63 | Note: the previously suggested combined command of "python setup.py build -c |
|
34 | Next, invoke ``build.py`` to produce an Inno installer. You will | |
64 | mingw32 py2exe -b 2" doesn't work correctly anymore as it doesn't include the |
|
35 | need to supply the path to the Python interpreter to use.: | |
65 | extensions in the mercurial subdirectory. |
|
|||
66 | If you want to create a file named setup.cfg with the contents: |
|
|||
67 | [build] |
|
|||
68 | compiler=mingw32 |
|
|||
69 | you can skip the first build step. |
|
|||
70 |
|
36 | |||
71 | Building instructions with MSVC 2008 Express Edition: |
|
37 | $ python3.exe contrib\packaging\inno\build.py \ | |
72 | for 32-bit: |
|
38 | --python c:\python27\python.exe | |
73 | "C:\Program Files\Microsoft Visual Studio 9.0\VC\vcvarsall.bat" x86 |
|
|||
74 | python setup.py py2exe -b 2 |
|
|||
75 | for 64-bit: |
|
|||
76 | "C:\Program Files\Microsoft Visual Studio 9.0\VC\vcvarsall.bat" x86_amd64 |
|
|||
77 | python setup.py py2exe -b 3 |
|
|||
78 |
|
39 | |||
79 | Copy add_path.exe and cacert.pem files into the dist directory that just got created. |
|
40 | .. note:: | |
80 |
|
41 | |||
81 | If you are using Python 2.6 or later, or if you are using MSVC 2008 to compile |
|
42 | The script validates that the Visual C++ environment is | |
82 | mercurial, you must include the C runtime libraries in the installer. To do so, |
|
43 | active and that the architecture of the specified Python | |
83 | install the Visual C++ 2008 redistributable package. Then in your windows\winsxs |
|
44 | interpreter matches the Visual C++ environment and errors | |
84 | folder, locate the folder containing the dlls version 9.0.21022.8. |
|
45 | if not. | |
85 | For x86, it should be named like x86_Microsoft.VC90.CRT_(...)_9.0.21022.8(...). |
|
|||
86 | For x64, it should be named like amd64_Microsoft.VC90.CRT_(...)_9.0.21022.8(...). |
|
|||
87 | Copy the files named msvcm90.dll, msvcp90.dll and msvcr90.dll into the dist |
|
|||
88 | directory. |
|
|||
89 | Then in the windows\winsxs\manifests folder, locate the corresponding manifest |
|
|||
90 | file (x86_Microsoft.VC90.CRT_(...)_9.0.21022.8(...).manifest for x86, |
|
|||
91 | amd64_Microsoft.VC90.CRT_(...)_9.0.21022.8(...).manifest for x64), copy it in the |
|
|||
92 | dist directory and rename it to Microsoft.VC90.CRT.manifest. |
|
|||
93 |
|
46 | |||
94 | Before building the installer, you have to build Mercurial HTML documentation |
|
47 | If everything runs as intended, dependencies will be fetched and | |
95 | (or fix mercurial.iss to not reference the doc directory): |
|
48 | configured into the ``build`` sub-directory, Mercurial will be built, | |
96 |
|
49 | and an installer placed in the ``dist`` sub-directory. The final | ||
97 | cd doc |
|
50 | line of output should print the name of the generated installer. | |
98 | mingw32-make html |
|
|||
99 | cd .. |
|
|||
100 |
|
51 | |||
101 | If you use ISTool, you open the C:\hg\hg-release\contrib\win32\mercurial.iss |
|
52 | Additional options may be configured. Run ``build.py --help`` to | |
102 | file and type Ctrl-F9 to compile the installer file. |
|
53 | see a list of program flags. | |
103 |
|
||||
104 | Otherwise you run the Inno Setup compiler. Assuming it's in the path |
|
|||
105 | you should execute: |
|
|||
106 |
|
||||
107 | iscc contrib\win32\mercurial.iss /dVERSION=foo |
|
|||
108 |
|
54 | |||
109 | Where 'foo' is the version number you would like to see in the |
|
55 | MinGW | |
110 | 'Add/Remove Applications' tool. The installer will be placed into |
|
56 | ===== | |
111 | a directory named Output/ at the root of your repository. |
|
|||
112 | If the /dVERSION=foo parameter is not given in the command line, the |
|
|||
113 | installer will retrieve the version information from the __version__.py file. |
|
|||
114 |
|
||||
115 | If you want to build an installer for a 64-bit mercurial, add /dARCH=x64 to |
|
|||
116 | your command line: |
|
|||
117 | iscc contrib\win32\mercurial.iss /dARCH=x64 |
|
|||
118 |
|
57 | |||
119 | To automate the steps above you may want to create a batchfile based on the |
|
58 | It is theoretically possible to generate an installer that uses | |
120 | following (MinGW build chain): |
|
59 | MinGW. This isn't well tested and ``build.py`` and may properly | |
121 |
|
60 | support it. See old versions of this file in version control for | ||
122 | echo [build] > setup.cfg |
|
61 | potentially useful hints as to how to achieve this. | |
123 | echo compiler=mingw32 >> setup.cfg |
|
|||
124 | python setup.py py2exe -b 2 |
|
|||
125 | cd doc |
|
|||
126 | mingw32-make html |
|
|||
127 | cd .. |
|
|||
128 | iscc contrib\win32\mercurial.iss /dVERSION=snapshot |
|
|||
129 |
|
||||
130 | and run it from the root of the hg repository (c:\hg\hg-release). |
|
1 | NO CONTENT: file renamed from contrib/wix/COPYING.rtf to contrib/packaging/wix/COPYING.rtf, binary diff hidden |
|
NO CONTENT: file renamed from contrib/wix/COPYING.rtf to contrib/packaging/wix/COPYING.rtf, binary diff hidden |
1 | NO CONTENT: file renamed from contrib/wix/contrib.wxs to contrib/packaging/wix/contrib.wxs |
|
NO CONTENT: file renamed from contrib/wix/contrib.wxs to contrib/packaging/wix/contrib.wxs |
1 | NO CONTENT: file renamed from contrib/wix/defines.wxi to contrib/packaging/wix/defines.wxi |
|
NO CONTENT: file renamed from contrib/wix/defines.wxi to contrib/packaging/wix/defines.wxi |
@@ -9,28 +9,6 b'' | |||||
9 | <Component Id="distOutput" Guid="$(var.dist.guid)" Win64='$(var.IsX64)'> |
|
9 | <Component Id="distOutput" Guid="$(var.dist.guid)" Win64='$(var.IsX64)'> | |
10 | <File Name="python27.dll" KeyPath="yes" /> |
|
10 | <File Name="python27.dll" KeyPath="yes" /> | |
11 | </Component> |
|
11 | </Component> | |
12 | <Directory Id="libdir" Name="lib" FileSource="$(var.SourceDir)/lib"> |
|
|||
13 | <Component Id="libOutput" Guid="$(var.lib.guid)" Win64='$(var.IsX64)'> |
|
|||
14 | <File Name="library.zip" KeyPath="yes" /> |
|
|||
15 | <File Name="mercurial.cext.base85.pyd" /> |
|
|||
16 | <File Name="mercurial.cext.bdiff.pyd" /> |
|
|||
17 | <File Name="mercurial.cext.mpatch.pyd" /> |
|
|||
18 | <File Name="mercurial.cext.osutil.pyd" /> |
|
|||
19 | <File Name="mercurial.cext.parsers.pyd" /> |
|
|||
20 | <File Name="mercurial.zstd.pyd" /> |
|
|||
21 | <File Name="hgext.fsmonitor.pywatchman.bser.pyd" /> |
|
|||
22 | <File Name="pyexpat.pyd" /> |
|
|||
23 | <File Name="bz2.pyd" /> |
|
|||
24 | <File Name="select.pyd" /> |
|
|||
25 | <File Name="unicodedata.pyd" /> |
|
|||
26 | <File Name="_ctypes.pyd" /> |
|
|||
27 | <File Name="_elementtree.pyd" /> |
|
|||
28 | <File Name="_testcapi.pyd" /> |
|
|||
29 | <File Name="_hashlib.pyd" /> |
|
|||
30 | <File Name="_socket.pyd" /> |
|
|||
31 | <File Name="_ssl.pyd" /> |
|
|||
32 | </Component> |
|
|||
33 | </Directory> |
|
|||
34 | </DirectoryRef> |
|
12 | </DirectoryRef> | |
35 | </Fragment> |
|
13 | </Fragment> | |
36 |
|
14 |
1 | NO CONTENT: file renamed from contrib/wix/doc.wxs to contrib/packaging/wix/doc.wxs |
|
NO CONTENT: file renamed from contrib/wix/doc.wxs to contrib/packaging/wix/doc.wxs |
1 | NO CONTENT: file renamed from contrib/wix/guids.wxi to contrib/packaging/wix/guids.wxi |
|
NO CONTENT: file renamed from contrib/wix/guids.wxi to contrib/packaging/wix/guids.wxi |
1 | NO CONTENT: file renamed from contrib/wix/help.wxs to contrib/packaging/wix/help.wxs |
|
NO CONTENT: file renamed from contrib/wix/help.wxs to contrib/packaging/wix/help.wxs |
1 | NO CONTENT: file renamed from contrib/wix/i18n.wxs to contrib/packaging/wix/i18n.wxs |
|
NO CONTENT: file renamed from contrib/wix/i18n.wxs to contrib/packaging/wix/i18n.wxs |
1 | NO CONTENT: file renamed from contrib/wix/locale.wxs to contrib/packaging/wix/locale.wxs |
|
NO CONTENT: file renamed from contrib/wix/locale.wxs to contrib/packaging/wix/locale.wxs |
@@ -69,7 +69,7 b'' | |||||
69 | KeyPath='yes'/> |
|
69 | KeyPath='yes'/> | |
70 | </Component> |
|
70 | </Component> | |
71 | <Component Id='COPYING' Guid='$(var.COPYING.guid)' Win64='$(var.IsX64)'> |
|
71 | <Component Id='COPYING' Guid='$(var.COPYING.guid)' Win64='$(var.IsX64)'> | |
72 | <File Id='COPYING' Name='COPYING.rtf' Source='contrib\wix\COPYING.rtf' |
|
72 | <File Id='COPYING' Name='COPYING.rtf' Source='contrib\packaging\wix\COPYING.rtf' | |
73 | KeyPath='yes'/> |
|
73 | KeyPath='yes'/> | |
74 | </Component> |
|
74 | </Component> | |
75 |
|
75 | |||
@@ -129,6 +129,11 b'' | |||||
129 | <MergeRef Id='VCRuntime' /> |
|
129 | <MergeRef Id='VCRuntime' /> | |
130 | <MergeRef Id='VCRuntimePolicy' /> |
|
130 | <MergeRef Id='VCRuntimePolicy' /> | |
131 | </Feature> |
|
131 | </Feature> | |
|
132 | <?ifdef MercurialExtraFeatures?> | |||
|
133 | <?foreach EXTRAFEAT in $(var.MercurialExtraFeatures)?> | |||
|
134 | <FeatureRef Id="$(var.EXTRAFEAT)" /> | |||
|
135 | <?endforeach?> | |||
|
136 | <?endif?> | |||
132 | <Feature Id='Locales' Title='Translations' Description='Translations' Level='1'> |
|
137 | <Feature Id='Locales' Title='Translations' Description='Translations' Level='1'> | |
133 | <ComponentGroupRef Id='localeFolder' /> |
|
138 | <ComponentGroupRef Id='localeFolder' /> | |
134 | <ComponentRef Id='i18nFolder' /> |
|
139 | <ComponentRef Id='i18nFolder' /> | |
@@ -144,7 +149,7 b'' | |||||
144 | <UIRef Id="WixUI_FeatureTree" /> |
|
149 | <UIRef Id="WixUI_FeatureTree" /> | |
145 | <UIRef Id="WixUI_ErrorProgressText" /> |
|
150 | <UIRef Id="WixUI_ErrorProgressText" /> | |
146 |
|
151 | |||
147 | <WixVariable Id="WixUILicenseRtf" Value="contrib\wix\COPYING.rtf" /> |
|
152 | <WixVariable Id="WixUILicenseRtf" Value="contrib\packaging\wix\COPYING.rtf" /> | |
148 |
|
153 | |||
149 | <Icon Id="hgIcon.ico" SourceFile="contrib/win32/mercurial.ico" /> |
|
154 | <Icon Id="hgIcon.ico" SourceFile="contrib/win32/mercurial.ico" /> | |
150 |
|
155 |
@@ -1,31 +1,71 b'' | |||||
1 |
WiX |
|
1 | WiX Installer | |
|
2 | ============= | |||
|
3 | ||||
|
4 | The files in this directory are used to produce an MSI installer using | |||
|
5 | the WiX Toolset (http://wixtoolset.org/). | |||
|
6 | ||||
|
7 | The MSI installers require elevated (admin) privileges due to the | |||
|
8 | installation of MSVC CRT libraries into the Windows system store. See | |||
|
9 | the Inno Setup installers in the ``inno`` sibling directory for installers | |||
|
10 | that do not have this requirement. | |||
|
11 | ||||
|
12 | Requirements | |||
|
13 | ============ | |||
|
14 | ||||
|
15 | Building the WiX installers requires a Windows machine. The following | |||
|
16 | dependencies must be installed: | |||
|
17 | ||||
|
18 | * Python 2.7 (download from https://www.python.org/downloads/) | |||
|
19 | * Microsoft Visual C++ Compiler for Python 2.7 | |||
|
20 | (https://www.microsoft.com/en-us/download/details.aspx?id=44266) | |||
|
21 | * Python 3.5+ (to run the ``build.py`` script) | |||
|
22 | ||||
|
23 | Building | |||
|
24 | ======== | |||
|
25 | ||||
|
26 | The ``build.py`` script automates the process of producing an MSI | |||
|
27 | installer. It manages fetching and configuring non-system dependencies | |||
|
28 | (such as py2exe, gettext, and various Python packages). | |||
|
29 | ||||
|
30 | The script requires an activated ``Visual C++ 2008`` command prompt. | |||
|
31 | A shortcut to such a prompt was installed with ``Microsoft Visual | |||
|
32 | C++ Compiler for Python 2.7``. From your Start Menu, look for | |||
|
33 | ``Microsoft Visual C++ Compiler Package for Python 2.7`` then | |||
|
34 | launch either ``Visual C++ 2008 32-bit Command Prompt`` or | |||
|
35 | ``Visual C++ 2008 64-bit Command Prompt``. | |||
|
36 | ||||
|
37 | From the prompt, change to the Mercurial source directory. e.g. | |||
|
38 | ``cd c:\src\hg``. | |||
|
39 | ||||
|
40 | Next, invoke ``build.py`` to produce an MSI installer. You will need | |||
|
41 | to supply the path to the Python interpreter to use.:: | |||
|
42 | ||||
|
43 | $ python3 contrib\packaging\wix\build.py \ | |||
|
44 | --python c:\python27\python.exe | |||
|
45 | ||||
|
46 | .. note:: | |||
|
47 | ||||
|
48 | The script validates that the Visual C++ environment is active and | |||
|
49 | that the architecture of the specified Python interpreter matches the | |||
|
50 | Visual C++ environment. An error is raised otherwise. | |||
|
51 | ||||
|
52 | If everything runs as intended, dependencies will be fetched and | |||
|
53 | configured into the ``build`` sub-directory, Mercurial will be built, | |||
|
54 | and an installer placed in the ``dist`` sub-directory. The final line | |||
|
55 | of output should print the name of the generated installer. | |||
|
56 | ||||
|
57 | Additional options may be configured. Run ``build.py --help`` to see | |||
|
58 | a list of program flags. | |||
|
59 | ||||
|
60 | Relationship to TortoiseHG | |||
2 | ========================== |
|
61 | ========================== | |
3 |
|
62 | |||
4 | The files in this folder are used by the thg-winbuild [1] package |
|
63 | TortoiseHG uses the WiX files in this directory. | |
5 | building architecture to create a Mercurial MSI installer. These files |
|
|||
6 | are versioned within the Mercurial source tree because the WXS files |
|
|||
7 | must kept up to date with distribution changes within their branch. In |
|
|||
8 | other words, the default branch WXS files are expected to diverge from |
|
|||
9 | the stable branch WXS files. Storing them within the same repository is |
|
|||
10 | the only sane way to keep the source tree and the installer in sync. |
|
|||
11 |
|
||||
12 | The MSI installer builder uses only the mercurial.ini file from the |
|
|||
13 | contrib/win32 folder, the contents of which have been historically used |
|
|||
14 | to create an InnoSetup based installer. The rest of the files there are |
|
|||
15 | ignored. |
|
|||
16 |
|
64 | |||
17 | The MSI packages built by thg-winbuild require elevated (admin) |
|
65 | The code for building TortoiseHG installers lives at | |
18 | privileges to be installed due to the installation of MSVC CRT libraries |
|
66 | https://bitbucket.org/tortoisehg/thg-winbuild and is maintained by | |
19 | under the C:\WINDOWS\WinSxS folder. Thus the InnoSetup installers may |
|
67 | Steve Borho (steve@borho.org). | |
20 | still be useful to some users. |
|
|||
21 |
|
68 | |||
22 | To build your own MSI packages, clone the thg-winbuild [1] repository |
|
69 | When changing behavior of the WiX installer, be sure to notify | |
23 | and follow the README.txt [2] instructions closely. There are fewer |
|
70 | the TortoiseHG Project of the changes so they have ample time | |
24 | prerequisites for a WiX [3] installer than an InnoSetup installer, but |
|
71 | provide feedback and react to those changes. | |
25 | they are more specific. |
|
|||
26 |
|
||||
27 | Direct questions or comments to Steve Borho <steve@borho.org> |
|
|||
28 |
|
||||
29 | [1] http://bitbucket.org/tortoisehg/thg-winbuild |
|
|||
30 | [2] http://bitbucket.org/tortoisehg/thg-winbuild/src/tip/README.txt |
|
|||
31 | [3] http://wix.sourceforge.net/ |
|
1 | NO CONTENT: file renamed from contrib/wix/templates.wxs to contrib/packaging/wix/templates.wxs |
|
NO CONTENT: file renamed from contrib/wix/templates.wxs to contrib/packaging/wix/templates.wxs |
@@ -28,9 +28,13 b'' | |||||
28 |
|
28 | |||
29 | set -euo pipefail |
|
29 | set -euo pipefail | |
30 |
|
30 | |||
|
31 | printusage () { | |||
|
32 | echo "usage: `basename $0` REPO NBHEADS DEPTH [left|right]" >&2 | |||
|
33 | } | |||
|
34 | ||||
31 | if [ $# -lt 3 ]; then |
|
35 | if [ $# -lt 3 ]; then | |
32 | echo "usage: `basename $0` REPO NBHEADS DEPTH" |
|
36 | printusage | |
33 |
|
|
37 | exit 64 | |
34 | fi |
|
38 | fi | |
35 |
|
39 | |||
36 | repo="$1" |
|
40 | repo="$1" | |
@@ -42,8 +46,26 b' shift' | |||||
42 | depth="$1" |
|
46 | depth="$1" | |
43 | shift |
|
47 | shift | |
44 |
|
48 | |||
45 | leftrepo="${repo}-left" |
|
49 | doleft=1 | |
46 | rightrepo="${repo}-right" |
|
50 | doright=1 | |
|
51 | if [ $# -gt 1 ]; then | |||
|
52 | printusage | |||
|
53 | exit 64 | |||
|
54 | elif [ $# -eq 1 ]; then | |||
|
55 | if [ "$1" == "left" ]; then | |||
|
56 | doleft=1 | |||
|
57 | doright=0 | |||
|
58 | elif [ "$1" == "right" ]; then | |||
|
59 | doleft=0 | |||
|
60 | doright=1 | |||
|
61 | else | |||
|
62 | printusage | |||
|
63 | exit 64 | |||
|
64 | fi | |||
|
65 | fi | |||
|
66 | ||||
|
67 | leftrepo="${repo}-${nbheads}h-${depth}d-left" | |||
|
68 | rightrepo="${repo}-${nbheads}h-${depth}d-right" | |||
47 |
|
69 | |||
48 | left="first(sort(heads(all()), 'desc'), $nbheads)" |
|
70 | left="first(sort(heads(all()), 'desc'), $nbheads)" | |
49 | right="last(sort(heads(all()), 'desc'), $nbheads)" |
|
71 | right="last(sort(heads(all()), 'desc'), $nbheads)" | |
@@ -51,14 +73,35 b' right="last(sort(heads(all()), \'desc\'), ' | |||||
51 | leftsubset="ancestors($left, $depth) and only($left, heads(all() - $left))" |
|
73 | leftsubset="ancestors($left, $depth) and only($left, heads(all() - $left))" | |
52 | rightsubset="ancestors($right, $depth) and only($right, heads(all() - $right))" |
|
74 | rightsubset="ancestors($right, $depth) and only($right, heads(all() - $right))" | |
53 |
|
75 | |||
54 | echo '### building left repository:' $left-repo |
|
76 | echo '### creating left/right repositories with missing changesets:' | |
55 | echo '# cloning' |
|
77 | if [ $doleft -eq 1 ]; then | |
56 | hg clone --noupdate "${repo}" "${leftrepo}" |
|
78 | echo '# left revset:' '"'${leftsubset}'"' | |
57 | echo '# stripping' '"'${leftsubset}'"' |
|
79 | fi | |
58 | hg -R "${leftrepo}" --config extensions.strip= strip --rev "$leftsubset" --no-backup |
|
80 | if [ $doright -eq 1 ]; then | |
|
81 | echo '# right revset:' '"'${rightsubset}'"' | |||
|
82 | fi | |||
59 |
|
83 | |||
60 | echo '### building right repository:' $right-repo |
|
84 | buildone() { | |
61 | echo '# cloning' |
|
85 | side="$1" | |
62 | hg clone --noupdate "${repo}" "${rightrepo}" |
|
86 | dest="$2" | |
63 | echo '# stripping:' '"'${rightsubset}'"' |
|
87 | revset="$3" | |
64 | hg -R "${rightrepo}" --config extensions.strip= strip --rev "$rightsubset" --no-backup |
|
88 | echo "### building $side repository: $dest" | |
|
89 | if [ -e "$dest" ]; then | |||
|
90 | echo "destination repo already exists: $dest" >&2 | |||
|
91 | exit 1 | |||
|
92 | fi | |||
|
93 | echo '# cloning' | |||
|
94 | if ! cp --recursive --reflink=always ${repo} ${dest}; then | |||
|
95 | hg clone --noupdate "${repo}" "${dest}" | |||
|
96 | fi | |||
|
97 | echo '# stripping' '"'${revset}'"' | |||
|
98 | hg -R "${dest}" --config extensions.strip= strip --rev "$revset" --no-backup | |||
|
99 | } | |||
|
100 | ||||
|
101 | if [ $doleft -eq 1 ]; then | |||
|
102 | buildone left "$leftrepo" "$leftsubset" | |||
|
103 | fi | |||
|
104 | ||||
|
105 | if [ $doright -eq 1 ]; then | |||
|
106 | buildone right "$rightrepo" "$rightsubset" | |||
|
107 | fi |
@@ -1,5 +1,34 b'' | |||||
1 | # perf.py - performance test routines |
|
1 | # perf.py - performance test routines | |
2 |
'''helper extension to measure performance |
|
2 | '''helper extension to measure performance | |
|
3 | ||||
|
4 | Configurations | |||
|
5 | ============== | |||
|
6 | ||||
|
7 | ``perf`` | |||
|
8 | -------- | |||
|
9 | ||||
|
10 | ``all-timing`` | |||
|
11 | When set, additional statistics will be reported for each benchmark: best, | |||
|
12 | worst, median average. If not set only the best timing is reported | |||
|
13 | (default: off). | |||
|
14 | ||||
|
15 | ``presleep`` | |||
|
16 | number of second to wait before any group of runs (default: 1) | |||
|
17 | ||||
|
18 | ``run-limits`` | |||
|
19 | Control the number of runs each benchmark will perform. The option value | |||
|
20 | should be a list of `<time>-<numberofrun>` pairs. After each run the | |||
|
21 | conditions are considered in order with the following logic: | |||
|
22 | ||||
|
23 | If benchmark has been running for <time> seconds, and we have performed | |||
|
24 | <numberofrun> iterations, stop the benchmark, | |||
|
25 | ||||
|
26 | The default value is: `3.0-100, 10.0-3` | |||
|
27 | ||||
|
28 | ``stub`` | |||
|
29 | When set, benchmarks will only be run once, useful for testing | |||
|
30 | (default: off) | |||
|
31 | ''' | |||
3 |
|
32 | |||
4 | # "historical portability" policy of perf.py: |
|
33 | # "historical portability" policy of perf.py: | |
5 | # |
|
34 | # | |
@@ -65,6 +94,10 b' try:' | |||||
65 | except ImportError: |
|
94 | except ImportError: | |
66 | pass |
|
95 | pass | |
67 | try: |
|
96 | try: | |
|
97 | from mercurial.utils import repoviewutil # since 5.0 | |||
|
98 | except ImportError: | |||
|
99 | repoviewutil = None | |||
|
100 | try: | |||
68 | from mercurial import scmutil # since 1.9 (or 8b252e826c68) |
|
101 | from mercurial import scmutil # since 1.9 (or 8b252e826c68) | |
69 | except ImportError: |
|
102 | except ImportError: | |
70 | pass |
|
103 | pass | |
@@ -207,6 +240,9 b' try:' | |||||
207 | configitem(b'perf', b'all-timing', |
|
240 | configitem(b'perf', b'all-timing', | |
208 | default=mercurial.configitems.dynamicdefault, |
|
241 | default=mercurial.configitems.dynamicdefault, | |
209 | ) |
|
242 | ) | |
|
243 | configitem(b'perf', b'run-limits', | |||
|
244 | default=mercurial.configitems.dynamicdefault, | |||
|
245 | ) | |||
210 | except (ImportError, AttributeError): |
|
246 | except (ImportError, AttributeError): | |
211 | pass |
|
247 | pass | |
212 |
|
248 | |||
@@ -279,7 +315,34 b' def gettimer(ui, opts=None):' | |||||
279 |
|
315 | |||
280 | # experimental config: perf.all-timing |
|
316 | # experimental config: perf.all-timing | |
281 | displayall = ui.configbool(b"perf", b"all-timing", False) |
|
317 | displayall = ui.configbool(b"perf", b"all-timing", False) | |
282 | return functools.partial(_timer, fm, displayall=displayall), fm |
|
318 | ||
|
319 | # experimental config: perf.run-limits | |||
|
320 | limitspec = ui.configlist(b"perf", b"run-limits", []) | |||
|
321 | limits = [] | |||
|
322 | for item in limitspec: | |||
|
323 | parts = item.split(b'-', 1) | |||
|
324 | if len(parts) < 2: | |||
|
325 | ui.warn((b'malformatted run limit entry, missing "-": %s\n' | |||
|
326 | % item)) | |||
|
327 | continue | |||
|
328 | try: | |||
|
329 | time_limit = float(pycompat.sysstr(parts[0])) | |||
|
330 | except ValueError as e: | |||
|
331 | ui.warn((b'malformatted run limit entry, %s: %s\n' | |||
|
332 | % (pycompat.bytestr(e), item))) | |||
|
333 | continue | |||
|
334 | try: | |||
|
335 | run_limit = int(pycompat.sysstr(parts[1])) | |||
|
336 | except ValueError as e: | |||
|
337 | ui.warn((b'malformatted run limit entry, %s: %s\n' | |||
|
338 | % (pycompat.bytestr(e), item))) | |||
|
339 | continue | |||
|
340 | limits.append((time_limit, run_limit)) | |||
|
341 | if not limits: | |||
|
342 | limits = DEFAULTLIMITS | |||
|
343 | ||||
|
344 | t = functools.partial(_timer, fm, displayall=displayall, limits=limits) | |||
|
345 | return t, fm | |||
283 |
|
346 | |||
284 | def stub_timer(fm, func, setup=None, title=None): |
|
347 | def stub_timer(fm, func, setup=None, title=None): | |
285 | if setup is not None: |
|
348 | if setup is not None: | |
@@ -297,12 +360,21 b' def timeone():' | |||||
297 | a, b = ostart, ostop |
|
360 | a, b = ostart, ostop | |
298 | r.append((cstop - cstart, b[0] - a[0], b[1]-a[1])) |
|
361 | r.append((cstop - cstart, b[0] - a[0], b[1]-a[1])) | |
299 |
|
362 | |||
300 | def _timer(fm, func, setup=None, title=None, displayall=False): |
|
363 | ||
|
364 | # list of stop condition (elapsed time, minimal run count) | |||
|
365 | DEFAULTLIMITS = ( | |||
|
366 | (3.0, 100), | |||
|
367 | (10.0, 3), | |||
|
368 | ) | |||
|
369 | ||||
|
370 | def _timer(fm, func, setup=None, title=None, displayall=False, | |||
|
371 | limits=DEFAULTLIMITS): | |||
301 | gc.collect() |
|
372 | gc.collect() | |
302 | results = [] |
|
373 | results = [] | |
303 | begin = util.timer() |
|
374 | begin = util.timer() | |
304 | count = 0 |
|
375 | count = 0 | |
305 | while True: |
|
376 | keepgoing = True | |
|
377 | while keepgoing: | |||
306 | if setup is not None: |
|
378 | if setup is not None: | |
307 | setup() |
|
379 | setup() | |
308 | with timeone() as item: |
|
380 | with timeone() as item: | |
@@ -310,10 +382,12 b' def _timer(fm, func, setup=None, title=N' | |||||
310 | count += 1 |
|
382 | count += 1 | |
311 | results.append(item[0]) |
|
383 | results.append(item[0]) | |
312 | cstop = util.timer() |
|
384 | cstop = util.timer() | |
313 | if cstop - begin > 3 and count >= 100: |
|
385 | # Look for a stop condition. | |
314 | break |
|
386 | elapsed = cstop - begin | |
315 | if cstop - begin > 10 and count >= 3: |
|
387 | for t, mincount in limits: | |
316 | break |
|
388 | if elapsed >= t and count >= mincount: | |
|
389 | keepgoing = False | |||
|
390 | break | |||
317 |
|
391 | |||
318 | formatone(fm, results, title=title, result=r, |
|
392 | formatone(fm, results, title=title, result=r, | |
319 | displayall=displayall) |
|
393 | displayall=displayall) | |
@@ -401,7 +475,8 b' def getbranchmapsubsettable():' | |||||
401 | # subsettable is defined in: |
|
475 | # subsettable is defined in: | |
402 | # - branchmap since 2.9 (or 175c6fd8cacc) |
|
476 | # - branchmap since 2.9 (or 175c6fd8cacc) | |
403 | # - repoview since 2.5 (or 59a9f18d4587) |
|
477 | # - repoview since 2.5 (or 59a9f18d4587) | |
404 | for mod in (branchmap, repoview): |
|
478 | # - repoviewutil since 5.0 | |
|
479 | for mod in (branchmap, repoview, repoviewutil): | |||
405 | subsettable = getattr(mod, 'subsettable', None) |
|
480 | subsettable = getattr(mod, 'subsettable', None) | |
406 | if subsettable: |
|
481 | if subsettable: | |
407 | return subsettable |
|
482 | return subsettable | |
@@ -519,7 +594,11 b' def perfaddremove(ui, repo, **opts):' | |||||
519 | repo.ui.quiet = True |
|
594 | repo.ui.quiet = True | |
520 | matcher = scmutil.match(repo[None]) |
|
595 | matcher = scmutil.match(repo[None]) | |
521 | opts[b'dry_run'] = True |
|
596 | opts[b'dry_run'] = True | |
522 | timer(lambda: scmutil.addremove(repo, matcher, b"", opts)) |
|
597 | if b'uipathfn' in getargspec(scmutil.addremove).args: | |
|
598 | uipathfn = scmutil.getuipathfn(repo) | |||
|
599 | timer(lambda: scmutil.addremove(repo, matcher, b"", uipathfn, opts)) | |||
|
600 | else: | |||
|
601 | timer(lambda: scmutil.addremove(repo, matcher, b"", opts)) | |||
523 | finally: |
|
602 | finally: | |
524 | repo.ui.quiet = oldquiet |
|
603 | repo.ui.quiet = oldquiet | |
525 | fm.end() |
|
604 | fm.end() | |
@@ -535,13 +614,15 b' def clearcaches(cl):' | |||||
535 |
|
614 | |||
536 | @command(b'perfheads', formatteropts) |
|
615 | @command(b'perfheads', formatteropts) | |
537 | def perfheads(ui, repo, **opts): |
|
616 | def perfheads(ui, repo, **opts): | |
|
617 | """benchmark the computation of a changelog heads""" | |||
538 | opts = _byteskwargs(opts) |
|
618 | opts = _byteskwargs(opts) | |
539 | timer, fm = gettimer(ui, opts) |
|
619 | timer, fm = gettimer(ui, opts) | |
540 | cl = repo.changelog |
|
620 | cl = repo.changelog | |
|
621 | def s(): | |||
|
622 | clearcaches(cl) | |||
541 | def d(): |
|
623 | def d(): | |
542 | len(cl.headrevs()) |
|
624 | len(cl.headrevs()) | |
543 | clearcaches(cl) |
|
625 | timer(d, setup=s) | |
544 | timer(d) |
|
|||
545 | fm.end() |
|
626 | fm.end() | |
546 |
|
627 | |||
547 | @command(b'perftags', formatteropts+ |
|
628 | @command(b'perftags', formatteropts+ | |
@@ -911,9 +992,7 b' def perfphasesremote(ui, repo, dest=None' | |||||
911 | raise error.Abort((b'default repository not configured!'), |
|
992 | raise error.Abort((b'default repository not configured!'), | |
912 | hint=(b"see 'hg help config.paths'")) |
|
993 | hint=(b"see 'hg help config.paths'")) | |
913 | dest = path.pushloc or path.loc |
|
994 | dest = path.pushloc or path.loc | |
914 | branches = (path.branch, opts.get(b'branch') or []) |
|
|||
915 | ui.status((b'analysing phase of %s\n') % util.hidepassword(dest)) |
|
995 | ui.status((b'analysing phase of %s\n') % util.hidepassword(dest)) | |
916 | revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev')) |
|
|||
917 | other = hg.peer(repo, opts, dest) |
|
996 | other = hg.peer(repo, opts, dest) | |
918 |
|
997 | |||
919 | # easier to perform discovery through the operation |
|
998 | # easier to perform discovery through the operation | |
@@ -1014,18 +1093,44 b' def perfignore(ui, repo, **opts):' | |||||
1014 | fm.end() |
|
1093 | fm.end() | |
1015 |
|
1094 | |||
1016 | @command(b'perfindex', [ |
|
1095 | @command(b'perfindex', [ | |
1017 |
(b'', b'rev', |
|
1096 | (b'', b'rev', [], b'revision to be looked up (default tip)'), | |
|
1097 | (b'', b'no-lookup', None, b'do not revision lookup post creation'), | |||
1018 | ] + formatteropts) |
|
1098 | ] + formatteropts) | |
1019 | def perfindex(ui, repo, **opts): |
|
1099 | def perfindex(ui, repo, **opts): | |
|
1100 | """benchmark index creation time followed by a lookup | |||
|
1101 | ||||
|
1102 | The default is to look `tip` up. Depending on the index implementation, | |||
|
1103 | the revision looked up can matters. For example, an implementation | |||
|
1104 | scanning the index will have a faster lookup time for `--rev tip` than for | |||
|
1105 | `--rev 0`. The number of looked up revisions and their order can also | |||
|
1106 | matters. | |||
|
1107 | ||||
|
1108 | Example of useful set to test: | |||
|
1109 | * tip | |||
|
1110 | * 0 | |||
|
1111 | * -10: | |||
|
1112 | * :10 | |||
|
1113 | * -10: + :10 | |||
|
1114 | * :10: + -10: | |||
|
1115 | * -10000: | |||
|
1116 | * -10000: + 0 | |||
|
1117 | ||||
|
1118 | It is not currently possible to check for lookup of a missing node. For | |||
|
1119 | deeper lookup benchmarking, checkout the `perfnodemap` command.""" | |||
1020 | import mercurial.revlog |
|
1120 | import mercurial.revlog | |
1021 | opts = _byteskwargs(opts) |
|
1121 | opts = _byteskwargs(opts) | |
1022 | timer, fm = gettimer(ui, opts) |
|
1122 | timer, fm = gettimer(ui, opts) | |
1023 | mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg |
|
1123 | mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg | |
1024 |
if opts[b' |
|
1124 | if opts[b'no_lookup']: | |
1025 | n = repo[b"tip"].node() |
|
1125 | if opts['rev']: | |
|
1126 | raise error.Abort('--no-lookup and --rev are mutually exclusive') | |||
|
1127 | nodes = [] | |||
|
1128 | elif not opts[b'rev']: | |||
|
1129 | nodes = [repo[b"tip"].node()] | |||
1026 | else: |
|
1130 | else: | |
1027 |
rev = scmutil.rev |
|
1131 | revs = scmutil.revrange(repo, opts[b'rev']) | |
1028 |
|
|
1132 | cl = repo.changelog | |
|
1133 | nodes = [cl.node(r) for r in revs] | |||
1029 |
|
1134 | |||
1030 | unfi = repo.unfiltered() |
|
1135 | unfi = repo.unfiltered() | |
1031 | # find the filecache func directly |
|
1136 | # find the filecache func directly | |
@@ -1036,7 +1141,67 b' def perfindex(ui, repo, **opts):' | |||||
1036 | clearchangelog(unfi) |
|
1141 | clearchangelog(unfi) | |
1037 | def d(): |
|
1142 | def d(): | |
1038 | cl = makecl(unfi) |
|
1143 | cl = makecl(unfi) | |
1039 | cl.rev(n) |
|
1144 | for n in nodes: | |
|
1145 | cl.rev(n) | |||
|
1146 | timer(d, setup=setup) | |||
|
1147 | fm.end() | |||
|
1148 | ||||
|
1149 | @command(b'perfnodemap', [ | |||
|
1150 | (b'', b'rev', [], b'revision to be looked up (default tip)'), | |||
|
1151 | (b'', b'clear-caches', True, b'clear revlog cache between calls'), | |||
|
1152 | ] + formatteropts) | |||
|
1153 | def perfnodemap(ui, repo, **opts): | |||
|
1154 | """benchmark the time necessary to look up revision from a cold nodemap | |||
|
1155 | ||||
|
1156 | Depending on the implementation, the amount and order of revision we look | |||
|
1157 | up can varies. Example of useful set to test: | |||
|
1158 | * tip | |||
|
1159 | * 0 | |||
|
1160 | * -10: | |||
|
1161 | * :10 | |||
|
1162 | * -10: + :10 | |||
|
1163 | * :10: + -10: | |||
|
1164 | * -10000: | |||
|
1165 | * -10000: + 0 | |||
|
1166 | ||||
|
1167 | The command currently focus on valid binary lookup. Benchmarking for | |||
|
1168 | hexlookup, prefix lookup and missing lookup would also be valuable. | |||
|
1169 | """ | |||
|
1170 | import mercurial.revlog | |||
|
1171 | opts = _byteskwargs(opts) | |||
|
1172 | timer, fm = gettimer(ui, opts) | |||
|
1173 | mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg | |||
|
1174 | ||||
|
1175 | unfi = repo.unfiltered() | |||
|
1176 | clearcaches = opts['clear_caches'] | |||
|
1177 | # find the filecache func directly | |||
|
1178 | # This avoid polluting the benchmark with the filecache logic | |||
|
1179 | makecl = unfi.__class__.changelog.func | |||
|
1180 | if not opts[b'rev']: | |||
|
1181 | raise error.Abort('use --rev to specify revisions to look up') | |||
|
1182 | revs = scmutil.revrange(repo, opts[b'rev']) | |||
|
1183 | cl = repo.changelog | |||
|
1184 | nodes = [cl.node(r) for r in revs] | |||
|
1185 | ||||
|
1186 | # use a list to pass reference to a nodemap from one closure to the next | |||
|
1187 | nodeget = [None] | |||
|
1188 | def setnodeget(): | |||
|
1189 | # probably not necessary, but for good measure | |||
|
1190 | clearchangelog(unfi) | |||
|
1191 | nodeget[0] = makecl(unfi).nodemap.get | |||
|
1192 | ||||
|
1193 | def d(): | |||
|
1194 | get = nodeget[0] | |||
|
1195 | for n in nodes: | |||
|
1196 | get(n) | |||
|
1197 | ||||
|
1198 | setup = None | |||
|
1199 | if clearcaches: | |||
|
1200 | def setup(): | |||
|
1201 | setnodeget() | |||
|
1202 | else: | |||
|
1203 | setnodeget() | |||
|
1204 | d() # prewarm the data structure | |||
1040 | timer(d, setup=setup) |
|
1205 | timer(d, setup=setup) | |
1041 | fm.end() |
|
1206 | fm.end() | |
1042 |
|
1207 | |||
@@ -1056,6 +1221,13 b' def perfstartup(ui, repo, **opts):' | |||||
1056 |
|
1221 | |||
1057 | @command(b'perfparents', formatteropts) |
|
1222 | @command(b'perfparents', formatteropts) | |
1058 | def perfparents(ui, repo, **opts): |
|
1223 | def perfparents(ui, repo, **opts): | |
|
1224 | """benchmark the time necessary to fetch one changeset's parents. | |||
|
1225 | ||||
|
1226 | The fetch is done using the `node identifier`, traversing all object layers | |||
|
1227 | from the repository object. The first N revisions will be used for this | |||
|
1228 | benchmark. N is controlled by the ``perf.parentscount`` config option | |||
|
1229 | (default: 1000). | |||
|
1230 | """ | |||
1059 | opts = _byteskwargs(opts) |
|
1231 | opts = _byteskwargs(opts) | |
1060 | timer, fm = gettimer(ui, opts) |
|
1232 | timer, fm = gettimer(ui, opts) | |
1061 | # control the number of commits perfparents iterates over |
|
1233 | # control the number of commits perfparents iterates over | |
@@ -2290,13 +2462,18 b' def perfbranchmap(ui, repo, *filternames' | |||||
2290 | view = repo |
|
2462 | view = repo | |
2291 | else: |
|
2463 | else: | |
2292 | view = repo.filtered(filtername) |
|
2464 | view = repo.filtered(filtername) | |
|
2465 | if util.safehasattr(view._branchcaches, '_per_filter'): | |||
|
2466 | filtered = view._branchcaches._per_filter | |||
|
2467 | else: | |||
|
2468 | # older versions | |||
|
2469 | filtered = view._branchcaches | |||
2293 | def d(): |
|
2470 | def d(): | |
2294 | if clear_revbranch: |
|
2471 | if clear_revbranch: | |
2295 | repo.revbranchcache()._clear() |
|
2472 | repo.revbranchcache()._clear() | |
2296 | if full: |
|
2473 | if full: | |
2297 | view._branchcaches.clear() |
|
2474 | view._branchcaches.clear() | |
2298 | else: |
|
2475 | else: | |
2299 |
|
|
2476 | filtered.pop(filtername, None) | |
2300 | view.branchmap() |
|
2477 | view.branchmap() | |
2301 | return d |
|
2478 | return d | |
2302 | # add filter in smaller subset to bigger subset |
|
2479 | # add filter in smaller subset to bigger subset | |
@@ -2323,10 +2500,15 b' def perfbranchmap(ui, repo, *filternames' | |||||
2323 | # add unfiltered |
|
2500 | # add unfiltered | |
2324 | allfilters.append(None) |
|
2501 | allfilters.append(None) | |
2325 |
|
2502 | |||
2326 | branchcacheread = safeattrsetter(branchmap, b'read') |
|
2503 | if util.safehasattr(branchmap.branchcache, 'fromfile'): | |
|
2504 | branchcacheread = safeattrsetter(branchmap.branchcache, b'fromfile') | |||
|
2505 | branchcacheread.set(classmethod(lambda *args: None)) | |||
|
2506 | else: | |||
|
2507 | # older versions | |||
|
2508 | branchcacheread = safeattrsetter(branchmap, b'read') | |||
|
2509 | branchcacheread.set(lambda *args: None) | |||
2327 | branchcachewrite = safeattrsetter(branchmap.branchcache, b'write') |
|
2510 | branchcachewrite = safeattrsetter(branchmap.branchcache, b'write') | |
2328 |
branchcachere |
|
2511 | branchcachewrite.set(lambda *args: None) | |
2329 | branchcachewrite.set(lambda bc, repo: None) |
|
|||
2330 | try: |
|
2512 | try: | |
2331 | for name in allfilters: |
|
2513 | for name in allfilters: | |
2332 | printname = name |
|
2514 | printname = name | |
@@ -2470,9 +2652,15 b' def perfbranchmapload(ui, repo, filter=b' | |||||
2470 |
|
2652 | |||
2471 | repo.branchmap() # make sure we have a relevant, up to date branchmap |
|
2653 | repo.branchmap() # make sure we have a relevant, up to date branchmap | |
2472 |
|
2654 | |||
|
2655 | try: | |||
|
2656 | fromfile = branchmap.branchcache.fromfile | |||
|
2657 | except AttributeError: | |||
|
2658 | # older versions | |||
|
2659 | fromfile = branchmap.read | |||
|
2660 | ||||
2473 | currentfilter = filter |
|
2661 | currentfilter = filter | |
2474 | # try once without timer, the filter may not be cached |
|
2662 | # try once without timer, the filter may not be cached | |
2475 |
while |
|
2663 | while fromfile(repo) is None: | |
2476 | currentfilter = subsettable.get(currentfilter) |
|
2664 | currentfilter = subsettable.get(currentfilter) | |
2477 | if currentfilter is None: |
|
2665 | if currentfilter is None: | |
2478 | raise error.Abort(b'No branchmap cached for %s repo' |
|
2666 | raise error.Abort(b'No branchmap cached for %s repo' | |
@@ -2483,7 +2671,7 b' def perfbranchmapload(ui, repo, filter=b' | |||||
2483 | if clearrevlogs: |
|
2671 | if clearrevlogs: | |
2484 | clearchangelog(repo) |
|
2672 | clearchangelog(repo) | |
2485 | def bench(): |
|
2673 | def bench(): | |
2486 |
|
|
2674 | fromfile(repo) | |
2487 | timer(bench, setup=setup) |
|
2675 | timer(bench, setup=setup) | |
2488 | fm.end() |
|
2676 | fm.end() | |
2489 |
|
2677 |
@@ -5,6 +5,5 b' graft tests' | |||||
5 | include make_cffi.py |
|
5 | include make_cffi.py | |
6 | include setup_zstd.py |
|
6 | include setup_zstd.py | |
7 | include zstd.c |
|
7 | include zstd.c | |
8 | include zstd_cffi.py |
|
|||
9 | include LICENSE |
|
8 | include LICENSE | |
10 | include NEWS.rst |
|
9 | include NEWS.rst |
@@ -8,8 +8,18 b' 1.0.0 (not yet released)' | |||||
8 | Actions Blocking Release |
|
8 | Actions Blocking Release | |
9 | ------------------------ |
|
9 | ------------------------ | |
10 |
|
10 | |||
11 |
* compression and decompression APIs that support ``io. |
|
11 | * compression and decompression APIs that support ``io.RawIOBase`` interface | |
12 | (#13). |
|
12 | (#13). | |
|
13 | * ``stream_writer()`` APIs should support ``io.RawIOBase`` interface. | |||
|
14 | * Properly handle non-blocking I/O and partial writes for objects implementing | |||
|
15 | ``io.RawIOBase``. | |||
|
16 | * Make ``write_return_read=True`` the default for objects implementing | |||
|
17 | ``io.RawIOBase``. | |||
|
18 | * Audit for consistent and proper behavior of ``flush()`` and ``close()`` for | |||
|
19 | all objects implementing ``io.RawIOBase``. Is calling ``close()`` on | |||
|
20 | wrapped stream acceptable, should ``__exit__`` always call ``close()``, | |||
|
21 | should ``close()`` imply ``flush()``, etc. | |||
|
22 | * Consider making reads across frames configurable behavior. | |||
13 | * Refactor module names so C and CFFI extensions live under ``zstandard`` |
|
23 | * Refactor module names so C and CFFI extensions live under ``zstandard`` | |
14 | package. |
|
24 | package. | |
15 | * Overall API design review. |
|
25 | * Overall API design review. | |
@@ -43,6 +53,11 b' Actions Blocking Release' | |||||
43 | * Consider a ``chunker()`` API for decompression. |
|
53 | * Consider a ``chunker()`` API for decompression. | |
44 | * Consider stats for ``chunker()`` API, including finding the last consumed |
|
54 | * Consider stats for ``chunker()`` API, including finding the last consumed | |
45 | offset of input data. |
|
55 | offset of input data. | |
|
56 | * Consider exposing ``ZSTD_cParam_getBounds()`` and | |||
|
57 | ``ZSTD_dParam_getBounds()`` APIs. | |||
|
58 | * Consider controls over resetting compression contexts (session only, parameters, | |||
|
59 | or session and parameters). | |||
|
60 | * Actually use the CFFI backend in fuzzing tests. | |||
46 |
|
61 | |||
47 | Other Actions Not Blocking Release |
|
62 | Other Actions Not Blocking Release | |
48 | --------------------------------------- |
|
63 | --------------------------------------- | |
@@ -51,6 +66,207 b' Other Actions Not Blocking Release' | |||||
51 | * API for ensuring max memory ceiling isn't exceeded. |
|
66 | * API for ensuring max memory ceiling isn't exceeded. | |
52 | * Move off nose for testing. |
|
67 | * Move off nose for testing. | |
53 |
|
68 | |||
|
69 | 0.11.0 (released 2019-02-24) | |||
|
70 | ============================ | |||
|
71 | ||||
|
72 | Backwards Compatibility Nodes | |||
|
73 | ----------------------------- | |||
|
74 | ||||
|
75 | * ``ZstdDecompressor.read()`` now allows reading sizes of ``-1`` or ``0`` | |||
|
76 | and defaults to ``-1``, per the documented behavior of | |||
|
77 | ``io.RawIOBase.read()``. Previously, we required an argument that was | |||
|
78 | a positive value. | |||
|
79 | * The ``readline()``, ``readlines()``, ``__iter__``, and ``__next__`` methods | |||
|
80 | of ``ZstdDecompressionReader()`` now raise ``io.UnsupportedOperation`` | |||
|
81 | instead of ``NotImplementedError``. | |||
|
82 | * ``ZstdDecompressor.stream_reader()`` now accepts a ``read_across_frames`` | |||
|
83 | argument. The default value will likely be changed in a future release | |||
|
84 | and consumers are advised to pass the argument to avoid unwanted change | |||
|
85 | of behavior in the future. | |||
|
86 | * ``setup.py`` now always disables the CFFI backend if the installed | |||
|
87 | CFFI package does not meet the minimum version requirements. Before, it was | |||
|
88 | possible for the CFFI backend to be generated and a run-time error to | |||
|
89 | occur. | |||
|
90 | * In the CFFI backend, ``CompressionReader`` and ``DecompressionReader`` | |||
|
91 | were renamed to ``ZstdCompressionReader`` and ``ZstdDecompressionReader``, | |||
|
92 | respectively so naming is identical to the C extension. This should have | |||
|
93 | no meaningful end-user impact, as instances aren't meant to be | |||
|
94 | constructed directly. | |||
|
95 | * ``ZstdDecompressor.stream_writer()`` now accepts a ``write_return_read`` | |||
|
96 | argument to control whether ``write()`` returns the number of bytes | |||
|
97 | read from the source / written to the decompressor. It defaults to off, | |||
|
98 | which preserves the existing behavior of returning the number of bytes | |||
|
99 | emitted from the decompressor. The default will change in a future release | |||
|
100 | so behavior aligns with the specified behavior of ``io.RawIOBase``. | |||
|
101 | * ``ZstdDecompressionWriter.__exit__`` now calls ``self.close()``. This | |||
|
102 | will result in that stream plus the underlying stream being closed as | |||
|
103 | well. If this behavior is not desirable, do not use instances as | |||
|
104 | context managers. | |||
|
105 | * ``ZstdCompressor.stream_writer()`` now accepts a ``write_return_read`` | |||
|
106 | argument to control whether ``write()`` returns the number of bytes read | |||
|
107 | from the source / written to the compressor. It defaults to off, which | |||
|
108 | preserves the existing behavior of returning the number of bytes emitted | |||
|
109 | from the compressor. The default will change in a future release so | |||
|
110 | behavior aligns with the specified behavior of ``io.RawIOBase``. | |||
|
111 | * ``ZstdCompressionWriter.__exit__`` now calls ``self.close()``. This will | |||
|
112 | result in that stream plus any underlying stream being closed as well. If | |||
|
113 | this behavior is not desirable, do not use instances as context managers. | |||
|
114 | * ``ZstdDecompressionWriter`` no longer requires being used as a context | |||
|
115 | manager (#57). | |||
|
116 | * ``ZstdCompressionWriter`` no longer requires being used as a context | |||
|
117 | manager (#57). | |||
|
118 | * The ``overlap_size_log`` attribute on ``CompressionParameters`` instances | |||
|
119 | has been deprecated and will be removed in a future release. The | |||
|
120 | ``overlap_log`` attribute should be used instead. | |||
|
121 | * The ``overlap_size_log`` argument to ``CompressionParameters`` has been | |||
|
122 | deprecated and will be removed in a future release. The ``overlap_log`` | |||
|
123 | argument should be used instead. | |||
|
124 | * The ``ldm_hash_every_log`` attribute on ``CompressionParameters`` instances | |||
|
125 | has been deprecated and will be removed in a future release. The | |||
|
126 | ``ldm_hash_rate_log`` attribute should be used instead. | |||
|
127 | * The ``ldm_hash_every_log`` argument to ``CompressionParameters`` has been | |||
|
128 | deprecated and will be removed in a future release. The ``ldm_hash_rate_log`` | |||
|
129 | argument should be used instead. | |||
|
130 | * The ``compression_strategy`` argument to ``CompressionParameters`` has been | |||
|
131 | deprecated and will be removed in a future release. The ``strategy`` | |||
|
132 | argument should be used instead. | |||
|
133 | * The ``SEARCHLENGTH_MIN`` and ``SEARCHLENGTH_MAX`` constants are deprecated | |||
|
134 | and will be removed in a future release. Use ``MINMATCH_MIN`` and | |||
|
135 | ``MINMATCH_MAX`` instead. | |||
|
136 | * The ``zstd_cffi`` module has been renamed to ``zstandard.cffi``. As had | |||
|
137 | been documented in the ``README`` file since the ``0.9.0`` release, the | |||
|
138 | module should not be imported directly at its new location. Instead, | |||
|
139 | ``import zstandard`` to cause an appropriate backend module to be loaded | |||
|
140 | automatically. | |||
|
141 | ||||
|
142 | Bug Fixes | |||
|
143 | --------- | |||
|
144 | ||||
|
145 | * CFFI backend could encounter a failure when sending an empty chunk into | |||
|
146 | ``ZstdDecompressionObj.decompress()``. The issue has been fixed. | |||
|
147 | * CFFI backend could encounter an error when calling | |||
|
148 | ``ZstdDecompressionReader.read()`` if there was data remaining in an | |||
|
149 | internal buffer. The issue has been fixed. (#71) | |||
|
150 | ||||
|
151 | Changes | |||
|
152 | ------- | |||
|
153 | ||||
|
154 | * ``ZstDecompressionObj.decompress()`` now properly handles empty inputs in | |||
|
155 | the CFFI backend. | |||
|
156 | * ``ZstdCompressionReader`` now implements ``read1()`` and ``readinto1()``. | |||
|
157 | These are part of the ``io.BufferedIOBase`` interface. | |||
|
158 | * ``ZstdCompressionReader`` has gained a ``readinto(b)`` method for reading | |||
|
159 | compressed output into an existing buffer. | |||
|
160 | * ``ZstdCompressionReader.read()`` now defaults to ``size=-1`` and accepts | |||
|
161 | read sizes of ``-1`` and ``0``. The new behavior aligns with the documented | |||
|
162 | behavior of ``io.RawIOBase``. | |||
|
163 | * ``ZstdCompressionReader`` now implements ``readall()``. Previously, this | |||
|
164 | method raised ``NotImplementedError``. | |||
|
165 | * ``ZstdDecompressionReader`` now implements ``read1()`` and ``readinto1()``. | |||
|
166 | These are part of the ``io.BufferedIOBase`` interface. | |||
|
167 | * ``ZstdDecompressionReader.read()`` now defaults to ``size=-1`` and accepts | |||
|
168 | read sizes of ``-1`` and ``0``. The new behavior aligns with the documented | |||
|
169 | behavior of ``io.RawIOBase``. | |||
|
170 | * ``ZstdDecompressionReader()`` now implements ``readall()``. Previously, this | |||
|
171 | method raised ``NotImplementedError``. | |||
|
172 | * The ``readline()``, ``readlines()``, ``__iter__``, and ``__next__`` methods | |||
|
173 | of ``ZstdDecompressionReader()`` now raise ``io.UnsupportedOperation`` | |||
|
174 | instead of ``NotImplementedError``. This reflects a decision to never | |||
|
175 | implement text-based I/O on (de)compressors and keep the low-level API | |||
|
176 | operating in the binary domain. (#13) | |||
|
177 | * ``README.rst`` now documented how to achieve linewise iteration using | |||
|
178 | an ``io.TextIOWrapper`` with a ``ZstdDecompressionReader``. | |||
|
179 | * ``ZstdDecompressionReader`` has gained a ``readinto(b)`` method for | |||
|
180 | reading decompressed output into an existing buffer. This allows chaining | |||
|
181 | to an ``io.TextIOWrapper`` on Python 3 without using an ``io.BufferedReader``. | |||
|
182 | * ``ZstdDecompressor.stream_reader()`` now accepts a ``read_across_frames`` | |||
|
183 | argument to control behavior when the input data has multiple zstd | |||
|
184 | *frames*. When ``False`` (the default for backwards compatibility), a | |||
|
185 | ``read()`` will stop when the end of a zstd *frame* is encountered. When | |||
|
186 | ``True``, ``read()`` can potentially return data spanning multiple zstd | |||
|
187 | *frames*. The default will likely be changed to ``True`` in a future | |||
|
188 | release. | |||
|
189 | * ``setup.py`` now performs CFFI version sniffing and disables the CFFI | |||
|
190 | backend if CFFI is too old. Previously, we only used ``install_requires`` | |||
|
191 | to enforce the CFFI version and not all build modes would properly enforce | |||
|
192 | the minimum CFFI version. (#69) | |||
|
193 | * CFFI's ``ZstdDecompressionReader.read()`` now properly handles data | |||
|
194 | remaining in any internal buffer. Before, repeated ``read()`` could | |||
|
195 | result in *random* errors. (#71) | |||
|
196 | * Upgraded various Python packages in CI environment. | |||
|
197 | * Upgrade to hypothesis 4.5.11. | |||
|
198 | * In the CFFI backend, ``CompressionReader`` and ``DecompressionReader`` | |||
|
199 | were renamed to ``ZstdCompressionReader`` and ``ZstdDecompressionReader``, | |||
|
200 | respectively. | |||
|
201 | * ``ZstdDecompressor.stream_writer()`` now accepts a ``write_return_read`` | |||
|
202 | argument to control whether ``write()`` returns the number of bytes read | |||
|
203 | from the source. It defaults to ``False`` to preserve backwards | |||
|
204 | compatibility. | |||
|
205 | * ``ZstdDecompressor.stream_writer()`` now implements the ``io.RawIOBase`` | |||
|
206 | interface and behaves as a proper stream object. | |||
|
207 | * ``ZstdCompressor.stream_writer()`` now accepts a ``write_return_read`` | |||
|
208 | argument to control whether ``write()`` returns the number of bytes read | |||
|
209 | from the source. It defaults to ``False`` to preserve backwards | |||
|
210 | compatibility. | |||
|
211 | * ``ZstdCompressionWriter`` now implements the ``io.RawIOBase`` interface and | |||
|
212 | behaves as a proper stream object. ``close()`` will now close the stream | |||
|
213 | and the underlying stream (if possible). ``__exit__`` will now call | |||
|
214 | ``close()``. Methods like ``writable()`` and ``fileno()`` are implemented. | |||
|
215 | * ``ZstdDecompressionWriter`` no longer must be used as a context manager. | |||
|
216 | * ``ZstdCompressionWriter`` no longer must be used as a context manager. | |||
|
217 | When not using as a context manager, it is important to call | |||
|
218 | ``flush(FRAME_FRAME)`` or the compression stream won't be properly | |||
|
219 | terminated and decoders may complain about malformed input. | |||
|
220 | * ``ZstdCompressionWriter.flush()`` (what is returned from | |||
|
221 | ``ZstdCompressor.stream_writer()``) now accepts an argument controlling the | |||
|
222 | flush behavior. Its value can be one of the new constants | |||
|
223 | ``FLUSH_BLOCK`` or ``FLUSH_FRAME``. | |||
|
224 | * ``ZstdDecompressionObj`` instances now have a ``flush([length=None])`` method. | |||
|
225 | This provides parity with standard library equivalent types. (#65) | |||
|
226 | * ``CompressionParameters`` no longer redundantly store individual compression | |||
|
227 | parameters on each instance. Instead, compression parameters are stored inside | |||
|
228 | the underlying ``ZSTD_CCtx_params`` instance. Attributes for obtaining | |||
|
229 | parameters are now properties rather than instance variables. | |||
|
230 | * Exposed the ``STRATEGY_BTULTRA2`` constant. | |||
|
231 | * ``CompressionParameters`` instances now expose an ``overlap_log`` attribute. | |||
|
232 | This behaves identically to the ``overlap_size_log`` attribute. | |||
|
233 | * ``CompressionParameters()`` now accepts an ``overlap_log`` argument that | |||
|
234 | behaves identically to the ``overlap_size_log`` argument. An error will be | |||
|
235 | raised if both arguments are specified. | |||
|
236 | * ``CompressionParameters`` instances now expose an ``ldm_hash_rate_log`` | |||
|
237 | attribute. This behaves identically to the ``ldm_hash_every_log`` attribute. | |||
|
238 | * ``CompressionParameters()`` now accepts a ``ldm_hash_rate_log`` argument that | |||
|
239 | behaves identically to the ``ldm_hash_every_log`` argument. An error will be | |||
|
240 | raised if both arguments are specified. | |||
|
241 | * ``CompressionParameters()`` now accepts a ``strategy`` argument that behaves | |||
|
242 | identically to the ``compression_strategy`` argument. An error will be raised | |||
|
243 | if both arguments are specified. | |||
|
244 | * The ``MINMATCH_MIN`` and ``MINMATCH_MAX`` constants were added. They are | |||
|
245 | semantically equivalent to the old ``SEARCHLENGTH_MIN`` and | |||
|
246 | ``SEARCHLENGTH_MAX`` constants. | |||
|
247 | * Bundled zstandard library upgraded from 1.3.7 to 1.3.8. | |||
|
248 | * ``setup.py`` denotes support for Python 3.7 (Python 3.7 was supported and | |||
|
249 | tested in the 0.10 release). | |||
|
250 | * ``zstd_cffi`` module has been renamed to ``zstandard.cffi``. | |||
|
251 | * ``ZstdCompressor.stream_writer()`` now reuses a buffer in order to avoid | |||
|
252 | allocating a new buffer for every operation. This should result in faster | |||
|
253 | performance in cases where ``write()`` or ``flush()`` are being called | |||
|
254 | frequently. (#62) | |||
|
255 | * Bundled zstandard library upgraded from 1.3.6 to 1.3.7. | |||
|
256 | ||||
|
257 | 0.10.2 (released 2018-11-03) | |||
|
258 | ============================ | |||
|
259 | ||||
|
260 | Bug Fixes | |||
|
261 | --------- | |||
|
262 | ||||
|
263 | * ``zstd_cffi.py`` added to ``setup.py`` (#60). | |||
|
264 | ||||
|
265 | Changes | |||
|
266 | ------- | |||
|
267 | ||||
|
268 | * Change some integer casts to avoid ``ssize_t`` (#61). | |||
|
269 | ||||
54 | 0.10.1 (released 2018-10-08) |
|
270 | 0.10.1 (released 2018-10-08) | |
55 | ============================ |
|
271 | ============================ | |
56 |
|
272 |
@@ -20,9 +20,9 b' https://github.com/indygreg/python-zstan' | |||||
20 | Requirements |
|
20 | Requirements | |
21 | ============ |
|
21 | ============ | |
22 |
|
22 | |||
23 |
This extension is designed to run with Python 2.7, 3.4, 3.5, and 3. |
|
23 | This extension is designed to run with Python 2.7, 3.4, 3.5, 3.6, and 3.7 | |
24 |
on common platforms (Linux, Windows, and OS X). |
|
24 | on common platforms (Linux, Windows, and OS X). On PyPy (both PyPy2 and PyPy3) we support version 6.0.0 and above. | |
25 | on Windows. Only x86_64 is well-tested on Linux and macOS. |
|
25 | x86 and x86_64 are well-tested on Windows. Only x86_64 is well-tested on Linux and macOS. | |
26 |
|
26 | |||
27 | Installing |
|
27 | Installing | |
28 | ========== |
|
28 | ========== | |
@@ -215,7 +215,7 b' Instances can also be used as context ma' | |||||
215 |
|
215 | |||
216 | # Do something with compressed chunk. |
|
216 | # Do something with compressed chunk. | |
217 |
|
217 | |||
218 |
When the context manager exi |
|
218 | When the context manager exits or ``close()`` is called, the stream is closed, | |
219 | underlying resources are released, and future operations against the compression |
|
219 | underlying resources are released, and future operations against the compression | |
220 | stream will fail. |
|
220 | stream will fail. | |
221 |
|
221 | |||
@@ -251,8 +251,54 b' emitted so far.' | |||||
251 | Streaming Input API |
|
251 | Streaming Input API | |
252 | ^^^^^^^^^^^^^^^^^^^ |
|
252 | ^^^^^^^^^^^^^^^^^^^ | |
253 |
|
253 | |||
254 | ``stream_writer(fh)`` (which behaves as a context manager) allows you to *stream* |
|
254 | ``stream_writer(fh)`` allows you to *stream* data into a compressor. | |
255 | data into a compressor.:: |
|
255 | ||
|
256 | Returned instances implement the ``io.RawIOBase`` interface. Only methods | |||
|
257 | that involve writing will do useful things. | |||
|
258 | ||||
|
259 | The argument to ``stream_writer()`` must have a ``write(data)`` method. As | |||
|
260 | compressed data is available, ``write()`` will be called with the compressed | |||
|
261 | data as its argument. Many common Python types implement ``write()``, including | |||
|
262 | open file handles and ``io.BytesIO``. | |||
|
263 | ||||
|
264 | The ``write(data)`` method is used to feed data into the compressor. | |||
|
265 | ||||
|
266 | The ``flush([flush_mode=FLUSH_BLOCK])`` method can be called to evict whatever | |||
|
267 | data remains within the compressor's internal state into the output object. This | |||
|
268 | may result in 0 or more ``write()`` calls to the output object. This method | |||
|
269 | accepts an optional ``flush_mode`` argument to control the flushing behavior. | |||
|
270 | Its value can be any of the ``FLUSH_*`` constants. | |||
|
271 | ||||
|
272 | Both ``write()`` and ``flush()`` return the number of bytes written to the | |||
|
273 | object's ``write()``. In many cases, small inputs do not accumulate enough | |||
|
274 | data to cause a write and ``write()`` will return ``0``. | |||
|
275 | ||||
|
276 | Calling ``close()`` will mark the stream as closed and subsequent I/O | |||
|
277 | operations will raise ``ValueError`` (per the documented behavior of | |||
|
278 | ``io.RawIOBase``). ``close()`` will also call ``close()`` on the underlying | |||
|
279 | stream if such a method exists. | |||
|
280 | ||||
|
281 | Typically usage is as follows:: | |||
|
282 | ||||
|
283 | cctx = zstd.ZstdCompressor(level=10) | |||
|
284 | compressor = cctx.stream_writer(fh) | |||
|
285 | ||||
|
286 | compressor.write(b'chunk 0\n') | |||
|
287 | compressor.write(b'chunk 1\n') | |||
|
288 | compressor.flush() | |||
|
289 | # Receiver will be able to decode ``chunk 0\nchunk 1\n`` at this point. | |||
|
290 | # Receiver is also expecting more data in the zstd *frame*. | |||
|
291 | ||||
|
292 | compressor.write(b'chunk 2\n') | |||
|
293 | compressor.flush(zstd.FLUSH_FRAME) | |||
|
294 | # Receiver will be able to decode ``chunk 0\nchunk 1\nchunk 2``. | |||
|
295 | # Receiver is expecting no more data, as the zstd frame is closed. | |||
|
296 | # Any future calls to ``write()`` at this point will construct a new | |||
|
297 | # zstd frame. | |||
|
298 | ||||
|
299 | Instances can be used as context managers. Exiting the context manager is | |||
|
300 | the equivalent of calling ``close()``, which is equivalent to calling | |||
|
301 | ``flush(zstd.FLUSH_FRAME)``:: | |||
256 |
|
302 | |||
257 | cctx = zstd.ZstdCompressor(level=10) |
|
303 | cctx = zstd.ZstdCompressor(level=10) | |
258 | with cctx.stream_writer(fh) as compressor: |
|
304 | with cctx.stream_writer(fh) as compressor: | |
@@ -260,22 +306,12 b' data into a compressor.::' | |||||
260 | compressor.write(b'chunk 1') |
|
306 | compressor.write(b'chunk 1') | |
261 | ... |
|
307 | ... | |
262 |
|
308 | |||
263 | The argument to ``stream_writer()`` must have a ``write(data)`` method. As |
|
309 | .. important:: | |
264 | compressed data is available, ``write()`` will be called with the compressed |
|
|||
265 | data as its argument. Many common Python types implement ``write()``, including |
|
|||
266 | open file handles and ``io.BytesIO``. |
|
|||
267 |
|
310 | |||
268 | ``stream_writer()`` returns an object representing a streaming compressor |
|
311 | If ``flush(FLUSH_FRAME)`` is not called, emitted data doesn't constitute | |
269 | instance. It **must** be used as a context manager. That object's |
|
312 | a full zstd *frame* and consumers of this data may complain about malformed | |
270 | ``write(data)`` method is used to feed data into the compressor. |
|
313 | input. It is recommended to use instances as a context manager to ensure | |
271 |
|
314 | *frames* are properly finished. | ||
272 | A ``flush()`` method can be called to evict whatever data remains within the |
|
|||
273 | compressor's internal state into the output object. This may result in 0 or |
|
|||
274 | more ``write()`` calls to the output object. |
|
|||
275 |
|
||||
276 | Both ``write()`` and ``flush()`` return the number of bytes written to the |
|
|||
277 | object's ``write()``. In many cases, small inputs do not accumulate enough |
|
|||
278 | data to cause a write and ``write()`` will return ``0``. |
|
|||
279 |
|
315 | |||
280 | If the size of the data being fed to this streaming compressor is known, |
|
316 | If the size of the data being fed to this streaming compressor is known, | |
281 | you can declare it before compression begins:: |
|
317 | you can declare it before compression begins:: | |
@@ -310,6 +346,14 b' Thte total number of bytes written so fa' | |||||
310 | ... |
|
346 | ... | |
311 | total_written = compressor.tell() |
|
347 | total_written = compressor.tell() | |
312 |
|
348 | |||
|
349 | ``stream_writer()`` accepts a ``write_return_read`` boolean argument to control | |||
|
350 | the return value of ``write()``. When ``False`` (the default), ``write()`` returns | |||
|
351 | the number of bytes that were ``write()``en to the underlying object. When | |||
|
352 | ``True``, ``write()`` returns the number of bytes read from the input that | |||
|
353 | were subsequently written to the compressor. ``True`` is the *proper* behavior | |||
|
354 | for ``write()`` as specified by the ``io.RawIOBase`` interface and will become | |||
|
355 | the default value in a future release. | |||
|
356 | ||||
313 | Streaming Output API |
|
357 | Streaming Output API | |
314 | ^^^^^^^^^^^^^^^^^^^^ |
|
358 | ^^^^^^^^^^^^^^^^^^^^ | |
315 |
|
359 | |||
@@ -654,27 +698,63 b' will raise ``ValueError`` if attempted.' | |||||
654 | ``tell()`` returns the number of decompressed bytes read so far. |
|
698 | ``tell()`` returns the number of decompressed bytes read so far. | |
655 |
|
699 | |||
656 | Not all I/O methods are implemented. Notably missing is support for |
|
700 | Not all I/O methods are implemented. Notably missing is support for | |
657 |
``readline()``, ``readlines()``, and linewise iteration support. |
|
701 | ``readline()``, ``readlines()``, and linewise iteration support. This is | |
658 | these is planned for a future release. |
|
702 | because streams operate on binary data - not text data. If you want to | |
|
703 | convert decompressed output to text, you can chain an ``io.TextIOWrapper`` | |||
|
704 | to the stream:: | |||
|
705 | ||||
|
706 | with open(path, 'rb') as fh: | |||
|
707 | dctx = zstd.ZstdDecompressor() | |||
|
708 | stream_reader = dctx.stream_reader(fh) | |||
|
709 | text_stream = io.TextIOWrapper(stream_reader, encoding='utf-8') | |||
|
710 | ||||
|
711 | for line in text_stream: | |||
|
712 | ... | |||
|
713 | ||||
|
714 | The ``read_across_frames`` argument to ``stream_reader()`` controls the | |||
|
715 | behavior of read operations when the end of a zstd *frame* is encountered. | |||
|
716 | When ``False`` (the default), a read will complete when the end of a | |||
|
717 | zstd *frame* is encountered. When ``True``, a read can potentially | |||
|
718 | return data spanning multiple zstd *frames*. | |||
659 |
|
719 | |||
660 | Streaming Input API |
|
720 | Streaming Input API | |
661 | ^^^^^^^^^^^^^^^^^^^ |
|
721 | ^^^^^^^^^^^^^^^^^^^ | |
662 |
|
722 | |||
663 | ``stream_writer(fh)`` can be used to incrementally send compressed data to a |
|
723 | ``stream_writer(fh)`` allows you to *stream* data into a decompressor. | |
664 | decompressor.:: |
|
724 | ||
|
725 | Returned instances implement the ``io.RawIOBase`` interface. Only methods | |||
|
726 | that involve writing will do useful things. | |||
|
727 | ||||
|
728 | The argument to ``stream_writer()`` is typically an object that also implements | |||
|
729 | ``io.RawIOBase``. But any object with a ``write(data)`` method will work. Many | |||
|
730 | common Python types conform to this interface, including open file handles | |||
|
731 | and ``io.BytesIO``. | |||
|
732 | ||||
|
733 | Behavior is similar to ``ZstdCompressor.stream_writer()``: compressed data | |||
|
734 | is sent to the decompressor by calling ``write(data)`` and decompressed | |||
|
735 | output is written to the underlying stream by calling its ``write(data)`` | |||
|
736 | method.:: | |||
665 |
|
737 | |||
666 | dctx = zstd.ZstdDecompressor() |
|
738 | dctx = zstd.ZstdDecompressor() | |
667 |
|
|
739 | decompressor = dctx.stream_writer(fh) | |
668 | decompressor.write(compressed_data) |
|
|||
669 |
|
740 | |||
670 | This behaves similarly to ``zstd.ZstdCompressor``: compressed data is written to |
|
741 | decompressor.write(compressed_data) | |
671 | the decompressor by calling ``write(data)`` and decompressed output is written |
|
742 | ... | |
672 | to the output object by calling its ``write(data)`` method. |
|
743 | ||
673 |
|
|
744 | ||
674 | Calls to ``write()`` will return the number of bytes written to the output |
|
745 | Calls to ``write()`` will return the number of bytes written to the output | |
675 | object. Not all inputs will result in bytes being written, so return values |
|
746 | object. Not all inputs will result in bytes being written, so return values | |
676 | of ``0`` are possible. |
|
747 | of ``0`` are possible. | |
677 |
|
748 | |||
|
749 | Like the ``stream_writer()`` compressor, instances can be used as context | |||
|
750 | managers. However, context managers add no extra special behavior and offer | |||
|
751 | little to no benefit to being used. | |||
|
752 | ||||
|
753 | Calling ``close()`` will mark the stream as closed and subsequent I/O operations | |||
|
754 | will raise ``ValueError`` (per the documented behavior of ``io.RawIOBase``). | |||
|
755 | ``close()`` will also call ``close()`` on the underlying stream if such a | |||
|
756 | method exists. | |||
|
757 | ||||
678 | The size of chunks being ``write()`` to the destination can be specified:: |
|
758 | The size of chunks being ``write()`` to the destination can be specified:: | |
679 |
|
759 | |||
680 | dctx = zstd.ZstdDecompressor() |
|
760 | dctx = zstd.ZstdDecompressor() | |
@@ -687,6 +767,13 b' You can see how much memory is being use' | |||||
687 | with dctx.stream_writer(fh) as decompressor: |
|
767 | with dctx.stream_writer(fh) as decompressor: | |
688 | byte_size = decompressor.memory_size() |
|
768 | byte_size = decompressor.memory_size() | |
689 |
|
769 | |||
|
770 | ``stream_writer()`` accepts a ``write_return_read`` boolean argument to control | |||
|
771 | the return value of ``write()``. When ``False`` (the default)``, ``write()`` | |||
|
772 | returns the number of bytes that were ``write()``en to the underlying stream. | |||
|
773 | When ``True``, ``write()`` returns the number of bytes read from the input. | |||
|
774 | ``True`` is the *proper* behavior for ``write()`` as specified by the | |||
|
775 | ``io.RawIOBase`` interface and will become the default in a future release. | |||
|
776 | ||||
690 | Streaming Output API |
|
777 | Streaming Output API | |
691 | ^^^^^^^^^^^^^^^^^^^^ |
|
778 | ^^^^^^^^^^^^^^^^^^^^ | |
692 |
|
779 | |||
@@ -791,6 +878,10 b' these temporary chunks by passing ``writ' | |||||
791 | memory (re)allocations, this streaming decompression API isn't as |
|
878 | memory (re)allocations, this streaming decompression API isn't as | |
792 | efficient as other APIs. |
|
879 | efficient as other APIs. | |
793 |
|
880 | |||
|
881 | For compatibility with the standard library APIs, instances expose a | |||
|
882 | ``flush([length=None])`` method. This method no-ops and has no meaningful | |||
|
883 | side-effects, making it safe to call any time. | |||
|
884 | ||||
794 | Batch Decompression API |
|
885 | Batch Decompression API | |
795 | ^^^^^^^^^^^^^^^^^^^^^^^ |
|
886 | ^^^^^^^^^^^^^^^^^^^^^^^ | |
796 |
|
887 | |||
@@ -1147,18 +1238,21 b' follows:' | |||||
1147 | * search_log |
|
1238 | * search_log | |
1148 | * min_match |
|
1239 | * min_match | |
1149 | * target_length |
|
1240 | * target_length | |
1150 |
* |
|
1241 | * strategy | |
|
1242 | * compression_strategy (deprecated: same as ``strategy``) | |||
1151 | * write_content_size |
|
1243 | * write_content_size | |
1152 | * write_checksum |
|
1244 | * write_checksum | |
1153 | * write_dict_id |
|
1245 | * write_dict_id | |
1154 | * job_size |
|
1246 | * job_size | |
1155 |
* overlap_ |
|
1247 | * overlap_log | |
|
1248 | * overlap_size_log (deprecated: same as ``overlap_log``) | |||
1156 | * force_max_window |
|
1249 | * force_max_window | |
1157 | * enable_ldm |
|
1250 | * enable_ldm | |
1158 | * ldm_hash_log |
|
1251 | * ldm_hash_log | |
1159 | * ldm_min_match |
|
1252 | * ldm_min_match | |
1160 | * ldm_bucket_size_log |
|
1253 | * ldm_bucket_size_log | |
1161 |
* ldm_hash_e |
|
1254 | * ldm_hash_rate_log | |
|
1255 | * ldm_hash_every_log (deprecated: same as ``ldm_hash_rate_log``) | |||
1162 | * threads |
|
1256 | * threads | |
1163 |
|
1257 | |||
1164 | Some of these are very low-level settings. It may help to consult the official |
|
1258 | Some of these are very low-level settings. It may help to consult the official | |
@@ -1240,6 +1334,13 b' FRAME_HEADER' | |||||
1240 | MAGIC_NUMBER |
|
1334 | MAGIC_NUMBER | |
1241 | Frame header as an integer |
|
1335 | Frame header as an integer | |
1242 |
|
1336 | |||
|
1337 | FLUSH_BLOCK | |||
|
1338 | Flushing behavior that denotes to flush a zstd block. A decompressor will | |||
|
1339 | be able to decode all data fed into the compressor so far. | |||
|
1340 | FLUSH_FRAME | |||
|
1341 | Flushing behavior that denotes to end a zstd frame. Any new data fed | |||
|
1342 | to the compressor will start a new frame. | |||
|
1343 | ||||
1243 | CONTENTSIZE_UNKNOWN |
|
1344 | CONTENTSIZE_UNKNOWN | |
1244 | Value for content size when the content size is unknown. |
|
1345 | Value for content size when the content size is unknown. | |
1245 | CONTENTSIZE_ERROR |
|
1346 | CONTENTSIZE_ERROR | |
@@ -1261,10 +1362,18 b' SEARCHLOG_MIN' | |||||
1261 | Minimum value for compression parameter |
|
1362 | Minimum value for compression parameter | |
1262 | SEARCHLOG_MAX |
|
1363 | SEARCHLOG_MAX | |
1263 | Maximum value for compression parameter |
|
1364 | Maximum value for compression parameter | |
|
1365 | MINMATCH_MIN | |||
|
1366 | Minimum value for compression parameter | |||
|
1367 | MINMATCH_MAX | |||
|
1368 | Maximum value for compression parameter | |||
1264 | SEARCHLENGTH_MIN |
|
1369 | SEARCHLENGTH_MIN | |
1265 | Minimum value for compression parameter |
|
1370 | Minimum value for compression parameter | |
|
1371 | ||||
|
1372 | Deprecated: use ``MINMATCH_MIN`` | |||
1266 | SEARCHLENGTH_MAX |
|
1373 | SEARCHLENGTH_MAX | |
1267 | Maximum value for compression parameter |
|
1374 | Maximum value for compression parameter | |
|
1375 | ||||
|
1376 | Deprecated: use ``MINMATCH_MAX`` | |||
1268 | TARGETLENGTH_MIN |
|
1377 | TARGETLENGTH_MIN | |
1269 | Minimum value for compression parameter |
|
1378 | Minimum value for compression parameter | |
1270 | STRATEGY_FAST |
|
1379 | STRATEGY_FAST | |
@@ -1283,6 +1392,8 b' STRATEGY_BTOPT' | |||||
1283 | Compression strategy |
|
1392 | Compression strategy | |
1284 | STRATEGY_BTULTRA |
|
1393 | STRATEGY_BTULTRA | |
1285 | Compression strategy |
|
1394 | Compression strategy | |
|
1395 | STRATEGY_BTULTRA2 | |||
|
1396 | Compression strategy | |||
1286 |
|
1397 | |||
1287 | FORMAT_ZSTD1 |
|
1398 | FORMAT_ZSTD1 | |
1288 | Zstandard frame format |
|
1399 | Zstandard frame format |
@@ -43,7 +43,7 b' static PyObject* ZstdCompressionChunkerI' | |||||
43 | /* If we have data left in the input, consume it. */ |
|
43 | /* If we have data left in the input, consume it. */ | |
44 | while (chunker->input.pos < chunker->input.size) { |
|
44 | while (chunker->input.pos < chunker->input.size) { | |
45 | Py_BEGIN_ALLOW_THREADS |
|
45 | Py_BEGIN_ALLOW_THREADS | |
46 |
zresult = ZSTD_compress |
|
46 | zresult = ZSTD_compressStream2(chunker->compressor->cctx, &chunker->output, | |
47 | &chunker->input, ZSTD_e_continue); |
|
47 | &chunker->input, ZSTD_e_continue); | |
48 | Py_END_ALLOW_THREADS |
|
48 | Py_END_ALLOW_THREADS | |
49 |
|
49 | |||
@@ -104,7 +104,7 b' static PyObject* ZstdCompressionChunkerI' | |||||
104 | } |
|
104 | } | |
105 |
|
105 | |||
106 | Py_BEGIN_ALLOW_THREADS |
|
106 | Py_BEGIN_ALLOW_THREADS | |
107 |
zresult = ZSTD_compress |
|
107 | zresult = ZSTD_compressStream2(chunker->compressor->cctx, &chunker->output, | |
108 | &chunker->input, zFlushMode); |
|
108 | &chunker->input, zFlushMode); | |
109 | Py_END_ALLOW_THREADS |
|
109 | Py_END_ALLOW_THREADS | |
110 |
|
110 |
@@ -298,13 +298,9 b' static PyObject* ZstdCompressionDict_pre' | |||||
298 | cParams = ZSTD_getCParams(level, 0, self->dictSize); |
|
298 | cParams = ZSTD_getCParams(level, 0, self->dictSize); | |
299 | } |
|
299 | } | |
300 | else { |
|
300 | else { | |
301 | cParams.chainLog = compressionParams->chainLog; |
|
301 | if (to_cparams(compressionParams, &cParams)) { | |
302 | cParams.hashLog = compressionParams->hashLog; |
|
302 | return NULL; | |
303 | cParams.searchLength = compressionParams->minMatch; |
|
303 | } | |
304 | cParams.searchLog = compressionParams->searchLog; |
|
|||
305 | cParams.strategy = compressionParams->compressionStrategy; |
|
|||
306 | cParams.targetLength = compressionParams->targetLength; |
|
|||
307 | cParams.windowLog = compressionParams->windowLog; |
|
|||
308 | } |
|
304 | } | |
309 |
|
305 | |||
310 | assert(!self->cdict); |
|
306 | assert(!self->cdict); |
@@ -10,7 +10,7 b'' | |||||
10 |
|
10 | |||
11 | extern PyObject* ZstdError; |
|
11 | extern PyObject* ZstdError; | |
12 |
|
12 | |||
13 |
int set_parameter(ZSTD_CCtx_params* params, ZSTD_cParameter param, |
|
13 | int set_parameter(ZSTD_CCtx_params* params, ZSTD_cParameter param, int value) { | |
14 | size_t zresult = ZSTD_CCtxParam_setParameter(params, param, value); |
|
14 | size_t zresult = ZSTD_CCtxParam_setParameter(params, param, value); | |
15 | if (ZSTD_isError(zresult)) { |
|
15 | if (ZSTD_isError(zresult)) { | |
16 | PyErr_Format(ZstdError, "unable to set compression context parameter: %s", |
|
16 | PyErr_Format(ZstdError, "unable to set compression context parameter: %s", | |
@@ -23,28 +23,41 b' int set_parameter(ZSTD_CCtx_params* para' | |||||
23 |
|
23 | |||
24 | #define TRY_SET_PARAMETER(params, param, value) if (set_parameter(params, param, value)) return -1; |
|
24 | #define TRY_SET_PARAMETER(params, param, value) if (set_parameter(params, param, value)) return -1; | |
25 |
|
25 | |||
|
26 | #define TRY_COPY_PARAMETER(source, dest, param) { \ | |||
|
27 | int result; \ | |||
|
28 | size_t zresult = ZSTD_CCtxParam_getParameter(source, param, &result); \ | |||
|
29 | if (ZSTD_isError(zresult)) { \ | |||
|
30 | return 1; \ | |||
|
31 | } \ | |||
|
32 | zresult = ZSTD_CCtxParam_setParameter(dest, param, result); \ | |||
|
33 | if (ZSTD_isError(zresult)) { \ | |||
|
34 | return 1; \ | |||
|
35 | } \ | |||
|
36 | } | |||
|
37 | ||||
26 | int set_parameters(ZSTD_CCtx_params* params, ZstdCompressionParametersObject* obj) { |
|
38 | int set_parameters(ZSTD_CCtx_params* params, ZstdCompressionParametersObject* obj) { | |
27 |
TRY_ |
|
39 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_nbWorkers); | |
28 | TRY_SET_PARAMETER(params, ZSTD_p_compressionLevel, (unsigned)obj->compressionLevel); |
|
40 | ||
29 | TRY_SET_PARAMETER(params, ZSTD_p_windowLog, obj->windowLog); |
|
41 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_format); | |
30 | TRY_SET_PARAMETER(params, ZSTD_p_hashLog, obj->hashLog); |
|
42 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_compressionLevel); | |
31 |
TRY_ |
|
43 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_windowLog); | |
32 |
TRY_ |
|
44 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_hashLog); | |
33 | TRY_SET_PARAMETER(params, ZSTD_p_minMatch, obj->minMatch); |
|
45 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_chainLog); | |
34 | TRY_SET_PARAMETER(params, ZSTD_p_targetLength, obj->targetLength); |
|
46 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_searchLog); | |
35 | TRY_SET_PARAMETER(params, ZSTD_p_compressionStrategy, obj->compressionStrategy); |
|
47 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_minMatch); | |
36 | TRY_SET_PARAMETER(params, ZSTD_p_contentSizeFlag, obj->contentSizeFlag); |
|
48 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_targetLength); | |
37 | TRY_SET_PARAMETER(params, ZSTD_p_checksumFlag, obj->checksumFlag); |
|
49 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_strategy); | |
38 | TRY_SET_PARAMETER(params, ZSTD_p_dictIDFlag, obj->dictIDFlag); |
|
50 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_contentSizeFlag); | |
39 | TRY_SET_PARAMETER(params, ZSTD_p_nbWorkers, obj->threads); |
|
51 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_checksumFlag); | |
40 | TRY_SET_PARAMETER(params, ZSTD_p_jobSize, obj->jobSize); |
|
52 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_dictIDFlag); | |
41 | TRY_SET_PARAMETER(params, ZSTD_p_overlapSizeLog, obj->overlapSizeLog); |
|
53 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_jobSize); | |
42 | TRY_SET_PARAMETER(params, ZSTD_p_forceMaxWindow, obj->forceMaxWindow); |
|
54 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_overlapLog); | |
43 | TRY_SET_PARAMETER(params, ZSTD_p_enableLongDistanceMatching, obj->enableLongDistanceMatching); |
|
55 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_forceMaxWindow); | |
44 | TRY_SET_PARAMETER(params, ZSTD_p_ldmHashLog, obj->ldmHashLog); |
|
56 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_enableLongDistanceMatching); | |
45 |
TRY_ |
|
57 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_ldmHashLog); | |
46 | TRY_SET_PARAMETER(params, ZSTD_p_ldmBucketSizeLog, obj->ldmBucketSizeLog); |
|
58 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_ldmMinMatch); | |
47 | TRY_SET_PARAMETER(params, ZSTD_p_ldmHashEveryLog, obj->ldmHashEveryLog); |
|
59 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_ldmBucketSizeLog); | |
|
60 | TRY_COPY_PARAMETER(obj->params, params, ZSTD_c_ldmHashRateLog); | |||
48 |
|
61 | |||
49 | return 0; |
|
62 | return 0; | |
50 | } |
|
63 | } | |
@@ -64,6 +77,41 b' int reset_params(ZstdCompressionParamete' | |||||
64 | return set_parameters(params->params, params); |
|
77 | return set_parameters(params->params, params); | |
65 | } |
|
78 | } | |
66 |
|
79 | |||
|
80 | #define TRY_GET_PARAMETER(params, param, value) { \ | |||
|
81 | size_t zresult = ZSTD_CCtxParam_getParameter(params, param, value); \ | |||
|
82 | if (ZSTD_isError(zresult)) { \ | |||
|
83 | PyErr_Format(ZstdError, "unable to retrieve parameter: %s", ZSTD_getErrorName(zresult)); \ | |||
|
84 | return 1; \ | |||
|
85 | } \ | |||
|
86 | } | |||
|
87 | ||||
|
88 | int to_cparams(ZstdCompressionParametersObject* params, ZSTD_compressionParameters* cparams) { | |||
|
89 | int value; | |||
|
90 | ||||
|
91 | TRY_GET_PARAMETER(params->params, ZSTD_c_windowLog, &value); | |||
|
92 | cparams->windowLog = value; | |||
|
93 | ||||
|
94 | TRY_GET_PARAMETER(params->params, ZSTD_c_chainLog, &value); | |||
|
95 | cparams->chainLog = value; | |||
|
96 | ||||
|
97 | TRY_GET_PARAMETER(params->params, ZSTD_c_hashLog, &value); | |||
|
98 | cparams->hashLog = value; | |||
|
99 | ||||
|
100 | TRY_GET_PARAMETER(params->params, ZSTD_c_searchLog, &value); | |||
|
101 | cparams->searchLog = value; | |||
|
102 | ||||
|
103 | TRY_GET_PARAMETER(params->params, ZSTD_c_minMatch, &value); | |||
|
104 | cparams->minMatch = value; | |||
|
105 | ||||
|
106 | TRY_GET_PARAMETER(params->params, ZSTD_c_targetLength, &value); | |||
|
107 | cparams->targetLength = value; | |||
|
108 | ||||
|
109 | TRY_GET_PARAMETER(params->params, ZSTD_c_strategy, &value); | |||
|
110 | cparams->strategy = value; | |||
|
111 | ||||
|
112 | return 0; | |||
|
113 | } | |||
|
114 | ||||
67 | static int ZstdCompressionParameters_init(ZstdCompressionParametersObject* self, PyObject* args, PyObject* kwargs) { |
|
115 | static int ZstdCompressionParameters_init(ZstdCompressionParametersObject* self, PyObject* args, PyObject* kwargs) { | |
68 | static char* kwlist[] = { |
|
116 | static char* kwlist[] = { | |
69 | "format", |
|
117 | "format", | |
@@ -75,50 +123,60 b' static int ZstdCompressionParameters_ini' | |||||
75 | "min_match", |
|
123 | "min_match", | |
76 | "target_length", |
|
124 | "target_length", | |
77 | "compression_strategy", |
|
125 | "compression_strategy", | |
|
126 | "strategy", | |||
78 | "write_content_size", |
|
127 | "write_content_size", | |
79 | "write_checksum", |
|
128 | "write_checksum", | |
80 | "write_dict_id", |
|
129 | "write_dict_id", | |
81 | "job_size", |
|
130 | "job_size", | |
|
131 | "overlap_log", | |||
82 | "overlap_size_log", |
|
132 | "overlap_size_log", | |
83 | "force_max_window", |
|
133 | "force_max_window", | |
84 | "enable_ldm", |
|
134 | "enable_ldm", | |
85 | "ldm_hash_log", |
|
135 | "ldm_hash_log", | |
86 | "ldm_min_match", |
|
136 | "ldm_min_match", | |
87 | "ldm_bucket_size_log", |
|
137 | "ldm_bucket_size_log", | |
|
138 | "ldm_hash_rate_log", | |||
88 | "ldm_hash_every_log", |
|
139 | "ldm_hash_every_log", | |
89 | "threads", |
|
140 | "threads", | |
90 | NULL |
|
141 | NULL | |
91 | }; |
|
142 | }; | |
92 |
|
143 | |||
93 |
|
|
144 | int format = 0; | |
94 | int compressionLevel = 0; |
|
145 | int compressionLevel = 0; | |
95 |
|
|
146 | int windowLog = 0; | |
96 |
|
|
147 | int hashLog = 0; | |
97 |
|
|
148 | int chainLog = 0; | |
98 |
|
|
149 | int searchLog = 0; | |
99 |
|
|
150 | int minMatch = 0; | |
100 |
|
|
151 | int targetLength = 0; | |
101 |
|
|
152 | int compressionStrategy = -1; | |
102 | unsigned contentSizeFlag = 1; |
|
153 | int strategy = -1; | |
103 | unsigned checksumFlag = 0; |
|
154 | int contentSizeFlag = 1; | |
104 | unsigned dictIDFlag = 0; |
|
155 | int checksumFlag = 0; | |
105 | unsigned jobSize = 0; |
|
156 | int dictIDFlag = 0; | |
106 | unsigned overlapSizeLog = 0; |
|
157 | int jobSize = 0; | |
107 | unsigned forceMaxWindow = 0; |
|
158 | int overlapLog = -1; | |
108 | unsigned enableLDM = 0; |
|
159 | int overlapSizeLog = -1; | |
109 | unsigned ldmHashLog = 0; |
|
160 | int forceMaxWindow = 0; | |
110 | unsigned ldmMinMatch = 0; |
|
161 | int enableLDM = 0; | |
111 | unsigned ldmBucketSizeLog = 0; |
|
162 | int ldmHashLog = 0; | |
112 | unsigned ldmHashEveryLog = 0; |
|
163 | int ldmMinMatch = 0; | |
|
164 | int ldmBucketSizeLog = 0; | |||
|
165 | int ldmHashRateLog = -1; | |||
|
166 | int ldmHashEveryLog = -1; | |||
113 | int threads = 0; |
|
167 | int threads = 0; | |
114 |
|
168 | |||
115 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, |
|
169 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, | |
116 | "|IiIIIIIIIIIIIIIIIIIIi:CompressionParameters", |
|
170 | "|iiiiiiiiiiiiiiiiiiiiiiii:CompressionParameters", | |
117 | kwlist, &format, &compressionLevel, &windowLog, &hashLog, &chainLog, |
|
171 | kwlist, &format, &compressionLevel, &windowLog, &hashLog, &chainLog, | |
118 | &searchLog, &minMatch, &targetLength, &compressionStrategy, |
|
172 | &searchLog, &minMatch, &targetLength, &compressionStrategy, &strategy, | |
119 |
&contentSizeFlag, &checksumFlag, &dictIDFlag, &jobSize, &overlap |
|
173 | &contentSizeFlag, &checksumFlag, &dictIDFlag, &jobSize, &overlapLog, | |
120 |
&forceMaxWindow, &enableLDM, &ldmHashLog, &ldmMinMatch, |
|
174 | &overlapSizeLog, &forceMaxWindow, &enableLDM, &ldmHashLog, &ldmMinMatch, | |
121 | &ldmHashEveryLog, &threads)) { |
|
175 | &ldmBucketSizeLog, &ldmHashRateLog, &ldmHashEveryLog, &threads)) { | |
|
176 | return -1; | |||
|
177 | } | |||
|
178 | ||||
|
179 | if (reset_params(self)) { | |||
122 | return -1; |
|
180 | return -1; | |
123 | } |
|
181 | } | |
124 |
|
182 | |||
@@ -126,32 +184,70 b' static int ZstdCompressionParameters_ini' | |||||
126 | threads = cpu_count(); |
|
184 | threads = cpu_count(); | |
127 | } |
|
185 | } | |
128 |
|
186 | |||
129 | self->format = format; |
|
187 | /* We need to set ZSTD_c_nbWorkers before ZSTD_c_jobSize and ZSTD_c_overlapLog | |
130 | self->compressionLevel = compressionLevel; |
|
188 | * because setting ZSTD_c_nbWorkers resets the other parameters. */ | |
131 | self->windowLog = windowLog; |
|
189 | TRY_SET_PARAMETER(self->params, ZSTD_c_nbWorkers, threads); | |
132 | self->hashLog = hashLog; |
|
190 | ||
133 | self->chainLog = chainLog; |
|
191 | TRY_SET_PARAMETER(self->params, ZSTD_c_format, format); | |
134 | self->searchLog = searchLog; |
|
192 | TRY_SET_PARAMETER(self->params, ZSTD_c_compressionLevel, compressionLevel); | |
135 | self->minMatch = minMatch; |
|
193 | TRY_SET_PARAMETER(self->params, ZSTD_c_windowLog, windowLog); | |
136 | self->targetLength = targetLength; |
|
194 | TRY_SET_PARAMETER(self->params, ZSTD_c_hashLog, hashLog); | |
137 | self->compressionStrategy = compressionStrategy; |
|
195 | TRY_SET_PARAMETER(self->params, ZSTD_c_chainLog, chainLog); | |
138 | self->contentSizeFlag = contentSizeFlag; |
|
196 | TRY_SET_PARAMETER(self->params, ZSTD_c_searchLog, searchLog); | |
139 | self->checksumFlag = checksumFlag; |
|
197 | TRY_SET_PARAMETER(self->params, ZSTD_c_minMatch, minMatch); | |
140 | self->dictIDFlag = dictIDFlag; |
|
198 | TRY_SET_PARAMETER(self->params, ZSTD_c_targetLength, targetLength); | |
141 | self->threads = threads; |
|
|||
142 | self->jobSize = jobSize; |
|
|||
143 | self->overlapSizeLog = overlapSizeLog; |
|
|||
144 | self->forceMaxWindow = forceMaxWindow; |
|
|||
145 | self->enableLongDistanceMatching = enableLDM; |
|
|||
146 | self->ldmHashLog = ldmHashLog; |
|
|||
147 | self->ldmMinMatch = ldmMinMatch; |
|
|||
148 | self->ldmBucketSizeLog = ldmBucketSizeLog; |
|
|||
149 | self->ldmHashEveryLog = ldmHashEveryLog; |
|
|||
150 |
|
199 | |||
151 | if (reset_params(self)) { |
|
200 | if (compressionStrategy != -1 && strategy != -1) { | |
|
201 | PyErr_SetString(PyExc_ValueError, "cannot specify both compression_strategy and strategy"); | |||
|
202 | return -1; | |||
|
203 | } | |||
|
204 | ||||
|
205 | if (compressionStrategy != -1) { | |||
|
206 | strategy = compressionStrategy; | |||
|
207 | } | |||
|
208 | else if (strategy == -1) { | |||
|
209 | strategy = 0; | |||
|
210 | } | |||
|
211 | ||||
|
212 | TRY_SET_PARAMETER(self->params, ZSTD_c_strategy, strategy); | |||
|
213 | TRY_SET_PARAMETER(self->params, ZSTD_c_contentSizeFlag, contentSizeFlag); | |||
|
214 | TRY_SET_PARAMETER(self->params, ZSTD_c_checksumFlag, checksumFlag); | |||
|
215 | TRY_SET_PARAMETER(self->params, ZSTD_c_dictIDFlag, dictIDFlag); | |||
|
216 | TRY_SET_PARAMETER(self->params, ZSTD_c_jobSize, jobSize); | |||
|
217 | ||||
|
218 | if (overlapLog != -1 && overlapSizeLog != -1) { | |||
|
219 | PyErr_SetString(PyExc_ValueError, "cannot specify both overlap_log and overlap_size_log"); | |||
152 | return -1; |
|
220 | return -1; | |
153 | } |
|
221 | } | |
154 |
|
222 | |||
|
223 | if (overlapSizeLog != -1) { | |||
|
224 | overlapLog = overlapSizeLog; | |||
|
225 | } | |||
|
226 | else if (overlapLog == -1) { | |||
|
227 | overlapLog = 0; | |||
|
228 | } | |||
|
229 | ||||
|
230 | TRY_SET_PARAMETER(self->params, ZSTD_c_overlapLog, overlapLog); | |||
|
231 | TRY_SET_PARAMETER(self->params, ZSTD_c_forceMaxWindow, forceMaxWindow); | |||
|
232 | TRY_SET_PARAMETER(self->params, ZSTD_c_enableLongDistanceMatching, enableLDM); | |||
|
233 | TRY_SET_PARAMETER(self->params, ZSTD_c_ldmHashLog, ldmHashLog); | |||
|
234 | TRY_SET_PARAMETER(self->params, ZSTD_c_ldmMinMatch, ldmMinMatch); | |||
|
235 | TRY_SET_PARAMETER(self->params, ZSTD_c_ldmBucketSizeLog, ldmBucketSizeLog); | |||
|
236 | ||||
|
237 | if (ldmHashRateLog != -1 && ldmHashEveryLog != -1) { | |||
|
238 | PyErr_SetString(PyExc_ValueError, "cannot specify both ldm_hash_rate_log and ldm_hash_everyLog"); | |||
|
239 | return -1; | |||
|
240 | } | |||
|
241 | ||||
|
242 | if (ldmHashEveryLog != -1) { | |||
|
243 | ldmHashRateLog = ldmHashEveryLog; | |||
|
244 | } | |||
|
245 | else if (ldmHashRateLog == -1) { | |||
|
246 | ldmHashRateLog = 0; | |||
|
247 | } | |||
|
248 | ||||
|
249 | TRY_SET_PARAMETER(self->params, ZSTD_c_ldmHashRateLog, ldmHashRateLog); | |||
|
250 | ||||
155 | return 0; |
|
251 | return 0; | |
156 | } |
|
252 | } | |
157 |
|
253 | |||
@@ -259,7 +355,7 b' ZstdCompressionParametersObject* Compres' | |||||
259 |
|
355 | |||
260 | val = PyDict_GetItemString(kwargs, "min_match"); |
|
356 | val = PyDict_GetItemString(kwargs, "min_match"); | |
261 | if (!val) { |
|
357 | if (!val) { | |
262 |
val = PyLong_FromUnsignedLong(params. |
|
358 | val = PyLong_FromUnsignedLong(params.minMatch); | |
263 | if (!val) { |
|
359 | if (!val) { | |
264 | goto cleanup; |
|
360 | goto cleanup; | |
265 | } |
|
361 | } | |
@@ -336,6 +432,41 b' static void ZstdCompressionParameters_de' | |||||
336 | PyObject_Del(self); |
|
432 | PyObject_Del(self); | |
337 | } |
|
433 | } | |
338 |
|
434 | |||
|
435 | #define PARAM_GETTER(name, param) PyObject* ZstdCompressionParameters_get_##name(PyObject* self, void* unused) { \ | |||
|
436 | int result; \ | |||
|
437 | size_t zresult; \ | |||
|
438 | ZstdCompressionParametersObject* p = (ZstdCompressionParametersObject*)(self); \ | |||
|
439 | zresult = ZSTD_CCtxParam_getParameter(p->params, param, &result); \ | |||
|
440 | if (ZSTD_isError(zresult)) { \ | |||
|
441 | PyErr_Format(ZstdError, "unable to get compression parameter: %s", \ | |||
|
442 | ZSTD_getErrorName(zresult)); \ | |||
|
443 | return NULL; \ | |||
|
444 | } \ | |||
|
445 | return PyLong_FromLong(result); \ | |||
|
446 | } | |||
|
447 | ||||
|
448 | PARAM_GETTER(format, ZSTD_c_format) | |||
|
449 | PARAM_GETTER(compression_level, ZSTD_c_compressionLevel) | |||
|
450 | PARAM_GETTER(window_log, ZSTD_c_windowLog) | |||
|
451 | PARAM_GETTER(hash_log, ZSTD_c_hashLog) | |||
|
452 | PARAM_GETTER(chain_log, ZSTD_c_chainLog) | |||
|
453 | PARAM_GETTER(search_log, ZSTD_c_searchLog) | |||
|
454 | PARAM_GETTER(min_match, ZSTD_c_minMatch) | |||
|
455 | PARAM_GETTER(target_length, ZSTD_c_targetLength) | |||
|
456 | PARAM_GETTER(compression_strategy, ZSTD_c_strategy) | |||
|
457 | PARAM_GETTER(write_content_size, ZSTD_c_contentSizeFlag) | |||
|
458 | PARAM_GETTER(write_checksum, ZSTD_c_checksumFlag) | |||
|
459 | PARAM_GETTER(write_dict_id, ZSTD_c_dictIDFlag) | |||
|
460 | PARAM_GETTER(job_size, ZSTD_c_jobSize) | |||
|
461 | PARAM_GETTER(overlap_log, ZSTD_c_overlapLog) | |||
|
462 | PARAM_GETTER(force_max_window, ZSTD_c_forceMaxWindow) | |||
|
463 | PARAM_GETTER(enable_ldm, ZSTD_c_enableLongDistanceMatching) | |||
|
464 | PARAM_GETTER(ldm_hash_log, ZSTD_c_ldmHashLog) | |||
|
465 | PARAM_GETTER(ldm_min_match, ZSTD_c_ldmMinMatch) | |||
|
466 | PARAM_GETTER(ldm_bucket_size_log, ZSTD_c_ldmBucketSizeLog) | |||
|
467 | PARAM_GETTER(ldm_hash_rate_log, ZSTD_c_ldmHashRateLog) | |||
|
468 | PARAM_GETTER(threads, ZSTD_c_nbWorkers) | |||
|
469 | ||||
339 | static PyMethodDef ZstdCompressionParameters_methods[] = { |
|
470 | static PyMethodDef ZstdCompressionParameters_methods[] = { | |
340 | { |
|
471 | { | |
341 | "from_level", |
|
472 | "from_level", | |
@@ -352,70 +483,34 b' static PyMethodDef ZstdCompressionParame' | |||||
352 | { NULL, NULL } |
|
483 | { NULL, NULL } | |
353 | }; |
|
484 | }; | |
354 |
|
485 | |||
355 | static PyMemberDef ZstdCompressionParameters_members[] = { |
|
486 | #define GET_SET_ENTRY(name) { #name, ZstdCompressionParameters_get_##name, NULL, NULL, NULL } | |
356 | { "format", T_UINT, |
|
487 | ||
357 | offsetof(ZstdCompressionParametersObject, format), READONLY, |
|
488 | static PyGetSetDef ZstdCompressionParameters_getset[] = { | |
358 | "compression format" }, |
|
489 | GET_SET_ENTRY(format), | |
359 | { "compression_level", T_INT, |
|
490 | GET_SET_ENTRY(compression_level), | |
360 | offsetof(ZstdCompressionParametersObject, compressionLevel), READONLY, |
|
491 | GET_SET_ENTRY(window_log), | |
361 | "compression level" }, |
|
492 | GET_SET_ENTRY(hash_log), | |
362 | { "window_log", T_UINT, |
|
493 | GET_SET_ENTRY(chain_log), | |
363 | offsetof(ZstdCompressionParametersObject, windowLog), READONLY, |
|
494 | GET_SET_ENTRY(search_log), | |
364 | "window log" }, |
|
495 | GET_SET_ENTRY(min_match), | |
365 | { "hash_log", T_UINT, |
|
496 | GET_SET_ENTRY(target_length), | |
366 | offsetof(ZstdCompressionParametersObject, hashLog), READONLY, |
|
497 | GET_SET_ENTRY(compression_strategy), | |
367 | "hash log" }, |
|
498 | GET_SET_ENTRY(write_content_size), | |
368 | { "chain_log", T_UINT, |
|
499 | GET_SET_ENTRY(write_checksum), | |
369 | offsetof(ZstdCompressionParametersObject, chainLog), READONLY, |
|
500 | GET_SET_ENTRY(write_dict_id), | |
370 | "chain log" }, |
|
501 | GET_SET_ENTRY(threads), | |
371 | { "search_log", T_UINT, |
|
502 | GET_SET_ENTRY(job_size), | |
372 | offsetof(ZstdCompressionParametersObject, searchLog), READONLY, |
|
503 | GET_SET_ENTRY(overlap_log), | |
373 | "search log" }, |
|
504 | /* TODO remove this deprecated attribute */ | |
374 | { "min_match", T_UINT, |
|
505 | { "overlap_size_log", ZstdCompressionParameters_get_overlap_log, NULL, NULL, NULL }, | |
375 | offsetof(ZstdCompressionParametersObject, minMatch), READONLY, |
|
506 | GET_SET_ENTRY(force_max_window), | |
376 | "search length" }, |
|
507 | GET_SET_ENTRY(enable_ldm), | |
377 | { "target_length", T_UINT, |
|
508 | GET_SET_ENTRY(ldm_hash_log), | |
378 | offsetof(ZstdCompressionParametersObject, targetLength), READONLY, |
|
509 | GET_SET_ENTRY(ldm_min_match), | |
379 | "target length" }, |
|
510 | GET_SET_ENTRY(ldm_bucket_size_log), | |
380 | { "compression_strategy", T_UINT, |
|
511 | GET_SET_ENTRY(ldm_hash_rate_log), | |
381 | offsetof(ZstdCompressionParametersObject, compressionStrategy), READONLY, |
|
512 | /* TODO remove this deprecated attribute */ | |
382 | "compression strategy" }, |
|
513 | { "ldm_hash_every_log", ZstdCompressionParameters_get_ldm_hash_rate_log, NULL, NULL, NULL }, | |
383 | { "write_content_size", T_UINT, |
|
|||
384 | offsetof(ZstdCompressionParametersObject, contentSizeFlag), READONLY, |
|
|||
385 | "whether to write content size in frames" }, |
|
|||
386 | { "write_checksum", T_UINT, |
|
|||
387 | offsetof(ZstdCompressionParametersObject, checksumFlag), READONLY, |
|
|||
388 | "whether to write checksum in frames" }, |
|
|||
389 | { "write_dict_id", T_UINT, |
|
|||
390 | offsetof(ZstdCompressionParametersObject, dictIDFlag), READONLY, |
|
|||
391 | "whether to write dictionary ID in frames" }, |
|
|||
392 | { "threads", T_UINT, |
|
|||
393 | offsetof(ZstdCompressionParametersObject, threads), READONLY, |
|
|||
394 | "number of threads to use" }, |
|
|||
395 | { "job_size", T_UINT, |
|
|||
396 | offsetof(ZstdCompressionParametersObject, jobSize), READONLY, |
|
|||
397 | "size of compression job when using multiple threads" }, |
|
|||
398 | { "overlap_size_log", T_UINT, |
|
|||
399 | offsetof(ZstdCompressionParametersObject, overlapSizeLog), READONLY, |
|
|||
400 | "Size of previous input reloaded at the beginning of each job" }, |
|
|||
401 | { "force_max_window", T_UINT, |
|
|||
402 | offsetof(ZstdCompressionParametersObject, forceMaxWindow), READONLY, |
|
|||
403 | "force back references to remain smaller than window size" }, |
|
|||
404 | { "enable_ldm", T_UINT, |
|
|||
405 | offsetof(ZstdCompressionParametersObject, enableLongDistanceMatching), READONLY, |
|
|||
406 | "whether to enable long distance matching" }, |
|
|||
407 | { "ldm_hash_log", T_UINT, |
|
|||
408 | offsetof(ZstdCompressionParametersObject, ldmHashLog), READONLY, |
|
|||
409 | "Size of the table for long distance matching, as a power of 2" }, |
|
|||
410 | { "ldm_min_match", T_UINT, |
|
|||
411 | offsetof(ZstdCompressionParametersObject, ldmMinMatch), READONLY, |
|
|||
412 | "minimum size of searched matches for long distance matcher" }, |
|
|||
413 | { "ldm_bucket_size_log", T_UINT, |
|
|||
414 | offsetof(ZstdCompressionParametersObject, ldmBucketSizeLog), READONLY, |
|
|||
415 | "log size of each bucket in the LDM hash table for collision resolution" }, |
|
|||
416 | { "ldm_hash_every_log", T_UINT, |
|
|||
417 | offsetof(ZstdCompressionParametersObject, ldmHashEveryLog), READONLY, |
|
|||
418 | "frequency of inserting/looking up entries in the LDM hash table" }, |
|
|||
419 | { NULL } |
|
514 | { NULL } | |
420 | }; |
|
515 | }; | |
421 |
|
516 | |||
@@ -448,8 +543,8 b' PyTypeObject ZstdCompressionParametersTy' | |||||
448 | 0, /* tp_iter */ |
|
543 | 0, /* tp_iter */ | |
449 | 0, /* tp_iternext */ |
|
544 | 0, /* tp_iternext */ | |
450 | ZstdCompressionParameters_methods, /* tp_methods */ |
|
545 | ZstdCompressionParameters_methods, /* tp_methods */ | |
451 | ZstdCompressionParameters_members, /* tp_members */ |
|
546 | 0, /* tp_members */ | |
452 | 0, /* tp_getset */ |
|
547 | ZstdCompressionParameters_getset, /* tp_getset */ | |
453 | 0, /* tp_base */ |
|
548 | 0, /* tp_base */ | |
454 | 0, /* tp_dict */ |
|
549 | 0, /* tp_dict */ | |
455 | 0, /* tp_descr_get */ |
|
550 | 0, /* tp_descr_get */ |
This diff has been collapsed as it changes many lines, (604 lines changed) Show them Hide them | |||||
@@ -128,6 +128,96 b' static PyObject* reader_tell(ZstdCompres' | |||||
128 | return PyLong_FromUnsignedLongLong(self->bytesCompressed); |
|
128 | return PyLong_FromUnsignedLongLong(self->bytesCompressed); | |
129 | } |
|
129 | } | |
130 |
|
130 | |||
|
131 | int read_compressor_input(ZstdCompressionReader* self) { | |||
|
132 | if (self->finishedInput) { | |||
|
133 | return 0; | |||
|
134 | } | |||
|
135 | ||||
|
136 | if (self->input.pos != self->input.size) { | |||
|
137 | return 0; | |||
|
138 | } | |||
|
139 | ||||
|
140 | if (self->reader) { | |||
|
141 | Py_buffer buffer; | |||
|
142 | ||||
|
143 | assert(self->readResult == NULL); | |||
|
144 | ||||
|
145 | self->readResult = PyObject_CallMethod(self->reader, "read", | |||
|
146 | "k", self->readSize); | |||
|
147 | ||||
|
148 | if (NULL == self->readResult) { | |||
|
149 | return -1; | |||
|
150 | } | |||
|
151 | ||||
|
152 | memset(&buffer, 0, sizeof(buffer)); | |||
|
153 | ||||
|
154 | if (0 != PyObject_GetBuffer(self->readResult, &buffer, PyBUF_CONTIG_RO)) { | |||
|
155 | return -1; | |||
|
156 | } | |||
|
157 | ||||
|
158 | /* EOF */ | |||
|
159 | if (0 == buffer.len) { | |||
|
160 | self->finishedInput = 1; | |||
|
161 | Py_CLEAR(self->readResult); | |||
|
162 | } | |||
|
163 | else { | |||
|
164 | self->input.src = buffer.buf; | |||
|
165 | self->input.size = buffer.len; | |||
|
166 | self->input.pos = 0; | |||
|
167 | } | |||
|
168 | ||||
|
169 | PyBuffer_Release(&buffer); | |||
|
170 | } | |||
|
171 | else { | |||
|
172 | assert(self->buffer.buf); | |||
|
173 | ||||
|
174 | self->input.src = self->buffer.buf; | |||
|
175 | self->input.size = self->buffer.len; | |||
|
176 | self->input.pos = 0; | |||
|
177 | } | |||
|
178 | ||||
|
179 | return 1; | |||
|
180 | } | |||
|
181 | ||||
|
182 | int compress_input(ZstdCompressionReader* self, ZSTD_outBuffer* output) { | |||
|
183 | size_t oldPos; | |||
|
184 | size_t zresult; | |||
|
185 | ||||
|
186 | /* If we have data left over, consume it. */ | |||
|
187 | if (self->input.pos < self->input.size) { | |||
|
188 | oldPos = output->pos; | |||
|
189 | ||||
|
190 | Py_BEGIN_ALLOW_THREADS | |||
|
191 | zresult = ZSTD_compressStream2(self->compressor->cctx, | |||
|
192 | output, &self->input, ZSTD_e_continue); | |||
|
193 | Py_END_ALLOW_THREADS | |||
|
194 | ||||
|
195 | self->bytesCompressed += output->pos - oldPos; | |||
|
196 | ||||
|
197 | /* Input exhausted. Clear out state tracking. */ | |||
|
198 | if (self->input.pos == self->input.size) { | |||
|
199 | memset(&self->input, 0, sizeof(self->input)); | |||
|
200 | Py_CLEAR(self->readResult); | |||
|
201 | ||||
|
202 | if (self->buffer.buf) { | |||
|
203 | self->finishedInput = 1; | |||
|
204 | } | |||
|
205 | } | |||
|
206 | ||||
|
207 | if (ZSTD_isError(zresult)) { | |||
|
208 | PyErr_Format(ZstdError, "zstd compress error: %s", ZSTD_getErrorName(zresult)); | |||
|
209 | return -1; | |||
|
210 | } | |||
|
211 | } | |||
|
212 | ||||
|
213 | if (output->pos && output->pos == output->size) { | |||
|
214 | return 1; | |||
|
215 | } | |||
|
216 | else { | |||
|
217 | return 0; | |||
|
218 | } | |||
|
219 | } | |||
|
220 | ||||
131 | static PyObject* reader_read(ZstdCompressionReader* self, PyObject* args, PyObject* kwargs) { |
|
221 | static PyObject* reader_read(ZstdCompressionReader* self, PyObject* args, PyObject* kwargs) { | |
132 | static char* kwlist[] = { |
|
222 | static char* kwlist[] = { | |
133 | "size", |
|
223 | "size", | |
@@ -140,25 +230,30 b' static PyObject* reader_read(ZstdCompres' | |||||
140 | Py_ssize_t resultSize; |
|
230 | Py_ssize_t resultSize; | |
141 | size_t zresult; |
|
231 | size_t zresult; | |
142 | size_t oldPos; |
|
232 | size_t oldPos; | |
|
233 | int readResult, compressResult; | |||
143 |
|
234 | |||
144 | if (self->closed) { |
|
235 | if (self->closed) { | |
145 | PyErr_SetString(PyExc_ValueError, "stream is closed"); |
|
236 | PyErr_SetString(PyExc_ValueError, "stream is closed"); | |
146 | return NULL; |
|
237 | return NULL; | |
147 | } |
|
238 | } | |
148 |
|
239 | |||
149 | if (self->finishedOutput) { |
|
240 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|n", kwlist, &size)) { | |
150 | return PyBytes_FromStringAndSize("", 0); |
|
|||
151 | } |
|
|||
152 |
|
||||
153 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "n", kwlist, &size)) { |
|
|||
154 | return NULL; |
|
241 | return NULL; | |
155 | } |
|
242 | } | |
156 |
|
243 | |||
157 | if (size < 1) { |
|
244 | if (size < -1) { | |
158 |
PyErr_SetString(PyExc_ValueError, "cannot read negative |
|
245 | PyErr_SetString(PyExc_ValueError, "cannot read negative amounts less than -1"); | |
159 | return NULL; |
|
246 | return NULL; | |
160 | } |
|
247 | } | |
161 |
|
248 | |||
|
249 | if (size == -1) { | |||
|
250 | return PyObject_CallMethod((PyObject*)self, "readall", NULL); | |||
|
251 | } | |||
|
252 | ||||
|
253 | if (self->finishedOutput || size == 0) { | |||
|
254 | return PyBytes_FromStringAndSize("", 0); | |||
|
255 | } | |||
|
256 | ||||
162 | result = PyBytes_FromStringAndSize(NULL, size); |
|
257 | result = PyBytes_FromStringAndSize(NULL, size); | |
163 | if (NULL == result) { |
|
258 | if (NULL == result) { | |
164 | return NULL; |
|
259 | return NULL; | |
@@ -172,86 +267,34 b' static PyObject* reader_read(ZstdCompres' | |||||
172 |
|
267 | |||
173 | readinput: |
|
268 | readinput: | |
174 |
|
269 | |||
175 | /* If we have data left over, consume it. */ |
|
270 | compressResult = compress_input(self, &self->output); | |
176 | if (self->input.pos < self->input.size) { |
|
|||
177 | oldPos = self->output.pos; |
|
|||
178 |
|
||||
179 | Py_BEGIN_ALLOW_THREADS |
|
|||
180 | zresult = ZSTD_compress_generic(self->compressor->cctx, |
|
|||
181 | &self->output, &self->input, ZSTD_e_continue); |
|
|||
182 |
|
||||
183 | Py_END_ALLOW_THREADS |
|
|||
184 |
|
||||
185 | self->bytesCompressed += self->output.pos - oldPos; |
|
|||
186 |
|
||||
187 | /* Input exhausted. Clear out state tracking. */ |
|
|||
188 | if (self->input.pos == self->input.size) { |
|
|||
189 | memset(&self->input, 0, sizeof(self->input)); |
|
|||
190 | Py_CLEAR(self->readResult); |
|
|||
191 |
|
271 | |||
192 | if (self->buffer.buf) { |
|
272 | if (-1 == compressResult) { | |
193 | self->finishedInput = 1; |
|
273 | Py_XDECREF(result); | |
194 | } |
|
274 | return NULL; | |
195 |
|
|
275 | } | |
196 |
|
276 | else if (0 == compressResult) { | ||
197 | if (ZSTD_isError(zresult)) { |
|
277 | /* There is room in the output. We fall through to below, which will | |
198 | PyErr_Format(ZstdError, "zstd compress error: %s", ZSTD_getErrorName(zresult)); |
|
278 | * either get more input for us or will attempt to end the stream. | |
199 | return NULL; |
|
279 | */ | |
200 |
|
|
280 | } | |
201 |
|
281 | else if (1 == compressResult) { | ||
202 | if (self->output.pos) { |
|
282 | memset(&self->output, 0, sizeof(self->output)); | |
203 | /* If no more room in output, emit it. */ |
|
283 | return result; | |
204 | if (self->output.pos == self->output.size) { |
|
284 | } | |
205 | memset(&self->output, 0, sizeof(self->output)); |
|
285 | else { | |
206 | return result; |
|
286 | assert(0); | |
207 | } |
|
|||
208 |
|
||||
209 | /* |
|
|||
210 | * There is room in the output. We fall through to below, which will either |
|
|||
211 | * get more input for us or will attempt to end the stream. |
|
|||
212 | */ |
|
|||
213 | } |
|
|||
214 |
|
||||
215 | /* Fall through to gather more input. */ |
|
|||
216 | } |
|
287 | } | |
217 |
|
288 | |||
218 | if (!self->finishedInput) { |
|
289 | readResult = read_compressor_input(self); | |
219 | if (self->reader) { |
|
|||
220 | Py_buffer buffer; |
|
|||
221 |
|
||||
222 | assert(self->readResult == NULL); |
|
|||
223 | self->readResult = PyObject_CallMethod(self->reader, "read", |
|
|||
224 | "k", self->readSize); |
|
|||
225 | if (self->readResult == NULL) { |
|
|||
226 | return NULL; |
|
|||
227 | } |
|
|||
228 |
|
||||
229 | memset(&buffer, 0, sizeof(buffer)); |
|
|||
230 |
|
||||
231 | if (0 != PyObject_GetBuffer(self->readResult, &buffer, PyBUF_CONTIG_RO)) { |
|
|||
232 | return NULL; |
|
|||
233 | } |
|
|||
234 |
|
290 | |||
235 | /* EOF */ |
|
291 | if (-1 == readResult) { | |
236 | if (0 == buffer.len) { |
|
292 | return NULL; | |
237 | self->finishedInput = 1; |
|
293 | } | |
238 | Py_CLEAR(self->readResult); |
|
294 | else if (0 == readResult) { } | |
239 | } |
|
295 | else if (1 == readResult) { } | |
240 |
|
|
296 | else { | |
241 | self->input.src = buffer.buf; |
|
297 | assert(0); | |
242 | self->input.size = buffer.len; |
|
|||
243 | self->input.pos = 0; |
|
|||
244 | } |
|
|||
245 |
|
||||
246 | PyBuffer_Release(&buffer); |
|
|||
247 | } |
|
|||
248 | else { |
|
|||
249 | assert(self->buffer.buf); |
|
|||
250 |
|
||||
251 | self->input.src = self->buffer.buf; |
|
|||
252 | self->input.size = self->buffer.len; |
|
|||
253 | self->input.pos = 0; |
|
|||
254 | } |
|
|||
255 | } |
|
298 | } | |
256 |
|
299 | |||
257 | if (self->input.size) { |
|
300 | if (self->input.size) { | |
@@ -261,7 +304,7 b' readinput:' | |||||
261 | /* Else EOF */ |
|
304 | /* Else EOF */ | |
262 | oldPos = self->output.pos; |
|
305 | oldPos = self->output.pos; | |
263 |
|
306 | |||
264 |
zresult = ZSTD_compress |
|
307 | zresult = ZSTD_compressStream2(self->compressor->cctx, &self->output, | |
265 | &self->input, ZSTD_e_end); |
|
308 | &self->input, ZSTD_e_end); | |
266 |
|
309 | |||
267 | self->bytesCompressed += self->output.pos - oldPos; |
|
310 | self->bytesCompressed += self->output.pos - oldPos; | |
@@ -269,6 +312,7 b' readinput:' | |||||
269 | if (ZSTD_isError(zresult)) { |
|
312 | if (ZSTD_isError(zresult)) { | |
270 | PyErr_Format(ZstdError, "error ending compression stream: %s", |
|
313 | PyErr_Format(ZstdError, "error ending compression stream: %s", | |
271 | ZSTD_getErrorName(zresult)); |
|
314 | ZSTD_getErrorName(zresult)); | |
|
315 | Py_XDECREF(result); | |||
272 | return NULL; |
|
316 | return NULL; | |
273 | } |
|
317 | } | |
274 |
|
318 | |||
@@ -288,9 +332,394 b' readinput:' | |||||
288 | return result; |
|
332 | return result; | |
289 | } |
|
333 | } | |
290 |
|
334 | |||
|
335 | static PyObject* reader_read1(ZstdCompressionReader* self, PyObject* args, PyObject* kwargs) { | |||
|
336 | static char* kwlist[] = { | |||
|
337 | "size", | |||
|
338 | NULL | |||
|
339 | }; | |||
|
340 | ||||
|
341 | Py_ssize_t size = -1; | |||
|
342 | PyObject* result = NULL; | |||
|
343 | char* resultBuffer; | |||
|
344 | Py_ssize_t resultSize; | |||
|
345 | ZSTD_outBuffer output; | |||
|
346 | int compressResult; | |||
|
347 | size_t oldPos; | |||
|
348 | size_t zresult; | |||
|
349 | ||||
|
350 | if (self->closed) { | |||
|
351 | PyErr_SetString(PyExc_ValueError, "stream is closed"); | |||
|
352 | return NULL; | |||
|
353 | } | |||
|
354 | ||||
|
355 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|n:read1", kwlist, &size)) { | |||
|
356 | return NULL; | |||
|
357 | } | |||
|
358 | ||||
|
359 | if (size < -1) { | |||
|
360 | PyErr_SetString(PyExc_ValueError, "cannot read negative amounts less than -1"); | |||
|
361 | return NULL; | |||
|
362 | } | |||
|
363 | ||||
|
364 | if (self->finishedOutput || size == 0) { | |||
|
365 | return PyBytes_FromStringAndSize("", 0); | |||
|
366 | } | |||
|
367 | ||||
|
368 | if (size == -1) { | |||
|
369 | size = ZSTD_CStreamOutSize(); | |||
|
370 | } | |||
|
371 | ||||
|
372 | result = PyBytes_FromStringAndSize(NULL, size); | |||
|
373 | if (NULL == result) { | |||
|
374 | return NULL; | |||
|
375 | } | |||
|
376 | ||||
|
377 | PyBytes_AsStringAndSize(result, &resultBuffer, &resultSize); | |||
|
378 | ||||
|
379 | output.dst = resultBuffer; | |||
|
380 | output.size = resultSize; | |||
|
381 | output.pos = 0; | |||
|
382 | ||||
|
383 | /* read1() is supposed to use at most 1 read() from the underlying stream. | |||
|
384 | However, we can't satisfy this requirement with compression because | |||
|
385 | not every input will generate output. We /could/ flush the compressor, | |||
|
386 | but this may not be desirable. We allow multiple read() from the | |||
|
387 | underlying stream. But unlike read(), we return as soon as output data | |||
|
388 | is available. | |||
|
389 | */ | |||
|
390 | ||||
|
391 | compressResult = compress_input(self, &output); | |||
|
392 | ||||
|
393 | if (-1 == compressResult) { | |||
|
394 | Py_XDECREF(result); | |||
|
395 | return NULL; | |||
|
396 | } | |||
|
397 | else if (0 == compressResult || 1 == compressResult) { } | |||
|
398 | else { | |||
|
399 | assert(0); | |||
|
400 | } | |||
|
401 | ||||
|
402 | if (output.pos) { | |||
|
403 | goto finally; | |||
|
404 | } | |||
|
405 | ||||
|
406 | while (!self->finishedInput) { | |||
|
407 | int readResult = read_compressor_input(self); | |||
|
408 | ||||
|
409 | if (-1 == readResult) { | |||
|
410 | Py_XDECREF(result); | |||
|
411 | return NULL; | |||
|
412 | } | |||
|
413 | else if (0 == readResult || 1 == readResult) { } | |||
|
414 | else { | |||
|
415 | assert(0); | |||
|
416 | } | |||
|
417 | ||||
|
418 | compressResult = compress_input(self, &output); | |||
|
419 | ||||
|
420 | if (-1 == compressResult) { | |||
|
421 | Py_XDECREF(result); | |||
|
422 | return NULL; | |||
|
423 | } | |||
|
424 | else if (0 == compressResult || 1 == compressResult) { } | |||
|
425 | else { | |||
|
426 | assert(0); | |||
|
427 | } | |||
|
428 | ||||
|
429 | if (output.pos) { | |||
|
430 | goto finally; | |||
|
431 | } | |||
|
432 | } | |||
|
433 | ||||
|
434 | /* EOF */ | |||
|
435 | oldPos = output.pos; | |||
|
436 | ||||
|
437 | zresult = ZSTD_compressStream2(self->compressor->cctx, &output, &self->input, | |||
|
438 | ZSTD_e_end); | |||
|
439 | ||||
|
440 | self->bytesCompressed += output.pos - oldPos; | |||
|
441 | ||||
|
442 | if (ZSTD_isError(zresult)) { | |||
|
443 | PyErr_Format(ZstdError, "error ending compression stream: %s", | |||
|
444 | ZSTD_getErrorName(zresult)); | |||
|
445 | Py_XDECREF(result); | |||
|
446 | return NULL; | |||
|
447 | } | |||
|
448 | ||||
|
449 | if (zresult == 0) { | |||
|
450 | self->finishedOutput = 1; | |||
|
451 | } | |||
|
452 | ||||
|
453 | finally: | |||
|
454 | if (result) { | |||
|
455 | if (safe_pybytes_resize(&result, output.pos)) { | |||
|
456 | Py_XDECREF(result); | |||
|
457 | return NULL; | |||
|
458 | } | |||
|
459 | } | |||
|
460 | ||||
|
461 | return result; | |||
|
462 | } | |||
|
463 | ||||
291 | static PyObject* reader_readall(PyObject* self) { |
|
464 | static PyObject* reader_readall(PyObject* self) { | |
292 | PyErr_SetNone(PyExc_NotImplementedError); |
|
465 | PyObject* chunks = NULL; | |
293 | return NULL; |
|
466 | PyObject* empty = NULL; | |
|
467 | PyObject* result = NULL; | |||
|
468 | ||||
|
469 | /* Our strategy is to collect chunks into a list then join all the | |||
|
470 | * chunks at the end. We could potentially use e.g. an io.BytesIO. But | |||
|
471 | * this feels simple enough to implement and avoids potentially expensive | |||
|
472 | * reallocations of large buffers. | |||
|
473 | */ | |||
|
474 | chunks = PyList_New(0); | |||
|
475 | if (NULL == chunks) { | |||
|
476 | return NULL; | |||
|
477 | } | |||
|
478 | ||||
|
479 | while (1) { | |||
|
480 | PyObject* chunk = PyObject_CallMethod(self, "read", "i", 1048576); | |||
|
481 | if (NULL == chunk) { | |||
|
482 | Py_DECREF(chunks); | |||
|
483 | return NULL; | |||
|
484 | } | |||
|
485 | ||||
|
486 | if (!PyBytes_Size(chunk)) { | |||
|
487 | Py_DECREF(chunk); | |||
|
488 | break; | |||
|
489 | } | |||
|
490 | ||||
|
491 | if (PyList_Append(chunks, chunk)) { | |||
|
492 | Py_DECREF(chunk); | |||
|
493 | Py_DECREF(chunks); | |||
|
494 | return NULL; | |||
|
495 | } | |||
|
496 | ||||
|
497 | Py_DECREF(chunk); | |||
|
498 | } | |||
|
499 | ||||
|
500 | empty = PyBytes_FromStringAndSize("", 0); | |||
|
501 | if (NULL == empty) { | |||
|
502 | Py_DECREF(chunks); | |||
|
503 | return NULL; | |||
|
504 | } | |||
|
505 | ||||
|
506 | result = PyObject_CallMethod(empty, "join", "O", chunks); | |||
|
507 | ||||
|
508 | Py_DECREF(empty); | |||
|
509 | Py_DECREF(chunks); | |||
|
510 | ||||
|
511 | return result; | |||
|
512 | } | |||
|
513 | ||||
|
514 | static PyObject* reader_readinto(ZstdCompressionReader* self, PyObject* args) { | |||
|
515 | Py_buffer dest; | |||
|
516 | ZSTD_outBuffer output; | |||
|
517 | int readResult, compressResult; | |||
|
518 | PyObject* result = NULL; | |||
|
519 | size_t zresult; | |||
|
520 | size_t oldPos; | |||
|
521 | ||||
|
522 | if (self->closed) { | |||
|
523 | PyErr_SetString(PyExc_ValueError, "stream is closed"); | |||
|
524 | return NULL; | |||
|
525 | } | |||
|
526 | ||||
|
527 | if (self->finishedOutput) { | |||
|
528 | return PyLong_FromLong(0); | |||
|
529 | } | |||
|
530 | ||||
|
531 | if (!PyArg_ParseTuple(args, "w*:readinto", &dest)) { | |||
|
532 | return NULL; | |||
|
533 | } | |||
|
534 | ||||
|
535 | if (!PyBuffer_IsContiguous(&dest, 'C') || dest.ndim > 1) { | |||
|
536 | PyErr_SetString(PyExc_ValueError, | |||
|
537 | "destination buffer should be contiguous and have at most one dimension"); | |||
|
538 | goto finally; | |||
|
539 | } | |||
|
540 | ||||
|
541 | output.dst = dest.buf; | |||
|
542 | output.size = dest.len; | |||
|
543 | output.pos = 0; | |||
|
544 | ||||
|
545 | compressResult = compress_input(self, &output); | |||
|
546 | ||||
|
547 | if (-1 == compressResult) { | |||
|
548 | goto finally; | |||
|
549 | } | |||
|
550 | else if (0 == compressResult) { } | |||
|
551 | else if (1 == compressResult) { | |||
|
552 | result = PyLong_FromSize_t(output.pos); | |||
|
553 | goto finally; | |||
|
554 | } | |||
|
555 | else { | |||
|
556 | assert(0); | |||
|
557 | } | |||
|
558 | ||||
|
559 | while (!self->finishedInput) { | |||
|
560 | readResult = read_compressor_input(self); | |||
|
561 | ||||
|
562 | if (-1 == readResult) { | |||
|
563 | goto finally; | |||
|
564 | } | |||
|
565 | else if (0 == readResult || 1 == readResult) {} | |||
|
566 | else { | |||
|
567 | assert(0); | |||
|
568 | } | |||
|
569 | ||||
|
570 | compressResult = compress_input(self, &output); | |||
|
571 | ||||
|
572 | if (-1 == compressResult) { | |||
|
573 | goto finally; | |||
|
574 | } | |||
|
575 | else if (0 == compressResult) { } | |||
|
576 | else if (1 == compressResult) { | |||
|
577 | result = PyLong_FromSize_t(output.pos); | |||
|
578 | goto finally; | |||
|
579 | } | |||
|
580 | else { | |||
|
581 | assert(0); | |||
|
582 | } | |||
|
583 | } | |||
|
584 | ||||
|
585 | /* EOF */ | |||
|
586 | oldPos = output.pos; | |||
|
587 | ||||
|
588 | zresult = ZSTD_compressStream2(self->compressor->cctx, &output, &self->input, | |||
|
589 | ZSTD_e_end); | |||
|
590 | ||||
|
591 | self->bytesCompressed += self->output.pos - oldPos; | |||
|
592 | ||||
|
593 | if (ZSTD_isError(zresult)) { | |||
|
594 | PyErr_Format(ZstdError, "error ending compression stream: %s", | |||
|
595 | ZSTD_getErrorName(zresult)); | |||
|
596 | goto finally; | |||
|
597 | } | |||
|
598 | ||||
|
599 | assert(output.pos); | |||
|
600 | ||||
|
601 | if (0 == zresult) { | |||
|
602 | self->finishedOutput = 1; | |||
|
603 | } | |||
|
604 | ||||
|
605 | result = PyLong_FromSize_t(output.pos); | |||
|
606 | ||||
|
607 | finally: | |||
|
608 | PyBuffer_Release(&dest); | |||
|
609 | ||||
|
610 | return result; | |||
|
611 | } | |||
|
612 | ||||
|
613 | static PyObject* reader_readinto1(ZstdCompressionReader* self, PyObject* args) { | |||
|
614 | Py_buffer dest; | |||
|
615 | PyObject* result = NULL; | |||
|
616 | ZSTD_outBuffer output; | |||
|
617 | int compressResult; | |||
|
618 | size_t oldPos; | |||
|
619 | size_t zresult; | |||
|
620 | ||||
|
621 | if (self->closed) { | |||
|
622 | PyErr_SetString(PyExc_ValueError, "stream is closed"); | |||
|
623 | return NULL; | |||
|
624 | } | |||
|
625 | ||||
|
626 | if (self->finishedOutput) { | |||
|
627 | return PyLong_FromLong(0); | |||
|
628 | } | |||
|
629 | ||||
|
630 | if (!PyArg_ParseTuple(args, "w*:readinto1", &dest)) { | |||
|
631 | return NULL; | |||
|
632 | } | |||
|
633 | ||||
|
634 | if (!PyBuffer_IsContiguous(&dest, 'C') || dest.ndim > 1) { | |||
|
635 | PyErr_SetString(PyExc_ValueError, | |||
|
636 | "destination buffer should be contiguous and have at most one dimension"); | |||
|
637 | goto finally; | |||
|
638 | } | |||
|
639 | ||||
|
640 | output.dst = dest.buf; | |||
|
641 | output.size = dest.len; | |||
|
642 | output.pos = 0; | |||
|
643 | ||||
|
644 | compressResult = compress_input(self, &output); | |||
|
645 | ||||
|
646 | if (-1 == compressResult) { | |||
|
647 | goto finally; | |||
|
648 | } | |||
|
649 | else if (0 == compressResult || 1 == compressResult) { } | |||
|
650 | else { | |||
|
651 | assert(0); | |||
|
652 | } | |||
|
653 | ||||
|
654 | if (output.pos) { | |||
|
655 | result = PyLong_FromSize_t(output.pos); | |||
|
656 | goto finally; | |||
|
657 | } | |||
|
658 | ||||
|
659 | while (!self->finishedInput) { | |||
|
660 | int readResult = read_compressor_input(self); | |||
|
661 | ||||
|
662 | if (-1 == readResult) { | |||
|
663 | goto finally; | |||
|
664 | } | |||
|
665 | else if (0 == readResult || 1 == readResult) { } | |||
|
666 | else { | |||
|
667 | assert(0); | |||
|
668 | } | |||
|
669 | ||||
|
670 | compressResult = compress_input(self, &output); | |||
|
671 | ||||
|
672 | if (-1 == compressResult) { | |||
|
673 | goto finally; | |||
|
674 | } | |||
|
675 | else if (0 == compressResult) { } | |||
|
676 | else if (1 == compressResult) { | |||
|
677 | result = PyLong_FromSize_t(output.pos); | |||
|
678 | goto finally; | |||
|
679 | } | |||
|
680 | else { | |||
|
681 | assert(0); | |||
|
682 | } | |||
|
683 | ||||
|
684 | /* If we produced output and we're not done with input, emit | |||
|
685 | * that output now, as we've hit restrictions of read1(). | |||
|
686 | */ | |||
|
687 | if (output.pos && !self->finishedInput) { | |||
|
688 | result = PyLong_FromSize_t(output.pos); | |||
|
689 | goto finally; | |||
|
690 | } | |||
|
691 | ||||
|
692 | /* Otherwise we either have no output or we've exhausted the | |||
|
693 | * input. Either we try to get more input or we fall through | |||
|
694 | * to EOF below */ | |||
|
695 | } | |||
|
696 | ||||
|
697 | /* EOF */ | |||
|
698 | oldPos = output.pos; | |||
|
699 | ||||
|
700 | zresult = ZSTD_compressStream2(self->compressor->cctx, &output, &self->input, | |||
|
701 | ZSTD_e_end); | |||
|
702 | ||||
|
703 | self->bytesCompressed += self->output.pos - oldPos; | |||
|
704 | ||||
|
705 | if (ZSTD_isError(zresult)) { | |||
|
706 | PyErr_Format(ZstdError, "error ending compression stream: %s", | |||
|
707 | ZSTD_getErrorName(zresult)); | |||
|
708 | goto finally; | |||
|
709 | } | |||
|
710 | ||||
|
711 | assert(output.pos); | |||
|
712 | ||||
|
713 | if (0 == zresult) { | |||
|
714 | self->finishedOutput = 1; | |||
|
715 | } | |||
|
716 | ||||
|
717 | result = PyLong_FromSize_t(output.pos); | |||
|
718 | ||||
|
719 | finally: | |||
|
720 | PyBuffer_Release(&dest); | |||
|
721 | ||||
|
722 | return result; | |||
294 | } |
|
723 | } | |
295 |
|
724 | |||
296 | static PyObject* reader_iter(PyObject* self) { |
|
725 | static PyObject* reader_iter(PyObject* self) { | |
@@ -315,7 +744,10 b' static PyMethodDef reader_methods[] = {' | |||||
315 | { "readable", (PyCFunction)reader_readable, METH_NOARGS, |
|
744 | { "readable", (PyCFunction)reader_readable, METH_NOARGS, | |
316 | PyDoc_STR("Returns True") }, |
|
745 | PyDoc_STR("Returns True") }, | |
317 | { "read", (PyCFunction)reader_read, METH_VARARGS | METH_KEYWORDS, PyDoc_STR("read compressed data") }, |
|
746 | { "read", (PyCFunction)reader_read, METH_VARARGS | METH_KEYWORDS, PyDoc_STR("read compressed data") }, | |
|
747 | { "read1", (PyCFunction)reader_read1, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
318 | { "readall", (PyCFunction)reader_readall, METH_NOARGS, PyDoc_STR("Not implemented") }, |
|
748 | { "readall", (PyCFunction)reader_readall, METH_NOARGS, PyDoc_STR("Not implemented") }, | |
|
749 | { "readinto", (PyCFunction)reader_readinto, METH_VARARGS, NULL }, | |||
|
750 | { "readinto1", (PyCFunction)reader_readinto1, METH_VARARGS, NULL }, | |||
319 | { "readline", (PyCFunction)reader_readline, METH_VARARGS, PyDoc_STR("Not implemented") }, |
|
751 | { "readline", (PyCFunction)reader_readline, METH_VARARGS, PyDoc_STR("Not implemented") }, | |
320 | { "readlines", (PyCFunction)reader_readlines, METH_VARARGS, PyDoc_STR("Not implemented") }, |
|
752 | { "readlines", (PyCFunction)reader_readlines, METH_VARARGS, PyDoc_STR("Not implemented") }, | |
321 | { "seekable", (PyCFunction)reader_seekable, METH_NOARGS, |
|
753 | { "seekable", (PyCFunction)reader_seekable, METH_NOARGS, |
@@ -18,24 +18,23 b' static void ZstdCompressionWriter_deallo' | |||||
18 | Py_XDECREF(self->compressor); |
|
18 | Py_XDECREF(self->compressor); | |
19 | Py_XDECREF(self->writer); |
|
19 | Py_XDECREF(self->writer); | |
20 |
|
20 | |||
|
21 | PyMem_Free(self->output.dst); | |||
|
22 | self->output.dst = NULL; | |||
|
23 | ||||
21 | PyObject_Del(self); |
|
24 | PyObject_Del(self); | |
22 | } |
|
25 | } | |
23 |
|
26 | |||
24 | static PyObject* ZstdCompressionWriter_enter(ZstdCompressionWriter* self) { |
|
27 | static PyObject* ZstdCompressionWriter_enter(ZstdCompressionWriter* self) { | |
25 | size_t zresult; |
|
28 | if (self->closed) { | |
|
29 | PyErr_SetString(PyExc_ValueError, "stream is closed"); | |||
|
30 | return NULL; | |||
|
31 | } | |||
26 |
|
32 | |||
27 | if (self->entered) { |
|
33 | if (self->entered) { | |
28 | PyErr_SetString(ZstdError, "cannot __enter__ multiple times"); |
|
34 | PyErr_SetString(ZstdError, "cannot __enter__ multiple times"); | |
29 | return NULL; |
|
35 | return NULL; | |
30 | } |
|
36 | } | |
31 |
|
37 | |||
32 | zresult = ZSTD_CCtx_setPledgedSrcSize(self->compressor->cctx, self->sourceSize); |
|
|||
33 | if (ZSTD_isError(zresult)) { |
|
|||
34 | PyErr_Format(ZstdError, "error setting source size: %s", |
|
|||
35 | ZSTD_getErrorName(zresult)); |
|
|||
36 | return NULL; |
|
|||
37 | } |
|
|||
38 |
|
||||
39 | self->entered = 1; |
|
38 | self->entered = 1; | |
40 |
|
39 | |||
41 | Py_INCREF(self); |
|
40 | Py_INCREF(self); | |
@@ -46,10 +45,6 b' static PyObject* ZstdCompressionWriter_e' | |||||
46 | PyObject* exc_type; |
|
45 | PyObject* exc_type; | |
47 | PyObject* exc_value; |
|
46 | PyObject* exc_value; | |
48 | PyObject* exc_tb; |
|
47 | PyObject* exc_tb; | |
49 | size_t zresult; |
|
|||
50 |
|
||||
51 | ZSTD_outBuffer output; |
|
|||
52 | PyObject* res; |
|
|||
53 |
|
48 | |||
54 | if (!PyArg_ParseTuple(args, "OOO:__exit__", &exc_type, &exc_value, &exc_tb)) { |
|
49 | if (!PyArg_ParseTuple(args, "OOO:__exit__", &exc_type, &exc_value, &exc_tb)) { | |
55 | return NULL; |
|
50 | return NULL; | |
@@ -58,46 +53,11 b' static PyObject* ZstdCompressionWriter_e' | |||||
58 | self->entered = 0; |
|
53 | self->entered = 0; | |
59 |
|
54 | |||
60 | if (exc_type == Py_None && exc_value == Py_None && exc_tb == Py_None) { |
|
55 | if (exc_type == Py_None && exc_value == Py_None && exc_tb == Py_None) { | |
61 | ZSTD_inBuffer inBuffer; |
|
56 | PyObject* result = PyObject_CallMethod((PyObject*)self, "close", NULL); | |
62 |
|
||||
63 | inBuffer.src = NULL; |
|
|||
64 | inBuffer.size = 0; |
|
|||
65 | inBuffer.pos = 0; |
|
|||
66 |
|
||||
67 | output.dst = PyMem_Malloc(self->outSize); |
|
|||
68 | if (!output.dst) { |
|
|||
69 | return PyErr_NoMemory(); |
|
|||
70 | } |
|
|||
71 | output.size = self->outSize; |
|
|||
72 | output.pos = 0; |
|
|||
73 |
|
57 | |||
74 | while (1) { |
|
58 | if (NULL == result) { | |
75 | zresult = ZSTD_compress_generic(self->compressor->cctx, &output, &inBuffer, ZSTD_e_end); |
|
59 | return NULL; | |
76 | if (ZSTD_isError(zresult)) { |
|
|||
77 | PyErr_Format(ZstdError, "error ending compression stream: %s", |
|
|||
78 | ZSTD_getErrorName(zresult)); |
|
|||
79 | PyMem_Free(output.dst); |
|
|||
80 | return NULL; |
|
|||
81 | } |
|
|||
82 |
|
||||
83 | if (output.pos) { |
|
|||
84 | #if PY_MAJOR_VERSION >= 3 |
|
|||
85 | res = PyObject_CallMethod(self->writer, "write", "y#", |
|
|||
86 | #else |
|
|||
87 | res = PyObject_CallMethod(self->writer, "write", "s#", |
|
|||
88 | #endif |
|
|||
89 | output.dst, output.pos); |
|
|||
90 | Py_XDECREF(res); |
|
|||
91 | } |
|
|||
92 |
|
||||
93 | if (!zresult) { |
|
|||
94 | break; |
|
|||
95 | } |
|
|||
96 |
|
||||
97 | output.pos = 0; |
|
|||
98 | } |
|
60 | } | |
99 |
|
||||
100 | PyMem_Free(output.dst); |
|
|||
101 | } |
|
61 | } | |
102 |
|
62 | |||
103 | Py_RETURN_FALSE; |
|
63 | Py_RETURN_FALSE; | |
@@ -117,7 +77,6 b' static PyObject* ZstdCompressionWriter_w' | |||||
117 | Py_buffer source; |
|
77 | Py_buffer source; | |
118 | size_t zresult; |
|
78 | size_t zresult; | |
119 | ZSTD_inBuffer input; |
|
79 | ZSTD_inBuffer input; | |
120 | ZSTD_outBuffer output; |
|
|||
121 | PyObject* res; |
|
80 | PyObject* res; | |
122 | Py_ssize_t totalWrite = 0; |
|
81 | Py_ssize_t totalWrite = 0; | |
123 |
|
82 | |||
@@ -130,143 +89,240 b' static PyObject* ZstdCompressionWriter_w' | |||||
130 | return NULL; |
|
89 | return NULL; | |
131 | } |
|
90 | } | |
132 |
|
91 | |||
133 | if (!self->entered) { |
|
|||
134 | PyErr_SetString(ZstdError, "compress must be called from an active context manager"); |
|
|||
135 | goto finally; |
|
|||
136 | } |
|
|||
137 |
|
||||
138 | if (!PyBuffer_IsContiguous(&source, 'C') || source.ndim > 1) { |
|
92 | if (!PyBuffer_IsContiguous(&source, 'C') || source.ndim > 1) { | |
139 | PyErr_SetString(PyExc_ValueError, |
|
93 | PyErr_SetString(PyExc_ValueError, | |
140 | "data buffer should be contiguous and have at most one dimension"); |
|
94 | "data buffer should be contiguous and have at most one dimension"); | |
141 | goto finally; |
|
95 | goto finally; | |
142 | } |
|
96 | } | |
143 |
|
97 | |||
144 | output.dst = PyMem_Malloc(self->outSize); |
|
98 | if (self->closed) { | |
145 | if (!output.dst) { |
|
99 | PyErr_SetString(PyExc_ValueError, "stream is closed"); | |
146 | PyErr_NoMemory(); |
|
100 | return NULL; | |
147 | goto finally; |
|
|||
148 | } |
|
101 | } | |
149 | output.size = self->outSize; |
|
102 | ||
150 | output.pos = 0; |
|
103 | self->output.pos = 0; | |
151 |
|
104 | |||
152 | input.src = source.buf; |
|
105 | input.src = source.buf; | |
153 | input.size = source.len; |
|
106 | input.size = source.len; | |
154 | input.pos = 0; |
|
107 | input.pos = 0; | |
155 |
|
108 | |||
156 |
while ( |
|
109 | while (input.pos < (size_t)source.len) { | |
157 | Py_BEGIN_ALLOW_THREADS |
|
110 | Py_BEGIN_ALLOW_THREADS | |
158 |
zresult = ZSTD_compress |
|
111 | zresult = ZSTD_compressStream2(self->compressor->cctx, &self->output, &input, ZSTD_e_continue); | |
159 | Py_END_ALLOW_THREADS |
|
112 | Py_END_ALLOW_THREADS | |
160 |
|
113 | |||
161 | if (ZSTD_isError(zresult)) { |
|
114 | if (ZSTD_isError(zresult)) { | |
162 | PyMem_Free(output.dst); |
|
|||
163 | PyErr_Format(ZstdError, "zstd compress error: %s", ZSTD_getErrorName(zresult)); |
|
115 | PyErr_Format(ZstdError, "zstd compress error: %s", ZSTD_getErrorName(zresult)); | |
164 | goto finally; |
|
116 | goto finally; | |
165 | } |
|
117 | } | |
166 |
|
118 | |||
167 | /* Copy data from output buffer to writer. */ |
|
119 | /* Copy data from output buffer to writer. */ | |
168 | if (output.pos) { |
|
120 | if (self->output.pos) { | |
169 | #if PY_MAJOR_VERSION >= 3 |
|
121 | #if PY_MAJOR_VERSION >= 3 | |
170 | res = PyObject_CallMethod(self->writer, "write", "y#", |
|
122 | res = PyObject_CallMethod(self->writer, "write", "y#", | |
171 | #else |
|
123 | #else | |
172 | res = PyObject_CallMethod(self->writer, "write", "s#", |
|
124 | res = PyObject_CallMethod(self->writer, "write", "s#", | |
173 | #endif |
|
125 | #endif | |
174 | output.dst, output.pos); |
|
126 | self->output.dst, self->output.pos); | |
175 | Py_XDECREF(res); |
|
127 | Py_XDECREF(res); | |
176 | totalWrite += output.pos; |
|
128 | totalWrite += self->output.pos; | |
177 | self->bytesCompressed += output.pos; |
|
129 | self->bytesCompressed += self->output.pos; | |
178 | } |
|
130 | } | |
179 | output.pos = 0; |
|
131 | self->output.pos = 0; | |
180 | } |
|
132 | } | |
181 |
|
133 | |||
182 | PyMem_Free(output.dst); |
|
134 | if (self->writeReturnRead) { | |
183 |
|
135 | result = PyLong_FromSize_t(input.pos); | ||
184 | result = PyLong_FromSsize_t(totalWrite); |
|
136 | } | |
|
137 | else { | |||
|
138 | result = PyLong_FromSsize_t(totalWrite); | |||
|
139 | } | |||
185 |
|
140 | |||
186 | finally: |
|
141 | finally: | |
187 | PyBuffer_Release(&source); |
|
142 | PyBuffer_Release(&source); | |
188 | return result; |
|
143 | return result; | |
189 | } |
|
144 | } | |
190 |
|
145 | |||
191 | static PyObject* ZstdCompressionWriter_flush(ZstdCompressionWriter* self, PyObject* args) { |
|
146 | static PyObject* ZstdCompressionWriter_flush(ZstdCompressionWriter* self, PyObject* args, PyObject* kwargs) { | |
|
147 | static char* kwlist[] = { | |||
|
148 | "flush_mode", | |||
|
149 | NULL | |||
|
150 | }; | |||
|
151 | ||||
192 | size_t zresult; |
|
152 | size_t zresult; | |
193 | ZSTD_outBuffer output; |
|
|||
194 | ZSTD_inBuffer input; |
|
153 | ZSTD_inBuffer input; | |
195 | PyObject* res; |
|
154 | PyObject* res; | |
196 | Py_ssize_t totalWrite = 0; |
|
155 | Py_ssize_t totalWrite = 0; | |
|
156 | unsigned flush_mode = 0; | |||
|
157 | ZSTD_EndDirective flush; | |||
197 |
|
158 | |||
198 | if (!self->entered) { |
|
159 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|I:flush", | |
199 | PyErr_SetString(ZstdError, "flush must be called from an active context manager"); |
|
160 | kwlist, &flush_mode)) { | |
200 | return NULL; |
|
161 | return NULL; | |
201 | } |
|
162 | } | |
202 |
|
163 | |||
|
164 | switch (flush_mode) { | |||
|
165 | case 0: | |||
|
166 | flush = ZSTD_e_flush; | |||
|
167 | break; | |||
|
168 | case 1: | |||
|
169 | flush = ZSTD_e_end; | |||
|
170 | break; | |||
|
171 | default: | |||
|
172 | PyErr_Format(PyExc_ValueError, "unknown flush_mode: %d", flush_mode); | |||
|
173 | return NULL; | |||
|
174 | } | |||
|
175 | ||||
|
176 | if (self->closed) { | |||
|
177 | PyErr_SetString(PyExc_ValueError, "stream is closed"); | |||
|
178 | return NULL; | |||
|
179 | } | |||
|
180 | ||||
|
181 | self->output.pos = 0; | |||
|
182 | ||||
203 | input.src = NULL; |
|
183 | input.src = NULL; | |
204 | input.size = 0; |
|
184 | input.size = 0; | |
205 | input.pos = 0; |
|
185 | input.pos = 0; | |
206 |
|
186 | |||
207 | output.dst = PyMem_Malloc(self->outSize); |
|
|||
208 | if (!output.dst) { |
|
|||
209 | return PyErr_NoMemory(); |
|
|||
210 | } |
|
|||
211 | output.size = self->outSize; |
|
|||
212 | output.pos = 0; |
|
|||
213 |
|
||||
214 | while (1) { |
|
187 | while (1) { | |
215 | Py_BEGIN_ALLOW_THREADS |
|
188 | Py_BEGIN_ALLOW_THREADS | |
216 |
zresult = ZSTD_compress |
|
189 | zresult = ZSTD_compressStream2(self->compressor->cctx, &self->output, &input, flush); | |
217 | Py_END_ALLOW_THREADS |
|
190 | Py_END_ALLOW_THREADS | |
218 |
|
191 | |||
219 | if (ZSTD_isError(zresult)) { |
|
192 | if (ZSTD_isError(zresult)) { | |
220 | PyMem_Free(output.dst); |
|
|||
221 | PyErr_Format(ZstdError, "zstd compress error: %s", ZSTD_getErrorName(zresult)); |
|
193 | PyErr_Format(ZstdError, "zstd compress error: %s", ZSTD_getErrorName(zresult)); | |
222 | return NULL; |
|
194 | return NULL; | |
223 | } |
|
195 | } | |
224 |
|
196 | |||
225 | /* Copy data from output buffer to writer. */ |
|
197 | /* Copy data from output buffer to writer. */ | |
226 | if (output.pos) { |
|
198 | if (self->output.pos) { | |
227 | #if PY_MAJOR_VERSION >= 3 |
|
199 | #if PY_MAJOR_VERSION >= 3 | |
228 | res = PyObject_CallMethod(self->writer, "write", "y#", |
|
200 | res = PyObject_CallMethod(self->writer, "write", "y#", | |
229 | #else |
|
201 | #else | |
230 | res = PyObject_CallMethod(self->writer, "write", "s#", |
|
202 | res = PyObject_CallMethod(self->writer, "write", "s#", | |
231 | #endif |
|
203 | #endif | |
232 | output.dst, output.pos); |
|
204 | self->output.dst, self->output.pos); | |
233 | Py_XDECREF(res); |
|
205 | Py_XDECREF(res); | |
234 | totalWrite += output.pos; |
|
206 | totalWrite += self->output.pos; | |
235 | self->bytesCompressed += output.pos; |
|
207 | self->bytesCompressed += self->output.pos; | |
236 | } |
|
208 | } | |
237 |
|
209 | |||
238 | output.pos = 0; |
|
210 | self->output.pos = 0; | |
239 |
|
211 | |||
240 | if (!zresult) { |
|
212 | if (!zresult) { | |
241 | break; |
|
213 | break; | |
242 | } |
|
214 | } | |
243 | } |
|
215 | } | |
244 |
|
216 | |||
245 | PyMem_Free(output.dst); |
|
217 | return PyLong_FromSsize_t(totalWrite); | |
|
218 | } | |||
|
219 | ||||
|
220 | static PyObject* ZstdCompressionWriter_close(ZstdCompressionWriter* self) { | |||
|
221 | PyObject* result; | |||
|
222 | ||||
|
223 | if (self->closed) { | |||
|
224 | Py_RETURN_NONE; | |||
|
225 | } | |||
|
226 | ||||
|
227 | result = PyObject_CallMethod((PyObject*)self, "flush", "I", 1); | |||
|
228 | self->closed = 1; | |||
|
229 | ||||
|
230 | if (NULL == result) { | |||
|
231 | return NULL; | |||
|
232 | } | |||
246 |
|
233 | |||
247 | return PyLong_FromSsize_t(totalWrite); |
|
234 | /* Call close on underlying stream as well. */ | |
|
235 | if (PyObject_HasAttrString(self->writer, "close")) { | |||
|
236 | return PyObject_CallMethod(self->writer, "close", NULL); | |||
|
237 | } | |||
|
238 | ||||
|
239 | Py_RETURN_NONE; | |||
|
240 | } | |||
|
241 | ||||
|
242 | static PyObject* ZstdCompressionWriter_fileno(ZstdCompressionWriter* self) { | |||
|
243 | if (PyObject_HasAttrString(self->writer, "fileno")) { | |||
|
244 | return PyObject_CallMethod(self->writer, "fileno", NULL); | |||
|
245 | } | |||
|
246 | else { | |||
|
247 | PyErr_SetString(PyExc_OSError, "fileno not available on underlying writer"); | |||
|
248 | return NULL; | |||
|
249 | } | |||
248 | } |
|
250 | } | |
249 |
|
251 | |||
250 | static PyObject* ZstdCompressionWriter_tell(ZstdCompressionWriter* self) { |
|
252 | static PyObject* ZstdCompressionWriter_tell(ZstdCompressionWriter* self) { | |
251 | return PyLong_FromUnsignedLongLong(self->bytesCompressed); |
|
253 | return PyLong_FromUnsignedLongLong(self->bytesCompressed); | |
252 | } |
|
254 | } | |
253 |
|
255 | |||
|
256 | static PyObject* ZstdCompressionWriter_writelines(PyObject* self, PyObject* args) { | |||
|
257 | PyErr_SetNone(PyExc_NotImplementedError); | |||
|
258 | return NULL; | |||
|
259 | } | |||
|
260 | ||||
|
261 | static PyObject* ZstdCompressionWriter_false(PyObject* self, PyObject* args) { | |||
|
262 | Py_RETURN_FALSE; | |||
|
263 | } | |||
|
264 | ||||
|
265 | static PyObject* ZstdCompressionWriter_true(PyObject* self, PyObject* args) { | |||
|
266 | Py_RETURN_TRUE; | |||
|
267 | } | |||
|
268 | ||||
|
269 | static PyObject* ZstdCompressionWriter_unsupported(PyObject* self, PyObject* args, PyObject* kwargs) { | |||
|
270 | PyObject* iomod; | |||
|
271 | PyObject* exc; | |||
|
272 | ||||
|
273 | iomod = PyImport_ImportModule("io"); | |||
|
274 | if (NULL == iomod) { | |||
|
275 | return NULL; | |||
|
276 | } | |||
|
277 | ||||
|
278 | exc = PyObject_GetAttrString(iomod, "UnsupportedOperation"); | |||
|
279 | if (NULL == exc) { | |||
|
280 | Py_DECREF(iomod); | |||
|
281 | return NULL; | |||
|
282 | } | |||
|
283 | ||||
|
284 | PyErr_SetNone(exc); | |||
|
285 | Py_DECREF(exc); | |||
|
286 | Py_DECREF(iomod); | |||
|
287 | ||||
|
288 | return NULL; | |||
|
289 | } | |||
|
290 | ||||
254 | static PyMethodDef ZstdCompressionWriter_methods[] = { |
|
291 | static PyMethodDef ZstdCompressionWriter_methods[] = { | |
255 | { "__enter__", (PyCFunction)ZstdCompressionWriter_enter, METH_NOARGS, |
|
292 | { "__enter__", (PyCFunction)ZstdCompressionWriter_enter, METH_NOARGS, | |
256 | PyDoc_STR("Enter a compression context.") }, |
|
293 | PyDoc_STR("Enter a compression context.") }, | |
257 | { "__exit__", (PyCFunction)ZstdCompressionWriter_exit, METH_VARARGS, |
|
294 | { "__exit__", (PyCFunction)ZstdCompressionWriter_exit, METH_VARARGS, | |
258 | PyDoc_STR("Exit a compression context.") }, |
|
295 | PyDoc_STR("Exit a compression context.") }, | |
|
296 | { "close", (PyCFunction)ZstdCompressionWriter_close, METH_NOARGS, NULL }, | |||
|
297 | { "fileno", (PyCFunction)ZstdCompressionWriter_fileno, METH_NOARGS, NULL }, | |||
|
298 | { "isatty", (PyCFunction)ZstdCompressionWriter_false, METH_NOARGS, NULL }, | |||
|
299 | { "readable", (PyCFunction)ZstdCompressionWriter_false, METH_NOARGS, NULL }, | |||
|
300 | { "readline", (PyCFunction)ZstdCompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
|
301 | { "readlines", (PyCFunction)ZstdCompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
|
302 | { "seek", (PyCFunction)ZstdCompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
|
303 | { "seekable", ZstdCompressionWriter_false, METH_NOARGS, NULL }, | |||
|
304 | { "truncate", (PyCFunction)ZstdCompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
|
305 | { "writable", ZstdCompressionWriter_true, METH_NOARGS, NULL }, | |||
|
306 | { "writelines", ZstdCompressionWriter_writelines, METH_VARARGS, NULL }, | |||
|
307 | { "read", (PyCFunction)ZstdCompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
|
308 | { "readall", (PyCFunction)ZstdCompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
|
309 | { "readinto", (PyCFunction)ZstdCompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
259 | { "memory_size", (PyCFunction)ZstdCompressionWriter_memory_size, METH_NOARGS, |
|
310 | { "memory_size", (PyCFunction)ZstdCompressionWriter_memory_size, METH_NOARGS, | |
260 | PyDoc_STR("Obtain the memory size of the underlying compressor") }, |
|
311 | PyDoc_STR("Obtain the memory size of the underlying compressor") }, | |
261 | { "write", (PyCFunction)ZstdCompressionWriter_write, METH_VARARGS | METH_KEYWORDS, |
|
312 | { "write", (PyCFunction)ZstdCompressionWriter_write, METH_VARARGS | METH_KEYWORDS, | |
262 | PyDoc_STR("Compress data") }, |
|
313 | PyDoc_STR("Compress data") }, | |
263 |
{ "flush", (PyCFunction)ZstdCompressionWriter_flush, METH_ |
|
314 | { "flush", (PyCFunction)ZstdCompressionWriter_flush, METH_VARARGS | METH_KEYWORDS, | |
264 | PyDoc_STR("Flush data and finish a zstd frame") }, |
|
315 | PyDoc_STR("Flush data and finish a zstd frame") }, | |
265 | { "tell", (PyCFunction)ZstdCompressionWriter_tell, METH_NOARGS, |
|
316 | { "tell", (PyCFunction)ZstdCompressionWriter_tell, METH_NOARGS, | |
266 | PyDoc_STR("Returns current number of bytes compressed") }, |
|
317 | PyDoc_STR("Returns current number of bytes compressed") }, | |
267 | { NULL, NULL } |
|
318 | { NULL, NULL } | |
268 | }; |
|
319 | }; | |
269 |
|
320 | |||
|
321 | static PyMemberDef ZstdCompressionWriter_members[] = { | |||
|
322 | { "closed", T_BOOL, offsetof(ZstdCompressionWriter, closed), READONLY, NULL }, | |||
|
323 | { NULL } | |||
|
324 | }; | |||
|
325 | ||||
270 | PyTypeObject ZstdCompressionWriterType = { |
|
326 | PyTypeObject ZstdCompressionWriterType = { | |
271 | PyVarObject_HEAD_INIT(NULL, 0) |
|
327 | PyVarObject_HEAD_INIT(NULL, 0) | |
272 | "zstd.ZstdCompressionWriter", /* tp_name */ |
|
328 | "zstd.ZstdCompressionWriter", /* tp_name */ | |
@@ -296,7 +352,7 b' PyTypeObject ZstdCompressionWriterType =' | |||||
296 | 0, /* tp_iter */ |
|
352 | 0, /* tp_iter */ | |
297 | 0, /* tp_iternext */ |
|
353 | 0, /* tp_iternext */ | |
298 | ZstdCompressionWriter_methods, /* tp_methods */ |
|
354 | ZstdCompressionWriter_methods, /* tp_methods */ | |
299 | 0, /* tp_members */ |
|
355 | ZstdCompressionWriter_members, /* tp_members */ | |
300 | 0, /* tp_getset */ |
|
356 | 0, /* tp_getset */ | |
301 | 0, /* tp_base */ |
|
357 | 0, /* tp_base */ | |
302 | 0, /* tp_dict */ |
|
358 | 0, /* tp_dict */ |
@@ -59,9 +59,9 b' static PyObject* ZstdCompressionObj_comp' | |||||
59 | input.size = source.len; |
|
59 | input.size = source.len; | |
60 | input.pos = 0; |
|
60 | input.pos = 0; | |
61 |
|
61 | |||
62 |
while ( |
|
62 | while (input.pos < (size_t)source.len) { | |
63 | Py_BEGIN_ALLOW_THREADS |
|
63 | Py_BEGIN_ALLOW_THREADS | |
64 |
zresult = ZSTD_compress |
|
64 | zresult = ZSTD_compressStream2(self->compressor->cctx, &self->output, | |
65 | &input, ZSTD_e_continue); |
|
65 | &input, ZSTD_e_continue); | |
66 | Py_END_ALLOW_THREADS |
|
66 | Py_END_ALLOW_THREADS | |
67 |
|
67 | |||
@@ -154,7 +154,7 b' static PyObject* ZstdCompressionObj_flus' | |||||
154 |
|
154 | |||
155 | while (1) { |
|
155 | while (1) { | |
156 | Py_BEGIN_ALLOW_THREADS |
|
156 | Py_BEGIN_ALLOW_THREADS | |
157 |
zresult = ZSTD_compress |
|
157 | zresult = ZSTD_compressStream2(self->compressor->cctx, &self->output, | |
158 | &input, zFlushMode); |
|
158 | &input, zFlushMode); | |
159 | Py_END_ALLOW_THREADS |
|
159 | Py_END_ALLOW_THREADS | |
160 |
|
160 |
@@ -204,27 +204,27 b' static int ZstdCompressor_init(ZstdCompr' | |||||
204 | } |
|
204 | } | |
205 | } |
|
205 | } | |
206 | else { |
|
206 | else { | |
207 |
if (set_parameter(self->params, ZSTD_ |
|
207 | if (set_parameter(self->params, ZSTD_c_compressionLevel, level)) { | |
208 | return -1; |
|
208 | return -1; | |
209 | } |
|
209 | } | |
210 |
|
210 | |||
211 |
if (set_parameter(self->params, ZSTD_ |
|
211 | if (set_parameter(self->params, ZSTD_c_contentSizeFlag, | |
212 | writeContentSize ? PyObject_IsTrue(writeContentSize) : 1)) { |
|
212 | writeContentSize ? PyObject_IsTrue(writeContentSize) : 1)) { | |
213 | return -1; |
|
213 | return -1; | |
214 | } |
|
214 | } | |
215 |
|
215 | |||
216 |
if (set_parameter(self->params, ZSTD_ |
|
216 | if (set_parameter(self->params, ZSTD_c_checksumFlag, | |
217 | writeChecksum ? PyObject_IsTrue(writeChecksum) : 0)) { |
|
217 | writeChecksum ? PyObject_IsTrue(writeChecksum) : 0)) { | |
218 | return -1; |
|
218 | return -1; | |
219 | } |
|
219 | } | |
220 |
|
220 | |||
221 |
if (set_parameter(self->params, ZSTD_ |
|
221 | if (set_parameter(self->params, ZSTD_c_dictIDFlag, | |
222 | writeDictID ? PyObject_IsTrue(writeDictID) : 1)) { |
|
222 | writeDictID ? PyObject_IsTrue(writeDictID) : 1)) { | |
223 | return -1; |
|
223 | return -1; | |
224 | } |
|
224 | } | |
225 |
|
225 | |||
226 | if (threads) { |
|
226 | if (threads) { | |
227 |
if (set_parameter(self->params, ZSTD_ |
|
227 | if (set_parameter(self->params, ZSTD_c_nbWorkers, threads)) { | |
228 | return -1; |
|
228 | return -1; | |
229 | } |
|
229 | } | |
230 | } |
|
230 | } | |
@@ -344,7 +344,7 b' static PyObject* ZstdCompressor_copy_str' | |||||
344 | return NULL; |
|
344 | return NULL; | |
345 | } |
|
345 | } | |
346 |
|
346 | |||
347 | ZSTD_CCtx_reset(self->cctx); |
|
347 | ZSTD_CCtx_reset(self->cctx, ZSTD_reset_session_only); | |
348 |
|
348 | |||
349 | zresult = ZSTD_CCtx_setPledgedSrcSize(self->cctx, sourceSize); |
|
349 | zresult = ZSTD_CCtx_setPledgedSrcSize(self->cctx, sourceSize); | |
350 | if (ZSTD_isError(zresult)) { |
|
350 | if (ZSTD_isError(zresult)) { | |
@@ -391,7 +391,7 b' static PyObject* ZstdCompressor_copy_str' | |||||
391 |
|
391 | |||
392 | while (input.pos < input.size) { |
|
392 | while (input.pos < input.size) { | |
393 | Py_BEGIN_ALLOW_THREADS |
|
393 | Py_BEGIN_ALLOW_THREADS | |
394 |
zresult = ZSTD_compress |
|
394 | zresult = ZSTD_compressStream2(self->cctx, &output, &input, ZSTD_e_continue); | |
395 | Py_END_ALLOW_THREADS |
|
395 | Py_END_ALLOW_THREADS | |
396 |
|
396 | |||
397 | if (ZSTD_isError(zresult)) { |
|
397 | if (ZSTD_isError(zresult)) { | |
@@ -421,7 +421,7 b' static PyObject* ZstdCompressor_copy_str' | |||||
421 |
|
421 | |||
422 | while (1) { |
|
422 | while (1) { | |
423 | Py_BEGIN_ALLOW_THREADS |
|
423 | Py_BEGIN_ALLOW_THREADS | |
424 |
zresult = ZSTD_compress |
|
424 | zresult = ZSTD_compressStream2(self->cctx, &output, &input, ZSTD_e_end); | |
425 | Py_END_ALLOW_THREADS |
|
425 | Py_END_ALLOW_THREADS | |
426 |
|
426 | |||
427 | if (ZSTD_isError(zresult)) { |
|
427 | if (ZSTD_isError(zresult)) { | |
@@ -517,7 +517,7 b' static ZstdCompressionReader* ZstdCompre' | |||||
517 | goto except; |
|
517 | goto except; | |
518 | } |
|
518 | } | |
519 |
|
519 | |||
520 | ZSTD_CCtx_reset(self->cctx); |
|
520 | ZSTD_CCtx_reset(self->cctx, ZSTD_reset_session_only); | |
521 |
|
521 | |||
522 | zresult = ZSTD_CCtx_setPledgedSrcSize(self->cctx, sourceSize); |
|
522 | zresult = ZSTD_CCtx_setPledgedSrcSize(self->cctx, sourceSize); | |
523 | if (ZSTD_isError(zresult)) { |
|
523 | if (ZSTD_isError(zresult)) { | |
@@ -577,7 +577,7 b' static PyObject* ZstdCompressor_compress' | |||||
577 | goto finally; |
|
577 | goto finally; | |
578 | } |
|
578 | } | |
579 |
|
579 | |||
580 | ZSTD_CCtx_reset(self->cctx); |
|
580 | ZSTD_CCtx_reset(self->cctx, ZSTD_reset_session_only); | |
581 |
|
581 | |||
582 | destSize = ZSTD_compressBound(source.len); |
|
582 | destSize = ZSTD_compressBound(source.len); | |
583 | output = PyBytes_FromStringAndSize(NULL, destSize); |
|
583 | output = PyBytes_FromStringAndSize(NULL, destSize); | |
@@ -605,7 +605,7 b' static PyObject* ZstdCompressor_compress' | |||||
605 | /* By avoiding ZSTD_compress(), we don't necessarily write out content |
|
605 | /* By avoiding ZSTD_compress(), we don't necessarily write out content | |
606 | size. This means the argument to ZstdCompressor to control frame |
|
606 | size. This means the argument to ZstdCompressor to control frame | |
607 | parameters is honored. */ |
|
607 | parameters is honored. */ | |
608 |
zresult = ZSTD_compress |
|
608 | zresult = ZSTD_compressStream2(self->cctx, &outBuffer, &inBuffer, ZSTD_e_end); | |
609 | Py_END_ALLOW_THREADS |
|
609 | Py_END_ALLOW_THREADS | |
610 |
|
610 | |||
611 | if (ZSTD_isError(zresult)) { |
|
611 | if (ZSTD_isError(zresult)) { | |
@@ -651,7 +651,7 b' static ZstdCompressionObj* ZstdCompresso' | |||||
651 | return NULL; |
|
651 | return NULL; | |
652 | } |
|
652 | } | |
653 |
|
653 | |||
654 | ZSTD_CCtx_reset(self->cctx); |
|
654 | ZSTD_CCtx_reset(self->cctx, ZSTD_reset_session_only); | |
655 |
|
655 | |||
656 | zresult = ZSTD_CCtx_setPledgedSrcSize(self->cctx, inSize); |
|
656 | zresult = ZSTD_CCtx_setPledgedSrcSize(self->cctx, inSize); | |
657 | if (ZSTD_isError(zresult)) { |
|
657 | if (ZSTD_isError(zresult)) { | |
@@ -740,7 +740,7 b' static ZstdCompressorIterator* ZstdCompr' | |||||
740 | goto except; |
|
740 | goto except; | |
741 | } |
|
741 | } | |
742 |
|
742 | |||
743 | ZSTD_CCtx_reset(self->cctx); |
|
743 | ZSTD_CCtx_reset(self->cctx, ZSTD_reset_session_only); | |
744 |
|
744 | |||
745 | zresult = ZSTD_CCtx_setPledgedSrcSize(self->cctx, sourceSize); |
|
745 | zresult = ZSTD_CCtx_setPledgedSrcSize(self->cctx, sourceSize); | |
746 | if (ZSTD_isError(zresult)) { |
|
746 | if (ZSTD_isError(zresult)) { | |
@@ -794,16 +794,19 b' static ZstdCompressionWriter* ZstdCompre' | |||||
794 | "writer", |
|
794 | "writer", | |
795 | "size", |
|
795 | "size", | |
796 | "write_size", |
|
796 | "write_size", | |
|
797 | "write_return_read", | |||
797 | NULL |
|
798 | NULL | |
798 | }; |
|
799 | }; | |
799 |
|
800 | |||
800 | PyObject* writer; |
|
801 | PyObject* writer; | |
801 | ZstdCompressionWriter* result; |
|
802 | ZstdCompressionWriter* result; | |
|
803 | size_t zresult; | |||
802 | unsigned long long sourceSize = ZSTD_CONTENTSIZE_UNKNOWN; |
|
804 | unsigned long long sourceSize = ZSTD_CONTENTSIZE_UNKNOWN; | |
803 | size_t outSize = ZSTD_CStreamOutSize(); |
|
805 | size_t outSize = ZSTD_CStreamOutSize(); | |
|
806 | PyObject* writeReturnRead = NULL; | |||
804 |
|
807 | |||
805 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|Kk:stream_writer", kwlist, |
|
808 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|KkO:stream_writer", kwlist, | |
806 | &writer, &sourceSize, &outSize)) { |
|
809 | &writer, &sourceSize, &outSize, &writeReturnRead)) { | |
807 | return NULL; |
|
810 | return NULL; | |
808 | } |
|
811 | } | |
809 |
|
812 | |||
@@ -812,22 +815,38 b' static ZstdCompressionWriter* ZstdCompre' | |||||
812 | return NULL; |
|
815 | return NULL; | |
813 | } |
|
816 | } | |
814 |
|
817 | |||
815 | ZSTD_CCtx_reset(self->cctx); |
|
818 | ZSTD_CCtx_reset(self->cctx, ZSTD_reset_session_only); | |
|
819 | ||||
|
820 | zresult = ZSTD_CCtx_setPledgedSrcSize(self->cctx, sourceSize); | |||
|
821 | if (ZSTD_isError(zresult)) { | |||
|
822 | PyErr_Format(ZstdError, "error setting source size: %s", | |||
|
823 | ZSTD_getErrorName(zresult)); | |||
|
824 | return NULL; | |||
|
825 | } | |||
816 |
|
826 | |||
817 | result = (ZstdCompressionWriter*)PyObject_CallObject((PyObject*)&ZstdCompressionWriterType, NULL); |
|
827 | result = (ZstdCompressionWriter*)PyObject_CallObject((PyObject*)&ZstdCompressionWriterType, NULL); | |
818 | if (!result) { |
|
828 | if (!result) { | |
819 | return NULL; |
|
829 | return NULL; | |
820 | } |
|
830 | } | |
821 |
|
831 | |||
|
832 | result->output.dst = PyMem_Malloc(outSize); | |||
|
833 | if (!result->output.dst) { | |||
|
834 | Py_DECREF(result); | |||
|
835 | return (ZstdCompressionWriter*)PyErr_NoMemory(); | |||
|
836 | } | |||
|
837 | ||||
|
838 | result->output.pos = 0; | |||
|
839 | result->output.size = outSize; | |||
|
840 | ||||
822 | result->compressor = self; |
|
841 | result->compressor = self; | |
823 | Py_INCREF(result->compressor); |
|
842 | Py_INCREF(result->compressor); | |
824 |
|
843 | |||
825 | result->writer = writer; |
|
844 | result->writer = writer; | |
826 | Py_INCREF(result->writer); |
|
845 | Py_INCREF(result->writer); | |
827 |
|
846 | |||
828 | result->sourceSize = sourceSize; |
|
|||
829 | result->outSize = outSize; |
|
847 | result->outSize = outSize; | |
830 | result->bytesCompressed = 0; |
|
848 | result->bytesCompressed = 0; | |
|
849 | result->writeReturnRead = writeReturnRead ? PyObject_IsTrue(writeReturnRead) : 0; | |||
831 |
|
850 | |||
832 | return result; |
|
851 | return result; | |
833 | } |
|
852 | } | |
@@ -853,7 +872,7 b' static ZstdCompressionChunker* ZstdCompr' | |||||
853 | return NULL; |
|
872 | return NULL; | |
854 | } |
|
873 | } | |
855 |
|
874 | |||
856 | ZSTD_CCtx_reset(self->cctx); |
|
875 | ZSTD_CCtx_reset(self->cctx, ZSTD_reset_session_only); | |
857 |
|
876 | |||
858 | zresult = ZSTD_CCtx_setPledgedSrcSize(self->cctx, sourceSize); |
|
877 | zresult = ZSTD_CCtx_setPledgedSrcSize(self->cctx, sourceSize); | |
859 | if (ZSTD_isError(zresult)) { |
|
878 | if (ZSTD_isError(zresult)) { | |
@@ -1115,7 +1134,7 b' static void compress_worker(WorkerState*' | |||||
1115 | break; |
|
1134 | break; | |
1116 | } |
|
1135 | } | |
1117 |
|
1136 | |||
1118 |
zresult = ZSTD_compress |
|
1137 | zresult = ZSTD_compressStream2(state->cctx, &opOutBuffer, &opInBuffer, ZSTD_e_end); | |
1119 | if (ZSTD_isError(zresult)) { |
|
1138 | if (ZSTD_isError(zresult)) { | |
1120 | state->error = WorkerError_zstd; |
|
1139 | state->error = WorkerError_zstd; | |
1121 | state->zresult = zresult; |
|
1140 | state->zresult = zresult; |
@@ -57,7 +57,7 b' feedcompressor:' | |||||
57 | /* If we have data left in the input, consume it. */ |
|
57 | /* If we have data left in the input, consume it. */ | |
58 | if (self->input.pos < self->input.size) { |
|
58 | if (self->input.pos < self->input.size) { | |
59 | Py_BEGIN_ALLOW_THREADS |
|
59 | Py_BEGIN_ALLOW_THREADS | |
60 |
zresult = ZSTD_compress |
|
60 | zresult = ZSTD_compressStream2(self->compressor->cctx, &self->output, | |
61 | &self->input, ZSTD_e_continue); |
|
61 | &self->input, ZSTD_e_continue); | |
62 | Py_END_ALLOW_THREADS |
|
62 | Py_END_ALLOW_THREADS | |
63 |
|
63 | |||
@@ -127,7 +127,7 b' feedcompressor:' | |||||
127 | self->input.size = 0; |
|
127 | self->input.size = 0; | |
128 | self->input.pos = 0; |
|
128 | self->input.pos = 0; | |
129 |
|
129 | |||
130 |
zresult = ZSTD_compress |
|
130 | zresult = ZSTD_compressStream2(self->compressor->cctx, &self->output, | |
131 | &self->input, ZSTD_e_end); |
|
131 | &self->input, ZSTD_e_end); | |
132 | if (ZSTD_isError(zresult)) { |
|
132 | if (ZSTD_isError(zresult)) { | |
133 | PyErr_Format(ZstdError, "error ending compression stream: %s", |
|
133 | PyErr_Format(ZstdError, "error ending compression stream: %s", | |
@@ -152,7 +152,7 b' feedcompressor:' | |||||
152 | self->input.pos = 0; |
|
152 | self->input.pos = 0; | |
153 |
|
153 | |||
154 | Py_BEGIN_ALLOW_THREADS |
|
154 | Py_BEGIN_ALLOW_THREADS | |
155 |
zresult = ZSTD_compress |
|
155 | zresult = ZSTD_compressStream2(self->compressor->cctx, &self->output, | |
156 | &self->input, ZSTD_e_continue); |
|
156 | &self->input, ZSTD_e_continue); | |
157 | Py_END_ALLOW_THREADS |
|
157 | Py_END_ALLOW_THREADS | |
158 |
|
158 |
@@ -32,6 +32,9 b' void constants_module_init(PyObject* mod' | |||||
32 | ZstdError = PyErr_NewException("zstd.ZstdError", NULL, NULL); |
|
32 | ZstdError = PyErr_NewException("zstd.ZstdError", NULL, NULL); | |
33 | PyModule_AddObject(mod, "ZstdError", ZstdError); |
|
33 | PyModule_AddObject(mod, "ZstdError", ZstdError); | |
34 |
|
34 | |||
|
35 | PyModule_AddIntConstant(mod, "FLUSH_BLOCK", 0); | |||
|
36 | PyModule_AddIntConstant(mod, "FLUSH_FRAME", 1); | |||
|
37 | ||||
35 | PyModule_AddIntConstant(mod, "COMPRESSOBJ_FLUSH_FINISH", compressorobj_flush_finish); |
|
38 | PyModule_AddIntConstant(mod, "COMPRESSOBJ_FLUSH_FINISH", compressorobj_flush_finish); | |
36 | PyModule_AddIntConstant(mod, "COMPRESSOBJ_FLUSH_BLOCK", compressorobj_flush_block); |
|
39 | PyModule_AddIntConstant(mod, "COMPRESSOBJ_FLUSH_BLOCK", compressorobj_flush_block); | |
37 |
|
40 | |||
@@ -77,8 +80,11 b' void constants_module_init(PyObject* mod' | |||||
77 | PyModule_AddIntConstant(mod, "HASHLOG3_MAX", ZSTD_HASHLOG3_MAX); |
|
80 | PyModule_AddIntConstant(mod, "HASHLOG3_MAX", ZSTD_HASHLOG3_MAX); | |
78 | PyModule_AddIntConstant(mod, "SEARCHLOG_MIN", ZSTD_SEARCHLOG_MIN); |
|
81 | PyModule_AddIntConstant(mod, "SEARCHLOG_MIN", ZSTD_SEARCHLOG_MIN); | |
79 | PyModule_AddIntConstant(mod, "SEARCHLOG_MAX", ZSTD_SEARCHLOG_MAX); |
|
82 | PyModule_AddIntConstant(mod, "SEARCHLOG_MAX", ZSTD_SEARCHLOG_MAX); | |
80 |
PyModule_AddIntConstant(mod, " |
|
83 | PyModule_AddIntConstant(mod, "MINMATCH_MIN", ZSTD_MINMATCH_MIN); | |
81 |
PyModule_AddIntConstant(mod, " |
|
84 | PyModule_AddIntConstant(mod, "MINMATCH_MAX", ZSTD_MINMATCH_MAX); | |
|
85 | /* TODO SEARCHLENGTH_* is deprecated. */ | |||
|
86 | PyModule_AddIntConstant(mod, "SEARCHLENGTH_MIN", ZSTD_MINMATCH_MIN); | |||
|
87 | PyModule_AddIntConstant(mod, "SEARCHLENGTH_MAX", ZSTD_MINMATCH_MAX); | |||
82 | PyModule_AddIntConstant(mod, "TARGETLENGTH_MIN", ZSTD_TARGETLENGTH_MIN); |
|
88 | PyModule_AddIntConstant(mod, "TARGETLENGTH_MIN", ZSTD_TARGETLENGTH_MIN); | |
83 | PyModule_AddIntConstant(mod, "TARGETLENGTH_MAX", ZSTD_TARGETLENGTH_MAX); |
|
89 | PyModule_AddIntConstant(mod, "TARGETLENGTH_MAX", ZSTD_TARGETLENGTH_MAX); | |
84 | PyModule_AddIntConstant(mod, "LDM_MINMATCH_MIN", ZSTD_LDM_MINMATCH_MIN); |
|
90 | PyModule_AddIntConstant(mod, "LDM_MINMATCH_MIN", ZSTD_LDM_MINMATCH_MIN); | |
@@ -93,6 +99,7 b' void constants_module_init(PyObject* mod' | |||||
93 | PyModule_AddIntConstant(mod, "STRATEGY_BTLAZY2", ZSTD_btlazy2); |
|
99 | PyModule_AddIntConstant(mod, "STRATEGY_BTLAZY2", ZSTD_btlazy2); | |
94 | PyModule_AddIntConstant(mod, "STRATEGY_BTOPT", ZSTD_btopt); |
|
100 | PyModule_AddIntConstant(mod, "STRATEGY_BTOPT", ZSTD_btopt); | |
95 | PyModule_AddIntConstant(mod, "STRATEGY_BTULTRA", ZSTD_btultra); |
|
101 | PyModule_AddIntConstant(mod, "STRATEGY_BTULTRA", ZSTD_btultra); | |
|
102 | PyModule_AddIntConstant(mod, "STRATEGY_BTULTRA2", ZSTD_btultra2); | |||
96 |
|
103 | |||
97 | PyModule_AddIntConstant(mod, "DICT_TYPE_AUTO", ZSTD_dct_auto); |
|
104 | PyModule_AddIntConstant(mod, "DICT_TYPE_AUTO", ZSTD_dct_auto); | |
98 | PyModule_AddIntConstant(mod, "DICT_TYPE_RAWCONTENT", ZSTD_dct_rawContent); |
|
105 | PyModule_AddIntConstant(mod, "DICT_TYPE_RAWCONTENT", ZSTD_dct_rawContent); |
This diff has been collapsed as it changes many lines, (511 lines changed) Show them Hide them | |||||
@@ -102,6 +102,114 b' static PyObject* reader_isatty(PyObject*' | |||||
102 | Py_RETURN_FALSE; |
|
102 | Py_RETURN_FALSE; | |
103 | } |
|
103 | } | |
104 |
|
104 | |||
|
105 | /** | |||
|
106 | * Read available input. | |||
|
107 | * | |||
|
108 | * Returns 0 if no data was added to input. | |||
|
109 | * Returns 1 if new input data is available. | |||
|
110 | * Returns -1 on error and sets a Python exception as a side-effect. | |||
|
111 | */ | |||
|
112 | int read_decompressor_input(ZstdDecompressionReader* self) { | |||
|
113 | if (self->finishedInput) { | |||
|
114 | return 0; | |||
|
115 | } | |||
|
116 | ||||
|
117 | if (self->input.pos != self->input.size) { | |||
|
118 | return 0; | |||
|
119 | } | |||
|
120 | ||||
|
121 | if (self->reader) { | |||
|
122 | Py_buffer buffer; | |||
|
123 | ||||
|
124 | assert(self->readResult == NULL); | |||
|
125 | self->readResult = PyObject_CallMethod(self->reader, "read", | |||
|
126 | "k", self->readSize); | |||
|
127 | if (NULL == self->readResult) { | |||
|
128 | return -1; | |||
|
129 | } | |||
|
130 | ||||
|
131 | memset(&buffer, 0, sizeof(buffer)); | |||
|
132 | ||||
|
133 | if (0 != PyObject_GetBuffer(self->readResult, &buffer, PyBUF_CONTIG_RO)) { | |||
|
134 | return -1; | |||
|
135 | } | |||
|
136 | ||||
|
137 | /* EOF */ | |||
|
138 | if (0 == buffer.len) { | |||
|
139 | self->finishedInput = 1; | |||
|
140 | Py_CLEAR(self->readResult); | |||
|
141 | } | |||
|
142 | else { | |||
|
143 | self->input.src = buffer.buf; | |||
|
144 | self->input.size = buffer.len; | |||
|
145 | self->input.pos = 0; | |||
|
146 | } | |||
|
147 | ||||
|
148 | PyBuffer_Release(&buffer); | |||
|
149 | } | |||
|
150 | else { | |||
|
151 | assert(self->buffer.buf); | |||
|
152 | /* | |||
|
153 | * We should only get here once since expectation is we always | |||
|
154 | * exhaust input buffer before reading again. | |||
|
155 | */ | |||
|
156 | assert(self->input.src == NULL); | |||
|
157 | ||||
|
158 | self->input.src = self->buffer.buf; | |||
|
159 | self->input.size = self->buffer.len; | |||
|
160 | self->input.pos = 0; | |||
|
161 | } | |||
|
162 | ||||
|
163 | return 1; | |||
|
164 | } | |||
|
165 | ||||
|
166 | /** | |||
|
167 | * Decompresses available input into an output buffer. | |||
|
168 | * | |||
|
169 | * Returns 0 if we need more input. | |||
|
170 | * Returns 1 if output buffer should be emitted. | |||
|
171 | * Returns -1 on error and sets a Python exception. | |||
|
172 | */ | |||
|
173 | int decompress_input(ZstdDecompressionReader* self, ZSTD_outBuffer* output) { | |||
|
174 | size_t zresult; | |||
|
175 | ||||
|
176 | if (self->input.pos >= self->input.size) { | |||
|
177 | return 0; | |||
|
178 | } | |||
|
179 | ||||
|
180 | Py_BEGIN_ALLOW_THREADS | |||
|
181 | zresult = ZSTD_decompressStream(self->decompressor->dctx, output, &self->input); | |||
|
182 | Py_END_ALLOW_THREADS | |||
|
183 | ||||
|
184 | /* Input exhausted. Clear our state tracking. */ | |||
|
185 | if (self->input.pos == self->input.size) { | |||
|
186 | memset(&self->input, 0, sizeof(self->input)); | |||
|
187 | Py_CLEAR(self->readResult); | |||
|
188 | ||||
|
189 | if (self->buffer.buf) { | |||
|
190 | self->finishedInput = 1; | |||
|
191 | } | |||
|
192 | } | |||
|
193 | ||||
|
194 | if (ZSTD_isError(zresult)) { | |||
|
195 | PyErr_Format(ZstdError, "zstd decompress error: %s", ZSTD_getErrorName(zresult)); | |||
|
196 | return -1; | |||
|
197 | } | |||
|
198 | ||||
|
199 | /* We fulfilled the full read request. Signal to emit. */ | |||
|
200 | if (output->pos && output->pos == output->size) { | |||
|
201 | return 1; | |||
|
202 | } | |||
|
203 | /* We're at the end of a frame and we aren't allowed to return data | |||
|
204 | spanning frames. */ | |||
|
205 | else if (output->pos && zresult == 0 && !self->readAcrossFrames) { | |||
|
206 | return 1; | |||
|
207 | } | |||
|
208 | ||||
|
209 | /* There is more room in the output. Signal to collect more data. */ | |||
|
210 | return 0; | |||
|
211 | } | |||
|
212 | ||||
105 | static PyObject* reader_read(ZstdDecompressionReader* self, PyObject* args, PyObject* kwargs) { |
|
213 | static PyObject* reader_read(ZstdDecompressionReader* self, PyObject* args, PyObject* kwargs) { | |
106 | static char* kwlist[] = { |
|
214 | static char* kwlist[] = { | |
107 | "size", |
|
215 | "size", | |
@@ -113,26 +221,30 b' static PyObject* reader_read(ZstdDecompr' | |||||
113 | char* resultBuffer; |
|
221 | char* resultBuffer; | |
114 | Py_ssize_t resultSize; |
|
222 | Py_ssize_t resultSize; | |
115 | ZSTD_outBuffer output; |
|
223 | ZSTD_outBuffer output; | |
116 | size_t zresult; |
|
224 | int decompressResult, readResult; | |
117 |
|
225 | |||
118 | if (self->closed) { |
|
226 | if (self->closed) { | |
119 | PyErr_SetString(PyExc_ValueError, "stream is closed"); |
|
227 | PyErr_SetString(PyExc_ValueError, "stream is closed"); | |
120 | return NULL; |
|
228 | return NULL; | |
121 | } |
|
229 | } | |
122 |
|
230 | |||
123 | if (self->finishedOutput) { |
|
231 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|n", kwlist, &size)) { | |
124 | return PyBytes_FromStringAndSize("", 0); |
|
|||
125 | } |
|
|||
126 |
|
||||
127 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "n", kwlist, &size)) { |
|
|||
128 | return NULL; |
|
232 | return NULL; | |
129 | } |
|
233 | } | |
130 |
|
234 | |||
131 | if (size < 1) { |
|
235 | if (size < -1) { | |
132 |
PyErr_SetString(PyExc_ValueError, "cannot read negative |
|
236 | PyErr_SetString(PyExc_ValueError, "cannot read negative amounts less than -1"); | |
133 | return NULL; |
|
237 | return NULL; | |
134 | } |
|
238 | } | |
135 |
|
239 | |||
|
240 | if (size == -1) { | |||
|
241 | return PyObject_CallMethod((PyObject*)self, "readall", NULL); | |||
|
242 | } | |||
|
243 | ||||
|
244 | if (self->finishedOutput || size == 0) { | |||
|
245 | return PyBytes_FromStringAndSize("", 0); | |||
|
246 | } | |||
|
247 | ||||
136 | result = PyBytes_FromStringAndSize(NULL, size); |
|
248 | result = PyBytes_FromStringAndSize(NULL, size); | |
137 | if (NULL == result) { |
|
249 | if (NULL == result) { | |
138 | return NULL; |
|
250 | return NULL; | |
@@ -146,85 +258,38 b' static PyObject* reader_read(ZstdDecompr' | |||||
146 |
|
258 | |||
147 | readinput: |
|
259 | readinput: | |
148 |
|
260 | |||
149 | /* Consume input data left over from last time. */ |
|
261 | decompressResult = decompress_input(self, &output); | |
150 | if (self->input.pos < self->input.size) { |
|
|||
151 | Py_BEGIN_ALLOW_THREADS |
|
|||
152 | zresult = ZSTD_decompress_generic(self->decompressor->dctx, |
|
|||
153 | &output, &self->input); |
|
|||
154 | Py_END_ALLOW_THREADS |
|
|||
155 |
|
262 | |||
156 | /* Input exhausted. Clear our state tracking. */ |
|
263 | if (-1 == decompressResult) { | |
157 | if (self->input.pos == self->input.size) { |
|
264 | Py_XDECREF(result); | |
158 | memset(&self->input, 0, sizeof(self->input)); |
|
265 | return NULL; | |
159 | Py_CLEAR(self->readResult); |
|
266 | } | |
|
267 | else if (0 == decompressResult) { } | |||
|
268 | else if (1 == decompressResult) { | |||
|
269 | self->bytesDecompressed += output.pos; | |||
160 |
|
270 | |||
161 | if (self->buffer.buf) { |
|
271 | if (output.pos != output.size) { | |
162 | self->finishedInput = 1; |
|
272 | if (safe_pybytes_resize(&result, output.pos)) { | |
|
273 | Py_XDECREF(result); | |||
|
274 | return NULL; | |||
163 | } |
|
275 | } | |
164 | } |
|
276 | } | |
165 |
|
277 | return result; | ||
166 | if (ZSTD_isError(zresult)) { |
|
278 | } | |
167 | PyErr_Format(ZstdError, "zstd decompress error: %s", ZSTD_getErrorName(zresult)); |
|
279 | else { | |
168 | return NULL; |
|
280 | assert(0); | |
169 | } |
|
|||
170 | else if (0 == zresult) { |
|
|||
171 | self->finishedOutput = 1; |
|
|||
172 | } |
|
|||
173 |
|
||||
174 | /* We fulfilled the full read request. Emit it. */ |
|
|||
175 | if (output.pos && output.pos == output.size) { |
|
|||
176 | self->bytesDecompressed += output.size; |
|
|||
177 | return result; |
|
|||
178 | } |
|
|||
179 |
|
||||
180 | /* |
|
|||
181 | * There is more room in the output. Fall through to try to collect |
|
|||
182 | * more data so we can try to fill the output. |
|
|||
183 | */ |
|
|||
184 | } |
|
281 | } | |
185 |
|
282 | |||
186 | if (!self->finishedInput) { |
|
283 | readResult = read_decompressor_input(self); | |
187 | if (self->reader) { |
|
|||
188 | Py_buffer buffer; |
|
|||
189 |
|
||||
190 | assert(self->readResult == NULL); |
|
|||
191 | self->readResult = PyObject_CallMethod(self->reader, "read", |
|
|||
192 | "k", self->readSize); |
|
|||
193 | if (NULL == self->readResult) { |
|
|||
194 | return NULL; |
|
|||
195 | } |
|
|||
196 |
|
||||
197 | memset(&buffer, 0, sizeof(buffer)); |
|
|||
198 |
|
||||
199 | if (0 != PyObject_GetBuffer(self->readResult, &buffer, PyBUF_CONTIG_RO)) { |
|
|||
200 | return NULL; |
|
|||
201 | } |
|
|||
202 |
|
284 | |||
203 | /* EOF */ |
|
285 | if (-1 == readResult) { | |
204 | if (0 == buffer.len) { |
|
286 | Py_XDECREF(result); | |
205 | self->finishedInput = 1; |
|
287 | return NULL; | |
206 | Py_CLEAR(self->readResult); |
|
288 | } | |
207 | } |
|
289 | else if (0 == readResult) {} | |
208 | else { |
|
290 | else if (1 == readResult) {} | |
209 | self->input.src = buffer.buf; |
|
291 | else { | |
210 | self->input.size = buffer.len; |
|
292 | assert(0); | |
211 | self->input.pos = 0; |
|
|||
212 | } |
|
|||
213 |
|
||||
214 | PyBuffer_Release(&buffer); |
|
|||
215 | } |
|
|||
216 | else { |
|
|||
217 | assert(self->buffer.buf); |
|
|||
218 | /* |
|
|||
219 | * We should only get here once since above block will exhaust |
|
|||
220 | * source buffer until finishedInput is set. |
|
|||
221 | */ |
|
|||
222 | assert(self->input.src == NULL); |
|
|||
223 |
|
||||
224 | self->input.src = self->buffer.buf; |
|
|||
225 | self->input.size = self->buffer.len; |
|
|||
226 | self->input.pos = 0; |
|
|||
227 | } |
|
|||
228 | } |
|
293 | } | |
229 |
|
294 | |||
230 | if (self->input.size) { |
|
295 | if (self->input.size) { | |
@@ -242,18 +307,288 b' readinput:' | |||||
242 | return result; |
|
307 | return result; | |
243 | } |
|
308 | } | |
244 |
|
309 | |||
|
310 | static PyObject* reader_read1(ZstdDecompressionReader* self, PyObject* args, PyObject* kwargs) { | |||
|
311 | static char* kwlist[] = { | |||
|
312 | "size", | |||
|
313 | NULL | |||
|
314 | }; | |||
|
315 | ||||
|
316 | Py_ssize_t size = -1; | |||
|
317 | PyObject* result = NULL; | |||
|
318 | char* resultBuffer; | |||
|
319 | Py_ssize_t resultSize; | |||
|
320 | ZSTD_outBuffer output; | |||
|
321 | ||||
|
322 | if (self->closed) { | |||
|
323 | PyErr_SetString(PyExc_ValueError, "stream is closed"); | |||
|
324 | return NULL; | |||
|
325 | } | |||
|
326 | ||||
|
327 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|n", kwlist, &size)) { | |||
|
328 | return NULL; | |||
|
329 | } | |||
|
330 | ||||
|
331 | if (size < -1) { | |||
|
332 | PyErr_SetString(PyExc_ValueError, "cannot read negative amounts less than -1"); | |||
|
333 | return NULL; | |||
|
334 | } | |||
|
335 | ||||
|
336 | if (self->finishedOutput || size == 0) { | |||
|
337 | return PyBytes_FromStringAndSize("", 0); | |||
|
338 | } | |||
|
339 | ||||
|
340 | if (size == -1) { | |||
|
341 | size = ZSTD_DStreamOutSize(); | |||
|
342 | } | |||
|
343 | ||||
|
344 | result = PyBytes_FromStringAndSize(NULL, size); | |||
|
345 | if (NULL == result) { | |||
|
346 | return NULL; | |||
|
347 | } | |||
|
348 | ||||
|
349 | PyBytes_AsStringAndSize(result, &resultBuffer, &resultSize); | |||
|
350 | ||||
|
351 | output.dst = resultBuffer; | |||
|
352 | output.size = resultSize; | |||
|
353 | output.pos = 0; | |||
|
354 | ||||
|
355 | /* read1() is supposed to use at most 1 read() from the underlying stream. | |||
|
356 | * However, we can't satisfy this requirement with decompression due to the | |||
|
357 | * nature of how decompression works. Our strategy is to read + decompress | |||
|
358 | * until we get any output, at which point we return. This satisfies the | |||
|
359 | * intent of the read1() API to limit read operations. | |||
|
360 | */ | |||
|
361 | while (!self->finishedInput) { | |||
|
362 | int readResult, decompressResult; | |||
|
363 | ||||
|
364 | readResult = read_decompressor_input(self); | |||
|
365 | if (-1 == readResult) { | |||
|
366 | Py_XDECREF(result); | |||
|
367 | return NULL; | |||
|
368 | } | |||
|
369 | else if (0 == readResult || 1 == readResult) { } | |||
|
370 | else { | |||
|
371 | assert(0); | |||
|
372 | } | |||
|
373 | ||||
|
374 | decompressResult = decompress_input(self, &output); | |||
|
375 | ||||
|
376 | if (-1 == decompressResult) { | |||
|
377 | Py_XDECREF(result); | |||
|
378 | return NULL; | |||
|
379 | } | |||
|
380 | else if (0 == decompressResult || 1 == decompressResult) { } | |||
|
381 | else { | |||
|
382 | assert(0); | |||
|
383 | } | |||
|
384 | ||||
|
385 | if (output.pos) { | |||
|
386 | break; | |||
|
387 | } | |||
|
388 | } | |||
|
389 | ||||
|
390 | self->bytesDecompressed += output.pos; | |||
|
391 | if (safe_pybytes_resize(&result, output.pos)) { | |||
|
392 | Py_XDECREF(result); | |||
|
393 | return NULL; | |||
|
394 | } | |||
|
395 | ||||
|
396 | return result; | |||
|
397 | } | |||
|
398 | ||||
|
399 | static PyObject* reader_readinto(ZstdDecompressionReader* self, PyObject* args) { | |||
|
400 | Py_buffer dest; | |||
|
401 | ZSTD_outBuffer output; | |||
|
402 | int decompressResult, readResult; | |||
|
403 | PyObject* result = NULL; | |||
|
404 | ||||
|
405 | if (self->closed) { | |||
|
406 | PyErr_SetString(PyExc_ValueError, "stream is closed"); | |||
|
407 | return NULL; | |||
|
408 | } | |||
|
409 | ||||
|
410 | if (self->finishedOutput) { | |||
|
411 | return PyLong_FromLong(0); | |||
|
412 | } | |||
|
413 | ||||
|
414 | if (!PyArg_ParseTuple(args, "w*:readinto", &dest)) { | |||
|
415 | return NULL; | |||
|
416 | } | |||
|
417 | ||||
|
418 | if (!PyBuffer_IsContiguous(&dest, 'C') || dest.ndim > 1) { | |||
|
419 | PyErr_SetString(PyExc_ValueError, | |||
|
420 | "destination buffer should be contiguous and have at most one dimension"); | |||
|
421 | goto finally; | |||
|
422 | } | |||
|
423 | ||||
|
424 | output.dst = dest.buf; | |||
|
425 | output.size = dest.len; | |||
|
426 | output.pos = 0; | |||
|
427 | ||||
|
428 | readinput: | |||
|
429 | ||||
|
430 | decompressResult = decompress_input(self, &output); | |||
|
431 | ||||
|
432 | if (-1 == decompressResult) { | |||
|
433 | goto finally; | |||
|
434 | } | |||
|
435 | else if (0 == decompressResult) { } | |||
|
436 | else if (1 == decompressResult) { | |||
|
437 | self->bytesDecompressed += output.pos; | |||
|
438 | result = PyLong_FromSize_t(output.pos); | |||
|
439 | goto finally; | |||
|
440 | } | |||
|
441 | else { | |||
|
442 | assert(0); | |||
|
443 | } | |||
|
444 | ||||
|
445 | readResult = read_decompressor_input(self); | |||
|
446 | ||||
|
447 | if (-1 == readResult) { | |||
|
448 | goto finally; | |||
|
449 | } | |||
|
450 | else if (0 == readResult) {} | |||
|
451 | else if (1 == readResult) {} | |||
|
452 | else { | |||
|
453 | assert(0); | |||
|
454 | } | |||
|
455 | ||||
|
456 | if (self->input.size) { | |||
|
457 | goto readinput; | |||
|
458 | } | |||
|
459 | ||||
|
460 | /* EOF */ | |||
|
461 | self->bytesDecompressed += output.pos; | |||
|
462 | result = PyLong_FromSize_t(output.pos); | |||
|
463 | ||||
|
464 | finally: | |||
|
465 | PyBuffer_Release(&dest); | |||
|
466 | ||||
|
467 | return result; | |||
|
468 | } | |||
|
469 | ||||
|
470 | static PyObject* reader_readinto1(ZstdDecompressionReader* self, PyObject* args) { | |||
|
471 | Py_buffer dest; | |||
|
472 | ZSTD_outBuffer output; | |||
|
473 | PyObject* result = NULL; | |||
|
474 | ||||
|
475 | if (self->closed) { | |||
|
476 | PyErr_SetString(PyExc_ValueError, "stream is closed"); | |||
|
477 | return NULL; | |||
|
478 | } | |||
|
479 | ||||
|
480 | if (self->finishedOutput) { | |||
|
481 | return PyLong_FromLong(0); | |||
|
482 | } | |||
|
483 | ||||
|
484 | if (!PyArg_ParseTuple(args, "w*:readinto1", &dest)) { | |||
|
485 | return NULL; | |||
|
486 | } | |||
|
487 | ||||
|
488 | if (!PyBuffer_IsContiguous(&dest, 'C') || dest.ndim > 1) { | |||
|
489 | PyErr_SetString(PyExc_ValueError, | |||
|
490 | "destination buffer should be contiguous and have at most one dimension"); | |||
|
491 | goto finally; | |||
|
492 | } | |||
|
493 | ||||
|
494 | output.dst = dest.buf; | |||
|
495 | output.size = dest.len; | |||
|
496 | output.pos = 0; | |||
|
497 | ||||
|
498 | while (!self->finishedInput && !self->finishedOutput) { | |||
|
499 | int decompressResult, readResult; | |||
|
500 | ||||
|
501 | readResult = read_decompressor_input(self); | |||
|
502 | ||||
|
503 | if (-1 == readResult) { | |||
|
504 | goto finally; | |||
|
505 | } | |||
|
506 | else if (0 == readResult || 1 == readResult) {} | |||
|
507 | else { | |||
|
508 | assert(0); | |||
|
509 | } | |||
|
510 | ||||
|
511 | decompressResult = decompress_input(self, &output); | |||
|
512 | ||||
|
513 | if (-1 == decompressResult) { | |||
|
514 | goto finally; | |||
|
515 | } | |||
|
516 | else if (0 == decompressResult || 1 == decompressResult) {} | |||
|
517 | else { | |||
|
518 | assert(0); | |||
|
519 | } | |||
|
520 | ||||
|
521 | if (output.pos) { | |||
|
522 | break; | |||
|
523 | } | |||
|
524 | } | |||
|
525 | ||||
|
526 | self->bytesDecompressed += output.pos; | |||
|
527 | result = PyLong_FromSize_t(output.pos); | |||
|
528 | ||||
|
529 | finally: | |||
|
530 | PyBuffer_Release(&dest); | |||
|
531 | ||||
|
532 | return result; | |||
|
533 | } | |||
|
534 | ||||
245 | static PyObject* reader_readall(PyObject* self) { |
|
535 | static PyObject* reader_readall(PyObject* self) { | |
246 | PyErr_SetNone(PyExc_NotImplementedError); |
|
536 | PyObject* chunks = NULL; | |
247 | return NULL; |
|
537 | PyObject* empty = NULL; | |
|
538 | PyObject* result = NULL; | |||
|
539 | ||||
|
540 | /* Our strategy is to collect chunks into a list then join all the | |||
|
541 | * chunks at the end. We could potentially use e.g. an io.BytesIO. But | |||
|
542 | * this feels simple enough to implement and avoids potentially expensive | |||
|
543 | * reallocations of large buffers. | |||
|
544 | */ | |||
|
545 | chunks = PyList_New(0); | |||
|
546 | if (NULL == chunks) { | |||
|
547 | return NULL; | |||
|
548 | } | |||
|
549 | ||||
|
550 | while (1) { | |||
|
551 | PyObject* chunk = PyObject_CallMethod(self, "read", "i", 1048576); | |||
|
552 | if (NULL == chunk) { | |||
|
553 | Py_DECREF(chunks); | |||
|
554 | return NULL; | |||
|
555 | } | |||
|
556 | ||||
|
557 | if (!PyBytes_Size(chunk)) { | |||
|
558 | Py_DECREF(chunk); | |||
|
559 | break; | |||
|
560 | } | |||
|
561 | ||||
|
562 | if (PyList_Append(chunks, chunk)) { | |||
|
563 | Py_DECREF(chunk); | |||
|
564 | Py_DECREF(chunks); | |||
|
565 | return NULL; | |||
|
566 | } | |||
|
567 | ||||
|
568 | Py_DECREF(chunk); | |||
|
569 | } | |||
|
570 | ||||
|
571 | empty = PyBytes_FromStringAndSize("", 0); | |||
|
572 | if (NULL == empty) { | |||
|
573 | Py_DECREF(chunks); | |||
|
574 | return NULL; | |||
|
575 | } | |||
|
576 | ||||
|
577 | result = PyObject_CallMethod(empty, "join", "O", chunks); | |||
|
578 | ||||
|
579 | Py_DECREF(empty); | |||
|
580 | Py_DECREF(chunks); | |||
|
581 | ||||
|
582 | return result; | |||
248 | } |
|
583 | } | |
249 |
|
584 | |||
250 | static PyObject* reader_readline(PyObject* self) { |
|
585 | static PyObject* reader_readline(PyObject* self) { | |
251 | PyErr_SetNone(PyExc_NotImplementedError); |
|
586 | set_unsupported_operation(); | |
252 | return NULL; |
|
587 | return NULL; | |
253 | } |
|
588 | } | |
254 |
|
589 | |||
255 | static PyObject* reader_readlines(PyObject* self) { |
|
590 | static PyObject* reader_readlines(PyObject* self) { | |
256 | PyErr_SetNone(PyExc_NotImplementedError); |
|
591 | set_unsupported_operation(); | |
257 | return NULL; |
|
592 | return NULL; | |
258 | } |
|
593 | } | |
259 |
|
594 | |||
@@ -345,12 +680,12 b' static PyObject* reader_writelines(PyObj' | |||||
345 | } |
|
680 | } | |
346 |
|
681 | |||
347 | static PyObject* reader_iter(PyObject* self) { |
|
682 | static PyObject* reader_iter(PyObject* self) { | |
348 | PyErr_SetNone(PyExc_NotImplementedError); |
|
683 | set_unsupported_operation(); | |
349 | return NULL; |
|
684 | return NULL; | |
350 | } |
|
685 | } | |
351 |
|
686 | |||
352 | static PyObject* reader_iternext(PyObject* self) { |
|
687 | static PyObject* reader_iternext(PyObject* self) { | |
353 | PyErr_SetNone(PyExc_NotImplementedError); |
|
688 | set_unsupported_operation(); | |
354 | return NULL; |
|
689 | return NULL; | |
355 | } |
|
690 | } | |
356 |
|
691 | |||
@@ -367,6 +702,10 b' static PyMethodDef reader_methods[] = {' | |||||
367 | PyDoc_STR("Returns True") }, |
|
702 | PyDoc_STR("Returns True") }, | |
368 | { "read", (PyCFunction)reader_read, METH_VARARGS | METH_KEYWORDS, |
|
703 | { "read", (PyCFunction)reader_read, METH_VARARGS | METH_KEYWORDS, | |
369 | PyDoc_STR("read compressed data") }, |
|
704 | PyDoc_STR("read compressed data") }, | |
|
705 | { "read1", (PyCFunction)reader_read1, METH_VARARGS | METH_KEYWORDS, | |||
|
706 | PyDoc_STR("read compressed data") }, | |||
|
707 | { "readinto", (PyCFunction)reader_readinto, METH_VARARGS, NULL }, | |||
|
708 | { "readinto1", (PyCFunction)reader_readinto1, METH_VARARGS, NULL }, | |||
370 | { "readall", (PyCFunction)reader_readall, METH_NOARGS, PyDoc_STR("Not implemented") }, |
|
709 | { "readall", (PyCFunction)reader_readall, METH_NOARGS, PyDoc_STR("Not implemented") }, | |
371 | { "readline", (PyCFunction)reader_readline, METH_NOARGS, PyDoc_STR("Not implemented") }, |
|
710 | { "readline", (PyCFunction)reader_readline, METH_NOARGS, PyDoc_STR("Not implemented") }, | |
372 | { "readlines", (PyCFunction)reader_readlines, METH_NOARGS, PyDoc_STR("Not implemented") }, |
|
711 | { "readlines", (PyCFunction)reader_readlines, METH_NOARGS, PyDoc_STR("Not implemented") }, |
@@ -22,12 +22,13 b' static void ZstdDecompressionWriter_deal' | |||||
22 | } |
|
22 | } | |
23 |
|
23 | |||
24 | static PyObject* ZstdDecompressionWriter_enter(ZstdDecompressionWriter* self) { |
|
24 | static PyObject* ZstdDecompressionWriter_enter(ZstdDecompressionWriter* self) { | |
25 |
if (self-> |
|
25 | if (self->closed) { | |
26 | PyErr_SetString(ZstdError, "cannot __enter__ multiple times"); |
|
26 | PyErr_SetString(PyExc_ValueError, "stream is closed"); | |
27 | return NULL; |
|
27 | return NULL; | |
28 | } |
|
28 | } | |
29 |
|
29 | |||
30 | if (ensure_dctx(self->decompressor, 1)) { |
|
30 | if (self->entered) { | |
|
31 | PyErr_SetString(ZstdError, "cannot __enter__ multiple times"); | |||
31 | return NULL; |
|
32 | return NULL; | |
32 | } |
|
33 | } | |
33 |
|
34 | |||
@@ -40,6 +41,10 b' static PyObject* ZstdDecompressionWriter' | |||||
40 | static PyObject* ZstdDecompressionWriter_exit(ZstdDecompressionWriter* self, PyObject* args) { |
|
41 | static PyObject* ZstdDecompressionWriter_exit(ZstdDecompressionWriter* self, PyObject* args) { | |
41 | self->entered = 0; |
|
42 | self->entered = 0; | |
42 |
|
43 | |||
|
44 | if (NULL == PyObject_CallMethod((PyObject*)self, "close", NULL)) { | |||
|
45 | return NULL; | |||
|
46 | } | |||
|
47 | ||||
43 | Py_RETURN_FALSE; |
|
48 | Py_RETURN_FALSE; | |
44 | } |
|
49 | } | |
45 |
|
50 | |||
@@ -76,9 +81,9 b' static PyObject* ZstdDecompressionWriter' | |||||
76 | goto finally; |
|
81 | goto finally; | |
77 | } |
|
82 | } | |
78 |
|
83 | |||
79 |
if ( |
|
84 | if (self->closed) { | |
80 | PyErr_SetString(ZstdError, "write must be called from an active context manager"); |
|
85 | PyErr_SetString(PyExc_ValueError, "stream is closed"); | |
81 | goto finally; |
|
86 | return NULL; | |
82 | } |
|
87 | } | |
83 |
|
88 | |||
84 | output.dst = PyMem_Malloc(self->outSize); |
|
89 | output.dst = PyMem_Malloc(self->outSize); | |
@@ -93,9 +98,9 b' static PyObject* ZstdDecompressionWriter' | |||||
93 | input.size = source.len; |
|
98 | input.size = source.len; | |
94 | input.pos = 0; |
|
99 | input.pos = 0; | |
95 |
|
100 | |||
96 |
while ( |
|
101 | while (input.pos < (size_t)source.len) { | |
97 | Py_BEGIN_ALLOW_THREADS |
|
102 | Py_BEGIN_ALLOW_THREADS | |
98 |
zresult = ZSTD_decompress |
|
103 | zresult = ZSTD_decompressStream(self->decompressor->dctx, &output, &input); | |
99 | Py_END_ALLOW_THREADS |
|
104 | Py_END_ALLOW_THREADS | |
100 |
|
105 | |||
101 | if (ZSTD_isError(zresult)) { |
|
106 | if (ZSTD_isError(zresult)) { | |
@@ -120,13 +125,94 b' static PyObject* ZstdDecompressionWriter' | |||||
120 |
|
125 | |||
121 | PyMem_Free(output.dst); |
|
126 | PyMem_Free(output.dst); | |
122 |
|
127 | |||
123 | result = PyLong_FromSsize_t(totalWrite); |
|
128 | if (self->writeReturnRead) { | |
|
129 | result = PyLong_FromSize_t(input.pos); | |||
|
130 | } | |||
|
131 | else { | |||
|
132 | result = PyLong_FromSsize_t(totalWrite); | |||
|
133 | } | |||
124 |
|
134 | |||
125 | finally: |
|
135 | finally: | |
126 | PyBuffer_Release(&source); |
|
136 | PyBuffer_Release(&source); | |
127 | return result; |
|
137 | return result; | |
128 | } |
|
138 | } | |
129 |
|
139 | |||
|
140 | static PyObject* ZstdDecompressionWriter_close(ZstdDecompressionWriter* self) { | |||
|
141 | PyObject* result; | |||
|
142 | ||||
|
143 | if (self->closed) { | |||
|
144 | Py_RETURN_NONE; | |||
|
145 | } | |||
|
146 | ||||
|
147 | result = PyObject_CallMethod((PyObject*)self, "flush", NULL); | |||
|
148 | self->closed = 1; | |||
|
149 | ||||
|
150 | if (NULL == result) { | |||
|
151 | return NULL; | |||
|
152 | } | |||
|
153 | ||||
|
154 | /* Call close on underlying stream as well. */ | |||
|
155 | if (PyObject_HasAttrString(self->writer, "close")) { | |||
|
156 | return PyObject_CallMethod(self->writer, "close", NULL); | |||
|
157 | } | |||
|
158 | ||||
|
159 | Py_RETURN_NONE; | |||
|
160 | } | |||
|
161 | ||||
|
162 | static PyObject* ZstdDecompressionWriter_fileno(ZstdDecompressionWriter* self) { | |||
|
163 | if (PyObject_HasAttrString(self->writer, "fileno")) { | |||
|
164 | return PyObject_CallMethod(self->writer, "fileno", NULL); | |||
|
165 | } | |||
|
166 | else { | |||
|
167 | PyErr_SetString(PyExc_OSError, "fileno not available on underlying writer"); | |||
|
168 | return NULL; | |||
|
169 | } | |||
|
170 | } | |||
|
171 | ||||
|
172 | static PyObject* ZstdDecompressionWriter_flush(ZstdDecompressionWriter* self) { | |||
|
173 | if (self->closed) { | |||
|
174 | PyErr_SetString(PyExc_ValueError, "stream is closed"); | |||
|
175 | return NULL; | |||
|
176 | } | |||
|
177 | ||||
|
178 | if (PyObject_HasAttrString(self->writer, "flush")) { | |||
|
179 | return PyObject_CallMethod(self->writer, "flush", NULL); | |||
|
180 | } | |||
|
181 | else { | |||
|
182 | Py_RETURN_NONE; | |||
|
183 | } | |||
|
184 | } | |||
|
185 | ||||
|
186 | static PyObject* ZstdDecompressionWriter_false(PyObject* self, PyObject* args) { | |||
|
187 | Py_RETURN_FALSE; | |||
|
188 | } | |||
|
189 | ||||
|
190 | static PyObject* ZstdDecompressionWriter_true(PyObject* self, PyObject* args) { | |||
|
191 | Py_RETURN_TRUE; | |||
|
192 | } | |||
|
193 | ||||
|
194 | static PyObject* ZstdDecompressionWriter_unsupported(PyObject* self, PyObject* args, PyObject* kwargs) { | |||
|
195 | PyObject* iomod; | |||
|
196 | PyObject* exc; | |||
|
197 | ||||
|
198 | iomod = PyImport_ImportModule("io"); | |||
|
199 | if (NULL == iomod) { | |||
|
200 | return NULL; | |||
|
201 | } | |||
|
202 | ||||
|
203 | exc = PyObject_GetAttrString(iomod, "UnsupportedOperation"); | |||
|
204 | if (NULL == exc) { | |||
|
205 | Py_DECREF(iomod); | |||
|
206 | return NULL; | |||
|
207 | } | |||
|
208 | ||||
|
209 | PyErr_SetNone(exc); | |||
|
210 | Py_DECREF(exc); | |||
|
211 | Py_DECREF(iomod); | |||
|
212 | ||||
|
213 | return NULL; | |||
|
214 | } | |||
|
215 | ||||
130 | static PyMethodDef ZstdDecompressionWriter_methods[] = { |
|
216 | static PyMethodDef ZstdDecompressionWriter_methods[] = { | |
131 | { "__enter__", (PyCFunction)ZstdDecompressionWriter_enter, METH_NOARGS, |
|
217 | { "__enter__", (PyCFunction)ZstdDecompressionWriter_enter, METH_NOARGS, | |
132 | PyDoc_STR("Enter a decompression context.") }, |
|
218 | PyDoc_STR("Enter a decompression context.") }, | |
@@ -134,11 +220,32 b' static PyMethodDef ZstdDecompressionWrit' | |||||
134 | PyDoc_STR("Exit a decompression context.") }, |
|
220 | PyDoc_STR("Exit a decompression context.") }, | |
135 | { "memory_size", (PyCFunction)ZstdDecompressionWriter_memory_size, METH_NOARGS, |
|
221 | { "memory_size", (PyCFunction)ZstdDecompressionWriter_memory_size, METH_NOARGS, | |
136 | PyDoc_STR("Obtain the memory size in bytes of the underlying decompressor.") }, |
|
222 | PyDoc_STR("Obtain the memory size in bytes of the underlying decompressor.") }, | |
|
223 | { "close", (PyCFunction)ZstdDecompressionWriter_close, METH_NOARGS, NULL }, | |||
|
224 | { "fileno", (PyCFunction)ZstdDecompressionWriter_fileno, METH_NOARGS, NULL }, | |||
|
225 | { "flush", (PyCFunction)ZstdDecompressionWriter_flush, METH_NOARGS, NULL }, | |||
|
226 | { "isatty", ZstdDecompressionWriter_false, METH_NOARGS, NULL }, | |||
|
227 | { "readable", ZstdDecompressionWriter_false, METH_NOARGS, NULL }, | |||
|
228 | { "readline", (PyCFunction)ZstdDecompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
|
229 | { "readlines", (PyCFunction)ZstdDecompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
|
230 | { "seek", (PyCFunction)ZstdDecompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
|
231 | { "seekable", ZstdDecompressionWriter_false, METH_NOARGS, NULL }, | |||
|
232 | { "tell", (PyCFunction)ZstdDecompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
|
233 | { "truncate", (PyCFunction)ZstdDecompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
|
234 | { "writable", ZstdDecompressionWriter_true, METH_NOARGS, NULL }, | |||
|
235 | { "writelines" , (PyCFunction)ZstdDecompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
|
236 | { "read", (PyCFunction)ZstdDecompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
|
237 | { "readall", (PyCFunction)ZstdDecompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
|
238 | { "readinto", (PyCFunction)ZstdDecompressionWriter_unsupported, METH_VARARGS | METH_KEYWORDS, NULL }, | |||
137 | { "write", (PyCFunction)ZstdDecompressionWriter_write, METH_VARARGS | METH_KEYWORDS, |
|
239 | { "write", (PyCFunction)ZstdDecompressionWriter_write, METH_VARARGS | METH_KEYWORDS, | |
138 | PyDoc_STR("Compress data") }, |
|
240 | PyDoc_STR("Compress data") }, | |
139 | { NULL, NULL } |
|
241 | { NULL, NULL } | |
140 | }; |
|
242 | }; | |
141 |
|
243 | |||
|
244 | static PyMemberDef ZstdDecompressionWriter_members[] = { | |||
|
245 | { "closed", T_BOOL, offsetof(ZstdDecompressionWriter, closed), READONLY, NULL }, | |||
|
246 | { NULL } | |||
|
247 | }; | |||
|
248 | ||||
142 | PyTypeObject ZstdDecompressionWriterType = { |
|
249 | PyTypeObject ZstdDecompressionWriterType = { | |
143 | PyVarObject_HEAD_INIT(NULL, 0) |
|
250 | PyVarObject_HEAD_INIT(NULL, 0) | |
144 | "zstd.ZstdDecompressionWriter", /* tp_name */ |
|
251 | "zstd.ZstdDecompressionWriter", /* tp_name */ | |
@@ -168,7 +275,7 b' PyTypeObject ZstdDecompressionWriterType' | |||||
168 | 0, /* tp_iter */ |
|
275 | 0, /* tp_iter */ | |
169 | 0, /* tp_iternext */ |
|
276 | 0, /* tp_iternext */ | |
170 | ZstdDecompressionWriter_methods,/* tp_methods */ |
|
277 | ZstdDecompressionWriter_methods,/* tp_methods */ | |
171 | 0, /* tp_members */ |
|
278 | ZstdDecompressionWriter_members,/* tp_members */ | |
172 | 0, /* tp_getset */ |
|
279 | 0, /* tp_getset */ | |
173 | 0, /* tp_base */ |
|
280 | 0, /* tp_base */ | |
174 | 0, /* tp_dict */ |
|
281 | 0, /* tp_dict */ |
@@ -75,7 +75,7 b' static PyObject* DecompressionObj_decomp' | |||||
75 |
|
75 | |||
76 | while (1) { |
|
76 | while (1) { | |
77 | Py_BEGIN_ALLOW_THREADS |
|
77 | Py_BEGIN_ALLOW_THREADS | |
78 |
zresult = ZSTD_decompress |
|
78 | zresult = ZSTD_decompressStream(self->decompressor->dctx, &output, &input); | |
79 | Py_END_ALLOW_THREADS |
|
79 | Py_END_ALLOW_THREADS | |
80 |
|
80 | |||
81 | if (ZSTD_isError(zresult)) { |
|
81 | if (ZSTD_isError(zresult)) { | |
@@ -130,9 +130,26 b' finally:' | |||||
130 | return result; |
|
130 | return result; | |
131 | } |
|
131 | } | |
132 |
|
132 | |||
|
133 | static PyObject* DecompressionObj_flush(ZstdDecompressionObj* self, PyObject* args, PyObject* kwargs) { | |||
|
134 | static char* kwlist[] = { | |||
|
135 | "length", | |||
|
136 | NULL | |||
|
137 | }; | |||
|
138 | ||||
|
139 | PyObject* length = NULL; | |||
|
140 | ||||
|
141 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O:flush", kwlist, &length)) { | |||
|
142 | return NULL; | |||
|
143 | } | |||
|
144 | ||||
|
145 | Py_RETURN_NONE; | |||
|
146 | } | |||
|
147 | ||||
133 | static PyMethodDef DecompressionObj_methods[] = { |
|
148 | static PyMethodDef DecompressionObj_methods[] = { | |
134 | { "decompress", (PyCFunction)DecompressionObj_decompress, |
|
149 | { "decompress", (PyCFunction)DecompressionObj_decompress, | |
135 | METH_VARARGS | METH_KEYWORDS, PyDoc_STR("decompress data") }, |
|
150 | METH_VARARGS | METH_KEYWORDS, PyDoc_STR("decompress data") }, | |
|
151 | { "flush", (PyCFunction)DecompressionObj_flush, | |||
|
152 | METH_VARARGS | METH_KEYWORDS, PyDoc_STR("no-op") }, | |||
136 | { NULL, NULL } |
|
153 | { NULL, NULL } | |
137 | }; |
|
154 | }; | |
138 |
|
155 |
@@ -17,7 +17,7 b' extern PyObject* ZstdError;' | |||||
17 | int ensure_dctx(ZstdDecompressor* decompressor, int loadDict) { |
|
17 | int ensure_dctx(ZstdDecompressor* decompressor, int loadDict) { | |
18 | size_t zresult; |
|
18 | size_t zresult; | |
19 |
|
19 | |||
20 | ZSTD_DCtx_reset(decompressor->dctx); |
|
20 | ZSTD_DCtx_reset(decompressor->dctx, ZSTD_reset_session_only); | |
21 |
|
21 | |||
22 | if (decompressor->maxWindowSize) { |
|
22 | if (decompressor->maxWindowSize) { | |
23 | zresult = ZSTD_DCtx_setMaxWindowSize(decompressor->dctx, decompressor->maxWindowSize); |
|
23 | zresult = ZSTD_DCtx_setMaxWindowSize(decompressor->dctx, decompressor->maxWindowSize); | |
@@ -229,7 +229,7 b' static PyObject* Decompressor_copy_strea' | |||||
229 |
|
229 | |||
230 | while (input.pos < input.size) { |
|
230 | while (input.pos < input.size) { | |
231 | Py_BEGIN_ALLOW_THREADS |
|
231 | Py_BEGIN_ALLOW_THREADS | |
232 |
zresult = ZSTD_decompress |
|
232 | zresult = ZSTD_decompressStream(self->dctx, &output, &input); | |
233 | Py_END_ALLOW_THREADS |
|
233 | Py_END_ALLOW_THREADS | |
234 |
|
234 | |||
235 | if (ZSTD_isError(zresult)) { |
|
235 | if (ZSTD_isError(zresult)) { | |
@@ -379,7 +379,7 b' PyObject* Decompressor_decompress(ZstdDe' | |||||
379 | inBuffer.pos = 0; |
|
379 | inBuffer.pos = 0; | |
380 |
|
380 | |||
381 | Py_BEGIN_ALLOW_THREADS |
|
381 | Py_BEGIN_ALLOW_THREADS | |
382 |
zresult = ZSTD_decompress |
|
382 | zresult = ZSTD_decompressStream(self->dctx, &outBuffer, &inBuffer); | |
383 | Py_END_ALLOW_THREADS |
|
383 | Py_END_ALLOW_THREADS | |
384 |
|
384 | |||
385 | if (ZSTD_isError(zresult)) { |
|
385 | if (ZSTD_isError(zresult)) { | |
@@ -550,28 +550,35 b' finally:' | |||||
550 | } |
|
550 | } | |
551 |
|
551 | |||
552 | PyDoc_STRVAR(Decompressor_stream_reader__doc__, |
|
552 | PyDoc_STRVAR(Decompressor_stream_reader__doc__, | |
553 | "stream_reader(source, [read_size=default])\n" |
|
553 | "stream_reader(source, [read_size=default, [read_across_frames=False]])\n" | |
554 | "\n" |
|
554 | "\n" | |
555 | "Obtain an object that behaves like an I/O stream that can be used for\n" |
|
555 | "Obtain an object that behaves like an I/O stream that can be used for\n" | |
556 | "reading decompressed output from an object.\n" |
|
556 | "reading decompressed output from an object.\n" | |
557 | "\n" |
|
557 | "\n" | |
558 | "The source object can be any object with a ``read(size)`` method or that\n" |
|
558 | "The source object can be any object with a ``read(size)`` method or that\n" | |
559 | "conforms to the buffer protocol.\n" |
|
559 | "conforms to the buffer protocol.\n" | |
|
560 | "\n" | |||
|
561 | "``read_across_frames`` controls the behavior of ``read()`` when the end\n" | |||
|
562 | "of a zstd frame is reached. When ``True``, ``read()`` can potentially\n" | |||
|
563 | "return data belonging to multiple zstd frames. When ``False``, ``read()``\n" | |||
|
564 | "will return when the end of a frame is reached.\n" | |||
560 | ); |
|
565 | ); | |
561 |
|
566 | |||
562 | static ZstdDecompressionReader* Decompressor_stream_reader(ZstdDecompressor* self, PyObject* args, PyObject* kwargs) { |
|
567 | static ZstdDecompressionReader* Decompressor_stream_reader(ZstdDecompressor* self, PyObject* args, PyObject* kwargs) { | |
563 | static char* kwlist[] = { |
|
568 | static char* kwlist[] = { | |
564 | "source", |
|
569 | "source", | |
565 | "read_size", |
|
570 | "read_size", | |
|
571 | "read_across_frames", | |||
566 | NULL |
|
572 | NULL | |
567 | }; |
|
573 | }; | |
568 |
|
574 | |||
569 | PyObject* source; |
|
575 | PyObject* source; | |
570 | size_t readSize = ZSTD_DStreamInSize(); |
|
576 | size_t readSize = ZSTD_DStreamInSize(); | |
|
577 | PyObject* readAcrossFrames = NULL; | |||
571 | ZstdDecompressionReader* result; |
|
578 | ZstdDecompressionReader* result; | |
572 |
|
579 | |||
573 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|k:stream_reader", kwlist, |
|
580 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|kO:stream_reader", kwlist, | |
574 | &source, &readSize)) { |
|
581 | &source, &readSize, &readAcrossFrames)) { | |
575 | return NULL; |
|
582 | return NULL; | |
576 | } |
|
583 | } | |
577 |
|
584 | |||
@@ -604,6 +611,7 b' static ZstdDecompressionReader* Decompre' | |||||
604 |
|
611 | |||
605 | result->decompressor = self; |
|
612 | result->decompressor = self; | |
606 | Py_INCREF(self); |
|
613 | Py_INCREF(self); | |
|
614 | result->readAcrossFrames = readAcrossFrames ? PyObject_IsTrue(readAcrossFrames) : 0; | |||
607 |
|
615 | |||
608 | return result; |
|
616 | return result; | |
609 | } |
|
617 | } | |
@@ -625,15 +633,17 b' static ZstdDecompressionWriter* Decompre' | |||||
625 | static char* kwlist[] = { |
|
633 | static char* kwlist[] = { | |
626 | "writer", |
|
634 | "writer", | |
627 | "write_size", |
|
635 | "write_size", | |
|
636 | "write_return_read", | |||
628 | NULL |
|
637 | NULL | |
629 | }; |
|
638 | }; | |
630 |
|
639 | |||
631 | PyObject* writer; |
|
640 | PyObject* writer; | |
632 | size_t outSize = ZSTD_DStreamOutSize(); |
|
641 | size_t outSize = ZSTD_DStreamOutSize(); | |
|
642 | PyObject* writeReturnRead = NULL; | |||
633 | ZstdDecompressionWriter* result; |
|
643 | ZstdDecompressionWriter* result; | |
634 |
|
644 | |||
635 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|k:stream_writer", kwlist, |
|
645 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|kO:stream_writer", kwlist, | |
636 | &writer, &outSize)) { |
|
646 | &writer, &outSize, &writeReturnRead)) { | |
637 | return NULL; |
|
647 | return NULL; | |
638 | } |
|
648 | } | |
639 |
|
649 | |||
@@ -642,6 +652,10 b' static ZstdDecompressionWriter* Decompre' | |||||
642 | return NULL; |
|
652 | return NULL; | |
643 | } |
|
653 | } | |
644 |
|
654 | |||
|
655 | if (ensure_dctx(self, 1)) { | |||
|
656 | return NULL; | |||
|
657 | } | |||
|
658 | ||||
645 | result = (ZstdDecompressionWriter*)PyObject_CallObject((PyObject*)&ZstdDecompressionWriterType, NULL); |
|
659 | result = (ZstdDecompressionWriter*)PyObject_CallObject((PyObject*)&ZstdDecompressionWriterType, NULL); | |
646 | if (!result) { |
|
660 | if (!result) { | |
647 | return NULL; |
|
661 | return NULL; | |
@@ -654,6 +668,7 b' static ZstdDecompressionWriter* Decompre' | |||||
654 | Py_INCREF(result->writer); |
|
668 | Py_INCREF(result->writer); | |
655 |
|
669 | |||
656 | result->outSize = outSize; |
|
670 | result->outSize = outSize; | |
|
671 | result->writeReturnRead = writeReturnRead ? PyObject_IsTrue(writeReturnRead) : 0; | |||
657 |
|
672 | |||
658 | return result; |
|
673 | return result; | |
659 | } |
|
674 | } | |
@@ -756,7 +771,7 b' static PyObject* Decompressor_decompress' | |||||
756 | inBuffer.pos = 0; |
|
771 | inBuffer.pos = 0; | |
757 |
|
772 | |||
758 | Py_BEGIN_ALLOW_THREADS |
|
773 | Py_BEGIN_ALLOW_THREADS | |
759 |
zresult = ZSTD_decompress |
|
774 | zresult = ZSTD_decompressStream(self->dctx, &outBuffer, &inBuffer); | |
760 | Py_END_ALLOW_THREADS |
|
775 | Py_END_ALLOW_THREADS | |
761 | if (ZSTD_isError(zresult)) { |
|
776 | if (ZSTD_isError(zresult)) { | |
762 | PyErr_Format(ZstdError, "could not decompress chunk 0: %s", ZSTD_getErrorName(zresult)); |
|
777 | PyErr_Format(ZstdError, "could not decompress chunk 0: %s", ZSTD_getErrorName(zresult)); | |
@@ -852,7 +867,7 b' static PyObject* Decompressor_decompress' | |||||
852 | outBuffer.pos = 0; |
|
867 | outBuffer.pos = 0; | |
853 |
|
868 | |||
854 | Py_BEGIN_ALLOW_THREADS |
|
869 | Py_BEGIN_ALLOW_THREADS | |
855 |
zresult = ZSTD_decompress |
|
870 | zresult = ZSTD_decompressStream(self->dctx, &outBuffer, &inBuffer); | |
856 | Py_END_ALLOW_THREADS |
|
871 | Py_END_ALLOW_THREADS | |
857 | if (ZSTD_isError(zresult)) { |
|
872 | if (ZSTD_isError(zresult)) { | |
858 | PyErr_Format(ZstdError, "could not decompress chunk %zd: %s", |
|
873 | PyErr_Format(ZstdError, "could not decompress chunk %zd: %s", | |
@@ -892,7 +907,7 b' static PyObject* Decompressor_decompress' | |||||
892 | outBuffer.pos = 0; |
|
907 | outBuffer.pos = 0; | |
893 |
|
908 | |||
894 | Py_BEGIN_ALLOW_THREADS |
|
909 | Py_BEGIN_ALLOW_THREADS | |
895 |
zresult = ZSTD_decompress |
|
910 | zresult = ZSTD_decompressStream(self->dctx, &outBuffer, &inBuffer); | |
896 | Py_END_ALLOW_THREADS |
|
911 | Py_END_ALLOW_THREADS | |
897 | if (ZSTD_isError(zresult)) { |
|
912 | if (ZSTD_isError(zresult)) { | |
898 | PyErr_Format(ZstdError, "could not decompress chunk %zd: %s", |
|
913 | PyErr_Format(ZstdError, "could not decompress chunk %zd: %s", | |
@@ -1176,7 +1191,7 b' static void decompress_worker(WorkerStat' | |||||
1176 | inBuffer.size = sourceSize; |
|
1191 | inBuffer.size = sourceSize; | |
1177 | inBuffer.pos = 0; |
|
1192 | inBuffer.pos = 0; | |
1178 |
|
1193 | |||
1179 |
zresult = ZSTD_decompress |
|
1194 | zresult = ZSTD_decompressStream(state->dctx, &outBuffer, &inBuffer); | |
1180 | if (ZSTD_isError(zresult)) { |
|
1195 | if (ZSTD_isError(zresult)) { | |
1181 | state->error = WorkerError_zstd; |
|
1196 | state->error = WorkerError_zstd; | |
1182 | state->zresult = zresult; |
|
1197 | state->zresult = zresult; |
@@ -57,7 +57,7 b' static DecompressorIteratorResult read_d' | |||||
57 | self->output.pos = 0; |
|
57 | self->output.pos = 0; | |
58 |
|
58 | |||
59 | Py_BEGIN_ALLOW_THREADS |
|
59 | Py_BEGIN_ALLOW_THREADS | |
60 |
zresult = ZSTD_decompress |
|
60 | zresult = ZSTD_decompressStream(self->decompressor->dctx, &self->output, &self->input); | |
61 | Py_END_ALLOW_THREADS |
|
61 | Py_END_ALLOW_THREADS | |
62 |
|
62 | |||
63 | /* We're done with the pointer. Nullify to prevent anyone from getting a |
|
63 | /* We're done with the pointer. Nullify to prevent anyone from getting a |
@@ -16,7 +16,7 b'' | |||||
16 | #include <zdict.h> |
|
16 | #include <zdict.h> | |
17 |
|
17 | |||
18 | /* Remember to change the string in zstandard/__init__ as well */ |
|
18 | /* Remember to change the string in zstandard/__init__ as well */ | |
19 |
#define PYTHON_ZSTANDARD_VERSION "0.1 |
|
19 | #define PYTHON_ZSTANDARD_VERSION "0.11.0" | |
20 |
|
20 | |||
21 | typedef enum { |
|
21 | typedef enum { | |
22 | compressorobj_flush_finish, |
|
22 | compressorobj_flush_finish, | |
@@ -31,27 +31,6 b' typedef enum {' | |||||
31 | typedef struct { |
|
31 | typedef struct { | |
32 | PyObject_HEAD |
|
32 | PyObject_HEAD | |
33 | ZSTD_CCtx_params* params; |
|
33 | ZSTD_CCtx_params* params; | |
34 | unsigned format; |
|
|||
35 | int compressionLevel; |
|
|||
36 | unsigned windowLog; |
|
|||
37 | unsigned hashLog; |
|
|||
38 | unsigned chainLog; |
|
|||
39 | unsigned searchLog; |
|
|||
40 | unsigned minMatch; |
|
|||
41 | unsigned targetLength; |
|
|||
42 | unsigned compressionStrategy; |
|
|||
43 | unsigned contentSizeFlag; |
|
|||
44 | unsigned checksumFlag; |
|
|||
45 | unsigned dictIDFlag; |
|
|||
46 | unsigned threads; |
|
|||
47 | unsigned jobSize; |
|
|||
48 | unsigned overlapSizeLog; |
|
|||
49 | unsigned forceMaxWindow; |
|
|||
50 | unsigned enableLongDistanceMatching; |
|
|||
51 | unsigned ldmHashLog; |
|
|||
52 | unsigned ldmMinMatch; |
|
|||
53 | unsigned ldmBucketSizeLog; |
|
|||
54 | unsigned ldmHashEveryLog; |
|
|||
55 | } ZstdCompressionParametersObject; |
|
34 | } ZstdCompressionParametersObject; | |
56 |
|
35 | |||
57 | extern PyTypeObject ZstdCompressionParametersType; |
|
36 | extern PyTypeObject ZstdCompressionParametersType; | |
@@ -129,9 +108,11 b' typedef struct {' | |||||
129 |
|
108 | |||
130 | ZstdCompressor* compressor; |
|
109 | ZstdCompressor* compressor; | |
131 | PyObject* writer; |
|
110 | PyObject* writer; | |
132 | unsigned long long sourceSize; |
|
111 | ZSTD_outBuffer output; | |
133 | size_t outSize; |
|
112 | size_t outSize; | |
134 | int entered; |
|
113 | int entered; | |
|
114 | int closed; | |||
|
115 | int writeReturnRead; | |||
135 | unsigned long long bytesCompressed; |
|
116 | unsigned long long bytesCompressed; | |
136 | } ZstdCompressionWriter; |
|
117 | } ZstdCompressionWriter; | |
137 |
|
118 | |||
@@ -235,6 +216,8 b' typedef struct {' | |||||
235 | PyObject* reader; |
|
216 | PyObject* reader; | |
236 | /* Size for read() operations on reader. */ |
|
217 | /* Size for read() operations on reader. */ | |
237 | size_t readSize; |
|
218 | size_t readSize; | |
|
219 | /* Whether a read() can return data spanning multiple zstd frames. */ | |||
|
220 | int readAcrossFrames; | |||
238 | /* Buffer to read from (if reading from a buffer). */ |
|
221 | /* Buffer to read from (if reading from a buffer). */ | |
239 | Py_buffer buffer; |
|
222 | Py_buffer buffer; | |
240 |
|
223 | |||
@@ -267,6 +250,8 b' typedef struct {' | |||||
267 | PyObject* writer; |
|
250 | PyObject* writer; | |
268 | size_t outSize; |
|
251 | size_t outSize; | |
269 | int entered; |
|
252 | int entered; | |
|
253 | int closed; | |||
|
254 | int writeReturnRead; | |||
270 | } ZstdDecompressionWriter; |
|
255 | } ZstdDecompressionWriter; | |
271 |
|
256 | |||
272 | extern PyTypeObject ZstdDecompressionWriterType; |
|
257 | extern PyTypeObject ZstdDecompressionWriterType; | |
@@ -360,8 +345,9 b' typedef struct {' | |||||
360 |
|
345 | |||
361 | extern PyTypeObject ZstdBufferWithSegmentsCollectionType; |
|
346 | extern PyTypeObject ZstdBufferWithSegmentsCollectionType; | |
362 |
|
347 | |||
363 |
int set_parameter(ZSTD_CCtx_params* params, ZSTD_cParameter param, |
|
348 | int set_parameter(ZSTD_CCtx_params* params, ZSTD_cParameter param, int value); | |
364 | int set_parameters(ZSTD_CCtx_params* params, ZstdCompressionParametersObject* obj); |
|
349 | int set_parameters(ZSTD_CCtx_params* params, ZstdCompressionParametersObject* obj); | |
|
350 | int to_cparams(ZstdCompressionParametersObject* params, ZSTD_compressionParameters* cparams); | |||
365 | FrameParametersObject* get_frame_parameters(PyObject* self, PyObject* args, PyObject* kwargs); |
|
351 | FrameParametersObject* get_frame_parameters(PyObject* self, PyObject* args, PyObject* kwargs); | |
366 | int ensure_ddict(ZstdCompressionDict* dict); |
|
352 | int ensure_ddict(ZstdCompressionDict* dict); | |
367 | int ensure_dctx(ZstdDecompressor* decompressor, int loadDict); |
|
353 | int ensure_dctx(ZstdDecompressor* decompressor, int loadDict); |
@@ -36,7 +36,9 b" SOURCES = ['zstd/%s' % p for p in (" | |||||
36 | 'compress/zstd_opt.c', |
|
36 | 'compress/zstd_opt.c', | |
37 | 'compress/zstdmt_compress.c', |
|
37 | 'compress/zstdmt_compress.c', | |
38 | 'decompress/huf_decompress.c', |
|
38 | 'decompress/huf_decompress.c', | |
|
39 | 'decompress/zstd_ddict.c', | |||
39 | 'decompress/zstd_decompress.c', |
|
40 | 'decompress/zstd_decompress.c', | |
|
41 | 'decompress/zstd_decompress_block.c', | |||
40 | 'dictBuilder/cover.c', |
|
42 | 'dictBuilder/cover.c', | |
41 | 'dictBuilder/fastcover.c', |
|
43 | 'dictBuilder/fastcover.c', | |
42 | 'dictBuilder/divsufsort.c', |
|
44 | 'dictBuilder/divsufsort.c', |
@@ -5,12 +5,32 b'' | |||||
5 | # This software may be modified and distributed under the terms |
|
5 | # This software may be modified and distributed under the terms | |
6 | # of the BSD license. See the LICENSE file for details. |
|
6 | # of the BSD license. See the LICENSE file for details. | |
7 |
|
7 | |||
|
8 | from __future__ import print_function | |||
|
9 | ||||
|
10 | from distutils.version import LooseVersion | |||
8 | import os |
|
11 | import os | |
9 | import sys |
|
12 | import sys | |
10 | from setuptools import setup |
|
13 | from setuptools import setup | |
11 |
|
14 | |||
|
15 | # Need change in 1.10 for ffi.from_buffer() to handle all buffer types | |||
|
16 | # (like memoryview). | |||
|
17 | # Need feature in 1.11 for ffi.gc() to declare size of objects so we avoid | |||
|
18 | # garbage collection pitfalls. | |||
|
19 | MINIMUM_CFFI_VERSION = '1.11' | |||
|
20 | ||||
12 | try: |
|
21 | try: | |
13 | import cffi |
|
22 | import cffi | |
|
23 | ||||
|
24 | # PyPy (and possibly other distros) have CFFI distributed as part of | |||
|
25 | # them. The install_requires for CFFI below won't work. We need to sniff | |||
|
26 | # out the CFFI version here and reject CFFI if it is too old. | |||
|
27 | cffi_version = LooseVersion(cffi.__version__) | |||
|
28 | if cffi_version < LooseVersion(MINIMUM_CFFI_VERSION): | |||
|
29 | print('CFFI 1.11 or newer required (%s found); ' | |||
|
30 | 'not building CFFI backend' % cffi_version, | |||
|
31 | file=sys.stderr) | |||
|
32 | cffi = None | |||
|
33 | ||||
14 | except ImportError: |
|
34 | except ImportError: | |
15 | cffi = None |
|
35 | cffi = None | |
16 |
|
36 | |||
@@ -49,12 +69,7 b' install_requires = []' | |||||
49 | if cffi: |
|
69 | if cffi: | |
50 | import make_cffi |
|
70 | import make_cffi | |
51 | extensions.append(make_cffi.ffi.distutils_extension()) |
|
71 | extensions.append(make_cffi.ffi.distutils_extension()) | |
52 |
|
72 | install_requires.append('cffi>=%s' % MINIMUM_CFFI_VERSION) | ||
53 | # Need change in 1.10 for ffi.from_buffer() to handle all buffer types |
|
|||
54 | # (like memoryview). |
|
|||
55 | # Need feature in 1.11 for ffi.gc() to declare size of objects so we avoid |
|
|||
56 | # garbage collection pitfalls. |
|
|||
57 | install_requires.append('cffi>=1.11') |
|
|||
58 |
|
73 | |||
59 | version = None |
|
74 | version = None | |
60 |
|
75 | |||
@@ -88,6 +103,7 b' setup(' | |||||
88 | 'Programming Language :: Python :: 3.4', |
|
103 | 'Programming Language :: Python :: 3.4', | |
89 | 'Programming Language :: Python :: 3.5', |
|
104 | 'Programming Language :: Python :: 3.5', | |
90 | 'Programming Language :: Python :: 3.6', |
|
105 | 'Programming Language :: Python :: 3.6', | |
|
106 | 'Programming Language :: Python :: 3.7', | |||
91 | ], |
|
107 | ], | |
92 | keywords='zstandard zstd compression', |
|
108 | keywords='zstandard zstd compression', | |
93 | packages=['zstandard'], |
|
109 | packages=['zstandard'], |
@@ -30,7 +30,9 b" zstd_sources = ['zstd/%s' % p for p in (" | |||||
30 | 'compress/zstd_opt.c', |
|
30 | 'compress/zstd_opt.c', | |
31 | 'compress/zstdmt_compress.c', |
|
31 | 'compress/zstdmt_compress.c', | |
32 | 'decompress/huf_decompress.c', |
|
32 | 'decompress/huf_decompress.c', | |
|
33 | 'decompress/zstd_ddict.c', | |||
33 | 'decompress/zstd_decompress.c', |
|
34 | 'decompress/zstd_decompress.c', | |
|
35 | 'decompress/zstd_decompress_block.c', | |||
34 | 'dictBuilder/cover.c', |
|
36 | 'dictBuilder/cover.c', | |
35 | 'dictBuilder/divsufsort.c', |
|
37 | 'dictBuilder/divsufsort.c', | |
36 | 'dictBuilder/fastcover.c', |
|
38 | 'dictBuilder/fastcover.c', |
@@ -79,12 +79,37 b' def make_cffi(cls):' | |||||
79 | return cls |
|
79 | return cls | |
80 |
|
80 | |||
81 |
|
81 | |||
82 |
class |
|
82 | class NonClosingBytesIO(io.BytesIO): | |
|
83 | """BytesIO that saves the underlying buffer on close(). | |||
|
84 | ||||
|
85 | This allows us to access written data after close(). | |||
|
86 | """ | |||
83 | def __init__(self, *args, **kwargs): |
|
87 | def __init__(self, *args, **kwargs): | |
|
88 | super(NonClosingBytesIO, self).__init__(*args, **kwargs) | |||
|
89 | self._saved_buffer = None | |||
|
90 | ||||
|
91 | def close(self): | |||
|
92 | self._saved_buffer = self.getvalue() | |||
|
93 | return super(NonClosingBytesIO, self).close() | |||
|
94 | ||||
|
95 | def getvalue(self): | |||
|
96 | if self.closed: | |||
|
97 | return self._saved_buffer | |||
|
98 | else: | |||
|
99 | return super(NonClosingBytesIO, self).getvalue() | |||
|
100 | ||||
|
101 | ||||
|
102 | class OpCountingBytesIO(NonClosingBytesIO): | |||
|
103 | def __init__(self, *args, **kwargs): | |||
|
104 | self._flush_count = 0 | |||
84 | self._read_count = 0 |
|
105 | self._read_count = 0 | |
85 | self._write_count = 0 |
|
106 | self._write_count = 0 | |
86 | return super(OpCountingBytesIO, self).__init__(*args, **kwargs) |
|
107 | return super(OpCountingBytesIO, self).__init__(*args, **kwargs) | |
87 |
|
108 | |||
|
109 | def flush(self): | |||
|
110 | self._flush_count += 1 | |||
|
111 | return super(OpCountingBytesIO, self).flush() | |||
|
112 | ||||
88 | def read(self, *args): |
|
113 | def read(self, *args): | |
89 | self._read_count += 1 |
|
114 | self._read_count += 1 | |
90 | return super(OpCountingBytesIO, self).read(*args) |
|
115 | return super(OpCountingBytesIO, self).read(*args) | |
@@ -117,6 +142,13 b' def random_input_data():' | |||||
117 | except OSError: |
|
142 | except OSError: | |
118 | pass |
|
143 | pass | |
119 |
|
144 | |||
|
145 | # Also add some actual random data. | |||
|
146 | _source_files.append(os.urandom(100)) | |||
|
147 | _source_files.append(os.urandom(1000)) | |||
|
148 | _source_files.append(os.urandom(10000)) | |||
|
149 | _source_files.append(os.urandom(100000)) | |||
|
150 | _source_files.append(os.urandom(1000000)) | |||
|
151 | ||||
120 | return _source_files |
|
152 | return _source_files | |
121 |
|
153 | |||
122 |
|
154 | |||
@@ -140,12 +172,14 b' def generate_samples():' | |||||
140 |
|
172 | |||
141 |
|
173 | |||
142 | if hypothesis: |
|
174 | if hypothesis: | |
143 | default_settings = hypothesis.settings() |
|
175 | default_settings = hypothesis.settings(deadline=10000) | |
144 | hypothesis.settings.register_profile('default', default_settings) |
|
176 | hypothesis.settings.register_profile('default', default_settings) | |
145 |
|
177 | |||
146 |
ci_settings = hypothesis.settings(max_examples= |
|
178 | ci_settings = hypothesis.settings(deadline=20000, max_examples=1000) | |
147 | max_iterations=2500) |
|
|||
148 | hypothesis.settings.register_profile('ci', ci_settings) |
|
179 | hypothesis.settings.register_profile('ci', ci_settings) | |
149 |
|
180 | |||
|
181 | expensive_settings = hypothesis.settings(deadline=None, max_examples=10000) | |||
|
182 | hypothesis.settings.register_profile('expensive', expensive_settings) | |||
|
183 | ||||
150 | hypothesis.settings.load_profile( |
|
184 | hypothesis.settings.load_profile( | |
151 | os.environ.get('HYPOTHESIS_PROFILE', 'default')) |
|
185 | os.environ.get('HYPOTHESIS_PROFILE', 'default')) |
@@ -8,6 +8,9 b" ss = struct.Struct('=QQ')" | |||||
8 |
|
8 | |||
9 | class TestBufferWithSegments(unittest.TestCase): |
|
9 | class TestBufferWithSegments(unittest.TestCase): | |
10 | def test_arguments(self): |
|
10 | def test_arguments(self): | |
|
11 | if not hasattr(zstd, 'BufferWithSegments'): | |||
|
12 | self.skipTest('BufferWithSegments not available') | |||
|
13 | ||||
11 | with self.assertRaises(TypeError): |
|
14 | with self.assertRaises(TypeError): | |
12 | zstd.BufferWithSegments() |
|
15 | zstd.BufferWithSegments() | |
13 |
|
16 | |||
@@ -19,10 +22,16 b' class TestBufferWithSegments(unittest.Te' | |||||
19 | zstd.BufferWithSegments(b'foo', b'\x00\x00') |
|
22 | zstd.BufferWithSegments(b'foo', b'\x00\x00') | |
20 |
|
23 | |||
21 | def test_invalid_offset(self): |
|
24 | def test_invalid_offset(self): | |
|
25 | if not hasattr(zstd, 'BufferWithSegments'): | |||
|
26 | self.skipTest('BufferWithSegments not available') | |||
|
27 | ||||
22 | with self.assertRaisesRegexp(ValueError, 'offset within segments array references memory'): |
|
28 | with self.assertRaisesRegexp(ValueError, 'offset within segments array references memory'): | |
23 | zstd.BufferWithSegments(b'foo', ss.pack(0, 4)) |
|
29 | zstd.BufferWithSegments(b'foo', ss.pack(0, 4)) | |
24 |
|
30 | |||
25 | def test_invalid_getitem(self): |
|
31 | def test_invalid_getitem(self): | |
|
32 | if not hasattr(zstd, 'BufferWithSegments'): | |||
|
33 | self.skipTest('BufferWithSegments not available') | |||
|
34 | ||||
26 | b = zstd.BufferWithSegments(b'foo', ss.pack(0, 3)) |
|
35 | b = zstd.BufferWithSegments(b'foo', ss.pack(0, 3)) | |
27 |
|
36 | |||
28 | with self.assertRaisesRegexp(IndexError, 'offset must be non-negative'): |
|
37 | with self.assertRaisesRegexp(IndexError, 'offset must be non-negative'): | |
@@ -35,6 +44,9 b' class TestBufferWithSegments(unittest.Te' | |||||
35 | test = b[2] |
|
44 | test = b[2] | |
36 |
|
45 | |||
37 | def test_single(self): |
|
46 | def test_single(self): | |
|
47 | if not hasattr(zstd, 'BufferWithSegments'): | |||
|
48 | self.skipTest('BufferWithSegments not available') | |||
|
49 | ||||
38 | b = zstd.BufferWithSegments(b'foo', ss.pack(0, 3)) |
|
50 | b = zstd.BufferWithSegments(b'foo', ss.pack(0, 3)) | |
39 | self.assertEqual(len(b), 1) |
|
51 | self.assertEqual(len(b), 1) | |
40 | self.assertEqual(b.size, 3) |
|
52 | self.assertEqual(b.size, 3) | |
@@ -45,6 +57,9 b' class TestBufferWithSegments(unittest.Te' | |||||
45 | self.assertEqual(b[0].tobytes(), b'foo') |
|
57 | self.assertEqual(b[0].tobytes(), b'foo') | |
46 |
|
58 | |||
47 | def test_multiple(self): |
|
59 | def test_multiple(self): | |
|
60 | if not hasattr(zstd, 'BufferWithSegments'): | |||
|
61 | self.skipTest('BufferWithSegments not available') | |||
|
62 | ||||
48 | b = zstd.BufferWithSegments(b'foofooxfooxy', b''.join([ss.pack(0, 3), |
|
63 | b = zstd.BufferWithSegments(b'foofooxfooxy', b''.join([ss.pack(0, 3), | |
49 | ss.pack(3, 4), |
|
64 | ss.pack(3, 4), | |
50 | ss.pack(7, 5)])) |
|
65 | ss.pack(7, 5)])) | |
@@ -59,10 +74,16 b' class TestBufferWithSegments(unittest.Te' | |||||
59 |
|
74 | |||
60 | class TestBufferWithSegmentsCollection(unittest.TestCase): |
|
75 | class TestBufferWithSegmentsCollection(unittest.TestCase): | |
61 | def test_empty_constructor(self): |
|
76 | def test_empty_constructor(self): | |
|
77 | if not hasattr(zstd, 'BufferWithSegmentsCollection'): | |||
|
78 | self.skipTest('BufferWithSegmentsCollection not available') | |||
|
79 | ||||
62 | with self.assertRaisesRegexp(ValueError, 'must pass at least 1 argument'): |
|
80 | with self.assertRaisesRegexp(ValueError, 'must pass at least 1 argument'): | |
63 | zstd.BufferWithSegmentsCollection() |
|
81 | zstd.BufferWithSegmentsCollection() | |
64 |
|
82 | |||
65 | def test_argument_validation(self): |
|
83 | def test_argument_validation(self): | |
|
84 | if not hasattr(zstd, 'BufferWithSegmentsCollection'): | |||
|
85 | self.skipTest('BufferWithSegmentsCollection not available') | |||
|
86 | ||||
66 | with self.assertRaisesRegexp(TypeError, 'arguments must be BufferWithSegments'): |
|
87 | with self.assertRaisesRegexp(TypeError, 'arguments must be BufferWithSegments'): | |
67 | zstd.BufferWithSegmentsCollection(None) |
|
88 | zstd.BufferWithSegmentsCollection(None) | |
68 |
|
89 | |||
@@ -74,6 +95,9 b' class TestBufferWithSegmentsCollection(u' | |||||
74 | zstd.BufferWithSegmentsCollection(zstd.BufferWithSegments(b'', b'')) |
|
95 | zstd.BufferWithSegmentsCollection(zstd.BufferWithSegments(b'', b'')) | |
75 |
|
96 | |||
76 | def test_length(self): |
|
97 | def test_length(self): | |
|
98 | if not hasattr(zstd, 'BufferWithSegmentsCollection'): | |||
|
99 | self.skipTest('BufferWithSegmentsCollection not available') | |||
|
100 | ||||
77 | b1 = zstd.BufferWithSegments(b'foo', ss.pack(0, 3)) |
|
101 | b1 = zstd.BufferWithSegments(b'foo', ss.pack(0, 3)) | |
78 | b2 = zstd.BufferWithSegments(b'barbaz', b''.join([ss.pack(0, 3), |
|
102 | b2 = zstd.BufferWithSegments(b'barbaz', b''.join([ss.pack(0, 3), | |
79 | ss.pack(3, 3)])) |
|
103 | ss.pack(3, 3)])) | |
@@ -91,6 +115,9 b' class TestBufferWithSegmentsCollection(u' | |||||
91 | self.assertEqual(c.size(), 9) |
|
115 | self.assertEqual(c.size(), 9) | |
92 |
|
116 | |||
93 | def test_getitem(self): |
|
117 | def test_getitem(self): | |
|
118 | if not hasattr(zstd, 'BufferWithSegmentsCollection'): | |||
|
119 | self.skipTest('BufferWithSegmentsCollection not available') | |||
|
120 | ||||
94 | b1 = zstd.BufferWithSegments(b'foo', ss.pack(0, 3)) |
|
121 | b1 = zstd.BufferWithSegments(b'foo', ss.pack(0, 3)) | |
95 | b2 = zstd.BufferWithSegments(b'barbaz', b''.join([ss.pack(0, 3), |
|
122 | b2 = zstd.BufferWithSegments(b'barbaz', b''.join([ss.pack(0, 3), | |
96 | ss.pack(3, 3)])) |
|
123 | ss.pack(3, 3)])) |
@@ -1,14 +1,17 b'' | |||||
1 | import hashlib |
|
1 | import hashlib | |
2 | import io |
|
2 | import io | |
|
3 | import os | |||
3 | import struct |
|
4 | import struct | |
4 | import sys |
|
5 | import sys | |
5 | import tarfile |
|
6 | import tarfile | |
|
7 | import tempfile | |||
6 | import unittest |
|
8 | import unittest | |
7 |
|
9 | |||
8 | import zstandard as zstd |
|
10 | import zstandard as zstd | |
9 |
|
11 | |||
10 | from .common import ( |
|
12 | from .common import ( | |
11 | make_cffi, |
|
13 | make_cffi, | |
|
14 | NonClosingBytesIO, | |||
12 | OpCountingBytesIO, |
|
15 | OpCountingBytesIO, | |
13 | ) |
|
16 | ) | |
14 |
|
17 | |||
@@ -272,7 +275,7 b' class TestCompressor_compressobj(unittes' | |||||
272 |
|
275 | |||
273 | params = zstd.get_frame_parameters(result) |
|
276 | params = zstd.get_frame_parameters(result) | |
274 | self.assertEqual(params.content_size, zstd.CONTENTSIZE_UNKNOWN) |
|
277 | self.assertEqual(params.content_size, zstd.CONTENTSIZE_UNKNOWN) | |
275 |
self.assertEqual(params.window_size, |
|
278 | self.assertEqual(params.window_size, 2097152) | |
276 | self.assertEqual(params.dict_id, 0) |
|
279 | self.assertEqual(params.dict_id, 0) | |
277 | self.assertFalse(params.has_checksum) |
|
280 | self.assertFalse(params.has_checksum) | |
278 |
|
281 | |||
@@ -321,7 +324,7 b' class TestCompressor_compressobj(unittes' | |||||
321 | cobj.compress(b'foo') |
|
324 | cobj.compress(b'foo') | |
322 | cobj.flush() |
|
325 | cobj.flush() | |
323 |
|
326 | |||
324 | with self.assertRaisesRegexp(zstd.ZstdError, 'cannot call compress\(\) after compressor'): |
|
327 | with self.assertRaisesRegexp(zstd.ZstdError, r'cannot call compress\(\) after compressor'): | |
325 | cobj.compress(b'foo') |
|
328 | cobj.compress(b'foo') | |
326 |
|
329 | |||
327 | with self.assertRaisesRegexp(zstd.ZstdError, 'compressor object already finished'): |
|
330 | with self.assertRaisesRegexp(zstd.ZstdError, 'compressor object already finished'): | |
@@ -453,7 +456,7 b' class TestCompressor_copy_stream(unittes' | |||||
453 |
|
456 | |||
454 | params = zstd.get_frame_parameters(dest.getvalue()) |
|
457 | params = zstd.get_frame_parameters(dest.getvalue()) | |
455 | self.assertEqual(params.content_size, zstd.CONTENTSIZE_UNKNOWN) |
|
458 | self.assertEqual(params.content_size, zstd.CONTENTSIZE_UNKNOWN) | |
456 |
self.assertEqual(params.window_size, |
|
459 | self.assertEqual(params.window_size, 2097152) | |
457 | self.assertEqual(params.dict_id, 0) |
|
460 | self.assertEqual(params.dict_id, 0) | |
458 | self.assertFalse(params.has_checksum) |
|
461 | self.assertFalse(params.has_checksum) | |
459 |
|
462 | |||
@@ -605,10 +608,6 b' class TestCompressor_stream_reader(unitt' | |||||
605 | with self.assertRaises(io.UnsupportedOperation): |
|
608 | with self.assertRaises(io.UnsupportedOperation): | |
606 | reader.readlines() |
|
609 | reader.readlines() | |
607 |
|
610 | |||
608 | # This could probably be implemented someday. |
|
|||
609 | with self.assertRaises(NotImplementedError): |
|
|||
610 | reader.readall() |
|
|||
611 |
|
||||
612 | with self.assertRaises(io.UnsupportedOperation): |
|
611 | with self.assertRaises(io.UnsupportedOperation): | |
613 | iter(reader) |
|
612 | iter(reader) | |
614 |
|
613 | |||
@@ -644,15 +643,16 b' class TestCompressor_stream_reader(unitt' | |||||
644 | with self.assertRaisesRegexp(ValueError, 'stream is closed'): |
|
643 | with self.assertRaisesRegexp(ValueError, 'stream is closed'): | |
645 | reader.read(10) |
|
644 | reader.read(10) | |
646 |
|
645 | |||
647 |
def test_read_ |
|
646 | def test_read_sizes(self): | |
648 | cctx = zstd.ZstdCompressor() |
|
647 | cctx = zstd.ZstdCompressor() | |
|
648 | foo = cctx.compress(b'foo') | |||
649 |
|
649 | |||
650 | with cctx.stream_reader(b'foo') as reader: |
|
650 | with cctx.stream_reader(b'foo') as reader: | |
651 |
with self.assertRaisesRegexp(ValueError, 'cannot read negative |
|
651 | with self.assertRaisesRegexp(ValueError, 'cannot read negative amounts less than -1'): | |
652 |
reader.read(- |
|
652 | reader.read(-2) | |
653 |
|
653 | |||
654 | with self.assertRaisesRegexp(ValueError, 'cannot read negative or size 0 amounts'): |
|
654 | self.assertEqual(reader.read(0), b'') | |
655 |
|
|
655 | self.assertEqual(reader.read(), foo) | |
656 |
|
656 | |||
657 | def test_read_buffer(self): |
|
657 | def test_read_buffer(self): | |
658 | cctx = zstd.ZstdCompressor() |
|
658 | cctx = zstd.ZstdCompressor() | |
@@ -746,11 +746,202 b' class TestCompressor_stream_reader(unitt' | |||||
746 | with cctx.stream_reader(source, size=42): |
|
746 | with cctx.stream_reader(source, size=42): | |
747 | pass |
|
747 | pass | |
748 |
|
748 | |||
|
749 | def test_readall(self): | |||
|
750 | cctx = zstd.ZstdCompressor() | |||
|
751 | frame = cctx.compress(b'foo' * 1024) | |||
|
752 | ||||
|
753 | reader = cctx.stream_reader(b'foo' * 1024) | |||
|
754 | self.assertEqual(reader.readall(), frame) | |||
|
755 | ||||
|
756 | def test_readinto(self): | |||
|
757 | cctx = zstd.ZstdCompressor() | |||
|
758 | foo = cctx.compress(b'foo') | |||
|
759 | ||||
|
760 | reader = cctx.stream_reader(b'foo') | |||
|
761 | with self.assertRaises(Exception): | |||
|
762 | reader.readinto(b'foobar') | |||
|
763 | ||||
|
764 | # readinto() with sufficiently large destination. | |||
|
765 | b = bytearray(1024) | |||
|
766 | reader = cctx.stream_reader(b'foo') | |||
|
767 | self.assertEqual(reader.readinto(b), len(foo)) | |||
|
768 | self.assertEqual(b[0:len(foo)], foo) | |||
|
769 | self.assertEqual(reader.readinto(b), 0) | |||
|
770 | self.assertEqual(b[0:len(foo)], foo) | |||
|
771 | ||||
|
772 | # readinto() with small reads. | |||
|
773 | b = bytearray(1024) | |||
|
774 | reader = cctx.stream_reader(b'foo', read_size=1) | |||
|
775 | self.assertEqual(reader.readinto(b), len(foo)) | |||
|
776 | self.assertEqual(b[0:len(foo)], foo) | |||
|
777 | ||||
|
778 | # Too small destination buffer. | |||
|
779 | b = bytearray(2) | |||
|
780 | reader = cctx.stream_reader(b'foo') | |||
|
781 | self.assertEqual(reader.readinto(b), 2) | |||
|
782 | self.assertEqual(b[:], foo[0:2]) | |||
|
783 | self.assertEqual(reader.readinto(b), 2) | |||
|
784 | self.assertEqual(b[:], foo[2:4]) | |||
|
785 | self.assertEqual(reader.readinto(b), 2) | |||
|
786 | self.assertEqual(b[:], foo[4:6]) | |||
|
787 | ||||
|
788 | def test_readinto1(self): | |||
|
789 | cctx = zstd.ZstdCompressor() | |||
|
790 | foo = b''.join(cctx.read_to_iter(io.BytesIO(b'foo'))) | |||
|
791 | ||||
|
792 | reader = cctx.stream_reader(b'foo') | |||
|
793 | with self.assertRaises(Exception): | |||
|
794 | reader.readinto1(b'foobar') | |||
|
795 | ||||
|
796 | b = bytearray(1024) | |||
|
797 | source = OpCountingBytesIO(b'foo') | |||
|
798 | reader = cctx.stream_reader(source) | |||
|
799 | self.assertEqual(reader.readinto1(b), len(foo)) | |||
|
800 | self.assertEqual(b[0:len(foo)], foo) | |||
|
801 | self.assertEqual(source._read_count, 2) | |||
|
802 | ||||
|
803 | # readinto1() with small reads. | |||
|
804 | b = bytearray(1024) | |||
|
805 | source = OpCountingBytesIO(b'foo') | |||
|
806 | reader = cctx.stream_reader(source, read_size=1) | |||
|
807 | self.assertEqual(reader.readinto1(b), len(foo)) | |||
|
808 | self.assertEqual(b[0:len(foo)], foo) | |||
|
809 | self.assertEqual(source._read_count, 4) | |||
|
810 | ||||
|
811 | def test_read1(self): | |||
|
812 | cctx = zstd.ZstdCompressor() | |||
|
813 | foo = b''.join(cctx.read_to_iter(io.BytesIO(b'foo'))) | |||
|
814 | ||||
|
815 | b = OpCountingBytesIO(b'foo') | |||
|
816 | reader = cctx.stream_reader(b) | |||
|
817 | ||||
|
818 | self.assertEqual(reader.read1(), foo) | |||
|
819 | self.assertEqual(b._read_count, 2) | |||
|
820 | ||||
|
821 | b = OpCountingBytesIO(b'foo') | |||
|
822 | reader = cctx.stream_reader(b) | |||
|
823 | ||||
|
824 | self.assertEqual(reader.read1(0), b'') | |||
|
825 | self.assertEqual(reader.read1(2), foo[0:2]) | |||
|
826 | self.assertEqual(b._read_count, 2) | |||
|
827 | self.assertEqual(reader.read1(2), foo[2:4]) | |||
|
828 | self.assertEqual(reader.read1(1024), foo[4:]) | |||
|
829 | ||||
749 |
|
830 | |||
750 | @make_cffi |
|
831 | @make_cffi | |
751 | class TestCompressor_stream_writer(unittest.TestCase): |
|
832 | class TestCompressor_stream_writer(unittest.TestCase): | |
|
833 | def test_io_api(self): | |||
|
834 | buffer = io.BytesIO() | |||
|
835 | cctx = zstd.ZstdCompressor() | |||
|
836 | writer = cctx.stream_writer(buffer) | |||
|
837 | ||||
|
838 | self.assertFalse(writer.isatty()) | |||
|
839 | self.assertFalse(writer.readable()) | |||
|
840 | ||||
|
841 | with self.assertRaises(io.UnsupportedOperation): | |||
|
842 | writer.readline() | |||
|
843 | ||||
|
844 | with self.assertRaises(io.UnsupportedOperation): | |||
|
845 | writer.readline(42) | |||
|
846 | ||||
|
847 | with self.assertRaises(io.UnsupportedOperation): | |||
|
848 | writer.readline(size=42) | |||
|
849 | ||||
|
850 | with self.assertRaises(io.UnsupportedOperation): | |||
|
851 | writer.readlines() | |||
|
852 | ||||
|
853 | with self.assertRaises(io.UnsupportedOperation): | |||
|
854 | writer.readlines(42) | |||
|
855 | ||||
|
856 | with self.assertRaises(io.UnsupportedOperation): | |||
|
857 | writer.readlines(hint=42) | |||
|
858 | ||||
|
859 | with self.assertRaises(io.UnsupportedOperation): | |||
|
860 | writer.seek(0) | |||
|
861 | ||||
|
862 | with self.assertRaises(io.UnsupportedOperation): | |||
|
863 | writer.seek(10, os.SEEK_SET) | |||
|
864 | ||||
|
865 | self.assertFalse(writer.seekable()) | |||
|
866 | ||||
|
867 | with self.assertRaises(io.UnsupportedOperation): | |||
|
868 | writer.truncate() | |||
|
869 | ||||
|
870 | with self.assertRaises(io.UnsupportedOperation): | |||
|
871 | writer.truncate(42) | |||
|
872 | ||||
|
873 | with self.assertRaises(io.UnsupportedOperation): | |||
|
874 | writer.truncate(size=42) | |||
|
875 | ||||
|
876 | self.assertTrue(writer.writable()) | |||
|
877 | ||||
|
878 | with self.assertRaises(NotImplementedError): | |||
|
879 | writer.writelines([]) | |||
|
880 | ||||
|
881 | with self.assertRaises(io.UnsupportedOperation): | |||
|
882 | writer.read() | |||
|
883 | ||||
|
884 | with self.assertRaises(io.UnsupportedOperation): | |||
|
885 | writer.read(42) | |||
|
886 | ||||
|
887 | with self.assertRaises(io.UnsupportedOperation): | |||
|
888 | writer.read(size=42) | |||
|
889 | ||||
|
890 | with self.assertRaises(io.UnsupportedOperation): | |||
|
891 | writer.readall() | |||
|
892 | ||||
|
893 | with self.assertRaises(io.UnsupportedOperation): | |||
|
894 | writer.readinto(None) | |||
|
895 | ||||
|
896 | with self.assertRaises(io.UnsupportedOperation): | |||
|
897 | writer.fileno() | |||
|
898 | ||||
|
899 | self.assertFalse(writer.closed) | |||
|
900 | ||||
|
901 | def test_fileno_file(self): | |||
|
902 | with tempfile.TemporaryFile('wb') as tf: | |||
|
903 | cctx = zstd.ZstdCompressor() | |||
|
904 | writer = cctx.stream_writer(tf) | |||
|
905 | ||||
|
906 | self.assertEqual(writer.fileno(), tf.fileno()) | |||
|
907 | ||||
|
908 | def test_close(self): | |||
|
909 | buffer = NonClosingBytesIO() | |||
|
910 | cctx = zstd.ZstdCompressor(level=1) | |||
|
911 | writer = cctx.stream_writer(buffer) | |||
|
912 | ||||
|
913 | writer.write(b'foo' * 1024) | |||
|
914 | self.assertFalse(writer.closed) | |||
|
915 | self.assertFalse(buffer.closed) | |||
|
916 | writer.close() | |||
|
917 | self.assertTrue(writer.closed) | |||
|
918 | self.assertTrue(buffer.closed) | |||
|
919 | ||||
|
920 | with self.assertRaisesRegexp(ValueError, 'stream is closed'): | |||
|
921 | writer.write(b'foo') | |||
|
922 | ||||
|
923 | with self.assertRaisesRegexp(ValueError, 'stream is closed'): | |||
|
924 | writer.flush() | |||
|
925 | ||||
|
926 | with self.assertRaisesRegexp(ValueError, 'stream is closed'): | |||
|
927 | with writer: | |||
|
928 | pass | |||
|
929 | ||||
|
930 | self.assertEqual(buffer.getvalue(), | |||
|
931 | b'\x28\xb5\x2f\xfd\x00\x48\x55\x00\x00\x18\x66\x6f' | |||
|
932 | b'\x6f\x01\x00\xfa\xd3\x77\x43') | |||
|
933 | ||||
|
934 | # Context manager exit should close stream. | |||
|
935 | buffer = io.BytesIO() | |||
|
936 | writer = cctx.stream_writer(buffer) | |||
|
937 | ||||
|
938 | with writer: | |||
|
939 | writer.write(b'foo') | |||
|
940 | ||||
|
941 | self.assertTrue(writer.closed) | |||
|
942 | ||||
752 | def test_empty(self): |
|
943 | def test_empty(self): | |
753 |
buffer = |
|
944 | buffer = NonClosingBytesIO() | |
754 | cctx = zstd.ZstdCompressor(level=1, write_content_size=False) |
|
945 | cctx = zstd.ZstdCompressor(level=1, write_content_size=False) | |
755 | with cctx.stream_writer(buffer) as compressor: |
|
946 | with cctx.stream_writer(buffer) as compressor: | |
756 | compressor.write(b'') |
|
947 | compressor.write(b'') | |
@@ -764,6 +955,25 b' class TestCompressor_stream_writer(unitt' | |||||
764 | self.assertEqual(params.dict_id, 0) |
|
955 | self.assertEqual(params.dict_id, 0) | |
765 | self.assertFalse(params.has_checksum) |
|
956 | self.assertFalse(params.has_checksum) | |
766 |
|
957 | |||
|
958 | # Test without context manager. | |||
|
959 | buffer = io.BytesIO() | |||
|
960 | compressor = cctx.stream_writer(buffer) | |||
|
961 | self.assertEqual(compressor.write(b''), 0) | |||
|
962 | self.assertEqual(buffer.getvalue(), b'') | |||
|
963 | self.assertEqual(compressor.flush(zstd.FLUSH_FRAME), 9) | |||
|
964 | result = buffer.getvalue() | |||
|
965 | self.assertEqual(result, b'\x28\xb5\x2f\xfd\x00\x48\x01\x00\x00') | |||
|
966 | ||||
|
967 | params = zstd.get_frame_parameters(result) | |||
|
968 | self.assertEqual(params.content_size, zstd.CONTENTSIZE_UNKNOWN) | |||
|
969 | self.assertEqual(params.window_size, 524288) | |||
|
970 | self.assertEqual(params.dict_id, 0) | |||
|
971 | self.assertFalse(params.has_checksum) | |||
|
972 | ||||
|
973 | # Test write_return_read=True | |||
|
974 | compressor = cctx.stream_writer(buffer, write_return_read=True) | |||
|
975 | self.assertEqual(compressor.write(b''), 0) | |||
|
976 | ||||
767 | def test_input_types(self): |
|
977 | def test_input_types(self): | |
768 | expected = b'\x28\xb5\x2f\xfd\x00\x48\x19\x00\x00\x66\x6f\x6f' |
|
978 | expected = b'\x28\xb5\x2f\xfd\x00\x48\x19\x00\x00\x66\x6f\x6f' | |
769 | cctx = zstd.ZstdCompressor(level=1) |
|
979 | cctx = zstd.ZstdCompressor(level=1) | |
@@ -778,14 +988,17 b' class TestCompressor_stream_writer(unitt' | |||||
778 | ] |
|
988 | ] | |
779 |
|
989 | |||
780 | for source in sources: |
|
990 | for source in sources: | |
781 |
buffer = |
|
991 | buffer = NonClosingBytesIO() | |
782 | with cctx.stream_writer(buffer) as compressor: |
|
992 | with cctx.stream_writer(buffer) as compressor: | |
783 | compressor.write(source) |
|
993 | compressor.write(source) | |
784 |
|
994 | |||
785 | self.assertEqual(buffer.getvalue(), expected) |
|
995 | self.assertEqual(buffer.getvalue(), expected) | |
786 |
|
996 | |||
|
997 | compressor = cctx.stream_writer(buffer, write_return_read=True) | |||
|
998 | self.assertEqual(compressor.write(source), len(source)) | |||
|
999 | ||||
787 | def test_multiple_compress(self): |
|
1000 | def test_multiple_compress(self): | |
788 |
buffer = |
|
1001 | buffer = NonClosingBytesIO() | |
789 | cctx = zstd.ZstdCompressor(level=5) |
|
1002 | cctx = zstd.ZstdCompressor(level=5) | |
790 | with cctx.stream_writer(buffer) as compressor: |
|
1003 | with cctx.stream_writer(buffer) as compressor: | |
791 | self.assertEqual(compressor.write(b'foo'), 0) |
|
1004 | self.assertEqual(compressor.write(b'foo'), 0) | |
@@ -794,9 +1007,27 b' class TestCompressor_stream_writer(unitt' | |||||
794 |
|
1007 | |||
795 | result = buffer.getvalue() |
|
1008 | result = buffer.getvalue() | |
796 | self.assertEqual(result, |
|
1009 | self.assertEqual(result, | |
797 |
b'\x28\xb5\x2f\xfd\x00\x5 |
|
1010 | b'\x28\xb5\x2f\xfd\x00\x58\x75\x00\x00\x38\x66\x6f' | |
798 | b'\x6f\x62\x61\x72\x78\x01\x00\xfc\xdf\x03\x23') |
|
1011 | b'\x6f\x62\x61\x72\x78\x01\x00\xfc\xdf\x03\x23') | |
799 |
|
1012 | |||
|
1013 | # Test without context manager. | |||
|
1014 | buffer = io.BytesIO() | |||
|
1015 | compressor = cctx.stream_writer(buffer) | |||
|
1016 | self.assertEqual(compressor.write(b'foo'), 0) | |||
|
1017 | self.assertEqual(compressor.write(b'bar'), 0) | |||
|
1018 | self.assertEqual(compressor.write(b'x' * 8192), 0) | |||
|
1019 | self.assertEqual(compressor.flush(zstd.FLUSH_FRAME), 23) | |||
|
1020 | result = buffer.getvalue() | |||
|
1021 | self.assertEqual(result, | |||
|
1022 | b'\x28\xb5\x2f\xfd\x00\x58\x75\x00\x00\x38\x66\x6f' | |||
|
1023 | b'\x6f\x62\x61\x72\x78\x01\x00\xfc\xdf\x03\x23') | |||
|
1024 | ||||
|
1025 | # Test with write_return_read=True. | |||
|
1026 | compressor = cctx.stream_writer(buffer, write_return_read=True) | |||
|
1027 | self.assertEqual(compressor.write(b'foo'), 3) | |||
|
1028 | self.assertEqual(compressor.write(b'barbiz'), 6) | |||
|
1029 | self.assertEqual(compressor.write(b'x' * 8192), 8192) | |||
|
1030 | ||||
800 | def test_dictionary(self): |
|
1031 | def test_dictionary(self): | |
801 | samples = [] |
|
1032 | samples = [] | |
802 | for i in range(128): |
|
1033 | for i in range(128): | |
@@ -807,9 +1038,9 b' class TestCompressor_stream_writer(unitt' | |||||
807 | d = zstd.train_dictionary(8192, samples) |
|
1038 | d = zstd.train_dictionary(8192, samples) | |
808 |
|
1039 | |||
809 | h = hashlib.sha1(d.as_bytes()).hexdigest() |
|
1040 | h = hashlib.sha1(d.as_bytes()).hexdigest() | |
810 | self.assertEqual(h, '2b3b6428da5bf2c9cc9d4bb58ba0bc5990dd0e79') |
|
1041 | self.assertEqual(h, '88ca0d38332aff379d4ced166a51c280a7679aad') | |
811 |
|
1042 | |||
812 |
buffer = |
|
1043 | buffer = NonClosingBytesIO() | |
813 | cctx = zstd.ZstdCompressor(level=9, dict_data=d) |
|
1044 | cctx = zstd.ZstdCompressor(level=9, dict_data=d) | |
814 | with cctx.stream_writer(buffer) as compressor: |
|
1045 | with cctx.stream_writer(buffer) as compressor: | |
815 | self.assertEqual(compressor.write(b'foo'), 0) |
|
1046 | self.assertEqual(compressor.write(b'foo'), 0) | |
@@ -825,7 +1056,7 b' class TestCompressor_stream_writer(unitt' | |||||
825 | self.assertFalse(params.has_checksum) |
|
1056 | self.assertFalse(params.has_checksum) | |
826 |
|
1057 | |||
827 | h = hashlib.sha1(compressed).hexdigest() |
|
1058 | h = hashlib.sha1(compressed).hexdigest() | |
828 |
self.assertEqual(h, ' |
|
1059 | self.assertEqual(h, '8703b4316f274d26697ea5dd480f29c08e85d940') | |
829 |
|
1060 | |||
830 | source = b'foo' + b'bar' + (b'foo' * 16384) |
|
1061 | source = b'foo' + b'bar' + (b'foo' * 16384) | |
831 |
|
1062 | |||
@@ -842,9 +1073,9 b' class TestCompressor_stream_writer(unitt' | |||||
842 | min_match=5, |
|
1073 | min_match=5, | |
843 | search_log=4, |
|
1074 | search_log=4, | |
844 | target_length=10, |
|
1075 | target_length=10, | |
845 |
|
|
1076 | strategy=zstd.STRATEGY_FAST) | |
846 |
|
1077 | |||
847 |
buffer = |
|
1078 | buffer = NonClosingBytesIO() | |
848 | cctx = zstd.ZstdCompressor(compression_params=params) |
|
1079 | cctx = zstd.ZstdCompressor(compression_params=params) | |
849 | with cctx.stream_writer(buffer) as compressor: |
|
1080 | with cctx.stream_writer(buffer) as compressor: | |
850 | self.assertEqual(compressor.write(b'foo'), 0) |
|
1081 | self.assertEqual(compressor.write(b'foo'), 0) | |
@@ -863,12 +1094,12 b' class TestCompressor_stream_writer(unitt' | |||||
863 | self.assertEqual(h, '2a8111d72eb5004cdcecbdac37da9f26720d30ef') |
|
1094 | self.assertEqual(h, '2a8111d72eb5004cdcecbdac37da9f26720d30ef') | |
864 |
|
1095 | |||
865 | def test_write_checksum(self): |
|
1096 | def test_write_checksum(self): | |
866 |
no_checksum = |
|
1097 | no_checksum = NonClosingBytesIO() | |
867 | cctx = zstd.ZstdCompressor(level=1) |
|
1098 | cctx = zstd.ZstdCompressor(level=1) | |
868 | with cctx.stream_writer(no_checksum) as compressor: |
|
1099 | with cctx.stream_writer(no_checksum) as compressor: | |
869 | self.assertEqual(compressor.write(b'foobar'), 0) |
|
1100 | self.assertEqual(compressor.write(b'foobar'), 0) | |
870 |
|
1101 | |||
871 |
with_checksum = |
|
1102 | with_checksum = NonClosingBytesIO() | |
872 | cctx = zstd.ZstdCompressor(level=1, write_checksum=True) |
|
1103 | cctx = zstd.ZstdCompressor(level=1, write_checksum=True) | |
873 | with cctx.stream_writer(with_checksum) as compressor: |
|
1104 | with cctx.stream_writer(with_checksum) as compressor: | |
874 | self.assertEqual(compressor.write(b'foobar'), 0) |
|
1105 | self.assertEqual(compressor.write(b'foobar'), 0) | |
@@ -886,12 +1117,12 b' class TestCompressor_stream_writer(unitt' | |||||
886 | len(no_checksum.getvalue()) + 4) |
|
1117 | len(no_checksum.getvalue()) + 4) | |
887 |
|
1118 | |||
888 | def test_write_content_size(self): |
|
1119 | def test_write_content_size(self): | |
889 |
no_size = |
|
1120 | no_size = NonClosingBytesIO() | |
890 | cctx = zstd.ZstdCompressor(level=1, write_content_size=False) |
|
1121 | cctx = zstd.ZstdCompressor(level=1, write_content_size=False) | |
891 | with cctx.stream_writer(no_size) as compressor: |
|
1122 | with cctx.stream_writer(no_size) as compressor: | |
892 | self.assertEqual(compressor.write(b'foobar' * 256), 0) |
|
1123 | self.assertEqual(compressor.write(b'foobar' * 256), 0) | |
893 |
|
1124 | |||
894 |
with_size = |
|
1125 | with_size = NonClosingBytesIO() | |
895 | cctx = zstd.ZstdCompressor(level=1) |
|
1126 | cctx = zstd.ZstdCompressor(level=1) | |
896 | with cctx.stream_writer(with_size) as compressor: |
|
1127 | with cctx.stream_writer(with_size) as compressor: | |
897 | self.assertEqual(compressor.write(b'foobar' * 256), 0) |
|
1128 | self.assertEqual(compressor.write(b'foobar' * 256), 0) | |
@@ -902,7 +1133,7 b' class TestCompressor_stream_writer(unitt' | |||||
902 | len(no_size.getvalue())) |
|
1133 | len(no_size.getvalue())) | |
903 |
|
1134 | |||
904 | # Declaring size will write the header. |
|
1135 | # Declaring size will write the header. | |
905 |
with_size = |
|
1136 | with_size = NonClosingBytesIO() | |
906 | with cctx.stream_writer(with_size, size=len(b'foobar' * 256)) as compressor: |
|
1137 | with cctx.stream_writer(with_size, size=len(b'foobar' * 256)) as compressor: | |
907 | self.assertEqual(compressor.write(b'foobar' * 256), 0) |
|
1138 | self.assertEqual(compressor.write(b'foobar' * 256), 0) | |
908 |
|
1139 | |||
@@ -927,7 +1158,7 b' class TestCompressor_stream_writer(unitt' | |||||
927 |
|
1158 | |||
928 | d = zstd.train_dictionary(1024, samples) |
|
1159 | d = zstd.train_dictionary(1024, samples) | |
929 |
|
1160 | |||
930 |
with_dict_id = |
|
1161 | with_dict_id = NonClosingBytesIO() | |
931 | cctx = zstd.ZstdCompressor(level=1, dict_data=d) |
|
1162 | cctx = zstd.ZstdCompressor(level=1, dict_data=d) | |
932 | with cctx.stream_writer(with_dict_id) as compressor: |
|
1163 | with cctx.stream_writer(with_dict_id) as compressor: | |
933 | self.assertEqual(compressor.write(b'foobarfoobar'), 0) |
|
1164 | self.assertEqual(compressor.write(b'foobarfoobar'), 0) | |
@@ -935,7 +1166,7 b' class TestCompressor_stream_writer(unitt' | |||||
935 | self.assertEqual(with_dict_id.getvalue()[4:5], b'\x03') |
|
1166 | self.assertEqual(with_dict_id.getvalue()[4:5], b'\x03') | |
936 |
|
1167 | |||
937 | cctx = zstd.ZstdCompressor(level=1, dict_data=d, write_dict_id=False) |
|
1168 | cctx = zstd.ZstdCompressor(level=1, dict_data=d, write_dict_id=False) | |
938 |
no_dict_id = |
|
1169 | no_dict_id = NonClosingBytesIO() | |
939 | with cctx.stream_writer(no_dict_id) as compressor: |
|
1170 | with cctx.stream_writer(no_dict_id) as compressor: | |
940 | self.assertEqual(compressor.write(b'foobarfoobar'), 0) |
|
1171 | self.assertEqual(compressor.write(b'foobarfoobar'), 0) | |
941 |
|
1172 | |||
@@ -1009,8 +1240,32 b' class TestCompressor_stream_writer(unitt' | |||||
1009 | header = trailing[0:3] |
|
1240 | header = trailing[0:3] | |
1010 | self.assertEqual(header, b'\x01\x00\x00') |
|
1241 | self.assertEqual(header, b'\x01\x00\x00') | |
1011 |
|
1242 | |||
|
1243 | def test_flush_frame(self): | |||
|
1244 | cctx = zstd.ZstdCompressor(level=3) | |||
|
1245 | dest = OpCountingBytesIO() | |||
|
1246 | ||||
|
1247 | with cctx.stream_writer(dest) as compressor: | |||
|
1248 | self.assertEqual(compressor.write(b'foobar' * 8192), 0) | |||
|
1249 | self.assertEqual(compressor.flush(zstd.FLUSH_FRAME), 23) | |||
|
1250 | compressor.write(b'biz' * 16384) | |||
|
1251 | ||||
|
1252 | self.assertEqual(dest.getvalue(), | |||
|
1253 | # Frame 1. | |||
|
1254 | b'\x28\xb5\x2f\xfd\x00\x58\x75\x00\x00\x30\x66\x6f\x6f' | |||
|
1255 | b'\x62\x61\x72\x01\x00\xf7\xbf\xe8\xa5\x08' | |||
|
1256 | # Frame 2. | |||
|
1257 | b'\x28\xb5\x2f\xfd\x00\x58\x5d\x00\x00\x18\x62\x69\x7a' | |||
|
1258 | b'\x01\x00\xfa\x3f\x75\x37\x04') | |||
|
1259 | ||||
|
1260 | def test_bad_flush_mode(self): | |||
|
1261 | cctx = zstd.ZstdCompressor() | |||
|
1262 | dest = io.BytesIO() | |||
|
1263 | with cctx.stream_writer(dest) as compressor: | |||
|
1264 | with self.assertRaisesRegexp(ValueError, 'unknown flush_mode: 42'): | |||
|
1265 | compressor.flush(flush_mode=42) | |||
|
1266 | ||||
1012 | def test_multithreaded(self): |
|
1267 | def test_multithreaded(self): | |
1013 |
dest = |
|
1268 | dest = NonClosingBytesIO() | |
1014 | cctx = zstd.ZstdCompressor(threads=2) |
|
1269 | cctx = zstd.ZstdCompressor(threads=2) | |
1015 | with cctx.stream_writer(dest) as compressor: |
|
1270 | with cctx.stream_writer(dest) as compressor: | |
1016 | compressor.write(b'a' * 1048576) |
|
1271 | compressor.write(b'a' * 1048576) | |
@@ -1043,22 +1298,21 b' class TestCompressor_stream_writer(unitt' | |||||
1043 | pass |
|
1298 | pass | |
1044 |
|
1299 | |||
1045 | def test_tarfile_compat(self): |
|
1300 | def test_tarfile_compat(self): | |
1046 | raise unittest.SkipTest('not yet fully working') |
|
1301 | dest = NonClosingBytesIO() | |
1047 |
|
||||
1048 | dest = io.BytesIO() |
|
|||
1049 | cctx = zstd.ZstdCompressor() |
|
1302 | cctx = zstd.ZstdCompressor() | |
1050 | with cctx.stream_writer(dest) as compressor: |
|
1303 | with cctx.stream_writer(dest) as compressor: | |
1051 | with tarfile.open('tf', mode='w', fileobj=compressor) as tf: |
|
1304 | with tarfile.open('tf', mode='w|', fileobj=compressor) as tf: | |
1052 | tf.add(__file__, 'test_compressor.py') |
|
1305 | tf.add(__file__, 'test_compressor.py') | |
1053 |
|
1306 | |||
1054 | dest.seek(0) |
|
1307 | dest = io.BytesIO(dest.getvalue()) | |
1055 |
|
1308 | |||
1056 | dctx = zstd.ZstdDecompressor() |
|
1309 | dctx = zstd.ZstdDecompressor() | |
1057 | with dctx.stream_reader(dest) as reader: |
|
1310 | with dctx.stream_reader(dest) as reader: | |
1058 |
with tarfile.open(mode='r |
|
1311 | with tarfile.open(mode='r|', fileobj=reader) as tf: | |
1059 | for member in tf: |
|
1312 | for member in tf: | |
1060 | self.assertEqual(member.name, 'test_compressor.py') |
|
1313 | self.assertEqual(member.name, 'test_compressor.py') | |
1061 |
|
1314 | |||
|
1315 | ||||
1062 | @make_cffi |
|
1316 | @make_cffi | |
1063 | class TestCompressor_read_to_iter(unittest.TestCase): |
|
1317 | class TestCompressor_read_to_iter(unittest.TestCase): | |
1064 | def test_type_validation(self): |
|
1318 | def test_type_validation(self): | |
@@ -1192,7 +1446,7 b' class TestCompressor_chunker(unittest.Te' | |||||
1192 |
|
1446 | |||
1193 | it = chunker.finish() |
|
1447 | it = chunker.finish() | |
1194 |
|
1448 | |||
1195 |
self.assertEqual(next(it), b'\x28\xb5\x2f\xfd\x00\x5 |
|
1449 | self.assertEqual(next(it), b'\x28\xb5\x2f\xfd\x00\x58\x01\x00\x00') | |
1196 |
|
1450 | |||
1197 | with self.assertRaises(StopIteration): |
|
1451 | with self.assertRaises(StopIteration): | |
1198 | next(it) |
|
1452 | next(it) | |
@@ -1214,7 +1468,7 b' class TestCompressor_chunker(unittest.Te' | |||||
1214 | it = chunker.finish() |
|
1468 | it = chunker.finish() | |
1215 |
|
1469 | |||
1216 | self.assertEqual(next(it), |
|
1470 | self.assertEqual(next(it), | |
1217 |
b'\x28\xb5\x2f\xfd\x00\x5 |
|
1471 | b'\x28\xb5\x2f\xfd\x00\x58\x7d\x00\x00\x48\x66\x6f' | |
1218 | b'\x6f\x62\x61\x72\x62\x61\x7a\x01\x00\xe4\xe4\x8e') |
|
1472 | b'\x6f\x62\x61\x72\x62\x61\x7a\x01\x00\xe4\xe4\x8e') | |
1219 |
|
1473 | |||
1220 | with self.assertRaises(StopIteration): |
|
1474 | with self.assertRaises(StopIteration): | |
@@ -1258,7 +1512,7 b' class TestCompressor_chunker(unittest.Te' | |||||
1258 |
|
1512 | |||
1259 | self.assertEqual( |
|
1513 | self.assertEqual( | |
1260 | b''.join(chunks), |
|
1514 | b''.join(chunks), | |
1261 |
b'\x28\xb5\x2f\xfd\x00\x5 |
|
1515 | b'\x28\xb5\x2f\xfd\x00\x58\x55\x00\x00\x18\x66\x6f\x6f\x01\x00' | |
1262 | b'\xfa\xd3\x77\x43') |
|
1516 | b'\xfa\xd3\x77\x43') | |
1263 |
|
1517 | |||
1264 | dctx = zstd.ZstdDecompressor() |
|
1518 | dctx = zstd.ZstdDecompressor() | |
@@ -1283,7 +1537,7 b' class TestCompressor_chunker(unittest.Te' | |||||
1283 |
|
1537 | |||
1284 | self.assertEqual(list(chunker.compress(source)), []) |
|
1538 | self.assertEqual(list(chunker.compress(source)), []) | |
1285 | self.assertEqual(list(chunker.finish()), [ |
|
1539 | self.assertEqual(list(chunker.finish()), [ | |
1286 |
b'\x28\xb5\x2f\xfd\x00\x5 |
|
1540 | b'\x28\xb5\x2f\xfd\x00\x58\x19\x00\x00\x66\x6f\x6f' | |
1287 | ]) |
|
1541 | ]) | |
1288 |
|
1542 | |||
1289 | def test_flush(self): |
|
1543 | def test_flush(self): | |
@@ -1296,7 +1550,7 b' class TestCompressor_chunker(unittest.Te' | |||||
1296 | chunks1 = list(chunker.flush()) |
|
1550 | chunks1 = list(chunker.flush()) | |
1297 |
|
1551 | |||
1298 | self.assertEqual(chunks1, [ |
|
1552 | self.assertEqual(chunks1, [ | |
1299 |
b'\x28\xb5\x2f\xfd\x00\x5 |
|
1553 | b'\x28\xb5\x2f\xfd\x00\x58\x8c\x00\x00\x30\x66\x6f\x6f\x62\x61\x72' | |
1300 | b'\x02\x00\xfa\x03\xfe\xd0\x9f\xbe\x1b\x02' |
|
1554 | b'\x02\x00\xfa\x03\xfe\xd0\x9f\xbe\x1b\x02' | |
1301 | ]) |
|
1555 | ]) | |
1302 |
|
1556 | |||
@@ -1326,7 +1580,7 b' class TestCompressor_chunker(unittest.Te' | |||||
1326 |
|
1580 | |||
1327 | with self.assertRaisesRegexp( |
|
1581 | with self.assertRaisesRegexp( | |
1328 | zstd.ZstdError, |
|
1582 | zstd.ZstdError, | |
1329 | 'cannot call compress\(\) after compression finished'): |
|
1583 | r'cannot call compress\(\) after compression finished'): | |
1330 | list(chunker.compress(b'foo')) |
|
1584 | list(chunker.compress(b'foo')) | |
1331 |
|
1585 | |||
1332 | def test_flush_after_finish(self): |
|
1586 | def test_flush_after_finish(self): | |
@@ -1338,7 +1592,7 b' class TestCompressor_chunker(unittest.Te' | |||||
1338 |
|
1592 | |||
1339 | with self.assertRaisesRegexp( |
|
1593 | with self.assertRaisesRegexp( | |
1340 | zstd.ZstdError, |
|
1594 | zstd.ZstdError, | |
1341 | 'cannot call flush\(\) after compression finished'): |
|
1595 | r'cannot call flush\(\) after compression finished'): | |
1342 | list(chunker.flush()) |
|
1596 | list(chunker.flush()) | |
1343 |
|
1597 | |||
1344 | def test_finish_after_finish(self): |
|
1598 | def test_finish_after_finish(self): | |
@@ -1350,7 +1604,7 b' class TestCompressor_chunker(unittest.Te' | |||||
1350 |
|
1604 | |||
1351 | with self.assertRaisesRegexp( |
|
1605 | with self.assertRaisesRegexp( | |
1352 | zstd.ZstdError, |
|
1606 | zstd.ZstdError, | |
1353 | 'cannot call finish\(\) after compression finished'): |
|
1607 | r'cannot call finish\(\) after compression finished'): | |
1354 | list(chunker.finish()) |
|
1608 | list(chunker.finish()) | |
1355 |
|
1609 | |||
1356 |
|
1610 | |||
@@ -1358,6 +1612,9 b' class TestCompressor_multi_compress_to_b' | |||||
1358 | def test_invalid_inputs(self): |
|
1612 | def test_invalid_inputs(self): | |
1359 | cctx = zstd.ZstdCompressor() |
|
1613 | cctx = zstd.ZstdCompressor() | |
1360 |
|
1614 | |||
|
1615 | if not hasattr(cctx, 'multi_compress_to_buffer'): | |||
|
1616 | self.skipTest('multi_compress_to_buffer not available') | |||
|
1617 | ||||
1361 | with self.assertRaises(TypeError): |
|
1618 | with self.assertRaises(TypeError): | |
1362 | cctx.multi_compress_to_buffer(True) |
|
1619 | cctx.multi_compress_to_buffer(True) | |
1363 |
|
1620 | |||
@@ -1370,6 +1627,9 b' class TestCompressor_multi_compress_to_b' | |||||
1370 | def test_empty_input(self): |
|
1627 | def test_empty_input(self): | |
1371 | cctx = zstd.ZstdCompressor() |
|
1628 | cctx = zstd.ZstdCompressor() | |
1372 |
|
1629 | |||
|
1630 | if not hasattr(cctx, 'multi_compress_to_buffer'): | |||
|
1631 | self.skipTest('multi_compress_to_buffer not available') | |||
|
1632 | ||||
1373 | with self.assertRaisesRegexp(ValueError, 'no source elements found'): |
|
1633 | with self.assertRaisesRegexp(ValueError, 'no source elements found'): | |
1374 | cctx.multi_compress_to_buffer([]) |
|
1634 | cctx.multi_compress_to_buffer([]) | |
1375 |
|
1635 | |||
@@ -1379,6 +1639,9 b' class TestCompressor_multi_compress_to_b' | |||||
1379 | def test_list_input(self): |
|
1639 | def test_list_input(self): | |
1380 | cctx = zstd.ZstdCompressor(write_checksum=True) |
|
1640 | cctx = zstd.ZstdCompressor(write_checksum=True) | |
1381 |
|
1641 | |||
|
1642 | if not hasattr(cctx, 'multi_compress_to_buffer'): | |||
|
1643 | self.skipTest('multi_compress_to_buffer not available') | |||
|
1644 | ||||
1382 | original = [b'foo' * 12, b'bar' * 6] |
|
1645 | original = [b'foo' * 12, b'bar' * 6] | |
1383 | frames = [cctx.compress(c) for c in original] |
|
1646 | frames = [cctx.compress(c) for c in original] | |
1384 | b = cctx.multi_compress_to_buffer(original) |
|
1647 | b = cctx.multi_compress_to_buffer(original) | |
@@ -1394,6 +1657,9 b' class TestCompressor_multi_compress_to_b' | |||||
1394 | def test_buffer_with_segments_input(self): |
|
1657 | def test_buffer_with_segments_input(self): | |
1395 | cctx = zstd.ZstdCompressor(write_checksum=True) |
|
1658 | cctx = zstd.ZstdCompressor(write_checksum=True) | |
1396 |
|
1659 | |||
|
1660 | if not hasattr(cctx, 'multi_compress_to_buffer'): | |||
|
1661 | self.skipTest('multi_compress_to_buffer not available') | |||
|
1662 | ||||
1397 | original = [b'foo' * 4, b'bar' * 6] |
|
1663 | original = [b'foo' * 4, b'bar' * 6] | |
1398 | frames = [cctx.compress(c) for c in original] |
|
1664 | frames = [cctx.compress(c) for c in original] | |
1399 |
|
1665 | |||
@@ -1412,6 +1678,9 b' class TestCompressor_multi_compress_to_b' | |||||
1412 | def test_buffer_with_segments_collection_input(self): |
|
1678 | def test_buffer_with_segments_collection_input(self): | |
1413 | cctx = zstd.ZstdCompressor(write_checksum=True) |
|
1679 | cctx = zstd.ZstdCompressor(write_checksum=True) | |
1414 |
|
1680 | |||
|
1681 | if not hasattr(cctx, 'multi_compress_to_buffer'): | |||
|
1682 | self.skipTest('multi_compress_to_buffer not available') | |||
|
1683 | ||||
1415 | original = [ |
|
1684 | original = [ | |
1416 | b'foo1', |
|
1685 | b'foo1', | |
1417 | b'foo2' * 2, |
|
1686 | b'foo2' * 2, | |
@@ -1449,6 +1718,9 b' class TestCompressor_multi_compress_to_b' | |||||
1449 |
|
1718 | |||
1450 | cctx = zstd.ZstdCompressor(write_checksum=True) |
|
1719 | cctx = zstd.ZstdCompressor(write_checksum=True) | |
1451 |
|
1720 | |||
|
1721 | if not hasattr(cctx, 'multi_compress_to_buffer'): | |||
|
1722 | self.skipTest('multi_compress_to_buffer not available') | |||
|
1723 | ||||
1452 | frames = [] |
|
1724 | frames = [] | |
1453 | frames.extend(b'x' * 64 for i in range(256)) |
|
1725 | frames.extend(b'x' * 64 for i in range(256)) | |
1454 | frames.extend(b'y' * 64 for i in range(256)) |
|
1726 | frames.extend(b'y' * 64 for i in range(256)) |
@@ -12,6 +12,7 b' import zstandard as zstd' | |||||
12 |
|
12 | |||
13 | from . common import ( |
|
13 | from . common import ( | |
14 | make_cffi, |
|
14 | make_cffi, | |
|
15 | NonClosingBytesIO, | |||
15 | random_input_data, |
|
16 | random_input_data, | |
16 | ) |
|
17 | ) | |
17 |
|
18 | |||
@@ -19,6 +20,62 b' from . common import (' | |||||
19 | @unittest.skipUnless('ZSTD_SLOW_TESTS' in os.environ, 'ZSTD_SLOW_TESTS not set') |
|
20 | @unittest.skipUnless('ZSTD_SLOW_TESTS' in os.environ, 'ZSTD_SLOW_TESTS not set') | |
20 | @make_cffi |
|
21 | @make_cffi | |
21 | class TestCompressor_stream_reader_fuzzing(unittest.TestCase): |
|
22 | class TestCompressor_stream_reader_fuzzing(unittest.TestCase): | |
|
23 | @hypothesis.settings( | |||
|
24 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
25 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
26 | level=strategies.integers(min_value=1, max_value=5), | |||
|
27 | source_read_size=strategies.integers(1, 16384), | |||
|
28 | read_size=strategies.integers(-1, zstd.COMPRESSION_RECOMMENDED_OUTPUT_SIZE)) | |||
|
29 | def test_stream_source_read(self, original, level, source_read_size, | |||
|
30 | read_size): | |||
|
31 | if read_size == 0: | |||
|
32 | read_size = -1 | |||
|
33 | ||||
|
34 | refctx = zstd.ZstdCompressor(level=level) | |||
|
35 | ref_frame = refctx.compress(original) | |||
|
36 | ||||
|
37 | cctx = zstd.ZstdCompressor(level=level) | |||
|
38 | with cctx.stream_reader(io.BytesIO(original), size=len(original), | |||
|
39 | read_size=source_read_size) as reader: | |||
|
40 | chunks = [] | |||
|
41 | while True: | |||
|
42 | chunk = reader.read(read_size) | |||
|
43 | if not chunk: | |||
|
44 | break | |||
|
45 | ||||
|
46 | chunks.append(chunk) | |||
|
47 | ||||
|
48 | self.assertEqual(b''.join(chunks), ref_frame) | |||
|
49 | ||||
|
50 | @hypothesis.settings( | |||
|
51 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
52 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
53 | level=strategies.integers(min_value=1, max_value=5), | |||
|
54 | source_read_size=strategies.integers(1, 16384), | |||
|
55 | read_size=strategies.integers(-1, zstd.COMPRESSION_RECOMMENDED_OUTPUT_SIZE)) | |||
|
56 | def test_buffer_source_read(self, original, level, source_read_size, | |||
|
57 | read_size): | |||
|
58 | if read_size == 0: | |||
|
59 | read_size = -1 | |||
|
60 | ||||
|
61 | refctx = zstd.ZstdCompressor(level=level) | |||
|
62 | ref_frame = refctx.compress(original) | |||
|
63 | ||||
|
64 | cctx = zstd.ZstdCompressor(level=level) | |||
|
65 | with cctx.stream_reader(original, size=len(original), | |||
|
66 | read_size=source_read_size) as reader: | |||
|
67 | chunks = [] | |||
|
68 | while True: | |||
|
69 | chunk = reader.read(read_size) | |||
|
70 | if not chunk: | |||
|
71 | break | |||
|
72 | ||||
|
73 | chunks.append(chunk) | |||
|
74 | ||||
|
75 | self.assertEqual(b''.join(chunks), ref_frame) | |||
|
76 | ||||
|
77 | @hypothesis.settings( | |||
|
78 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
22 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), |
|
79 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |
23 | level=strategies.integers(min_value=1, max_value=5), |
|
80 | level=strategies.integers(min_value=1, max_value=5), | |
24 | source_read_size=strategies.integers(1, 16384), |
|
81 | source_read_size=strategies.integers(1, 16384), | |
@@ -33,15 +90,17 b' class TestCompressor_stream_reader_fuzzi' | |||||
33 | read_size=source_read_size) as reader: |
|
90 | read_size=source_read_size) as reader: | |
34 | chunks = [] |
|
91 | chunks = [] | |
35 | while True: |
|
92 | while True: | |
36 | read_size = read_sizes.draw(strategies.integers(1, 16384)) |
|
93 | read_size = read_sizes.draw(strategies.integers(-1, 16384)) | |
37 | chunk = reader.read(read_size) |
|
94 | chunk = reader.read(read_size) | |
|
95 | if not chunk and read_size: | |||
|
96 | break | |||
38 |
|
97 | |||
39 | if not chunk: |
|
|||
40 | break |
|
|||
41 | chunks.append(chunk) |
|
98 | chunks.append(chunk) | |
42 |
|
99 | |||
43 | self.assertEqual(b''.join(chunks), ref_frame) |
|
100 | self.assertEqual(b''.join(chunks), ref_frame) | |
44 |
|
101 | |||
|
102 | @hypothesis.settings( | |||
|
103 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
45 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), |
|
104 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |
46 | level=strategies.integers(min_value=1, max_value=5), |
|
105 | level=strategies.integers(min_value=1, max_value=5), | |
47 | source_read_size=strategies.integers(1, 16384), |
|
106 | source_read_size=strategies.integers(1, 16384), | |
@@ -57,14 +116,343 b' class TestCompressor_stream_reader_fuzzi' | |||||
57 | read_size=source_read_size) as reader: |
|
116 | read_size=source_read_size) as reader: | |
58 | chunks = [] |
|
117 | chunks = [] | |
59 | while True: |
|
118 | while True: | |
|
119 | read_size = read_sizes.draw(strategies.integers(-1, 16384)) | |||
|
120 | chunk = reader.read(read_size) | |||
|
121 | if not chunk and read_size: | |||
|
122 | break | |||
|
123 | ||||
|
124 | chunks.append(chunk) | |||
|
125 | ||||
|
126 | self.assertEqual(b''.join(chunks), ref_frame) | |||
|
127 | ||||
|
128 | @hypothesis.settings( | |||
|
129 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
130 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
131 | level=strategies.integers(min_value=1, max_value=5), | |||
|
132 | source_read_size=strategies.integers(1, 16384), | |||
|
133 | read_size=strategies.integers(1, zstd.COMPRESSION_RECOMMENDED_OUTPUT_SIZE)) | |||
|
134 | def test_stream_source_readinto(self, original, level, | |||
|
135 | source_read_size, read_size): | |||
|
136 | refctx = zstd.ZstdCompressor(level=level) | |||
|
137 | ref_frame = refctx.compress(original) | |||
|
138 | ||||
|
139 | cctx = zstd.ZstdCompressor(level=level) | |||
|
140 | with cctx.stream_reader(io.BytesIO(original), size=len(original), | |||
|
141 | read_size=source_read_size) as reader: | |||
|
142 | chunks = [] | |||
|
143 | while True: | |||
|
144 | b = bytearray(read_size) | |||
|
145 | count = reader.readinto(b) | |||
|
146 | ||||
|
147 | if not count: | |||
|
148 | break | |||
|
149 | ||||
|
150 | chunks.append(bytes(b[0:count])) | |||
|
151 | ||||
|
152 | self.assertEqual(b''.join(chunks), ref_frame) | |||
|
153 | ||||
|
154 | @hypothesis.settings( | |||
|
155 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
156 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
157 | level=strategies.integers(min_value=1, max_value=5), | |||
|
158 | source_read_size=strategies.integers(1, 16384), | |||
|
159 | read_size=strategies.integers(1, zstd.COMPRESSION_RECOMMENDED_OUTPUT_SIZE)) | |||
|
160 | def test_buffer_source_readinto(self, original, level, | |||
|
161 | source_read_size, read_size): | |||
|
162 | ||||
|
163 | refctx = zstd.ZstdCompressor(level=level) | |||
|
164 | ref_frame = refctx.compress(original) | |||
|
165 | ||||
|
166 | cctx = zstd.ZstdCompressor(level=level) | |||
|
167 | with cctx.stream_reader(original, size=len(original), | |||
|
168 | read_size=source_read_size) as reader: | |||
|
169 | chunks = [] | |||
|
170 | while True: | |||
|
171 | b = bytearray(read_size) | |||
|
172 | count = reader.readinto(b) | |||
|
173 | ||||
|
174 | if not count: | |||
|
175 | break | |||
|
176 | ||||
|
177 | chunks.append(bytes(b[0:count])) | |||
|
178 | ||||
|
179 | self.assertEqual(b''.join(chunks), ref_frame) | |||
|
180 | ||||
|
181 | @hypothesis.settings( | |||
|
182 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
183 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
184 | level=strategies.integers(min_value=1, max_value=5), | |||
|
185 | source_read_size=strategies.integers(1, 16384), | |||
|
186 | read_sizes=strategies.data()) | |||
|
187 | def test_stream_source_readinto_variance(self, original, level, | |||
|
188 | source_read_size, read_sizes): | |||
|
189 | refctx = zstd.ZstdCompressor(level=level) | |||
|
190 | ref_frame = refctx.compress(original) | |||
|
191 | ||||
|
192 | cctx = zstd.ZstdCompressor(level=level) | |||
|
193 | with cctx.stream_reader(io.BytesIO(original), size=len(original), | |||
|
194 | read_size=source_read_size) as reader: | |||
|
195 | chunks = [] | |||
|
196 | while True: | |||
60 | read_size = read_sizes.draw(strategies.integers(1, 16384)) |
|
197 | read_size = read_sizes.draw(strategies.integers(1, 16384)) | |
61 |
|
|
198 | b = bytearray(read_size) | |
|
199 | count = reader.readinto(b) | |||
|
200 | ||||
|
201 | if not count: | |||
|
202 | break | |||
|
203 | ||||
|
204 | chunks.append(bytes(b[0:count])) | |||
|
205 | ||||
|
206 | self.assertEqual(b''.join(chunks), ref_frame) | |||
|
207 | ||||
|
208 | @hypothesis.settings( | |||
|
209 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
210 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
211 | level=strategies.integers(min_value=1, max_value=5), | |||
|
212 | source_read_size=strategies.integers(1, 16384), | |||
|
213 | read_sizes=strategies.data()) | |||
|
214 | def test_buffer_source_readinto_variance(self, original, level, | |||
|
215 | source_read_size, read_sizes): | |||
|
216 | ||||
|
217 | refctx = zstd.ZstdCompressor(level=level) | |||
|
218 | ref_frame = refctx.compress(original) | |||
|
219 | ||||
|
220 | cctx = zstd.ZstdCompressor(level=level) | |||
|
221 | with cctx.stream_reader(original, size=len(original), | |||
|
222 | read_size=source_read_size) as reader: | |||
|
223 | chunks = [] | |||
|
224 | while True: | |||
|
225 | read_size = read_sizes.draw(strategies.integers(1, 16384)) | |||
|
226 | b = bytearray(read_size) | |||
|
227 | count = reader.readinto(b) | |||
|
228 | ||||
|
229 | if not count: | |||
|
230 | break | |||
|
231 | ||||
|
232 | chunks.append(bytes(b[0:count])) | |||
|
233 | ||||
|
234 | self.assertEqual(b''.join(chunks), ref_frame) | |||
|
235 | ||||
|
236 | @hypothesis.settings( | |||
|
237 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
238 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
239 | level=strategies.integers(min_value=1, max_value=5), | |||
|
240 | source_read_size=strategies.integers(1, 16384), | |||
|
241 | read_size=strategies.integers(-1, zstd.COMPRESSION_RECOMMENDED_OUTPUT_SIZE)) | |||
|
242 | def test_stream_source_read1(self, original, level, source_read_size, | |||
|
243 | read_size): | |||
|
244 | if read_size == 0: | |||
|
245 | read_size = -1 | |||
|
246 | ||||
|
247 | refctx = zstd.ZstdCompressor(level=level) | |||
|
248 | ref_frame = refctx.compress(original) | |||
|
249 | ||||
|
250 | cctx = zstd.ZstdCompressor(level=level) | |||
|
251 | with cctx.stream_reader(io.BytesIO(original), size=len(original), | |||
|
252 | read_size=source_read_size) as reader: | |||
|
253 | chunks = [] | |||
|
254 | while True: | |||
|
255 | chunk = reader.read1(read_size) | |||
62 | if not chunk: |
|
256 | if not chunk: | |
63 | break |
|
257 | break | |
|
258 | ||||
64 | chunks.append(chunk) |
|
259 | chunks.append(chunk) | |
65 |
|
260 | |||
66 | self.assertEqual(b''.join(chunks), ref_frame) |
|
261 | self.assertEqual(b''.join(chunks), ref_frame) | |
67 |
|
262 | |||
|
263 | @hypothesis.settings( | |||
|
264 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
265 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
266 | level=strategies.integers(min_value=1, max_value=5), | |||
|
267 | source_read_size=strategies.integers(1, 16384), | |||
|
268 | read_size=strategies.integers(-1, zstd.COMPRESSION_RECOMMENDED_OUTPUT_SIZE)) | |||
|
269 | def test_buffer_source_read1(self, original, level, source_read_size, | |||
|
270 | read_size): | |||
|
271 | if read_size == 0: | |||
|
272 | read_size = -1 | |||
|
273 | ||||
|
274 | refctx = zstd.ZstdCompressor(level=level) | |||
|
275 | ref_frame = refctx.compress(original) | |||
|
276 | ||||
|
277 | cctx = zstd.ZstdCompressor(level=level) | |||
|
278 | with cctx.stream_reader(original, size=len(original), | |||
|
279 | read_size=source_read_size) as reader: | |||
|
280 | chunks = [] | |||
|
281 | while True: | |||
|
282 | chunk = reader.read1(read_size) | |||
|
283 | if not chunk: | |||
|
284 | break | |||
|
285 | ||||
|
286 | chunks.append(chunk) | |||
|
287 | ||||
|
288 | self.assertEqual(b''.join(chunks), ref_frame) | |||
|
289 | ||||
|
290 | @hypothesis.settings( | |||
|
291 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
292 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
293 | level=strategies.integers(min_value=1, max_value=5), | |||
|
294 | source_read_size=strategies.integers(1, 16384), | |||
|
295 | read_sizes=strategies.data()) | |||
|
296 | def test_stream_source_read1_variance(self, original, level, source_read_size, | |||
|
297 | read_sizes): | |||
|
298 | refctx = zstd.ZstdCompressor(level=level) | |||
|
299 | ref_frame = refctx.compress(original) | |||
|
300 | ||||
|
301 | cctx = zstd.ZstdCompressor(level=level) | |||
|
302 | with cctx.stream_reader(io.BytesIO(original), size=len(original), | |||
|
303 | read_size=source_read_size) as reader: | |||
|
304 | chunks = [] | |||
|
305 | while True: | |||
|
306 | read_size = read_sizes.draw(strategies.integers(-1, 16384)) | |||
|
307 | chunk = reader.read1(read_size) | |||
|
308 | if not chunk and read_size: | |||
|
309 | break | |||
|
310 | ||||
|
311 | chunks.append(chunk) | |||
|
312 | ||||
|
313 | self.assertEqual(b''.join(chunks), ref_frame) | |||
|
314 | ||||
|
315 | @hypothesis.settings( | |||
|
316 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
317 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
318 | level=strategies.integers(min_value=1, max_value=5), | |||
|
319 | source_read_size=strategies.integers(1, 16384), | |||
|
320 | read_sizes=strategies.data()) | |||
|
321 | def test_buffer_source_read1_variance(self, original, level, source_read_size, | |||
|
322 | read_sizes): | |||
|
323 | ||||
|
324 | refctx = zstd.ZstdCompressor(level=level) | |||
|
325 | ref_frame = refctx.compress(original) | |||
|
326 | ||||
|
327 | cctx = zstd.ZstdCompressor(level=level) | |||
|
328 | with cctx.stream_reader(original, size=len(original), | |||
|
329 | read_size=source_read_size) as reader: | |||
|
330 | chunks = [] | |||
|
331 | while True: | |||
|
332 | read_size = read_sizes.draw(strategies.integers(-1, 16384)) | |||
|
333 | chunk = reader.read1(read_size) | |||
|
334 | if not chunk and read_size: | |||
|
335 | break | |||
|
336 | ||||
|
337 | chunks.append(chunk) | |||
|
338 | ||||
|
339 | self.assertEqual(b''.join(chunks), ref_frame) | |||
|
340 | ||||
|
341 | ||||
|
342 | @hypothesis.settings( | |||
|
343 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
344 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
345 | level=strategies.integers(min_value=1, max_value=5), | |||
|
346 | source_read_size=strategies.integers(1, 16384), | |||
|
347 | read_size=strategies.integers(1, zstd.COMPRESSION_RECOMMENDED_OUTPUT_SIZE)) | |||
|
348 | def test_stream_source_readinto1(self, original, level, source_read_size, | |||
|
349 | read_size): | |||
|
350 | if read_size == 0: | |||
|
351 | read_size = -1 | |||
|
352 | ||||
|
353 | refctx = zstd.ZstdCompressor(level=level) | |||
|
354 | ref_frame = refctx.compress(original) | |||
|
355 | ||||
|
356 | cctx = zstd.ZstdCompressor(level=level) | |||
|
357 | with cctx.stream_reader(io.BytesIO(original), size=len(original), | |||
|
358 | read_size=source_read_size) as reader: | |||
|
359 | chunks = [] | |||
|
360 | while True: | |||
|
361 | b = bytearray(read_size) | |||
|
362 | count = reader.readinto1(b) | |||
|
363 | ||||
|
364 | if not count: | |||
|
365 | break | |||
|
366 | ||||
|
367 | chunks.append(bytes(b[0:count])) | |||
|
368 | ||||
|
369 | self.assertEqual(b''.join(chunks), ref_frame) | |||
|
370 | ||||
|
371 | @hypothesis.settings( | |||
|
372 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
373 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
374 | level=strategies.integers(min_value=1, max_value=5), | |||
|
375 | source_read_size=strategies.integers(1, 16384), | |||
|
376 | read_size=strategies.integers(1, zstd.COMPRESSION_RECOMMENDED_OUTPUT_SIZE)) | |||
|
377 | def test_buffer_source_readinto1(self, original, level, source_read_size, | |||
|
378 | read_size): | |||
|
379 | if read_size == 0: | |||
|
380 | read_size = -1 | |||
|
381 | ||||
|
382 | refctx = zstd.ZstdCompressor(level=level) | |||
|
383 | ref_frame = refctx.compress(original) | |||
|
384 | ||||
|
385 | cctx = zstd.ZstdCompressor(level=level) | |||
|
386 | with cctx.stream_reader(original, size=len(original), | |||
|
387 | read_size=source_read_size) as reader: | |||
|
388 | chunks = [] | |||
|
389 | while True: | |||
|
390 | b = bytearray(read_size) | |||
|
391 | count = reader.readinto1(b) | |||
|
392 | ||||
|
393 | if not count: | |||
|
394 | break | |||
|
395 | ||||
|
396 | chunks.append(bytes(b[0:count])) | |||
|
397 | ||||
|
398 | self.assertEqual(b''.join(chunks), ref_frame) | |||
|
399 | ||||
|
400 | @hypothesis.settings( | |||
|
401 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
402 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
403 | level=strategies.integers(min_value=1, max_value=5), | |||
|
404 | source_read_size=strategies.integers(1, 16384), | |||
|
405 | read_sizes=strategies.data()) | |||
|
406 | def test_stream_source_readinto1_variance(self, original, level, source_read_size, | |||
|
407 | read_sizes): | |||
|
408 | refctx = zstd.ZstdCompressor(level=level) | |||
|
409 | ref_frame = refctx.compress(original) | |||
|
410 | ||||
|
411 | cctx = zstd.ZstdCompressor(level=level) | |||
|
412 | with cctx.stream_reader(io.BytesIO(original), size=len(original), | |||
|
413 | read_size=source_read_size) as reader: | |||
|
414 | chunks = [] | |||
|
415 | while True: | |||
|
416 | read_size = read_sizes.draw(strategies.integers(1, 16384)) | |||
|
417 | b = bytearray(read_size) | |||
|
418 | count = reader.readinto1(b) | |||
|
419 | ||||
|
420 | if not count: | |||
|
421 | break | |||
|
422 | ||||
|
423 | chunks.append(bytes(b[0:count])) | |||
|
424 | ||||
|
425 | self.assertEqual(b''.join(chunks), ref_frame) | |||
|
426 | ||||
|
427 | @hypothesis.settings( | |||
|
428 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
429 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
430 | level=strategies.integers(min_value=1, max_value=5), | |||
|
431 | source_read_size=strategies.integers(1, 16384), | |||
|
432 | read_sizes=strategies.data()) | |||
|
433 | def test_buffer_source_readinto1_variance(self, original, level, source_read_size, | |||
|
434 | read_sizes): | |||
|
435 | ||||
|
436 | refctx = zstd.ZstdCompressor(level=level) | |||
|
437 | ref_frame = refctx.compress(original) | |||
|
438 | ||||
|
439 | cctx = zstd.ZstdCompressor(level=level) | |||
|
440 | with cctx.stream_reader(original, size=len(original), | |||
|
441 | read_size=source_read_size) as reader: | |||
|
442 | chunks = [] | |||
|
443 | while True: | |||
|
444 | read_size = read_sizes.draw(strategies.integers(1, 16384)) | |||
|
445 | b = bytearray(read_size) | |||
|
446 | count = reader.readinto1(b) | |||
|
447 | ||||
|
448 | if not count: | |||
|
449 | break | |||
|
450 | ||||
|
451 | chunks.append(bytes(b[0:count])) | |||
|
452 | ||||
|
453 | self.assertEqual(b''.join(chunks), ref_frame) | |||
|
454 | ||||
|
455 | ||||
68 |
|
456 | |||
69 | @unittest.skipUnless('ZSTD_SLOW_TESTS' in os.environ, 'ZSTD_SLOW_TESTS not set') |
|
457 | @unittest.skipUnless('ZSTD_SLOW_TESTS' in os.environ, 'ZSTD_SLOW_TESTS not set') | |
70 | @make_cffi |
|
458 | @make_cffi | |
@@ -77,7 +465,7 b' class TestCompressor_stream_writer_fuzzi' | |||||
77 | ref_frame = refctx.compress(original) |
|
465 | ref_frame = refctx.compress(original) | |
78 |
|
466 | |||
79 | cctx = zstd.ZstdCompressor(level=level) |
|
467 | cctx = zstd.ZstdCompressor(level=level) | |
80 |
b = |
|
468 | b = NonClosingBytesIO() | |
81 | with cctx.stream_writer(b, size=len(original), write_size=write_size) as compressor: |
|
469 | with cctx.stream_writer(b, size=len(original), write_size=write_size) as compressor: | |
82 | compressor.write(original) |
|
470 | compressor.write(original) | |
83 |
|
471 | |||
@@ -219,6 +607,9 b' class TestCompressor_multi_compress_to_b' | |||||
219 | write_checksum=True, |
|
607 | write_checksum=True, | |
220 | **kwargs) |
|
608 | **kwargs) | |
221 |
|
609 | |||
|
610 | if not hasattr(cctx, 'multi_compress_to_buffer'): | |||
|
611 | self.skipTest('multi_compress_to_buffer not available') | |||
|
612 | ||||
222 | result = cctx.multi_compress_to_buffer(original, threads=-1) |
|
613 | result = cctx.multi_compress_to_buffer(original, threads=-1) | |
223 |
|
614 | |||
224 | self.assertEqual(len(result), len(original)) |
|
615 | self.assertEqual(len(result), len(original)) |
@@ -15,17 +15,17 b' class TestCompressionParameters(unittest' | |||||
15 | chain_log=zstd.CHAINLOG_MIN, |
|
15 | chain_log=zstd.CHAINLOG_MIN, | |
16 | hash_log=zstd.HASHLOG_MIN, |
|
16 | hash_log=zstd.HASHLOG_MIN, | |
17 | search_log=zstd.SEARCHLOG_MIN, |
|
17 | search_log=zstd.SEARCHLOG_MIN, | |
18 |
min_match=zstd. |
|
18 | min_match=zstd.MINMATCH_MIN + 1, | |
19 | target_length=zstd.TARGETLENGTH_MIN, |
|
19 | target_length=zstd.TARGETLENGTH_MIN, | |
20 |
|
|
20 | strategy=zstd.STRATEGY_FAST) | |
21 |
|
21 | |||
22 | zstd.ZstdCompressionParameters(window_log=zstd.WINDOWLOG_MAX, |
|
22 | zstd.ZstdCompressionParameters(window_log=zstd.WINDOWLOG_MAX, | |
23 | chain_log=zstd.CHAINLOG_MAX, |
|
23 | chain_log=zstd.CHAINLOG_MAX, | |
24 | hash_log=zstd.HASHLOG_MAX, |
|
24 | hash_log=zstd.HASHLOG_MAX, | |
25 | search_log=zstd.SEARCHLOG_MAX, |
|
25 | search_log=zstd.SEARCHLOG_MAX, | |
26 |
min_match=zstd. |
|
26 | min_match=zstd.MINMATCH_MAX - 1, | |
27 | target_length=zstd.TARGETLENGTH_MAX, |
|
27 | target_length=zstd.TARGETLENGTH_MAX, | |
28 |
|
|
28 | strategy=zstd.STRATEGY_BTULTRA2) | |
29 |
|
29 | |||
30 | def test_from_level(self): |
|
30 | def test_from_level(self): | |
31 | p = zstd.ZstdCompressionParameters.from_level(1) |
|
31 | p = zstd.ZstdCompressionParameters.from_level(1) | |
@@ -43,7 +43,7 b' class TestCompressionParameters(unittest' | |||||
43 | search_log=4, |
|
43 | search_log=4, | |
44 | min_match=5, |
|
44 | min_match=5, | |
45 | target_length=8, |
|
45 | target_length=8, | |
46 |
|
|
46 | strategy=1) | |
47 | self.assertEqual(p.window_log, 10) |
|
47 | self.assertEqual(p.window_log, 10) | |
48 | self.assertEqual(p.chain_log, 6) |
|
48 | self.assertEqual(p.chain_log, 6) | |
49 | self.assertEqual(p.hash_log, 7) |
|
49 | self.assertEqual(p.hash_log, 7) | |
@@ -59,9 +59,10 b' class TestCompressionParameters(unittest' | |||||
59 | self.assertEqual(p.threads, 4) |
|
59 | self.assertEqual(p.threads, 4) | |
60 |
|
60 | |||
61 | p = zstd.ZstdCompressionParameters(threads=2, job_size=1048576, |
|
61 | p = zstd.ZstdCompressionParameters(threads=2, job_size=1048576, | |
62 |
overlap |
|
62 | overlap_log=6) | |
63 | self.assertEqual(p.threads, 2) |
|
63 | self.assertEqual(p.threads, 2) | |
64 | self.assertEqual(p.job_size, 1048576) |
|
64 | self.assertEqual(p.job_size, 1048576) | |
|
65 | self.assertEqual(p.overlap_log, 6) | |||
65 | self.assertEqual(p.overlap_size_log, 6) |
|
66 | self.assertEqual(p.overlap_size_log, 6) | |
66 |
|
67 | |||
67 | p = zstd.ZstdCompressionParameters(compression_level=-1) |
|
68 | p = zstd.ZstdCompressionParameters(compression_level=-1) | |
@@ -85,8 +86,9 b' class TestCompressionParameters(unittest' | |||||
85 | p = zstd.ZstdCompressionParameters(ldm_bucket_size_log=7) |
|
86 | p = zstd.ZstdCompressionParameters(ldm_bucket_size_log=7) | |
86 | self.assertEqual(p.ldm_bucket_size_log, 7) |
|
87 | self.assertEqual(p.ldm_bucket_size_log, 7) | |
87 |
|
88 | |||
88 |
p = zstd.ZstdCompressionParameters(ldm_hash_e |
|
89 | p = zstd.ZstdCompressionParameters(ldm_hash_rate_log=8) | |
89 | self.assertEqual(p.ldm_hash_every_log, 8) |
|
90 | self.assertEqual(p.ldm_hash_every_log, 8) | |
|
91 | self.assertEqual(p.ldm_hash_rate_log, 8) | |||
90 |
|
92 | |||
91 | def test_estimated_compression_context_size(self): |
|
93 | def test_estimated_compression_context_size(self): | |
92 | p = zstd.ZstdCompressionParameters(window_log=20, |
|
94 | p = zstd.ZstdCompressionParameters(window_log=20, | |
@@ -95,12 +97,44 b' class TestCompressionParameters(unittest' | |||||
95 | search_log=1, |
|
97 | search_log=1, | |
96 | min_match=5, |
|
98 | min_match=5, | |
97 | target_length=16, |
|
99 | target_length=16, | |
98 |
|
|
100 | strategy=zstd.STRATEGY_DFAST) | |
99 |
|
101 | |||
100 | # 32-bit has slightly different values from 64-bit. |
|
102 | # 32-bit has slightly different values from 64-bit. | |
101 | self.assertAlmostEqual(p.estimated_compression_context_size(), 1294072, |
|
103 | self.assertAlmostEqual(p.estimated_compression_context_size(), 1294072, | |
102 | delta=250) |
|
104 | delta=250) | |
103 |
|
105 | |||
|
106 | def test_strategy(self): | |||
|
107 | with self.assertRaisesRegexp(ValueError, 'cannot specify both compression_strategy'): | |||
|
108 | zstd.ZstdCompressionParameters(strategy=0, compression_strategy=0) | |||
|
109 | ||||
|
110 | p = zstd.ZstdCompressionParameters(strategy=2) | |||
|
111 | self.assertEqual(p.compression_strategy, 2) | |||
|
112 | ||||
|
113 | p = zstd.ZstdCompressionParameters(strategy=3) | |||
|
114 | self.assertEqual(p.compression_strategy, 3) | |||
|
115 | ||||
|
116 | def test_ldm_hash_rate_log(self): | |||
|
117 | with self.assertRaisesRegexp(ValueError, 'cannot specify both ldm_hash_rate_log'): | |||
|
118 | zstd.ZstdCompressionParameters(ldm_hash_rate_log=8, ldm_hash_every_log=4) | |||
|
119 | ||||
|
120 | p = zstd.ZstdCompressionParameters(ldm_hash_rate_log=8) | |||
|
121 | self.assertEqual(p.ldm_hash_every_log, 8) | |||
|
122 | ||||
|
123 | p = zstd.ZstdCompressionParameters(ldm_hash_every_log=16) | |||
|
124 | self.assertEqual(p.ldm_hash_every_log, 16) | |||
|
125 | ||||
|
126 | def test_overlap_log(self): | |||
|
127 | with self.assertRaisesRegexp(ValueError, 'cannot specify both overlap_log'): | |||
|
128 | zstd.ZstdCompressionParameters(overlap_log=1, overlap_size_log=9) | |||
|
129 | ||||
|
130 | p = zstd.ZstdCompressionParameters(overlap_log=2) | |||
|
131 | self.assertEqual(p.overlap_log, 2) | |||
|
132 | self.assertEqual(p.overlap_size_log, 2) | |||
|
133 | ||||
|
134 | p = zstd.ZstdCompressionParameters(overlap_size_log=4) | |||
|
135 | self.assertEqual(p.overlap_log, 4) | |||
|
136 | self.assertEqual(p.overlap_size_log, 4) | |||
|
137 | ||||
104 |
|
138 | |||
105 | @make_cffi |
|
139 | @make_cffi | |
106 | class TestFrameParameters(unittest.TestCase): |
|
140 | class TestFrameParameters(unittest.TestCase): |
@@ -24,8 +24,8 b' s_hashlog = strategies.integers(min_valu' | |||||
24 | max_value=zstd.HASHLOG_MAX) |
|
24 | max_value=zstd.HASHLOG_MAX) | |
25 | s_searchlog = strategies.integers(min_value=zstd.SEARCHLOG_MIN, |
|
25 | s_searchlog = strategies.integers(min_value=zstd.SEARCHLOG_MIN, | |
26 | max_value=zstd.SEARCHLOG_MAX) |
|
26 | max_value=zstd.SEARCHLOG_MAX) | |
27 |
s_ |
|
27 | s_minmatch = strategies.integers(min_value=zstd.MINMATCH_MIN, | |
28 |
|
|
28 | max_value=zstd.MINMATCH_MAX) | |
29 | s_targetlength = strategies.integers(min_value=zstd.TARGETLENGTH_MIN, |
|
29 | s_targetlength = strategies.integers(min_value=zstd.TARGETLENGTH_MIN, | |
30 | max_value=zstd.TARGETLENGTH_MAX) |
|
30 | max_value=zstd.TARGETLENGTH_MAX) | |
31 | s_strategy = strategies.sampled_from((zstd.STRATEGY_FAST, |
|
31 | s_strategy = strategies.sampled_from((zstd.STRATEGY_FAST, | |
@@ -35,41 +35,42 b' s_strategy = strategies.sampled_from((zs' | |||||
35 | zstd.STRATEGY_LAZY2, |
|
35 | zstd.STRATEGY_LAZY2, | |
36 | zstd.STRATEGY_BTLAZY2, |
|
36 | zstd.STRATEGY_BTLAZY2, | |
37 | zstd.STRATEGY_BTOPT, |
|
37 | zstd.STRATEGY_BTOPT, | |
38 |
zstd.STRATEGY_BTULTRA |
|
38 | zstd.STRATEGY_BTULTRA, | |
|
39 | zstd.STRATEGY_BTULTRA2)) | |||
39 |
|
40 | |||
40 |
|
41 | |||
41 | @make_cffi |
|
42 | @make_cffi | |
42 | @unittest.skipUnless('ZSTD_SLOW_TESTS' in os.environ, 'ZSTD_SLOW_TESTS not set') |
|
43 | @unittest.skipUnless('ZSTD_SLOW_TESTS' in os.environ, 'ZSTD_SLOW_TESTS not set') | |
43 | class TestCompressionParametersHypothesis(unittest.TestCase): |
|
44 | class TestCompressionParametersHypothesis(unittest.TestCase): | |
44 | @hypothesis.given(s_windowlog, s_chainlog, s_hashlog, s_searchlog, |
|
45 | @hypothesis.given(s_windowlog, s_chainlog, s_hashlog, s_searchlog, | |
45 |
s_ |
|
46 | s_minmatch, s_targetlength, s_strategy) | |
46 | def test_valid_init(self, windowlog, chainlog, hashlog, searchlog, |
|
47 | def test_valid_init(self, windowlog, chainlog, hashlog, searchlog, | |
47 |
|
|
48 | minmatch, targetlength, strategy): | |
48 | zstd.ZstdCompressionParameters(window_log=windowlog, |
|
49 | zstd.ZstdCompressionParameters(window_log=windowlog, | |
49 | chain_log=chainlog, |
|
50 | chain_log=chainlog, | |
50 | hash_log=hashlog, |
|
51 | hash_log=hashlog, | |
51 | search_log=searchlog, |
|
52 | search_log=searchlog, | |
52 |
min_match= |
|
53 | min_match=minmatch, | |
53 | target_length=targetlength, |
|
54 | target_length=targetlength, | |
54 |
|
|
55 | strategy=strategy) | |
55 |
|
56 | |||
56 | @hypothesis.given(s_windowlog, s_chainlog, s_hashlog, s_searchlog, |
|
57 | @hypothesis.given(s_windowlog, s_chainlog, s_hashlog, s_searchlog, | |
57 |
|
|
58 | s_minmatch, s_targetlength, s_strategy) | |
58 | def test_estimated_compression_context_size(self, windowlog, chainlog, |
|
59 | def test_estimated_compression_context_size(self, windowlog, chainlog, | |
59 | hashlog, searchlog, |
|
60 | hashlog, searchlog, | |
60 |
|
|
61 | minmatch, targetlength, | |
61 | strategy): |
|
62 | strategy): | |
62 |
if |
|
63 | if minmatch == zstd.MINMATCH_MIN and strategy in (zstd.STRATEGY_FAST, zstd.STRATEGY_GREEDY): | |
63 |
|
|
64 | minmatch += 1 | |
64 |
elif |
|
65 | elif minmatch == zstd.MINMATCH_MAX and strategy != zstd.STRATEGY_FAST: | |
65 |
|
|
66 | minmatch -= 1 | |
66 |
|
67 | |||
67 | p = zstd.ZstdCompressionParameters(window_log=windowlog, |
|
68 | p = zstd.ZstdCompressionParameters(window_log=windowlog, | |
68 | chain_log=chainlog, |
|
69 | chain_log=chainlog, | |
69 | hash_log=hashlog, |
|
70 | hash_log=hashlog, | |
70 | search_log=searchlog, |
|
71 | search_log=searchlog, | |
71 |
min_match= |
|
72 | min_match=minmatch, | |
72 | target_length=targetlength, |
|
73 | target_length=targetlength, | |
73 |
|
|
74 | strategy=strategy) | |
74 | size = p.estimated_compression_context_size() |
|
75 | size = p.estimated_compression_context_size() | |
75 |
|
76 |
@@ -3,6 +3,7 b' import os' | |||||
3 | import random |
|
3 | import random | |
4 | import struct |
|
4 | import struct | |
5 | import sys |
|
5 | import sys | |
|
6 | import tempfile | |||
6 | import unittest |
|
7 | import unittest | |
7 |
|
8 | |||
8 | import zstandard as zstd |
|
9 | import zstandard as zstd | |
@@ -10,6 +11,7 b' import zstandard as zstd' | |||||
10 | from .common import ( |
|
11 | from .common import ( | |
11 | generate_samples, |
|
12 | generate_samples, | |
12 | make_cffi, |
|
13 | make_cffi, | |
|
14 | NonClosingBytesIO, | |||
13 | OpCountingBytesIO, |
|
15 | OpCountingBytesIO, | |
14 | ) |
|
16 | ) | |
15 |
|
17 | |||
@@ -219,7 +221,7 b' class TestDecompressor_decompress(unitte' | |||||
219 | cctx = zstd.ZstdCompressor(write_content_size=False) |
|
221 | cctx = zstd.ZstdCompressor(write_content_size=False) | |
220 | frame = cctx.compress(source) |
|
222 | frame = cctx.compress(source) | |
221 |
|
223 | |||
222 |
dctx = zstd.ZstdDecompressor(max_window_size= |
|
224 | dctx = zstd.ZstdDecompressor(max_window_size=2**zstd.WINDOWLOG_MIN) | |
223 |
|
225 | |||
224 | with self.assertRaisesRegexp( |
|
226 | with self.assertRaisesRegexp( | |
225 | zstd.ZstdError, 'decompression error: Frame requires too much memory'): |
|
227 | zstd.ZstdError, 'decompression error: Frame requires too much memory'): | |
@@ -302,19 +304,16 b' class TestDecompressor_stream_reader(uni' | |||||
302 | dctx = zstd.ZstdDecompressor() |
|
304 | dctx = zstd.ZstdDecompressor() | |
303 |
|
305 | |||
304 | with dctx.stream_reader(b'foo') as reader: |
|
306 | with dctx.stream_reader(b'foo') as reader: | |
305 |
with self.assertRaises( |
|
307 | with self.assertRaises(io.UnsupportedOperation): | |
306 | reader.readline() |
|
308 | reader.readline() | |
307 |
|
309 | |||
308 |
with self.assertRaises( |
|
310 | with self.assertRaises(io.UnsupportedOperation): | |
309 | reader.readlines() |
|
311 | reader.readlines() | |
310 |
|
312 | |||
311 |
with self.assertRaises( |
|
313 | with self.assertRaises(io.UnsupportedOperation): | |
312 | reader.readall() |
|
|||
313 |
|
||||
314 | with self.assertRaises(NotImplementedError): |
|
|||
315 | iter(reader) |
|
314 | iter(reader) | |
316 |
|
315 | |||
317 |
with self.assertRaises( |
|
316 | with self.assertRaises(io.UnsupportedOperation): | |
318 | next(reader) |
|
317 | next(reader) | |
319 |
|
318 | |||
320 | with self.assertRaises(io.UnsupportedOperation): |
|
319 | with self.assertRaises(io.UnsupportedOperation): | |
@@ -347,15 +346,18 b' class TestDecompressor_stream_reader(uni' | |||||
347 | with self.assertRaisesRegexp(ValueError, 'stream is closed'): |
|
346 | with self.assertRaisesRegexp(ValueError, 'stream is closed'): | |
348 | reader.read(1) |
|
347 | reader.read(1) | |
349 |
|
348 | |||
350 |
def test_ |
|
349 | def test_read_sizes(self): | |
|
350 | cctx = zstd.ZstdCompressor() | |||
|
351 | foo = cctx.compress(b'foo') | |||
|
352 | ||||
351 | dctx = zstd.ZstdDecompressor() |
|
353 | dctx = zstd.ZstdDecompressor() | |
352 |
|
354 | |||
353 |
with dctx.stream_reader( |
|
355 | with dctx.stream_reader(foo) as reader: | |
354 |
with self.assertRaisesRegexp(ValueError, 'cannot read negative |
|
356 | with self.assertRaisesRegexp(ValueError, 'cannot read negative amounts less than -1'): | |
355 |
reader.read(- |
|
357 | reader.read(-2) | |
356 |
|
358 | |||
357 | with self.assertRaisesRegexp(ValueError, 'cannot read negative or size 0 amounts'): |
|
359 | self.assertEqual(reader.read(0), b'') | |
358 |
|
|
360 | self.assertEqual(reader.read(), b'foo') | |
359 |
|
361 | |||
360 | def test_read_buffer(self): |
|
362 | def test_read_buffer(self): | |
361 | cctx = zstd.ZstdCompressor() |
|
363 | cctx = zstd.ZstdCompressor() | |
@@ -524,13 +526,243 b' class TestDecompressor_stream_reader(uni' | |||||
524 | reader = dctx.stream_reader(source) |
|
526 | reader = dctx.stream_reader(source) | |
525 |
|
527 | |||
526 | with reader: |
|
528 | with reader: | |
527 | with self.assertRaises(TypeError): |
|
529 | reader.read(0) | |
528 | reader.read() |
|
|||
529 |
|
530 | |||
530 | with reader: |
|
531 | with reader: | |
531 | with self.assertRaisesRegexp(ValueError, 'stream is closed'): |
|
532 | with self.assertRaisesRegexp(ValueError, 'stream is closed'): | |
532 | reader.read(100) |
|
533 | reader.read(100) | |
533 |
|
534 | |||
|
535 | def test_partial_read(self): | |||
|
536 | # Inspired by https://github.com/indygreg/python-zstandard/issues/71. | |||
|
537 | buffer = io.BytesIO() | |||
|
538 | cctx = zstd.ZstdCompressor() | |||
|
539 | writer = cctx.stream_writer(buffer) | |||
|
540 | writer.write(bytearray(os.urandom(1000000))) | |||
|
541 | writer.flush(zstd.FLUSH_FRAME) | |||
|
542 | buffer.seek(0) | |||
|
543 | ||||
|
544 | dctx = zstd.ZstdDecompressor() | |||
|
545 | reader = dctx.stream_reader(buffer) | |||
|
546 | ||||
|
547 | while True: | |||
|
548 | chunk = reader.read(8192) | |||
|
549 | if not chunk: | |||
|
550 | break | |||
|
551 | ||||
|
552 | def test_read_multiple_frames(self): | |||
|
553 | cctx = zstd.ZstdCompressor() | |||
|
554 | source = io.BytesIO() | |||
|
555 | writer = cctx.stream_writer(source) | |||
|
556 | writer.write(b'foo') | |||
|
557 | writer.flush(zstd.FLUSH_FRAME) | |||
|
558 | writer.write(b'bar') | |||
|
559 | writer.flush(zstd.FLUSH_FRAME) | |||
|
560 | ||||
|
561 | dctx = zstd.ZstdDecompressor() | |||
|
562 | ||||
|
563 | reader = dctx.stream_reader(source.getvalue()) | |||
|
564 | self.assertEqual(reader.read(2), b'fo') | |||
|
565 | self.assertEqual(reader.read(2), b'o') | |||
|
566 | self.assertEqual(reader.read(2), b'ba') | |||
|
567 | self.assertEqual(reader.read(2), b'r') | |||
|
568 | ||||
|
569 | source.seek(0) | |||
|
570 | reader = dctx.stream_reader(source) | |||
|
571 | self.assertEqual(reader.read(2), b'fo') | |||
|
572 | self.assertEqual(reader.read(2), b'o') | |||
|
573 | self.assertEqual(reader.read(2), b'ba') | |||
|
574 | self.assertEqual(reader.read(2), b'r') | |||
|
575 | ||||
|
576 | reader = dctx.stream_reader(source.getvalue()) | |||
|
577 | self.assertEqual(reader.read(3), b'foo') | |||
|
578 | self.assertEqual(reader.read(3), b'bar') | |||
|
579 | ||||
|
580 | source.seek(0) | |||
|
581 | reader = dctx.stream_reader(source) | |||
|
582 | self.assertEqual(reader.read(3), b'foo') | |||
|
583 | self.assertEqual(reader.read(3), b'bar') | |||
|
584 | ||||
|
585 | reader = dctx.stream_reader(source.getvalue()) | |||
|
586 | self.assertEqual(reader.read(4), b'foo') | |||
|
587 | self.assertEqual(reader.read(4), b'bar') | |||
|
588 | ||||
|
589 | source.seek(0) | |||
|
590 | reader = dctx.stream_reader(source) | |||
|
591 | self.assertEqual(reader.read(4), b'foo') | |||
|
592 | self.assertEqual(reader.read(4), b'bar') | |||
|
593 | ||||
|
594 | reader = dctx.stream_reader(source.getvalue()) | |||
|
595 | self.assertEqual(reader.read(128), b'foo') | |||
|
596 | self.assertEqual(reader.read(128), b'bar') | |||
|
597 | ||||
|
598 | source.seek(0) | |||
|
599 | reader = dctx.stream_reader(source) | |||
|
600 | self.assertEqual(reader.read(128), b'foo') | |||
|
601 | self.assertEqual(reader.read(128), b'bar') | |||
|
602 | ||||
|
603 | # Now tests for reads spanning frames. | |||
|
604 | reader = dctx.stream_reader(source.getvalue(), read_across_frames=True) | |||
|
605 | self.assertEqual(reader.read(3), b'foo') | |||
|
606 | self.assertEqual(reader.read(3), b'bar') | |||
|
607 | ||||
|
608 | source.seek(0) | |||
|
609 | reader = dctx.stream_reader(source, read_across_frames=True) | |||
|
610 | self.assertEqual(reader.read(3), b'foo') | |||
|
611 | self.assertEqual(reader.read(3), b'bar') | |||
|
612 | ||||
|
613 | reader = dctx.stream_reader(source.getvalue(), read_across_frames=True) | |||
|
614 | self.assertEqual(reader.read(6), b'foobar') | |||
|
615 | ||||
|
616 | source.seek(0) | |||
|
617 | reader = dctx.stream_reader(source, read_across_frames=True) | |||
|
618 | self.assertEqual(reader.read(6), b'foobar') | |||
|
619 | ||||
|
620 | reader = dctx.stream_reader(source.getvalue(), read_across_frames=True) | |||
|
621 | self.assertEqual(reader.read(7), b'foobar') | |||
|
622 | ||||
|
623 | source.seek(0) | |||
|
624 | reader = dctx.stream_reader(source, read_across_frames=True) | |||
|
625 | self.assertEqual(reader.read(7), b'foobar') | |||
|
626 | ||||
|
627 | reader = dctx.stream_reader(source.getvalue(), read_across_frames=True) | |||
|
628 | self.assertEqual(reader.read(128), b'foobar') | |||
|
629 | ||||
|
630 | source.seek(0) | |||
|
631 | reader = dctx.stream_reader(source, read_across_frames=True) | |||
|
632 | self.assertEqual(reader.read(128), b'foobar') | |||
|
633 | ||||
|
634 | def test_readinto(self): | |||
|
635 | cctx = zstd.ZstdCompressor() | |||
|
636 | foo = cctx.compress(b'foo') | |||
|
637 | ||||
|
638 | dctx = zstd.ZstdDecompressor() | |||
|
639 | ||||
|
640 | # Attempting to readinto() a non-writable buffer fails. | |||
|
641 | # The exact exception varies based on the backend. | |||
|
642 | reader = dctx.stream_reader(foo) | |||
|
643 | with self.assertRaises(Exception): | |||
|
644 | reader.readinto(b'foobar') | |||
|
645 | ||||
|
646 | # readinto() with sufficiently large destination. | |||
|
647 | b = bytearray(1024) | |||
|
648 | reader = dctx.stream_reader(foo) | |||
|
649 | self.assertEqual(reader.readinto(b), 3) | |||
|
650 | self.assertEqual(b[0:3], b'foo') | |||
|
651 | self.assertEqual(reader.readinto(b), 0) | |||
|
652 | self.assertEqual(b[0:3], b'foo') | |||
|
653 | ||||
|
654 | # readinto() with small reads. | |||
|
655 | b = bytearray(1024) | |||
|
656 | reader = dctx.stream_reader(foo, read_size=1) | |||
|
657 | self.assertEqual(reader.readinto(b), 3) | |||
|
658 | self.assertEqual(b[0:3], b'foo') | |||
|
659 | ||||
|
660 | # Too small destination buffer. | |||
|
661 | b = bytearray(2) | |||
|
662 | reader = dctx.stream_reader(foo) | |||
|
663 | self.assertEqual(reader.readinto(b), 2) | |||
|
664 | self.assertEqual(b[:], b'fo') | |||
|
665 | ||||
|
666 | def test_readinto1(self): | |||
|
667 | cctx = zstd.ZstdCompressor() | |||
|
668 | foo = cctx.compress(b'foo') | |||
|
669 | ||||
|
670 | dctx = zstd.ZstdDecompressor() | |||
|
671 | ||||
|
672 | reader = dctx.stream_reader(foo) | |||
|
673 | with self.assertRaises(Exception): | |||
|
674 | reader.readinto1(b'foobar') | |||
|
675 | ||||
|
676 | # Sufficiently large destination. | |||
|
677 | b = bytearray(1024) | |||
|
678 | reader = dctx.stream_reader(foo) | |||
|
679 | self.assertEqual(reader.readinto1(b), 3) | |||
|
680 | self.assertEqual(b[0:3], b'foo') | |||
|
681 | self.assertEqual(reader.readinto1(b), 0) | |||
|
682 | self.assertEqual(b[0:3], b'foo') | |||
|
683 | ||||
|
684 | # readinto() with small reads. | |||
|
685 | b = bytearray(1024) | |||
|
686 | reader = dctx.stream_reader(foo, read_size=1) | |||
|
687 | self.assertEqual(reader.readinto1(b), 3) | |||
|
688 | self.assertEqual(b[0:3], b'foo') | |||
|
689 | ||||
|
690 | # Too small destination buffer. | |||
|
691 | b = bytearray(2) | |||
|
692 | reader = dctx.stream_reader(foo) | |||
|
693 | self.assertEqual(reader.readinto1(b), 2) | |||
|
694 | self.assertEqual(b[:], b'fo') | |||
|
695 | ||||
|
696 | def test_readall(self): | |||
|
697 | cctx = zstd.ZstdCompressor() | |||
|
698 | foo = cctx.compress(b'foo') | |||
|
699 | ||||
|
700 | dctx = zstd.ZstdDecompressor() | |||
|
701 | reader = dctx.stream_reader(foo) | |||
|
702 | ||||
|
703 | self.assertEqual(reader.readall(), b'foo') | |||
|
704 | ||||
|
705 | def test_read1(self): | |||
|
706 | cctx = zstd.ZstdCompressor() | |||
|
707 | foo = cctx.compress(b'foo') | |||
|
708 | ||||
|
709 | dctx = zstd.ZstdDecompressor() | |||
|
710 | ||||
|
711 | b = OpCountingBytesIO(foo) | |||
|
712 | reader = dctx.stream_reader(b) | |||
|
713 | ||||
|
714 | self.assertEqual(reader.read1(), b'foo') | |||
|
715 | self.assertEqual(b._read_count, 1) | |||
|
716 | ||||
|
717 | b = OpCountingBytesIO(foo) | |||
|
718 | reader = dctx.stream_reader(b) | |||
|
719 | ||||
|
720 | self.assertEqual(reader.read1(0), b'') | |||
|
721 | self.assertEqual(reader.read1(2), b'fo') | |||
|
722 | self.assertEqual(b._read_count, 1) | |||
|
723 | self.assertEqual(reader.read1(1), b'o') | |||
|
724 | self.assertEqual(b._read_count, 1) | |||
|
725 | self.assertEqual(reader.read1(1), b'') | |||
|
726 | self.assertEqual(b._read_count, 2) | |||
|
727 | ||||
|
728 | def test_read_lines(self): | |||
|
729 | cctx = zstd.ZstdCompressor() | |||
|
730 | source = b'\n'.join(('line %d' % i).encode('ascii') for i in range(1024)) | |||
|
731 | ||||
|
732 | frame = cctx.compress(source) | |||
|
733 | ||||
|
734 | dctx = zstd.ZstdDecompressor() | |||
|
735 | reader = dctx.stream_reader(frame) | |||
|
736 | tr = io.TextIOWrapper(reader, encoding='utf-8') | |||
|
737 | ||||
|
738 | lines = [] | |||
|
739 | for line in tr: | |||
|
740 | lines.append(line.encode('utf-8')) | |||
|
741 | ||||
|
742 | self.assertEqual(len(lines), 1024) | |||
|
743 | self.assertEqual(b''.join(lines), source) | |||
|
744 | ||||
|
745 | reader = dctx.stream_reader(frame) | |||
|
746 | tr = io.TextIOWrapper(reader, encoding='utf-8') | |||
|
747 | ||||
|
748 | lines = tr.readlines() | |||
|
749 | self.assertEqual(len(lines), 1024) | |||
|
750 | self.assertEqual(''.join(lines).encode('utf-8'), source) | |||
|
751 | ||||
|
752 | reader = dctx.stream_reader(frame) | |||
|
753 | tr = io.TextIOWrapper(reader, encoding='utf-8') | |||
|
754 | ||||
|
755 | lines = [] | |||
|
756 | while True: | |||
|
757 | line = tr.readline() | |||
|
758 | if not line: | |||
|
759 | break | |||
|
760 | ||||
|
761 | lines.append(line.encode('utf-8')) | |||
|
762 | ||||
|
763 | self.assertEqual(len(lines), 1024) | |||
|
764 | self.assertEqual(b''.join(lines), source) | |||
|
765 | ||||
534 |
|
766 | |||
535 | @make_cffi |
|
767 | @make_cffi | |
536 | class TestDecompressor_decompressobj(unittest.TestCase): |
|
768 | class TestDecompressor_decompressobj(unittest.TestCase): | |
@@ -540,6 +772,9 b' class TestDecompressor_decompressobj(uni' | |||||
540 | dctx = zstd.ZstdDecompressor() |
|
772 | dctx = zstd.ZstdDecompressor() | |
541 | dobj = dctx.decompressobj() |
|
773 | dobj = dctx.decompressobj() | |
542 | self.assertEqual(dobj.decompress(data), b'foobar') |
|
774 | self.assertEqual(dobj.decompress(data), b'foobar') | |
|
775 | self.assertIsNone(dobj.flush()) | |||
|
776 | self.assertIsNone(dobj.flush(10)) | |||
|
777 | self.assertIsNone(dobj.flush(length=100)) | |||
543 |
|
778 | |||
544 | def test_input_types(self): |
|
779 | def test_input_types(self): | |
545 | compressed = zstd.ZstdCompressor(level=1).compress(b'foo') |
|
780 | compressed = zstd.ZstdCompressor(level=1).compress(b'foo') | |
@@ -557,7 +792,11 b' class TestDecompressor_decompressobj(uni' | |||||
557 |
|
792 | |||
558 | for source in sources: |
|
793 | for source in sources: | |
559 | dobj = dctx.decompressobj() |
|
794 | dobj = dctx.decompressobj() | |
|
795 | self.assertIsNone(dobj.flush()) | |||
|
796 | self.assertIsNone(dobj.flush(10)) | |||
|
797 | self.assertIsNone(dobj.flush(length=100)) | |||
560 | self.assertEqual(dobj.decompress(source), b'foo') |
|
798 | self.assertEqual(dobj.decompress(source), b'foo') | |
|
799 | self.assertIsNone(dobj.flush()) | |||
561 |
|
800 | |||
562 | def test_reuse(self): |
|
801 | def test_reuse(self): | |
563 | data = zstd.ZstdCompressor(level=1).compress(b'foobar') |
|
802 | data = zstd.ZstdCompressor(level=1).compress(b'foobar') | |
@@ -568,6 +807,7 b' class TestDecompressor_decompressobj(uni' | |||||
568 |
|
807 | |||
569 | with self.assertRaisesRegexp(zstd.ZstdError, 'cannot use a decompressobj'): |
|
808 | with self.assertRaisesRegexp(zstd.ZstdError, 'cannot use a decompressobj'): | |
570 | dobj.decompress(data) |
|
809 | dobj.decompress(data) | |
|
810 | self.assertIsNone(dobj.flush()) | |||
571 |
|
811 | |||
572 | def test_bad_write_size(self): |
|
812 | def test_bad_write_size(self): | |
573 | dctx = zstd.ZstdDecompressor() |
|
813 | dctx = zstd.ZstdDecompressor() | |
@@ -585,16 +825,141 b' class TestDecompressor_decompressobj(uni' | |||||
585 | dobj = dctx.decompressobj(write_size=i + 1) |
|
825 | dobj = dctx.decompressobj(write_size=i + 1) | |
586 | self.assertEqual(dobj.decompress(data), source) |
|
826 | self.assertEqual(dobj.decompress(data), source) | |
587 |
|
827 | |||
|
828 | ||||
588 | def decompress_via_writer(data): |
|
829 | def decompress_via_writer(data): | |
589 | buffer = io.BytesIO() |
|
830 | buffer = io.BytesIO() | |
590 | dctx = zstd.ZstdDecompressor() |
|
831 | dctx = zstd.ZstdDecompressor() | |
591 |
|
|
832 | decompressor = dctx.stream_writer(buffer) | |
592 |
|
|
833 | decompressor.write(data) | |
|
834 | ||||
593 | return buffer.getvalue() |
|
835 | return buffer.getvalue() | |
594 |
|
836 | |||
595 |
|
837 | |||
596 | @make_cffi |
|
838 | @make_cffi | |
597 | class TestDecompressor_stream_writer(unittest.TestCase): |
|
839 | class TestDecompressor_stream_writer(unittest.TestCase): | |
|
840 | def test_io_api(self): | |||
|
841 | buffer = io.BytesIO() | |||
|
842 | dctx = zstd.ZstdDecompressor() | |||
|
843 | writer = dctx.stream_writer(buffer) | |||
|
844 | ||||
|
845 | self.assertFalse(writer.closed) | |||
|
846 | self.assertFalse(writer.isatty()) | |||
|
847 | self.assertFalse(writer.readable()) | |||
|
848 | ||||
|
849 | with self.assertRaises(io.UnsupportedOperation): | |||
|
850 | writer.readline() | |||
|
851 | ||||
|
852 | with self.assertRaises(io.UnsupportedOperation): | |||
|
853 | writer.readline(42) | |||
|
854 | ||||
|
855 | with self.assertRaises(io.UnsupportedOperation): | |||
|
856 | writer.readline(size=42) | |||
|
857 | ||||
|
858 | with self.assertRaises(io.UnsupportedOperation): | |||
|
859 | writer.readlines() | |||
|
860 | ||||
|
861 | with self.assertRaises(io.UnsupportedOperation): | |||
|
862 | writer.readlines(42) | |||
|
863 | ||||
|
864 | with self.assertRaises(io.UnsupportedOperation): | |||
|
865 | writer.readlines(hint=42) | |||
|
866 | ||||
|
867 | with self.assertRaises(io.UnsupportedOperation): | |||
|
868 | writer.seek(0) | |||
|
869 | ||||
|
870 | with self.assertRaises(io.UnsupportedOperation): | |||
|
871 | writer.seek(10, os.SEEK_SET) | |||
|
872 | ||||
|
873 | self.assertFalse(writer.seekable()) | |||
|
874 | ||||
|
875 | with self.assertRaises(io.UnsupportedOperation): | |||
|
876 | writer.tell() | |||
|
877 | ||||
|
878 | with self.assertRaises(io.UnsupportedOperation): | |||
|
879 | writer.truncate() | |||
|
880 | ||||
|
881 | with self.assertRaises(io.UnsupportedOperation): | |||
|
882 | writer.truncate(42) | |||
|
883 | ||||
|
884 | with self.assertRaises(io.UnsupportedOperation): | |||
|
885 | writer.truncate(size=42) | |||
|
886 | ||||
|
887 | self.assertTrue(writer.writable()) | |||
|
888 | ||||
|
889 | with self.assertRaises(io.UnsupportedOperation): | |||
|
890 | writer.writelines([]) | |||
|
891 | ||||
|
892 | with self.assertRaises(io.UnsupportedOperation): | |||
|
893 | writer.read() | |||
|
894 | ||||
|
895 | with self.assertRaises(io.UnsupportedOperation): | |||
|
896 | writer.read(42) | |||
|
897 | ||||
|
898 | with self.assertRaises(io.UnsupportedOperation): | |||
|
899 | writer.read(size=42) | |||
|
900 | ||||
|
901 | with self.assertRaises(io.UnsupportedOperation): | |||
|
902 | writer.readall() | |||
|
903 | ||||
|
904 | with self.assertRaises(io.UnsupportedOperation): | |||
|
905 | writer.readinto(None) | |||
|
906 | ||||
|
907 | with self.assertRaises(io.UnsupportedOperation): | |||
|
908 | writer.fileno() | |||
|
909 | ||||
|
910 | def test_fileno_file(self): | |||
|
911 | with tempfile.TemporaryFile('wb') as tf: | |||
|
912 | dctx = zstd.ZstdDecompressor() | |||
|
913 | writer = dctx.stream_writer(tf) | |||
|
914 | ||||
|
915 | self.assertEqual(writer.fileno(), tf.fileno()) | |||
|
916 | ||||
|
917 | def test_close(self): | |||
|
918 | foo = zstd.ZstdCompressor().compress(b'foo') | |||
|
919 | ||||
|
920 | buffer = NonClosingBytesIO() | |||
|
921 | dctx = zstd.ZstdDecompressor() | |||
|
922 | writer = dctx.stream_writer(buffer) | |||
|
923 | ||||
|
924 | writer.write(foo) | |||
|
925 | self.assertFalse(writer.closed) | |||
|
926 | self.assertFalse(buffer.closed) | |||
|
927 | writer.close() | |||
|
928 | self.assertTrue(writer.closed) | |||
|
929 | self.assertTrue(buffer.closed) | |||
|
930 | ||||
|
931 | with self.assertRaisesRegexp(ValueError, 'stream is closed'): | |||
|
932 | writer.write(b'') | |||
|
933 | ||||
|
934 | with self.assertRaisesRegexp(ValueError, 'stream is closed'): | |||
|
935 | writer.flush() | |||
|
936 | ||||
|
937 | with self.assertRaisesRegexp(ValueError, 'stream is closed'): | |||
|
938 | with writer: | |||
|
939 | pass | |||
|
940 | ||||
|
941 | self.assertEqual(buffer.getvalue(), b'foo') | |||
|
942 | ||||
|
943 | # Context manager exit should close stream. | |||
|
944 | buffer = NonClosingBytesIO() | |||
|
945 | writer = dctx.stream_writer(buffer) | |||
|
946 | ||||
|
947 | with writer: | |||
|
948 | writer.write(foo) | |||
|
949 | ||||
|
950 | self.assertTrue(writer.closed) | |||
|
951 | self.assertEqual(buffer.getvalue(), b'foo') | |||
|
952 | ||||
|
953 | def test_flush(self): | |||
|
954 | buffer = OpCountingBytesIO() | |||
|
955 | dctx = zstd.ZstdDecompressor() | |||
|
956 | writer = dctx.stream_writer(buffer) | |||
|
957 | ||||
|
958 | writer.flush() | |||
|
959 | self.assertEqual(buffer._flush_count, 1) | |||
|
960 | writer.flush() | |||
|
961 | self.assertEqual(buffer._flush_count, 2) | |||
|
962 | ||||
598 | def test_empty_roundtrip(self): |
|
963 | def test_empty_roundtrip(self): | |
599 | cctx = zstd.ZstdCompressor() |
|
964 | cctx = zstd.ZstdCompressor() | |
600 | empty = cctx.compress(b'') |
|
965 | empty = cctx.compress(b'') | |
@@ -616,9 +981,21 b' class TestDecompressor_stream_writer(uni' | |||||
616 | dctx = zstd.ZstdDecompressor() |
|
981 | dctx = zstd.ZstdDecompressor() | |
617 | for source in sources: |
|
982 | for source in sources: | |
618 | buffer = io.BytesIO() |
|
983 | buffer = io.BytesIO() | |
|
984 | ||||
|
985 | decompressor = dctx.stream_writer(buffer) | |||
|
986 | decompressor.write(source) | |||
|
987 | self.assertEqual(buffer.getvalue(), b'foo') | |||
|
988 | ||||
|
989 | buffer = NonClosingBytesIO() | |||
|
990 | ||||
619 | with dctx.stream_writer(buffer) as decompressor: |
|
991 | with dctx.stream_writer(buffer) as decompressor: | |
620 | decompressor.write(source) |
|
992 | self.assertEqual(decompressor.write(source), 3) | |
|
993 | ||||
|
994 | self.assertEqual(buffer.getvalue(), b'foo') | |||
621 |
|
995 | |||
|
996 | buffer = io.BytesIO() | |||
|
997 | writer = dctx.stream_writer(buffer, write_return_read=True) | |||
|
998 | self.assertEqual(writer.write(source), len(source)) | |||
622 | self.assertEqual(buffer.getvalue(), b'foo') |
|
999 | self.assertEqual(buffer.getvalue(), b'foo') | |
623 |
|
1000 | |||
624 | def test_large_roundtrip(self): |
|
1001 | def test_large_roundtrip(self): | |
@@ -641,7 +1018,7 b' class TestDecompressor_stream_writer(uni' | |||||
641 | cctx = zstd.ZstdCompressor() |
|
1018 | cctx = zstd.ZstdCompressor() | |
642 | compressed = cctx.compress(orig) |
|
1019 | compressed = cctx.compress(orig) | |
643 |
|
1020 | |||
644 |
buffer = |
|
1021 | buffer = NonClosingBytesIO() | |
645 | dctx = zstd.ZstdDecompressor() |
|
1022 | dctx = zstd.ZstdDecompressor() | |
646 | with dctx.stream_writer(buffer) as decompressor: |
|
1023 | with dctx.stream_writer(buffer) as decompressor: | |
647 | pos = 0 |
|
1024 | pos = 0 | |
@@ -651,6 +1028,17 b' class TestDecompressor_stream_writer(uni' | |||||
651 | pos += 8192 |
|
1028 | pos += 8192 | |
652 | self.assertEqual(buffer.getvalue(), orig) |
|
1029 | self.assertEqual(buffer.getvalue(), orig) | |
653 |
|
1030 | |||
|
1031 | # Again with write_return_read=True | |||
|
1032 | buffer = io.BytesIO() | |||
|
1033 | writer = dctx.stream_writer(buffer, write_return_read=True) | |||
|
1034 | pos = 0 | |||
|
1035 | while pos < len(compressed): | |||
|
1036 | pos2 = pos + 8192 | |||
|
1037 | chunk = compressed[pos:pos2] | |||
|
1038 | self.assertEqual(writer.write(chunk), len(chunk)) | |||
|
1039 | pos += 8192 | |||
|
1040 | self.assertEqual(buffer.getvalue(), orig) | |||
|
1041 | ||||
654 | def test_dictionary(self): |
|
1042 | def test_dictionary(self): | |
655 | samples = [] |
|
1043 | samples = [] | |
656 | for i in range(128): |
|
1044 | for i in range(128): | |
@@ -661,7 +1049,7 b' class TestDecompressor_stream_writer(uni' | |||||
661 | d = zstd.train_dictionary(8192, samples) |
|
1049 | d = zstd.train_dictionary(8192, samples) | |
662 |
|
1050 | |||
663 | orig = b'foobar' * 16384 |
|
1051 | orig = b'foobar' * 16384 | |
664 |
buffer = |
|
1052 | buffer = NonClosingBytesIO() | |
665 | cctx = zstd.ZstdCompressor(dict_data=d) |
|
1053 | cctx = zstd.ZstdCompressor(dict_data=d) | |
666 | with cctx.stream_writer(buffer) as compressor: |
|
1054 | with cctx.stream_writer(buffer) as compressor: | |
667 | self.assertEqual(compressor.write(orig), 0) |
|
1055 | self.assertEqual(compressor.write(orig), 0) | |
@@ -670,6 +1058,12 b' class TestDecompressor_stream_writer(uni' | |||||
670 | buffer = io.BytesIO() |
|
1058 | buffer = io.BytesIO() | |
671 |
|
1059 | |||
672 | dctx = zstd.ZstdDecompressor(dict_data=d) |
|
1060 | dctx = zstd.ZstdDecompressor(dict_data=d) | |
|
1061 | decompressor = dctx.stream_writer(buffer) | |||
|
1062 | self.assertEqual(decompressor.write(compressed), len(orig)) | |||
|
1063 | self.assertEqual(buffer.getvalue(), orig) | |||
|
1064 | ||||
|
1065 | buffer = NonClosingBytesIO() | |||
|
1066 | ||||
673 | with dctx.stream_writer(buffer) as decompressor: |
|
1067 | with dctx.stream_writer(buffer) as decompressor: | |
674 | self.assertEqual(decompressor.write(compressed), len(orig)) |
|
1068 | self.assertEqual(decompressor.write(compressed), len(orig)) | |
675 |
|
1069 | |||
@@ -678,6 +1072,11 b' class TestDecompressor_stream_writer(uni' | |||||
678 | def test_memory_size(self): |
|
1072 | def test_memory_size(self): | |
679 | dctx = zstd.ZstdDecompressor() |
|
1073 | dctx = zstd.ZstdDecompressor() | |
680 | buffer = io.BytesIO() |
|
1074 | buffer = io.BytesIO() | |
|
1075 | ||||
|
1076 | decompressor = dctx.stream_writer(buffer) | |||
|
1077 | size = decompressor.memory_size() | |||
|
1078 | self.assertGreater(size, 100000) | |||
|
1079 | ||||
681 | with dctx.stream_writer(buffer) as decompressor: |
|
1080 | with dctx.stream_writer(buffer) as decompressor: | |
682 | size = decompressor.memory_size() |
|
1081 | size = decompressor.memory_size() | |
683 |
|
1082 | |||
@@ -810,7 +1209,7 b' class TestDecompressor_read_to_iter(unit' | |||||
810 | @unittest.skipUnless('ZSTD_SLOW_TESTS' in os.environ, 'ZSTD_SLOW_TESTS not set') |
|
1209 | @unittest.skipUnless('ZSTD_SLOW_TESTS' in os.environ, 'ZSTD_SLOW_TESTS not set') | |
811 | def test_large_input(self): |
|
1210 | def test_large_input(self): | |
812 | bytes = list(struct.Struct('>B').pack(i) for i in range(256)) |
|
1211 | bytes = list(struct.Struct('>B').pack(i) for i in range(256)) | |
813 |
compressed = |
|
1212 | compressed = NonClosingBytesIO() | |
814 | input_size = 0 |
|
1213 | input_size = 0 | |
815 | cctx = zstd.ZstdCompressor(level=1) |
|
1214 | cctx = zstd.ZstdCompressor(level=1) | |
816 | with cctx.stream_writer(compressed) as compressor: |
|
1215 | with cctx.stream_writer(compressed) as compressor: | |
@@ -823,7 +1222,7 b' class TestDecompressor_read_to_iter(unit' | |||||
823 | if have_compressed and have_raw: |
|
1222 | if have_compressed and have_raw: | |
824 | break |
|
1223 | break | |
825 |
|
1224 | |||
826 |
compressed. |
|
1225 | compressed = io.BytesIO(compressed.getvalue()) | |
827 | self.assertGreater(len(compressed.getvalue()), |
|
1226 | self.assertGreater(len(compressed.getvalue()), | |
828 | zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE) |
|
1227 | zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE) | |
829 |
|
1228 | |||
@@ -861,7 +1260,7 b' class TestDecompressor_read_to_iter(unit' | |||||
861 |
|
1260 | |||
862 | source = io.BytesIO() |
|
1261 | source = io.BytesIO() | |
863 |
|
1262 | |||
864 |
compressed = |
|
1263 | compressed = NonClosingBytesIO() | |
865 | with cctx.stream_writer(compressed) as compressor: |
|
1264 | with cctx.stream_writer(compressed) as compressor: | |
866 | for i in range(256): |
|
1265 | for i in range(256): | |
867 | chunk = b'\0' * 1024 |
|
1266 | chunk = b'\0' * 1024 | |
@@ -874,7 +1273,7 b' class TestDecompressor_read_to_iter(unit' | |||||
874 | max_output_size=len(source.getvalue())) |
|
1273 | max_output_size=len(source.getvalue())) | |
875 | self.assertEqual(simple, source.getvalue()) |
|
1274 | self.assertEqual(simple, source.getvalue()) | |
876 |
|
1275 | |||
877 |
compressed. |
|
1276 | compressed = io.BytesIO(compressed.getvalue()) | |
878 | streamed = b''.join(dctx.read_to_iter(compressed)) |
|
1277 | streamed = b''.join(dctx.read_to_iter(compressed)) | |
879 | self.assertEqual(streamed, source.getvalue()) |
|
1278 | self.assertEqual(streamed, source.getvalue()) | |
880 |
|
1279 | |||
@@ -1001,6 +1400,9 b' class TestDecompressor_multi_decompress_' | |||||
1001 | def test_invalid_inputs(self): |
|
1400 | def test_invalid_inputs(self): | |
1002 | dctx = zstd.ZstdDecompressor() |
|
1401 | dctx = zstd.ZstdDecompressor() | |
1003 |
|
1402 | |||
|
1403 | if not hasattr(dctx, 'multi_decompress_to_buffer'): | |||
|
1404 | self.skipTest('multi_decompress_to_buffer not available') | |||
|
1405 | ||||
1004 | with self.assertRaises(TypeError): |
|
1406 | with self.assertRaises(TypeError): | |
1005 | dctx.multi_decompress_to_buffer(True) |
|
1407 | dctx.multi_decompress_to_buffer(True) | |
1006 |
|
1408 | |||
@@ -1020,6 +1422,10 b' class TestDecompressor_multi_decompress_' | |||||
1020 | frames = [cctx.compress(d) for d in original] |
|
1422 | frames = [cctx.compress(d) for d in original] | |
1021 |
|
1423 | |||
1022 | dctx = zstd.ZstdDecompressor() |
|
1424 | dctx = zstd.ZstdDecompressor() | |
|
1425 | ||||
|
1426 | if not hasattr(dctx, 'multi_decompress_to_buffer'): | |||
|
1427 | self.skipTest('multi_decompress_to_buffer not available') | |||
|
1428 | ||||
1023 | result = dctx.multi_decompress_to_buffer(frames) |
|
1429 | result = dctx.multi_decompress_to_buffer(frames) | |
1024 |
|
1430 | |||
1025 | self.assertEqual(len(result), len(frames)) |
|
1431 | self.assertEqual(len(result), len(frames)) | |
@@ -1041,6 +1447,10 b' class TestDecompressor_multi_decompress_' | |||||
1041 | sizes = struct.pack('=' + 'Q' * len(original), *map(len, original)) |
|
1447 | sizes = struct.pack('=' + 'Q' * len(original), *map(len, original)) | |
1042 |
|
1448 | |||
1043 | dctx = zstd.ZstdDecompressor() |
|
1449 | dctx = zstd.ZstdDecompressor() | |
|
1450 | ||||
|
1451 | if not hasattr(dctx, 'multi_decompress_to_buffer'): | |||
|
1452 | self.skipTest('multi_decompress_to_buffer not available') | |||
|
1453 | ||||
1044 | result = dctx.multi_decompress_to_buffer(frames, decompressed_sizes=sizes) |
|
1454 | result = dctx.multi_decompress_to_buffer(frames, decompressed_sizes=sizes) | |
1045 |
|
1455 | |||
1046 | self.assertEqual(len(result), len(frames)) |
|
1456 | self.assertEqual(len(result), len(frames)) | |
@@ -1057,6 +1467,9 b' class TestDecompressor_multi_decompress_' | |||||
1057 |
|
1467 | |||
1058 | dctx = zstd.ZstdDecompressor() |
|
1468 | dctx = zstd.ZstdDecompressor() | |
1059 |
|
1469 | |||
|
1470 | if not hasattr(dctx, 'multi_decompress_to_buffer'): | |||
|
1471 | self.skipTest('multi_decompress_to_buffer not available') | |||
|
1472 | ||||
1060 | segments = struct.pack('=QQQQ', 0, len(frames[0]), len(frames[0]), len(frames[1])) |
|
1473 | segments = struct.pack('=QQQQ', 0, len(frames[0]), len(frames[0]), len(frames[1])) | |
1061 | b = zstd.BufferWithSegments(b''.join(frames), segments) |
|
1474 | b = zstd.BufferWithSegments(b''.join(frames), segments) | |
1062 |
|
1475 | |||
@@ -1074,12 +1487,16 b' class TestDecompressor_multi_decompress_' | |||||
1074 | frames = [cctx.compress(d) for d in original] |
|
1487 | frames = [cctx.compress(d) for d in original] | |
1075 | sizes = struct.pack('=' + 'Q' * len(original), *map(len, original)) |
|
1488 | sizes = struct.pack('=' + 'Q' * len(original), *map(len, original)) | |
1076 |
|
1489 | |||
|
1490 | dctx = zstd.ZstdDecompressor() | |||
|
1491 | ||||
|
1492 | if not hasattr(dctx, 'multi_decompress_to_buffer'): | |||
|
1493 | self.skipTest('multi_decompress_to_buffer not available') | |||
|
1494 | ||||
1077 | segments = struct.pack('=QQQQQQ', 0, len(frames[0]), |
|
1495 | segments = struct.pack('=QQQQQQ', 0, len(frames[0]), | |
1078 | len(frames[0]), len(frames[1]), |
|
1496 | len(frames[0]), len(frames[1]), | |
1079 | len(frames[0]) + len(frames[1]), len(frames[2])) |
|
1497 | len(frames[0]) + len(frames[1]), len(frames[2])) | |
1080 | b = zstd.BufferWithSegments(b''.join(frames), segments) |
|
1498 | b = zstd.BufferWithSegments(b''.join(frames), segments) | |
1081 |
|
1499 | |||
1082 | dctx = zstd.ZstdDecompressor() |
|
|||
1083 | result = dctx.multi_decompress_to_buffer(b, decompressed_sizes=sizes) |
|
1500 | result = dctx.multi_decompress_to_buffer(b, decompressed_sizes=sizes) | |
1084 |
|
1501 | |||
1085 | self.assertEqual(len(result), len(frames)) |
|
1502 | self.assertEqual(len(result), len(frames)) | |
@@ -1099,10 +1516,14 b' class TestDecompressor_multi_decompress_' | |||||
1099 | b'foo4' * 6, |
|
1516 | b'foo4' * 6, | |
1100 | ] |
|
1517 | ] | |
1101 |
|
1518 | |||
|
1519 | if not hasattr(cctx, 'multi_compress_to_buffer'): | |||
|
1520 | self.skipTest('multi_compress_to_buffer not available') | |||
|
1521 | ||||
1102 | frames = cctx.multi_compress_to_buffer(original) |
|
1522 | frames = cctx.multi_compress_to_buffer(original) | |
1103 |
|
1523 | |||
1104 | # Check round trip. |
|
1524 | # Check round trip. | |
1105 | dctx = zstd.ZstdDecompressor() |
|
1525 | dctx = zstd.ZstdDecompressor() | |
|
1526 | ||||
1106 | decompressed = dctx.multi_decompress_to_buffer(frames, threads=3) |
|
1527 | decompressed = dctx.multi_decompress_to_buffer(frames, threads=3) | |
1107 |
|
1528 | |||
1108 | self.assertEqual(len(decompressed), len(original)) |
|
1529 | self.assertEqual(len(decompressed), len(original)) | |
@@ -1138,7 +1559,12 b' class TestDecompressor_multi_decompress_' | |||||
1138 | frames = [cctx.compress(s) for s in generate_samples()] |
|
1559 | frames = [cctx.compress(s) for s in generate_samples()] | |
1139 |
|
1560 | |||
1140 | dctx = zstd.ZstdDecompressor(dict_data=d) |
|
1561 | dctx = zstd.ZstdDecompressor(dict_data=d) | |
|
1562 | ||||
|
1563 | if not hasattr(dctx, 'multi_decompress_to_buffer'): | |||
|
1564 | self.skipTest('multi_decompress_to_buffer not available') | |||
|
1565 | ||||
1141 | result = dctx.multi_decompress_to_buffer(frames) |
|
1566 | result = dctx.multi_decompress_to_buffer(frames) | |
|
1567 | ||||
1142 | self.assertEqual([o.tobytes() for o in result], generate_samples()) |
|
1568 | self.assertEqual([o.tobytes() for o in result], generate_samples()) | |
1143 |
|
1569 | |||
1144 | def test_multiple_threads(self): |
|
1570 | def test_multiple_threads(self): | |
@@ -1149,6 +1575,10 b' class TestDecompressor_multi_decompress_' | |||||
1149 | frames.extend(cctx.compress(b'y' * 64) for i in range(256)) |
|
1575 | frames.extend(cctx.compress(b'y' * 64) for i in range(256)) | |
1150 |
|
1576 | |||
1151 | dctx = zstd.ZstdDecompressor() |
|
1577 | dctx = zstd.ZstdDecompressor() | |
|
1578 | ||||
|
1579 | if not hasattr(dctx, 'multi_decompress_to_buffer'): | |||
|
1580 | self.skipTest('multi_decompress_to_buffer not available') | |||
|
1581 | ||||
1152 | result = dctx.multi_decompress_to_buffer(frames, threads=-1) |
|
1582 | result = dctx.multi_decompress_to_buffer(frames, threads=-1) | |
1153 |
|
1583 | |||
1154 | self.assertEqual(len(result), len(frames)) |
|
1584 | self.assertEqual(len(result), len(frames)) | |
@@ -1164,6 +1594,9 b' class TestDecompressor_multi_decompress_' | |||||
1164 |
|
1594 | |||
1165 | dctx = zstd.ZstdDecompressor() |
|
1595 | dctx = zstd.ZstdDecompressor() | |
1166 |
|
1596 | |||
|
1597 | if not hasattr(dctx, 'multi_decompress_to_buffer'): | |||
|
1598 | self.skipTest('multi_decompress_to_buffer not available') | |||
|
1599 | ||||
1167 | with self.assertRaisesRegexp(zstd.ZstdError, |
|
1600 | with self.assertRaisesRegexp(zstd.ZstdError, | |
1168 | 'error decompressing item 1: (' |
|
1601 | 'error decompressing item 1: (' | |
1169 | 'Corrupted block|' |
|
1602 | 'Corrupted block|' |
@@ -12,6 +12,7 b' import zstandard as zstd' | |||||
12 |
|
12 | |||
13 | from . common import ( |
|
13 | from . common import ( | |
14 | make_cffi, |
|
14 | make_cffi, | |
|
15 | NonClosingBytesIO, | |||
15 | random_input_data, |
|
16 | random_input_data, | |
16 | ) |
|
17 | ) | |
17 |
|
18 | |||
@@ -23,22 +24,200 b' class TestDecompressor_stream_reader_fuz' | |||||
23 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) |
|
24 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |
24 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), |
|
25 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |
25 | level=strategies.integers(min_value=1, max_value=5), |
|
26 | level=strategies.integers(min_value=1, max_value=5), | |
26 |
s |
|
27 | streaming=strategies.booleans(), | |
|
28 | source_read_size=strategies.integers(1, 1048576), | |||
27 | read_sizes=strategies.data()) |
|
29 | read_sizes=strategies.data()) | |
28 |
def test_stream_source_read_variance(self, original, level, s |
|
30 | def test_stream_source_read_variance(self, original, level, streaming, | |
29 | read_sizes): |
|
31 | source_read_size, read_sizes): | |
30 | cctx = zstd.ZstdCompressor(level=level) |
|
32 | cctx = zstd.ZstdCompressor(level=level) | |
31 | frame = cctx.compress(original) |
|
33 | ||
|
34 | if streaming: | |||
|
35 | source = io.BytesIO() | |||
|
36 | writer = cctx.stream_writer(source) | |||
|
37 | writer.write(original) | |||
|
38 | writer.flush(zstd.FLUSH_FRAME) | |||
|
39 | source.seek(0) | |||
|
40 | else: | |||
|
41 | frame = cctx.compress(original) | |||
|
42 | source = io.BytesIO(frame) | |||
32 |
|
43 | |||
33 | dctx = zstd.ZstdDecompressor() |
|
44 | dctx = zstd.ZstdDecompressor() | |
34 | source = io.BytesIO(frame) |
|
|||
35 |
|
45 | |||
36 | chunks = [] |
|
46 | chunks = [] | |
37 | with dctx.stream_reader(source, read_size=source_read_size) as reader: |
|
47 | with dctx.stream_reader(source, read_size=source_read_size) as reader: | |
38 | while True: |
|
48 | while True: | |
39 |
read_size = read_sizes.draw(strategies.integers(1, 1 |
|
49 | read_size = read_sizes.draw(strategies.integers(-1, 131072)) | |
|
50 | chunk = reader.read(read_size) | |||
|
51 | if not chunk and read_size: | |||
|
52 | break | |||
|
53 | ||||
|
54 | chunks.append(chunk) | |||
|
55 | ||||
|
56 | self.assertEqual(b''.join(chunks), original) | |||
|
57 | ||||
|
58 | # Similar to above except we have a constant read() size. | |||
|
59 | @hypothesis.settings( | |||
|
60 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
61 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
62 | level=strategies.integers(min_value=1, max_value=5), | |||
|
63 | streaming=strategies.booleans(), | |||
|
64 | source_read_size=strategies.integers(1, 1048576), | |||
|
65 | read_size=strategies.integers(-1, 131072)) | |||
|
66 | def test_stream_source_read_size(self, original, level, streaming, | |||
|
67 | source_read_size, read_size): | |||
|
68 | if read_size == 0: | |||
|
69 | read_size = 1 | |||
|
70 | ||||
|
71 | cctx = zstd.ZstdCompressor(level=level) | |||
|
72 | ||||
|
73 | if streaming: | |||
|
74 | source = io.BytesIO() | |||
|
75 | writer = cctx.stream_writer(source) | |||
|
76 | writer.write(original) | |||
|
77 | writer.flush(zstd.FLUSH_FRAME) | |||
|
78 | source.seek(0) | |||
|
79 | else: | |||
|
80 | frame = cctx.compress(original) | |||
|
81 | source = io.BytesIO(frame) | |||
|
82 | ||||
|
83 | dctx = zstd.ZstdDecompressor() | |||
|
84 | ||||
|
85 | chunks = [] | |||
|
86 | reader = dctx.stream_reader(source, read_size=source_read_size) | |||
|
87 | while True: | |||
|
88 | chunk = reader.read(read_size) | |||
|
89 | if not chunk and read_size: | |||
|
90 | break | |||
|
91 | ||||
|
92 | chunks.append(chunk) | |||
|
93 | ||||
|
94 | self.assertEqual(b''.join(chunks), original) | |||
|
95 | ||||
|
96 | @hypothesis.settings( | |||
|
97 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
98 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
99 | level=strategies.integers(min_value=1, max_value=5), | |||
|
100 | streaming=strategies.booleans(), | |||
|
101 | source_read_size=strategies.integers(1, 1048576), | |||
|
102 | read_sizes=strategies.data()) | |||
|
103 | def test_buffer_source_read_variance(self, original, level, streaming, | |||
|
104 | source_read_size, read_sizes): | |||
|
105 | cctx = zstd.ZstdCompressor(level=level) | |||
|
106 | ||||
|
107 | if streaming: | |||
|
108 | source = io.BytesIO() | |||
|
109 | writer = cctx.stream_writer(source) | |||
|
110 | writer.write(original) | |||
|
111 | writer.flush(zstd.FLUSH_FRAME) | |||
|
112 | frame = source.getvalue() | |||
|
113 | else: | |||
|
114 | frame = cctx.compress(original) | |||
|
115 | ||||
|
116 | dctx = zstd.ZstdDecompressor() | |||
|
117 | chunks = [] | |||
|
118 | ||||
|
119 | with dctx.stream_reader(frame, read_size=source_read_size) as reader: | |||
|
120 | while True: | |||
|
121 | read_size = read_sizes.draw(strategies.integers(-1, 131072)) | |||
40 | chunk = reader.read(read_size) |
|
122 | chunk = reader.read(read_size) | |
41 | if not chunk: |
|
123 | if not chunk and read_size: | |
|
124 | break | |||
|
125 | ||||
|
126 | chunks.append(chunk) | |||
|
127 | ||||
|
128 | self.assertEqual(b''.join(chunks), original) | |||
|
129 | ||||
|
130 | # Similar to above except we have a constant read() size. | |||
|
131 | @hypothesis.settings( | |||
|
132 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
133 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
134 | level=strategies.integers(min_value=1, max_value=5), | |||
|
135 | streaming=strategies.booleans(), | |||
|
136 | source_read_size=strategies.integers(1, 1048576), | |||
|
137 | read_size=strategies.integers(-1, 131072)) | |||
|
138 | def test_buffer_source_constant_read_size(self, original, level, streaming, | |||
|
139 | source_read_size, read_size): | |||
|
140 | if read_size == 0: | |||
|
141 | read_size = -1 | |||
|
142 | ||||
|
143 | cctx = zstd.ZstdCompressor(level=level) | |||
|
144 | ||||
|
145 | if streaming: | |||
|
146 | source = io.BytesIO() | |||
|
147 | writer = cctx.stream_writer(source) | |||
|
148 | writer.write(original) | |||
|
149 | writer.flush(zstd.FLUSH_FRAME) | |||
|
150 | frame = source.getvalue() | |||
|
151 | else: | |||
|
152 | frame = cctx.compress(original) | |||
|
153 | ||||
|
154 | dctx = zstd.ZstdDecompressor() | |||
|
155 | chunks = [] | |||
|
156 | ||||
|
157 | reader = dctx.stream_reader(frame, read_size=source_read_size) | |||
|
158 | while True: | |||
|
159 | chunk = reader.read(read_size) | |||
|
160 | if not chunk and read_size: | |||
|
161 | break | |||
|
162 | ||||
|
163 | chunks.append(chunk) | |||
|
164 | ||||
|
165 | self.assertEqual(b''.join(chunks), original) | |||
|
166 | ||||
|
167 | @hypothesis.settings( | |||
|
168 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
169 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
170 | level=strategies.integers(min_value=1, max_value=5), | |||
|
171 | streaming=strategies.booleans(), | |||
|
172 | source_read_size=strategies.integers(1, 1048576)) | |||
|
173 | def test_stream_source_readall(self, original, level, streaming, | |||
|
174 | source_read_size): | |||
|
175 | cctx = zstd.ZstdCompressor(level=level) | |||
|
176 | ||||
|
177 | if streaming: | |||
|
178 | source = io.BytesIO() | |||
|
179 | writer = cctx.stream_writer(source) | |||
|
180 | writer.write(original) | |||
|
181 | writer.flush(zstd.FLUSH_FRAME) | |||
|
182 | source.seek(0) | |||
|
183 | else: | |||
|
184 | frame = cctx.compress(original) | |||
|
185 | source = io.BytesIO(frame) | |||
|
186 | ||||
|
187 | dctx = zstd.ZstdDecompressor() | |||
|
188 | ||||
|
189 | data = dctx.stream_reader(source, read_size=source_read_size).readall() | |||
|
190 | self.assertEqual(data, original) | |||
|
191 | ||||
|
192 | @hypothesis.settings( | |||
|
193 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
194 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |||
|
195 | level=strategies.integers(min_value=1, max_value=5), | |||
|
196 | streaming=strategies.booleans(), | |||
|
197 | source_read_size=strategies.integers(1, 1048576), | |||
|
198 | read_sizes=strategies.data()) | |||
|
199 | def test_stream_source_read1_variance(self, original, level, streaming, | |||
|
200 | source_read_size, read_sizes): | |||
|
201 | cctx = zstd.ZstdCompressor(level=level) | |||
|
202 | ||||
|
203 | if streaming: | |||
|
204 | source = io.BytesIO() | |||
|
205 | writer = cctx.stream_writer(source) | |||
|
206 | writer.write(original) | |||
|
207 | writer.flush(zstd.FLUSH_FRAME) | |||
|
208 | source.seek(0) | |||
|
209 | else: | |||
|
210 | frame = cctx.compress(original) | |||
|
211 | source = io.BytesIO(frame) | |||
|
212 | ||||
|
213 | dctx = zstd.ZstdDecompressor() | |||
|
214 | ||||
|
215 | chunks = [] | |||
|
216 | with dctx.stream_reader(source, read_size=source_read_size) as reader: | |||
|
217 | while True: | |||
|
218 | read_size = read_sizes.draw(strategies.integers(-1, 131072)) | |||
|
219 | chunk = reader.read1(read_size) | |||
|
220 | if not chunk and read_size: | |||
42 | break |
|
221 | break | |
43 |
|
222 | |||
44 | chunks.append(chunk) |
|
223 | chunks.append(chunk) | |
@@ -49,24 +228,36 b' class TestDecompressor_stream_reader_fuz' | |||||
49 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) |
|
228 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |
50 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), |
|
229 | @hypothesis.given(original=strategies.sampled_from(random_input_data()), | |
51 | level=strategies.integers(min_value=1, max_value=5), |
|
230 | level=strategies.integers(min_value=1, max_value=5), | |
52 |
s |
|
231 | streaming=strategies.booleans(), | |
|
232 | source_read_size=strategies.integers(1, 1048576), | |||
53 | read_sizes=strategies.data()) |
|
233 | read_sizes=strategies.data()) | |
54 |
def test_ |
|
234 | def test_stream_source_readinto1_variance(self, original, level, streaming, | |
55 | read_sizes): |
|
235 | source_read_size, read_sizes): | |
56 | cctx = zstd.ZstdCompressor(level=level) |
|
236 | cctx = zstd.ZstdCompressor(level=level) | |
57 | frame = cctx.compress(original) |
|
237 | ||
|
238 | if streaming: | |||
|
239 | source = io.BytesIO() | |||
|
240 | writer = cctx.stream_writer(source) | |||
|
241 | writer.write(original) | |||
|
242 | writer.flush(zstd.FLUSH_FRAME) | |||
|
243 | source.seek(0) | |||
|
244 | else: | |||
|
245 | frame = cctx.compress(original) | |||
|
246 | source = io.BytesIO(frame) | |||
58 |
|
247 | |||
59 | dctx = zstd.ZstdDecompressor() |
|
248 | dctx = zstd.ZstdDecompressor() | |
|
249 | ||||
60 | chunks = [] |
|
250 | chunks = [] | |
61 |
|
251 | with dctx.stream_reader(source, read_size=source_read_size) as reader: | ||
62 | with dctx.stream_reader(frame, read_size=source_read_size) as reader: |
|
|||
63 | while True: |
|
252 | while True: | |
64 |
read_size = read_sizes.draw(strategies.integers(1, 1 |
|
253 | read_size = read_sizes.draw(strategies.integers(1, 131072)) | |
65 |
|
|
254 | b = bytearray(read_size) | |
66 | if not chunk: |
|
255 | count = reader.readinto1(b) | |
|
256 | ||||
|
257 | if not count: | |||
67 | break |
|
258 | break | |
68 |
|
259 | |||
69 |
chunks.append( |
|
260 | chunks.append(bytes(b[0:count])) | |
70 |
|
261 | |||
71 | self.assertEqual(b''.join(chunks), original) |
|
262 | self.assertEqual(b''.join(chunks), original) | |
72 |
|
263 | |||
@@ -75,7 +266,7 b' class TestDecompressor_stream_reader_fuz' | |||||
75 | @hypothesis.given( |
|
266 | @hypothesis.given( | |
76 | original=strategies.sampled_from(random_input_data()), |
|
267 | original=strategies.sampled_from(random_input_data()), | |
77 | level=strategies.integers(min_value=1, max_value=5), |
|
268 | level=strategies.integers(min_value=1, max_value=5), | |
78 |
source_read_size=strategies.integers(1, 1 |
|
269 | source_read_size=strategies.integers(1, 1048576), | |
79 | seek_amounts=strategies.data(), |
|
270 | seek_amounts=strategies.data(), | |
80 | read_sizes=strategies.data()) |
|
271 | read_sizes=strategies.data()) | |
81 | def test_relative_seeks(self, original, level, source_read_size, seek_amounts, |
|
272 | def test_relative_seeks(self, original, level, source_read_size, seek_amounts, | |
@@ -99,6 +290,46 b' class TestDecompressor_stream_reader_fuz' | |||||
99 |
|
290 | |||
100 | self.assertEqual(original[offset:offset + len(chunk)], chunk) |
|
291 | self.assertEqual(original[offset:offset + len(chunk)], chunk) | |
101 |
|
292 | |||
|
293 | @hypothesis.settings( | |||
|
294 | suppress_health_check=[hypothesis.HealthCheck.large_base_example]) | |||
|
295 | @hypothesis.given( | |||
|
296 | originals=strategies.data(), | |||
|
297 | frame_count=strategies.integers(min_value=2, max_value=10), | |||
|
298 | level=strategies.integers(min_value=1, max_value=5), | |||
|
299 | source_read_size=strategies.integers(1, 1048576), | |||
|
300 | read_sizes=strategies.data()) | |||
|
301 | def test_multiple_frames(self, originals, frame_count, level, | |||
|
302 | source_read_size, read_sizes): | |||
|
303 | ||||
|
304 | cctx = zstd.ZstdCompressor(level=level) | |||
|
305 | source = io.BytesIO() | |||
|
306 | buffer = io.BytesIO() | |||
|
307 | writer = cctx.stream_writer(buffer) | |||
|
308 | ||||
|
309 | for i in range(frame_count): | |||
|
310 | data = originals.draw(strategies.sampled_from(random_input_data())) | |||
|
311 | source.write(data) | |||
|
312 | writer.write(data) | |||
|
313 | writer.flush(zstd.FLUSH_FRAME) | |||
|
314 | ||||
|
315 | dctx = zstd.ZstdDecompressor() | |||
|
316 | buffer.seek(0) | |||
|
317 | reader = dctx.stream_reader(buffer, read_size=source_read_size, | |||
|
318 | read_across_frames=True) | |||
|
319 | ||||
|
320 | chunks = [] | |||
|
321 | ||||
|
322 | while True: | |||
|
323 | read_amount = read_sizes.draw(strategies.integers(-1, 16384)) | |||
|
324 | chunk = reader.read(read_amount) | |||
|
325 | ||||
|
326 | if not chunk and read_amount: | |||
|
327 | break | |||
|
328 | ||||
|
329 | chunks.append(chunk) | |||
|
330 | ||||
|
331 | self.assertEqual(source.getvalue(), b''.join(chunks)) | |||
|
332 | ||||
102 |
|
333 | |||
103 | @unittest.skipUnless('ZSTD_SLOW_TESTS' in os.environ, 'ZSTD_SLOW_TESTS not set') |
|
334 | @unittest.skipUnless('ZSTD_SLOW_TESTS' in os.environ, 'ZSTD_SLOW_TESTS not set') | |
104 | @make_cffi |
|
335 | @make_cffi | |
@@ -113,7 +344,7 b' class TestDecompressor_stream_writer_fuz' | |||||
113 |
|
344 | |||
114 | dctx = zstd.ZstdDecompressor() |
|
345 | dctx = zstd.ZstdDecompressor() | |
115 | source = io.BytesIO(frame) |
|
346 | source = io.BytesIO(frame) | |
116 |
dest = |
|
347 | dest = NonClosingBytesIO() | |
117 |
|
348 | |||
118 | with dctx.stream_writer(dest, write_size=write_size) as decompressor: |
|
349 | with dctx.stream_writer(dest, write_size=write_size) as decompressor: | |
119 | while True: |
|
350 | while True: | |
@@ -234,10 +465,12 b' class TestDecompressor_multi_decompress_' | |||||
234 | write_checksum=True, |
|
465 | write_checksum=True, | |
235 | **kwargs) |
|
466 | **kwargs) | |
236 |
|
467 | |||
|
468 | if not hasattr(cctx, 'multi_compress_to_buffer'): | |||
|
469 | self.skipTest('multi_compress_to_buffer not available') | |||
|
470 | ||||
237 | frames_buffer = cctx.multi_compress_to_buffer(original, threads=-1) |
|
471 | frames_buffer = cctx.multi_compress_to_buffer(original, threads=-1) | |
238 |
|
472 | |||
239 | dctx = zstd.ZstdDecompressor(**kwargs) |
|
473 | dctx = zstd.ZstdDecompressor(**kwargs) | |
240 |
|
||||
241 | result = dctx.multi_decompress_to_buffer(frames_buffer) |
|
474 | result = dctx.multi_decompress_to_buffer(frames_buffer) | |
242 |
|
475 | |||
243 | self.assertEqual(len(result), len(original)) |
|
476 | self.assertEqual(len(result), len(original)) |
@@ -12,9 +12,9 b' from . common import (' | |||||
12 | @make_cffi |
|
12 | @make_cffi | |
13 | class TestModuleAttributes(unittest.TestCase): |
|
13 | class TestModuleAttributes(unittest.TestCase): | |
14 | def test_version(self): |
|
14 | def test_version(self): | |
15 |
self.assertEqual(zstd.ZSTD_VERSION, (1, 3, |
|
15 | self.assertEqual(zstd.ZSTD_VERSION, (1, 3, 8)) | |
16 |
|
16 | |||
17 |
self.assertEqual(zstd.__version__, '0.1 |
|
17 | self.assertEqual(zstd.__version__, '0.11.0') | |
18 |
|
18 | |||
19 | def test_constants(self): |
|
19 | def test_constants(self): | |
20 | self.assertEqual(zstd.MAX_COMPRESSION_LEVEL, 22) |
|
20 | self.assertEqual(zstd.MAX_COMPRESSION_LEVEL, 22) | |
@@ -29,6 +29,8 b' class TestModuleAttributes(unittest.Test' | |||||
29 | 'DECOMPRESSION_RECOMMENDED_INPUT_SIZE', |
|
29 | 'DECOMPRESSION_RECOMMENDED_INPUT_SIZE', | |
30 | 'DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE', |
|
30 | 'DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE', | |
31 | 'MAGIC_NUMBER', |
|
31 | 'MAGIC_NUMBER', | |
|
32 | 'FLUSH_BLOCK', | |||
|
33 | 'FLUSH_FRAME', | |||
32 | 'BLOCKSIZELOG_MAX', |
|
34 | 'BLOCKSIZELOG_MAX', | |
33 | 'BLOCKSIZE_MAX', |
|
35 | 'BLOCKSIZE_MAX', | |
34 | 'WINDOWLOG_MIN', |
|
36 | 'WINDOWLOG_MIN', | |
@@ -38,6 +40,8 b' class TestModuleAttributes(unittest.Test' | |||||
38 | 'HASHLOG_MIN', |
|
40 | 'HASHLOG_MIN', | |
39 | 'HASHLOG_MAX', |
|
41 | 'HASHLOG_MAX', | |
40 | 'HASHLOG3_MAX', |
|
42 | 'HASHLOG3_MAX', | |
|
43 | 'MINMATCH_MIN', | |||
|
44 | 'MINMATCH_MAX', | |||
41 | 'SEARCHLOG_MIN', |
|
45 | 'SEARCHLOG_MIN', | |
42 | 'SEARCHLOG_MAX', |
|
46 | 'SEARCHLOG_MAX', | |
43 | 'SEARCHLENGTH_MIN', |
|
47 | 'SEARCHLENGTH_MIN', | |
@@ -55,6 +59,7 b' class TestModuleAttributes(unittest.Test' | |||||
55 | 'STRATEGY_BTLAZY2', |
|
59 | 'STRATEGY_BTLAZY2', | |
56 | 'STRATEGY_BTOPT', |
|
60 | 'STRATEGY_BTOPT', | |
57 | 'STRATEGY_BTULTRA', |
|
61 | 'STRATEGY_BTULTRA', | |
|
62 | 'STRATEGY_BTULTRA2', | |||
58 | 'DICT_TYPE_AUTO', |
|
63 | 'DICT_TYPE_AUTO', | |
59 | 'DICT_TYPE_RAWCONTENT', |
|
64 | 'DICT_TYPE_RAWCONTENT', | |
60 | 'DICT_TYPE_FULLDICT', |
|
65 | 'DICT_TYPE_FULLDICT', |
@@ -35,31 +35,31 b" if _module_policy == 'default':" | |||||
35 | from zstd import * |
|
35 | from zstd import * | |
36 | backend = 'cext' |
|
36 | backend = 'cext' | |
37 | elif platform.python_implementation() in ('PyPy',): |
|
37 | elif platform.python_implementation() in ('PyPy',): | |
38 |
from |
|
38 | from .cffi import * | |
39 | backend = 'cffi' |
|
39 | backend = 'cffi' | |
40 | else: |
|
40 | else: | |
41 | try: |
|
41 | try: | |
42 | from zstd import * |
|
42 | from zstd import * | |
43 | backend = 'cext' |
|
43 | backend = 'cext' | |
44 | except ImportError: |
|
44 | except ImportError: | |
45 |
from |
|
45 | from .cffi import * | |
46 | backend = 'cffi' |
|
46 | backend = 'cffi' | |
47 | elif _module_policy == 'cffi_fallback': |
|
47 | elif _module_policy == 'cffi_fallback': | |
48 | try: |
|
48 | try: | |
49 | from zstd import * |
|
49 | from zstd import * | |
50 | backend = 'cext' |
|
50 | backend = 'cext' | |
51 | except ImportError: |
|
51 | except ImportError: | |
52 |
from |
|
52 | from .cffi import * | |
53 | backend = 'cffi' |
|
53 | backend = 'cffi' | |
54 | elif _module_policy == 'cext': |
|
54 | elif _module_policy == 'cext': | |
55 | from zstd import * |
|
55 | from zstd import * | |
56 | backend = 'cext' |
|
56 | backend = 'cext' | |
57 | elif _module_policy == 'cffi': |
|
57 | elif _module_policy == 'cffi': | |
58 |
from |
|
58 | from .cffi import * | |
59 | backend = 'cffi' |
|
59 | backend = 'cffi' | |
60 | else: |
|
60 | else: | |
61 | raise ImportError('unknown module import policy: %s; use default, cffi_fallback, ' |
|
61 | raise ImportError('unknown module import policy: %s; use default, cffi_fallback, ' | |
62 | 'cext, or cffi' % _module_policy) |
|
62 | 'cext, or cffi' % _module_policy) | |
63 |
|
63 | |||
64 | # Keep this in sync with python-zstandard.h. |
|
64 | # Keep this in sync with python-zstandard.h. | |
65 |
__version__ = '0.1 |
|
65 | __version__ = '0.11.0' |
This diff has been collapsed as it changes many lines, (1203 lines changed) Show them Hide them | |||||
@@ -28,6 +28,8 b' from __future__ import absolute_import, ' | |||||
28 | 'train_dictionary', |
|
28 | 'train_dictionary', | |
29 |
|
29 | |||
30 | # Constants. |
|
30 | # Constants. | |
|
31 | 'FLUSH_BLOCK', | |||
|
32 | 'FLUSH_FRAME', | |||
31 | 'COMPRESSOBJ_FLUSH_FINISH', |
|
33 | 'COMPRESSOBJ_FLUSH_FINISH', | |
32 | 'COMPRESSOBJ_FLUSH_BLOCK', |
|
34 | 'COMPRESSOBJ_FLUSH_BLOCK', | |
33 | 'ZSTD_VERSION', |
|
35 | 'ZSTD_VERSION', | |
@@ -49,6 +51,8 b' from __future__ import absolute_import, ' | |||||
49 | 'HASHLOG_MIN', |
|
51 | 'HASHLOG_MIN', | |
50 | 'HASHLOG_MAX', |
|
52 | 'HASHLOG_MAX', | |
51 | 'HASHLOG3_MAX', |
|
53 | 'HASHLOG3_MAX', | |
|
54 | 'MINMATCH_MIN', | |||
|
55 | 'MINMATCH_MAX', | |||
52 | 'SEARCHLOG_MIN', |
|
56 | 'SEARCHLOG_MIN', | |
53 | 'SEARCHLOG_MAX', |
|
57 | 'SEARCHLOG_MAX', | |
54 | 'SEARCHLENGTH_MIN', |
|
58 | 'SEARCHLENGTH_MIN', | |
@@ -66,6 +70,7 b' from __future__ import absolute_import, ' | |||||
66 | 'STRATEGY_BTLAZY2', |
|
70 | 'STRATEGY_BTLAZY2', | |
67 | 'STRATEGY_BTOPT', |
|
71 | 'STRATEGY_BTOPT', | |
68 | 'STRATEGY_BTULTRA', |
|
72 | 'STRATEGY_BTULTRA', | |
|
73 | 'STRATEGY_BTULTRA2', | |||
69 | 'DICT_TYPE_AUTO', |
|
74 | 'DICT_TYPE_AUTO', | |
70 | 'DICT_TYPE_RAWCONTENT', |
|
75 | 'DICT_TYPE_RAWCONTENT', | |
71 | 'DICT_TYPE_FULLDICT', |
|
76 | 'DICT_TYPE_FULLDICT', | |
@@ -114,10 +119,12 b' CHAINLOG_MAX = lib.ZSTD_CHAINLOG_MAX' | |||||
114 | HASHLOG_MIN = lib.ZSTD_HASHLOG_MIN |
|
119 | HASHLOG_MIN = lib.ZSTD_HASHLOG_MIN | |
115 | HASHLOG_MAX = lib.ZSTD_HASHLOG_MAX |
|
120 | HASHLOG_MAX = lib.ZSTD_HASHLOG_MAX | |
116 | HASHLOG3_MAX = lib.ZSTD_HASHLOG3_MAX |
|
121 | HASHLOG3_MAX = lib.ZSTD_HASHLOG3_MAX | |
|
122 | MINMATCH_MIN = lib.ZSTD_MINMATCH_MIN | |||
|
123 | MINMATCH_MAX = lib.ZSTD_MINMATCH_MAX | |||
117 | SEARCHLOG_MIN = lib.ZSTD_SEARCHLOG_MIN |
|
124 | SEARCHLOG_MIN = lib.ZSTD_SEARCHLOG_MIN | |
118 | SEARCHLOG_MAX = lib.ZSTD_SEARCHLOG_MAX |
|
125 | SEARCHLOG_MAX = lib.ZSTD_SEARCHLOG_MAX | |
119 |
SEARCHLENGTH_MIN = lib.ZSTD_ |
|
126 | SEARCHLENGTH_MIN = lib.ZSTD_MINMATCH_MIN | |
120 |
SEARCHLENGTH_MAX = lib.ZSTD_ |
|
127 | SEARCHLENGTH_MAX = lib.ZSTD_MINMATCH_MAX | |
121 | TARGETLENGTH_MIN = lib.ZSTD_TARGETLENGTH_MIN |
|
128 | TARGETLENGTH_MIN = lib.ZSTD_TARGETLENGTH_MIN | |
122 | TARGETLENGTH_MAX = lib.ZSTD_TARGETLENGTH_MAX |
|
129 | TARGETLENGTH_MAX = lib.ZSTD_TARGETLENGTH_MAX | |
123 | LDM_MINMATCH_MIN = lib.ZSTD_LDM_MINMATCH_MIN |
|
130 | LDM_MINMATCH_MIN = lib.ZSTD_LDM_MINMATCH_MIN | |
@@ -132,6 +139,7 b' STRATEGY_LAZY2 = lib.ZSTD_lazy2' | |||||
132 | STRATEGY_BTLAZY2 = lib.ZSTD_btlazy2 |
|
139 | STRATEGY_BTLAZY2 = lib.ZSTD_btlazy2 | |
133 | STRATEGY_BTOPT = lib.ZSTD_btopt |
|
140 | STRATEGY_BTOPT = lib.ZSTD_btopt | |
134 | STRATEGY_BTULTRA = lib.ZSTD_btultra |
|
141 | STRATEGY_BTULTRA = lib.ZSTD_btultra | |
|
142 | STRATEGY_BTULTRA2 = lib.ZSTD_btultra2 | |||
135 |
|
143 | |||
136 | DICT_TYPE_AUTO = lib.ZSTD_dct_auto |
|
144 | DICT_TYPE_AUTO = lib.ZSTD_dct_auto | |
137 | DICT_TYPE_RAWCONTENT = lib.ZSTD_dct_rawContent |
|
145 | DICT_TYPE_RAWCONTENT = lib.ZSTD_dct_rawContent | |
@@ -140,6 +148,9 b' DICT_TYPE_FULLDICT = lib.ZSTD_dct_fullDi' | |||||
140 | FORMAT_ZSTD1 = lib.ZSTD_f_zstd1 |
|
148 | FORMAT_ZSTD1 = lib.ZSTD_f_zstd1 | |
141 | FORMAT_ZSTD1_MAGICLESS = lib.ZSTD_f_zstd1_magicless |
|
149 | FORMAT_ZSTD1_MAGICLESS = lib.ZSTD_f_zstd1_magicless | |
142 |
|
150 | |||
|
151 | FLUSH_BLOCK = 0 | |||
|
152 | FLUSH_FRAME = 1 | |||
|
153 | ||||
143 | COMPRESSOBJ_FLUSH_FINISH = 0 |
|
154 | COMPRESSOBJ_FLUSH_FINISH = 0 | |
144 | COMPRESSOBJ_FLUSH_BLOCK = 1 |
|
155 | COMPRESSOBJ_FLUSH_BLOCK = 1 | |
145 |
|
156 | |||
@@ -182,27 +193,27 b' def _make_cctx_params(params):' | |||||
182 | res = ffi.gc(res, lib.ZSTD_freeCCtxParams) |
|
193 | res = ffi.gc(res, lib.ZSTD_freeCCtxParams) | |
183 |
|
194 | |||
184 | attrs = [ |
|
195 | attrs = [ | |
185 |
(lib.ZSTD_ |
|
196 | (lib.ZSTD_c_format, params.format), | |
186 |
(lib.ZSTD_ |
|
197 | (lib.ZSTD_c_compressionLevel, params.compression_level), | |
187 |
(lib.ZSTD_ |
|
198 | (lib.ZSTD_c_windowLog, params.window_log), | |
188 |
(lib.ZSTD_ |
|
199 | (lib.ZSTD_c_hashLog, params.hash_log), | |
189 |
(lib.ZSTD_ |
|
200 | (lib.ZSTD_c_chainLog, params.chain_log), | |
190 |
(lib.ZSTD_ |
|
201 | (lib.ZSTD_c_searchLog, params.search_log), | |
191 |
(lib.ZSTD_ |
|
202 | (lib.ZSTD_c_minMatch, params.min_match), | |
192 |
(lib.ZSTD_ |
|
203 | (lib.ZSTD_c_targetLength, params.target_length), | |
193 |
(lib.ZSTD_ |
|
204 | (lib.ZSTD_c_strategy, params.compression_strategy), | |
194 |
(lib.ZSTD_ |
|
205 | (lib.ZSTD_c_contentSizeFlag, params.write_content_size), | |
195 |
(lib.ZSTD_ |
|
206 | (lib.ZSTD_c_checksumFlag, params.write_checksum), | |
196 |
(lib.ZSTD_ |
|
207 | (lib.ZSTD_c_dictIDFlag, params.write_dict_id), | |
197 |
(lib.ZSTD_ |
|
208 | (lib.ZSTD_c_nbWorkers, params.threads), | |
198 |
(lib.ZSTD_ |
|
209 | (lib.ZSTD_c_jobSize, params.job_size), | |
199 |
(lib.ZSTD_ |
|
210 | (lib.ZSTD_c_overlapLog, params.overlap_log), | |
200 |
(lib.ZSTD_ |
|
211 | (lib.ZSTD_c_forceMaxWindow, params.force_max_window), | |
201 |
(lib.ZSTD_ |
|
212 | (lib.ZSTD_c_enableLongDistanceMatching, params.enable_ldm), | |
202 |
(lib.ZSTD_ |
|
213 | (lib.ZSTD_c_ldmHashLog, params.ldm_hash_log), | |
203 |
(lib.ZSTD_ |
|
214 | (lib.ZSTD_c_ldmMinMatch, params.ldm_min_match), | |
204 |
(lib.ZSTD_ |
|
215 | (lib.ZSTD_c_ldmBucketSizeLog, params.ldm_bucket_size_log), | |
205 |
(lib.ZSTD_ |
|
216 | (lib.ZSTD_c_ldmHashRateLog, params.ldm_hash_rate_log), | |
206 | ] |
|
217 | ] | |
207 |
|
218 | |||
208 | for param, value in attrs: |
|
219 | for param, value in attrs: | |
@@ -220,7 +231,7 b' class ZstdCompressionParameters(object):' | |||||
220 | 'chain_log': 'chainLog', |
|
231 | 'chain_log': 'chainLog', | |
221 | 'hash_log': 'hashLog', |
|
232 | 'hash_log': 'hashLog', | |
222 | 'search_log': 'searchLog', |
|
233 | 'search_log': 'searchLog', | |
223 |
'min_match': ' |
|
234 | 'min_match': 'minMatch', | |
224 | 'target_length': 'targetLength', |
|
235 | 'target_length': 'targetLength', | |
225 | 'compression_strategy': 'strategy', |
|
236 | 'compression_strategy': 'strategy', | |
226 | } |
|
237 | } | |
@@ -233,41 +244,170 b' class ZstdCompressionParameters(object):' | |||||
233 |
|
244 | |||
234 | def __init__(self, format=0, compression_level=0, window_log=0, hash_log=0, |
|
245 | def __init__(self, format=0, compression_level=0, window_log=0, hash_log=0, | |
235 | chain_log=0, search_log=0, min_match=0, target_length=0, |
|
246 | chain_log=0, search_log=0, min_match=0, target_length=0, | |
236 | compression_strategy=0, write_content_size=1, write_checksum=0, |
|
247 | strategy=-1, compression_strategy=-1, | |
237 |
write_ |
|
248 | write_content_size=1, write_checksum=0, | |
238 |
|
|
249 | write_dict_id=0, job_size=0, overlap_log=-1, | |
239 | ldm_min_match=0, ldm_bucket_size_log=0, ldm_hash_every_log=0, |
|
250 | overlap_size_log=-1, force_max_window=0, enable_ldm=0, | |
240 | threads=0): |
|
251 | ldm_hash_log=0, ldm_min_match=0, ldm_bucket_size_log=0, | |
|
252 | ldm_hash_rate_log=-1, ldm_hash_every_log=-1, threads=0): | |||
|
253 | ||||
|
254 | params = lib.ZSTD_createCCtxParams() | |||
|
255 | if params == ffi.NULL: | |||
|
256 | raise MemoryError() | |||
|
257 | ||||
|
258 | params = ffi.gc(params, lib.ZSTD_freeCCtxParams) | |||
|
259 | ||||
|
260 | self._params = params | |||
241 |
|
261 | |||
242 | if threads < 0: |
|
262 | if threads < 0: | |
243 | threads = _cpu_count() |
|
263 | threads = _cpu_count() | |
244 |
|
264 | |||
245 | self.format = format |
|
265 | # We need to set ZSTD_c_nbWorkers before ZSTD_c_jobSize and ZSTD_c_overlapLog | |
246 | self.compression_level = compression_level |
|
266 | # because setting ZSTD_c_nbWorkers resets the other parameters. | |
247 | self.window_log = window_log |
|
267 | _set_compression_parameter(params, lib.ZSTD_c_nbWorkers, threads) | |
248 | self.hash_log = hash_log |
|
268 | ||
249 | self.chain_log = chain_log |
|
269 | _set_compression_parameter(params, lib.ZSTD_c_format, format) | |
250 | self.search_log = search_log |
|
270 | _set_compression_parameter(params, lib.ZSTD_c_compressionLevel, compression_level) | |
251 | self.min_match = min_match |
|
271 | _set_compression_parameter(params, lib.ZSTD_c_windowLog, window_log) | |
252 | self.target_length = target_length |
|
272 | _set_compression_parameter(params, lib.ZSTD_c_hashLog, hash_log) | |
253 | self.compression_strategy = compression_strategy |
|
273 | _set_compression_parameter(params, lib.ZSTD_c_chainLog, chain_log) | |
254 | self.write_content_size = write_content_size |
|
274 | _set_compression_parameter(params, lib.ZSTD_c_searchLog, search_log) | |
255 | self.write_checksum = write_checksum |
|
275 | _set_compression_parameter(params, lib.ZSTD_c_minMatch, min_match) | |
256 | self.write_dict_id = write_dict_id |
|
276 | _set_compression_parameter(params, lib.ZSTD_c_targetLength, target_length) | |
257 | self.job_size = job_size |
|
277 | ||
258 | self.overlap_size_log = overlap_size_log |
|
278 | if strategy != -1 and compression_strategy != -1: | |
259 | self.force_max_window = force_max_window |
|
279 | raise ValueError('cannot specify both compression_strategy and strategy') | |
260 | self.enable_ldm = enable_ldm |
|
280 | ||
261 | self.ldm_hash_log = ldm_hash_log |
|
281 | if compression_strategy != -1: | |
262 | self.ldm_min_match = ldm_min_match |
|
282 | strategy = compression_strategy | |
263 | self.ldm_bucket_size_log = ldm_bucket_size_log |
|
283 | elif strategy == -1: | |
264 | self.ldm_hash_every_log = ldm_hash_every_log |
|
284 | strategy = 0 | |
265 | self.threads = threads |
|
285 | ||
266 |
|
286 | _set_compression_parameter(params, lib.ZSTD_c_strategy, strategy) | ||
267 | self.params = _make_cctx_params(self) |
|
287 | _set_compression_parameter(params, lib.ZSTD_c_contentSizeFlag, write_content_size) | |
|
288 | _set_compression_parameter(params, lib.ZSTD_c_checksumFlag, write_checksum) | |||
|
289 | _set_compression_parameter(params, lib.ZSTD_c_dictIDFlag, write_dict_id) | |||
|
290 | _set_compression_parameter(params, lib.ZSTD_c_jobSize, job_size) | |||
|
291 | ||||
|
292 | if overlap_log != -1 and overlap_size_log != -1: | |||
|
293 | raise ValueError('cannot specify both overlap_log and overlap_size_log') | |||
|
294 | ||||
|
295 | if overlap_size_log != -1: | |||
|
296 | overlap_log = overlap_size_log | |||
|
297 | elif overlap_log == -1: | |||
|
298 | overlap_log = 0 | |||
|
299 | ||||
|
300 | _set_compression_parameter(params, lib.ZSTD_c_overlapLog, overlap_log) | |||
|
301 | _set_compression_parameter(params, lib.ZSTD_c_forceMaxWindow, force_max_window) | |||
|
302 | _set_compression_parameter(params, lib.ZSTD_c_enableLongDistanceMatching, enable_ldm) | |||
|
303 | _set_compression_parameter(params, lib.ZSTD_c_ldmHashLog, ldm_hash_log) | |||
|
304 | _set_compression_parameter(params, lib.ZSTD_c_ldmMinMatch, ldm_min_match) | |||
|
305 | _set_compression_parameter(params, lib.ZSTD_c_ldmBucketSizeLog, ldm_bucket_size_log) | |||
|
306 | ||||
|
307 | if ldm_hash_rate_log != -1 and ldm_hash_every_log != -1: | |||
|
308 | raise ValueError('cannot specify both ldm_hash_rate_log and ldm_hash_every_log') | |||
|
309 | ||||
|
310 | if ldm_hash_every_log != -1: | |||
|
311 | ldm_hash_rate_log = ldm_hash_every_log | |||
|
312 | elif ldm_hash_rate_log == -1: | |||
|
313 | ldm_hash_rate_log = 0 | |||
|
314 | ||||
|
315 | _set_compression_parameter(params, lib.ZSTD_c_ldmHashRateLog, ldm_hash_rate_log) | |||
|
316 | ||||
|
317 | @property | |||
|
318 | def format(self): | |||
|
319 | return _get_compression_parameter(self._params, lib.ZSTD_c_format) | |||
|
320 | ||||
|
321 | @property | |||
|
322 | def compression_level(self): | |||
|
323 | return _get_compression_parameter(self._params, lib.ZSTD_c_compressionLevel) | |||
|
324 | ||||
|
325 | @property | |||
|
326 | def window_log(self): | |||
|
327 | return _get_compression_parameter(self._params, lib.ZSTD_c_windowLog) | |||
|
328 | ||||
|
329 | @property | |||
|
330 | def hash_log(self): | |||
|
331 | return _get_compression_parameter(self._params, lib.ZSTD_c_hashLog) | |||
|
332 | ||||
|
333 | @property | |||
|
334 | def chain_log(self): | |||
|
335 | return _get_compression_parameter(self._params, lib.ZSTD_c_chainLog) | |||
|
336 | ||||
|
337 | @property | |||
|
338 | def search_log(self): | |||
|
339 | return _get_compression_parameter(self._params, lib.ZSTD_c_searchLog) | |||
|
340 | ||||
|
341 | @property | |||
|
342 | def min_match(self): | |||
|
343 | return _get_compression_parameter(self._params, lib.ZSTD_c_minMatch) | |||
|
344 | ||||
|
345 | @property | |||
|
346 | def target_length(self): | |||
|
347 | return _get_compression_parameter(self._params, lib.ZSTD_c_targetLength) | |||
|
348 | ||||
|
349 | @property | |||
|
350 | def compression_strategy(self): | |||
|
351 | return _get_compression_parameter(self._params, lib.ZSTD_c_strategy) | |||
|
352 | ||||
|
353 | @property | |||
|
354 | def write_content_size(self): | |||
|
355 | return _get_compression_parameter(self._params, lib.ZSTD_c_contentSizeFlag) | |||
|
356 | ||||
|
357 | @property | |||
|
358 | def write_checksum(self): | |||
|
359 | return _get_compression_parameter(self._params, lib.ZSTD_c_checksumFlag) | |||
|
360 | ||||
|
361 | @property | |||
|
362 | def write_dict_id(self): | |||
|
363 | return _get_compression_parameter(self._params, lib.ZSTD_c_dictIDFlag) | |||
|
364 | ||||
|
365 | @property | |||
|
366 | def job_size(self): | |||
|
367 | return _get_compression_parameter(self._params, lib.ZSTD_c_jobSize) | |||
|
368 | ||||
|
369 | @property | |||
|
370 | def overlap_log(self): | |||
|
371 | return _get_compression_parameter(self._params, lib.ZSTD_c_overlapLog) | |||
|
372 | ||||
|
373 | @property | |||
|
374 | def overlap_size_log(self): | |||
|
375 | return self.overlap_log | |||
|
376 | ||||
|
377 | @property | |||
|
378 | def force_max_window(self): | |||
|
379 | return _get_compression_parameter(self._params, lib.ZSTD_c_forceMaxWindow) | |||
|
380 | ||||
|
381 | @property | |||
|
382 | def enable_ldm(self): | |||
|
383 | return _get_compression_parameter(self._params, lib.ZSTD_c_enableLongDistanceMatching) | |||
|
384 | ||||
|
385 | @property | |||
|
386 | def ldm_hash_log(self): | |||
|
387 | return _get_compression_parameter(self._params, lib.ZSTD_c_ldmHashLog) | |||
|
388 | ||||
|
389 | @property | |||
|
390 | def ldm_min_match(self): | |||
|
391 | return _get_compression_parameter(self._params, lib.ZSTD_c_ldmMinMatch) | |||
|
392 | ||||
|
393 | @property | |||
|
394 | def ldm_bucket_size_log(self): | |||
|
395 | return _get_compression_parameter(self._params, lib.ZSTD_c_ldmBucketSizeLog) | |||
|
396 | ||||
|
397 | @property | |||
|
398 | def ldm_hash_rate_log(self): | |||
|
399 | return _get_compression_parameter(self._params, lib.ZSTD_c_ldmHashRateLog) | |||
|
400 | ||||
|
401 | @property | |||
|
402 | def ldm_hash_every_log(self): | |||
|
403 | return self.ldm_hash_rate_log | |||
|
404 | ||||
|
405 | @property | |||
|
406 | def threads(self): | |||
|
407 | return _get_compression_parameter(self._params, lib.ZSTD_c_nbWorkers) | |||
268 |
|
408 | |||
269 | def estimated_compression_context_size(self): |
|
409 | def estimated_compression_context_size(self): | |
270 | return lib.ZSTD_estimateCCtxSize_usingCCtxParams(self.params) |
|
410 | return lib.ZSTD_estimateCCtxSize_usingCCtxParams(self._params) | |
271 |
|
411 | |||
272 | CompressionParameters = ZstdCompressionParameters |
|
412 | CompressionParameters = ZstdCompressionParameters | |
273 |
|
413 | |||
@@ -276,31 +416,53 b' def estimate_decompression_context_size(' | |||||
276 |
|
416 | |||
277 |
|
417 | |||
278 | def _set_compression_parameter(params, param, value): |
|
418 | def _set_compression_parameter(params, param, value): | |
279 | zresult = lib.ZSTD_CCtxParam_setParameter(params, param, |
|
419 | zresult = lib.ZSTD_CCtxParam_setParameter(params, param, value) | |
280 | ffi.cast('unsigned', value)) |
|
|||
281 | if lib.ZSTD_isError(zresult): |
|
420 | if lib.ZSTD_isError(zresult): | |
282 | raise ZstdError('unable to set compression context parameter: %s' % |
|
421 | raise ZstdError('unable to set compression context parameter: %s' % | |
283 | _zstd_error(zresult)) |
|
422 | _zstd_error(zresult)) | |
284 |
|
423 | |||
|
424 | ||||
|
425 | def _get_compression_parameter(params, param): | |||
|
426 | result = ffi.new('int *') | |||
|
427 | ||||
|
428 | zresult = lib.ZSTD_CCtxParam_getParameter(params, param, result) | |||
|
429 | if lib.ZSTD_isError(zresult): | |||
|
430 | raise ZstdError('unable to get compression context parameter: %s' % | |||
|
431 | _zstd_error(zresult)) | |||
|
432 | ||||
|
433 | return result[0] | |||
|
434 | ||||
|
435 | ||||
285 | class ZstdCompressionWriter(object): |
|
436 | class ZstdCompressionWriter(object): | |
286 |
def __init__(self, compressor, writer, source_size, write_size |
|
437 | def __init__(self, compressor, writer, source_size, write_size, | |
|
438 | write_return_read): | |||
287 | self._compressor = compressor |
|
439 | self._compressor = compressor | |
288 | self._writer = writer |
|
440 | self._writer = writer | |
289 | self._source_size = source_size |
|
|||
290 | self._write_size = write_size |
|
441 | self._write_size = write_size | |
|
442 | self._write_return_read = bool(write_return_read) | |||
291 | self._entered = False |
|
443 | self._entered = False | |
|
444 | self._closed = False | |||
292 | self._bytes_compressed = 0 |
|
445 | self._bytes_compressed = 0 | |
293 |
|
446 | |||
294 | def __enter__(self): |
|
447 | self._dst_buffer = ffi.new('char[]', write_size) | |
295 | if self._entered: |
|
448 | self._out_buffer = ffi.new('ZSTD_outBuffer *') | |
296 | raise ZstdError('cannot __enter__ multiple times') |
|
449 | self._out_buffer.dst = self._dst_buffer | |
297 |
|
450 | self._out_buffer.size = len(self._dst_buffer) | ||
298 | zresult = lib.ZSTD_CCtx_setPledgedSrcSize(self._compressor._cctx, |
|
451 | self._out_buffer.pos = 0 | |
299 | self._source_size) |
|
452 | ||
|
453 | zresult = lib.ZSTD_CCtx_setPledgedSrcSize(compressor._cctx, | |||
|
454 | source_size) | |||
300 | if lib.ZSTD_isError(zresult): |
|
455 | if lib.ZSTD_isError(zresult): | |
301 | raise ZstdError('error setting source size: %s' % |
|
456 | raise ZstdError('error setting source size: %s' % | |
302 | _zstd_error(zresult)) |
|
457 | _zstd_error(zresult)) | |
303 |
|
458 | |||
|
459 | def __enter__(self): | |||
|
460 | if self._closed: | |||
|
461 | raise ValueError('stream is closed') | |||
|
462 | ||||
|
463 | if self._entered: | |||
|
464 | raise ZstdError('cannot __enter__ multiple times') | |||
|
465 | ||||
304 | self._entered = True |
|
466 | self._entered = True | |
305 | return self |
|
467 | return self | |
306 |
|
468 | |||
@@ -308,50 +470,79 b' class ZstdCompressionWriter(object):' | |||||
308 | self._entered = False |
|
470 | self._entered = False | |
309 |
|
471 | |||
310 | if not exc_type and not exc_value and not exc_tb: |
|
472 | if not exc_type and not exc_value and not exc_tb: | |
311 | dst_buffer = ffi.new('char[]', self._write_size) |
|
473 | self.close() | |
312 |
|
||||
313 | out_buffer = ffi.new('ZSTD_outBuffer *') |
|
|||
314 | in_buffer = ffi.new('ZSTD_inBuffer *') |
|
|||
315 |
|
||||
316 | out_buffer.dst = dst_buffer |
|
|||
317 | out_buffer.size = len(dst_buffer) |
|
|||
318 | out_buffer.pos = 0 |
|
|||
319 |
|
||||
320 | in_buffer.src = ffi.NULL |
|
|||
321 | in_buffer.size = 0 |
|
|||
322 | in_buffer.pos = 0 |
|
|||
323 |
|
||||
324 | while True: |
|
|||
325 | zresult = lib.ZSTD_compress_generic(self._compressor._cctx, |
|
|||
326 | out_buffer, in_buffer, |
|
|||
327 | lib.ZSTD_e_end) |
|
|||
328 |
|
||||
329 | if lib.ZSTD_isError(zresult): |
|
|||
330 | raise ZstdError('error ending compression stream: %s' % |
|
|||
331 | _zstd_error(zresult)) |
|
|||
332 |
|
||||
333 | if out_buffer.pos: |
|
|||
334 | self._writer.write(ffi.buffer(out_buffer.dst, out_buffer.pos)[:]) |
|
|||
335 | out_buffer.pos = 0 |
|
|||
336 |
|
||||
337 | if zresult == 0: |
|
|||
338 | break |
|
|||
339 |
|
474 | |||
340 | self._compressor = None |
|
475 | self._compressor = None | |
341 |
|
476 | |||
342 | return False |
|
477 | return False | |
343 |
|
478 | |||
344 | def memory_size(self): |
|
479 | def memory_size(self): | |
345 | if not self._entered: |
|
|||
346 | raise ZstdError('cannot determine size of an inactive compressor; ' |
|
|||
347 | 'call when a context manager is active') |
|
|||
348 |
|
||||
349 | return lib.ZSTD_sizeof_CCtx(self._compressor._cctx) |
|
480 | return lib.ZSTD_sizeof_CCtx(self._compressor._cctx) | |
350 |
|
481 | |||
|
482 | def fileno(self): | |||
|
483 | f = getattr(self._writer, 'fileno', None) | |||
|
484 | if f: | |||
|
485 | return f() | |||
|
486 | else: | |||
|
487 | raise OSError('fileno not available on underlying writer') | |||
|
488 | ||||
|
489 | def close(self): | |||
|
490 | if self._closed: | |||
|
491 | return | |||
|
492 | ||||
|
493 | try: | |||
|
494 | self.flush(FLUSH_FRAME) | |||
|
495 | finally: | |||
|
496 | self._closed = True | |||
|
497 | ||||
|
498 | # Call close() on underlying stream as well. | |||
|
499 | f = getattr(self._writer, 'close', None) | |||
|
500 | if f: | |||
|
501 | f() | |||
|
502 | ||||
|
503 | @property | |||
|
504 | def closed(self): | |||
|
505 | return self._closed | |||
|
506 | ||||
|
507 | def isatty(self): | |||
|
508 | return False | |||
|
509 | ||||
|
510 | def readable(self): | |||
|
511 | return False | |||
|
512 | ||||
|
513 | def readline(self, size=-1): | |||
|
514 | raise io.UnsupportedOperation() | |||
|
515 | ||||
|
516 | def readlines(self, hint=-1): | |||
|
517 | raise io.UnsupportedOperation() | |||
|
518 | ||||
|
519 | def seek(self, offset, whence=None): | |||
|
520 | raise io.UnsupportedOperation() | |||
|
521 | ||||
|
522 | def seekable(self): | |||
|
523 | return False | |||
|
524 | ||||
|
525 | def truncate(self, size=None): | |||
|
526 | raise io.UnsupportedOperation() | |||
|
527 | ||||
|
528 | def writable(self): | |||
|
529 | return True | |||
|
530 | ||||
|
531 | def writelines(self, lines): | |||
|
532 | raise NotImplementedError('writelines() is not yet implemented') | |||
|
533 | ||||
|
534 | def read(self, size=-1): | |||
|
535 | raise io.UnsupportedOperation() | |||
|
536 | ||||
|
537 | def readall(self): | |||
|
538 | raise io.UnsupportedOperation() | |||
|
539 | ||||
|
540 | def readinto(self, b): | |||
|
541 | raise io.UnsupportedOperation() | |||
|
542 | ||||
351 | def write(self, data): |
|
543 | def write(self, data): | |
352 |
if |
|
544 | if self._closed: | |
353 | raise ZstdError('write() must be called from an active context ' |
|
545 | raise ValueError('stream is closed') | |
354 | 'manager') |
|
|||
355 |
|
546 | |||
356 | total_write = 0 |
|
547 | total_write = 0 | |
357 |
|
548 | |||
@@ -362,16 +553,13 b' class ZstdCompressionWriter(object):' | |||||
362 | in_buffer.size = len(data_buffer) |
|
553 | in_buffer.size = len(data_buffer) | |
363 | in_buffer.pos = 0 |
|
554 | in_buffer.pos = 0 | |
364 |
|
555 | |||
365 |
out_buffer = f |
|
556 | out_buffer = self._out_buffer | |
366 | dst_buffer = ffi.new('char[]', self._write_size) |
|
|||
367 | out_buffer.dst = dst_buffer |
|
|||
368 | out_buffer.size = self._write_size |
|
|||
369 | out_buffer.pos = 0 |
|
557 | out_buffer.pos = 0 | |
370 |
|
558 | |||
371 | while in_buffer.pos < in_buffer.size: |
|
559 | while in_buffer.pos < in_buffer.size: | |
372 |
zresult = lib.ZSTD_compress |
|
560 | zresult = lib.ZSTD_compressStream2(self._compressor._cctx, | |
373 |
|
|
561 | out_buffer, in_buffer, | |
374 |
|
|
562 | lib.ZSTD_e_continue) | |
375 | if lib.ZSTD_isError(zresult): |
|
563 | if lib.ZSTD_isError(zresult): | |
376 | raise ZstdError('zstd compress error: %s' % |
|
564 | raise ZstdError('zstd compress error: %s' % | |
377 | _zstd_error(zresult)) |
|
565 | _zstd_error(zresult)) | |
@@ -382,18 +570,25 b' class ZstdCompressionWriter(object):' | |||||
382 | self._bytes_compressed += out_buffer.pos |
|
570 | self._bytes_compressed += out_buffer.pos | |
383 | out_buffer.pos = 0 |
|
571 | out_buffer.pos = 0 | |
384 |
|
572 | |||
385 | return total_write |
|
573 | if self._write_return_read: | |
386 |
|
574 | return in_buffer.pos | ||
387 | def flush(self): |
|
575 | else: | |
388 | if not self._entered: |
|
576 | return total_write | |
389 | raise ZstdError('flush must be called from an active context manager') |
|
577 | ||
|
578 | def flush(self, flush_mode=FLUSH_BLOCK): | |||
|
579 | if flush_mode == FLUSH_BLOCK: | |||
|
580 | flush = lib.ZSTD_e_flush | |||
|
581 | elif flush_mode == FLUSH_FRAME: | |||
|
582 | flush = lib.ZSTD_e_end | |||
|
583 | else: | |||
|
584 | raise ValueError('unknown flush_mode: %r' % flush_mode) | |||
|
585 | ||||
|
586 | if self._closed: | |||
|
587 | raise ValueError('stream is closed') | |||
390 |
|
588 | |||
391 | total_write = 0 |
|
589 | total_write = 0 | |
392 |
|
590 | |||
393 |
out_buffer = f |
|
591 | out_buffer = self._out_buffer | |
394 | dst_buffer = ffi.new('char[]', self._write_size) |
|
|||
395 | out_buffer.dst = dst_buffer |
|
|||
396 | out_buffer.size = self._write_size |
|
|||
397 | out_buffer.pos = 0 |
|
592 | out_buffer.pos = 0 | |
398 |
|
593 | |||
399 | in_buffer = ffi.new('ZSTD_inBuffer *') |
|
594 | in_buffer = ffi.new('ZSTD_inBuffer *') | |
@@ -402,9 +597,9 b' class ZstdCompressionWriter(object):' | |||||
402 | in_buffer.pos = 0 |
|
597 | in_buffer.pos = 0 | |
403 |
|
598 | |||
404 | while True: |
|
599 | while True: | |
405 |
zresult = lib.ZSTD_compress |
|
600 | zresult = lib.ZSTD_compressStream2(self._compressor._cctx, | |
406 |
|
|
601 | out_buffer, in_buffer, | |
407 |
|
|
602 | flush) | |
408 | if lib.ZSTD_isError(zresult): |
|
603 | if lib.ZSTD_isError(zresult): | |
409 | raise ZstdError('zstd compress error: %s' % |
|
604 | raise ZstdError('zstd compress error: %s' % | |
410 | _zstd_error(zresult)) |
|
605 | _zstd_error(zresult)) | |
@@ -438,10 +633,10 b' class ZstdCompressionObj(object):' | |||||
438 | chunks = [] |
|
633 | chunks = [] | |
439 |
|
634 | |||
440 | while source.pos < len(data): |
|
635 | while source.pos < len(data): | |
441 |
zresult = lib.ZSTD_compress |
|
636 | zresult = lib.ZSTD_compressStream2(self._compressor._cctx, | |
442 |
|
|
637 | self._out, | |
443 |
|
|
638 | source, | |
444 |
|
|
639 | lib.ZSTD_e_continue) | |
445 | if lib.ZSTD_isError(zresult): |
|
640 | if lib.ZSTD_isError(zresult): | |
446 | raise ZstdError('zstd compress error: %s' % |
|
641 | raise ZstdError('zstd compress error: %s' % | |
447 | _zstd_error(zresult)) |
|
642 | _zstd_error(zresult)) | |
@@ -477,10 +672,10 b' class ZstdCompressionObj(object):' | |||||
477 | chunks = [] |
|
672 | chunks = [] | |
478 |
|
673 | |||
479 | while True: |
|
674 | while True: | |
480 |
zresult = lib.ZSTD_compress |
|
675 | zresult = lib.ZSTD_compressStream2(self._compressor._cctx, | |
481 |
|
|
676 | self._out, | |
482 |
|
|
677 | in_buffer, | |
483 |
|
|
678 | z_flush_mode) | |
484 | if lib.ZSTD_isError(zresult): |
|
679 | if lib.ZSTD_isError(zresult): | |
485 | raise ZstdError('error ending compression stream: %s' % |
|
680 | raise ZstdError('error ending compression stream: %s' % | |
486 | _zstd_error(zresult)) |
|
681 | _zstd_error(zresult)) | |
@@ -528,10 +723,10 b' class ZstdCompressionChunker(object):' | |||||
528 | self._in.pos = 0 |
|
723 | self._in.pos = 0 | |
529 |
|
724 | |||
530 | while self._in.pos < self._in.size: |
|
725 | while self._in.pos < self._in.size: | |
531 |
zresult = lib.ZSTD_compress |
|
726 | zresult = lib.ZSTD_compressStream2(self._compressor._cctx, | |
532 |
|
|
727 | self._out, | |
533 |
|
|
728 | self._in, | |
534 |
|
|
729 | lib.ZSTD_e_continue) | |
535 |
|
730 | |||
536 | if self._in.pos == self._in.size: |
|
731 | if self._in.pos == self._in.size: | |
537 | self._in.src = ffi.NULL |
|
732 | self._in.src = ffi.NULL | |
@@ -555,9 +750,9 b' class ZstdCompressionChunker(object):' | |||||
555 | 'previous operation') |
|
750 | 'previous operation') | |
556 |
|
751 | |||
557 | while True: |
|
752 | while True: | |
558 |
zresult = lib.ZSTD_compress |
|
753 | zresult = lib.ZSTD_compressStream2(self._compressor._cctx, | |
559 |
|
|
754 | self._out, self._in, | |
560 |
|
|
755 | lib.ZSTD_e_flush) | |
561 | if lib.ZSTD_isError(zresult): |
|
756 | if lib.ZSTD_isError(zresult): | |
562 | raise ZstdError('zstd compress error: %s' % _zstd_error(zresult)) |
|
757 | raise ZstdError('zstd compress error: %s' % _zstd_error(zresult)) | |
563 |
|
758 | |||
@@ -577,9 +772,9 b' class ZstdCompressionChunker(object):' | |||||
577 | 'previous operation') |
|
772 | 'previous operation') | |
578 |
|
773 | |||
579 | while True: |
|
774 | while True: | |
580 |
zresult = lib.ZSTD_compress |
|
775 | zresult = lib.ZSTD_compressStream2(self._compressor._cctx, | |
581 |
|
|
776 | self._out, self._in, | |
582 |
|
|
777 | lib.ZSTD_e_end) | |
583 | if lib.ZSTD_isError(zresult): |
|
778 | if lib.ZSTD_isError(zresult): | |
584 | raise ZstdError('zstd compress error: %s' % _zstd_error(zresult)) |
|
779 | raise ZstdError('zstd compress error: %s' % _zstd_error(zresult)) | |
585 |
|
780 | |||
@@ -592,7 +787,7 b' class ZstdCompressionChunker(object):' | |||||
592 | return |
|
787 | return | |
593 |
|
788 | |||
594 |
|
789 | |||
595 | class CompressionReader(object): |
|
790 | class ZstdCompressionReader(object): | |
596 | def __init__(self, compressor, source, read_size): |
|
791 | def __init__(self, compressor, source, read_size): | |
597 | self._compressor = compressor |
|
792 | self._compressor = compressor | |
598 | self._source = source |
|
793 | self._source = source | |
@@ -661,7 +856,16 b' class CompressionReader(object):' | |||||
661 | return self._bytes_compressed |
|
856 | return self._bytes_compressed | |
662 |
|
857 | |||
663 | def readall(self): |
|
858 | def readall(self): | |
664 | raise NotImplementedError() |
|
859 | chunks = [] | |
|
860 | ||||
|
861 | while True: | |||
|
862 | chunk = self.read(1048576) | |||
|
863 | if not chunk: | |||
|
864 | break | |||
|
865 | ||||
|
866 | chunks.append(chunk) | |||
|
867 | ||||
|
868 | return b''.join(chunks) | |||
665 |
|
869 | |||
666 | def __iter__(self): |
|
870 | def __iter__(self): | |
667 | raise io.UnsupportedOperation() |
|
871 | raise io.UnsupportedOperation() | |
@@ -671,16 +875,67 b' class CompressionReader(object):' | |||||
671 |
|
875 | |||
672 | next = __next__ |
|
876 | next = __next__ | |
673 |
|
877 | |||
|
878 | def _read_input(self): | |||
|
879 | if self._finished_input: | |||
|
880 | return | |||
|
881 | ||||
|
882 | if hasattr(self._source, 'read'): | |||
|
883 | data = self._source.read(self._read_size) | |||
|
884 | ||||
|
885 | if not data: | |||
|
886 | self._finished_input = True | |||
|
887 | return | |||
|
888 | ||||
|
889 | self._source_buffer = ffi.from_buffer(data) | |||
|
890 | self._in_buffer.src = self._source_buffer | |||
|
891 | self._in_buffer.size = len(self._source_buffer) | |||
|
892 | self._in_buffer.pos = 0 | |||
|
893 | else: | |||
|
894 | self._source_buffer = ffi.from_buffer(self._source) | |||
|
895 | self._in_buffer.src = self._source_buffer | |||
|
896 | self._in_buffer.size = len(self._source_buffer) | |||
|
897 | self._in_buffer.pos = 0 | |||
|
898 | ||||
|
899 | def _compress_into_buffer(self, out_buffer): | |||
|
900 | if self._in_buffer.pos >= self._in_buffer.size: | |||
|
901 | return | |||
|
902 | ||||
|
903 | old_pos = out_buffer.pos | |||
|
904 | ||||
|
905 | zresult = lib.ZSTD_compressStream2(self._compressor._cctx, | |||
|
906 | out_buffer, self._in_buffer, | |||
|
907 | lib.ZSTD_e_continue) | |||
|
908 | ||||
|
909 | self._bytes_compressed += out_buffer.pos - old_pos | |||
|
910 | ||||
|
911 | if self._in_buffer.pos == self._in_buffer.size: | |||
|
912 | self._in_buffer.src = ffi.NULL | |||
|
913 | self._in_buffer.pos = 0 | |||
|
914 | self._in_buffer.size = 0 | |||
|
915 | self._source_buffer = None | |||
|
916 | ||||
|
917 | if not hasattr(self._source, 'read'): | |||
|
918 | self._finished_input = True | |||
|
919 | ||||
|
920 | if lib.ZSTD_isError(zresult): | |||
|
921 | raise ZstdError('zstd compress error: %s', | |||
|
922 | _zstd_error(zresult)) | |||
|
923 | ||||
|
924 | return out_buffer.pos and out_buffer.pos == out_buffer.size | |||
|
925 | ||||
674 | def read(self, size=-1): |
|
926 | def read(self, size=-1): | |
675 | if self._closed: |
|
927 | if self._closed: | |
676 | raise ValueError('stream is closed') |
|
928 | raise ValueError('stream is closed') | |
677 |
|
929 | |||
678 | if self._finished_output: |
|
930 | if size < -1: | |
|
931 | raise ValueError('cannot read negative amounts less than -1') | |||
|
932 | ||||
|
933 | if size == -1: | |||
|
934 | return self.readall() | |||
|
935 | ||||
|
936 | if self._finished_output or size == 0: | |||
679 | return b'' |
|
937 | return b'' | |
680 |
|
938 | |||
681 | if size < 1: |
|
|||
682 | raise ValueError('cannot read negative or size 0 amounts') |
|
|||
683 |
|
||||
684 | # Need a dedicated ref to dest buffer otherwise it gets collected. |
|
939 | # Need a dedicated ref to dest buffer otherwise it gets collected. | |
685 | dst_buffer = ffi.new('char[]', size) |
|
940 | dst_buffer = ffi.new('char[]', size) | |
686 | out_buffer = ffi.new('ZSTD_outBuffer *') |
|
941 | out_buffer = ffi.new('ZSTD_outBuffer *') | |
@@ -688,71 +943,21 b' class CompressionReader(object):' | |||||
688 | out_buffer.size = size |
|
943 | out_buffer.size = size | |
689 | out_buffer.pos = 0 |
|
944 | out_buffer.pos = 0 | |
690 |
|
945 | |||
691 |
|
|
946 | if self._compress_into_buffer(out_buffer): | |
692 | if self._in_buffer.pos >= self._in_buffer.size: |
|
947 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] | |
693 | return |
|
|||
694 |
|
||||
695 | old_pos = out_buffer.pos |
|
|||
696 |
|
||||
697 | zresult = lib.ZSTD_compress_generic(self._compressor._cctx, |
|
|||
698 | out_buffer, self._in_buffer, |
|
|||
699 | lib.ZSTD_e_continue) |
|
|||
700 |
|
||||
701 | self._bytes_compressed += out_buffer.pos - old_pos |
|
|||
702 |
|
||||
703 | if self._in_buffer.pos == self._in_buffer.size: |
|
|||
704 | self._in_buffer.src = ffi.NULL |
|
|||
705 | self._in_buffer.pos = 0 |
|
|||
706 | self._in_buffer.size = 0 |
|
|||
707 | self._source_buffer = None |
|
|||
708 |
|
||||
709 | if not hasattr(self._source, 'read'): |
|
|||
710 | self._finished_input = True |
|
|||
711 |
|
||||
712 | if lib.ZSTD_isError(zresult): |
|
|||
713 | raise ZstdError('zstd compress error: %s', |
|
|||
714 | _zstd_error(zresult)) |
|
|||
715 |
|
||||
716 | if out_buffer.pos and out_buffer.pos == out_buffer.size: |
|
|||
717 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] |
|
|||
718 |
|
||||
719 | def get_input(): |
|
|||
720 | if self._finished_input: |
|
|||
721 | return |
|
|||
722 |
|
||||
723 | if hasattr(self._source, 'read'): |
|
|||
724 | data = self._source.read(self._read_size) |
|
|||
725 |
|
||||
726 | if not data: |
|
|||
727 | self._finished_input = True |
|
|||
728 | return |
|
|||
729 |
|
||||
730 | self._source_buffer = ffi.from_buffer(data) |
|
|||
731 | self._in_buffer.src = self._source_buffer |
|
|||
732 | self._in_buffer.size = len(self._source_buffer) |
|
|||
733 | self._in_buffer.pos = 0 |
|
|||
734 | else: |
|
|||
735 | self._source_buffer = ffi.from_buffer(self._source) |
|
|||
736 | self._in_buffer.src = self._source_buffer |
|
|||
737 | self._in_buffer.size = len(self._source_buffer) |
|
|||
738 | self._in_buffer.pos = 0 |
|
|||
739 |
|
||||
740 | result = compress_input() |
|
|||
741 | if result: |
|
|||
742 | return result |
|
|||
743 |
|
948 | |||
744 | while not self._finished_input: |
|
949 | while not self._finished_input: | |
745 |
|
|
950 | self._read_input() | |
746 | result = compress_input() |
|
951 | ||
747 | if result: |
|
952 | if self._compress_into_buffer(out_buffer): | |
748 | return result |
|
953 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] | |
749 |
|
954 | |||
750 | # EOF |
|
955 | # EOF | |
751 | old_pos = out_buffer.pos |
|
956 | old_pos = out_buffer.pos | |
752 |
|
957 | |||
753 |
zresult = lib.ZSTD_compress |
|
958 | zresult = lib.ZSTD_compressStream2(self._compressor._cctx, | |
754 |
|
|
959 | out_buffer, self._in_buffer, | |
755 |
|
|
960 | lib.ZSTD_e_end) | |
756 |
|
961 | |||
757 | self._bytes_compressed += out_buffer.pos - old_pos |
|
962 | self._bytes_compressed += out_buffer.pos - old_pos | |
758 |
|
963 | |||
@@ -765,6 +970,159 b' class CompressionReader(object):' | |||||
765 |
|
970 | |||
766 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] |
|
971 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] | |
767 |
|
972 | |||
|
973 | def read1(self, size=-1): | |||
|
974 | if self._closed: | |||
|
975 | raise ValueError('stream is closed') | |||
|
976 | ||||
|
977 | if size < -1: | |||
|
978 | raise ValueError('cannot read negative amounts less than -1') | |||
|
979 | ||||
|
980 | if self._finished_output or size == 0: | |||
|
981 | return b'' | |||
|
982 | ||||
|
983 | # -1 returns arbitrary number of bytes. | |||
|
984 | if size == -1: | |||
|
985 | size = COMPRESSION_RECOMMENDED_OUTPUT_SIZE | |||
|
986 | ||||
|
987 | dst_buffer = ffi.new('char[]', size) | |||
|
988 | out_buffer = ffi.new('ZSTD_outBuffer *') | |||
|
989 | out_buffer.dst = dst_buffer | |||
|
990 | out_buffer.size = size | |||
|
991 | out_buffer.pos = 0 | |||
|
992 | ||||
|
993 | # read1() dictates that we can perform at most 1 call to the | |||
|
994 | # underlying stream to get input. However, we can't satisfy this | |||
|
995 | # restriction with compression because not all input generates output. | |||
|
996 | # It is possible to perform a block flush in order to ensure output. | |||
|
997 | # But this may not be desirable behavior. So we allow multiple read() | |||
|
998 | # to the underlying stream. But unlike read(), we stop once we have | |||
|
999 | # any output. | |||
|
1000 | ||||
|
1001 | self._compress_into_buffer(out_buffer) | |||
|
1002 | if out_buffer.pos: | |||
|
1003 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] | |||
|
1004 | ||||
|
1005 | while not self._finished_input: | |||
|
1006 | self._read_input() | |||
|
1007 | ||||
|
1008 | # If we've filled the output buffer, return immediately. | |||
|
1009 | if self._compress_into_buffer(out_buffer): | |||
|
1010 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] | |||
|
1011 | ||||
|
1012 | # If we've populated the output buffer and we're not at EOF, | |||
|
1013 | # also return, as we've satisfied the read1() limits. | |||
|
1014 | if out_buffer.pos and not self._finished_input: | |||
|
1015 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] | |||
|
1016 | ||||
|
1017 | # Else if we're at EOS and we have room left in the buffer, | |||
|
1018 | # fall through to below and try to add more data to the output. | |||
|
1019 | ||||
|
1020 | # EOF. | |||
|
1021 | old_pos = out_buffer.pos | |||
|
1022 | ||||
|
1023 | zresult = lib.ZSTD_compressStream2(self._compressor._cctx, | |||
|
1024 | out_buffer, self._in_buffer, | |||
|
1025 | lib.ZSTD_e_end) | |||
|
1026 | ||||
|
1027 | self._bytes_compressed += out_buffer.pos - old_pos | |||
|
1028 | ||||
|
1029 | if lib.ZSTD_isError(zresult): | |||
|
1030 | raise ZstdError('error ending compression stream: %s' % | |||
|
1031 | _zstd_error(zresult)) | |||
|
1032 | ||||
|
1033 | if zresult == 0: | |||
|
1034 | self._finished_output = True | |||
|
1035 | ||||
|
1036 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] | |||
|
1037 | ||||
|
1038 | def readinto(self, b): | |||
|
1039 | if self._closed: | |||
|
1040 | raise ValueError('stream is closed') | |||
|
1041 | ||||
|
1042 | if self._finished_output: | |||
|
1043 | return 0 | |||
|
1044 | ||||
|
1045 | # TODO use writable=True once we require CFFI >= 1.12. | |||
|
1046 | dest_buffer = ffi.from_buffer(b) | |||
|
1047 | ffi.memmove(b, b'', 0) | |||
|
1048 | out_buffer = ffi.new('ZSTD_outBuffer *') | |||
|
1049 | out_buffer.dst = dest_buffer | |||
|
1050 | out_buffer.size = len(dest_buffer) | |||
|
1051 | out_buffer.pos = 0 | |||
|
1052 | ||||
|
1053 | if self._compress_into_buffer(out_buffer): | |||
|
1054 | return out_buffer.pos | |||
|
1055 | ||||
|
1056 | while not self._finished_input: | |||
|
1057 | self._read_input() | |||
|
1058 | if self._compress_into_buffer(out_buffer): | |||
|
1059 | return out_buffer.pos | |||
|
1060 | ||||
|
1061 | # EOF. | |||
|
1062 | old_pos = out_buffer.pos | |||
|
1063 | zresult = lib.ZSTD_compressStream2(self._compressor._cctx, | |||
|
1064 | out_buffer, self._in_buffer, | |||
|
1065 | lib.ZSTD_e_end) | |||
|
1066 | ||||
|
1067 | self._bytes_compressed += out_buffer.pos - old_pos | |||
|
1068 | ||||
|
1069 | if lib.ZSTD_isError(zresult): | |||
|
1070 | raise ZstdError('error ending compression stream: %s', | |||
|
1071 | _zstd_error(zresult)) | |||
|
1072 | ||||
|
1073 | if zresult == 0: | |||
|
1074 | self._finished_output = True | |||
|
1075 | ||||
|
1076 | return out_buffer.pos | |||
|
1077 | ||||
|
1078 | def readinto1(self, b): | |||
|
1079 | if self._closed: | |||
|
1080 | raise ValueError('stream is closed') | |||
|
1081 | ||||
|
1082 | if self._finished_output: | |||
|
1083 | return 0 | |||
|
1084 | ||||
|
1085 | # TODO use writable=True once we require CFFI >= 1.12. | |||
|
1086 | dest_buffer = ffi.from_buffer(b) | |||
|
1087 | ffi.memmove(b, b'', 0) | |||
|
1088 | ||||
|
1089 | out_buffer = ffi.new('ZSTD_outBuffer *') | |||
|
1090 | out_buffer.dst = dest_buffer | |||
|
1091 | out_buffer.size = len(dest_buffer) | |||
|
1092 | out_buffer.pos = 0 | |||
|
1093 | ||||
|
1094 | self._compress_into_buffer(out_buffer) | |||
|
1095 | if out_buffer.pos: | |||
|
1096 | return out_buffer.pos | |||
|
1097 | ||||
|
1098 | while not self._finished_input: | |||
|
1099 | self._read_input() | |||
|
1100 | ||||
|
1101 | if self._compress_into_buffer(out_buffer): | |||
|
1102 | return out_buffer.pos | |||
|
1103 | ||||
|
1104 | if out_buffer.pos and not self._finished_input: | |||
|
1105 | return out_buffer.pos | |||
|
1106 | ||||
|
1107 | # EOF. | |||
|
1108 | old_pos = out_buffer.pos | |||
|
1109 | ||||
|
1110 | zresult = lib.ZSTD_compressStream2(self._compressor._cctx, | |||
|
1111 | out_buffer, self._in_buffer, | |||
|
1112 | lib.ZSTD_e_end) | |||
|
1113 | ||||
|
1114 | self._bytes_compressed += out_buffer.pos - old_pos | |||
|
1115 | ||||
|
1116 | if lib.ZSTD_isError(zresult): | |||
|
1117 | raise ZstdError('error ending compression stream: %s' % | |||
|
1118 | _zstd_error(zresult)) | |||
|
1119 | ||||
|
1120 | if zresult == 0: | |||
|
1121 | self._finished_output = True | |||
|
1122 | ||||
|
1123 | return out_buffer.pos | |||
|
1124 | ||||
|
1125 | ||||
768 | class ZstdCompressor(object): |
|
1126 | class ZstdCompressor(object): | |
769 | def __init__(self, level=3, dict_data=None, compression_params=None, |
|
1127 | def __init__(self, level=3, dict_data=None, compression_params=None, | |
770 | write_checksum=None, write_content_size=None, |
|
1128 | write_checksum=None, write_content_size=None, | |
@@ -803,25 +1161,25 b' class ZstdCompressor(object):' | |||||
803 | self._params = ffi.gc(params, lib.ZSTD_freeCCtxParams) |
|
1161 | self._params = ffi.gc(params, lib.ZSTD_freeCCtxParams) | |
804 |
|
1162 | |||
805 | _set_compression_parameter(self._params, |
|
1163 | _set_compression_parameter(self._params, | |
806 |
lib.ZSTD_ |
|
1164 | lib.ZSTD_c_compressionLevel, | |
807 | level) |
|
1165 | level) | |
808 |
|
1166 | |||
809 | _set_compression_parameter( |
|
1167 | _set_compression_parameter( | |
810 | self._params, |
|
1168 | self._params, | |
811 |
lib.ZSTD_ |
|
1169 | lib.ZSTD_c_contentSizeFlag, | |
812 | write_content_size if write_content_size is not None else 1) |
|
1170 | write_content_size if write_content_size is not None else 1) | |
813 |
|
1171 | |||
814 | _set_compression_parameter(self._params, |
|
1172 | _set_compression_parameter(self._params, | |
815 |
lib.ZSTD_ |
|
1173 | lib.ZSTD_c_checksumFlag, | |
816 | 1 if write_checksum else 0) |
|
1174 | 1 if write_checksum else 0) | |
817 |
|
1175 | |||
818 | _set_compression_parameter(self._params, |
|
1176 | _set_compression_parameter(self._params, | |
819 |
lib.ZSTD_ |
|
1177 | lib.ZSTD_c_dictIDFlag, | |
820 | 1 if write_dict_id else 0) |
|
1178 | 1 if write_dict_id else 0) | |
821 |
|
1179 | |||
822 | if threads: |
|
1180 | if threads: | |
823 | _set_compression_parameter(self._params, |
|
1181 | _set_compression_parameter(self._params, | |
824 |
lib.ZSTD_ |
|
1182 | lib.ZSTD_c_nbWorkers, | |
825 | threads) |
|
1183 | threads) | |
826 |
|
1184 | |||
827 | cctx = lib.ZSTD_createCCtx() |
|
1185 | cctx = lib.ZSTD_createCCtx() | |
@@ -864,7 +1222,7 b' class ZstdCompressor(object):' | |||||
864 | return lib.ZSTD_sizeof_CCtx(self._cctx) |
|
1222 | return lib.ZSTD_sizeof_CCtx(self._cctx) | |
865 |
|
1223 | |||
866 | def compress(self, data): |
|
1224 | def compress(self, data): | |
867 | lib.ZSTD_CCtx_reset(self._cctx) |
|
1225 | lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only) | |
868 |
|
1226 | |||
869 | data_buffer = ffi.from_buffer(data) |
|
1227 | data_buffer = ffi.from_buffer(data) | |
870 |
|
1228 | |||
@@ -887,10 +1245,10 b' class ZstdCompressor(object):' | |||||
887 | in_buffer.size = len(data_buffer) |
|
1245 | in_buffer.size = len(data_buffer) | |
888 | in_buffer.pos = 0 |
|
1246 | in_buffer.pos = 0 | |
889 |
|
1247 | |||
890 |
zresult = lib.ZSTD_compress |
|
1248 | zresult = lib.ZSTD_compressStream2(self._cctx, | |
891 |
|
|
1249 | out_buffer, | |
892 |
|
|
1250 | in_buffer, | |
893 |
|
|
1251 | lib.ZSTD_e_end) | |
894 |
|
1252 | |||
895 | if lib.ZSTD_isError(zresult): |
|
1253 | if lib.ZSTD_isError(zresult): | |
896 | raise ZstdError('cannot compress: %s' % |
|
1254 | raise ZstdError('cannot compress: %s' % | |
@@ -901,7 +1259,7 b' class ZstdCompressor(object):' | |||||
901 | return ffi.buffer(out, out_buffer.pos)[:] |
|
1259 | return ffi.buffer(out, out_buffer.pos)[:] | |
902 |
|
1260 | |||
903 | def compressobj(self, size=-1): |
|
1261 | def compressobj(self, size=-1): | |
904 | lib.ZSTD_CCtx_reset(self._cctx) |
|
1262 | lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only) | |
905 |
|
1263 | |||
906 | if size < 0: |
|
1264 | if size < 0: | |
907 | size = lib.ZSTD_CONTENTSIZE_UNKNOWN |
|
1265 | size = lib.ZSTD_CONTENTSIZE_UNKNOWN | |
@@ -923,7 +1281,7 b' class ZstdCompressor(object):' | |||||
923 | return cobj |
|
1281 | return cobj | |
924 |
|
1282 | |||
925 | def chunker(self, size=-1, chunk_size=COMPRESSION_RECOMMENDED_OUTPUT_SIZE): |
|
1283 | def chunker(self, size=-1, chunk_size=COMPRESSION_RECOMMENDED_OUTPUT_SIZE): | |
926 | lib.ZSTD_CCtx_reset(self._cctx) |
|
1284 | lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only) | |
927 |
|
1285 | |||
928 | if size < 0: |
|
1286 | if size < 0: | |
929 | size = lib.ZSTD_CONTENTSIZE_UNKNOWN |
|
1287 | size = lib.ZSTD_CONTENTSIZE_UNKNOWN | |
@@ -944,7 +1302,7 b' class ZstdCompressor(object):' | |||||
944 | if not hasattr(ofh, 'write'): |
|
1302 | if not hasattr(ofh, 'write'): | |
945 | raise ValueError('second argument must have a write() method') |
|
1303 | raise ValueError('second argument must have a write() method') | |
946 |
|
1304 | |||
947 | lib.ZSTD_CCtx_reset(self._cctx) |
|
1305 | lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only) | |
948 |
|
1306 | |||
949 | if size < 0: |
|
1307 | if size < 0: | |
950 | size = lib.ZSTD_CONTENTSIZE_UNKNOWN |
|
1308 | size = lib.ZSTD_CONTENTSIZE_UNKNOWN | |
@@ -976,10 +1334,10 b' class ZstdCompressor(object):' | |||||
976 | in_buffer.pos = 0 |
|
1334 | in_buffer.pos = 0 | |
977 |
|
1335 | |||
978 | while in_buffer.pos < in_buffer.size: |
|
1336 | while in_buffer.pos < in_buffer.size: | |
979 |
zresult = lib.ZSTD_compress |
|
1337 | zresult = lib.ZSTD_compressStream2(self._cctx, | |
980 |
|
|
1338 | out_buffer, | |
981 |
|
|
1339 | in_buffer, | |
982 |
|
|
1340 | lib.ZSTD_e_continue) | |
983 | if lib.ZSTD_isError(zresult): |
|
1341 | if lib.ZSTD_isError(zresult): | |
984 | raise ZstdError('zstd compress error: %s' % |
|
1342 | raise ZstdError('zstd compress error: %s' % | |
985 | _zstd_error(zresult)) |
|
1343 | _zstd_error(zresult)) | |
@@ -991,10 +1349,10 b' class ZstdCompressor(object):' | |||||
991 |
|
1349 | |||
992 | # We've finished reading. Flush the compressor. |
|
1350 | # We've finished reading. Flush the compressor. | |
993 | while True: |
|
1351 | while True: | |
994 |
zresult = lib.ZSTD_compress |
|
1352 | zresult = lib.ZSTD_compressStream2(self._cctx, | |
995 |
|
|
1353 | out_buffer, | |
996 |
|
|
1354 | in_buffer, | |
997 |
|
|
1355 | lib.ZSTD_e_end) | |
998 | if lib.ZSTD_isError(zresult): |
|
1356 | if lib.ZSTD_isError(zresult): | |
999 | raise ZstdError('error ending compression stream: %s' % |
|
1357 | raise ZstdError('error ending compression stream: %s' % | |
1000 | _zstd_error(zresult)) |
|
1358 | _zstd_error(zresult)) | |
@@ -1011,7 +1369,7 b' class ZstdCompressor(object):' | |||||
1011 |
|
1369 | |||
1012 | def stream_reader(self, source, size=-1, |
|
1370 | def stream_reader(self, source, size=-1, | |
1013 | read_size=COMPRESSION_RECOMMENDED_INPUT_SIZE): |
|
1371 | read_size=COMPRESSION_RECOMMENDED_INPUT_SIZE): | |
1014 | lib.ZSTD_CCtx_reset(self._cctx) |
|
1372 | lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only) | |
1015 |
|
1373 | |||
1016 | try: |
|
1374 | try: | |
1017 | size = len(source) |
|
1375 | size = len(source) | |
@@ -1026,20 +1384,22 b' class ZstdCompressor(object):' | |||||
1026 | raise ZstdError('error setting source size: %s' % |
|
1384 | raise ZstdError('error setting source size: %s' % | |
1027 | _zstd_error(zresult)) |
|
1385 | _zstd_error(zresult)) | |
1028 |
|
1386 | |||
1029 | return CompressionReader(self, source, read_size) |
|
1387 | return ZstdCompressionReader(self, source, read_size) | |
1030 |
|
1388 | |||
1031 | def stream_writer(self, writer, size=-1, |
|
1389 | def stream_writer(self, writer, size=-1, | |
1032 |
write_size=COMPRESSION_RECOMMENDED_OUTPUT_SIZE |
|
1390 | write_size=COMPRESSION_RECOMMENDED_OUTPUT_SIZE, | |
|
1391 | write_return_read=False): | |||
1033 |
|
1392 | |||
1034 | if not hasattr(writer, 'write'): |
|
1393 | if not hasattr(writer, 'write'): | |
1035 | raise ValueError('must pass an object with a write() method') |
|
1394 | raise ValueError('must pass an object with a write() method') | |
1036 |
|
1395 | |||
1037 | lib.ZSTD_CCtx_reset(self._cctx) |
|
1396 | lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only) | |
1038 |
|
1397 | |||
1039 | if size < 0: |
|
1398 | if size < 0: | |
1040 | size = lib.ZSTD_CONTENTSIZE_UNKNOWN |
|
1399 | size = lib.ZSTD_CONTENTSIZE_UNKNOWN | |
1041 |
|
1400 | |||
1042 |
return ZstdCompressionWriter(self, writer, size, write_size |
|
1401 | return ZstdCompressionWriter(self, writer, size, write_size, | |
|
1402 | write_return_read) | |||
1043 |
|
1403 | |||
1044 | write_to = stream_writer |
|
1404 | write_to = stream_writer | |
1045 |
|
1405 | |||
@@ -1056,7 +1416,7 b' class ZstdCompressor(object):' | |||||
1056 | raise ValueError('must pass an object with a read() method or ' |
|
1416 | raise ValueError('must pass an object with a read() method or ' | |
1057 | 'conforms to buffer protocol') |
|
1417 | 'conforms to buffer protocol') | |
1058 |
|
1418 | |||
1059 | lib.ZSTD_CCtx_reset(self._cctx) |
|
1419 | lib.ZSTD_CCtx_reset(self._cctx, lib.ZSTD_reset_session_only) | |
1060 |
|
1420 | |||
1061 | if size < 0: |
|
1421 | if size < 0: | |
1062 | size = lib.ZSTD_CONTENTSIZE_UNKNOWN |
|
1422 | size = lib.ZSTD_CONTENTSIZE_UNKNOWN | |
@@ -1104,8 +1464,8 b' class ZstdCompressor(object):' | |||||
1104 | in_buffer.pos = 0 |
|
1464 | in_buffer.pos = 0 | |
1105 |
|
1465 | |||
1106 | while in_buffer.pos < in_buffer.size: |
|
1466 | while in_buffer.pos < in_buffer.size: | |
1107 |
zresult = lib.ZSTD_compress |
|
1467 | zresult = lib.ZSTD_compressStream2(self._cctx, out_buffer, in_buffer, | |
1108 |
|
|
1468 | lib.ZSTD_e_continue) | |
1109 | if lib.ZSTD_isError(zresult): |
|
1469 | if lib.ZSTD_isError(zresult): | |
1110 | raise ZstdError('zstd compress error: %s' % |
|
1470 | raise ZstdError('zstd compress error: %s' % | |
1111 | _zstd_error(zresult)) |
|
1471 | _zstd_error(zresult)) | |
@@ -1124,10 +1484,10 b' class ZstdCompressor(object):' | |||||
1124 | # remains. |
|
1484 | # remains. | |
1125 | while True: |
|
1485 | while True: | |
1126 | assert out_buffer.pos == 0 |
|
1486 | assert out_buffer.pos == 0 | |
1127 |
zresult = lib.ZSTD_compress |
|
1487 | zresult = lib.ZSTD_compressStream2(self._cctx, | |
1128 |
|
|
1488 | out_buffer, | |
1129 |
|
|
1489 | in_buffer, | |
1130 |
|
|
1490 | lib.ZSTD_e_end) | |
1131 | if lib.ZSTD_isError(zresult): |
|
1491 | if lib.ZSTD_isError(zresult): | |
1132 | raise ZstdError('error ending compression stream: %s' % |
|
1492 | raise ZstdError('error ending compression stream: %s' % | |
1133 | _zstd_error(zresult)) |
|
1493 | _zstd_error(zresult)) | |
@@ -1234,7 +1594,7 b' class ZstdCompressionDict(object):' | |||||
1234 | cparams = ffi.new('ZSTD_compressionParameters') |
|
1594 | cparams = ffi.new('ZSTD_compressionParameters') | |
1235 | cparams.chainLog = compression_params.chain_log |
|
1595 | cparams.chainLog = compression_params.chain_log | |
1236 | cparams.hashLog = compression_params.hash_log |
|
1596 | cparams.hashLog = compression_params.hash_log | |
1237 |
cparams. |
|
1597 | cparams.minMatch = compression_params.min_match | |
1238 | cparams.searchLog = compression_params.search_log |
|
1598 | cparams.searchLog = compression_params.search_log | |
1239 | cparams.strategy = compression_params.compression_strategy |
|
1599 | cparams.strategy = compression_params.compression_strategy | |
1240 | cparams.targetLength = compression_params.target_length |
|
1600 | cparams.targetLength = compression_params.target_length | |
@@ -1345,6 +1705,10 b' class ZstdDecompressionObj(object):' | |||||
1345 | out_buffer = ffi.new('ZSTD_outBuffer *') |
|
1705 | out_buffer = ffi.new('ZSTD_outBuffer *') | |
1346 |
|
1706 | |||
1347 | data_buffer = ffi.from_buffer(data) |
|
1707 | data_buffer = ffi.from_buffer(data) | |
|
1708 | ||||
|
1709 | if len(data_buffer) == 0: | |||
|
1710 | return b'' | |||
|
1711 | ||||
1348 | in_buffer.src = data_buffer |
|
1712 | in_buffer.src = data_buffer | |
1349 | in_buffer.size = len(data_buffer) |
|
1713 | in_buffer.size = len(data_buffer) | |
1350 | in_buffer.pos = 0 |
|
1714 | in_buffer.pos = 0 | |
@@ -1357,8 +1721,8 b' class ZstdDecompressionObj(object):' | |||||
1357 | chunks = [] |
|
1721 | chunks = [] | |
1358 |
|
1722 | |||
1359 | while True: |
|
1723 | while True: | |
1360 |
zresult = lib.ZSTD_decompress |
|
1724 | zresult = lib.ZSTD_decompressStream(self._decompressor._dctx, | |
1361 |
|
|
1725 | out_buffer, in_buffer) | |
1362 | if lib.ZSTD_isError(zresult): |
|
1726 | if lib.ZSTD_isError(zresult): | |
1363 | raise ZstdError('zstd decompressor error: %s' % |
|
1727 | raise ZstdError('zstd decompressor error: %s' % | |
1364 | _zstd_error(zresult)) |
|
1728 | _zstd_error(zresult)) | |
@@ -1378,12 +1742,16 b' class ZstdDecompressionObj(object):' | |||||
1378 |
|
1742 | |||
1379 | return b''.join(chunks) |
|
1743 | return b''.join(chunks) | |
1380 |
|
1744 | |||
1381 |
|
1745 | def flush(self, length=0): | ||
1382 | class DecompressionReader(object): |
|
1746 | pass | |
1383 | def __init__(self, decompressor, source, read_size): |
|
1747 | ||
|
1748 | ||||
|
1749 | class ZstdDecompressionReader(object): | |||
|
1750 | def __init__(self, decompressor, source, read_size, read_across_frames): | |||
1384 | self._decompressor = decompressor |
|
1751 | self._decompressor = decompressor | |
1385 | self._source = source |
|
1752 | self._source = source | |
1386 | self._read_size = read_size |
|
1753 | self._read_size = read_size | |
|
1754 | self._read_across_frames = bool(read_across_frames) | |||
1387 | self._entered = False |
|
1755 | self._entered = False | |
1388 | self._closed = False |
|
1756 | self._closed = False | |
1389 | self._bytes_decompressed = 0 |
|
1757 | self._bytes_decompressed = 0 | |
@@ -1418,10 +1786,10 b' class DecompressionReader(object):' | |||||
1418 | return True |
|
1786 | return True | |
1419 |
|
1787 | |||
1420 | def readline(self): |
|
1788 | def readline(self): | |
1421 |
raise |
|
1789 | raise io.UnsupportedOperation() | |
1422 |
|
1790 | |||
1423 | def readlines(self): |
|
1791 | def readlines(self): | |
1424 |
raise |
|
1792 | raise io.UnsupportedOperation() | |
1425 |
|
1793 | |||
1426 | def write(self, data): |
|
1794 | def write(self, data): | |
1427 | raise io.UnsupportedOperation() |
|
1795 | raise io.UnsupportedOperation() | |
@@ -1447,25 +1815,158 b' class DecompressionReader(object):' | |||||
1447 | return self._bytes_decompressed |
|
1815 | return self._bytes_decompressed | |
1448 |
|
1816 | |||
1449 | def readall(self): |
|
1817 | def readall(self): | |
1450 | raise NotImplementedError() |
|
1818 | chunks = [] | |
|
1819 | ||||
|
1820 | while True: | |||
|
1821 | chunk = self.read(1048576) | |||
|
1822 | if not chunk: | |||
|
1823 | break | |||
|
1824 | ||||
|
1825 | chunks.append(chunk) | |||
|
1826 | ||||
|
1827 | return b''.join(chunks) | |||
1451 |
|
1828 | |||
1452 | def __iter__(self): |
|
1829 | def __iter__(self): | |
1453 |
raise |
|
1830 | raise io.UnsupportedOperation() | |
1454 |
|
1831 | |||
1455 | def __next__(self): |
|
1832 | def __next__(self): | |
1456 |
raise |
|
1833 | raise io.UnsupportedOperation() | |
1457 |
|
1834 | |||
1458 | next = __next__ |
|
1835 | next = __next__ | |
1459 |
|
1836 | |||
1460 |
def read(self |
|
1837 | def _read_input(self): | |
|
1838 | # We have data left over in the input buffer. Use it. | |||
|
1839 | if self._in_buffer.pos < self._in_buffer.size: | |||
|
1840 | return | |||
|
1841 | ||||
|
1842 | # All input data exhausted. Nothing to do. | |||
|
1843 | if self._finished_input: | |||
|
1844 | return | |||
|
1845 | ||||
|
1846 | # Else populate the input buffer from our source. | |||
|
1847 | if hasattr(self._source, 'read'): | |||
|
1848 | data = self._source.read(self._read_size) | |||
|
1849 | ||||
|
1850 | if not data: | |||
|
1851 | self._finished_input = True | |||
|
1852 | return | |||
|
1853 | ||||
|
1854 | self._source_buffer = ffi.from_buffer(data) | |||
|
1855 | self._in_buffer.src = self._source_buffer | |||
|
1856 | self._in_buffer.size = len(self._source_buffer) | |||
|
1857 | self._in_buffer.pos = 0 | |||
|
1858 | else: | |||
|
1859 | self._source_buffer = ffi.from_buffer(self._source) | |||
|
1860 | self._in_buffer.src = self._source_buffer | |||
|
1861 | self._in_buffer.size = len(self._source_buffer) | |||
|
1862 | self._in_buffer.pos = 0 | |||
|
1863 | ||||
|
1864 | def _decompress_into_buffer(self, out_buffer): | |||
|
1865 | """Decompress available input into an output buffer. | |||
|
1866 | ||||
|
1867 | Returns True if data in output buffer should be emitted. | |||
|
1868 | """ | |||
|
1869 | zresult = lib.ZSTD_decompressStream(self._decompressor._dctx, | |||
|
1870 | out_buffer, self._in_buffer) | |||
|
1871 | ||||
|
1872 | if self._in_buffer.pos == self._in_buffer.size: | |||
|
1873 | self._in_buffer.src = ffi.NULL | |||
|
1874 | self._in_buffer.pos = 0 | |||
|
1875 | self._in_buffer.size = 0 | |||
|
1876 | self._source_buffer = None | |||
|
1877 | ||||
|
1878 | if not hasattr(self._source, 'read'): | |||
|
1879 | self._finished_input = True | |||
|
1880 | ||||
|
1881 | if lib.ZSTD_isError(zresult): | |||
|
1882 | raise ZstdError('zstd decompress error: %s' % | |||
|
1883 | _zstd_error(zresult)) | |||
|
1884 | ||||
|
1885 | # Emit data if there is data AND either: | |||
|
1886 | # a) output buffer is full (read amount is satisfied) | |||
|
1887 | # b) we're at end of a frame and not in frame spanning mode | |||
|
1888 | return (out_buffer.pos and | |||
|
1889 | (out_buffer.pos == out_buffer.size or | |||
|
1890 | zresult == 0 and not self._read_across_frames)) | |||
|
1891 | ||||
|
1892 | def read(self, size=-1): | |||
|
1893 | if self._closed: | |||
|
1894 | raise ValueError('stream is closed') | |||
|
1895 | ||||
|
1896 | if size < -1: | |||
|
1897 | raise ValueError('cannot read negative amounts less than -1') | |||
|
1898 | ||||
|
1899 | if size == -1: | |||
|
1900 | # This is recursive. But it gets the job done. | |||
|
1901 | return self.readall() | |||
|
1902 | ||||
|
1903 | if self._finished_output or size == 0: | |||
|
1904 | return b'' | |||
|
1905 | ||||
|
1906 | # We /could/ call into readinto() here. But that introduces more | |||
|
1907 | # overhead. | |||
|
1908 | dst_buffer = ffi.new('char[]', size) | |||
|
1909 | out_buffer = ffi.new('ZSTD_outBuffer *') | |||
|
1910 | out_buffer.dst = dst_buffer | |||
|
1911 | out_buffer.size = size | |||
|
1912 | out_buffer.pos = 0 | |||
|
1913 | ||||
|
1914 | self._read_input() | |||
|
1915 | if self._decompress_into_buffer(out_buffer): | |||
|
1916 | self._bytes_decompressed += out_buffer.pos | |||
|
1917 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] | |||
|
1918 | ||||
|
1919 | while not self._finished_input: | |||
|
1920 | self._read_input() | |||
|
1921 | if self._decompress_into_buffer(out_buffer): | |||
|
1922 | self._bytes_decompressed += out_buffer.pos | |||
|
1923 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] | |||
|
1924 | ||||
|
1925 | self._bytes_decompressed += out_buffer.pos | |||
|
1926 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] | |||
|
1927 | ||||
|
1928 | def readinto(self, b): | |||
1461 | if self._closed: |
|
1929 | if self._closed: | |
1462 | raise ValueError('stream is closed') |
|
1930 | raise ValueError('stream is closed') | |
1463 |
|
1931 | |||
1464 | if self._finished_output: |
|
1932 | if self._finished_output: | |
|
1933 | return 0 | |||
|
1934 | ||||
|
1935 | # TODO use writable=True once we require CFFI >= 1.12. | |||
|
1936 | dest_buffer = ffi.from_buffer(b) | |||
|
1937 | ffi.memmove(b, b'', 0) | |||
|
1938 | out_buffer = ffi.new('ZSTD_outBuffer *') | |||
|
1939 | out_buffer.dst = dest_buffer | |||
|
1940 | out_buffer.size = len(dest_buffer) | |||
|
1941 | out_buffer.pos = 0 | |||
|
1942 | ||||
|
1943 | self._read_input() | |||
|
1944 | if self._decompress_into_buffer(out_buffer): | |||
|
1945 | self._bytes_decompressed += out_buffer.pos | |||
|
1946 | return out_buffer.pos | |||
|
1947 | ||||
|
1948 | while not self._finished_input: | |||
|
1949 | self._read_input() | |||
|
1950 | if self._decompress_into_buffer(out_buffer): | |||
|
1951 | self._bytes_decompressed += out_buffer.pos | |||
|
1952 | return out_buffer.pos | |||
|
1953 | ||||
|
1954 | self._bytes_decompressed += out_buffer.pos | |||
|
1955 | return out_buffer.pos | |||
|
1956 | ||||
|
1957 | def read1(self, size=-1): | |||
|
1958 | if self._closed: | |||
|
1959 | raise ValueError('stream is closed') | |||
|
1960 | ||||
|
1961 | if size < -1: | |||
|
1962 | raise ValueError('cannot read negative amounts less than -1') | |||
|
1963 | ||||
|
1964 | if self._finished_output or size == 0: | |||
1465 | return b'' |
|
1965 | return b'' | |
1466 |
|
1966 | |||
1467 | if size < 1: |
|
1967 | # -1 returns arbitrary number of bytes. | |
1468 | raise ValueError('cannot read negative or size 0 amounts') |
|
1968 | if size == -1: | |
|
1969 | size = DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE | |||
1469 |
|
1970 | |||
1470 | dst_buffer = ffi.new('char[]', size) |
|
1971 | dst_buffer = ffi.new('char[]', size) | |
1471 | out_buffer = ffi.new('ZSTD_outBuffer *') |
|
1972 | out_buffer = ffi.new('ZSTD_outBuffer *') | |
@@ -1473,64 +1974,46 b' class DecompressionReader(object):' | |||||
1473 | out_buffer.size = size |
|
1974 | out_buffer.size = size | |
1474 | out_buffer.pos = 0 |
|
1975 | out_buffer.pos = 0 | |
1475 |
|
1976 | |||
1476 | def decompress(): |
|
1977 | # read1() dictates that we can perform at most 1 call to underlying | |
1477 | zresult = lib.ZSTD_decompress_generic(self._decompressor._dctx, |
|
1978 | # stream to get input. However, we can't satisfy this restriction with | |
1478 | out_buffer, self._in_buffer) |
|
1979 | # decompression because not all input generates output. So we allow | |
1479 |
|
1980 | # multiple read(). But unlike read(), we stop once we have any output. | ||
1480 | if self._in_buffer.pos == self._in_buffer.size: |
|
|||
1481 | self._in_buffer.src = ffi.NULL |
|
|||
1482 | self._in_buffer.pos = 0 |
|
|||
1483 | self._in_buffer.size = 0 |
|
|||
1484 | self._source_buffer = None |
|
|||
1485 |
|
||||
1486 | if not hasattr(self._source, 'read'): |
|
|||
1487 | self._finished_input = True |
|
|||
1488 |
|
||||
1489 | if lib.ZSTD_isError(zresult): |
|
|||
1490 | raise ZstdError('zstd decompress error: %s', |
|
|||
1491 | _zstd_error(zresult)) |
|
|||
1492 | elif zresult == 0: |
|
|||
1493 | self._finished_output = True |
|
|||
1494 |
|
||||
1495 | if out_buffer.pos and out_buffer.pos == out_buffer.size: |
|
|||
1496 | self._bytes_decompressed += out_buffer.size |
|
|||
1497 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] |
|
|||
1498 |
|
||||
1499 | def get_input(): |
|
|||
1500 | if self._finished_input: |
|
|||
1501 | return |
|
|||
1502 |
|
||||
1503 | if hasattr(self._source, 'read'): |
|
|||
1504 | data = self._source.read(self._read_size) |
|
|||
1505 |
|
||||
1506 | if not data: |
|
|||
1507 | self._finished_input = True |
|
|||
1508 | return |
|
|||
1509 |
|
||||
1510 | self._source_buffer = ffi.from_buffer(data) |
|
|||
1511 | self._in_buffer.src = self._source_buffer |
|
|||
1512 | self._in_buffer.size = len(self._source_buffer) |
|
|||
1513 | self._in_buffer.pos = 0 |
|
|||
1514 | else: |
|
|||
1515 | self._source_buffer = ffi.from_buffer(self._source) |
|
|||
1516 | self._in_buffer.src = self._source_buffer |
|
|||
1517 | self._in_buffer.size = len(self._source_buffer) |
|
|||
1518 | self._in_buffer.pos = 0 |
|
|||
1519 |
|
||||
1520 | get_input() |
|
|||
1521 | result = decompress() |
|
|||
1522 | if result: |
|
|||
1523 | return result |
|
|||
1524 |
|
||||
1525 | while not self._finished_input: |
|
1981 | while not self._finished_input: | |
1526 |
|
|
1982 | self._read_input() | |
1527 |
|
|
1983 | self._decompress_into_buffer(out_buffer) | |
1528 | if result: |
|
1984 | ||
1529 | return result |
|
1985 | if out_buffer.pos: | |
|
1986 | break | |||
1530 |
|
1987 | |||
1531 | self._bytes_decompressed += out_buffer.pos |
|
1988 | self._bytes_decompressed += out_buffer.pos | |
1532 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] |
|
1989 | return ffi.buffer(out_buffer.dst, out_buffer.pos)[:] | |
1533 |
|
1990 | |||
|
1991 | def readinto1(self, b): | |||
|
1992 | if self._closed: | |||
|
1993 | raise ValueError('stream is closed') | |||
|
1994 | ||||
|
1995 | if self._finished_output: | |||
|
1996 | return 0 | |||
|
1997 | ||||
|
1998 | # TODO use writable=True once we require CFFI >= 1.12. | |||
|
1999 | dest_buffer = ffi.from_buffer(b) | |||
|
2000 | ffi.memmove(b, b'', 0) | |||
|
2001 | ||||
|
2002 | out_buffer = ffi.new('ZSTD_outBuffer *') | |||
|
2003 | out_buffer.dst = dest_buffer | |||
|
2004 | out_buffer.size = len(dest_buffer) | |||
|
2005 | out_buffer.pos = 0 | |||
|
2006 | ||||
|
2007 | while not self._finished_input and not self._finished_output: | |||
|
2008 | self._read_input() | |||
|
2009 | self._decompress_into_buffer(out_buffer) | |||
|
2010 | ||||
|
2011 | if out_buffer.pos: | |||
|
2012 | break | |||
|
2013 | ||||
|
2014 | self._bytes_decompressed += out_buffer.pos | |||
|
2015 | return out_buffer.pos | |||
|
2016 | ||||
1534 | def seek(self, pos, whence=os.SEEK_SET): |
|
2017 | def seek(self, pos, whence=os.SEEK_SET): | |
1535 | if self._closed: |
|
2018 | if self._closed: | |
1536 | raise ValueError('stream is closed') |
|
2019 | raise ValueError('stream is closed') | |
@@ -1569,34 +2052,108 b' class DecompressionReader(object):' | |||||
1569 | return self._bytes_decompressed |
|
2052 | return self._bytes_decompressed | |
1570 |
|
2053 | |||
1571 | class ZstdDecompressionWriter(object): |
|
2054 | class ZstdDecompressionWriter(object): | |
1572 | def __init__(self, decompressor, writer, write_size): |
|
2055 | def __init__(self, decompressor, writer, write_size, write_return_read): | |
|
2056 | decompressor._ensure_dctx() | |||
|
2057 | ||||
1573 | self._decompressor = decompressor |
|
2058 | self._decompressor = decompressor | |
1574 | self._writer = writer |
|
2059 | self._writer = writer | |
1575 | self._write_size = write_size |
|
2060 | self._write_size = write_size | |
|
2061 | self._write_return_read = bool(write_return_read) | |||
1576 | self._entered = False |
|
2062 | self._entered = False | |
|
2063 | self._closed = False | |||
1577 |
|
2064 | |||
1578 | def __enter__(self): |
|
2065 | def __enter__(self): | |
|
2066 | if self._closed: | |||
|
2067 | raise ValueError('stream is closed') | |||
|
2068 | ||||
1579 | if self._entered: |
|
2069 | if self._entered: | |
1580 | raise ZstdError('cannot __enter__ multiple times') |
|
2070 | raise ZstdError('cannot __enter__ multiple times') | |
1581 |
|
2071 | |||
1582 | self._decompressor._ensure_dctx() |
|
|||
1583 | self._entered = True |
|
2072 | self._entered = True | |
1584 |
|
2073 | |||
1585 | return self |
|
2074 | return self | |
1586 |
|
2075 | |||
1587 | def __exit__(self, exc_type, exc_value, exc_tb): |
|
2076 | def __exit__(self, exc_type, exc_value, exc_tb): | |
1588 | self._entered = False |
|
2077 | self._entered = False | |
|
2078 | self.close() | |||
1589 |
|
2079 | |||
1590 | def memory_size(self): |
|
2080 | def memory_size(self): | |
1591 | if not self._decompressor._dctx: |
|
|||
1592 | raise ZstdError('cannot determine size of inactive decompressor ' |
|
|||
1593 | 'call when context manager is active') |
|
|||
1594 |
|
||||
1595 | return lib.ZSTD_sizeof_DCtx(self._decompressor._dctx) |
|
2081 | return lib.ZSTD_sizeof_DCtx(self._decompressor._dctx) | |
1596 |
|
2082 | |||
|
2083 | def close(self): | |||
|
2084 | if self._closed: | |||
|
2085 | return | |||
|
2086 | ||||
|
2087 | try: | |||
|
2088 | self.flush() | |||
|
2089 | finally: | |||
|
2090 | self._closed = True | |||
|
2091 | ||||
|
2092 | f = getattr(self._writer, 'close', None) | |||
|
2093 | if f: | |||
|
2094 | f() | |||
|
2095 | ||||
|
2096 | @property | |||
|
2097 | def closed(self): | |||
|
2098 | return self._closed | |||
|
2099 | ||||
|
2100 | def fileno(self): | |||
|
2101 | f = getattr(self._writer, 'fileno', None) | |||
|
2102 | if f: | |||
|
2103 | return f() | |||
|
2104 | else: | |||
|
2105 | raise OSError('fileno not available on underlying writer') | |||
|
2106 | ||||
|
2107 | def flush(self): | |||
|
2108 | if self._closed: | |||
|
2109 | raise ValueError('stream is closed') | |||
|
2110 | ||||
|
2111 | f = getattr(self._writer, 'flush', None) | |||
|
2112 | if f: | |||
|
2113 | return f() | |||
|
2114 | ||||
|
2115 | def isatty(self): | |||
|
2116 | return False | |||
|
2117 | ||||
|
2118 | def readable(self): | |||
|
2119 | return False | |||
|
2120 | ||||
|
2121 | def readline(self, size=-1): | |||
|
2122 | raise io.UnsupportedOperation() | |||
|
2123 | ||||
|
2124 | def readlines(self, hint=-1): | |||
|
2125 | raise io.UnsupportedOperation() | |||
|
2126 | ||||
|
2127 | def seek(self, offset, whence=None): | |||
|
2128 | raise io.UnsupportedOperation() | |||
|
2129 | ||||
|
2130 | def seekable(self): | |||
|
2131 | return False | |||
|
2132 | ||||
|
2133 | def tell(self): | |||
|
2134 | raise io.UnsupportedOperation() | |||
|
2135 | ||||
|
2136 | def truncate(self, size=None): | |||
|
2137 | raise io.UnsupportedOperation() | |||
|
2138 | ||||
|
2139 | def writable(self): | |||
|
2140 | return True | |||
|
2141 | ||||
|
2142 | def writelines(self, lines): | |||
|
2143 | raise io.UnsupportedOperation() | |||
|
2144 | ||||
|
2145 | def read(self, size=-1): | |||
|
2146 | raise io.UnsupportedOperation() | |||
|
2147 | ||||
|
2148 | def readall(self): | |||
|
2149 | raise io.UnsupportedOperation() | |||
|
2150 | ||||
|
2151 | def readinto(self, b): | |||
|
2152 | raise io.UnsupportedOperation() | |||
|
2153 | ||||
1597 | def write(self, data): |
|
2154 | def write(self, data): | |
1598 |
if |
|
2155 | if self._closed: | |
1599 | raise ZstdError('write must be called from an active context manager') |
|
2156 | raise ValueError('stream is closed') | |
1600 |
|
2157 | |||
1601 | total_write = 0 |
|
2158 | total_write = 0 | |
1602 |
|
2159 | |||
@@ -1616,7 +2173,7 b' class ZstdDecompressionWriter(object):' | |||||
1616 | dctx = self._decompressor._dctx |
|
2173 | dctx = self._decompressor._dctx | |
1617 |
|
2174 | |||
1618 | while in_buffer.pos < in_buffer.size: |
|
2175 | while in_buffer.pos < in_buffer.size: | |
1619 |
zresult = lib.ZSTD_decompress |
|
2176 | zresult = lib.ZSTD_decompressStream(dctx, out_buffer, in_buffer) | |
1620 | if lib.ZSTD_isError(zresult): |
|
2177 | if lib.ZSTD_isError(zresult): | |
1621 | raise ZstdError('zstd decompress error: %s' % |
|
2178 | raise ZstdError('zstd decompress error: %s' % | |
1622 | _zstd_error(zresult)) |
|
2179 | _zstd_error(zresult)) | |
@@ -1626,7 +2183,10 b' class ZstdDecompressionWriter(object):' | |||||
1626 | total_write += out_buffer.pos |
|
2183 | total_write += out_buffer.pos | |
1627 | out_buffer.pos = 0 |
|
2184 | out_buffer.pos = 0 | |
1628 |
|
2185 | |||
1629 | return total_write |
|
2186 | if self._write_return_read: | |
|
2187 | return in_buffer.pos | |||
|
2188 | else: | |||
|
2189 | return total_write | |||
1630 |
|
2190 | |||
1631 |
|
2191 | |||
1632 | class ZstdDecompressor(object): |
|
2192 | class ZstdDecompressor(object): | |
@@ -1684,7 +2244,7 b' class ZstdDecompressor(object):' | |||||
1684 | in_buffer.size = len(data_buffer) |
|
2244 | in_buffer.size = len(data_buffer) | |
1685 | in_buffer.pos = 0 |
|
2245 | in_buffer.pos = 0 | |
1686 |
|
2246 | |||
1687 |
zresult = lib.ZSTD_decompress |
|
2247 | zresult = lib.ZSTD_decompressStream(self._dctx, out_buffer, in_buffer) | |
1688 | if lib.ZSTD_isError(zresult): |
|
2248 | if lib.ZSTD_isError(zresult): | |
1689 | raise ZstdError('decompression error: %s' % |
|
2249 | raise ZstdError('decompression error: %s' % | |
1690 | _zstd_error(zresult)) |
|
2250 | _zstd_error(zresult)) | |
@@ -1696,9 +2256,10 b' class ZstdDecompressor(object):' | |||||
1696 |
|
2256 | |||
1697 | return ffi.buffer(result_buffer, out_buffer.pos)[:] |
|
2257 | return ffi.buffer(result_buffer, out_buffer.pos)[:] | |
1698 |
|
2258 | |||
1699 |
def stream_reader(self, source, read_size=DECOMPRESSION_RECOMMENDED_INPUT_SIZE |
|
2259 | def stream_reader(self, source, read_size=DECOMPRESSION_RECOMMENDED_INPUT_SIZE, | |
|
2260 | read_across_frames=False): | |||
1700 | self._ensure_dctx() |
|
2261 | self._ensure_dctx() | |
1701 | return DecompressionReader(self, source, read_size) |
|
2262 | return ZstdDecompressionReader(self, source, read_size, read_across_frames) | |
1702 |
|
2263 | |||
1703 | def decompressobj(self, write_size=DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE): |
|
2264 | def decompressobj(self, write_size=DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE): | |
1704 | if write_size < 1: |
|
2265 | if write_size < 1: | |
@@ -1767,7 +2328,7 b' class ZstdDecompressor(object):' | |||||
1767 | while in_buffer.pos < in_buffer.size: |
|
2328 | while in_buffer.pos < in_buffer.size: | |
1768 | assert out_buffer.pos == 0 |
|
2329 | assert out_buffer.pos == 0 | |
1769 |
|
2330 | |||
1770 |
zresult = lib.ZSTD_decompress |
|
2331 | zresult = lib.ZSTD_decompressStream(self._dctx, out_buffer, in_buffer) | |
1771 | if lib.ZSTD_isError(zresult): |
|
2332 | if lib.ZSTD_isError(zresult): | |
1772 | raise ZstdError('zstd decompress error: %s' % |
|
2333 | raise ZstdError('zstd decompress error: %s' % | |
1773 | _zstd_error(zresult)) |
|
2334 | _zstd_error(zresult)) | |
@@ -1787,11 +2348,13 b' class ZstdDecompressor(object):' | |||||
1787 |
|
2348 | |||
1788 | read_from = read_to_iter |
|
2349 | read_from = read_to_iter | |
1789 |
|
2350 | |||
1790 |
def stream_writer(self, writer, write_size=DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE |
|
2351 | def stream_writer(self, writer, write_size=DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE, | |
|
2352 | write_return_read=False): | |||
1791 | if not hasattr(writer, 'write'): |
|
2353 | if not hasattr(writer, 'write'): | |
1792 | raise ValueError('must pass an object with a write() method') |
|
2354 | raise ValueError('must pass an object with a write() method') | |
1793 |
|
2355 | |||
1794 |
return ZstdDecompressionWriter(self, writer, write_size |
|
2356 | return ZstdDecompressionWriter(self, writer, write_size, | |
|
2357 | write_return_read) | |||
1795 |
|
2358 | |||
1796 | write_to = stream_writer |
|
2359 | write_to = stream_writer | |
1797 |
|
2360 | |||
@@ -1829,7 +2392,7 b' class ZstdDecompressor(object):' | |||||
1829 |
|
2392 | |||
1830 | # Flush all read data to output. |
|
2393 | # Flush all read data to output. | |
1831 | while in_buffer.pos < in_buffer.size: |
|
2394 | while in_buffer.pos < in_buffer.size: | |
1832 |
zresult = lib.ZSTD_decompress |
|
2395 | zresult = lib.ZSTD_decompressStream(self._dctx, out_buffer, in_buffer) | |
1833 | if lib.ZSTD_isError(zresult): |
|
2396 | if lib.ZSTD_isError(zresult): | |
1834 | raise ZstdError('zstd decompressor error: %s' % |
|
2397 | raise ZstdError('zstd decompressor error: %s' % | |
1835 | _zstd_error(zresult)) |
|
2398 | _zstd_error(zresult)) | |
@@ -1881,7 +2444,7 b' class ZstdDecompressor(object):' | |||||
1881 | in_buffer.size = len(chunk_buffer) |
|
2444 | in_buffer.size = len(chunk_buffer) | |
1882 | in_buffer.pos = 0 |
|
2445 | in_buffer.pos = 0 | |
1883 |
|
2446 | |||
1884 |
zresult = lib.ZSTD_decompress |
|
2447 | zresult = lib.ZSTD_decompressStream(self._dctx, out_buffer, in_buffer) | |
1885 | if lib.ZSTD_isError(zresult): |
|
2448 | if lib.ZSTD_isError(zresult): | |
1886 | raise ZstdError('could not decompress chunk 0: %s' % |
|
2449 | raise ZstdError('could not decompress chunk 0: %s' % | |
1887 | _zstd_error(zresult)) |
|
2450 | _zstd_error(zresult)) | |
@@ -1918,7 +2481,7 b' class ZstdDecompressor(object):' | |||||
1918 | in_buffer.size = len(chunk_buffer) |
|
2481 | in_buffer.size = len(chunk_buffer) | |
1919 | in_buffer.pos = 0 |
|
2482 | in_buffer.pos = 0 | |
1920 |
|
2483 | |||
1921 |
zresult = lib.ZSTD_decompress |
|
2484 | zresult = lib.ZSTD_decompressStream(self._dctx, out_buffer, in_buffer) | |
1922 | if lib.ZSTD_isError(zresult): |
|
2485 | if lib.ZSTD_isError(zresult): | |
1923 | raise ZstdError('could not decompress chunk %d: %s' % |
|
2486 | raise ZstdError('could not decompress chunk %d: %s' % | |
1924 | _zstd_error(zresult)) |
|
2487 | _zstd_error(zresult)) | |
@@ -1931,7 +2494,7 b' class ZstdDecompressor(object):' | |||||
1931 | return ffi.buffer(last_buffer, len(last_buffer))[:] |
|
2494 | return ffi.buffer(last_buffer, len(last_buffer))[:] | |
1932 |
|
2495 | |||
1933 | def _ensure_dctx(self, load_dict=True): |
|
2496 | def _ensure_dctx(self, load_dict=True): | |
1934 | lib.ZSTD_DCtx_reset(self._dctx) |
|
2497 | lib.ZSTD_DCtx_reset(self._dctx, lib.ZSTD_reset_session_only) | |
1935 |
|
2498 | |||
1936 | if self._max_window_size: |
|
2499 | if self._max_window_size: | |
1937 | zresult = lib.ZSTD_DCtx_setMaxWindowSize(self._dctx, |
|
2500 | zresult = lib.ZSTD_DCtx_setMaxWindowSize(self._dctx, |
@@ -210,7 +210,7 b' void zstd_module_init(PyObject* m) {' | |||||
210 | We detect this mismatch here and refuse to load the module if this |
|
210 | We detect this mismatch here and refuse to load the module if this | |
211 | scenario is detected. |
|
211 | scenario is detected. | |
212 | */ |
|
212 | */ | |
213 |
if (ZSTD_VERSION_NUMBER != 1030 |
|
213 | if (ZSTD_VERSION_NUMBER != 10308 || ZSTD_versionNumber() != 10308) { | |
214 | PyErr_SetString(PyExc_ImportError, "zstd C API mismatch; Python bindings not compiled against expected zstd version"); |
|
214 | PyErr_SetString(PyExc_ImportError, "zstd C API mismatch; Python bindings not compiled against expected zstd version"); | |
215 | return; |
|
215 | return; | |
216 | } |
|
216 | } |
@@ -339,17 +339,10 b' MEM_STATIC size_t BIT_getUpperBits(size_' | |||||
339 |
|
339 | |||
340 | MEM_STATIC size_t BIT_getMiddleBits(size_t bitContainer, U32 const start, U32 const nbBits) |
|
340 | MEM_STATIC size_t BIT_getMiddleBits(size_t bitContainer, U32 const start, U32 const nbBits) | |
341 | { |
|
341 | { | |
342 | #if defined(__BMI__) && defined(__GNUC__) && __GNUC__*1000+__GNUC_MINOR__ >= 4008 /* experimental */ |
|
342 | U32 const regMask = sizeof(bitContainer)*8 - 1; | |
343 | # if defined(__x86_64__) |
|
343 | /* if start > regMask, bitstream is corrupted, and result is undefined */ | |
344 | if (sizeof(bitContainer)==8) |
|
|||
345 | return _bextr_u64(bitContainer, start, nbBits); |
|
|||
346 | else |
|
|||
347 | # endif |
|
|||
348 | return _bextr_u32(bitContainer, start, nbBits); |
|
|||
349 | #else |
|
|||
350 | assert(nbBits < BIT_MASK_SIZE); |
|
344 | assert(nbBits < BIT_MASK_SIZE); | |
351 | return (bitContainer >> start) & BIT_mask[nbBits]; |
|
345 | return (bitContainer >> (start & regMask)) & BIT_mask[nbBits]; | |
352 | #endif |
|
|||
353 | } |
|
346 | } | |
354 |
|
347 | |||
355 | MEM_STATIC size_t BIT_getLowerBits(size_t bitContainer, U32 const nbBits) |
|
348 | MEM_STATIC size_t BIT_getLowerBits(size_t bitContainer, U32 const nbBits) | |
@@ -366,9 +359,13 b' MEM_STATIC size_t BIT_getLowerBits(size_' | |||||
366 | * @return : value extracted */ |
|
359 | * @return : value extracted */ | |
367 | MEM_STATIC size_t BIT_lookBits(const BIT_DStream_t* bitD, U32 nbBits) |
|
360 | MEM_STATIC size_t BIT_lookBits(const BIT_DStream_t* bitD, U32 nbBits) | |
368 | { |
|
361 | { | |
369 | #if defined(__BMI__) && defined(__GNUC__) /* experimental; fails if bitD->bitsConsumed + nbBits > sizeof(bitD->bitContainer)*8 */ |
|
362 | /* arbitrate between double-shift and shift+mask */ | |
|
363 | #if 1 | |||
|
364 | /* if bitD->bitsConsumed + nbBits > sizeof(bitD->bitContainer)*8, | |||
|
365 | * bitstream is likely corrupted, and result is undefined */ | |||
370 | return BIT_getMiddleBits(bitD->bitContainer, (sizeof(bitD->bitContainer)*8) - bitD->bitsConsumed - nbBits, nbBits); |
|
366 | return BIT_getMiddleBits(bitD->bitContainer, (sizeof(bitD->bitContainer)*8) - bitD->bitsConsumed - nbBits, nbBits); | |
371 | #else |
|
367 | #else | |
|
368 | /* this code path is slower on my os-x laptop */ | |||
372 | U32 const regMask = sizeof(bitD->bitContainer)*8 - 1; |
|
369 | U32 const regMask = sizeof(bitD->bitContainer)*8 - 1; | |
373 | return ((bitD->bitContainer << (bitD->bitsConsumed & regMask)) >> 1) >> ((regMask-nbBits) & regMask); |
|
370 | return ((bitD->bitContainer << (bitD->bitsConsumed & regMask)) >> 1) >> ((regMask-nbBits) & regMask); | |
374 | #endif |
|
371 | #endif | |
@@ -392,7 +389,7 b' MEM_STATIC void BIT_skipBits(BIT_DStream' | |||||
392 | * Read (consume) next n bits from local register and update. |
|
389 | * Read (consume) next n bits from local register and update. | |
393 | * Pay attention to not read more than nbBits contained into local register. |
|
390 | * Pay attention to not read more than nbBits contained into local register. | |
394 | * @return : extracted value. */ |
|
391 | * @return : extracted value. */ | |
395 |
MEM_STATIC size_t BIT_readBits(BIT_DStream_t* bitD, |
|
392 | MEM_STATIC size_t BIT_readBits(BIT_DStream_t* bitD, unsigned nbBits) | |
396 | { |
|
393 | { | |
397 | size_t const value = BIT_lookBits(bitD, nbBits); |
|
394 | size_t const value = BIT_lookBits(bitD, nbBits); | |
398 | BIT_skipBits(bitD, nbBits); |
|
395 | BIT_skipBits(bitD, nbBits); | |
@@ -401,7 +398,7 b' MEM_STATIC size_t BIT_readBits(BIT_DStre' | |||||
401 |
|
398 | |||
402 | /*! BIT_readBitsFast() : |
|
399 | /*! BIT_readBitsFast() : | |
403 | * unsafe version; only works only if nbBits >= 1 */ |
|
400 | * unsafe version; only works only if nbBits >= 1 */ | |
404 |
MEM_STATIC size_t BIT_readBitsFast(BIT_DStream_t* bitD, |
|
401 | MEM_STATIC size_t BIT_readBitsFast(BIT_DStream_t* bitD, unsigned nbBits) | |
405 | { |
|
402 | { | |
406 | size_t const value = BIT_lookBitsFast(bitD, nbBits); |
|
403 | size_t const value = BIT_lookBitsFast(bitD, nbBits); | |
407 | assert(nbBits >= 1); |
|
404 | assert(nbBits >= 1); |
@@ -15,6 +15,8 b'' | |||||
15 | * Compiler specifics |
|
15 | * Compiler specifics | |
16 | *********************************************************/ |
|
16 | *********************************************************/ | |
17 | /* force inlining */ |
|
17 | /* force inlining */ | |
|
18 | ||||
|
19 | #if !defined(ZSTD_NO_INLINE) | |||
18 | #if defined (__GNUC__) || defined(__cplusplus) || defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ |
|
20 | #if defined (__GNUC__) || defined(__cplusplus) || defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ | |
19 | # define INLINE_KEYWORD inline |
|
21 | # define INLINE_KEYWORD inline | |
20 | #else |
|
22 | #else | |
@@ -29,6 +31,13 b'' | |||||
29 | # define FORCE_INLINE_ATTR |
|
31 | # define FORCE_INLINE_ATTR | |
30 | #endif |
|
32 | #endif | |
31 |
|
33 | |||
|
34 | #else | |||
|
35 | ||||
|
36 | #define INLINE_KEYWORD | |||
|
37 | #define FORCE_INLINE_ATTR | |||
|
38 | ||||
|
39 | #endif | |||
|
40 | ||||
32 | /** |
|
41 | /** | |
33 | * FORCE_INLINE_TEMPLATE is used to define C "templates", which take constant |
|
42 | * FORCE_INLINE_TEMPLATE is used to define C "templates", which take constant | |
34 | * parameters. They must be inlined for the compiler to elimininate the constant |
|
43 | * parameters. They must be inlined for the compiler to elimininate the constant | |
@@ -89,23 +98,21 b'' | |||||
89 | #endif |
|
98 | #endif | |
90 |
|
99 | |||
91 | /* prefetch |
|
100 | /* prefetch | |
92 | * can be disabled, by declaring NO_PREFETCH macro |
|
101 | * can be disabled, by declaring NO_PREFETCH build macro */ | |
93 | * All prefetch invocations use a single default locality 2, |
|
|||
94 | * generating instruction prefetcht1, |
|
|||
95 | * which, according to Intel, means "load data into L2 cache". |
|
|||
96 | * This is a good enough "middle ground" for the time being, |
|
|||
97 | * though in theory, it would be better to specialize locality depending on data being prefetched. |
|
|||
98 | * Tests could not determine any sensible difference based on locality value. */ |
|
|||
99 | #if defined(NO_PREFETCH) |
|
102 | #if defined(NO_PREFETCH) | |
100 |
# define PREFETCH(ptr) |
|
103 | # define PREFETCH_L1(ptr) (void)(ptr) /* disabled */ | |
|
104 | # define PREFETCH_L2(ptr) (void)(ptr) /* disabled */ | |||
101 | #else |
|
105 | #else | |
102 | # if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_I86)) /* _mm_prefetch() is not defined outside of x86/x64 */ |
|
106 | # if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_I86)) /* _mm_prefetch() is not defined outside of x86/x64 */ | |
103 | # include <mmintrin.h> /* https://msdn.microsoft.com/fr-fr/library/84szxsww(v=vs.90).aspx */ |
|
107 | # include <mmintrin.h> /* https://msdn.microsoft.com/fr-fr/library/84szxsww(v=vs.90).aspx */ | |
104 |
# define PREFETCH(ptr) |
|
108 | # define PREFETCH_L1(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T0) | |
|
109 | # define PREFETCH_L2(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T1) | |||
105 | # elif defined(__GNUC__) && ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) ) |
|
110 | # elif defined(__GNUC__) && ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) ) | |
106 |
# define PREFETCH(ptr) |
|
111 | # define PREFETCH_L1(ptr) __builtin_prefetch((ptr), 0 /* rw==read */, 3 /* locality */) | |
|
112 | # define PREFETCH_L2(ptr) __builtin_prefetch((ptr), 0 /* rw==read */, 2 /* locality */) | |||
107 | # else |
|
113 | # else | |
108 |
# define PREFETCH(ptr) |
|
114 | # define PREFETCH_L1(ptr) (void)(ptr) /* disabled */ | |
|
115 | # define PREFETCH_L2(ptr) (void)(ptr) /* disabled */ | |||
109 | # endif |
|
116 | # endif | |
110 | #endif /* NO_PREFETCH */ |
|
117 | #endif /* NO_PREFETCH */ | |
111 |
|
118 | |||
@@ -116,7 +123,7 b'' | |||||
116 | size_t const _size = (size_t)(s); \ |
|
123 | size_t const _size = (size_t)(s); \ | |
117 | size_t _pos; \ |
|
124 | size_t _pos; \ | |
118 | for (_pos=0; _pos<_size; _pos+=CACHELINE_SIZE) { \ |
|
125 | for (_pos=0; _pos<_size; _pos+=CACHELINE_SIZE) { \ | |
119 |
PREFETCH(_ptr + _pos); |
|
126 | PREFETCH_L2(_ptr + _pos); \ | |
120 | } \ |
|
127 | } \ | |
121 | } |
|
128 | } | |
122 |
|
129 |
@@ -78,7 +78,7 b' MEM_STATIC ZSTD_cpuid_t ZSTD_cpuid(void)' | |||||
78 | __asm__( |
|
78 | __asm__( | |
79 | "pushl %%ebx\n\t" |
|
79 | "pushl %%ebx\n\t" | |
80 | "cpuid\n\t" |
|
80 | "cpuid\n\t" | |
81 |
"movl %%ebx, %%eax\n\ |
|
81 | "movl %%ebx, %%eax\n\t" | |
82 | "popl %%ebx" |
|
82 | "popl %%ebx" | |
83 | : "=a"(f7b), "=c"(f7c) |
|
83 | : "=a"(f7b), "=c"(f7c) | |
84 | : "a"(7), "c"(0) |
|
84 | : "a"(7), "c"(0) |
@@ -57,9 +57,9 b' extern "C" {' | |||||
57 | #endif |
|
57 | #endif | |
58 |
|
58 | |||
59 |
|
59 | |||
60 |
/* static assert is triggered at compile time, leaving no runtime artefact |
|
60 | /* static assert is triggered at compile time, leaving no runtime artefact. | |
61 |
* |
|
61 | * static assert only works with compile-time constants. | |
62 |
* |
|
62 | * Also, this variant can only be used inside a function. */ | |
63 | #define DEBUG_STATIC_ASSERT(c) (void)sizeof(char[(c) ? 1 : -1]) |
|
63 | #define DEBUG_STATIC_ASSERT(c) (void)sizeof(char[(c) ? 1 : -1]) | |
64 |
|
64 | |||
65 |
|
65 | |||
@@ -70,9 +70,19 b' extern "C" {' | |||||
70 | # define DEBUGLEVEL 0 |
|
70 | # define DEBUGLEVEL 0 | |
71 | #endif |
|
71 | #endif | |
72 |
|
72 | |||
|
73 | ||||
|
74 | /* DEBUGFILE can be defined externally, | |||
|
75 | * typically through compiler command line. | |||
|
76 | * note : currently useless. | |||
|
77 | * Value must be stderr or stdout */ | |||
|
78 | #ifndef DEBUGFILE | |||
|
79 | # define DEBUGFILE stderr | |||
|
80 | #endif | |||
|
81 | ||||
|
82 | ||||
73 | /* recommended values for DEBUGLEVEL : |
|
83 | /* recommended values for DEBUGLEVEL : | |
74 |
* 0 : no debug, all run-time |
|
84 | * 0 : release mode, no debug, all run-time checks disabled | |
75 |
* 1 : |
|
85 | * 1 : enables assert() only, no display | |
76 | * 2 : reserved, for currently active debug path |
|
86 | * 2 : reserved, for currently active debug path | |
77 | * 3 : events once per object lifetime (CCtx, CDict, etc.) |
|
87 | * 3 : events once per object lifetime (CCtx, CDict, etc.) | |
78 | * 4 : events once per frame |
|
88 | * 4 : events once per frame | |
@@ -81,7 +91,7 b' extern "C" {' | |||||
81 | * 7+: events at every position (*very* verbose) |
|
91 | * 7+: events at every position (*very* verbose) | |
82 | * |
|
92 | * | |
83 | * It's generally inconvenient to output traces > 5. |
|
93 | * It's generally inconvenient to output traces > 5. | |
84 |
* In which case, it's possible to selectively |
|
94 | * In which case, it's possible to selectively trigger high verbosity levels | |
85 | * by modifying g_debug_level. |
|
95 | * by modifying g_debug_level. | |
86 | */ |
|
96 | */ | |
87 |
|
97 | |||
@@ -95,11 +105,12 b' extern "C" {' | |||||
95 |
|
105 | |||
96 | #if (DEBUGLEVEL>=2) |
|
106 | #if (DEBUGLEVEL>=2) | |
97 | # include <stdio.h> |
|
107 | # include <stdio.h> | |
98 |
extern int g_debuglevel; /* he |
|
108 | extern int g_debuglevel; /* the variable is only declared, | |
99 | it actually lives in debug.c, |
|
109 | it actually lives in debug.c, | |
100 | and is shared by the whole process. |
|
110 | and is shared by the whole process. | |
101 |
|
|
111 | It's not thread-safe. | |
102 | on selective conditions (such as position in src) */ |
|
112 | It's useful when enabling very verbose levels | |
|
113 | on selective conditions (such as position in src) */ | |||
103 |
|
114 | |||
104 | # define RAWLOG(l, ...) { \ |
|
115 | # define RAWLOG(l, ...) { \ | |
105 | if (l<=g_debuglevel) { \ |
|
116 | if (l<=g_debuglevel) { \ |
@@ -14,6 +14,10 b'' | |||||
14 |
|
14 | |||
15 | const char* ERR_getErrorString(ERR_enum code) |
|
15 | const char* ERR_getErrorString(ERR_enum code) | |
16 | { |
|
16 | { | |
|
17 | #ifdef ZSTD_STRIP_ERROR_STRINGS | |||
|
18 | (void)code; | |||
|
19 | return "Error strings stripped"; | |||
|
20 | #else | |||
17 | static const char* const notErrorCode = "Unspecified error code"; |
|
21 | static const char* const notErrorCode = "Unspecified error code"; | |
18 | switch( code ) |
|
22 | switch( code ) | |
19 | { |
|
23 | { | |
@@ -39,10 +43,12 b' const char* ERR_getErrorString(ERR_enum ' | |||||
39 | case PREFIX(dictionaryCreation_failed): return "Cannot create Dictionary from provided samples"; |
|
43 | case PREFIX(dictionaryCreation_failed): return "Cannot create Dictionary from provided samples"; | |
40 | case PREFIX(dstSize_tooSmall): return "Destination buffer is too small"; |
|
44 | case PREFIX(dstSize_tooSmall): return "Destination buffer is too small"; | |
41 | case PREFIX(srcSize_wrong): return "Src size is incorrect"; |
|
45 | case PREFIX(srcSize_wrong): return "Src size is incorrect"; | |
|
46 | case PREFIX(dstBuffer_null): return "Operation on NULL destination buffer"; | |||
42 | /* following error codes are not stable and may be removed or changed in a future version */ |
|
47 | /* following error codes are not stable and may be removed or changed in a future version */ | |
43 | case PREFIX(frameIndex_tooLarge): return "Frame index is too large"; |
|
48 | case PREFIX(frameIndex_tooLarge): return "Frame index is too large"; | |
44 | case PREFIX(seekableIO): return "An I/O error occurred when reading/seeking"; |
|
49 | case PREFIX(seekableIO): return "An I/O error occurred when reading/seeking"; | |
45 | case PREFIX(maxCode): |
|
50 | case PREFIX(maxCode): | |
46 | default: return notErrorCode; |
|
51 | default: return notErrorCode; | |
47 | } |
|
52 | } | |
|
53 | #endif | |||
48 | } |
|
54 | } |
@@ -512,7 +512,7 b' MEM_STATIC void FSE_initCState(FSE_CStat' | |||||
512 | const U32 tableLog = MEM_read16(ptr); |
|
512 | const U32 tableLog = MEM_read16(ptr); | |
513 | statePtr->value = (ptrdiff_t)1<<tableLog; |
|
513 | statePtr->value = (ptrdiff_t)1<<tableLog; | |
514 | statePtr->stateTable = u16ptr+2; |
|
514 | statePtr->stateTable = u16ptr+2; | |
515 |
statePtr->symbolTT = |
|
515 | statePtr->symbolTT = ct + 1 + (tableLog ? (1<<(tableLog-1)) : 1); | |
516 | statePtr->stateLog = tableLog; |
|
516 | statePtr->stateLog = tableLog; | |
517 | } |
|
517 | } | |
518 |
|
518 | |||
@@ -531,7 +531,7 b' MEM_STATIC void FSE_initCState2(FSE_CSta' | |||||
531 | } |
|
531 | } | |
532 | } |
|
532 | } | |
533 |
|
533 | |||
534 |
MEM_STATIC void FSE_encodeSymbol(BIT_CStream_t* bitC, FSE_CState_t* statePtr, |
|
534 | MEM_STATIC void FSE_encodeSymbol(BIT_CStream_t* bitC, FSE_CState_t* statePtr, unsigned symbol) | |
535 | { |
|
535 | { | |
536 | FSE_symbolCompressionTransform const symbolTT = ((const FSE_symbolCompressionTransform*)(statePtr->symbolTT))[symbol]; |
|
536 | FSE_symbolCompressionTransform const symbolTT = ((const FSE_symbolCompressionTransform*)(statePtr->symbolTT))[symbol]; | |
537 | const U16* const stateTable = (const U16*)(statePtr->stateTable); |
|
537 | const U16* const stateTable = (const U16*)(statePtr->stateTable); |
@@ -173,15 +173,19 b' typedef U32 HUF_DTable;' | |||||
173 | * Advanced decompression functions |
|
173 | * Advanced decompression functions | |
174 | ******************************************/ |
|
174 | ******************************************/ | |
175 | size_t HUF_decompress4X1 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< single-symbol decoder */ |
|
175 | size_t HUF_decompress4X1 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< single-symbol decoder */ | |
|
176 | #ifndef HUF_FORCE_DECOMPRESS_X1 | |||
176 | size_t HUF_decompress4X2 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< double-symbols decoder */ |
|
177 | size_t HUF_decompress4X2 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< double-symbols decoder */ | |
|
178 | #endif | |||
177 |
|
179 | |||
178 | size_t HUF_decompress4X_DCtx (HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< decodes RLE and uncompressed */ |
|
180 | size_t HUF_decompress4X_DCtx (HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< decodes RLE and uncompressed */ | |
179 | size_t HUF_decompress4X_hufOnly(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< considers RLE and uncompressed as errors */ |
|
181 | size_t HUF_decompress4X_hufOnly(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< considers RLE and uncompressed as errors */ | |
180 | size_t HUF_decompress4X_hufOnly_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< considers RLE and uncompressed as errors */ |
|
182 | size_t HUF_decompress4X_hufOnly_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< considers RLE and uncompressed as errors */ | |
181 | size_t HUF_decompress4X1_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< single-symbol decoder */ |
|
183 | size_t HUF_decompress4X1_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< single-symbol decoder */ | |
182 | size_t HUF_decompress4X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< single-symbol decoder */ |
|
184 | size_t HUF_decompress4X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< single-symbol decoder */ | |
|
185 | #ifndef HUF_FORCE_DECOMPRESS_X1 | |||
183 | size_t HUF_decompress4X2_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< double-symbols decoder */ |
|
186 | size_t HUF_decompress4X2_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< double-symbols decoder */ | |
184 | size_t HUF_decompress4X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< double-symbols decoder */ |
|
187 | size_t HUF_decompress4X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< double-symbols decoder */ | |
|
188 | #endif | |||
185 |
|
189 | |||
186 |
|
190 | |||
187 | /* **************************************** |
|
191 | /* **************************************** | |
@@ -228,7 +232,7 b' size_t HUF_compress4X_repeat(void* dst, ' | |||||
228 | #define HUF_CTABLE_WORKSPACE_SIZE_U32 (2*HUF_SYMBOLVALUE_MAX +1 +1) |
|
232 | #define HUF_CTABLE_WORKSPACE_SIZE_U32 (2*HUF_SYMBOLVALUE_MAX +1 +1) | |
229 | #define HUF_CTABLE_WORKSPACE_SIZE (HUF_CTABLE_WORKSPACE_SIZE_U32 * sizeof(unsigned)) |
|
233 | #define HUF_CTABLE_WORKSPACE_SIZE (HUF_CTABLE_WORKSPACE_SIZE_U32 * sizeof(unsigned)) | |
230 | size_t HUF_buildCTable_wksp (HUF_CElt* tree, |
|
234 | size_t HUF_buildCTable_wksp (HUF_CElt* tree, | |
231 |
const |
|
235 | const unsigned* count, U32 maxSymbolValue, U32 maxNbBits, | |
232 | void* workSpace, size_t wkspSize); |
|
236 | void* workSpace, size_t wkspSize); | |
233 |
|
237 | |||
234 | /*! HUF_readStats() : |
|
238 | /*! HUF_readStats() : | |
@@ -277,14 +281,22 b' U32 HUF_selectDecoder (size_t dstSize, s' | |||||
277 | #define HUF_DECOMPRESS_WORKSPACE_SIZE (2 << 10) |
|
281 | #define HUF_DECOMPRESS_WORKSPACE_SIZE (2 << 10) | |
278 | #define HUF_DECOMPRESS_WORKSPACE_SIZE_U32 (HUF_DECOMPRESS_WORKSPACE_SIZE / sizeof(U32)) |
|
282 | #define HUF_DECOMPRESS_WORKSPACE_SIZE_U32 (HUF_DECOMPRESS_WORKSPACE_SIZE / sizeof(U32)) | |
279 |
|
283 | |||
|
284 | #ifndef HUF_FORCE_DECOMPRESS_X2 | |||
280 | size_t HUF_readDTableX1 (HUF_DTable* DTable, const void* src, size_t srcSize); |
|
285 | size_t HUF_readDTableX1 (HUF_DTable* DTable, const void* src, size_t srcSize); | |
281 | size_t HUF_readDTableX1_wksp (HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize); |
|
286 | size_t HUF_readDTableX1_wksp (HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize); | |
|
287 | #endif | |||
|
288 | #ifndef HUF_FORCE_DECOMPRESS_X1 | |||
282 | size_t HUF_readDTableX2 (HUF_DTable* DTable, const void* src, size_t srcSize); |
|
289 | size_t HUF_readDTableX2 (HUF_DTable* DTable, const void* src, size_t srcSize); | |
283 | size_t HUF_readDTableX2_wksp (HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize); |
|
290 | size_t HUF_readDTableX2_wksp (HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize); | |
|
291 | #endif | |||
284 |
|
292 | |||
285 | size_t HUF_decompress4X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); |
|
293 | size_t HUF_decompress4X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); | |
|
294 | #ifndef HUF_FORCE_DECOMPRESS_X2 | |||
286 | size_t HUF_decompress4X1_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); |
|
295 | size_t HUF_decompress4X1_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); | |
|
296 | #endif | |||
|
297 | #ifndef HUF_FORCE_DECOMPRESS_X1 | |||
287 | size_t HUF_decompress4X2_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); |
|
298 | size_t HUF_decompress4X2_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); | |
|
299 | #endif | |||
288 |
|
300 | |||
289 |
|
301 | |||
290 | /* ====================== */ |
|
302 | /* ====================== */ | |
@@ -306,24 +318,36 b' size_t HUF_compress1X_repeat(void* dst, ' | |||||
306 | HUF_CElt* hufTable, HUF_repeat* repeat, int preferRepeat, int bmi2); |
|
318 | HUF_CElt* hufTable, HUF_repeat* repeat, int preferRepeat, int bmi2); | |
307 |
|
319 | |||
308 | size_t HUF_decompress1X1 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /* single-symbol decoder */ |
|
320 | size_t HUF_decompress1X1 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /* single-symbol decoder */ | |
|
321 | #ifndef HUF_FORCE_DECOMPRESS_X1 | |||
309 | size_t HUF_decompress1X2 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /* double-symbol decoder */ |
|
322 | size_t HUF_decompress1X2 (void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /* double-symbol decoder */ | |
|
323 | #endif | |||
310 |
|
324 | |||
311 | size_t HUF_decompress1X_DCtx (HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); |
|
325 | size_t HUF_decompress1X_DCtx (HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); | |
312 | size_t HUF_decompress1X_DCtx_wksp (HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); |
|
326 | size_t HUF_decompress1X_DCtx_wksp (HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); | |
|
327 | #ifndef HUF_FORCE_DECOMPRESS_X2 | |||
313 | size_t HUF_decompress1X1_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< single-symbol decoder */ |
|
328 | size_t HUF_decompress1X1_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< single-symbol decoder */ | |
314 | size_t HUF_decompress1X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< single-symbol decoder */ |
|
329 | size_t HUF_decompress1X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< single-symbol decoder */ | |
|
330 | #endif | |||
|
331 | #ifndef HUF_FORCE_DECOMPRESS_X1 | |||
315 | size_t HUF_decompress1X2_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< double-symbols decoder */ |
|
332 | size_t HUF_decompress1X2_DCtx(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize); /**< double-symbols decoder */ | |
316 | size_t HUF_decompress1X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< double-symbols decoder */ |
|
333 | size_t HUF_decompress1X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize); /**< double-symbols decoder */ | |
|
334 | #endif | |||
317 |
|
335 | |||
318 | size_t HUF_decompress1X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); /**< automatic selection of sing or double symbol decoder, based on DTable */ |
|
336 | size_t HUF_decompress1X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); /**< automatic selection of sing or double symbol decoder, based on DTable */ | |
|
337 | #ifndef HUF_FORCE_DECOMPRESS_X2 | |||
319 | size_t HUF_decompress1X1_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); |
|
338 | size_t HUF_decompress1X1_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); | |
|
339 | #endif | |||
|
340 | #ifndef HUF_FORCE_DECOMPRESS_X1 | |||
320 | size_t HUF_decompress1X2_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); |
|
341 | size_t HUF_decompress1X2_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable); | |
|
342 | #endif | |||
321 |
|
343 | |||
322 | /* BMI2 variants. |
|
344 | /* BMI2 variants. | |
323 | * If the CPU has BMI2 support, pass bmi2=1, otherwise pass bmi2=0. |
|
345 | * If the CPU has BMI2 support, pass bmi2=1, otherwise pass bmi2=0. | |
324 | */ |
|
346 | */ | |
325 | size_t HUF_decompress1X_usingDTable_bmi2(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int bmi2); |
|
347 | size_t HUF_decompress1X_usingDTable_bmi2(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int bmi2); | |
|
348 | #ifndef HUF_FORCE_DECOMPRESS_X2 | |||
326 | size_t HUF_decompress1X1_DCtx_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int bmi2); |
|
349 | size_t HUF_decompress1X1_DCtx_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int bmi2); | |
|
350 | #endif | |||
327 | size_t HUF_decompress4X_usingDTable_bmi2(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int bmi2); |
|
351 | size_t HUF_decompress4X_usingDTable_bmi2(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int bmi2); | |
328 | size_t HUF_decompress4X_hufOnly_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int bmi2); |
|
352 | size_t HUF_decompress4X_hufOnly_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int bmi2); | |
329 |
|
353 |
@@ -39,6 +39,10 b' extern "C" {' | |||||
39 | # define MEM_STATIC static /* this version may generate warnings for unused static functions; disable the relevant warning */ |
|
39 | # define MEM_STATIC static /* this version may generate warnings for unused static functions; disable the relevant warning */ | |
40 | #endif |
|
40 | #endif | |
41 |
|
41 | |||
|
42 | #ifndef __has_builtin | |||
|
43 | # define __has_builtin(x) 0 /* compat. with non-clang compilers */ | |||
|
44 | #endif | |||
|
45 | ||||
42 | /* code only tested on 32 and 64 bits systems */ |
|
46 | /* code only tested on 32 and 64 bits systems */ | |
43 | #define MEM_STATIC_ASSERT(c) { enum { MEM_static_assert = 1/(int)(!!(c)) }; } |
|
47 | #define MEM_STATIC_ASSERT(c) { enum { MEM_static_assert = 1/(int)(!!(c)) }; } | |
44 | MEM_STATIC void MEM_check(void) { MEM_STATIC_ASSERT((sizeof(size_t)==4) || (sizeof(size_t)==8)); } |
|
48 | MEM_STATIC void MEM_check(void) { MEM_STATIC_ASSERT((sizeof(size_t)==4) || (sizeof(size_t)==8)); } | |
@@ -198,7 +202,8 b' MEM_STATIC U32 MEM_swap32(U32 in)' | |||||
198 | { |
|
202 | { | |
199 | #if defined(_MSC_VER) /* Visual Studio */ |
|
203 | #if defined(_MSC_VER) /* Visual Studio */ | |
200 | return _byteswap_ulong(in); |
|
204 | return _byteswap_ulong(in); | |
201 | #elif defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403) |
|
205 | #elif (defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403)) \ | |
|
206 | || (defined(__clang__) && __has_builtin(__builtin_bswap32)) | |||
202 | return __builtin_bswap32(in); |
|
207 | return __builtin_bswap32(in); | |
203 | #else |
|
208 | #else | |
204 | return ((in << 24) & 0xff000000 ) | |
|
209 | return ((in << 24) & 0xff000000 ) | | |
@@ -212,7 +217,8 b' MEM_STATIC U64 MEM_swap64(U64 in)' | |||||
212 | { |
|
217 | { | |
213 | #if defined(_MSC_VER) /* Visual Studio */ |
|
218 | #if defined(_MSC_VER) /* Visual Studio */ | |
214 | return _byteswap_uint64(in); |
|
219 | return _byteswap_uint64(in); | |
215 | #elif defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403) |
|
220 | #elif (defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403)) \ | |
|
221 | || (defined(__clang__) && __has_builtin(__builtin_bswap64)) | |||
216 | return __builtin_bswap64(in); |
|
222 | return __builtin_bswap64(in); | |
217 | #else |
|
223 | #else | |
218 | return ((in << 56) & 0xff00000000000000ULL) | |
|
224 | return ((in << 56) & 0xff00000000000000ULL) | |
@@ -88,8 +88,8 b' static void* POOL_thread(void* opaque) {' | |||||
88 | ctx->numThreadsBusy++; |
|
88 | ctx->numThreadsBusy++; | |
89 | ctx->queueEmpty = ctx->queueHead == ctx->queueTail; |
|
89 | ctx->queueEmpty = ctx->queueHead == ctx->queueTail; | |
90 | /* Unlock the mutex, signal a pusher, and run the job */ |
|
90 | /* Unlock the mutex, signal a pusher, and run the job */ | |
|
91 | ZSTD_pthread_cond_signal(&ctx->queuePushCond); | |||
91 | ZSTD_pthread_mutex_unlock(&ctx->queueMutex); |
|
92 | ZSTD_pthread_mutex_unlock(&ctx->queueMutex); | |
92 | ZSTD_pthread_cond_signal(&ctx->queuePushCond); |
|
|||
93 |
|
93 | |||
94 | job.function(job.opaque); |
|
94 | job.function(job.opaque); | |
95 |
|
95 |
@@ -30,8 +30,10 b' const char* ZSTD_versionString(void) { r' | |||||
30 | /*-**************************************** |
|
30 | /*-**************************************** | |
31 | * ZSTD Error Management |
|
31 | * ZSTD Error Management | |
32 | ******************************************/ |
|
32 | ******************************************/ | |
|
33 | #undef ZSTD_isError /* defined within zstd_internal.h */ | |||
33 | /*! ZSTD_isError() : |
|
34 | /*! ZSTD_isError() : | |
34 |
* tells if a return value is an error code |
|
35 | * tells if a return value is an error code | |
|
36 | * symbol is required for external callers */ | |||
35 | unsigned ZSTD_isError(size_t code) { return ERR_isError(code); } |
|
37 | unsigned ZSTD_isError(size_t code) { return ERR_isError(code); } | |
36 |
|
38 | |||
37 | /*! ZSTD_getErrorName() : |
|
39 | /*! ZSTD_getErrorName() : |
@@ -72,6 +72,7 b' typedef enum {' | |||||
72 | ZSTD_error_workSpace_tooSmall= 66, |
|
72 | ZSTD_error_workSpace_tooSmall= 66, | |
73 | ZSTD_error_dstSize_tooSmall = 70, |
|
73 | ZSTD_error_dstSize_tooSmall = 70, | |
74 | ZSTD_error_srcSize_wrong = 72, |
|
74 | ZSTD_error_srcSize_wrong = 72, | |
|
75 | ZSTD_error_dstBuffer_null = 74, | |||
75 | /* following error codes are __NOT STABLE__, they can be removed or changed in future versions */ |
|
76 | /* following error codes are __NOT STABLE__, they can be removed or changed in future versions */ | |
76 | ZSTD_error_frameIndex_tooLarge = 100, |
|
77 | ZSTD_error_frameIndex_tooLarge = 100, | |
77 | ZSTD_error_seekableIO = 102, |
|
78 | ZSTD_error_seekableIO = 102, |
@@ -41,6 +41,9 b' extern "C" {' | |||||
41 |
|
41 | |||
42 | /* ---- static assert (debug) --- */ |
|
42 | /* ---- static assert (debug) --- */ | |
43 | #define ZSTD_STATIC_ASSERT(c) DEBUG_STATIC_ASSERT(c) |
|
43 | #define ZSTD_STATIC_ASSERT(c) DEBUG_STATIC_ASSERT(c) | |
|
44 | #define ZSTD_isError ERR_isError /* for inlining */ | |||
|
45 | #define FSE_isError ERR_isError | |||
|
46 | #define HUF_isError ERR_isError | |||
44 |
|
47 | |||
45 |
|
48 | |||
46 | /*-************************************* |
|
49 | /*-************************************* | |
@@ -75,7 +78,6 b' static const U32 repStartValue[ZSTD_REP_' | |||||
75 | #define BIT0 1 |
|
78 | #define BIT0 1 | |
76 |
|
79 | |||
77 | #define ZSTD_WINDOWLOG_ABSOLUTEMIN 10 |
|
80 | #define ZSTD_WINDOWLOG_ABSOLUTEMIN 10 | |
78 | #define ZSTD_WINDOWLOG_DEFAULTMAX 27 /* Default maximum allowed window log */ |
|
|||
79 | static const size_t ZSTD_fcs_fieldSize[4] = { 0, 2, 4, 8 }; |
|
81 | static const size_t ZSTD_fcs_fieldSize[4] = { 0, 2, 4, 8 }; | |
80 | static const size_t ZSTD_did_fieldSize[4] = { 0, 1, 2, 4 }; |
|
82 | static const size_t ZSTD_did_fieldSize[4] = { 0, 1, 2, 4 }; | |
81 |
|
83 | |||
@@ -242,7 +244,7 b' typedef struct {' | |||||
242 | blockType_e blockType; |
|
244 | blockType_e blockType; | |
243 | U32 lastBlock; |
|
245 | U32 lastBlock; | |
244 | U32 origSize; |
|
246 | U32 origSize; | |
245 | } blockProperties_t; |
|
247 | } blockProperties_t; /* declared here for decompress and fullbench */ | |
246 |
|
248 | |||
247 | /*! ZSTD_getcBlockSize() : |
|
249 | /*! ZSTD_getcBlockSize() : | |
248 | * Provides the size of compressed block from block header `src` */ |
|
250 | * Provides the size of compressed block from block header `src` */ | |
@@ -250,6 +252,13 b' typedef struct {' | |||||
250 | size_t ZSTD_getcBlockSize(const void* src, size_t srcSize, |
|
252 | size_t ZSTD_getcBlockSize(const void* src, size_t srcSize, | |
251 | blockProperties_t* bpPtr); |
|
253 | blockProperties_t* bpPtr); | |
252 |
|
254 | |||
|
255 | /*! ZSTD_decodeSeqHeaders() : | |||
|
256 | * decode sequence header from src */ | |||
|
257 | /* Used by: decompress, fullbench (does not get its definition from here) */ | |||
|
258 | size_t ZSTD_decodeSeqHeaders(ZSTD_DCtx* dctx, int* nbSeqPtr, | |||
|
259 | const void* src, size_t srcSize); | |||
|
260 | ||||
|
261 | ||||
253 | #if defined (__cplusplus) |
|
262 | #if defined (__cplusplus) | |
254 | } |
|
263 | } | |
255 | #endif |
|
264 | #endif |
@@ -115,7 +115,7 b' size_t FSE_buildCTable_wksp(FSE_CTable* ' | |||||
115 | /* symbol start positions */ |
|
115 | /* symbol start positions */ | |
116 | { U32 u; |
|
116 | { U32 u; | |
117 | cumul[0] = 0; |
|
117 | cumul[0] = 0; | |
118 | for (u=1; u<=maxSymbolValue+1; u++) { |
|
118 | for (u=1; u <= maxSymbolValue+1; u++) { | |
119 | if (normalizedCounter[u-1]==-1) { /* Low proba symbol */ |
|
119 | if (normalizedCounter[u-1]==-1) { /* Low proba symbol */ | |
120 | cumul[u] = cumul[u-1] + 1; |
|
120 | cumul[u] = cumul[u-1] + 1; | |
121 | tableSymbol[highThreshold--] = (FSE_FUNCTION_TYPE)(u-1); |
|
121 | tableSymbol[highThreshold--] = (FSE_FUNCTION_TYPE)(u-1); | |
@@ -658,7 +658,7 b' size_t FSE_compress_wksp (void* dst, siz' | |||||
658 | BYTE* op = ostart; |
|
658 | BYTE* op = ostart; | |
659 | BYTE* const oend = ostart + dstSize; |
|
659 | BYTE* const oend = ostart + dstSize; | |
660 |
|
660 | |||
661 |
|
|
661 | unsigned count[FSE_MAX_SYMBOL_VALUE+1]; | |
662 | S16 norm[FSE_MAX_SYMBOL_VALUE+1]; |
|
662 | S16 norm[FSE_MAX_SYMBOL_VALUE+1]; | |
663 | FSE_CTable* CTable = (FSE_CTable*)workSpace; |
|
663 | FSE_CTable* CTable = (FSE_CTable*)workSpace; | |
664 | size_t const CTableSize = FSE_CTABLE_SIZE_U32(tableLog, maxSymbolValue); |
|
664 | size_t const CTableSize = FSE_CTABLE_SIZE_U32(tableLog, maxSymbolValue); | |
@@ -672,7 +672,7 b' size_t FSE_compress_wksp (void* dst, siz' | |||||
672 | if (!tableLog) tableLog = FSE_DEFAULT_TABLELOG; |
|
672 | if (!tableLog) tableLog = FSE_DEFAULT_TABLELOG; | |
673 |
|
673 | |||
674 | /* Scan input and build symbol stats */ |
|
674 | /* Scan input and build symbol stats */ | |
675 |
{ CHECK_V_F(maxCount, HIST_count_wksp(count, &maxSymbolValue, src, srcSize, |
|
675 | { CHECK_V_F(maxCount, HIST_count_wksp(count, &maxSymbolValue, src, srcSize, scratchBuffer, scratchBufferSize) ); | |
676 | if (maxCount == srcSize) return 1; /* only a single symbol in src : rle */ |
|
676 | if (maxCount == srcSize) return 1; /* only a single symbol in src : rle */ | |
677 | if (maxCount == 1) return 0; /* each symbol present maximum once => not compressible */ |
|
677 | if (maxCount == 1) return 0; /* each symbol present maximum once => not compressible */ | |
678 | if (maxCount < (srcSize >> 7)) return 0; /* Heuristic : not compressible enough */ |
|
678 | if (maxCount < (srcSize >> 7)) return 0; /* Heuristic : not compressible enough */ |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file copied from mercurial/util.py to mercurial/utils/compression.py |
|
NO CONTENT: file copied from mercurial/util.py to mercurial/utils/compression.py | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
General Comments 0
You need to be logged in to leave comments.
Login now