stouputils.archive — stouputils 1.0.0 documentation

# Source code for stouputils.archive

    """
    This module provides functions for creating and managing archives.

    - make_archive: Make an archive with consistency using FILES_TO_WRITE variable
    - repair_zip_file: Try to repair a corrupted zip file (NOT IMPLEMENTED)
    """

    # Imports
    from .io import *
    from .print import *
    from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED

    # Function that makes an archive with consistency (same zip file each time)

    @handle_error()
    def make_archive(
    	source: str,
    	destinations: list[str]|str = [],
    	override_time: None | tuple[int, int, int, int, int, int] = None,
    	create_dir: bool = False
    ) -> bool:
    	""" Make an archive with consistency using FILES_TO_WRITE variable

    	Args:
    		source				(str):						The source folder to archive
    		destinations		(list[str]|str):			The destination folder(s) or file(s) to copy the archive to
    		override_time		(None | tuple[int, ...]):	The constant time to use for the archive (e.g. (2024, 1, 1, 0, 0, 0) for 2024-01-01 00:00:00)
    		create_dir			(bool):						Whether to create the destination directory if it doesn't exist (default: False)
    	Returns:
    		bool: Always returns True unless any strong error
    	Examples:

    	.. code-block:: python

    		> make_archive("/path/to/source", "/path/to/destination.zip")
    		> make_archive("/path/to/source", ["/path/to/destination.zip", "/path/to/destination2.zip"])
    	"""
    	# Fix copy_destinations type if needed
    	if destinations and isinstance(destinations, str):
    		destinations = [destinations]
    	if not destinations:
    		raise ValueError("destinations must be a list of at least one destination")

    	# Create the archive
    	destination: str = clean_path(destinations[0])
    	destination = destination if ".zip" in destination else destination + ".zip"
    	with ZipFile(destination, "w", compression=ZIP_DEFLATED, compresslevel=9) as zip:
    		for root, _, files in os.walk(source):
    			for file in files:
    				file_path: str = clean_path(os.path.join(root, file))
    				info: ZipInfo = ZipInfo(file_path)
    				info.compress_type = ZIP_DEFLATED
    				if override_time:
    					info.date_time = override_time
    				with open(file_path, "rb") as f:
    					zip.writestr(info, f.read())

    	# Copy the archive to the destination(s)
    	for dest_folder in destinations[1:]:
    		@handle_error(Exception, message=f"Unable to copy '{destination}' to '{dest_folder}'", error_log=LogLevels.WARNING)
    		def internal(src: str, dest: str) -> None:
    			super_copy(src, dest, create_dir=create_dir)
    		internal(destination, clean_path(dest_folder))

    	return True

    # Function that repair a corrupted zip file (ignoring some of the errors)
    from .dont_look.zip_file_override import ZipFileOverride

    @handle_error()
    def repair_zip_file(file_path: str, destination: str) -> bool:
    	""" Try to repair a corrupted zip file by ignoring some of the errors

    	Args:
    		file_path		(str):	Path of the zip file to repair
    		destination		(str):	Destination of the new file
    	Returns:
    		bool: Always returns True unless any strong error

    	Examples:

    	.. code-block:: python

    		> repair_zip_file("/path/to/source.zip", "/path/to/destination.zip")
    	"""
    	# Check
    	if not os.path.exists(file_path):
    		raise FileNotFoundError(f"File '{file_path}' not found")
    	dirname: str = os.path.dirname(destination)
    	if dirname and not os.path.exists(dirname):
    		raise FileNotFoundError(f"Directory '{dirname}' not found")

    	# Read it
    	with ZipFileOverride(file_path, "r") as zip_file:

    		# Get a list of all the files in the ZIP file
    		file_list: list[str] = zip_file.namelist()

    		# Create a new ZIP file at the destination
    		with ZipFileOverride(destination, "w", compression=ZIP_DEFLATED) as new_zip_file:
    			for file_name in file_list:
    				try:
    					new_zip_file.writestr(file_name, zip_file.read(file_name))
    				except KeyboardInterrupt:
    					continue

    	return True