mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 07:18:25 +01:00
169 lines
4.4 KiB
Python
169 lines
4.4 KiB
Python
import errno
|
|
import logging
|
|
import os
|
|
from typing import TYPE_CHECKING, List, Optional
|
|
|
|
from .base import RemoteActionNotImplemented
|
|
from .local import LocalFileSystem
|
|
|
|
if TYPE_CHECKING:
|
|
from .base import FileSystem
|
|
from .types import AnyPath
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _link(
|
|
link: "AnyPath",
|
|
from_fs: "FileSystem",
|
|
from_path: "AnyPath",
|
|
to_fs: "FileSystem",
|
|
to_path: "AnyPath",
|
|
) -> None:
|
|
if not isinstance(from_fs, type(to_fs)):
|
|
raise OSError(errno.EXDEV, "can't link across filesystems")
|
|
|
|
try:
|
|
func = getattr(to_fs, link)
|
|
func(from_path, to_path)
|
|
except (OSError, AttributeError, RemoteActionNotImplemented) as exc:
|
|
# NOTE: there are so many potential error codes that a link call can
|
|
# throw, that is safer to just catch them all and try another link.
|
|
raise OSError(
|
|
errno.ENOTSUP, f"'{link}' is not supported by {type(from_fs)}"
|
|
) from exc
|
|
|
|
|
|
def _copy(
|
|
from_fs: "FileSystem",
|
|
from_path: "AnyPath",
|
|
to_fs: "FileSystem",
|
|
to_path: "AnyPath",
|
|
) -> None:
|
|
if isinstance(from_fs, LocalFileSystem):
|
|
return to_fs.upload(from_path, to_path)
|
|
|
|
if isinstance(to_fs, LocalFileSystem):
|
|
return from_fs.download_file(from_path, to_path)
|
|
|
|
with from_fs.open(from_path, mode="rb") as fobj:
|
|
size = from_fs.getsize(from_path)
|
|
return to_fs.upload(fobj, to_path, total=size)
|
|
|
|
|
|
def _try_links(
|
|
links: List["AnyPath"],
|
|
from_fs: "FileSystem",
|
|
from_path: "AnyPath",
|
|
to_fs: "FileSystem",
|
|
to_path: "AnyPath",
|
|
) -> None:
|
|
error = None
|
|
while links:
|
|
link = links[0]
|
|
|
|
if link == "copy":
|
|
return _copy(from_fs, from_path, to_fs, to_path)
|
|
|
|
try:
|
|
return _link(link, from_fs, from_path, to_fs, to_path)
|
|
except OSError as exc:
|
|
if exc.errno not in [errno.ENOTSUP, errno.EXDEV]:
|
|
raise
|
|
error = exc
|
|
|
|
del links[0]
|
|
|
|
raise OSError(
|
|
errno.ENOTSUP, "no more link types left to try out"
|
|
) from error
|
|
|
|
|
|
def transfer(
|
|
from_fs: "FileSystem",
|
|
from_path: "AnyPath",
|
|
to_fs: "FileSystem",
|
|
to_path: "AnyPath",
|
|
hardlink: bool = False,
|
|
links: Optional[List["AnyPath"]] = None,
|
|
) -> None:
|
|
try:
|
|
assert not (hardlink and links)
|
|
if hardlink:
|
|
links = links or ["reflink", "hardlink", "copy"]
|
|
else:
|
|
links = links or ["reflink", "copy"]
|
|
_try_links(links, from_fs, from_path, to_fs, to_path)
|
|
except OSError as exc:
|
|
# If the target file already exists, we are going to simply
|
|
# ignore the exception (#4992).
|
|
#
|
|
# On Windows, it is not always guaranteed that you'll get
|
|
# FileExistsError (it might be PermissionError or a bare OSError)
|
|
# but all of those exceptions raised from the original
|
|
# FileExistsError so we have a separate check for that.
|
|
if isinstance(exc, FileExistsError) or (
|
|
os.name == "nt"
|
|
and exc.__context__
|
|
and isinstance(exc.__context__, FileExistsError)
|
|
):
|
|
logger.debug("'%s' file already exists, skipping", to_path)
|
|
return None
|
|
|
|
raise
|
|
|
|
|
|
def _test_link(
|
|
link: "AnyPath",
|
|
from_fs: "FileSystem",
|
|
from_file: "AnyPath",
|
|
to_fs: "FileSystem",
|
|
to_file: "AnyPath",
|
|
) -> bool:
|
|
try:
|
|
_try_links([link], from_fs, from_file, to_fs, to_file)
|
|
except OSError:
|
|
logger.debug("", exc_info=True)
|
|
return False
|
|
|
|
try:
|
|
_is_link_func = getattr(to_fs, f"is_{link}")
|
|
return _is_link_func(to_file)
|
|
except AttributeError:
|
|
pass
|
|
|
|
return True
|
|
|
|
|
|
def test_links(
|
|
links: List["AnyPath"],
|
|
from_fs: "FileSystem",
|
|
from_path: "AnyPath",
|
|
to_fs: "FileSystem",
|
|
to_path: "AnyPath",
|
|
) -> List["AnyPath"]:
|
|
from dvc.utils import tmp_fname
|
|
|
|
from_file = from_fs.path.join(from_path, tmp_fname())
|
|
to_file = to_fs.path.join(
|
|
to_fs.path.parent(to_path),
|
|
tmp_fname(),
|
|
)
|
|
|
|
from_fs.makedirs(from_fs.path.parent(from_file))
|
|
with from_fs.open(from_file, "wb") as fobj:
|
|
fobj.write(b"test")
|
|
|
|
ret = []
|
|
try:
|
|
for link in links:
|
|
try:
|
|
if _test_link(link, from_fs, from_file, to_fs, to_file):
|
|
ret.append(link)
|
|
finally:
|
|
to_fs.remove(to_file)
|
|
finally:
|
|
from_fs.remove(from_file)
|
|
|
|
return ret
|