py-zippy 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_zippy-1.0.0.dist-info/METADATA +149 -0
- py_zippy-1.0.0.dist-info/RECORD +16 -0
- py_zippy-1.0.0.dist-info/WHEEL +5 -0
- py_zippy-1.0.0.dist-info/entry_points.txt +2 -0
- py_zippy-1.0.0.dist-info/licenses/LICENSE +21 -0
- py_zippy-1.0.0.dist-info/top_level.txt +1 -0
- zippy/__init__.py +8 -0
- zippy/cli.py +286 -0
- zippy/create.py +237 -0
- zippy/extract.py +128 -0
- zippy/list.py +68 -0
- zippy/lock.py +104 -0
- zippy/repair.py +243 -0
- zippy/test.py +153 -0
- zippy/unlock.py +120 -0
- zippy/utils.py +375 -0
zippy/create.py
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
import bz2
|
2
|
+
import gzip
|
3
|
+
import lzma
|
4
|
+
import os
|
5
|
+
import posixpath
|
6
|
+
import shutil
|
7
|
+
import tarfile
|
8
|
+
import zipfile
|
9
|
+
|
10
|
+
try:
|
11
|
+
import pyzipper
|
12
|
+
except ImportError: # pragma: no cover - dependency declared; helpful fallback
|
13
|
+
pyzipper = None
|
14
|
+
|
15
|
+
from .utils import (
|
16
|
+
get_logger,
|
17
|
+
handle_errors,
|
18
|
+
is_single_file_type,
|
19
|
+
loading_animation,
|
20
|
+
requires_external_tool,
|
21
|
+
tar_write_mode,
|
22
|
+
validate_path,
|
23
|
+
get_archive_type,
|
24
|
+
)
|
25
|
+
|
26
|
+
|
27
|
+
logger = get_logger("__name__")
|
28
|
+
|
29
|
+
|
30
|
+
def _parse_input_files(files_to_add):
|
31
|
+
"""Return a list of cleaned input paths."""
|
32
|
+
if isinstance(files_to_add, (list, tuple, set)):
|
33
|
+
raw_items = [str(item).strip() for item in files_to_add]
|
34
|
+
elif isinstance(files_to_add, str):
|
35
|
+
raw_items = [segment.strip() for segment in files_to_add.split(",")]
|
36
|
+
else:
|
37
|
+
handle_errors(
|
38
|
+
"Files to add must be provided as a string or iterable of strings."
|
39
|
+
)
|
40
|
+
cleaned = [item for item in raw_items if item]
|
41
|
+
if not cleaned:
|
42
|
+
handle_errors("No files specified to add to the archive.")
|
43
|
+
# Preserve input order while dropping duplicates.
|
44
|
+
return list(dict.fromkeys(cleaned))
|
45
|
+
|
46
|
+
|
47
|
+
def _sanitize_arcname(original_path, absolute_path):
|
48
|
+
"""Derive a safe archive name based on the provided input path."""
|
49
|
+
if os.path.isabs(original_path):
|
50
|
+
return os.path.basename(absolute_path).replace(os.sep, "/")
|
51
|
+
normalized = os.path.normpath(original_path)
|
52
|
+
while normalized.startswith(".." + os.sep):
|
53
|
+
normalized = normalized[3:]
|
54
|
+
normalized = normalized.lstrip("./")
|
55
|
+
sanitized = normalized or os.path.basename(absolute_path)
|
56
|
+
return sanitized.replace(os.sep, "/")
|
57
|
+
|
58
|
+
|
59
|
+
def _iter_zip_entries(input_specs):
|
60
|
+
"""Yield (source_path, arcname) tuples for zip creation."""
|
61
|
+
for original_path, absolute_path in input_specs:
|
62
|
+
arc_root = _sanitize_arcname(original_path, absolute_path)
|
63
|
+
if os.path.isdir(absolute_path):
|
64
|
+
# Ensure directory entries exist to preserve empty folders.
|
65
|
+
for root, dirs, files in os.walk(absolute_path):
|
66
|
+
rel_root = os.path.relpath(root, absolute_path)
|
67
|
+
if rel_root == ".":
|
68
|
+
current_arc_root = arc_root
|
69
|
+
else:
|
70
|
+
current_arc_root = posixpath.join(
|
71
|
+
arc_root, rel_root.replace(os.sep, "/")
|
72
|
+
)
|
73
|
+
yield None, current_arc_root.rstrip("/") + "/"
|
74
|
+
if not files and not dirs:
|
75
|
+
continue
|
76
|
+
for file_name in files:
|
77
|
+
file_path = os.path.join(root, file_name)
|
78
|
+
arcname = posixpath.join(current_arc_root, file_name)
|
79
|
+
yield file_path, arcname
|
80
|
+
else:
|
81
|
+
yield absolute_path, arc_root
|
82
|
+
|
83
|
+
|
84
|
+
def _collect_input_specs(files_to_add):
|
85
|
+
cleaned = _parse_input_files(files_to_add)
|
86
|
+
specs = []
|
87
|
+
for item in cleaned:
|
88
|
+
absolute = validate_path(item, "File to add", must_exist=True)
|
89
|
+
specs.append((item, absolute))
|
90
|
+
return specs
|
91
|
+
|
92
|
+
|
93
|
+
def _create_archive_internal(
|
94
|
+
archive_path, files_to_add, archive_type, password, verbose, disable_animation
|
95
|
+
):
|
96
|
+
"""
|
97
|
+
Internal function to create an archive with the specified files.
|
98
|
+
|
99
|
+
Parameters:
|
100
|
+
- archive_path (str): Path to the output archive file.
|
101
|
+
- files_to_add (str): Comma-separated list of files/directories to add to the archive.
|
102
|
+
- archive_type (str): Type of the archive (zip, tar, tar.gz, gzip).
|
103
|
+
- password (str): Password for encrypted archives (if applicable).
|
104
|
+
- verbose (bool): Enable verbose output for debugging.
|
105
|
+
- disable_animation (bool): Disable loading animation.
|
106
|
+
|
107
|
+
Raises:
|
108
|
+
- ValueError: If no files are specified to add to the archive.
|
109
|
+
- RuntimeError: If archive creation fails due to various issues.
|
110
|
+
"""
|
111
|
+
specs = _collect_input_specs(files_to_add)
|
112
|
+
if not archive_type:
|
113
|
+
archive_type = get_archive_type(archive_path)
|
114
|
+
if not archive_type:
|
115
|
+
handle_errors(
|
116
|
+
"Could not infer archive type from output path. Please specify with --type (zip, tar, tar.gz, tar.bz2, tar.xz, tar.lzma, gzip, bz2, xz, lzma)."
|
117
|
+
)
|
118
|
+
try:
|
119
|
+
loading_animation(
|
120
|
+
f"Creating {os.path.basename(archive_path)}",
|
121
|
+
duration=2,
|
122
|
+
disable_animation=disable_animation,
|
123
|
+
)
|
124
|
+
if archive_type == "zip":
|
125
|
+
pwd = password.encode("utf-8") if password else None
|
126
|
+
if pwd:
|
127
|
+
if not pyzipper:
|
128
|
+
handle_errors(
|
129
|
+
"Password-protected ZIP creation requires the 'pyzipper' package. "
|
130
|
+
"Install it or omit the password to create an unencrypted archive.",
|
131
|
+
verbose,
|
132
|
+
)
|
133
|
+
with pyzipper.AESZipFile(
|
134
|
+
archive_path,
|
135
|
+
"w",
|
136
|
+
compression=zipfile.ZIP_DEFLATED,
|
137
|
+
encryption=pyzipper.WZ_AES,
|
138
|
+
) as zf:
|
139
|
+
zf.pwd = pwd
|
140
|
+
zf.setencryption(pyzipper.WZ_AES, nbits=256)
|
141
|
+
for source_path, arcname in _iter_zip_entries(specs):
|
142
|
+
if source_path is None:
|
143
|
+
info = zipfile.ZipInfo(
|
144
|
+
arcname if arcname.endswith("/") else arcname + "/"
|
145
|
+
)
|
146
|
+
info.external_attr = 0o755 << 16
|
147
|
+
zf.writestr(info, b"")
|
148
|
+
continue
|
149
|
+
zf.write(source_path, arcname)
|
150
|
+
else:
|
151
|
+
with zipfile.ZipFile(
|
152
|
+
archive_path, "w", compression=zipfile.ZIP_DEFLATED
|
153
|
+
) as zf:
|
154
|
+
for source_path, arcname in _iter_zip_entries(specs):
|
155
|
+
if source_path is None:
|
156
|
+
info = zipfile.ZipInfo(
|
157
|
+
arcname if arcname.endswith("/") else arcname + "/"
|
158
|
+
)
|
159
|
+
info.external_attr = 0o755 << 16
|
160
|
+
zf.writestr(info, b"")
|
161
|
+
continue
|
162
|
+
zf.write(source_path, arcname=arcname)
|
163
|
+
elif archive_type.startswith("tar"):
|
164
|
+
mode = tar_write_mode(archive_type)
|
165
|
+
if not mode:
|
166
|
+
handle_errors(f"Creation for {archive_type} is not supported.")
|
167
|
+
with tarfile.open(archive_path, mode) as tf:
|
168
|
+
for original_path, absolute_path in specs:
|
169
|
+
arcname = _sanitize_arcname(original_path, absolute_path)
|
170
|
+
tf.add(absolute_path, arcname=arcname)
|
171
|
+
elif is_single_file_type(archive_type):
|
172
|
+
if len(specs) != 1:
|
173
|
+
handle_errors(
|
174
|
+
f"{archive_type} archives can only contain a single file. Please specify one file to compress."
|
175
|
+
)
|
176
|
+
_, input_file = specs[0]
|
177
|
+
if os.path.isdir(input_file):
|
178
|
+
handle_errors(
|
179
|
+
f"{archive_type} archives cannot contain directories. Provide a single file."
|
180
|
+
)
|
181
|
+
with open(input_file, "rb") as infile:
|
182
|
+
if archive_type == "gzip":
|
183
|
+
with gzip.open(archive_path, "wb") as gf:
|
184
|
+
shutil.copyfileobj(infile, gf)
|
185
|
+
elif archive_type == "bz2":
|
186
|
+
with bz2.open(archive_path, "wb") as bf:
|
187
|
+
shutil.copyfileobj(infile, bf)
|
188
|
+
elif archive_type == "xz":
|
189
|
+
with lzma.open(archive_path, "wb", format=lzma.FORMAT_XZ) as lf:
|
190
|
+
shutil.copyfileobj(infile, lf)
|
191
|
+
elif archive_type == "lzma":
|
192
|
+
with lzma.open(archive_path, "wb", format=lzma.FORMAT_ALONE) as lf:
|
193
|
+
shutil.copyfileobj(infile, lf)
|
194
|
+
else:
|
195
|
+
handle_errors(f"Creation for {archive_type} is not implemented.")
|
196
|
+
elif requires_external_tool(archive_type):
|
197
|
+
handle_errors(
|
198
|
+
f"Creation for {archive_type} archives requires external tooling (patool + backend binaries).",
|
199
|
+
verbose,
|
200
|
+
)
|
201
|
+
else:
|
202
|
+
handle_errors(f"Creation for {archive_type} not implemented.", verbose)
|
203
|
+
logger.info("Successfully created archive: %s", archive_path)
|
204
|
+
except Exception as e:
|
205
|
+
handle_errors(f"Archive creation failed: {e}", verbose)
|
206
|
+
|
207
|
+
|
208
|
+
def create_archive(
|
209
|
+
archive_path,
|
210
|
+
files_to_add,
|
211
|
+
archive_type=None,
|
212
|
+
password=None,
|
213
|
+
verbose=False,
|
214
|
+
disable_animation=False,
|
215
|
+
):
|
216
|
+
"""
|
217
|
+
Creates an archive with the specified files.
|
218
|
+
|
219
|
+
Parameters:
|
220
|
+
- archive_path (str): Path to the output archive file.
|
221
|
+
- files_to_add (str): Comma-separated list of files/directories to add to the archive.
|
222
|
+
- archive_type (str): Type of the archive (zip, tar, tar.gz, gzip).
|
223
|
+
- password (str): Password for encrypted archives (if applicable).
|
224
|
+
- verbose (bool): Enable verbose output for debugging.
|
225
|
+
- disable_animation (bool): Disable loading animation.
|
226
|
+
|
227
|
+
Raises:
|
228
|
+
- ValueError: If no files are specified to add to the archive.
|
229
|
+
- RuntimeError: If archive creation fails due to various issues.
|
230
|
+
"""
|
231
|
+
if not files_to_add:
|
232
|
+
files_to_add = input(
|
233
|
+
"Files to add not provided. Please enter the files to add (comma-separated): "
|
234
|
+
)
|
235
|
+
_create_archive_internal(
|
236
|
+
archive_path, files_to_add, archive_type, password, verbose, disable_animation
|
237
|
+
)
|
zippy/extract.py
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
import bz2
|
2
|
+
import gzip
|
3
|
+
import lzma
|
4
|
+
import os
|
5
|
+
import shutil
|
6
|
+
import tarfile
|
7
|
+
import zipfile
|
8
|
+
|
9
|
+
try:
|
10
|
+
import pyzipper
|
11
|
+
except ImportError: # pragma: no cover - optional dependency
|
12
|
+
pyzipper = None
|
13
|
+
|
14
|
+
from .utils import (
|
15
|
+
get_logger,
|
16
|
+
get_archive_type,
|
17
|
+
handle_errors,
|
18
|
+
is_single_file_type,
|
19
|
+
loading_animation,
|
20
|
+
requires_external_tool,
|
21
|
+
external_extract,
|
22
|
+
tar_read_mode,
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
logger = get_logger(__name__)
|
27
|
+
|
28
|
+
|
29
|
+
def _extract_with_pyzipper(archive_path, output_path, password, verbose):
|
30
|
+
if not pyzipper:
|
31
|
+
handle_errors(
|
32
|
+
"Archive uses an unsupported ZIP encryption method. Install 'pyzipper' to extract it.",
|
33
|
+
verbose,
|
34
|
+
)
|
35
|
+
if not password:
|
36
|
+
handle_errors("Password is required for encrypted ZIP archives.", verbose)
|
37
|
+
with pyzipper.AESZipFile(archive_path, "r") as zf:
|
38
|
+
zf.pwd = password.encode("utf-8")
|
39
|
+
zf.extractall(output_path)
|
40
|
+
|
41
|
+
|
42
|
+
def extract_archive(
|
43
|
+
archive_path, output_path=".", password=None, verbose=False, disable_animation=False
|
44
|
+
):
|
45
|
+
"""
|
46
|
+
Extracts the contents of an archive to the specified output directory.
|
47
|
+
|
48
|
+
Parameters:
|
49
|
+
- archive_path (str): Path to the archive file.
|
50
|
+
- output_path (str): Directory where the contents will be extracted.
|
51
|
+
- password (str): Password for encrypted archives (if applicable).
|
52
|
+
- verbose (bool): Enable verbose output for debugging.
|
53
|
+
- disable_animation (bool): Disable loading animation.
|
54
|
+
|
55
|
+
Raises:
|
56
|
+
- ValueError: If the archive type is unsupported.
|
57
|
+
- RuntimeError: If extraction fails due to incorrect password or other issues.
|
58
|
+
"""
|
59
|
+
if not output_path:
|
60
|
+
output_path = input(
|
61
|
+
"Output directory not provided. Please enter the output directory: "
|
62
|
+
)
|
63
|
+
archive_type = get_archive_type(archive_path)
|
64
|
+
if not archive_type:
|
65
|
+
handle_errors(f"Unsupported archive type for: {archive_path}")
|
66
|
+
output_path = os.path.abspath(output_path)
|
67
|
+
os.makedirs(output_path, exist_ok=True)
|
68
|
+
try:
|
69
|
+
loading_animation(
|
70
|
+
f"Extracting {os.path.basename(archive_path)} to {output_path}",
|
71
|
+
duration=2,
|
72
|
+
disable_animation=disable_animation,
|
73
|
+
)
|
74
|
+
if archive_type == "zip":
|
75
|
+
try:
|
76
|
+
with zipfile.ZipFile(archive_path, "r") as zf:
|
77
|
+
zf.extractall(
|
78
|
+
output_path, pwd=password.encode("utf-8") if password else None
|
79
|
+
)
|
80
|
+
except RuntimeError as e:
|
81
|
+
lower = str(e).lower()
|
82
|
+
if "password" in lower:
|
83
|
+
handle_errors("Incorrect password for ZIP archive.", verbose)
|
84
|
+
elif "compression method" in lower or "requires" in lower:
|
85
|
+
_extract_with_pyzipper(archive_path, output_path, password, verbose)
|
86
|
+
else:
|
87
|
+
handle_errors(f"ZIP Extraction error: {e}", verbose)
|
88
|
+
except NotImplementedError:
|
89
|
+
_extract_with_pyzipper(archive_path, output_path, password, verbose)
|
90
|
+
elif archive_type.startswith("tar"):
|
91
|
+
mode = tar_read_mode(archive_type)
|
92
|
+
with tarfile.open(archive_path, mode) as tf:
|
93
|
+
try:
|
94
|
+
tf.extractall(output_path)
|
95
|
+
except tarfile.ReadError as e:
|
96
|
+
handle_errors(f"TAR Extraction error: {e}", verbose)
|
97
|
+
elif is_single_file_type(archive_type):
|
98
|
+
output_file = os.path.join(
|
99
|
+
output_path,
|
100
|
+
os.path.splitext(os.path.basename(archive_path))[0],
|
101
|
+
)
|
102
|
+
if archive_type == "gzip":
|
103
|
+
opener = gzip.open
|
104
|
+
open_kwargs = {}
|
105
|
+
elif archive_type == "bz2":
|
106
|
+
opener = bz2.open
|
107
|
+
open_kwargs = {}
|
108
|
+
elif archive_type == "xz":
|
109
|
+
opener = lzma.open
|
110
|
+
open_kwargs = {"format": lzma.FORMAT_XZ}
|
111
|
+
elif archive_type == "lzma":
|
112
|
+
opener = lzma.open
|
113
|
+
open_kwargs = {"format": lzma.FORMAT_ALONE}
|
114
|
+
else:
|
115
|
+
handle_errors(
|
116
|
+
f"Extraction for {archive_type} not implemented.", verbose
|
117
|
+
)
|
118
|
+
return
|
119
|
+
with opener(archive_path, "rb", **open_kwargs) as compressed:
|
120
|
+
with open(output_file, "wb") as outfile:
|
121
|
+
shutil.copyfileobj(compressed, outfile)
|
122
|
+
elif requires_external_tool(archive_type):
|
123
|
+
external_extract(archive_path, output_path, verbose=verbose)
|
124
|
+
else:
|
125
|
+
handle_errors(f"Extraction for {archive_type} not implemented.", verbose)
|
126
|
+
logger.info("Successfully extracted to: %s", output_path)
|
127
|
+
except Exception as e:
|
128
|
+
handle_errors(f"Extraction failed: {e}", verbose)
|
zippy/list.py
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
import os
|
2
|
+
import tarfile
|
3
|
+
import zipfile
|
4
|
+
|
5
|
+
from .utils import (
|
6
|
+
Fore,
|
7
|
+
color_text,
|
8
|
+
get_logger,
|
9
|
+
get_archive_type,
|
10
|
+
handle_errors,
|
11
|
+
is_single_file_type,
|
12
|
+
loading_animation,
|
13
|
+
requires_external_tool,
|
14
|
+
external_list,
|
15
|
+
tar_read_mode,
|
16
|
+
)
|
17
|
+
|
18
|
+
|
19
|
+
logger = get_logger(__name__)
|
20
|
+
|
21
|
+
|
22
|
+
def list_archive_contents(archive_path, verbose=False, disable_animation=False):
|
23
|
+
"""
|
24
|
+
Lists the contents of an archive.
|
25
|
+
|
26
|
+
Parameters:
|
27
|
+
- archive_path (str): Path to the archive file.
|
28
|
+
- verbose (bool): Enable verbose output for debugging.
|
29
|
+
- disable_animation (bool): Disable loading animation.
|
30
|
+
|
31
|
+
Raises:
|
32
|
+
- ValueError: If the archive type is unsupported.
|
33
|
+
- RuntimeError: If listing fails due to various issues.
|
34
|
+
"""
|
35
|
+
archive_type = get_archive_type(archive_path)
|
36
|
+
if not archive_type:
|
37
|
+
handle_errors(f"Unsupported archive type for: {archive_path}")
|
38
|
+
try:
|
39
|
+
loading_animation(
|
40
|
+
f"Listing contents of {os.path.basename(archive_path)}",
|
41
|
+
duration=1,
|
42
|
+
disable_animation=disable_animation,
|
43
|
+
)
|
44
|
+
heading = color_text(
|
45
|
+
f"\nContents of {archive_path}:\n", Fore.MAGENTA if Fore else None
|
46
|
+
)
|
47
|
+
print(heading)
|
48
|
+
if archive_type == "zip":
|
49
|
+
with zipfile.ZipFile(archive_path, "r") as zf:
|
50
|
+
for name in zf.namelist():
|
51
|
+
print(color_text(name, Fore.GREEN if Fore else None))
|
52
|
+
elif archive_type.startswith("tar"):
|
53
|
+
mode = tar_read_mode(archive_type)
|
54
|
+
with tarfile.open(archive_path, mode) as tf:
|
55
|
+
for member in tf.getnames():
|
56
|
+
print(color_text(member, Fore.GREEN if Fore else None))
|
57
|
+
elif is_single_file_type(archive_type):
|
58
|
+
msg = "Single-file compression detected. Use 'extract' to materialise the payload."
|
59
|
+
print(color_text(msg, Fore.YELLOW if Fore else None))
|
60
|
+
elif requires_external_tool(archive_type):
|
61
|
+
for line in external_list(archive_path, verbose=verbose):
|
62
|
+
print(color_text(line, Fore.GREEN if Fore else None))
|
63
|
+
else:
|
64
|
+
handle_errors(
|
65
|
+
f"Listing contents for {archive_type} not implemented.", verbose
|
66
|
+
)
|
67
|
+
except Exception as e:
|
68
|
+
handle_errors(f"Failed to list archive contents: {e}", verbose)
|
zippy/lock.py
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
import os
|
2
|
+
import shutil
|
3
|
+
|
4
|
+
from .utils import (
|
5
|
+
Fore,
|
6
|
+
color_text,
|
7
|
+
extract_archive,
|
8
|
+
get_logger,
|
9
|
+
get_password_interactive,
|
10
|
+
handle_errors,
|
11
|
+
loading_animation,
|
12
|
+
create_archive,
|
13
|
+
)
|
14
|
+
|
15
|
+
|
16
|
+
logger = get_logger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
def lock_archive(
|
20
|
+
archive_path,
|
21
|
+
files_to_add=None,
|
22
|
+
archive_type="zip",
|
23
|
+
password=None,
|
24
|
+
verbose=False,
|
25
|
+
disable_animation=False,
|
26
|
+
):
|
27
|
+
"""
|
28
|
+
Creates a password-protected ZIP archive or re-locks an existing archive with a new password.
|
29
|
+
|
30
|
+
Parameters:
|
31
|
+
- archive_path (str): Path to the archive file.
|
32
|
+
- files_to_add (str): Comma-separated list of files/directories to add to the archive.
|
33
|
+
- archive_type (str): Type of the archive (default: "zip").
|
34
|
+
- password (str): Password for the archive.
|
35
|
+
- verbose (bool): Enable verbose output for debugging.
|
36
|
+
- disable_animation (bool): Disable loading animation.
|
37
|
+
|
38
|
+
Raises:
|
39
|
+
- ValueError: If the archive type is unsupported or no files are specified.
|
40
|
+
- RuntimeError: If locking fails due to incorrect password or other issues.
|
41
|
+
"""
|
42
|
+
if archive_type != "zip":
|
43
|
+
handle_errors(
|
44
|
+
"Locking (password protection) is only supported for ZIP archives."
|
45
|
+
)
|
46
|
+
if not password:
|
47
|
+
password = get_password_interactive()
|
48
|
+
if not files_to_add:
|
49
|
+
files_to_add = input(
|
50
|
+
"Files to add not provided. Please enter the files to add (comma-separated): "
|
51
|
+
)
|
52
|
+
if files_to_add:
|
53
|
+
logger.debug("Locking archive '%s' with provided file list.", archive_path)
|
54
|
+
create_archive(
|
55
|
+
archive_path,
|
56
|
+
files_to_add,
|
57
|
+
archive_type,
|
58
|
+
password,
|
59
|
+
verbose,
|
60
|
+
disable_animation,
|
61
|
+
)
|
62
|
+
else:
|
63
|
+
if not os.path.exists(archive_path):
|
64
|
+
handle_errors(
|
65
|
+
f"Archive file not found: {archive_path}. Cannot lock a non-existent archive."
|
66
|
+
)
|
67
|
+
temp_dir = "zippsnipp_temp_relock"
|
68
|
+
os.makedirs(temp_dir, exist_ok=True)
|
69
|
+
try:
|
70
|
+
loading_animation(
|
71
|
+
f"Re-locking {os.path.basename(archive_path)} with password",
|
72
|
+
duration=2,
|
73
|
+
disable_animation=disable_animation,
|
74
|
+
)
|
75
|
+
extract_archive(
|
76
|
+
archive_path, temp_dir, verbose=verbose, disable_animation=True
|
77
|
+
)
|
78
|
+
extracted_items = sorted(os.listdir(temp_dir))
|
79
|
+
if not extracted_items:
|
80
|
+
handle_errors("Extracted archive is empty; nothing to lock.")
|
81
|
+
original_cwd = os.getcwd()
|
82
|
+
try:
|
83
|
+
os.chdir(temp_dir)
|
84
|
+
files_argument = ",".join(extracted_items)
|
85
|
+
create_archive(
|
86
|
+
archive_path,
|
87
|
+
files_argument,
|
88
|
+
"zip",
|
89
|
+
password,
|
90
|
+
verbose,
|
91
|
+
disable_animation=True,
|
92
|
+
)
|
93
|
+
finally:
|
94
|
+
os.chdir(original_cwd)
|
95
|
+
shutil.rmtree(temp_dir)
|
96
|
+
logger.info(
|
97
|
+
color_text(
|
98
|
+
f"Successfully re-locked archive: {archive_path} (Password protected)",
|
99
|
+
Fore.GREEN if Fore else None,
|
100
|
+
)
|
101
|
+
)
|
102
|
+
except Exception as e:
|
103
|
+
shutil.rmtree(temp_dir, ignore_errors=True)
|
104
|
+
handle_errors(f"Failed to re-lock archive: {e}", verbose)
|