|
1 | 1 | import asyncio
|
2 |
| -import bz2 |
3 |
| -import gzip |
4 | 2 | import hashlib
|
5 |
| -import io |
6 | 3 | import shutil
|
7 |
| -import tarfile |
8 | 4 | from dataclasses import dataclass
|
9 | 5 | from pathlib import Path
|
10 | 6 | from typing import Sequence
|
11 | 7 |
|
12 | 8 | import aiofiles
|
13 | 9 | import httpx
|
14 |
| -import unix_ar |
15 |
| -import zstandard |
16 | 10 |
|
17 | 11 | from ops2deb import logger
|
18 | 12 | from ops2deb.client import client_factory
|
19 |
| -from ops2deb.exceptions import Ops2debError, Ops2debExtractError, Ops2debFetcherError |
| 13 | +from ops2deb.exceptions import Ops2debError, Ops2debFetcherError |
| 14 | +from ops2deb.extracter import extract_archive, is_archive_format_supported |
20 | 15 | from ops2deb.lockfile import Lock
|
21 | 16 | from ops2deb.parser import Configuration
|
22 | 17 | from ops2deb.utils import log_and_raise, separate_results_from_errors
|
23 | 18 |
|
24 | 19 | DEFAULT_CACHE_DIRECTORY = Path("/tmp/ops2deb_cache")
|
25 | 20 |
|
26 | 21 |
|
27 |
| -def _unpack_gz(file_path: str, extract_path: str) -> None: |
28 |
| - output_path = Path(extract_path) / Path(file_path).stem |
29 |
| - with output_path.open("wb") as output: |
30 |
| - with gzip.open(file_path, "rb") as gz_archive: |
31 |
| - shutil.copyfileobj(gz_archive, output) |
32 |
| - |
33 |
| - |
34 |
| -def _unpack_bz2(file_path: str, extract_path: str) -> None: |
35 |
| - output_path = Path(extract_path) / Path(file_path).stem |
36 |
| - with output_path.open(mode="wb") as output: |
37 |
| - with bz2.open(file_path, "rb") as bz2_archive: |
38 |
| - shutil.copyfileobj(bz2_archive, output) |
39 |
| - |
40 |
| - |
41 |
| -def _unpack_deb(file_path: str, extract_path: str) -> None: |
42 |
| - ar_file = unix_ar.open(file_path) |
43 |
| - file_names = [info.name.decode("utf-8") for info in ar_file.infolist()] |
44 |
| - for file_name in file_names: |
45 |
| - if file_name.startswith("debian-binary"): |
46 |
| - continue |
47 |
| - tarball = ar_file.open(file_name) |
48 |
| - tar_file = tarfile.open(fileobj=tarball) |
49 |
| - try: |
50 |
| - tar_file.extractall(Path(extract_path) / file_name.split(".")[0]) |
51 |
| - finally: |
52 |
| - tar_file.close() |
53 |
| - |
54 |
| - |
55 |
| -def _unpack_zst(file_path: str, extract_path: str) -> None: |
56 |
| - output_path = Path(extract_path) / Path(file_path).stem |
57 |
| - dctx = zstandard.ZstdDecompressor() |
58 |
| - with open(file_path, "rb") as ifh, output_path.open("wb") as ofh: |
59 |
| - dctx.copy_stream(ifh, ofh) |
60 |
| - |
61 |
| - |
62 |
| -def _unpack_tar_zst(file_path: str, extract_path: str) -> None: |
63 |
| - dctx = zstandard.ZstdDecompressor() |
64 |
| - with open(file_path, "rb") as ifh, io.BytesIO() as ofh: |
65 |
| - dctx.copy_stream(ifh, ofh) |
66 |
| - ofh.seek(0) |
67 |
| - with tarfile.open(fileobj=ofh) as tar_file: |
68 |
| - tar_file.extractall(extract_path) |
69 |
| - |
70 |
| - |
71 |
| -shutil.register_unpack_format("gz", [".gz"], _unpack_gz) |
72 |
| -shutil.register_unpack_format("bz2", [".bz2"], _unpack_bz2) |
73 |
| -shutil.register_unpack_format("deb", [".deb"], _unpack_deb) |
74 |
| -shutil.register_unpack_format("zsttar", [".tar.zst"], _unpack_tar_zst) |
75 |
| -shutil.register_unpack_format("zst", [".zst"], _unpack_zst) |
76 |
| - |
77 |
| - |
78 | 22 | async def _download_file(url: str, download_path: Path) -> None:
|
79 | 23 | tmp_path = f"{download_path}.part"
|
80 | 24 | logger.info(f"Downloading {download_path.name}...")
|
@@ -106,32 +50,6 @@ async def _hash_file(file_path: Path) -> str:
|
106 | 50 | return sha256_hash.hexdigest()
|
107 | 51 |
|
108 | 52 |
|
109 |
| -def is_archive_format_supported(archive_path: Path) -> bool: |
110 |
| - for name, extensions, _ in shutil.get_unpack_formats(): |
111 |
| - for extension in extensions: |
112 |
| - if archive_path.name.endswith(extension): |
113 |
| - return True |
114 |
| - return False |
115 |
| - |
116 |
| - |
117 |
| -async def extract_archive(archive_path: Path, extract_path: Path) -> None: |
118 |
| - tmp_extract_path = f"{extract_path}_tmp" |
119 |
| - Path(tmp_extract_path).mkdir(exist_ok=True) |
120 |
| - logger.info(f"Extracting {archive_path.name}...") |
121 |
| - |
122 |
| - try: |
123 |
| - await asyncio.get_running_loop().run_in_executor( |
124 |
| - None, shutil.unpack_archive, archive_path, tmp_extract_path |
125 |
| - ) |
126 |
| - except Exception as e: |
127 |
| - error = f"Failed to extract archive {archive_path}" |
128 |
| - if str(e): |
129 |
| - error += f" ({e})" |
130 |
| - log_and_raise(Ops2debExtractError(error)) |
131 |
| - |
132 |
| - shutil.move(tmp_extract_path, extract_path) |
133 |
| - |
134 |
| - |
135 | 53 | @dataclass
|
136 | 54 | class FetchResult:
|
137 | 55 | url: str
|
|
0 commit comments