Skip to content

Feat: safer iCloud traversal + CLI polish#11

Open
alberti42 wants to merge 2 commits intofarnots:masterfrom
alberti42:master
Open

Feat: safer iCloud traversal + CLI polish#11
alberti42 wants to merge 2 commits intofarnots:masterfrom
alberti42:master

Conversation

@alberti42
Copy link

PR Summary

Motivation

Improve the iCloudDownloader CLI so it behaves more predictably with folders (which can appear downloaded even when their contents are not), supports explicit recursive traversal, avoids crawling excluded items, and reduces noisy output while offering a verbose mode when needed. Clean up Xcode user-specific state in the repo.

Changes

  • Added -r for recursive traversal and updated folder handling to enumerate contents properly.
  • Added -h/--help and improved no-argument behavior to show usage guidance.
  • Added -v for verbose output; default mode now prints only errors.
  • Skipped iCloud-excluded items using ubiquitousItemIsExcludedFromSync with macOS availability guards.
  • Deprecated -A in favor of icd $PWD (help/docs updated, warning emitted when used).
  • Refined README usage, examples, and deprecation notes.
  • Added a minimal .gitignore, removed tracked xcuserdata, and moved the scheme to xcshareddata.

Notes

  • Folder handling: the tool enumerates folder contents and downloads files it finds. Without -r, only files directly inside the provided folder are checked and subdirectories are not traversed; -r recurses into subdirectories. Excluded items are skipped.
  • Build verified with xcodebuild in Release mode.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the iCloudDownloader CLI tool with safer folder traversal, improved user experience, and better project hygiene. The changes add recursive directory traversal with the -r flag, implement a verbose mode with -v, deprecate the -A flag, skip iCloud-excluded items, and clean up Xcode user-specific files from the repository.

Changes:

  • Added recursive folder traversal with -r flag and improved folder enumeration to properly handle iCloud content
  • Implemented verbose mode (-v) and help display (-h/--help), with default output showing only errors
  • Added macOS 11.3+ support for excluding items from iCloud sync using availability guards

Reviewed changes

Copilot reviewed 6 out of 8 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
iCloudDownlader/main.swift Refactored argument parsing to support -r, -v, and -h flags; deprecated -A flag with warning
iCloudDownlader/Downloader.swift Added recursive directory traversal, iCloud exclusion checking, and proper folder enumeration
iCloudDownlader/ConsoleIO.swift Added verbose mode support with conditional message output
iCloudDownlader.xcodeproj/project.pbxproj Updated Xcode project settings including deployment target (11.0), build settings, and team configuration
iCloudDownlader.xcodeproj/xcshareddata/xcschemes/iCloudDownlader.xcscheme Moved scheme to shared location for team collaboration
iCloudDownlader.xcodeproj/xcuserdata/lucas.xcuserdatad/xcschemes/xcschememanagement.plist Removed user-specific Xcode data
README.md Updated documentation with new usage examples, options, and compatibility information
.gitignore Added to exclude Xcode user-specific files and build outputs

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

-r Recurse into subdirectories
-v Verbose output
-h Show this help

Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line contains trailing whitespace that should be removed.

Suggested change

