Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for depth and case sensitivity in directory exclusions, by directory #110

Merged
merged 4 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 36 additions & 10 deletions smbclientng/core/SMBSession.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def ping_smb_session(self):

# Operations

def find(self, paths=[], callback=None, excluded_dirs=[], exclude_dir_depth=0):
def find(self, paths=[], callback=None, exclusion_rules=[]):
"""
Finds files and directories on the SMB share based on the provided paths and executes a callback function on each entry.

Expand All @@ -250,12 +250,37 @@ def find(self, paths=[], callback=None, excluded_dirs=[], exclude_dir_depth=0):
paths (list, optional): A list of paths to start the search from. Defaults to an empty list.
callback (function, optional): A function to be called on each entry found. The function should accept three arguments:
the entry object, the full path of the entry, and the current depth of recursion. Defaults to None.
excluded_dirs (list, optional): A list of directories to exclude from the search. Defaults to None.
exclusion_rules (list, optional): A list of exclusion rules, each being a dictionary with keys:
'dirname', 'depth', 'case_sensitive'.

Note:
If the callback function is None, the method will print an error message and return without performing any action.
"""

def should_exclude(dir_name, depth):
"""
Determines whether a directory should be excluded based on the exclusion rules.

Args:
dir_name (str): The name of the directory.
depth (int): The current depth in the traversal.

Returns:
bool: True if the directory should be excluded, False otherwise.
"""
for rule in exclusion_rules:
# Check if the depth is within the exclusion range
if rule['depth'] != -1 and depth > rule['depth']:
continue # Current depth is beyond the specified depth, do not exclude
# Perform matching based on case sensitivity
if rule['case_sensitive']:
if dir_name == rule['dirname']:
return True
else:
if dir_name.lower() == rule['dirname'].lower():
return True
return False

def recurse_action(paths=[], depth=0, callback=None):
if callback is None:
return []
Expand All @@ -264,10 +289,11 @@ def recurse_action(paths=[], depth=0, callback=None):

for path in paths:
normalized_path = ntpath.normpath(path)
# Exclude paths that are in the exclude list based on depth
if exclude_dir_depth == -1 or depth == exclude_dir_depth:
if any(excluded.lower() == ntpath.basename(normalized_path).lower() for excluded in excluded_dirs):
continue # Skip this path
dir_name = ntpath.basename(normalized_path)

# Check if current path should be excluded
if should_exclude(dir_name, depth):
continue # Skip this path

remote_smb_path = ntpath.normpath(self.smb_cwd + ntpath.sep + path)
entries = []
Expand All @@ -285,13 +311,13 @@ def recurse_action(paths=[], depth=0, callback=None):
entries = sorted(entries, key=lambda x: x.get_longname().lower())

for entry in entries:
entry_name = entry.get_longname()
fullpath = ntpath.join(path, entry.get_longname())

if entry.is_directory():
# Exclude directories during traversal based on exclude_dir_depth
if exclude_dir_depth == -1 or depth + 1 == exclude_dir_depth:
if any(excluded.lower() == entry.get_longname().lower() for excluded in excluded_dirs):
continue # Skip this directory
# Check if this directory should be excluded
if should_exclude(entry_name, depth + 1):
continue # Skip this directory

next_directories_to_explore.append(fullpath)

Expand Down
53 changes: 49 additions & 4 deletions smbclientng/modules/Find.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@ def parseArgs(self, arguments):
parser.add_argument("-iname", type=str, help="Like -name, but the match is case insensitive.")
parser.add_argument("-type", type=str, default=None, help="File type (e.g., f for regular file, d for directory).")
parser.add_argument("-size", type=str, help="File uses n units of space.")
parser.add_argument("-exclude-dir", type=str, action='append', default=[], help="Subdirectories to exclude from the search.")
parser.add_argument("-exclude-dir-depth", type=int, default=0, help="Specify the depth at which to exclude directories. Use -1 to exclude directories at all levels. Default is 0 (initial level only).")
parser.add_argument('--exclude-dir', action='append', default=[], metavar='DIRNAME[:DEPTH[:CASE]]',
help=("Exclude directories matching DIRNAME until specified depth and case sensitivity. "
"DEPTH specifies the recursion depth (-1 for all depths, default is 0). "
"CASE can be 'i' for case-insensitive or 's' for case-sensitive (default). "
"Format: DIRNAME[:DEPTH[:CASE]]"))
# parser.add_argument("-mtime", type=str, help="File's data was last modified n*24 hours ago")
# parser.add_argument("-ctime", type=str, help="File's status was last changed n*24 hours ago")
# parser.add_argument("-atime", type=str, help="File was last accessed n*24 hours ago")
Expand All @@ -74,6 +77,47 @@ def parseArgs(self, arguments):
self.options = None

return self.options

def parse_exclude_dirs(self, exclude_dirs):
"""
Parses the exclude directory arguments and returns a list of exclusion rules.

Each exclusion rule is a dictionary with keys:
- 'dirname': The directory name to exclude.
- 'depth': The depth until which to exclude the directory (-1 for all depths).
- 'case_sensitive': Boolean indicating if the match is case-sensitive.
"""
exclusion_rules = []
for item in exclude_dirs:
parts = item.split(':')
dirname = parts[0]
depth = 0 # Default depth
case_sensitive = False # Default to case-insensitive

# Parse depth if provided
if len(parts) > 1 and parts[1]:
try:
depth = int(parts[1])
except ValueError:
depth = 0 # Default if depth is invalid

# Parse case sensitivity if provided
if len(parts) > 2 and parts[2]:
case_flag = parts[2].lower()
if case_flag == 's':
case_sensitive = True
elif case_flag == 'i':
case_sensitive = False
else:
# Invalid case flag, default to case-sensitive
case_sensitive = True

exclusion_rules.append({
'dirname': dirname,
'depth': depth,
'case_sensitive': case_sensitive
})
return exclusion_rules

def __find_callback(self, entry, fullpath, depth):
# Documentation for __find_callback function
Expand Down Expand Up @@ -243,11 +287,12 @@ def run(self, arguments):
next_directories_to_explore.append(ntpath.normpath(path) + ntpath.sep)
next_directories_to_explore = sorted(list(set(next_directories_to_explore)))

exclusion_rules = self.parse_exclude_dirs(self.options.exclude_dir)

self.smbSession.find(
paths=next_directories_to_explore,
callback=self.__find_callback,
excluded_dirs=self.options.exclude_dir,
exclude_dir_depth=self.options.exclude_dir_depth
exclusion_rules=exclusion_rules
)

except (BrokenPipeError, KeyboardInterrupt) as e:
Expand Down
Loading