|
16 | 16 | from .utils import ( |
17 | 17 | classproperty, |
18 | 18 | fspaths_converter, |
19 | | - to_mime_format_name, |
20 | | - IANA_MIME_TYPE_REGISTRIES, |
21 | 19 | describe_task, |
22 | 20 | matching_source, |
23 | 21 | import_extras_module, |
24 | | - SampleFileGenerator, |
| 22 | +) |
| 23 | +from .sampling import SampleFileGenerator |
| 24 | +from .identification import ( |
| 25 | + to_mime_format_name, |
| 26 | + IANA_MIME_TYPE_REGISTRIES, |
25 | 27 | ) |
26 | 28 | from .converter import SubtypeVar |
27 | 29 | from .classifier import Classifier |
|
36 | 38 | ) |
37 | 39 | from .datatype import DataType |
38 | 40 | from . import hook |
| 41 | +from .fs_mount_identifier import FsMountIdentifier |
| 42 | + |
39 | 43 |
|
40 | 44 | try: |
41 | 45 | from typing import Self |
@@ -1181,6 +1185,7 @@ class CopyMode(Enum): |
1181 | 1185 |
|
1182 | 1186 | # All other combinations (typically the result of bit-masking) |
1183 | 1187 |
|
| 1188 | + leave_or_copy = 0b1001 |
1184 | 1189 | leave_or_symlink = 0b0011 |
1185 | 1190 | leave_or_hardlink = 0b0101 |
1186 | 1191 | leave_or_link = 0b0111 |
@@ -1297,20 +1302,49 @@ def copy( |
1297 | 1302 | if isinstance(collation, str) |
1298 | 1303 | else collation |
1299 | 1304 | ) |
1300 | | - if new_stem: |
| 1305 | + # Rule out any copy modes that are not supported given the collation mode |
| 1306 | + # and file-system mounts the paths and destination directory reside on |
| 1307 | + constraints = [] |
| 1308 | + if FsMountIdentifier.on_cifs(dest_dir) and mode & self.CopyMode.symlink: |
| 1309 | + supported_modes -= self.CopyMode.symlink |
| 1310 | + constraint = ( |
| 1311 | + f"Destination directory is on CIFS mount ({dest_dir}) " |
| 1312 | + "and we therefore cannot create a symlink" |
| 1313 | + ) |
| 1314 | + logger.debug(constraint) |
| 1315 | + constraints.append(constraint) |
| 1316 | + not_on_same_mount = [ |
| 1317 | + p for p in self.fspaths if not FsMountIdentifier.on_same_mount(p, dest_dir) |
| 1318 | + ] |
| 1319 | + if not_on_same_mount and mode & self.CopyMode.hardlink: |
| 1320 | + supported_modes -= self.CopyMode.hardlink |
| 1321 | + constraint = ( |
| 1322 | + f"Some paths ({', '.join(str(p) for p in not_on_same_mount)}) are on " |
| 1323 | + f"not on same file-system mount as the destination directory {dest_dir}" |
| 1324 | + "and therefore cannot be hard-linked" |
| 1325 | + ) |
| 1326 | + logger.debug(constraint) |
| 1327 | + constraints.append(constraint) |
| 1328 | + if new_stem or ( |
| 1329 | + collation >= self.CopyCollation.siblings |
| 1330 | + and not all(p.parent == self.parent for p in self.fspaths) |
| 1331 | + ): |
1301 | 1332 | supported_modes -= self.CopyMode.leave |
| 1333 | + |
| 1334 | + # Get the intersection of copy modes that are supported and have been requested |
1302 | 1335 | selected_mode = mode & supported_modes |
1303 | | - if collation >= self.CopyCollation.siblings: |
1304 | | - if not all(p.parent == self.parent for p in self.fspaths): |
1305 | | - selected_mode -= self.CopyMode.leave |
1306 | 1336 | if not selected_mode: |
1307 | | - raise FileFormatsError( |
1308 | | - f"Cannot copy {self} using {mode} mode as it is not supported by " |
1309 | | - f"the {supported_modes} given the collation specification, {collation}" |
| 1337 | + msg = ( |
| 1338 | + f"Cannot copy {self} using '{mode}' mode as it is not supported by " |
| 1339 | + f"the '{supported_modes}' given the collation specification, {collation}" |
1310 | 1340 | ) |
| 1341 | + if constraints: |
| 1342 | + msg += ", and the following constraints:\n" + "\n".join(constraints) |
| 1343 | + raise FileFormatsError(msg) |
1311 | 1344 | if selected_mode & self.CopyMode.leave: |
1312 | 1345 | return self # Don't need to do anything |
1313 | 1346 |
|
| 1347 | + # Select inner copy/link methods |
1314 | 1348 | if selected_mode & self.CopyMode.symlink: |
1315 | 1349 | copy_dir = copy_file = os.symlink |
1316 | 1350 | elif selected_mode & self.CopyMode.hardlink: |
@@ -1339,10 +1373,12 @@ def hardlink_dir(src: Path, dest: Path): |
1339 | 1373 | extension_decomposition=extension_decomposition, |
1340 | 1374 | ) |
1341 | 1375 |
|
1342 | | - dest_dir = Path(dest_dir) # ensure a Path not a string |
| 1376 | + # Prepare destination directory |
| 1377 | + dest_dir = Path(dest_dir) |
1343 | 1378 | if make_dirs: |
1344 | 1379 | dest_dir.mkdir(parents=True, exist_ok=True) |
1345 | 1380 |
|
| 1381 | + # Iterate through the paths to copy, copying them to the destination directory |
1346 | 1382 | new_paths = [] |
1347 | 1383 | for fspath in fspaths_to_copy: |
1348 | 1384 | new_path, fspath = self._new_copy_path( |
|
0 commit comments