Copilot uses AI. Check for mistakes.
let values = try url.resourceValues(forKeys: [.ubiquitousItemIsExcludedFromSyncKey])
return values.ubiquitousItemIsExcludedFromSync ?? false
} catch {
consoleIO.writeMessage("Can't get attributes for \(fm.displayName(atPath: url.lastPathComponent)): \(error)", to: .error)
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name in fm.displayName(atPath:) expects a full path, but url.lastPathComponent only provides the last component of the path. This should be url.path to pass the complete path to get the proper display name.

Copilot uses AI. Check for mistakes.
Without -r, only files directly inside the provided folder are checked.
"""
consoleIO.writeMessage(usage)
} else if isAll {
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the user provides both -A and a path argument (e.g., icd -A /some/path), the code will execute the isAll branch (line 42-43) rather than processing the provided path. This might be confusing behavior. Consider either rejecting this combination with an error message, or giving priority to the path argument if both are provided.

Suggested change
} else if isAll {
} else if isAll && paths.isEmpty {

Copilot uses AI. Check for mistakes.
Comment on lines +156 to 158



Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are excessive blank lines at the end of the file (lines 156-158). Consider reducing this to a single blank line for consistency.

Suggested change

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +17
let isHelpRequested = args.contains("-h") || args.contains("--help")
let isNoArgs = args.isEmpty
let showHelp = isHelpRequested || isNoArgs
let isVerbose = args.contains("-v")
let isRecursive = args.contains("-r")
let isAll = args.contains("-A")
let paths = args.filter { $0 != "-r" && $0 != "-A" && $0 != "-v" && $0 != "-h" && $0 != "--help" }
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current argument parsing logic filters out flags by checking string equality, but it doesn't handle the case where a legitimate path argument might start with a dash (e.g., a file named -test or -r). Consider using a more robust argument parsing approach, such as stopping flag parsing after -- or ensuring that file/folder paths are positional arguments that come after all flags.

Suggested change
let isHelpRequested = args.contains("-h") || args.contains("--help")
let isNoArgs = args.isEmpty
let showHelp = isHelpRequested || isNoArgs
let isVerbose = args.contains("-v")
let isRecursive = args.contains("-r")
let isAll = args.contains("-A")
let paths = args.filter { $0 != "-r" && $0 != "-A" && $0 != "-v" && $0 != "-h" && $0 != "--help" }
let isNoArgs = args.isEmpty
var isHelpRequested = false
var isVerbose = false
var isRecursive = false
var isAll = false
var paths: [String] = []
var parsingFlags = true
for arg in args {
if parsingFlags && arg == "--" {
parsingFlags = false
continue
}
if parsingFlags {
switch arg {
case "-h", "--help":
isHelpRequested = true
case "-v":
isVerbose = true
case "-r":
isRecursive = true
case "-A":
isAll = true
default:
// Treat any non-flag argument as a positional path
paths.append(arg)
}
} else {
// After `--`, treat all arguments as positional paths, even if they start with '-'
paths.append(arg)
}
}
let showHelp = isHelpRequested || isNoArgs

Copilot uses AI. Check for mistakes.
if status.isUbiquitousItem ?? false {
if #available(macOS 11.3, *) {
if status.ubiquitousItemIsExcludedFromSync ?? false {
consoleIO.writeMessage("\(fm.displayName(atPath: fileUrl.lastPathComponent)) is excluded from iCloud sync", to: .warning)
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name in fm.displayName(atPath:) expects a full path, but fileUrl.lastPathComponent only provides the last component. This should be fileUrl.path to pass the complete path to get the proper display name.

Copilot uses AI. Check for mistakes.
}
}
if status.ubiquitousItemDownloadingStatus == .current {
consoleIO.writeMessage("\(fm.displayName(atPath: fileUrl.lastPathComponent)) is already downloaded", to: .warning)
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name in fm.displayName(atPath:) expects a full path, but fileUrl.lastPathComponent only provides the last component. This should be fileUrl.path to pass the complete path to get the proper display name.

Copilot uses AI. Check for mistakes.

## Compatibility

iCloudDownloader should work on macOS 10.12 Sierra and OS X 10.11 El Capitan.
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README states the tool "should work on macOS 10.12 Sierra and OS X 10.11 El Capitan," but the MACOSX_DEPLOYMENT_TARGET is now set to 11.0. This is inconsistent. Additionally, the code uses macOS 11.3-specific APIs (ubiquitousItemIsExcludedFromSyncKey), so the minimum supported version should be updated to macOS 11.0 (or 11.3 for full functionality) in the documentation.

Suggested change
iCloudDownloader should work on macOS 10.12 Sierra and OS X 10.11 El Capitan.
iCloudDownloader requires macOS 11.0 or later. For full functionality (including handling of items excluded from iCloud sync), macOS 11.3 or later is recommended.

Copilot uses AI. Check for mistakes.
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 9V3X7C8VCK;
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DEVELOPMENT_TEAM is hardcoded to a specific team ID (9V3X7C8VCK). This is user-specific configuration that should not be committed to the repository as it will cause build issues for other developers. Consider removing this setting or setting it to an empty string.

Suggested change
DEVELOPMENT_TEAM = 9V3X7C8VCK;
DEVELOPMENT_TEAM = "";

Copilot uses AI. Check for mistakes.
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 9V3X7C8VCK;
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DEVELOPMENT_TEAM is hardcoded to a specific team ID (9V3X7C8VCK). This is user-specific configuration that should not be committed to the repository as it will cause build issues for other developers. Consider removing this setting or setting it to an empty string.

Suggested change
DEVELOPMENT_TEAM = 9V3X7C8VCK;
DEVELOPMENT_TEAM = "";

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments