countFilesRoundedRg function
Count files in a directory recursively using ripgrep and round to the nearest power of 10 for privacy.
This is much more efficient than using native Dart methods for counting files in large directories since it uses ripgrep's highly optimized file traversal.
dirPath — Directory path to count files in.
ignorePatterns — Optional additional patterns to ignore (beyond .gitignore).
Returns approximate file count rounded to the nearest power of 10.
Implementation
Future<int?> countFilesRoundedRg(
String dirPath, {
List<String> ignorePatterns = const [],
Duration? timeout,
}) async {
// Cache key includes ignore patterns.
final cacheKey = '$dirPath|${ignorePatterns.join(',')}';
if (_fileCountCache.containsKey(cacheKey)) {
return _fileCountCache[cacheKey];
}
// Skip file counting if we're in the home directory to avoid triggering
// macOS TCC permission dialogs for Desktop, Downloads, Documents, etc.
final homeDir =
Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'] ?? '';
if (p.equals(p.canonicalize(dirPath), p.canonicalize(homeDir))) {
_fileCountCache[cacheKey] = null;
return null;
}
try {
// Build ripgrep arguments:
// --files: List files that would be searched
// --hidden: Search hidden files and directories
final args = ['--files', '--hidden'];
// Add ignore patterns if provided.
for (final pattern in ignorePatterns) {
args.addAll(['--glob', '!$pattern']);
}
final count = await ripGrepFileCount(args, dirPath, timeout: timeout);
// Round to nearest power of 10 for privacy.
if (count == 0) {
_fileCountCache[cacheKey] = 0;
return 0;
}
final magnitude = (log(count) / ln10).floor();
final power = pow(10, magnitude).toInt();
// Round to nearest power of 10.
// e.g., 8 -> 10, 42 -> 100, 350 -> 100, 750 -> 1000
final rounded = ((count / power).round()) * power;
_fileCountCache[cacheKey] = rounded;
return rounded;
} catch (error) {
// Timeout is expected on large/slow repos, not an error.
_fileCountCache[cacheKey] = null;
return null;
}
}