Source code for betty.os

"""
Provide OS interaction utilities.
"""

from __future__ import annotations

import asyncio
import os
import shutil
from asyncio import gather
from contextlib import suppress
from os import walk
from pathlib import Path
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from collections.abc import Callable, Awaitable






def _link_or_copy(source_file_path: Path, destination_file_path: Path) -> None:
    try:
        _retry_link(source_file_path, destination_file_path)
    except OSError:
        _retry_copyfile(source_file_path, destination_file_path)


def _retry(
    f: Callable[[Path, Path], Any], source_file_path: Path, destination_file_path: Path
) -> None:
    try:
        f(source_file_path, destination_file_path)
    except FileNotFoundError:
        destination_file_path.parent.mkdir(parents=True, exist_ok=True)
        f(source_file_path, destination_file_path)


def _retry_link(source_file_path: Path, destination_file_path: Path) -> None:
    with suppress(FileExistsError):
        _retry(os.link, source_file_path, destination_file_path)


def _retry_copyfile(source_file_path: Path, destination_file_path: Path) -> None:
    with suppress(shutil.SameFileError):
        _retry(shutil.copyfile, source_file_path, destination_file_path)


[docs] async def copy_tree( source_directory_path: Path, destination_directory_path: Path, *, file_callback: Callable[[Path], Awaitable[Any]] | None = None, ) -> None: """ Recursively copy all files in a source directory to a destination. """ await gather( *( _copy_tree_file( source_directory_path / file_path, destination_directory_path / file_path, file_callback=file_callback, ) for file_path in ( Path(directory_path).relative_to(source_directory_path) / file_name for directory_path, _, file_names in walk(str(source_directory_path)) for file_name in file_names ) ) )
async def _copy_tree_file( source_file_path: Path, destination_file_path: Path, *, file_callback: Callable[[Path], Awaitable[Any]] | None = None, ) -> None: await asyncio.to_thread(_retry_copyfile, source_file_path, destination_file_path) if file_callback: await file_callback(destination_file_path)