Skip to content

Commit

Permalink
mv: add fd-find scandir alternative
Browse files Browse the repository at this point in the history
  • Loading branch information
chapmanjacobd committed Feb 14, 2025
1 parent 26a39ea commit 58bbbfd
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 1 deletion.
7 changes: 6 additions & 1 deletion library/folders/merge_mv.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,12 @@ def gen_src_dest(args, sources, destination, shortcut_allowed=False):
log.debug("taking shortcut: success")
continue
# merge source folder with conflict folder/file
for p in file_utils.rglob_gen(source, args.ext or None):
if shutil.which('fd'): # slightly more reliable on corrupt fs trees
files = file_utils.fd_rglob_gen(source, args.ext or None)
else:
files = file_utils.rglob_gen(source, args.ext or None)

for p in files:
if filter_src(args, p) is False:
log.debug("rglob-file skipped %s", p)
continue
Expand Down
47 changes: 47 additions & 0 deletions library/utils/file_utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import subprocess
import errno, mimetypes, os, shlex, shutil, tempfile, time
from collections import Counter, namedtuple
from fnmatch import fnmatch
Expand Down Expand Up @@ -136,6 +137,52 @@ def rglob_gen(
continue
yield entry.path

def fd_rglob_gen(
base_dir: str | Path,
extensions=None,
exclude=None,
include=None,
):
fd_command = ["fd", "-tf", "-0", base_dir]

if extensions:
ext_args = []
for ext in extensions:
ext_args.extend(["-e", ext if not ext.startswith(".") else ext[1:]])
fd_command.extend(ext_args)

if exclude:
for pattern in exclude:
fd_command.extend(["-E", pattern])

process = subprocess.Popen(fd_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while True:
if process.stdout is None:
break
chunk = process.stdout.read(4096)
if not chunk: # End of stream
break

for path_bytes in chunk.split(b'\0'):
if path_bytes: # empty bytes at the end
path = path_bytes.decode('utf-8')

if include:
if not any(fnmatch(path, pattern) for pattern in include):
continue

yield path

exit_code = process.wait()
if process.stderr:
stderr = process.stderr.read().decode('utf-8')
if stderr:
log.error(f"fd stderr: {stderr}")

if exit_code != 0:
raise subprocess.CalledProcessError(exit_code, fd_command)



def file_temp_copy(src) -> str:
fo_dest = tempfile.NamedTemporaryFile(delete=False)
Expand Down

0 comments on commit 58bbbfd

Please sign in to comment.