├── DevCleaner
├── Resources
│ ├── Manual
│ │ ├── file.png
│ │ ├── menu.png
│ │ ├── file-dark.png
│ │ ├── file@2x.png
│ │ ├── menu-dark.png
│ │ ├── menu@2x.png
│ │ ├── Icon-128pt.png
│ │ ├── main-window.png
│ │ ├── preferences.png
│ │ ├── Icon-128pt@2x.png
│ │ ├── file-dark@2x.png
│ │ ├── main-window@2x.png
│ │ ├── menu-dark@2x.png
│ │ ├── preferences@2x.png
│ │ ├── main-window-dark.png
│ │ ├── preferences-dark.png
│ │ ├── main-window-dark@2x.png
│ │ ├── preferences-dark@2x.png
│ │ └── manual.css
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── OS
│ │ │ ├── iOS
│ │ │ │ ├── 2.imageset
│ │ │ │ │ ├── 2.png
│ │ │ │ │ ├── 2@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 3.imageset
│ │ │ │ │ ├── 3.png
│ │ │ │ │ ├── 3@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 4.imageset
│ │ │ │ │ ├── 4.png
│ │ │ │ │ ├── 4@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 5.imageset
│ │ │ │ │ ├── 5.png
│ │ │ │ │ ├── 5@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 6.imageset
│ │ │ │ │ ├── 6.png
│ │ │ │ │ ├── 6@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 7.imageset
│ │ │ │ │ ├── 7.png
│ │ │ │ │ ├── 7@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 8.imageset
│ │ │ │ │ ├── 8.png
│ │ │ │ │ ├── 8@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 9.imageset
│ │ │ │ │ ├── 9.png
│ │ │ │ │ ├── 9@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 10.imageset
│ │ │ │ │ ├── 10.png
│ │ │ │ │ ├── 10@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 11.imageset
│ │ │ │ │ ├── 11.png
│ │ │ │ │ ├── 11@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 12.imageset
│ │ │ │ │ ├── 12.png
│ │ │ │ │ ├── 12@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 13.imageset
│ │ │ │ │ ├── 13.png
│ │ │ │ │ ├── 13@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 14.imageset
│ │ │ │ │ ├── 14.png
│ │ │ │ │ ├── 14@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 15.imageset
│ │ │ │ │ ├── 15.png
│ │ │ │ │ ├── 15@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 16.imageset
│ │ │ │ │ ├── 16.png
│ │ │ │ │ ├── 16@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 17.imageset
│ │ │ │ │ ├── 17.png
│ │ │ │ │ ├── 17@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 18.imageset
│ │ │ │ │ ├── 18.png
│ │ │ │ │ ├── 18@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 26.imageset
│ │ │ │ │ ├── 26.png
│ │ │ │ │ ├── 26@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Generic.imageset
│ │ │ │ │ ├── Generic.png
│ │ │ │ │ ├── Generic@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── tvOS
│ │ │ │ ├── 9.imageset
│ │ │ │ │ ├── 9.png
│ │ │ │ │ ├── 9@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 10.imageset
│ │ │ │ │ ├── 10.png
│ │ │ │ │ ├── 10@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 11.imageset
│ │ │ │ │ ├── 11.png
│ │ │ │ │ ├── 11@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 12.imageset
│ │ │ │ │ ├── 12.png
│ │ │ │ │ ├── 12@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 13.imageset
│ │ │ │ │ ├── 13.png
│ │ │ │ │ ├── 13@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 14.imageset
│ │ │ │ │ ├── 14.png
│ │ │ │ │ ├── 14@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 15.imageset
│ │ │ │ │ ├── 15.png
│ │ │ │ │ ├── 15@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 16.imageset
│ │ │ │ │ ├── 16.png
│ │ │ │ │ ├── 16@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 17.imageset
│ │ │ │ │ ├── 17.png
│ │ │ │ │ ├── 17@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 18.imageset
│ │ │ │ │ ├── 18.png
│ │ │ │ │ ├── 18@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 26.imageset
│ │ │ │ │ ├── 26.png
│ │ │ │ │ ├── 26@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Generic.imageset
│ │ │ │ │ ├── Generic.png
│ │ │ │ │ ├── Generic@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── macOS
│ │ │ │ ├── 12.imageset
│ │ │ │ │ ├── 12.png
│ │ │ │ │ ├── 12@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 13.imageset
│ │ │ │ │ ├── 13.png
│ │ │ │ │ ├── 13@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 14.imageset
│ │ │ │ │ ├── 14.png
│ │ │ │ │ ├── 14@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 15.imageset
│ │ │ │ │ ├── 15.png
│ │ │ │ │ ├── 15@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 26.imageset
│ │ │ │ │ ├── 26.png
│ │ │ │ │ ├── 26@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Generic.imageset
│ │ │ │ │ ├── Generic.png
│ │ │ │ │ ├── Generic@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── watchOS
│ │ │ │ ├── 10.imageset
│ │ │ │ │ ├── 10.png
│ │ │ │ │ ├── 10@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 11.imageset
│ │ │ │ │ ├── 11.png
│ │ │ │ │ ├── 11@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 2.imageset
│ │ │ │ │ ├── 2.png
│ │ │ │ │ ├── 2@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 3.imageset
│ │ │ │ │ ├── 3.png
│ │ │ │ │ ├── 3@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 4.imageset
│ │ │ │ │ ├── 4.png
│ │ │ │ │ ├── 4@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 5.imageset
│ │ │ │ │ ├── 5.png
│ │ │ │ │ ├── 5@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 6.imageset
│ │ │ │ │ ├── 6.png
│ │ │ │ │ ├── 6@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 7.imageset
│ │ │ │ │ ├── 7.png
│ │ │ │ │ ├── 7@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 8.imageset
│ │ │ │ │ ├── 8.png
│ │ │ │ │ ├── 8@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 9.imageset
│ │ │ │ │ ├── 9.png
│ │ │ │ │ ├── 9@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 26.imageset
│ │ │ │ │ ├── watchOS26.png
│ │ │ │ │ ├── watchOS26@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Generic.imageset
│ │ │ │ │ ├── Generic.png
│ │ │ │ │ ├── Generic@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── visionOS
│ │ │ │ ├── 2.imageset
│ │ │ │ │ ├── 2-1.png
│ │ │ │ │ ├── 2@0.5x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── 26.imageset
│ │ │ │ │ ├── visionOS26.png
│ │ │ │ │ ├── visionOS26@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Generic.imageset
│ │ │ │ │ ├── Generic.png
│ │ │ │ │ ├── Generic@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Devices
│ │ │ ├── iPadIcon.imageset
│ │ │ │ ├── iPadIcon.png
│ │ │ │ ├── iPadIcon@2x.png
│ │ │ │ └── Contents.json
│ │ │ ├── WatchIcon.imageset
│ │ │ │ ├── WatchIcon.png
│ │ │ │ ├── WatchIcon@2x.png
│ │ │ │ └── Contents.json
│ │ │ ├── AppleTVIcon.imageset
│ │ │ │ ├── AppleTVIcon.png
│ │ │ │ ├── AppleTVIcon@2x.png
│ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ ├── MacBookIcon.imageset
│ │ │ │ ├── MacBookIcon.png
│ │ │ │ ├── MacBookIcon@2x.png
│ │ │ │ └── Contents.json
│ │ │ ├── iPhoneIcon.imageset
│ │ │ │ ├── iPhoneIcon.png
│ │ │ │ ├── iPhoneIcon@2x.png
│ │ │ │ └── Contents.json
│ │ │ └── VisionProIcon.imageset
│ │ │ │ ├── VisionProIcon.png
│ │ │ │ ├── VisionProIcon@2x.png
│ │ │ │ └── Contents.json
│ │ ├── Files
│ │ │ ├── XCArchive.imageset
│ │ │ │ ├── XCArchive.png
│ │ │ │ ├── XCArchive@2x.png
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ └── DevCleanerIcon.imageset
│ │ │ ├── DevCleaner Icon - Tahoe-macOS-Default-256x256@2x.png
│ │ │ └── Contents.json
│ ├── DevCleaner.icon
│ │ ├── Assets
│ │ │ ├── PNG image.png
│ │ │ ├── PNG image 3.png
│ │ │ └── PNG image 6.png
│ │ └── icon.json
│ ├── DevCleaner.entitlements
│ ├── dev-cleaner.sh
│ ├── Info.plist
│ ├── DevCleanerStoreKit.storekit
│ └── Credits.rtf
├── Utilities
│ ├── Extensions
│ │ ├── Digest+Helpers.swift
│ │ ├── FileManager+HomeFolder.swift
│ │ ├── FileManager+DateProperties.swift
│ │ ├── URL+AcquireAccessFromSandbox.swift
│ │ └── FileManager+DirectorySize.swift
│ ├── Weak.swift
│ ├── Stack.swift
│ ├── AppleDevices.swift
│ ├── Profiling
│ │ ├── Signposts.swift
│ │ └── Profiler.swift
│ ├── Alerts.swift
│ ├── AppleBuild.swift
│ ├── Logger.swift
│ ├── Version.swift
│ └── ArgumentsParser.swift
├── View Controllers
│ ├── Main Window
│ │ ├── Views
│ │ │ ├── SizeCellView.swift
│ │ │ └── XcodeEntryCellView.swift
│ │ └── CleaningViewController.swift
│ ├── Command Line Install Window
│ │ ├── CommandLineInstallViewController.swift
│ │ └── CommandLineInstall.storyboard
│ └── Help Window
│ │ ├── HelpViewController.swift
│ │ └── Help.storyboard
├── Model
│ └── Entries
│ │ ├── OldDocumentationFileEntry.swift
│ │ ├── DerivedDataFileEntry.swift
│ │ ├── DeviceLogsFileEntry.swift
│ │ ├── DocumentationCacheFileEntry.swift
│ │ ├── ArchiveFileEntry.swift
│ │ └── DeviceSupportFileEntry.swift
├── Views
│ ├── MessageView.swift
│ └── LoadingView.swift
├── Managers
│ ├── ReviewRequests.swift
│ ├── Files.swift
│ ├── ScanReminders.swift
│ └── FeedbackMailer.swift
└── Base
│ ├── AppDelegate.swift
│ └── main.swift
├── Documentation
├── Main Window Screenshot.png
├── Preferences Window Screenshot.png
└── Command Line Tool.md
├── .gitignore
└── README.md
/DevCleaner/Resources/Manual/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/file.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/menu.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/file-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/file-dark.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/file@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/file@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/menu-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/menu-dark.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/menu@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/menu@2x.png
--------------------------------------------------------------------------------
/Documentation/Main Window Screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/Documentation/Main Window Screenshot.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/Icon-128pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/Icon-128pt.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/main-window.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/main-window.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/preferences.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/preferences.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/Icon-128pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/Icon-128pt@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/file-dark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/file-dark@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/main-window@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/main-window@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/menu-dark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/menu-dark@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/preferences@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/preferences@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/main-window-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/main-window-dark.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/preferences-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/preferences-dark.png
--------------------------------------------------------------------------------
/Documentation/Preferences Window Screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/Documentation/Preferences Window Screenshot.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/main-window-dark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/main-window-dark@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/preferences-dark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Manual/preferences-dark@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/DevCleaner.icon/Assets/PNG image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/DevCleaner.icon/Assets/PNG image.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/2.imageset/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/2.imageset/2.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/3.imageset/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/3.imageset/3.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/4.imageset/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/4.imageset/4.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/5.imageset/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/5.imageset/5.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/6.imageset/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/6.imageset/6.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/7.imageset/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/7.imageset/7.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/8.imageset/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/8.imageset/8.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/9.imageset/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/9.imageset/9.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/9.imageset/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/9.imageset/9.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/DevCleaner.icon/Assets/PNG image 3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/DevCleaner.icon/Assets/PNG image 3.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/DevCleaner.icon/Assets/PNG image 6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/DevCleaner.icon/Assets/PNG image 6.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/10.imageset/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/10.imageset/10.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/11.imageset/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/11.imageset/11.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/12.imageset/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/12.imageset/12.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/13.imageset/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/13.imageset/13.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/14.imageset/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/14.imageset/14.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/15.imageset/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/15.imageset/15.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/16.imageset/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/16.imageset/16.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/17.imageset/17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/17.imageset/17.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/18.imageset/18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/18.imageset/18.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/2.imageset/2@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/2.imageset/2@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/26.imageset/26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/26.imageset/26.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/3.imageset/3@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/3.imageset/3@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/4.imageset/4@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/4.imageset/4@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/5.imageset/5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/5.imageset/5@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/6.imageset/6@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/6.imageset/6@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/7.imageset/7@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/7.imageset/7@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/8.imageset/8@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/8.imageset/8@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/9.imageset/9@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/9.imageset/9@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/10.imageset/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/10.imageset/10.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/11.imageset/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/11.imageset/11.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/12.imageset/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/12.imageset/12.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/13.imageset/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/13.imageset/13.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/14.imageset/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/14.imageset/14.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/15.imageset/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/15.imageset/15.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/16.imageset/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/16.imageset/16.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/17.imageset/17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/17.imageset/17.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/18.imageset/18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/18.imageset/18.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/26.imageset/26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/26.imageset/26.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/10.imageset/10@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/10.imageset/10@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/11.imageset/11@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/11.imageset/11@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/12.imageset/12@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/12.imageset/12@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/13.imageset/13@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/13.imageset/13@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/14.imageset/14@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/14.imageset/14@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/15.imageset/15@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/15.imageset/15@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/16.imageset/16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/16.imageset/16@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/17.imageset/17@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/17.imageset/17@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/18.imageset/18@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/18.imageset/18@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/26.imageset/26@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/26.imageset/26@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/12.imageset/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/12.imageset/12.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/13.imageset/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/13.imageset/13.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/14.imageset/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/14.imageset/14.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/15.imageset/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/15.imageset/15.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/26.imageset/26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/26.imageset/26.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/10.imageset/10@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/10.imageset/10@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/11.imageset/11@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/11.imageset/11@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/12.imageset/12@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/12.imageset/12@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/13.imageset/13@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/13.imageset/13@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/14.imageset/14@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/14.imageset/14@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/15.imageset/15@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/15.imageset/15@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/16.imageset/16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/16.imageset/16@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/17.imageset/17@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/17.imageset/17@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/18.imageset/18@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/18.imageset/18@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/26.imageset/26@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/26.imageset/26@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/9.imageset/9@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/9.imageset/9@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/10.imageset/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/10.imageset/10.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/11.imageset/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/11.imageset/11.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/2.imageset/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/2.imageset/2.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/3.imageset/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/3.imageset/3.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/4.imageset/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/4.imageset/4.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/5.imageset/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/5.imageset/5.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/6.imageset/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/6.imageset/6.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/7.imageset/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/7.imageset/7.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/8.imageset/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/8.imageset/8.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/9.imageset/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/9.imageset/9.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/12.imageset/12@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/12.imageset/12@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/13.imageset/13@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/13.imageset/13@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/14.imageset/14@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/14.imageset/14@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/15.imageset/15@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/15.imageset/15@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/26.imageset/26@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/26.imageset/26@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/visionOS/2.imageset/2-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/visionOS/2.imageset/2-1.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/2.imageset/2@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/2.imageset/2@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/3.imageset/3@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/3.imageset/3@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/4.imageset/4@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/4.imageset/4@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/5.imageset/5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/5.imageset/5@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/6.imageset/6@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/6.imageset/6@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/7.imageset/7@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/7.imageset/7@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/8.imageset/8@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/8.imageset/8@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/9.imageset/9@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/9.imageset/9@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/visionOS/2.imageset/2@0.5x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/visionOS/2.imageset/2@0.5x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/10.imageset/10@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/10.imageset/10@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/11.imageset/11@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/11.imageset/11@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/Generic.imageset/Generic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/Generic.imageset/Generic.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/Generic.imageset/Generic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/Generic.imageset/Generic.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/26.imageset/watchOS26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/26.imageset/watchOS26.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/iPadIcon.imageset/iPadIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/iPadIcon.imageset/iPadIcon.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Files/XCArchive.imageset/XCArchive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Files/XCArchive.imageset/XCArchive.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/Generic.imageset/Generic@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/iOS/Generic.imageset/Generic@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/Generic.imageset/Generic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/Generic.imageset/Generic.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/Generic.imageset/Generic@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/tvOS/Generic.imageset/Generic@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/visionOS/26.imageset/visionOS26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/visionOS/26.imageset/visionOS26.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/26.imageset/watchOS26@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/26.imageset/watchOS26@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/Generic.imageset/Generic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/Generic.imageset/Generic.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/WatchIcon.imageset/WatchIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/WatchIcon.imageset/WatchIcon.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/iPadIcon.imageset/iPadIcon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/iPadIcon.imageset/iPadIcon@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Files/XCArchive.imageset/XCArchive@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Files/XCArchive.imageset/XCArchive@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/Generic.imageset/Generic@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/macOS/Generic.imageset/Generic@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/visionOS/26.imageset/visionOS26@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/visionOS/26.imageset/visionOS26@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/visionOS/Generic.imageset/Generic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/visionOS/Generic.imageset/Generic.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/AppleTVIcon.imageset/AppleTVIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/AppleTVIcon.imageset/AppleTVIcon.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/MacBookIcon.imageset/MacBookIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/MacBookIcon.imageset/MacBookIcon.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/WatchIcon.imageset/WatchIcon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/WatchIcon.imageset/WatchIcon@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/iPhoneIcon.imageset/iPhoneIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/iPhoneIcon.imageset/iPhoneIcon.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Files/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/visionOS/Generic.imageset/Generic@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/visionOS/Generic.imageset/Generic@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/Generic.imageset/Generic@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/OS/watchOS/Generic.imageset/Generic@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/iPhoneIcon.imageset/iPhoneIcon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/iPhoneIcon.imageset/iPhoneIcon@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/visionOS/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/AppleTVIcon.imageset/AppleTVIcon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/AppleTVIcon.imageset/AppleTVIcon@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/MacBookIcon.imageset/MacBookIcon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/MacBookIcon.imageset/MacBookIcon@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/VisionProIcon.imageset/VisionProIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/VisionProIcon.imageset/VisionProIcon.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/VisionProIcon.imageset/VisionProIcon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/Devices/VisionProIcon.imageset/VisionProIcon@2x.png
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/DevCleanerIcon.imageset/DevCleaner Icon - Tahoe-macOS-Default-256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vashpan/xcode-dev-cleaner/HEAD/DevCleaner/Resources/Assets.xcassets/DevCleanerIcon.imageset/DevCleaner Icon - Tahoe-macOS-Default-256x256@2x.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X noise
2 | .DS_Store
3 | profile
4 |
5 | # XCode noise
6 | build/*
7 | *.pbxuser
8 | *.mode1v3
9 | *.mode2v3
10 |
11 | *.xcodeproj/xcuserdata/*
12 | *.xcodeproj/project.xcworkspace/*
13 | *.xcscheme
14 |
15 | # test suite files
16 | TestSuite/*
17 |
18 | # builds
19 | Builds/*
20 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/DevCleanerIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "DevCleaner Icon - Tahoe-macOS-Default-256x256@2x.png",
5 | "idiom" : "mac"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Manual/manual.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: -apple-system;
3 | margin-left: 16pt;
4 | margin-right: 16pt;
5 | }
6 |
7 | .header img {
8 | vertical-align: middle;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | body {
13 | background: rgb(37, 37, 37);
14 | color: white;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/10.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "10.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "10@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/11.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "11.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "11@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/12.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "12.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "12@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/13.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "13.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "13@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/14.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "14.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "14@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/15.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "15.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "15@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/16.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "16@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/17.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "17.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "17@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/18.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "18.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "18@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "2.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "2@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/26.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "26.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "26@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/3.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "3.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "3@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/4.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "4.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "4@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/5.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "5.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "5@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/6.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "6.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "6@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/7.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "7.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "7@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/8.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "8.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "8@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/9.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "9.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "9@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/10.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "10.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "10@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/11.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "11.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "11@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/12.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "12.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "12@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/13.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "13.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "13@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/14.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "14.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "14@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/15.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "15.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "15@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/16.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "16@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/17.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "17.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "17@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/18.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "18.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "18@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/26.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "26.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "26@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/9.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "9.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "9@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "2.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "2@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/3.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "3.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "3@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/4.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "4.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "4@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/5.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "5.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "5@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/6.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "6.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "6@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/7.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "7.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "7@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/8.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "8.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "8@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/9.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "9.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "9@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/12.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "12.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "12@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/13.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "13.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "13@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/14.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "14.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "14@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/15.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "15.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "15@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/26.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "26.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "26@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/visionOS/2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "2@0.5x.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "2-1.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/10.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "10.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "10@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/11.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "11.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "11@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/iOS/Generic.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Generic.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Generic@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/tvOS/Generic.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Generic.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Generic@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/iPadIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "iPadIcon.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "iPadIcon@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Files/XCArchive.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "XCArchive.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "XCArchive@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/macOS/Generic.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Generic.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Generic@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/visionOS/26.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "visionOS26.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "visionOS26@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/visionOS/Generic.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Generic.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Generic@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/26.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "watchOS26.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "watchOS26@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/OS/watchOS/Generic.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Generic.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Generic@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/WatchIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "WatchIcon.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "WatchIcon@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/iPhoneIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "iPhoneIcon.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "iPhoneIcon@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/AppleTVIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "AppleTVIcon.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "AppleTVIcon@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/MacBookIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "MacBookIcon.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "MacBookIcon@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Assets.xcassets/Devices/VisionProIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "VisionProIcon.png",
5 | "idiom" : "mac",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "VisionProIcon@2x.png",
10 | "idiom" : "mac",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/DevCleaner.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.bookmarks.app-scope
8 |
9 | com.apple.security.files.user-selected.read-write
10 |
11 | com.apple.security.network.client
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/Extensions/Digest+Helpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Digest+Helpers.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 08/02/2022.
6 | // Copyright © 2022 One Minute Games. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CryptoKit
11 |
12 | extension Digest {
13 | public var bytes: [UInt8] { Array(makeIterator()) }
14 | public var data: Data { Data(bytes) }
15 |
16 | public var hexStr: String {
17 | bytes.map { String(format: "%02X", $0) }.joined()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/Weak.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Weak.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 03/10/2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class Weak: Equatable {
12 | public private(set) weak var value: T?
13 |
14 | init(value: T?) {
15 | self.value = value
16 | }
17 |
18 | public static func == (lhs: Weak, rhs: Weak) -> Bool {
19 | return lhs.value === rhs.value
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/Extensions/FileManager+HomeFolder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URL+HomeFolder.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 18/02/2019.
6 | // Copyright © 2019 One Minute Games. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension FileManager {
12 | public var realHomeDirectoryForCurrentUser: URL {
13 | let pw = getpwuid(getuid())!
14 | let home = pw.pointee.pw_dir!
15 | let homePath = FileManager.default.string(withFileSystemRepresentation: home, length: Int(strlen(home)))
16 |
17 | return URL(fileURLWithPath: homePath, isDirectory: true)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Documentation/Command Line Tool.md:
--------------------------------------------------------------------------------
1 | # Command Line Tool
2 |
3 | Follow those instructions to install command line tool:
4 |
5 | This instructions assumes you have **DevCleaner** installed in `/Applications` folder. If you moved it, or installed it in a different location, please use it in instructions below.
6 |
7 | You need admistrator rights to install it in the location proposed in this instructions.
8 |
9 | 1. Open "Terminal" app
10 | 2. Type `sudo ln -s /Applications/DevCleaner.app/Contents/Resources/dev-cleaner.sh /usr/local/bin/dev-cleaner`
11 | 3. Enter your password if needed
12 |
13 | You can change destination path if you like, just remember that it has to be in your `PATH` if you want to use it from anywhere in your system.
14 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/Extensions/FileManager+DateProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileManager+DateProperties.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 09/11/2019.
6 | // Copyright © 2019 One Minute Games. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension FileManager {
12 | func dateModified(for url: URL) -> Date {
13 | guard let attrs = try? self.attributesOfItem(atPath: url.path) else {
14 | return Date.distantPast
15 | }
16 |
17 | guard let result = attrs[.modificationDate] as? Date else {
18 | return Date.distantPast
19 | }
20 |
21 | return result
22 | }
23 |
24 | func dateCreated(for url: URL) -> Date {
25 | guard let attrs = try? self.attributesOfItem(atPath: url.path) else {
26 | return Date.distantPast
27 | }
28 |
29 | guard let result = attrs[.creationDate] as? Date else {
30 | return Date.distantPast
31 | }
32 |
33 | return result
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/DevCleaner/View Controllers/Main Window/Views/SizeCellView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SizeCellView.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 14.04.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Cocoa
22 |
23 | final class SizeCellView: NSTableCellView {
24 | func setup(with xcodeEntry: XcodeFileEntry) {
25 | if let textField = self.textField, let sizeInBytes = xcodeEntry.size.numberOfBytes {
26 | textField.placeholderString = ByteCountFormatter.string(fromByteCount: sizeInBytes, countStyle: .file)
27 | textField.sizeToFit()
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/dev-cleaner.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # dev-cleaner.sh
4 | # DevCleaner
5 | #
6 | # "dev-cleaner" command line tool wrapper script.
7 | #
8 | # Copyright © 2019-2024 One Minute Games. All rights reserved.
9 | #
10 | # DevCleaner is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation; either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # DevCleaner is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with DevCleaner. If not, see .
22 |
23 | # get app updated app path
24 | DEV_CLEANER_PATH=$(defaults read com.oneminutegames.XcodeCleaner DCAppFolder)
25 |
26 | # set a command line run env value
27 | export DEV_CLEANER_FROM_COMMAND_LINE=1
28 |
29 | if [ -d $DEV_CLEANER_PATH ]; then
30 | "$DEV_CLEANER_PATH/Contents/MacOS/DevCleaner" "$@"
31 | else
32 | echo "DevCleaner cannot be found, check if you haven't uninstalled it or moved. Path where it was expected: $DEV_CLEANER_PATH"
33 | fi
34 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | 1
23 | ITSAppUsesNonExemptEncryption
24 |
25 | LSApplicationCategoryType
26 | public.app-category.developer-tools
27 | LSMinimumSystemVersion
28 | $(MACOSX_DEPLOYMENT_TARGET)
29 | NSHumanReadableCopyright
30 | Copyright © 2018-2025 Konrad Kołakowski. All rights reserved.
31 | NSMainStoryboardFile
32 | Main
33 | NSPrincipalClass
34 | NSApplication
35 | NSUserNotificationAlertStyle
36 | alert
37 |
38 |
39 |
--------------------------------------------------------------------------------
/DevCleaner/Model/Entries/OldDocumentationFileEntry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OldDocumentationFileEntry.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 14/07/2019.
6 | // Copyright © 2019 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 | import AppKit
23 |
24 | public final class OldDocumentationFileEntry: XcodeFileEntry {
25 | public override var fullDescription: String {
26 | return ""
27 | }
28 |
29 | public init(selected: Bool) {
30 | super.init(label: "Old Documentation Downloads",
31 | tooltipText: "Old offline documentations, not used anymore in modern Xcodes",
32 | icon: .none,
33 | tooltip: true,
34 | selected: selected)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/DevCleaner/Model/Entries/DerivedDataFileEntry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DerivedDataFileEntry.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 27.03.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 | import AppKit
23 |
24 | public final class DerivedDataFileEntry: XcodeFileEntry {
25 | // MARK: Properties
26 | public let projectName: String
27 | public let pathUrl: URL
28 |
29 | // MARK: Initialization
30 | public init(projectName: String, pathUrl: URL, selected: Bool) {
31 | self.projectName = projectName
32 | self.pathUrl = pathUrl
33 |
34 | super.init(label: "\(self.projectName)", extraInfo: "\(self.pathUrl.path)", icon: .system(name: NSImage.folderName), tooltip: true, selected: selected)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/DevCleaner/Model/Entries/DeviceLogsFileEntry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DeviceLogsFileEntry.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 13/07/2019.
6 | // Copyright © 2019 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 | import AppKit
23 |
24 | public final class DeviceLogsFileEntry: XcodeFileEntry {
25 | // MARK: Properties
26 | public let version: Version
27 |
28 | public init(version: Version, selected: Bool) {
29 | self.version = version
30 |
31 | super.init(label: "Logs from Xcode \(version)",
32 | tooltipText: "Old logs from Xcode \(version)",
33 | icon: .system(name: NSImage.multipleDocumentsName),
34 | tooltip: true,
35 | selected: selected)
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/DevCleaner/Model/Entries/DocumentationCacheFileEntry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DocumentationCacheFileEntry.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 16/10/2022.
6 | // Copyright © 2022 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 | import AppKit
23 |
24 | public final class DocumentationCacheFileEntry: XcodeFileEntry {
25 | // MARK: Properties
26 | public let version: Version
27 |
28 | public init(version: Version, selected: Bool) {
29 | self.version = version
30 |
31 | super.init(label: "Cache from Xcode \(version)",
32 | tooltipText: "Documentation cache files from Xcode \(version)",
33 | icon: .system(name: NSImage.multipleDocumentsName),
34 | tooltip: true,
35 | selected: selected)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/Stack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stack.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 30.04.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 |
23 | public struct Stack {
24 | // MARK: Properties
25 | private var array = [T]()
26 |
27 | public var top: T? {
28 | return self.array.last
29 | }
30 |
31 | public var count: Int {
32 | return self.array.count
33 | }
34 |
35 | public var isEmpty: Bool {
36 | return self.count == 0
37 | }
38 |
39 | // MARK: Manipulate stack
40 | public mutating func push(_ e: T) {
41 | self.array.append(e)
42 | }
43 |
44 | @discardableResult
45 | public mutating func pop() -> T? {
46 | return self.array.popLast()
47 | }
48 | }
49 |
50 | extension Stack: CustomStringConvertible {
51 | public var description: String {
52 | let contents = self.array.map { "\($0)" }.reversed().joined(separator: ", ")
53 |
54 | return "[" + contents + "]"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/DevCleaner/View Controllers/Command Line Install Window/CommandLineInstallViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommandLineInstallViewController.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 06/03/2024.
6 | // Copyright © 2024 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Cocoa
22 |
23 | final class CommandLineInstallViewController: NSViewController {
24 | // MARK: Properties & outlets
25 | @IBOutlet weak var commandTextField: NSTextField!
26 |
27 | private var commandString: String {
28 | let appPath = Bundle.main.bundlePath
29 |
30 | return "sudo ln -sf \(appPath)/Contents/Resources/dev-cleaner.sh /usr/local/bin/dev-cleaner"
31 | }
32 |
33 | // MARK: Initialization & overrides
34 | override func viewDidLoad() {
35 | super.viewDidLoad()
36 |
37 | self.commandTextField.stringValue = "$ " + self.commandString
38 | self.commandTextField.isSelectable = true
39 | }
40 |
41 | // MARK: Actions
42 | @IBAction func copyCommand(_ sender: Any) {
43 | let pasteboard = NSPasteboard.general
44 | pasteboard.clearContents()
45 |
46 | pasteboard.setString(self.commandString, forType: .string)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/DevCleaner/Views/MessageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MessageView.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 17.06.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | public class MessageView: NSView {
12 | // MARK: Properties
13 | private let label = NSTextField()
14 |
15 | public var message: String = String() {
16 | didSet {
17 | self.label.stringValue = self.message
18 | }
19 | }
20 |
21 | public var backgroundColor: NSColor = .windowBackgroundColor {
22 | didSet {
23 | self.wantsLayer = true
24 | self.layer?.backgroundColor = self.backgroundColor.cgColor
25 | }
26 | }
27 |
28 | // MARK: Initialization & overrides
29 | public convenience init() {
30 | self.init(frame: .zero)
31 | }
32 |
33 | public override init(frame frameRect: NSRect) {
34 | super.init(frame: frameRect)
35 |
36 | // set background
37 | self.backgroundColor = NSColor.windowBackgroundColor
38 |
39 | // set message label
40 | self.label.font = NSFont.systemFont(ofSize: 17.0, weight: .bold)
41 | self.label.isEditable = false
42 | self.label.isSelectable = false
43 | self.label.drawsBackground = false
44 | self.label.isBordered = false
45 | self.label.isBezeled = false
46 | self.label.usesSingleLineMode = true
47 | self.label.alignment = .center
48 |
49 | self.addSubview(self.label)
50 | }
51 |
52 | public required init?(coder decoder: NSCoder) {
53 | fatalError("init(coder:) has not been implemented")
54 | }
55 |
56 | public override func layout() {
57 | let targetHeight: CGFloat = 30.0
58 | self.label.frame = NSRect(x: 0.0, y: (self.frame.height - targetHeight) / 2.0,
59 | width: self.frame.width, height: targetHeight)
60 |
61 | super.layout()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/DevCleaner/Views/LoadingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoadingView.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 05.06.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | public class LoadingView: NSView {
12 | // MARK: Properties
13 | private let progressIndicator = NSProgressIndicator()
14 |
15 | // MARK: Constants
16 | private let indicatorSize: CGFloat = 32.0
17 |
18 | // MARK: Initialization & overrides
19 | public convenience init() {
20 | self.init(frame: .zero)
21 | }
22 |
23 | public override init(frame frameRect: NSRect) {
24 | super.init(frame: frameRect)
25 |
26 | // set background
27 | self.wantsLayer = true
28 | self.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor
29 |
30 | // add loading indicator
31 | self.progressIndicator.style = .spinning
32 | self.progressIndicator.controlSize = .regular
33 | self.progressIndicator.frame = self.indicatorFrame(size: indicatorSize, in: frameRect)
34 |
35 | self.addSubview(progressIndicator)
36 | }
37 |
38 | public required init?(coder decoder: NSCoder) {
39 | fatalError("init(coder:) has not been implemented")
40 | }
41 |
42 | public override func layout() {
43 | self.progressIndicator.frame = self.indicatorFrame(size: indicatorSize, in: self.frame)
44 |
45 | super.layout()
46 | }
47 |
48 | public override func viewWillMove(toSuperview newSuperview: NSView?) {
49 | if newSuperview == nil {
50 | self.progressIndicator.stopAnimation(self)
51 | } else {
52 | self.progressIndicator.startAnimation(self)
53 | }
54 | }
55 |
56 | // MARK: Helpers
57 | private func indicatorFrame(size: CGFloat, in frameRect: CGRect) -> CGRect {
58 | return CGRect(x: (frameRect.width - size) / 2.0, y: (frameRect.height - size) / 2.0,
59 | width: size, height: size)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/AppleDevices.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppleDevices.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 09/11/2025.
6 | // Copyright © 2025 One Minute Games. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import TabularData
11 |
12 | public struct AppleDevices {
13 | // MARK: Properties
14 | private static var deviceIdentifiersFile: URL? {
15 | let resourceName = "device_identifiers"
16 |
17 | if let url = Bundle.main.url(forResource: resourceName, withExtension: "csv") {
18 | return url
19 | }
20 |
21 | return Bundle.main.url(forResource: resourceName, withExtension: "csv")
22 | }
23 |
24 | private static let lookupTable: [String: String] = {
25 | guard let csvURL = Self.deviceIdentifiersFile else {
26 | log.warning("AppleDevices: device_identifiers.csv missing in bundle.")
27 | return [:]
28 | }
29 |
30 | do {
31 | let options = CSVReadingOptions(hasHeaderRow: true)
32 | let dataFrame = try DataFrame(contentsOfCSVFile: csvURL, options: options)
33 | var table: [String: String] = [:]
34 |
35 | for row in dataFrame.rows {
36 | guard let identifier = row["identifier"] as? String else {
37 | continue
38 | }
39 | let name = (row["name"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
40 | table[identifier] = name?.isEmpty == false ? name : identifier
41 | }
42 |
43 | return table
44 | } catch {
45 | log.error("AppleDevices: Unable to load device_identifiers.csv: \(error)")
46 | return [:]
47 | }
48 | }()
49 |
50 | // MARK: Fetch device name
51 | public static func deviceName(for deviceId: String) -> String {
52 | guard !deviceId.isEmpty else {
53 | return "Unknown Device"
54 | }
55 |
56 | return Self.lookupTable[deviceId] ?? deviceId
57 | }
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/DevCleaner.icon/icon.json:
--------------------------------------------------------------------------------
1 | {
2 | "fill" : {
3 | "linear-gradient" : [
4 | "display-p3:0.34704,0.74825,0.96220,1.00000",
5 | "display-p3:0.20876,0.42456,0.94757,1.00000"
6 | ],
7 | "orientation" : {
8 | "start" : {
9 | "x" : 0.5,
10 | "y" : 0
11 | },
12 | "stop" : {
13 | "x" : 0.5,
14 | "y" : 0.7
15 | }
16 | }
17 | },
18 | "groups" : [
19 | {
20 | "blur-material" : 0.5,
21 | "layers" : [
22 | {
23 | "image-name" : "PNG image 6.png",
24 | "name" : "PNG image 6"
25 | }
26 | ],
27 | "shadow" : {
28 | "kind" : "neutral",
29 | "opacity" : 1
30 | },
31 | "specular" : true,
32 | "translucency" : {
33 | "enabled" : false,
34 | "value" : 0.5
35 | }
36 | },
37 | {
38 | "blur-material" : 0.5,
39 | "layers" : [
40 | {
41 | "blend-mode" : "normal",
42 | "fill" : "automatic",
43 | "hidden" : true,
44 | "image-name" : "PNG image.png",
45 | "name" : "PNG image",
46 | "position" : {
47 | "scale" : 1,
48 | "translation-in-points" : [
49 | 0,
50 | 0
51 | ]
52 | }
53 | }
54 | ],
55 | "name" : "Group",
56 | "shadow" : {
57 | "kind" : "neutral",
58 | "opacity" : 0.5
59 | },
60 | "specular" : false,
61 | "translucency" : {
62 | "enabled" : true,
63 | "value" : 0.5
64 | }
65 | },
66 | {
67 | "blur-material" : 0.5,
68 | "layers" : [
69 | {
70 | "glass" : true,
71 | "image-name" : "PNG image 3.png",
72 | "name" : "PNG image 3",
73 | "opacity" : 0.25
74 | }
75 | ],
76 | "shadow" : {
77 | "kind" : "none",
78 | "opacity" : 0.5
79 | },
80 | "specular" : false,
81 | "translucency" : {
82 | "enabled" : false,
83 | "value" : 0.5
84 | }
85 | }
86 | ],
87 | "supported-platforms" : {
88 | "squares" : [
89 | "macOS"
90 | ]
91 | }
92 | }
--------------------------------------------------------------------------------
/DevCleaner/Managers/ReviewRequests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReviewRequests.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 31/08/2019.
6 | // Copyright © 2019 One Minute Games. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import StoreKit
11 |
12 | public final class ReviewRequests {
13 | // MARK: Properties
14 | public static let shared = ReviewRequests()
15 |
16 | // MARK: Constants
17 | private static let bytesNeededForReviewRequest: Int64 = 20 * 1024 * 1024 * 1024 // 20GB
18 | private static let cleansNeededForReviewRequest = 3
19 |
20 | // MARK: Showing review request
21 | public func requestReviewIfNeeded() {
22 | let totalBytesCleaned = Preferences.shared.totalBytesCleaned
23 | let totalCleansPerformedSinceLastRequest = Preferences.shared.cleansSinceLastReview
24 |
25 | // desired rules:
26 | // we show it either if we passed TOTAL of 20GB of cleaned bytes, which may be even on the first run of the app
27 | // or, if we clean smaller amounts, after 3 cleans
28 | // after we pass those 20GB total cleaned amount, we ask everytime we clean basically (limits according to system)
29 | if totalBytesCleaned > ReviewRequests.bytesNeededForReviewRequest || totalCleansPerformedSinceLastRequest >= ReviewRequests.cleansNeededForReviewRequest {
30 | // workaround due to an issue with presenting review popups in the background
31 | // https://furnacecreek.org/blog/2024-04-14-how-to-prevent-background-mac-app-store-rating-windows
32 | //
33 | if #available(macOS 14.0, *) {
34 | NSApp.yieldActivation(toApplicationWithBundleIdentifier: "com.apple.storeuid")
35 | }
36 |
37 | SKStoreReviewController.requestReview()
38 |
39 | Preferences.shared.cleansSinceLastReview = 0
40 | }
41 | }
42 |
43 | public func showReviewOnTheAppStore() {
44 | guard let appStoreUrl = URL(string: "macappstore://apps.apple.com/app/id1388020431?action=write-review") else {
45 | log.error("ReviewRequests: Can't make a review URL!")
46 | return
47 | }
48 |
49 | NSWorkspace.shared.open(appStoreUrl)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/Profiling/Signposts.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Signposts.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kolakowski on 04/08/2019.
6 | // Copyright © 2019 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 | import os
23 |
24 | public final class Signposts {
25 | // MARK: Properties
26 | private static var signpostLog: OSLog = {
27 | guard let bundleId = Bundle.main.bundleIdentifier else {
28 | fatalError("Signposts: No bundleId defined in main bundle Infp.plist file?")
29 | }
30 |
31 | return OSLog(subsystem: bundleId + ".Signposts", category: .pointsOfInterest)
32 | }()
33 |
34 | // MARK: Helpers
35 | private static func placeSignpost(type: OSSignpostType, name: StaticString, details: String, object: AnyObject?) {
36 | let spid: OSSignpostID
37 | if let objectForSpid = object {
38 | spid = OSSignpostID(log: Signposts.signpostLog, object: objectForSpid)
39 | } else {
40 | spid = OSSignpostID(log: Signposts.signpostLog)
41 | }
42 |
43 | os_signpost(type, dso: #dsohandle, log: Signposts.signpostLog, name: name, signpostID: spid, "%@", details)
44 | }
45 |
46 | // MARK: Placing signposts
47 | public static func begin(name: StaticString, details: String = String(), object: AnyObject? = nil) {
48 | placeSignpost(type: .begin, name: name, details: details, object: object)
49 | }
50 |
51 | public static func end(name: StaticString, details: String = String(), object: AnyObject? = nil) {
52 | placeSignpost(type: .end, name: name, details: details, object: object)
53 | }
54 |
55 | public static func event(name: StaticString, details: String = String(), object: AnyObject? = nil) {
56 | placeSignpost(type: .event, name: name, details: details, object: object)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/Alerts.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Alerts.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 18.03.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 | import Cocoa
23 |
24 | public class Alerts {
25 | class public func fatalErrorAlertAndQuit(title: String, message: String) {
26 | // display a popup that tells us that this is basically a fatal error, and quit!
27 | let alert = NSAlert()
28 | alert.alertStyle = .critical
29 | alert.messageText = title
30 | alert.informativeText = message
31 | alert.addButton(withTitle: "Quit")
32 |
33 | alert.runModal()
34 | NSApp.terminate(nil)
35 | }
36 |
37 | class public func warningAlert(title: String, message: String, okButtonText: String = "OK", cancelButtonText: String = "Cancel", window: NSWindow? = nil, completionHandler: ((NSApplication.ModalResponse) -> Void)? = nil) {
38 | let alert = NSAlert()
39 | alert.alertStyle = .critical
40 | alert.messageText = title
41 | alert.informativeText = message
42 | alert.addButton(withTitle: okButtonText)
43 | alert.addButton(withTitle: cancelButtonText)
44 |
45 | if let currentWindow = window {
46 | alert.beginSheetModal(for: currentWindow, completionHandler: completionHandler)
47 | } else {
48 | let response = alert.runModal()
49 | completionHandler?(response)
50 | }
51 | }
52 |
53 | class public func infoAlert(title: String, message: String, okButtonText: String = "OK") {
54 | let alert = NSAlert()
55 | alert.alertStyle = .informational
56 | alert.messageText = title
57 | alert.informativeText = message
58 | alert.addButton(withTitle: okButtonText)
59 |
60 | alert.runModal()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/DevCleaner/View Controllers/Help Window/HelpViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HelpViewController.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 07/09/2019.
6 | // Copyright © 2019 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Cocoa
22 | @preconcurrency import WebKit
23 |
24 | final class HelpViewController: NSViewController {
25 | // MARK: Properties & outlets
26 | @IBOutlet private weak var helpWebView: WKWebView!
27 |
28 | // MARK: Initialization & overrides
29 | override func viewDidLoad() {
30 | super.viewDidLoad()
31 |
32 | // [1] workaround to prevent "blinking" of help background when in dark mode:
33 | // https://stackoverflow.com/a/67674061
34 |
35 | // configuration
36 | self.helpWebView.setValue(true as NSNumber, forKey: "drawsTransparentBackground") // [1]
37 | self.helpWebView.navigationDelegate = self
38 |
39 | // load manual HTML
40 | guard let helpUrl = Bundle.main.url(forResource: "manual", withExtension: "html", subdirectory: "Manual") else {
41 | log.error("HelpViewController: Can't find manual HTML file!")
42 | return
43 | }
44 |
45 | self.helpWebView.loadFileURL(helpUrl, allowingReadAccessTo: helpUrl.deletingLastPathComponent())
46 | }
47 | }
48 |
49 | extension HelpViewController: WKNavigationDelegate {
50 | func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
51 | if navigationAction.navigationType == .linkActivated {
52 | if let url = navigationAction.request.url {
53 | NSWorkspace.shared.open(url)
54 | decisionHandler(.cancel)
55 | return
56 | }
57 | }
58 |
59 | decisionHandler(.allow)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/DevCleaner/Model/Entries/ArchiveFileEntry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArchiveFileEntry.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 27.03.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 |
23 | public final class ArchiveFileEntry: XcodeFileEntry {
24 | // MARK: Types
25 | public enum SubmissionStatus {
26 | case success, failure, undefined
27 |
28 | fileprivate var glyph: String {
29 | switch self {
30 | case .success: return "✅"
31 | case .failure: return "❌"
32 | case .undefined: return String()
33 | }
34 | }
35 | }
36 |
37 | // MARK: Properties
38 | public let projectName: String
39 | public let bundleName: String
40 | public let versionString: String
41 | public let version: Version?
42 | public let build: String
43 | public let date: Date
44 | public let submissionStatus: SubmissionStatus
45 |
46 | public override var fullDescription: String {
47 | return "\(projectName) \(self.versionString) (\(self.build)) (\(self.extraInfo))"
48 | }
49 |
50 | // MARK: Initialization
51 | public init(projectName: String, bundleName: String, version: String, build: String, date: Date, submissionStatus: SubmissionStatus, location: URL, selected: Bool) {
52 | self.projectName = projectName
53 | self.bundleName = bundleName
54 | self.versionString = version
55 | self.version = Version(describing: self.versionString)
56 | self.build = build
57 | self.date = date
58 | self.submissionStatus = submissionStatus
59 |
60 | let dateFormatter = DateFormatter()
61 | dateFormatter.dateStyle = .short
62 | dateFormatter.timeStyle = .short
63 |
64 | let dateString = dateFormatter.string(from: self.date)
65 |
66 | super.init(label: "\(self.versionString) (\(self.build)) \(submissionStatus.glyph)", extraInfo: dateString, icon: nil, tooltip: true, selected: selected)
67 |
68 | self.addPath(path: location)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/DevCleaner/Managers/Files.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Files.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 26/08/2019.
6 | // Copyright © 2019 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 |
23 | public final class Files {
24 | // MARK: Paths to common folders
25 | public static var userDeveloperFolder: URL {
26 | let userHomeDirectory = FileManager.default.realHomeDirectoryForCurrentUser
27 | let userDeveloperFolder = userHomeDirectory.appendingPathComponent("Library/Developer", isDirectory: true)
28 |
29 | return userDeveloperFolder
30 | }
31 |
32 | // MARK: Acquire folder permissions
33 | private static func acquireFolderPermissions(folderUrl: URL, allowCancel: Bool = true, openPanelMessage: String? = nil) -> URL? {
34 | let message = openPanelMessage ??
35 | "DevCleaner needs permission to this folder to scan its contents. Folder should be already selected and all you need to do is to click \"Open\"."
36 |
37 | return folderUrl.acquireAccessFromSandbox(bookmark: Preferences.shared.folderBookmark(for: folderUrl),
38 | allowCancel: allowCancel,
39 | openPanelMessage: message)
40 | }
41 |
42 | public static func acquireUserDeveloperFolderPermissions() -> URL? {
43 | return acquireFolderPermissions(folderUrl: Files.userDeveloperFolder,
44 | openPanelMessage: "DevCleaner needs permission to your Developer folder to scan Xcode cache files. Folder should be already selected and all you need to do is to click \"Open\".")
45 | }
46 |
47 | public static func acquireCustomDerivedDataFolderPermissions() -> URL? {
48 | guard let customDerivedDataFolder = Preferences.shared.customDerivedDataFolder else {
49 | return nil
50 | }
51 |
52 | return acquireFolderPermissions(folderUrl: customDerivedDataFolder)
53 | }
54 |
55 | public static func acquireCustomArchivesFolderPermissions() -> URL? {
56 | guard let customArchivesFolder = Preferences.shared.customArchivesFolder else {
57 | return nil
58 | }
59 |
60 | return acquireFolderPermissions(folderUrl: customArchivesFolder)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/DevCleaner/Base/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 11.02.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Cocoa
22 |
23 | class AppDelegate: NSObject, NSApplicationDelegate {
24 | // MARK: App configuration
25 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
26 | return true
27 | }
28 |
29 | // MARK: App lifetime events
30 | func applicationDidFinishLaunching(_ aNotification: Notification) {
31 | // register as transactions observer
32 | Donations.shared.startObservingTransactionsQueue()
33 |
34 | // update notifications
35 | if Preferences.shared.notificationsEnabled {
36 | ScanReminders.scheduleReminder(period: Preferences.shared.notificationsPeriod)
37 | } else {
38 | ScanReminders.disableReminder()
39 | }
40 |
41 | // information about upcoming notifications
42 | if let upcomingReminderDate = ScanReminders.dateOfNextReminder {
43 | log.info("Next reminder: \(upcomingReminderDate.description(with: Locale.current))")
44 | } else {
45 | log.info("No reminder scheduled!")
46 | }
47 | }
48 |
49 | func applicationWillTerminate(_ aNotification: Notification) {
50 | // Insert code here to tear down your application
51 | }
52 |
53 | // MARK: Actions
54 | @IBAction func openAppReview(_ sender: Any) {
55 | ReviewRequests.shared.showReviewOnTheAppStore()
56 | }
57 |
58 | @IBAction func showLogFiles(_ sender: Any) {
59 | // logs folder
60 | guard let logsUrl = log.logFilePath?.deletingLastPathComponent() else {
61 | return
62 | }
63 |
64 | NSWorkspace.shared.open(logsUrl)
65 | }
66 |
67 | @IBAction func installCommandLineTool(_ sender: Any) {
68 | guard let commandLineToolInstallInstructionsURL = URL(string: "https://github.com/vashpan/xcode-dev-cleaner/blob/566afe767c90001ba397f5907df11e09c68a1634/Documentation/Command%20Line%20Tool.md") else { return }
69 | NSWorkspace.shared.open(commandLineToolInstallInstructionsURL)
70 | }
71 |
72 | @IBAction func sendFeedback(_ sender: Any) {
73 | FeedbackMailer.shared.sendFeedback()
74 | }
75 |
76 | @IBAction func reportAnIssue(_ sender: Any) {
77 | FeedbackMailer.shared.reportAnIssue()
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/DevCleaner/Base/main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // main.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 20/08/2019.
6 | // Copyright © 2019 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Cocoa
22 |
23 | internal let log = Logger(name: "MainLog", level: .info, toFile: true)
24 |
25 | // MARK: Helpers
26 | private func commandLineDebugEnabled() -> Bool {
27 | #if DEBUG
28 | return Preferences.shared.envKeyPresent(key: "DCCmdLineDebug")
29 | #else
30 | return false
31 | #endif
32 | }
33 |
34 | private func isRunningFromCommandLine(args: [String]) -> Bool {
35 | // We have few ways of checking it, first is checking if our STDOUT is bound to some terminal
36 | // and second is checking for presence of env variable set in dev-cleaner.sh script. It helps
37 | // in cases where we want to run the script from headless environments where there's no TTY.
38 | //
39 | // Maybe there're better & more sure ways of handling/checking that, but I could't really found them.
40 | //
41 |
42 | let isTTY = isatty(STDIN_FILENO)
43 | let haveProperEnvValue = Preferences.shared.envKeyPresent(key: "DEV_CLEANER_FROM_COMMAND_LINE")
44 |
45 | let runningFromCommandLine = isTTY == 1 || haveProperEnvValue
46 |
47 | #if DEBUG
48 | let runningFromXcode = args.contains("-NSDocumentRevisionsDebugMode")
49 | #else
50 | let runningFromXcode = false
51 | #endif
52 |
53 | return commandLineDebugEnabled() || (runningFromCommandLine && !runningFromXcode)
54 | }
55 |
56 | private func cleanedCommandLineArguments(args: [String]) -> [String] {
57 | var resultArgs = args
58 |
59 | // we have to remove some Xcode stuff here
60 | if commandLineDebugEnabled() {
61 | if let index = resultArgs.firstIndex(of: "-NSDocumentRevisionsDebugMode") {
62 | resultArgs.remove(at: index)
63 | resultArgs.remove(at: index) // twice as there's "YES" afterwards
64 | }
65 | }
66 |
67 | return resultArgs
68 | }
69 |
70 | // MARK: App Start
71 |
72 | // save app path to defaults
73 | Preferences.shared.appFolder = Bundle.main.bundleURL
74 |
75 | let cleanedArgs = cleanedCommandLineArguments(args: CommandLine.arguments)
76 | if isRunningFromCommandLine(args: cleanedArgs) {
77 | log.consoleLogging = false // disable console logging to not interfere with console output, file log will still be available
78 | CmdLine.shared.start(args: cleanedArgs)
79 | } else {
80 | let _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
81 | }
82 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/DevCleanerStoreKit.storekit:
--------------------------------------------------------------------------------
1 | {
2 | "identifier" : "3AD57D62",
3 | "nonRenewingSubscriptions" : [
4 |
5 | ],
6 | "products" : [
7 | {
8 | "displayPrice" : "3.99",
9 | "familyShareable" : false,
10 | "internalID" : "1390773822",
11 | "localizations" : [
12 | {
13 | "description" : "A tip that could buy me a big coffee!",
14 | "displayName" : "Big Coffee",
15 | "locale" : "en_US"
16 | }
17 | ],
18 | "productID" : "BIG_COFFEE",
19 | "referenceName" : "Big Coffee",
20 | "type" : "Consumable"
21 | },
22 | {
23 | "displayPrice" : "9.99",
24 | "familyShareable" : false,
25 | "internalID" : "1390773830",
26 | "localizations" : [
27 | {
28 | "description" : "A tip that could buy me a lunch!",
29 | "displayName" : "Lunch",
30 | "locale" : "en_US"
31 | }
32 | ],
33 | "productID" : "LUNCH",
34 | "referenceName" : "Lunch",
35 | "type" : "Consumable"
36 | },
37 | {
38 | "displayPrice" : "1.99",
39 | "familyShareable" : false,
40 | "internalID" : "1390773318",
41 | "localizations" : [
42 | {
43 | "description" : "A tip that could buy me a small coffee!",
44 | "displayName" : "Small Coffee",
45 | "locale" : "en_US"
46 | }
47 | ],
48 | "productID" : "SMALL_COFFEE",
49 | "referenceName" : "Small Coffee",
50 | "type" : "Consumable"
51 | }
52 | ],
53 | "settings" : {
54 | "_applicationInternalID" : "1388020431",
55 | "_developerTeamID" : "3DS5SGYDG6",
56 | "_failTransactionsEnabled" : false,
57 | "_lastSynchronizedDate" : 723659050.64460695,
58 | "_locale" : "en_US",
59 | "_storefront" : "USA",
60 | "_storeKitErrors" : [
61 | {
62 | "current" : {
63 | "index" : 2,
64 | "type" : "generic"
65 | },
66 | "enabled" : false,
67 | "name" : "Load Products"
68 | },
69 | {
70 | "current" : null,
71 | "enabled" : false,
72 | "name" : "Purchase"
73 | },
74 | {
75 | "current" : null,
76 | "enabled" : false,
77 | "name" : "Verification"
78 | },
79 | {
80 | "current" : null,
81 | "enabled" : false,
82 | "name" : "App Store Sync"
83 | },
84 | {
85 | "current" : null,
86 | "enabled" : false,
87 | "name" : "Subscription Status"
88 | },
89 | {
90 | "current" : null,
91 | "enabled" : false,
92 | "name" : "App Transaction"
93 | },
94 | {
95 | "current" : null,
96 | "enabled" : false,
97 | "name" : "Manage Subscriptions Sheet"
98 | },
99 | {
100 | "current" : null,
101 | "enabled" : false,
102 | "name" : "Refund Request Sheet"
103 | },
104 | {
105 | "current" : null,
106 | "enabled" : false,
107 | "name" : "Offer Code Redeem Sheet"
108 | }
109 | ]
110 | },
111 | "subscriptionGroups" : [
112 |
113 | ],
114 | "version" : {
115 | "major" : 3,
116 | "minor" : 0
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/AppleBuild.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppleBuild.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 15/02/2022.
6 | // Copyright © 2022 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 |
23 | // MARK: AppleBuild struct
24 | public struct AppleBuild {
25 | // MARK: Properties
26 | public let textual: String
27 |
28 | public let major: Int?
29 | public let minor: Character?
30 | public let daily: String?
31 |
32 | // MARK: Constants
33 | public static let empty = AppleBuild()
34 |
35 | // MARK: Initialization
36 | init() {
37 | self.textual = String()
38 |
39 | self.major = nil
40 | self.minor = nil
41 | self.daily = nil
42 | }
43 |
44 | init(string: String) {
45 | self.textual = string
46 |
47 | // parse build number according to this:
48 | // https://tidbits.com/2020/07/08/how-to-decode-apple-version-and-build-numbers/
49 | let scanner = Scanner(string: self.textual)
50 | scanner.caseSensitive = false
51 |
52 | self.major = scanner.scanInt()
53 | self.minor = scanner.scanCharacter()
54 | self.daily = scanner.string[scanner.currentIndex...].description
55 | }
56 | }
57 |
58 | // MARK: - Comparable implementation
59 | extension AppleBuild: Comparable {
60 | public static func ==(lhs: AppleBuild, rhs: AppleBuild) -> Bool {
61 | if lhs.major == rhs.major {
62 | if lhs.minor == rhs.minor {
63 | let lhsDaily = lhs.daily ?? ""
64 | let rhsDaily = rhs.daily ?? ""
65 |
66 | if lhsDaily == rhsDaily {
67 | return true
68 | }
69 | }
70 | }
71 |
72 | return false
73 | }
74 |
75 | public static func <(lhs: AppleBuild, rhs: AppleBuild) -> Bool {
76 | if lhs.major == rhs.major {
77 | if lhs.minor == rhs.minor {
78 | let lhsDaily = lhs.daily ?? ""
79 | let rhsDaily = rhs.daily ?? ""
80 |
81 | return lhsDaily.compare(rhsDaily) == .orderedAscending
82 | } else {
83 | let lhsMinor = lhs.minor ?? Character("")
84 | let rhsMinor = rhs.minor ?? Character("")
85 |
86 | return lhsMinor < rhsMinor
87 | }
88 | } else {
89 | let lhsMajor = lhs.major ?? 0
90 | let rhsMajor = rhs.major ?? 0
91 |
92 | return lhsMajor < rhsMajor
93 | }
94 | }
95 | }
96 |
97 | // MARK: - CustomStringConvertible conformance
98 | extension AppleBuild: CustomStringConvertible {
99 | public var description: String {
100 | return self.textual
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/Profiling/Profiler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Profiler.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kolakowski on 04/08/2019.
6 | // Copyright © 2019 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import os
22 | import Foundation
23 | import QuartzCore
24 |
25 | public final class Profiler {
26 | private typealias ProfilerEntry = (name: StaticString, description: String, usingSignpost: Bool, start: CFTimeInterval)
27 | private static var starts = [ProfilerEntry]()
28 |
29 | private static let signpostLog: OSLog = {
30 | guard let bundleId = Bundle.main.bundleIdentifier else {
31 | fatalError("Profiler: No bundleId defined in main bundle Infp.plist file?")
32 | }
33 |
34 | return OSLog(subsystem: bundleId + ".Profiler", category: .pointsOfInterest)
35 | }()
36 |
37 | public static func tick(name: StaticString = StaticString(), description: String = String(), useSignpost: Bool = false) {
38 | let tickTime = CACurrentMediaTime()
39 | let entry = (name: name, description: description, usingSignpost: useSignpost, start: tickTime)
40 |
41 | if useSignpost {
42 | os_signpost(.begin, dso: #dsohandle, log: Profiler.signpostLog, name: name, "%@", description as NSString)
43 | }
44 |
45 | self.starts.append(entry)
46 | }
47 |
48 | @discardableResult
49 | public static func tock(noLog silent: Bool = false) -> CFTimeInterval {
50 | let tockTime = CACurrentMediaTime() // get time here to avoid any influence of "tock" function logic
51 | var time = CFTimeInterval(0.0)
52 |
53 | if let lastEntry = self.starts.popLast() {
54 | time = tockTime - lastEntry.start
55 |
56 | if !silent {
57 | if lastEntry.usingSignpost {
58 | if #available(iOS 12.0, *) {
59 | os_signpost(.end, dso: #dsohandle, log: Profiler.signpostLog, name: lastEntry.name)
60 | }
61 | }
62 |
63 | let number = self.starts.count
64 | let finalMessage: String
65 |
66 | if !lastEntry.name.description.isEmpty && !lastEntry.description.isEmpty {
67 | finalMessage = "[Profile: \(number)][\(lastEntry.name) - \(lastEntry.description)]"
68 | } else if !lastEntry.name.description.isEmpty {
69 | finalMessage = "[Profile: \(number)][\(lastEntry.name)]"
70 | } else {
71 | finalMessage = "[Profile: \(number)]"
72 | }
73 |
74 | print(String(format: "\(finalMessage): %.f ms", time * 1000.0))
75 | }
76 | } else {
77 | print("Cannot stop profiling that haven't started yet!")
78 | }
79 |
80 | return time
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/DevCleaner/Resources/Credits.rtf:
--------------------------------------------------------------------------------
1 | {\rtf1\ansi\ansicpg1250\cocoartf2822
2 | \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;}
3 | {\colortbl;\red255\green255\blue255;\red154\green154\blue154;}
4 | {\*\expandedcolortbl;;\csgray\c66667;}
5 | \paperw12240\paperh15840\margl1440\margr1440\vieww9000\viewh8400\viewkind0
6 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0
7 |
8 | \f0\b\fs24 \cf0 \
9 | Development\
10 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0
11 |
12 | \f1\b0 \cf2 Konrad Ko\uc0\u322 akowski\
13 | \
14 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0
15 |
16 | \f0\b \cf0 Special thanks\
17 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0
18 |
19 | \f1\b0 \cf2 Mladen Mladenov\
20 | Valeriy Van\
21 | Enrique Garcia\
22 | \pard\pardeftab720\partightenfactor0
23 | \cf2
24 | \f0\b \cf0 \
25 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0
26 | \cf0 \
27 | Icon uses assets from
28 | \f1\b0 \
29 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0
30 | \cf2 Broom graphics from IconDrawer on {\field{\*\fldinst{HYPERLINK "http://www.iconfinder.com"}}{\fldrslt www.iconfinder.com}}\
31 | Hard disk icon from Freepik on {\field{\*\fldinst{HYPERLINK "http://www.flaticon.com"}}{\fldrslt www.flaticon.com}} \cf0 \
32 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
33 | \cf0 \
34 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0
35 |
36 | \f0\b \cf0 You can follow me on Twitter:
37 | \f1\b0 {\field{\*\fldinst{HYPERLINK "https://twitter.com/vashpan"}}{\fldrslt @vashpan}}, {\field{\*\fldinst{HYPERLINK "https://twitter.com/OneMinuteGames"}}{\fldrslt @OneMinuteGames}}\
38 |
39 | \f0\b Find me on {\field{\*\fldinst{HYPERLINK "http://www.linkedin.com/in/konrad-kolakowski"}}{\fldrslt
40 | \f1\b0 LinkedIn}}
41 | \f1\b0 \
42 |
43 | \f0\b Check out my GitHub account: {\field{\*\fldinst{HYPERLINK "https://github.com/vashpan"}}{\fldrslt
44 | \f1\b0 @vashpan}}
45 | \f1\b0 \
46 | \
47 |
48 | \f0\b DevCleaner {\field{\*\fldinst{HYPERLINK "https://github.com/vashpan/xcode-dev-cleaner"}}{\fldrslt GitHub page}}
49 | \f1\b0 \
50 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
51 | \cf0 \
52 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0
53 |
54 | \f0\b \cf0 Xcode is trademark of Apple Inc., registered in the U.S. and other countries.
55 | \f1\b0 \
56 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
57 | \cf0 \
58 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc\partightenfactor0
59 | \cf2 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\
60 | \
61 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR PARTICULAR PURPOSE. See the GNU General Public License for more details.\
62 | \
63 | You should have received a copy of the GNU General Public License along with this program. If not, see {\field{\*\fldinst{HYPERLINK "http://www.gnu.org/licenses"}}{\fldrslt http://www.gnu.org/licenses}}\
64 | }
--------------------------------------------------------------------------------
/DevCleaner/Managers/ScanReminders.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScanReminders.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 11.05.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 | import Cocoa
23 | import NotificationCenter
24 |
25 | public final class ScanReminders {
26 | // MARK: Types
27 | public enum Period: Int {
28 | case everyWeek, every2weeks, everyMonth, every2Months
29 |
30 | private var dateComponents: DateComponents {
31 | var result = DateComponents()
32 |
33 | switch self {
34 | case .everyWeek:
35 | result.day = 7
36 | case .every2weeks:
37 | result.day = 7 * 2
38 | case .everyMonth:
39 | result.month = 1
40 | case .every2Months:
41 | result.month = 2
42 | }
43 |
44 | return result
45 | }
46 |
47 | internal var repeatInterval: DateComponents {
48 | var result = DateComponents()
49 |
50 | #if DEBUG
51 | if Preferences.shared.envKeyPresent(key: "DCNotificationsTest") {
52 | result.day = 1 // for debug we change our periods to one day
53 | } else {
54 | result = self.dateComponents
55 | }
56 | #else
57 | result = self.dateComponents
58 | #endif
59 |
60 | return result
61 | }
62 | }
63 |
64 | // MARK: Properties
65 | public static var dateOfNextReminder: Date? {
66 | if let firstScheduledNotification = NSUserNotificationCenter.default.scheduledNotifications.first {
67 | return firstScheduledNotification.deliveryDate
68 | } else {
69 | return nil
70 | }
71 | }
72 |
73 | // MARK: Constants
74 | private static let reminderIdentifier = "com.oneminutegames.DevCleaner.scanReminder"
75 |
76 | // MARK: Manage reminders
77 | public static func scheduleReminder(period: Period) {
78 | // notification
79 | let notification = NSUserNotification()
80 | notification.identifier = reminderIdentifier
81 | notification.title = "Scan Xcode cache?"
82 | notification.informativeText = "It's been a while since your last scan, check if you can reclaim some storage."
83 | notification.soundName = NSUserNotificationDefaultSoundName
84 |
85 | // buttons
86 | notification.hasActionButton = true
87 | notification.otherButtonTitle = "Close"
88 | notification.actionButtonTitle = "Scan"
89 |
90 | // schedule & repeat periodically
91 | if let initialDeliveryDate = NSCalendar.current.date(byAdding: period.repeatInterval, to: Date()) {
92 | notification.deliveryDate = initialDeliveryDate
93 | notification.deliveryRepeatInterval = period.repeatInterval
94 | }
95 |
96 | // schedule a notification
97 | let notificationCenter = NSUserNotificationCenter.default
98 | notificationCenter.scheduleNotification(notification)
99 | }
100 |
101 | public static func disableReminder() {
102 | NSUserNotificationCenter.default.scheduledNotifications = []
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DevCleaner
2 |
3 | 
4 |
5 | *Currently tested with macOS 14.x and Xcode 15.x. Should support earlier Xcode versions as well*
6 |
7 | Available on the [Mac App Store](https://itunes.apple.com/app/devcleaner/id1388020431)
8 |
9 | If you want to reclaim tens of gigabytes of your storage used for various Xcode caches - this tool is for you!
10 |
11 | Xcode could store tens of gigabytes in `~/Developer` folder. Most of those cached files & symbols is not reclaimed over time
12 | and could consume a large amount of your storage, which is especially important if you have relatively small SSD drive.
13 |
14 | DevCleaner gives you an easy way to inspect auto-generated files and clean them if necessary. It could also remind you about
15 | scan after a while.
16 |
17 | Please note that **this application is relying on internal folder structures and undocumented features**. It could stop working with
18 | newer versions of Xcode! I tried to make sure this application is safe, but if you want to be sure, please **make backup before use it**.
19 |
20 | ## What DevCleaner could actually clean?
21 |
22 | ### Device Support
23 |
24 | It consumes the largest part of the Xcode caches. Everytime you connect a device with the new iOS/visionOS/watchOS/tvOS, its symbols must be downloaded
25 | to your computer, for efficient debugging. It consumes around 2-5GB per version. Even smallest updates requires new set of symbols.
26 |
27 | It could accumulate to hold tens of gigabytes of data.
28 |
29 | This section is selected by default, with exception of the latest version of each system.
30 |
31 | ### Archives
32 |
33 | When we create build for distrubution or export, a new archive is created. For each version it contains a build, debug symbols and
34 | other informations. Usually we need those archived items for crashes symbolications for example, but for sure we don't need all of them.
35 | Xcode Cleaner allows to quickly inspect the archives and delete older ones or builds that are not on store.
36 |
37 | ### Derived Data
38 |
39 | This is the major "cache" part of Xcode files, where autocompletion data, logs, debug builds, intermediate products and other stuff lives.
40 | The point is that it could be regenerated if necessary. Also some older projects could be removed completely because its rather unusual that
41 | we would use them again.
42 |
43 | ### Documentation Cache
44 |
45 | Modern Xcodes are caching documentation that is accessed via the web. Unfortunately older caches are not cleaned up by Xcode and significant
46 | amount of space may be wasted due to that.
47 |
48 | ### Old Simulator & Device Logs
49 |
50 | Old device logs & crashes databases, only most recent ones are needed. It seems that new versions of Xcodes migrates old logs database, but keeping older ones on disk.
51 |
52 | ### Old Documentation Downloads
53 |
54 | Old Xcodes had ability to download documentation to browse offline, it could've been many gigabytes. Although newer Xcodes has online documentation browser,
55 | those old documentations may be still on your drive if you're an old Xcode user.
56 |
57 | ## Contact
58 |
59 | If you enjoy this application, consider support me by making a tip in the app, or by downloading/buying some of my other apps & games from the AppStore 😎
60 |
61 | Mastodon: [@kkolakowski](https://mastodon.social/@kkolakowski)
62 | Twitter: [@vashpan](https://twitter.com/vashpan), [@oneminutegames](https://twitter.com/OneMinuteGames)
63 |
64 | Website: http://www.one-minute-games.com
65 | AppStore: https://apps.apple.com/developer/one-minute-games/id395763583
66 |
67 | ## Contribution
68 |
69 | This application is my first (larger) macOS app, so feel free to give me some feedback or pull requests! If there's some new feature to support,
70 | maybe some caches I missed, let me know as well in issues.
71 |
72 | Application is licensed using GPL3. You may freely modify, download, redistribute and use this application from this source code, but only me,
73 | as copyright holder can submit it to the Mac App Store.
74 |
75 | Copyright © 2018-2025 One Minute Games Konrad Kołakowski.
76 |
--------------------------------------------------------------------------------
/DevCleaner/View Controllers/Help Window/Help.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logger.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 20.03.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 |
23 | public class Logger {
24 | // MARK: Types
25 | public enum Level {
26 | case info, warning, error
27 | }
28 |
29 | // MARK: Properties
30 | public let level: Level
31 | public let name: String
32 |
33 | public var consoleLogging: Bool
34 | public var fileLogging: Bool {
35 | return self.logFileHandle != nil
36 | }
37 |
38 | public let logFilePath: URL?
39 | public let oldLogFilePath: URL?
40 |
41 | private let logFileHandle: FileHandle?
42 |
43 | // MARK: Initialization
44 | public init(name: String, level: Level = .error, toFile: Bool = false) {
45 | self.name = name
46 | self.level = level
47 | self.consoleLogging = true
48 |
49 | if toFile {
50 | // create logfile path
51 | let bundleId = Bundle.main.bundleIdentifier ?? "UnknownApp"
52 | let newLogFileName = "\(bundleId)-\(self.name)-LogFile-latest.log"
53 | let oldLogFileName = "\(bundleId)-\(self.name)-LogFile-previous.log"
54 | let documentsFolder = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
55 |
56 | // create if needed & open log file to write
57 | if let newLogFilePath = documentsFolder?.appendingPathComponent(newLogFileName), let oldLogFilePath = documentsFolder?.appendingPathComponent(oldLogFileName) {
58 | // first rename current log to old if exists
59 | if FileManager.default.fileExists(atPath: newLogFilePath.path) {
60 | try? FileManager.default.moveItem(at: newLogFilePath, to: oldLogFilePath)
61 | self.oldLogFilePath = oldLogFilePath
62 | } else {
63 | self.oldLogFilePath = nil
64 | }
65 |
66 | do {
67 | FileManager.default.createFile(atPath: newLogFilePath.path, contents: nil, attributes: nil)
68 | self.logFileHandle = try FileHandle(forWritingTo: newLogFilePath)
69 | self.logFilePath = newLogFilePath
70 | } catch(let error) {
71 | self.logFileHandle = nil
72 | self.logFilePath = nil
73 | NSLog("❌ Can't create log file: \(newLogFilePath.path). Error: \(error)")
74 | }
75 | } else {
76 | self.logFileHandle = nil
77 | self.logFilePath = nil
78 | self.oldLogFilePath = nil
79 | }
80 | } else {
81 | self.logFileHandle = nil
82 | self.logFilePath = nil
83 | self.oldLogFilePath = nil
84 | }
85 | }
86 |
87 | deinit {
88 | self.logFileHandle?.closeFile()
89 | }
90 |
91 | // MARK: Helpers
92 | private func writeLog(text: String, level: Level) {
93 | if self.consoleLogging {
94 | NSLog(text)
95 | }
96 |
97 | if let fileHandle = self.logFileHandle {
98 | let textToLogToFile = text + "\n" // add new line for each entry
99 | if let logData = textToLogToFile.data(using: .utf8) {
100 | fileHandle.write(logData)
101 | fileHandle.synchronizeFile()
102 | }
103 | }
104 | }
105 |
106 | // MARK: Log methods
107 | public func info(_ message: String) {
108 | switch self.level {
109 | case .info:
110 | self.writeLog(text: "❕ \(message)", level: .info)
111 | default:
112 | return
113 | }
114 | }
115 |
116 | public func warning(_ message: String) {
117 | switch self.level {
118 | case .info, .warning:
119 | self.writeLog(text: "⚠️ \(message)", level: .warning)
120 | default:
121 | return
122 | }
123 | }
124 |
125 | public func error(_ message: String) {
126 | switch self.level {
127 | case .info, .warning, .error:
128 | self.writeLog(text: "❌ \(message)", level: .error)
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/DevCleaner/Managers/FeedbackMailer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FeedbackMailer.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 03/06/2021.
6 | // Copyright © 2021 One Minute Games. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import IOKit
11 | import Cocoa
12 |
13 | public final class FeedbackMailer {
14 | // MARK: Properties
15 | public static let shared = FeedbackMailer()
16 |
17 | // MARK: Constants
18 | private static let feedbackMailBase64 = "a29ucmFkLmtvbGFrb3dza2lAbWUuY29t" // base64 encoded to avoid spam bots
19 |
20 | // MARK: System profile
21 |
22 | private func macModelIdentifier() -> String? {
23 | let service = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IOPlatformExpertDevice"))
24 | var modelIdentifier: String?
25 | if let modelData = IORegistryEntryCreateCFProperty(service, "model" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Data {
26 | modelIdentifier = String(data: modelData, encoding: .utf8)?.trimmingCharacters(in: .controlCharacters)
27 | }
28 |
29 | IOObjectRelease(service)
30 | return modelIdentifier
31 | }
32 |
33 | private func prepareSystemProfileInfo() -> String {
34 | let fm = FileManager.default
35 | let appBundleInfoDict = Bundle.main.infoDictionary
36 | let appVersion = (appBundleInfoDict?["CFBundleShortVersionString"] as? String) ?? "-"
37 | let appBuildNumber = (appBundleInfoDict?["CFBundleVersion"] as? String) ?? "-"
38 | let osVersionInfo = ProcessInfo.processInfo.operatingSystemVersionString
39 | let osInfoString = "macOS \(osVersionInfo)"
40 | let macModelIdentifier = self.macModelIdentifier() ?? "-"
41 | let devFolderPath = Files.userDeveloperFolder.path
42 | let isDevFolderExists = XcodeFiles.isDeveloperFolderExists()
43 | let developerFolderReadable = fm.isReadableFile(atPath: devFolderPath)
44 | let developerFolderWriteable = fm.isWritableFile(atPath: devFolderPath)
45 | let customDerivedDataFolder = Preferences.shared.customDerivedDataFolder?.path ?? "-"
46 | let customArchivesFolder = Preferences.shared.customArchivesFolder?.path ?? "-"
47 |
48 | return """
49 | DevCleaner \(appVersion) (\(appBuildNumber))
50 | Does '~/Library/Developer' folder exists: \(isDevFolderExists ? "YES" : "NO")
51 | Can read '~/Library/Developer' folder: \(developerFolderReadable ? "YES" : "NO")
52 | Can write '~/Library/Developer' folder: \(developerFolderWriteable ? "YES" : "NO")
53 |
54 | Custom derived data folder: \(customDerivedDataFolder)
55 | Custom archives folder: \(customArchivesFolder)
56 |
57 | System: \(osInfoString)
58 | Mac model: \(macModelIdentifier)
59 |
60 | """
61 | }
62 |
63 | // MARK: Sending e-mails
64 | private func sendEmail(subject: String, body: String, attachments: [URL]) {
65 | guard let emailService = NSSharingService(named: .composeEmail) else {
66 | log.warning("FeedbackMailer: Can't create compose e-mail NSSharingService")
67 | return
68 | }
69 |
70 | guard let decodedEmailData = Data(base64Encoded: Self.feedbackMailBase64),
71 | let decodedEmail = String(data: decodedEmailData, encoding: .utf8) else {
72 | log.error("FeedbackMailer: Error while decoding support e-mail")
73 | return
74 | }
75 |
76 | var emailItems: [Any] = []
77 | emailItems.append(body)
78 | if !attachments.isEmpty { // for some reason it won't work if we have empty attachments array
79 | emailItems.append(contentsOf: attachments)
80 | }
81 |
82 | emailService.recipients = [decodedEmail]
83 | emailService.subject = subject
84 |
85 | if emailService.canPerform(withItems: emailItems) {
86 | emailService.perform(withItems: emailItems)
87 | } else {
88 | Alerts.infoAlert(title: "Can't send e-mail",
89 | message: "It seems your system doesn't have e-mail configured. You can send your feedback manually to \(decodedEmail)")
90 | }
91 | }
92 |
93 | public func sendFeedback() {
94 | self.sendEmail(subject: "DevCleaner Feedback", body: "", attachments: [])
95 | }
96 |
97 | public func reportAnIssue() {
98 | // logs attachments
99 | var logAttachments: [URL] = []
100 | if let currentLogPath = log.logFilePath { logAttachments.append(currentLogPath) }
101 | if let oldLogPath = log.oldLogFilePath { logAttachments.append(oldLogPath) }
102 |
103 | // some system informations
104 | let systemProfile = self.prepareSystemProfileInfo()
105 |
106 | let reportBody = """
107 | Write your report here
108 |
109 | =================================================
110 |
111 | \(systemProfile)
112 | """
113 |
114 | self.sendEmail(subject: "DevCleaner Issue Report", body: reportBody, attachments: logAttachments)
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/Version.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Version.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 18.03.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 |
23 | // MARK: Version struct
24 | public struct Version {
25 | // MARK: Properties
26 | public let major: UInt
27 | public let minor: UInt
28 | public let patch: UInt?
29 |
30 | // MARK: Initialization
31 | init(major: UInt, minor: UInt, patch: UInt? = nil) {
32 | self.major = major
33 | self.minor = minor
34 | self.patch = patch
35 | }
36 |
37 | init?(string: String) {
38 | let components = string.split(separator: ".", maxSplits: 3, omittingEmptySubsequences: true)
39 |
40 | if components.count == 3 {
41 | if let majorInt = UInt(components[0]) {
42 | self.major = majorInt
43 | } else {
44 | return nil
45 | }
46 |
47 | if let minorInt = UInt(components[1]) {
48 | self.minor = minorInt
49 | } else {
50 | return nil
51 | }
52 |
53 | if let patchInt = UInt(components[2]) {
54 | self.patch = patchInt
55 | } else {
56 | return nil
57 | }
58 | } else if components.count == 2 {
59 | if let majorInt = UInt(components[0]) {
60 | self.major = majorInt
61 | } else {
62 | return nil
63 | }
64 |
65 | if let minorInt = UInt(components[1]) {
66 | self.minor = minorInt
67 | } else {
68 | return nil
69 | }
70 |
71 | self.patch = nil
72 |
73 | } else {
74 | return nil
75 | }
76 | }
77 |
78 | init?(osVersionRawString: String) {
79 | // regex to get version from strings like those: NSOperatingSystemVersion(majorVersion/ 16, minorVersion/ 2, patchVersion/ 0)
80 | let pattern = #"majorVersion[:/]\s*(\d+).*?minorVersion[:/]\s*(\d+).*?patchVersion[:/]\s*(\d+)"#
81 | guard let regex = try? NSRegularExpression(pattern: pattern) else { return nil }
82 |
83 | let range = NSRange(osVersionRawString.startIndex..., in: osVersionRawString)
84 | guard let match = regex.firstMatch(in: osVersionRawString, range: range), match.numberOfRanges == 4 else { return nil }
85 |
86 | let nsString = osVersionRawString as NSString
87 | let major = nsString.substring(with: match.range(at: 1))
88 | let minor = nsString.substring(with: match.range(at: 2))
89 | let patch = nsString.substring(with: match.range(at: 3))
90 |
91 | // if we have "0" in patch, ignore it as Apple convention is to use 16.2 instead of 16.2.0
92 | let versionString: String
93 | if patch == "0" {
94 | versionString = "\(major).\(minor)"
95 | } else {
96 | versionString = "\(major).\(minor).\(patch)"
97 | }
98 |
99 | self.init(string: versionString)
100 | }
101 |
102 | init?(describing: String) {
103 | let isRawOSVersionString = describing.starts(with: "NSOperatingSystemVersion")
104 | switch isRawOSVersionString {
105 | case true: self.init(osVersionRawString: describing)
106 | case false: self.init(string: describing)
107 | }
108 | }
109 | }
110 |
111 | // MARK: - Comparable implementation
112 | extension Version: Comparable {
113 | public static func ==(lhs: Version, rhs: Version) -> Bool {
114 | if lhs.major == rhs.major {
115 | if lhs.minor == rhs.minor {
116 | let lhsPatch = lhs.patch ?? 0
117 | let rhsPatch = rhs.patch ?? 0
118 |
119 | if lhsPatch == rhsPatch {
120 | return true
121 | }
122 | }
123 | }
124 |
125 | return false
126 | }
127 |
128 | public static func <(lhs: Version, rhs: Version) -> Bool {
129 | if lhs.major == rhs.major {
130 | if lhs.minor == rhs.minor {
131 | let lhsPatch = lhs.patch ?? 0
132 | let rhsPatch = rhs.patch ?? 0
133 |
134 | return lhsPatch < rhsPatch
135 | } else {
136 | return lhs.minor < rhs.minor
137 | }
138 | } else {
139 | return lhs.major < rhs.major
140 | }
141 | }
142 | }
143 |
144 | // MARK: - CustomStringConvertible conformance
145 | extension Version: CustomStringConvertible {
146 | public var description: String {
147 | var result = "\(self.major).\(self.minor)"
148 | if let patch = self.patch {
149 | result += ".\(patch)"
150 | }
151 |
152 | return result
153 | }
154 | }
155 |
156 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/Extensions/URL+AcquireAccessFromSandbox.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URL+AcquireAccessFromSandbox.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 16.09.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | //
13 | // An article that tries to describe intricacies of sanbos file access:
14 | // https://benscheirman.com/2019/10/troubleshooting-appkit-file-permissions/
15 | //
16 |
17 | extension URL {
18 | private struct SandboxFolderAccessError: Error {
19 |
20 | }
21 |
22 | public func acquireAccessFromSandbox(bookmark: Data? = nil, allowCancel: Bool = true, openPanelMessage: String = "Application needs permission to access this folder") -> URL? {
23 | func doWeHaveAccess(for url: URL) -> Bool {
24 | let fm = FileManager.default
25 |
26 | let isAccesible: Bool
27 | let _ = url.startAccessingSecurityScopedResource()
28 | isAccesible = fm.isReadableFile(atPath: url.path) && fm.isWritableFile(atPath: url.path)
29 | url.stopAccessingSecurityScopedResource()
30 |
31 | return isAccesible
32 | }
33 |
34 | // check if we already have access, then we don't need to show the dialog or use security bookmarks
35 | if doWeHaveAccess(for: self) {
36 | return self
37 | }
38 |
39 | // if we don't have access, so first try to load security bookmark
40 | if let bookmarkData = bookmark {
41 | do {
42 | var isBookmarkStale = false
43 | let bookmarkedUrl = try URL(resolvingBookmarkData: bookmarkData, options: [.withSecurityScope], bookmarkDataIsStale: &isBookmarkStale)
44 |
45 | if !isBookmarkStale {
46 | if doWeHaveAccess(for: bookmarkedUrl) {
47 | return bookmarkedUrl
48 | } else {
49 | log.warning("URL+AcquireAccessFromSandbox: Access denied after using bookmark but bookmark is not stale!")
50 | throw SandboxFolderAccessError()
51 | }
52 | } else {
53 | // refresh bookmark as it's stale, it can happen on some system changes, updates etc.
54 | if let bookmarkData = try? self.bookmarkData(options: [.withSecurityScope]) {
55 | Preferences.shared.setFolderBookmark(bookmarkData: bookmarkData, for: self)
56 |
57 | return self.acquireAccessFromSandbox(bookmark: bookmarkData, allowCancel: allowCancel, openPanelMessage: openPanelMessage)
58 | } else {
59 | log.warning("URL+AcquireAccessFromSandbox: Bookmark was stale, but cannot refresh a bookmark for some reason!")
60 | throw SandboxFolderAccessError()
61 | }
62 | }
63 | } catch(let error) { // in case of stale bookmark or fail to get one, try again without it
64 | log.warning("URL+AcquireAccessFromSandbox: Failed to resolve bookmark: \(error.localizedDescription)")
65 |
66 | return self.acquireAccessFromSandbox(bookmark: nil, allowCancel: allowCancel, openPanelMessage: openPanelMessage)
67 | }
68 | }
69 |
70 | // well, so maybe first acquire the bookmark by opening open panel?
71 | let openPanel = NSOpenPanel()
72 | openPanel.directoryURL = self
73 | openPanel.message = openPanelMessage
74 | openPanel.prompt = "Open"
75 |
76 | openPanel.allowedContentTypes = []
77 | openPanel.allowsOtherFileTypes = false
78 | openPanel.canChooseDirectories = true
79 |
80 | let openPanelResponse = openPanel.runModal()
81 |
82 | // check if we get proper file & save bookmark to it, if not, repeat
83 | if let folderUrl = openPanel.urls.first {
84 | if folderUrl != self {
85 | Alerts.infoAlert(title: "Can't get access to \(self.path) folder",
86 | message: "Did you choose the right folder?",
87 | okButtonText: "Repeat")
88 |
89 | return self.acquireAccessFromSandbox(bookmark: nil, allowCancel: allowCancel, openPanelMessage: openPanelMessage)
90 | }
91 |
92 | if doWeHaveAccess(for: folderUrl) {
93 | if let bookmarkData = try? folderUrl.bookmarkData(options: [.withSecurityScope]) {
94 | Preferences.shared.setFolderBookmark(bookmarkData: bookmarkData, for: self)
95 |
96 | return folderUrl
97 | }
98 | } else {
99 | // well, we tried but we can't get access to this folder
100 | log.error("URL+AcquireAccessFromSandbox: Can't access folder after selecting it from Open panel, no access: \(self.path)")
101 |
102 | // delete folder bookmark just in case
103 | Preferences.shared.setFolderBookmark(bookmarkData: nil, for: self)
104 |
105 | return nil
106 | }
107 | } else {
108 | log.warning("URL+AcquireAccessFromSandbox: Didn't get folder from Open panel! Modal response: \(openPanelResponse)")
109 |
110 | // if we allow cancel, then legitimately return
111 | if allowCancel {
112 | return nil
113 | }
114 | }
115 |
116 | return self.acquireAccessFromSandbox(bookmark: nil, allowCancel: allowCancel, openPanelMessage: openPanelMessage)
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/DevCleaner/Model/Entries/DeviceSupportFileEntry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DeviceSupportFileEntry.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 27.03.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 |
23 | public final class DeviceSupportFileEntry: XcodeFileEntry {
24 | // MARK: Types
25 | public enum OSType {
26 | case iOS, watchOS, tvOS, macOS, visionOS, other
27 |
28 | public init(label: String) {
29 | switch label {
30 | case "iOS":
31 | self = .iOS
32 | case "watchOS":
33 | self = .watchOS
34 | case "tvOS":
35 | self = .tvOS
36 | case "macOS":
37 | self = .macOS
38 | case "visionOS":
39 | self = .visionOS
40 | default:
41 | self = .other
42 | }
43 | }
44 |
45 | public var description: String {
46 | switch self {
47 | case .iOS:
48 | return "iOS"
49 | case .watchOS:
50 | return "watchOS"
51 | case .tvOS:
52 | return "tvOS"
53 | case .macOS:
54 | return "macOS"
55 | case .visionOS:
56 | return "visionOS"
57 | case .other:
58 | return ""
59 | }
60 | }
61 | }
62 |
63 | // MARK: Properties
64 | public let device: String?
65 | public let osType: OSType
66 | public let version: Version
67 | public let build: AppleBuild?
68 | public let date: Date
69 | public let architecture: String?
70 |
71 | // MARK: Initialization
72 | public init(device: String?, osType: OSType, version: Version, build: AppleBuild?, date: Date, arch: String?, selected: Bool) {
73 | self.device = device
74 | self.osType = osType
75 | self.version = version
76 | self.build = build
77 | self.date = date
78 | self.architecture = arch
79 |
80 | let label = Self.label(for: osType, device: device, version: version, build: build)
81 | let icon = Self.icon(for: osType, version: version)
82 | let tooltip = label + " " + DateFormatter.localizedString(from: self.date, dateStyle: .medium, timeStyle: .none)
83 |
84 | super.init(label: label, tooltipText: tooltip, icon: icon, tooltip: true, selected: selected)
85 | }
86 |
87 | // MARK: Helpers
88 | private static func label(for os: OSType, device: String?, version: Version, build: AppleBuild?) -> String {
89 | let appleBuild = build ?? .empty
90 |
91 | var result = "\(os.description) \(version) \(appleBuild)"
92 | if let device {
93 | result = result + " (\(Self.modelName(from: device)))"
94 | }
95 |
96 | return result
97 | }
98 |
99 | private static func icon(for os: OSType, version: Version) -> Icon {
100 | var result: Icon
101 |
102 | switch os {
103 | case .iOS:
104 | if version.major >= 2 && version.major <= 18 || version.major == 26 {
105 | result = .image(name: "OS/iOS/\(version.major)")
106 | } else {
107 | result = .image(name: "OS/iOS/Generic")
108 | }
109 |
110 | case .watchOS:
111 | if version.major >= 2 && version.major <= 11 || version.major == 26 {
112 | result = .image(name: "OS/watchOS/\(version.major)")
113 | } else {
114 | result = .image(name: "OS/watchOS/Generic")
115 | }
116 |
117 | case .tvOS:
118 | if version.major >= 9 && version.major <= 18 || version.major == 26 {
119 | result = .image(name: "OS/tvOS/\(version.major)")
120 | } else {
121 | result = .image(name: "OS/tvOS/Generic")
122 | }
123 |
124 | case .macOS:
125 | if version.major >= 12 && version.major <= 15 || version.major == 26 {
126 | result = .image(name: "OS/macOS/\(version.major)")
127 | } else {
128 | result = .image(name: "OS/macOS/Generic")
129 | }
130 |
131 | case .visionOS:
132 | if version.major >= 2 && version.major <= 2 || version.major == 26 {
133 | result = .image(name: "OS/visionOS/\(version.major)")
134 | } else {
135 | result = .image(name: "OS/visionOS/Generic")
136 | }
137 |
138 | default:
139 | result = .image(name: "OS/iOS/Generic")
140 | }
141 |
142 | return result
143 | }
144 |
145 | // MARK: Map device model to device name
146 | private static func modelName(from deviceId: String) -> String {
147 | return AppleDevices.deviceName(for: deviceId)
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/DevCleaner/View Controllers/Main Window/CleaningViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CleaningViewController.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 29.04.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Cocoa
22 |
23 | // MARK: Cleaning view controller delegate
24 | internal protocol CleaningViewControllerDelegate: AnyObject {
25 | func cleaningDidFinish(_ vc: CleaningViewController)
26 | }
27 |
28 | // MARK: - Cleaning view controller
29 | internal final class CleaningViewController: NSViewController {
30 | // MARK: Types
31 | internal enum State {
32 | case undefined
33 |
34 | case idle(title: String, progress: Double)
35 | case working(title: String, details: String, progress: Double)
36 | }
37 |
38 | // MARK: Properties & outlets
39 | @IBOutlet private weak var headerLabel: NSTextField!
40 | @IBOutlet private weak var currentFileLabel: NSTextField!
41 | @IBOutlet private weak var progressIndicator: NSProgressIndicator!
42 |
43 | internal weak var delegate: CleaningViewControllerDelegate?
44 |
45 | internal var state: State = .undefined {
46 | didSet {
47 | if self.isViewLoaded {
48 | self.update(state: self.state)
49 | }
50 | }
51 | }
52 |
53 | // MARK: Initialization & overrides
54 | override func viewDidLoad() {
55 | super.viewDidLoad()
56 |
57 | // update first state we set
58 | self.update(state: self.state)
59 |
60 | // check if we are in dry run and mark it
61 | let dryRunText = Preferences.shared.dryRunEnabled ? "(Dry run) " : String()
62 | self.headerLabel.stringValue = "\(dryRunText)Cleaning Xcode cache files..."
63 | }
64 |
65 | override func viewDidAppear() {
66 | super.viewDidAppear()
67 |
68 | self.view.window?.styleMask.remove(.resizable)
69 | }
70 |
71 | // MARK: Updating state
72 | private func update(state: State) {
73 | switch state {
74 | case .idle(let title, let progress):
75 | self.currentFileLabel.stringValue = title
76 |
77 | self.progressIndicator.isIndeterminate = false
78 | self.progressIndicator.stopAnimation(self)
79 |
80 | self.progressIndicator.doubleValue = progress
81 |
82 | case .working(let title, let details, let progress):
83 | self.currentFileLabel.stringValue = "\(title): \(details)"
84 |
85 | self.progressIndicator.isIndeterminate = false
86 | self.progressIndicator.stopAnimation(self)
87 |
88 | self.progressIndicator.doubleValue = progress
89 |
90 | case .undefined:
91 | assert(false, "CleaningViewController: Cannot update to state 'undefined'")
92 | }
93 | }
94 |
95 | // MARK: Action
96 | @IBAction func dismissCleaningView(_ sender: Any) {
97 | self.dismiss(sender)
98 |
99 | self.delegate?.cleaningDidFinish(self)
100 | }
101 |
102 | @IBAction func stopCleaning(_ sender: Any) {
103 | self.dismiss(sender)
104 |
105 | self.delegate?.cleaningDidFinish(self)
106 | }
107 | }
108 |
109 | extension CleaningViewController: XcodeFilesDeleteDelegate {
110 | func deleteWillBegin(xcodeFiles: XcodeFiles) {
111 | DispatchQueue.main.async {
112 | self.state = .idle(title: "Initialization...", progress: 0.0)
113 | }
114 | }
115 |
116 | func deleteInProgress(xcodeFiles: XcodeFiles, location: String, label: String, url: URL?, current: Int, total: Int) {
117 | let progress = Double(current) / Double(total) * 100.0
118 | DispatchQueue.main.async {
119 | self.state = .working(title: location.capitalized, details: label, progress: progress)
120 | }
121 | }
122 |
123 | func deleteItemFailed(xcodeFiles: XcodeFiles, error: Error, location: String, label: String, url: URL?) {
124 | // prepare error message
125 | let message = """
126 | Following file couldn't be removed:\n\(location.capitalized): \(url?.path ?? "-")\n\n
127 | \(error.localizedDescription)
128 | """
129 |
130 | // show error message
131 | DispatchQueue.main.async {
132 | let alert = NSAlert()
133 | alert.alertStyle = .critical
134 | alert.messageText = "Failed to delete item"
135 | alert.informativeText = message
136 | alert.addButton(withTitle: "OK")
137 | alert.runModal()
138 | }
139 | }
140 |
141 | func deleteDidFinish(xcodeFiles: XcodeFiles) {
142 | DispatchQueue.main.async {
143 | self.state = .idle(title: "Finished!", progress: 100.0)
144 |
145 | // wait a little bit and then dismiss to avoid too abtrupt transition
146 | DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 0.5) {
147 | self.dismiss(self)
148 | self.delegate?.cleaningDidFinish(self)
149 | }
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/DevCleaner/View Controllers/Main Window/Views/XcodeEntryCellView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XcodeEntryCellView.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 14.04.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Cocoa
22 |
23 | // MARK: Xcode Entry Cell View Delegate
24 | protocol XcodeEntryCellViewDelegate: AnyObject {
25 | func xcodeEntryCellSelectedChanged(_ cell: XcodeEntryCellView, state: NSControl.StateValue, xcodeEntry: XcodeFileEntry?)
26 | }
27 |
28 | // MARK: Xcode Entry Cell View
29 | final class XcodeEntryCellView: NSTableCellView {
30 | // MARK: Properties & outlets
31 | @IBOutlet private weak var checkBox: NSButton!
32 |
33 | internal weak var delegate: XcodeEntryCellViewDelegate? = nil
34 |
35 | internal private(set) weak var entry: XcodeFileEntry?
36 |
37 | // MARK: Setup
38 | internal func setup(with xcodeEntry: XcodeFileEntry, delegate: XcodeEntryCellViewDelegate) {
39 | // reassing entry
40 | self.entry = xcodeEntry
41 |
42 | // delegate
43 | self.delegate = delegate
44 |
45 | // checkbox
46 | self.checkBox.state = self.entrySelectionToControlState(xcodeEntry.selection)
47 |
48 | // label
49 | self.textField?.font = NSFont.monospacedDigitSystemFont(ofSize: self.textField?.font?.pointSize ?? 13, weight: .regular)
50 | self.textField?.attributedStringValue = self.attributedString(for: xcodeEntry)
51 | self.textField?.sizeToFit()
52 |
53 | // tooltip
54 | if xcodeEntry.tooltip {
55 | self.toolTip = xcodeEntry.tooltipText
56 | } else {
57 | self.toolTip = nil
58 | }
59 |
60 | // icon
61 | self.imageView?.image = self.iconForEntry(xcodeEntry)
62 |
63 | // disable if no children and path
64 | if xcodeEntry.isEmpty {
65 | self.checkBox.isEnabled = false
66 | self.checkBox.state = .off
67 |
68 | self.imageView?.isEnabled = false
69 | self.textField?.isEnabled = false
70 | }
71 | }
72 |
73 | // MARK: Helpers
74 | override func prepareForReuse() {
75 | super.prepareForReuse()
76 |
77 | self.checkBox.isEnabled = true
78 | self.checkBox.state = .off
79 |
80 | self.imageView?.isEnabled = true
81 | self.textField?.isEnabled = true
82 | }
83 |
84 | private func attributedString(for xcodeEntry: XcodeFileEntry) -> NSAttributedString {
85 | let result = NSMutableAttributedString()
86 |
87 | // label
88 | let label = NSAttributedString(string: xcodeEntry.label)
89 | result.append(label)
90 |
91 | // extra info if present
92 | if !xcodeEntry.extraInfo.isEmpty {
93 | let extraInfo = NSAttributedString(string: " " + xcodeEntry.extraInfo, attributes: [
94 | NSAttributedString.Key.foregroundColor: NSColor.secondaryLabelColor
95 | ])
96 | result.append(extraInfo)
97 | }
98 |
99 | // add truncating options
100 | let style = NSMutableParagraphStyle()
101 | style.lineBreakMode = .byTruncatingTail
102 | result.addAttributes([NSAttributedString.Key.paragraphStyle: style], range: NSRange(location: 0, length: result.length))
103 |
104 | return result
105 | }
106 |
107 | private func entrySelectionToControlState(_ entrySelection: XcodeFileEntry.Selection) -> NSControl.StateValue {
108 | switch entrySelection {
109 | case .on:
110 | return .on
111 | case .off:
112 | return .off
113 | case .mixed:
114 | return .mixed
115 | }
116 | }
117 |
118 | private func iconForEntry(_ xcodeEntry: XcodeFileEntry) -> NSImage? {
119 | let result: NSImage?
120 |
121 | if let entryIcon = xcodeEntry.icon {
122 | switch entryIcon {
123 | case .path(let url):
124 | result = NSImage(byReferencing: url)
125 | case .image(let name):
126 | result = NSImage(imageLiteralResourceName: name)
127 | case .system(let name):
128 | result = NSImage(named: name)
129 | }
130 | } else {
131 | result = nil
132 | }
133 |
134 | return result
135 | }
136 |
137 | // MARK: Actions
138 | internal func toggleSelection() {
139 | let targetStateValue: NSControl.StateValue
140 | if self.checkBox.state == .on {
141 | targetStateValue = .off
142 | } else {
143 | targetStateValue = .on
144 | }
145 |
146 | self.checkBox.state = targetStateValue
147 | self.delegate?.xcodeEntryCellSelectedChanged(self, state: targetStateValue, xcodeEntry: self.entry)
148 | }
149 |
150 | @IBAction func checkBoxSwitched(_ sender: NSButton) {
151 | // when we click, disallow mixed state
152 | if sender.state == .mixed {
153 | sender.setNextState()
154 | }
155 |
156 | self.delegate?.xcodeEntryCellSelectedChanged(self, state: sender.state, xcodeEntry: self.entry)
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/Extensions/FileManager+DirectorySize.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileManager+DirectorySize.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 18.03.2018.
6 | // Copyright © 2018 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | //
22 | // Original code:
23 | //
24 | // Created by Nikolai Ruhe on 2016-02-10.
25 | // Copyright (c) 2016 Nikolai Ruhe. All rights reserved.
26 | //
27 | // https://gist.github.com/NikolaiRuhe/eeb135d20c84a7097516
28 | // https://gist.github.com/blender/a75f589e6bd86aa2121618155cbdf827
29 | //
30 |
31 | import Foundation
32 |
33 | extension FileManager {
34 | /// This method calculates the accumulated size of a directory on the volume in bytes.
35 | ///
36 | /// As there's no simple way to get this information from the file system it has to crawl the entire hierarchy,
37 | /// accumulating the overall sum on the way. The resulting value is roughly equivalent with the amount of bytes
38 | /// that would become available on the volume if the directory would be deleted.
39 | ///
40 | /// - note: There are a couple of oddities that are not taken into account (like symbolic links, meta data of
41 | /// directories, hard links, ...).
42 | public func allocatedSizeOfDirectory(atUrl url: URL) throws -> Int64 {
43 | // We'll sum up content size here:
44 | var accumulatedSize: Int64 = 0
45 |
46 | // prefetching some properties during traversal will speed up things a bit.
47 | let prefetchedProperties: [URLResourceKey] = [
48 | .isRegularFileKey,
49 | .fileAllocatedSizeKey,
50 | .totalFileAllocatedSizeKey
51 | ]
52 |
53 | // The error handler simply signals errors to outside code.
54 | var errorDidOccur: Error?
55 | let errorHandler: (URL, Error) -> Bool = { _, error in
56 | errorDidOccur = error
57 | return false
58 | }
59 |
60 |
61 | // We have to enumerate all directory contents, including subdirectories.
62 | let enumerator = self.enumerator(at: url,
63 | includingPropertiesForKeys: prefetchedProperties,
64 | options: FileManager.DirectoryEnumerationOptions.init(rawValue: 0),
65 | errorHandler: errorHandler)
66 |
67 | // Start the traversal:
68 | while let contentURL = (enumerator?.nextObject() as? URL) {
69 | // Bail out on errors from the errorHandler.
70 | if let error = errorDidOccur { throw error }
71 |
72 | // Get the type of this item, making sure we only sum up sizes of regular files.
73 | let isRegularFileResourceValues = try contentURL.resourceValues(forKeys: [.isRegularFileKey])
74 |
75 | guard isRegularFileResourceValues.isRegularFile ?? false else {
76 | continue
77 | }
78 |
79 | // Get size values only if we're sure we calculating file size
80 | let resourceValues = try contentURL.resourceValues(forKeys: [.fileAllocatedSizeKey, .totalFileAllocatedSizeKey])
81 |
82 | // To get the file's size we first try the most comprehensive value in terms of what the file may use on disk.
83 | // This includes metadata, compression (on file system level) and block size.
84 | var fileSize = resourceValues.totalFileAllocatedSize
85 |
86 | // In case the value is unavailable we use the fallback value (excluding meta data and compression)
87 | // This value should always be available.
88 | fileSize = fileSize ?? resourceValues.fileAllocatedSize
89 |
90 | // We're good, add up the value.
91 | accumulatedSize += Int64(fileSize ?? 0)
92 | }
93 |
94 | // Bail out on errors from the errorHandler.
95 | if let error = errorDidOccur { throw error }
96 |
97 | // We finally got it.
98 | return accumulatedSize
99 | }
100 |
101 | public func allocatedSizeOfFile(at url: URL) throws -> Int64 {
102 | // Get the type of this item, making sure we only sum up sizes of regular files.
103 | let resourceValues = try url.resourceValues(forKeys: [.isRegularFileKey, .totalFileAllocatedSizeKey, .fileAllocatedSizeKey])
104 |
105 | guard resourceValues.isRegularFile ?? false else {
106 | return 0
107 | }
108 |
109 | // To get the file's size we first try the most comprehensive value in terms of what the file may use on disk.
110 | // This includes metadata, compression (on file system level) and block size.
111 | var fileSize = resourceValues.totalFileAllocatedSize
112 |
113 | // In case the value is unavailable we use the fallback value (excluding meta data and compression)
114 | // This value should always be available.
115 | fileSize = fileSize ?? resourceValues.fileAllocatedSize
116 |
117 | return Int64(fileSize ?? 0)
118 | }
119 |
120 | public func volumeFreeDiskSpace(at url: URL) throws -> Int64 {
121 | do {
122 | let values = try url.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey])
123 | if let capacity = values.volumeAvailableCapacityForImportantUsage {
124 | return capacity
125 | }
126 | } catch let error {
127 | log.warning("FileManager+DirectorySize: Problem while requesting volume capacity: \(error)")
128 | }
129 |
130 | return 0
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/DevCleaner/Utilities/ArgumentsParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArgumentsParser.swift
3 | // DevCleaner
4 | //
5 | // Created by Konrad Kołakowski on 24/08/2019.
6 | // Copyright © 2019 One Minute Games. All rights reserved.
7 | //
8 | // DevCleaner is free software: you can redistribute it and/or modify
9 | // it under the terms of the GNU General Public License as published by
10 | // the Free Software Foundation; either version 3 of the License, or
11 | // (at your option) any later version.
12 | //
13 | // DevCleaner is distributed in the hope that it will be useful,
14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | // GNU General Public License for more details.
17 | //
18 | // You should have received a copy of the GNU General Public License
19 | // along with DevCleaner. If not, see .
20 |
21 | import Foundation
22 |
23 | // If you ever want to publish this as a separate library (or part of some utils code)
24 | // it will require some improvements:
25 | //
26 | // - support for "commands" in a separation of options
27 | // - built-in parsing of values
28 | // - shorthands
29 | // - improved help printing, with better tabulation (check out "swift" command help output)
30 |
31 | // MARK: Command line options
32 | public protocol CommandLineOption {
33 | var name: String {get}
34 | var description: String {get}
35 |
36 | var enabled: Bool {get}
37 | }
38 |
39 | public struct OptionWithValue: CommandLineOption {
40 | public let name: String
41 | public let description: String
42 |
43 | public let possibleValues: [String]?
44 | public var value: String?
45 |
46 | public var enabled: Bool {
47 | return self.value != nil
48 | }
49 | }
50 |
51 | public struct Option: CommandLineOption {
52 | public let name: String
53 | public let description: String
54 |
55 | public internal(set) var enabled: Bool
56 | }
57 |
58 | // MARK: - Arguments parser
59 | public class ArgumentsParser {
60 | // MARK: Types
61 | public enum Error: Swift.Error {
62 | case wrongArgument(name: String), noValue(optionName: String), insufficientArguments
63 | }
64 |
65 | // MARK: Properties
66 | private let toolName: String
67 | private let description: String
68 | private var options: [CommandLineOption]
69 |
70 | // MARK: Initialization
71 | public init(toolName: String? = nil, description: String) {
72 | self.toolName = toolName ?? ProcessInfo.processInfo.processName
73 | self.description = description
74 | self.options = []
75 | }
76 |
77 | // MARK: Adding arguments
78 | public func addOption(name: String, description: String) {
79 | let option = Option(name: name, description: description, enabled: false)
80 | self.options.append(option)
81 | }
82 |
83 | public func addOptionWithValue(name: String, description: String, possibleValues: [String]? = nil) {
84 | let option = OptionWithValue(name: name, description: description, possibleValues: possibleValues, value: nil)
85 | self.options.append(option)
86 | }
87 |
88 | // MARK: Parsing
89 | public func parse(using args: [String]) throws -> [CommandLineOption] {
90 | // we have options but non were given
91 | if self.options.count > 0 && args.count == 1 {
92 | throw Error.insufficientArguments
93 | } else if self.options.count == 0 {
94 | return [] // if no options then we can just return an empty array early on
95 | }
96 |
97 | var results = [CommandLineOption]()
98 |
99 | var i = 1 // ignore first one as its a name of program
100 | while i < args.count {
101 | let arg = args[i]
102 |
103 | var currentArgParsed = false
104 | for option in self.options {
105 | if arg == option.name && !results.contains(where: { $0.name == option.name }) {
106 | // option with value
107 | if var currentOptionWithValue = option as? OptionWithValue {
108 | // find and parse value
109 | let nextArgIndex = i + 1
110 | if nextArgIndex < args.count {
111 | currentOptionWithValue.value = args[nextArgIndex]
112 |
113 | i = nextArgIndex
114 | } else {
115 | throw Error.noValue(optionName: currentOptionWithValue.name)
116 | }
117 |
118 | results.append(currentOptionWithValue)
119 | }
120 | // just option
121 | else if var currentNewOption = option as? Option {
122 | currentNewOption.enabled = true
123 | results.append(currentNewOption)
124 | }
125 |
126 | currentArgParsed = true
127 | break
128 | }
129 | }
130 |
131 | if !currentArgParsed {
132 | throw Error.wrongArgument(name: arg)
133 | }
134 |
135 | i += 1
136 | }
137 |
138 | return results
139 | }
140 |
141 | // MARK: Utilities
142 | public func printHelp() {
143 | print("OVERVIEW: \(self.description)\n")
144 | print("USAGE: \(self.toolName + " [options]")\n")
145 | print("OPTIONS:\n")
146 |
147 | for option in self.options {
148 | if let optionWithValue = option as? OptionWithValue {
149 | let valueString: String
150 | if let possibleValues = optionWithValue.possibleValues {
151 | valueString = "<\(possibleValues.reduce("") { $0 + $1 + "," }.dropLast(1))>"
152 | } else {
153 | valueString = ""
154 | }
155 |
156 | print(" \(optionWithValue.name) \(valueString)")
157 | print("\t\(optionWithValue.description)")
158 | } else {
159 | print(" \(option.name)")
160 | print("\t\(option.description)")
161 | }
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/DevCleaner/View Controllers/Command Line Install Window/CommandLineInstall.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | You can use DevCleaner from the command line. To make it easier, you can link command line tool script to one of your directories in PATH, for example /usr/local/bin.
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------