92 lines
3.6 KiB
Python
Executable File
92 lines
3.6 KiB
Python
Executable File
# ***** BEGIN GPL LICENSE BLOCK *****
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
#
|
|
# ***** END GPL LICENCE BLOCK *****
|
|
#
|
|
# (c) 2018, Blender Foundation - Sybren A. Stüvel
|
|
"""ZIP file packer.
|
|
|
|
Note: There is no official file name encoding for ZIP files. Expect trouble
|
|
when you want to use the ZIP cross-platform and you have non-ASCII names.
|
|
"""
|
|
import logging
|
|
import pathlib
|
|
|
|
from . import Packer, transfer
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
# Suffixes to store uncompressed in the zip.
|
|
STORE_ONLY = {".jpg", ".jpeg", ".exr"}
|
|
|
|
|
|
class ZipPacker(Packer):
|
|
"""Creates a zipped BAT Pack instead of a directory."""
|
|
|
|
def _create_file_transferer(self) -> transfer.FileTransferer:
|
|
target_path = pathlib.Path(self._target_path)
|
|
return ZipTransferrer(target_path.absolute())
|
|
|
|
|
|
class ZipTransferrer(transfer.FileTransferer):
|
|
"""Creates a ZIP file instead of writing to a directory.
|
|
|
|
Note: There is no official file name encoding for ZIP files. If you have
|
|
unicode file names, they will be encoded as UTF-8. WinZip interprets all
|
|
file names as encoded in CP437, also known as DOS Latin.
|
|
"""
|
|
|
|
def __init__(self, zippath: pathlib.Path) -> None:
|
|
super().__init__()
|
|
self.zippath = zippath
|
|
|
|
def run(self) -> None:
|
|
import zipfile
|
|
|
|
zippath = self.zippath.absolute()
|
|
|
|
with zipfile.ZipFile(str(zippath), "w") as outzip:
|
|
for src, dst, act in self.iter_queue():
|
|
assert src.is_absolute(), "expecting only absolute paths, not %r" % src
|
|
|
|
dst = pathlib.Path(dst).absolute()
|
|
try:
|
|
relpath = dst.relative_to(zippath)
|
|
|
|
# Don't bother trying to compress already-compressed files.
|
|
if src.suffix.lower() in STORE_ONLY:
|
|
compression = zipfile.ZIP_STORED
|
|
log.debug("ZIP %s -> %s (uncompressed)", src, relpath)
|
|
else:
|
|
compression = zipfile.ZIP_DEFLATED
|
|
log.debug("ZIP %s -> %s", src, relpath)
|
|
outzip.write(
|
|
str(src), arcname=str(relpath), compress_type=compression
|
|
)
|
|
|
|
if act == transfer.Action.MOVE:
|
|
self.delete_file(src)
|
|
except Exception:
|
|
# We have to catch exceptions in a broad way, as this is running in
|
|
# a separate thread, and exceptions won't otherwise be seen.
|
|
log.exception("Error transferring %s to %s", src, dst)
|
|
# Put the files to copy back into the queue, and abort. This allows
|
|
# the main thread to inspect the queue and see which files were not
|
|
# copied. The one we just failed (due to this exception) should also
|
|
# be reported there.
|
|
self.queue.put((src, dst, act))
|
|
return
|