├── .vscode
└── settings.json
├── Gemfile
├── TrashSweep
├── AppIcon.icns
├── Assets.xcassets
│ ├── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Icon Status Badge.iconbadgeset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── TrashSweep.entitlements
├── Info.plist
├── TrashSweepApp.swift
└── ContentView.swift
├── assets
└── trash-sweep-screenshot.png
├── _config.yml
├── TrashSweep.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcuserdata
│ └── trancong.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── project.pbxproj
├── TrashSweepTests
└── TrashSweepTests.swift
├── .gitignore
├── appcast.xml
├── TrashSweepUITests
├── TrashSweepUITestsLaunchTests.swift
└── TrashSweepUITests.swift
└── README.md
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | gem 'github-pages', group: :jekyll_plugins
--------------------------------------------------------------------------------
/TrashSweep/AppIcon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tranhuycong/trash-sweep/HEAD/TrashSweep/AppIcon.icns
--------------------------------------------------------------------------------
/TrashSweep/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/assets/trash-sweep-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tranhuycong/trash-sweep/HEAD/assets/trash-sweep-screenshot.png
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
2 | title: TrashSweep's homepage
3 | description: Bookmark this to keep an eye on my project updates!
4 |
--------------------------------------------------------------------------------
/TrashSweep/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TrashSweep.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TrashSweep/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/TrashSweep/TrashSweep.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/TrashSweep/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleIconFile
6 | AppIcon
7 | SUFeedURL
8 | https://tranhuycong.github.io/trash-sweep/appcast.xml
9 |
10 |
11 |
--------------------------------------------------------------------------------
/TrashSweepTests/TrashSweepTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrashSweepTests.swift
3 | // TrashSweepTests
4 | //
5 | // Created by Tran Cong on 28/9/24.
6 | //
7 |
8 | import Testing
9 | @testable import TrashSweep
10 |
11 | struct TrashSweepTests {
12 |
13 | @Test func example() async throws {
14 | // Write your test here and use APIs like `#expect(...)` to check expected conditions.
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # macOS
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | DerivedData/
7 | *.xcworkspace
8 | xcuserdata/
9 | *.xccheckout
10 | *.xcscmblueprint
11 | _site/
12 |
13 | # Swift Package Manager
14 | .build/
15 |
16 | # CocoaPods
17 | Pods/
18 |
19 | # Carthage
20 | Carthage/Build/
21 |
22 | # Fastlane
23 | fastlane/report.xml
24 | fastlane/Preview.html
25 | fastlane/screenshots
26 | fastlane/test_output
27 |
28 | # Archives
29 | *.xcarchive
30 |
31 | # Other
32 | *.swp
33 | *.lock
--------------------------------------------------------------------------------
/TrashSweep.xcodeproj/xcuserdata/trancong.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | TrashSweep.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/appcast.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TrashSweep App Updates
5 | https://github.com/tranhuycong/TrashSweep/blob/main/appcast.xml
6 | Most recent changes with links to updates.
7 | en
8 | -
9 | Version 1.1
10 | https://example.com/release-notes.html
11 | Wed, 28 Sep 2024 12:00:00 +0000
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TrashSweepUITests/TrashSweepUITestsLaunchTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrashSweepUITestsLaunchTests.swift
3 | // TrashSweepUITests
4 | //
5 | // Created by Tran Cong on 28/9/24.
6 | //
7 |
8 | import XCTest
9 |
10 | final class TrashSweepUITestsLaunchTests: XCTestCase {
11 |
12 | override class var runsForEachTargetApplicationUIConfiguration: Bool {
13 | true
14 | }
15 |
16 | override func setUpWithError() throws {
17 | continueAfterFailure = false
18 | }
19 |
20 | @MainActor
21 | func testLaunch() throws {
22 | let app = XCUIApplication()
23 | app.launch()
24 |
25 | // Insert steps here to perform after app launch but before taking a screenshot,
26 | // such as logging into a test account or navigating somewhere in the app
27 |
28 | let attachment = XCTAttachment(screenshot: app.screenshot())
29 | attachment.name = "Launch Screen"
30 | attachment.lifetime = .keepAlways
31 | add(attachment)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/TrashSweep/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/TrashSweep/Assets.xcassets/Icon Status Badge.iconbadgeset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "7x7"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "7x7"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "11x11"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "11x11"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "24x24"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "24x24"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "50x50"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "50x50"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "100x100"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "100x100"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/TrashSweepUITests/TrashSweepUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrashSweepUITests.swift
3 | // TrashSweepUITests
4 | //
5 | // Created by Tran Cong on 28/9/24.
6 | //
7 |
8 | import XCTest
9 |
10 | final class TrashSweepUITests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 |
15 | // In UI tests it is usually best to stop immediately when a failure occurs.
16 | continueAfterFailure = false
17 |
18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
19 | }
20 |
21 | override func tearDownWithError() throws {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | }
24 |
25 | @MainActor
26 | func testExample() throws {
27 | // UI tests must launch the application that they test.
28 | let app = XCUIApplication()
29 | app.launch()
30 |
31 | // Use XCTAssert and related functions to verify your tests produce the correct results.
32 | }
33 |
34 | @MainActor
35 | func testLaunchPerformance() throws {
36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
37 | // This measures how long it takes to launch your application.
38 | measure(metrics: [XCTApplicationLaunchMetric()]) {
39 | XCUIApplication().launch()
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/TrashSweep/TrashSweepApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrashSweepApp.swift
3 | // TrashSweep
4 | //
5 | // Created by Tran Cong on 28/9/24.
6 | //
7 |
8 | import Cocoa
9 | import Sparkle
10 | import SwiftUI
11 |
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 | var statusItem: NSStatusItem?
14 | let updaterController = SPUStandardUpdaterController(
15 | startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil)
16 |
17 | func applicationDidFinishLaunching(_ notification: Notification) {
18 | // Create the status item
19 | statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
20 |
21 | if let button = statusItem?.button {
22 | button.image = NSImage(
23 | systemSymbolName: "arrow.up.trash.fill", accessibilityDescription: "Trash Sweep")
24 | button.action = #selector(statusBarButtonClicked)
25 | }
26 | }
27 |
28 | @objc func statusBarButtonClicked() {
29 | let contentView = ContentView()
30 | let popover = NSPopover()
31 | popover.contentSize = NSSize(width: 500, height: 250)
32 | popover.behavior = .transient
33 | popover.contentViewController = NSHostingController(rootView: contentView)
34 | popover.show(
35 | relativeTo: statusItem!.button!.bounds, of: statusItem!.button!,
36 | preferredEdge: NSRectEdge.minY)
37 |
38 | }
39 |
40 | @objc func checkForUpdates() {
41 | updaterController.checkForUpdates(nil)
42 | }
43 |
44 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
45 | return false
46 | }
47 | }
48 | @main
49 | struct TrashSweepApp: App {
50 | @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
51 | var body: some Scene {
52 | WindowGroup {
53 | ContentView()
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TrashSweep App
2 |
3 | 
4 |
5 | TrashSweep is an application designed to manage and clean up your trash directory using the FIFO (First In, First Out) rule. This ensures that the oldest files are deleted first, keeping your trash directory within a user-defined size limit.
6 |
7 | ## Download
8 |
9 | You can download the latest version of TrashSweep from the following link:
10 |
11 | [Download TrashSweep](https://github.com/tranhuycong/trash-sweep/releases/download/v1.3/TrashSweep-Installer-v1.3.dmg)
12 |
13 | For all release versions, visit the following link:
14 |
15 | [All Releases](https://github.com/tranhuycong/trash-sweep/releases)
16 |
17 | ## Open a Mac app from an unknown developer
18 |
19 | 1. On your Mac, choose Apple menu > System Settings, then click Privacy & Security in the sidebar. (You may need to scroll down.)
20 |
21 | 2. Go to Security, then click Open.
22 |
23 | 3. Click Open Anyway.
24 |
25 | This button is available for about an hour after you try to open the app.
26 |
27 | 4. Enter your login password, then click OK.
28 |
29 | For more detailed instructions, visit the [Apple Support page](https://support.apple.com/en-vn/guide/mac-help/mh40616/mac).
30 |
31 | ## Features
32 |
33 | - **FIFO Trash Management**: Automatically deletes the oldest files first to maintain the trash size within the specified limit.
34 | - **User-Defined Trash Size**: Allows users to set a maximum size for the trash directory.
35 |
36 | ## How It Works
37 |
38 | 1. **FIFO Rule**: The application monitors the trash directory and deletes the oldest files first when the total size exceeds the user-defined limit.
39 | 2. **Configurable Trash Size**: Users can specify the maximum size for the trash directory. The app will ensure that the total size of files in the trash does not exceed this limit.
40 |
41 | ## Usage
42 |
43 | 1. **Set Trash Size**: Define the maximum size for your trash directory in the app settings.
44 | 2. **Automatic Cleanup**: The app will automatically manage the trash directory, deleting the oldest files first to keep the total size within the specified limit.
45 |
46 | ## Updates
47 |
48 | For the latest updates and release notes, please refer to the [appcast.xml](appcast.xml) file.
49 |
50 | ## License
51 |
52 | This project is licensed under the MIT License.
53 |
--------------------------------------------------------------------------------
/TrashSweep/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // TrashSweep
4 | //
5 | // Created by Tran Cong on 28/9/24.
6 | //
7 |
8 | import Foundation
9 | import Sparkle
10 | import SwiftUI
11 |
12 | struct ContentView: View {
13 | @AppStorage("trashSize") public var trashSize = 0.0
14 | @AppStorage("keepTrashSizeMin") private var keepTrashSizeMin = 2.0
15 | @AppStorage("keepTrashSizeMax") public var keepTrashSizeMax = 5.0
16 | @AppStorage("isAutoSweepTrash") public var isAutoSweepTrash = false
17 | @AppStorage("isHavePermissionTrash") private var isHavePermissionTrash = false
18 | private var trashMonitor: TrashMonitor?
19 | let updateController = SPUStandardUpdaterController(
20 | startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil)
21 |
22 | init() {
23 | trashMonitor = TrashMonitor(contentView: self)
24 | }
25 |
26 | var body: some View {
27 | VStack(alignment: .leading, spacing: 10) {
28 | HStack {
29 | Text("TrashSweep")
30 | .font(.headline)
31 | Spacer()
32 | Menu {
33 | Button("About") {
34 | if let url = URL(string: "https://tranhuycong.github.io/trash-sweep") {
35 | NSWorkspace.shared.open(url)
36 | }
37 | }
38 | Button("Check for Updates") {
39 | updateController.checkForUpdates(self)
40 | }
41 | Button("Quit") {
42 | NSApplication.shared.terminate(self)
43 | }
44 | } label: {
45 | Image(systemName: "gearshape")
46 | }
47 | .menuStyle(BorderlessButtonMenuStyle())
48 | .menuIndicator(.hidden)
49 | .fixedSize()
50 | }
51 | if !isHavePermissionTrash {
52 | Text(
53 | "Please grant Full Disk Access to the app in System Preferences under Security & Privacy > Privacy > Full Disk Access."
54 | )
55 | .foregroundColor(.red)
56 | Button("Open permissions") {
57 | NSWorkspace.shared.open(
58 | URL(
59 | string:
60 | "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"
61 | )!)
62 | }
63 | }
64 | Divider()
65 | HStack {
66 | Text("Delete oldest files until trash size is under")
67 | TextField(
68 | "", value: $keepTrashSizeMin, format: .number.precision(.fractionLength(1))
69 | )
70 | .frame(width: 50)
71 | .textFieldStyle(RoundedBorderTextFieldStyle())
72 | Text("GB.")
73 | }
74 | HStack {
75 | Toggle(isOn: $isAutoSweepTrash) {
76 | Text("Automatically sweep trash when the trash size exceeds")
77 | }
78 | TextField(
79 | "", value: $keepTrashSizeMax, format: .number.precision(.fractionLength(1))
80 | )
81 | .frame(width: 50)
82 | .textFieldStyle(RoundedBorderTextFieldStyle())
83 | Text("GB")
84 | }
85 | Divider()
86 | Text("Trash size: \(trashSize, specifier: "%.3f") GB")
87 | HStack {
88 | Button("Sweep Trash Now") {
89 | print("Sweep Trash")
90 | sweepTrash()
91 | }
92 | }
93 | }
94 | .padding(.horizontal, 20)
95 | .padding(.vertical, 20)
96 | .frame(
97 | minWidth: 300, idealWidth: 350, maxWidth: .infinity,
98 | minHeight: 200, idealHeight: 200, maxHeight: .infinity,
99 | alignment: .top
100 | )
101 | .onAppear {
102 | if !isHavePermissionTrash {
103 | permissionRequestAlert()
104 | }
105 |
106 | updateTrashSize()
107 | trashMonitor!.startMonitoring()
108 | if isAutoSweepTrash {
109 | sweepTrash()
110 | }
111 | }
112 | }
113 |
114 | func sweepTrash() {
115 | deleteOldestFilesInTrash()
116 | updateTrashSize()
117 | }
118 |
119 | func permissionRequestAlert() {
120 | let fileManager = FileManager.default
121 | let trashURL: URL
122 |
123 | do {
124 | trashURL = try fileManager.url(
125 | for: .trashDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
126 | } catch {
127 | print("Failed to get Trash directory URL: \(error)")
128 | return
129 | }
130 |
131 | let keys: [URLResourceKey] = [.isReadableKey, .isWritableKey]
132 | let resourceValues: URLResourceValues
133 |
134 | do {
135 | resourceValues = try trashURL.resourceValues(forKeys: Set(keys))
136 | } catch {
137 | print("Failed to get resource values: \(error)")
138 | return
139 | }
140 |
141 | let isReadable = resourceValues.isReadable ?? false
142 | let isWritable = resourceValues.isWritable ?? false
143 |
144 | if !isReadable || !isWritable {
145 | let alert = NSAlert()
146 | alert.messageText = "Permission Required"
147 | alert.informativeText =
148 | "Please grant Full Disk Access to the app in System Preferences under Security & Privacy > Privacy > Full Disk Access."
149 | alert.addButton(withTitle: "OK")
150 | alert.runModal()
151 | } else {
152 | isHavePermissionTrash = true
153 | }
154 | }
155 |
156 | func getTrashSize() -> Double? {
157 | let fileManager = FileManager.default
158 | do {
159 | let trashURL = try fileManager.url(
160 | for: .trashDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
161 |
162 | let contents = try FileManager.default.contentsOfDirectory(
163 | at: trashURL, includingPropertiesForKeys: nil, options: []
164 | )
165 | let totalSize = contents.reduce(0) { (size, url) -> Int64 in
166 | let fileSize = (try? url.resourceValues(forKeys: [.fileSizeKey]).fileSize) ?? 0
167 | return size + Int64(fileSize)
168 | }
169 | return Double(totalSize)
170 | } catch {
171 | print("Failed to get contents of Trash directory: \(error)")
172 | return nil
173 | }
174 | }
175 |
176 | func convertInt64ToGB(_ size: Int64) -> Double {
177 | return Double(size) / 1_000_000_000
178 | }
179 |
180 | func convertGBToInt64(_ size: Double) -> Int64 {
181 | return Int64(size * 1_000_000_000)
182 | }
183 |
184 | func deleteOldestFilesInTrash() {
185 | if trashSize <= keepTrashSizeMin {
186 | return
187 | }
188 |
189 | let sizeLimit = convertGBToInt64(trashSize - keepTrashSizeMin)
190 | let fileManager = FileManager.default
191 |
192 | do {
193 | let trashURL = try fileManager.url(
194 | for: .trashDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
195 |
196 | let files = try FileManager.default.contentsOfDirectory(
197 | at: trashURL, includingPropertiesForKeys: nil, options: []
198 | )
199 |
200 | let sortedFiles = files.sorted {
201 | let date1 = try? $0.resourceValues(forKeys: [.addedToDirectoryDateKey])
202 | .addedToDirectoryDate
203 | let date2 = try? $1.resourceValues(forKeys: [.addedToDirectoryDateKey])
204 | .addedToDirectoryDate
205 | return date1 ?? Date.distantPast < date2 ?? Date.distantPast
206 | }
207 |
208 | var totalDeletedSize: Int64 = 0
209 |
210 | for file in sortedFiles {
211 | let fileSize = try file.resourceValues(forKeys: [.fileSizeKey]).fileSize ?? 0
212 | try fileManager.removeItem(at: file)
213 | print("Deleted \(file.lastPathComponent) (\(fileSize) bytes)")
214 | totalDeletedSize += Int64(fileSize)
215 |
216 | if totalDeletedSize >= sizeLimit {
217 | break
218 | }
219 | }
220 |
221 | print("Deleted \(totalDeletedSize) bytes of data from trash.")
222 | } catch {
223 | print("Error processing files in trash: \(error)")
224 | }
225 | }
226 |
227 | func updateTrashSize() {
228 | let size = getTrashSize()
229 | trashSize = convertInt64ToGB(Int64(size ?? 0))
230 | print("Trash size: \(size ?? 0)")
231 | }
232 |
233 | }
234 |
235 | class TrashMonitor {
236 | private var trashFolderMonitor: DispatchSourceFileSystemObject?
237 | private let trashURL: URL
238 | private var contentView: ContentView?
239 |
240 | init(contentView: ContentView) {
241 | self.contentView = contentView
242 | let fileManager = FileManager.default
243 | do {
244 | trashURL = try fileManager.url(
245 | for: .trashDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
246 | } catch {
247 | fatalError("Failed to get Trash directory URL: \(error)")
248 | }
249 | }
250 |
251 | func startMonitoring() {
252 | let fileDescriptor = open(trashURL.path, O_EVTONLY)
253 | if fileDescriptor == -1 {
254 | print("Failed to open trash folder")
255 | return
256 | }
257 |
258 | trashFolderMonitor = DispatchSource.makeFileSystemObjectSource(
259 | fileDescriptor: fileDescriptor, eventMask: .write, queue: DispatchQueue.global())
260 |
261 | trashFolderMonitor?.setEventHandler { [weak self] in
262 | self?.handleTrashFolderChange()
263 | }
264 |
265 | trashFolderMonitor?.setCancelHandler {
266 | close(fileDescriptor)
267 | }
268 |
269 | trashFolderMonitor?.resume()
270 | }
271 |
272 | private func handleTrashFolderChange() {
273 | DispatchQueue.main.async {
274 | guard let contentView = self.contentView else { return }
275 | if contentView.isAutoSweepTrash && contentView.trashSize > contentView.keepTrashSizeMax
276 | {
277 | contentView.sweepTrash()
278 | } else {
279 | contentView.updateTrashSize()
280 | }
281 | }
282 | }
283 |
284 | func stopMonitoring() {
285 | trashFolderMonitor?.cancel()
286 | trashFolderMonitor = nil
287 | }
288 |
289 | }
290 |
291 | #Preview {
292 | ContentView()
293 | }
294 |
--------------------------------------------------------------------------------
/TrashSweep.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 77;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | F84753512CAD5C4300558564 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = F84753502CAD5C4300558564 /* Sparkle */; };
11 | /* End PBXBuildFile section */
12 |
13 | /* Begin PBXContainerItemProxy section */
14 | F83B24122CA80E4500352C32 /* PBXContainerItemProxy */ = {
15 | isa = PBXContainerItemProxy;
16 | containerPortal = F83B23F82CA80E4400352C32 /* Project object */;
17 | proxyType = 1;
18 | remoteGlobalIDString = F83B23FF2CA80E4400352C32;
19 | remoteInfo = TrashSweep;
20 | };
21 | F83B241C2CA80E4500352C32 /* PBXContainerItemProxy */ = {
22 | isa = PBXContainerItemProxy;
23 | containerPortal = F83B23F82CA80E4400352C32 /* Project object */;
24 | proxyType = 1;
25 | remoteGlobalIDString = F83B23FF2CA80E4400352C32;
26 | remoteInfo = TrashSweep;
27 | };
28 | /* End PBXContainerItemProxy section */
29 |
30 | /* Begin PBXFileReference section */
31 | F83B24002CA80E4400352C32 /* TrashSweep.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TrashSweep.app; sourceTree = BUILT_PRODUCTS_DIR; };
32 | F83B24112CA80E4500352C32 /* TrashSweepTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TrashSweepTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
33 | F83B241B2CA80E4500352C32 /* TrashSweepUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TrashSweepUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
34 | /* End PBXFileReference section */
35 |
36 | /* Begin PBXFileSystemSynchronizedRootGroup section */
37 | F83B24022CA80E4400352C32 /* TrashSweep */ = {
38 | isa = PBXFileSystemSynchronizedRootGroup;
39 | path = TrashSweep;
40 | sourceTree = "";
41 | };
42 | F83B24142CA80E4500352C32 /* TrashSweepTests */ = {
43 | isa = PBXFileSystemSynchronizedRootGroup;
44 | path = TrashSweepTests;
45 | sourceTree = "";
46 | };
47 | F83B241E2CA80E4500352C32 /* TrashSweepUITests */ = {
48 | isa = PBXFileSystemSynchronizedRootGroup;
49 | path = TrashSweepUITests;
50 | sourceTree = "";
51 | };
52 | /* End PBXFileSystemSynchronizedRootGroup section */
53 |
54 | /* Begin PBXFrameworksBuildPhase section */
55 | F83B23FD2CA80E4400352C32 /* Frameworks */ = {
56 | isa = PBXFrameworksBuildPhase;
57 | buildActionMask = 2147483647;
58 | files = (
59 | F84753512CAD5C4300558564 /* Sparkle in Frameworks */,
60 | );
61 | runOnlyForDeploymentPostprocessing = 0;
62 | };
63 | F83B240E2CA80E4500352C32 /* Frameworks */ = {
64 | isa = PBXFrameworksBuildPhase;
65 | buildActionMask = 2147483647;
66 | files = (
67 | );
68 | runOnlyForDeploymentPostprocessing = 0;
69 | };
70 | F83B24182CA80E4500352C32 /* Frameworks */ = {
71 | isa = PBXFrameworksBuildPhase;
72 | buildActionMask = 2147483647;
73 | files = (
74 | );
75 | runOnlyForDeploymentPostprocessing = 0;
76 | };
77 | /* End PBXFrameworksBuildPhase section */
78 |
79 | /* Begin PBXGroup section */
80 | F83B23F72CA80E4400352C32 = {
81 | isa = PBXGroup;
82 | children = (
83 | F83B24022CA80E4400352C32 /* TrashSweep */,
84 | F83B24142CA80E4500352C32 /* TrashSweepTests */,
85 | F83B241E2CA80E4500352C32 /* TrashSweepUITests */,
86 | F83B24012CA80E4400352C32 /* Products */,
87 | );
88 | sourceTree = "";
89 | };
90 | F83B24012CA80E4400352C32 /* Products */ = {
91 | isa = PBXGroup;
92 | children = (
93 | F83B24002CA80E4400352C32 /* TrashSweep.app */,
94 | F83B24112CA80E4500352C32 /* TrashSweepTests.xctest */,
95 | F83B241B2CA80E4500352C32 /* TrashSweepUITests.xctest */,
96 | );
97 | name = Products;
98 | sourceTree = "";
99 | };
100 | /* End PBXGroup section */
101 |
102 | /* Begin PBXNativeTarget section */
103 | F83B23FF2CA80E4400352C32 /* TrashSweep */ = {
104 | isa = PBXNativeTarget;
105 | buildConfigurationList = F83B24252CA80E4500352C32 /* Build configuration list for PBXNativeTarget "TrashSweep" */;
106 | buildPhases = (
107 | F83B23FC2CA80E4400352C32 /* Sources */,
108 | F83B23FD2CA80E4400352C32 /* Frameworks */,
109 | F83B23FE2CA80E4400352C32 /* Resources */,
110 | );
111 | buildRules = (
112 | );
113 | dependencies = (
114 | );
115 | fileSystemSynchronizedGroups = (
116 | F83B24022CA80E4400352C32 /* TrashSweep */,
117 | );
118 | name = TrashSweep;
119 | packageProductDependencies = (
120 | F84753502CAD5C4300558564 /* Sparkle */,
121 | );
122 | productName = TrashSweep;
123 | productReference = F83B24002CA80E4400352C32 /* TrashSweep.app */;
124 | productType = "com.apple.product-type.application";
125 | };
126 | F83B24102CA80E4500352C32 /* TrashSweepTests */ = {
127 | isa = PBXNativeTarget;
128 | buildConfigurationList = F83B24282CA80E4500352C32 /* Build configuration list for PBXNativeTarget "TrashSweepTests" */;
129 | buildPhases = (
130 | F83B240D2CA80E4500352C32 /* Sources */,
131 | F83B240E2CA80E4500352C32 /* Frameworks */,
132 | F83B240F2CA80E4500352C32 /* Resources */,
133 | );
134 | buildRules = (
135 | );
136 | dependencies = (
137 | F83B24132CA80E4500352C32 /* PBXTargetDependency */,
138 | );
139 | fileSystemSynchronizedGroups = (
140 | F83B24142CA80E4500352C32 /* TrashSweepTests */,
141 | );
142 | name = TrashSweepTests;
143 | packageProductDependencies = (
144 | );
145 | productName = TrashSweepTests;
146 | productReference = F83B24112CA80E4500352C32 /* TrashSweepTests.xctest */;
147 | productType = "com.apple.product-type.bundle.unit-test";
148 | };
149 | F83B241A2CA80E4500352C32 /* TrashSweepUITests */ = {
150 | isa = PBXNativeTarget;
151 | buildConfigurationList = F83B242B2CA80E4500352C32 /* Build configuration list for PBXNativeTarget "TrashSweepUITests" */;
152 | buildPhases = (
153 | F83B24172CA80E4500352C32 /* Sources */,
154 | F83B24182CA80E4500352C32 /* Frameworks */,
155 | F83B24192CA80E4500352C32 /* Resources */,
156 | );
157 | buildRules = (
158 | );
159 | dependencies = (
160 | F83B241D2CA80E4500352C32 /* PBXTargetDependency */,
161 | );
162 | fileSystemSynchronizedGroups = (
163 | F83B241E2CA80E4500352C32 /* TrashSweepUITests */,
164 | );
165 | name = TrashSweepUITests;
166 | packageProductDependencies = (
167 | );
168 | productName = TrashSweepUITests;
169 | productReference = F83B241B2CA80E4500352C32 /* TrashSweepUITests.xctest */;
170 | productType = "com.apple.product-type.bundle.ui-testing";
171 | };
172 | /* End PBXNativeTarget section */
173 |
174 | /* Begin PBXProject section */
175 | F83B23F82CA80E4400352C32 /* Project object */ = {
176 | isa = PBXProject;
177 | attributes = {
178 | BuildIndependentTargetsInParallel = 1;
179 | LastSwiftUpdateCheck = 1600;
180 | LastUpgradeCheck = 1600;
181 | TargetAttributes = {
182 | F83B23FF2CA80E4400352C32 = {
183 | CreatedOnToolsVersion = 16.0;
184 | };
185 | F83B24102CA80E4500352C32 = {
186 | CreatedOnToolsVersion = 16.0;
187 | TestTargetID = F83B23FF2CA80E4400352C32;
188 | };
189 | F83B241A2CA80E4500352C32 = {
190 | CreatedOnToolsVersion = 16.0;
191 | TestTargetID = F83B23FF2CA80E4400352C32;
192 | };
193 | };
194 | };
195 | buildConfigurationList = F83B23FB2CA80E4400352C32 /* Build configuration list for PBXProject "TrashSweep" */;
196 | developmentRegion = en;
197 | hasScannedForEncodings = 0;
198 | knownRegions = (
199 | en,
200 | Base,
201 | );
202 | mainGroup = F83B23F72CA80E4400352C32;
203 | minimizedProjectReferenceProxies = 1;
204 | packageReferences = (
205 | F847534F2CAD5C4300558564 /* XCRemoteSwiftPackageReference "Sparkle" */,
206 | );
207 | preferredProjectObjectVersion = 77;
208 | productRefGroup = F83B24012CA80E4400352C32 /* Products */;
209 | projectDirPath = "";
210 | projectRoot = "";
211 | targets = (
212 | F83B23FF2CA80E4400352C32 /* TrashSweep */,
213 | F83B24102CA80E4500352C32 /* TrashSweepTests */,
214 | F83B241A2CA80E4500352C32 /* TrashSweepUITests */,
215 | );
216 | };
217 | /* End PBXProject section */
218 |
219 | /* Begin PBXResourcesBuildPhase section */
220 | F83B23FE2CA80E4400352C32 /* Resources */ = {
221 | isa = PBXResourcesBuildPhase;
222 | buildActionMask = 2147483647;
223 | files = (
224 | );
225 | runOnlyForDeploymentPostprocessing = 0;
226 | };
227 | F83B240F2CA80E4500352C32 /* Resources */ = {
228 | isa = PBXResourcesBuildPhase;
229 | buildActionMask = 2147483647;
230 | files = (
231 | );
232 | runOnlyForDeploymentPostprocessing = 0;
233 | };
234 | F83B24192CA80E4500352C32 /* Resources */ = {
235 | isa = PBXResourcesBuildPhase;
236 | buildActionMask = 2147483647;
237 | files = (
238 | );
239 | runOnlyForDeploymentPostprocessing = 0;
240 | };
241 | /* End PBXResourcesBuildPhase section */
242 |
243 | /* Begin PBXSourcesBuildPhase section */
244 | F83B23FC2CA80E4400352C32 /* Sources */ = {
245 | isa = PBXSourcesBuildPhase;
246 | buildActionMask = 2147483647;
247 | files = (
248 | );
249 | runOnlyForDeploymentPostprocessing = 0;
250 | };
251 | F83B240D2CA80E4500352C32 /* Sources */ = {
252 | isa = PBXSourcesBuildPhase;
253 | buildActionMask = 2147483647;
254 | files = (
255 | );
256 | runOnlyForDeploymentPostprocessing = 0;
257 | };
258 | F83B24172CA80E4500352C32 /* Sources */ = {
259 | isa = PBXSourcesBuildPhase;
260 | buildActionMask = 2147483647;
261 | files = (
262 | );
263 | runOnlyForDeploymentPostprocessing = 0;
264 | };
265 | /* End PBXSourcesBuildPhase section */
266 |
267 | /* Begin PBXTargetDependency section */
268 | F83B24132CA80E4500352C32 /* PBXTargetDependency */ = {
269 | isa = PBXTargetDependency;
270 | target = F83B23FF2CA80E4400352C32 /* TrashSweep */;
271 | targetProxy = F83B24122CA80E4500352C32 /* PBXContainerItemProxy */;
272 | };
273 | F83B241D2CA80E4500352C32 /* PBXTargetDependency */ = {
274 | isa = PBXTargetDependency;
275 | target = F83B23FF2CA80E4400352C32 /* TrashSweep */;
276 | targetProxy = F83B241C2CA80E4500352C32 /* PBXContainerItemProxy */;
277 | };
278 | /* End PBXTargetDependency section */
279 |
280 | /* Begin XCBuildConfiguration section */
281 | F83B24232CA80E4500352C32 /* Debug */ = {
282 | isa = XCBuildConfiguration;
283 | buildSettings = {
284 | ALWAYS_SEARCH_USER_PATHS = NO;
285 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
286 | CLANG_ANALYZER_NONNULL = YES;
287 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
288 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
289 | CLANG_ENABLE_MODULES = YES;
290 | CLANG_ENABLE_OBJC_ARC = YES;
291 | CLANG_ENABLE_OBJC_WEAK = YES;
292 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
293 | CLANG_WARN_BOOL_CONVERSION = YES;
294 | CLANG_WARN_COMMA = YES;
295 | CLANG_WARN_CONSTANT_CONVERSION = YES;
296 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
297 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
298 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
299 | CLANG_WARN_EMPTY_BODY = YES;
300 | CLANG_WARN_ENUM_CONVERSION = YES;
301 | CLANG_WARN_INFINITE_RECURSION = YES;
302 | CLANG_WARN_INT_CONVERSION = YES;
303 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
304 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
305 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
306 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
307 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
308 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
309 | CLANG_WARN_STRICT_PROTOTYPES = YES;
310 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
311 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
312 | CLANG_WARN_UNREACHABLE_CODE = YES;
313 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
314 | COPY_PHASE_STRIP = NO;
315 | DEBUG_INFORMATION_FORMAT = dwarf;
316 | ENABLE_STRICT_OBJC_MSGSEND = YES;
317 | ENABLE_TESTABILITY = YES;
318 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
319 | GCC_C_LANGUAGE_STANDARD = gnu17;
320 | GCC_DYNAMIC_NO_PIC = NO;
321 | GCC_NO_COMMON_BLOCKS = YES;
322 | GCC_OPTIMIZATION_LEVEL = 0;
323 | GCC_PREPROCESSOR_DEFINITIONS = (
324 | "DEBUG=1",
325 | "$(inherited)",
326 | );
327 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
328 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
329 | GCC_WARN_UNDECLARED_SELECTOR = YES;
330 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
331 | GCC_WARN_UNUSED_FUNCTION = YES;
332 | GCC_WARN_UNUSED_VARIABLE = YES;
333 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
334 | MACOSX_DEPLOYMENT_TARGET = 15.0;
335 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
336 | MTL_FAST_MATH = YES;
337 | ONLY_ACTIVE_ARCH = YES;
338 | SDKROOT = macosx;
339 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
340 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
341 | };
342 | name = Debug;
343 | };
344 | F83B24242CA80E4500352C32 /* Release */ = {
345 | isa = XCBuildConfiguration;
346 | buildSettings = {
347 | ALWAYS_SEARCH_USER_PATHS = NO;
348 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
349 | CLANG_ANALYZER_NONNULL = YES;
350 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
351 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
352 | CLANG_ENABLE_MODULES = YES;
353 | CLANG_ENABLE_OBJC_ARC = YES;
354 | CLANG_ENABLE_OBJC_WEAK = YES;
355 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
356 | CLANG_WARN_BOOL_CONVERSION = YES;
357 | CLANG_WARN_COMMA = YES;
358 | CLANG_WARN_CONSTANT_CONVERSION = YES;
359 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
360 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
361 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
362 | CLANG_WARN_EMPTY_BODY = YES;
363 | CLANG_WARN_ENUM_CONVERSION = YES;
364 | CLANG_WARN_INFINITE_RECURSION = YES;
365 | CLANG_WARN_INT_CONVERSION = YES;
366 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
367 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
368 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
369 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
370 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
371 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
372 | CLANG_WARN_STRICT_PROTOTYPES = YES;
373 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
374 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
375 | CLANG_WARN_UNREACHABLE_CODE = YES;
376 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
377 | COPY_PHASE_STRIP = NO;
378 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
379 | ENABLE_NS_ASSERTIONS = NO;
380 | ENABLE_STRICT_OBJC_MSGSEND = YES;
381 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
382 | GCC_C_LANGUAGE_STANDARD = gnu17;
383 | GCC_NO_COMMON_BLOCKS = YES;
384 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
385 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
386 | GCC_WARN_UNDECLARED_SELECTOR = YES;
387 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
388 | GCC_WARN_UNUSED_FUNCTION = YES;
389 | GCC_WARN_UNUSED_VARIABLE = YES;
390 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
391 | MACOSX_DEPLOYMENT_TARGET = 15.0;
392 | MTL_ENABLE_DEBUG_INFO = NO;
393 | MTL_FAST_MATH = YES;
394 | ONLY_ACTIVE_ARCH = YES;
395 | SDKROOT = macosx;
396 | SWIFT_COMPILATION_MODE = wholemodule;
397 | };
398 | name = Release;
399 | };
400 | F83B24262CA80E4500352C32 /* Debug */ = {
401 | isa = XCBuildConfiguration;
402 | buildSettings = {
403 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
404 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
405 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
406 | CODE_SIGN_ENTITLEMENTS = TrashSweep/TrashSweep.entitlements;
407 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
408 | CODE_SIGN_STYLE = Automatic;
409 | COMBINE_HIDPI_IMAGES = YES;
410 | CURRENT_PROJECT_VERSION = 3;
411 | DEVELOPMENT_ASSET_PATHS = "\"TrashSweep/Preview Content\"";
412 | DEVELOPMENT_TEAM = TH9FL7J7ZY;
413 | ENABLE_HARDENED_RUNTIME = YES;
414 | ENABLE_PREVIEWS = YES;
415 | GENERATE_INFOPLIST_FILE = YES;
416 | INFOPLIST_FILE = TrashSweep/Info.plist;
417 | INFOPLIST_KEY_CFBundleDisplayName = "";
418 | INFOPLIST_KEY_LSApplicationCategoryType = "";
419 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
420 | LD_RUNPATH_SEARCH_PATHS = (
421 | "$(inherited)",
422 | "@executable_path/../Frameworks",
423 | );
424 | MACOSX_DEPLOYMENT_TARGET = 12.4;
425 | MARKETING_VERSION = 1.3;
426 | PRODUCT_BUNDLE_IDENTIFIER = com.congth504.trashsweep;
427 | PRODUCT_NAME = "$(TARGET_NAME)";
428 | SWIFT_EMIT_LOC_STRINGS = YES;
429 | SWIFT_VERSION = 5.0;
430 | };
431 | name = Debug;
432 | };
433 | F83B24272CA80E4500352C32 /* Release */ = {
434 | isa = XCBuildConfiguration;
435 | buildSettings = {
436 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
437 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
438 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
439 | CODE_SIGN_ENTITLEMENTS = TrashSweep/TrashSweep.entitlements;
440 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
441 | CODE_SIGN_STYLE = Automatic;
442 | COMBINE_HIDPI_IMAGES = YES;
443 | CURRENT_PROJECT_VERSION = 3;
444 | DEVELOPMENT_ASSET_PATHS = "\"TrashSweep/Preview Content\"";
445 | DEVELOPMENT_TEAM = TH9FL7J7ZY;
446 | ENABLE_HARDENED_RUNTIME = YES;
447 | ENABLE_PREVIEWS = YES;
448 | GENERATE_INFOPLIST_FILE = YES;
449 | INFOPLIST_FILE = TrashSweep/Info.plist;
450 | INFOPLIST_KEY_CFBundleDisplayName = "";
451 | INFOPLIST_KEY_LSApplicationCategoryType = "";
452 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
453 | LD_RUNPATH_SEARCH_PATHS = (
454 | "$(inherited)",
455 | "@executable_path/../Frameworks",
456 | );
457 | MACOSX_DEPLOYMENT_TARGET = 12.4;
458 | MARKETING_VERSION = 1.3;
459 | PRODUCT_BUNDLE_IDENTIFIER = com.congth504.trashsweep;
460 | PRODUCT_NAME = "$(TARGET_NAME)";
461 | SWIFT_EMIT_LOC_STRINGS = YES;
462 | SWIFT_VERSION = 5.0;
463 | };
464 | name = Release;
465 | };
466 | F83B24292CA80E4500352C32 /* Debug */ = {
467 | isa = XCBuildConfiguration;
468 | buildSettings = {
469 | BUNDLE_LOADER = "$(TEST_HOST)";
470 | CODE_SIGN_STYLE = Automatic;
471 | CURRENT_PROJECT_VERSION = 1;
472 | DEVELOPMENT_TEAM = TH9FL7J7ZY;
473 | GENERATE_INFOPLIST_FILE = YES;
474 | MACOSX_DEPLOYMENT_TARGET = 15.0;
475 | MARKETING_VERSION = 1.0;
476 | PRODUCT_BUNDLE_IDENTIFIER = congth504.TrashSweepTests;
477 | PRODUCT_NAME = "$(TARGET_NAME)";
478 | SWIFT_EMIT_LOC_STRINGS = NO;
479 | SWIFT_VERSION = 5.0;
480 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TrashSweep.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TrashSweep";
481 | };
482 | name = Debug;
483 | };
484 | F83B242A2CA80E4500352C32 /* Release */ = {
485 | isa = XCBuildConfiguration;
486 | buildSettings = {
487 | BUNDLE_LOADER = "$(TEST_HOST)";
488 | CODE_SIGN_STYLE = Automatic;
489 | CURRENT_PROJECT_VERSION = 1;
490 | DEVELOPMENT_TEAM = TH9FL7J7ZY;
491 | GENERATE_INFOPLIST_FILE = YES;
492 | MACOSX_DEPLOYMENT_TARGET = 15.0;
493 | MARKETING_VERSION = 1.0;
494 | PRODUCT_BUNDLE_IDENTIFIER = congth504.TrashSweepTests;
495 | PRODUCT_NAME = "$(TARGET_NAME)";
496 | SWIFT_EMIT_LOC_STRINGS = NO;
497 | SWIFT_VERSION = 5.0;
498 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TrashSweep.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TrashSweep";
499 | };
500 | name = Release;
501 | };
502 | F83B242C2CA80E4500352C32 /* Debug */ = {
503 | isa = XCBuildConfiguration;
504 | buildSettings = {
505 | CODE_SIGN_STYLE = Automatic;
506 | CURRENT_PROJECT_VERSION = 1;
507 | DEVELOPMENT_TEAM = TH9FL7J7ZY;
508 | GENERATE_INFOPLIST_FILE = YES;
509 | MARKETING_VERSION = 1.0;
510 | PRODUCT_BUNDLE_IDENTIFIER = congth504.TrashSweepUITests;
511 | PRODUCT_NAME = "$(TARGET_NAME)";
512 | SWIFT_EMIT_LOC_STRINGS = NO;
513 | SWIFT_VERSION = 5.0;
514 | TEST_TARGET_NAME = TrashSweep;
515 | };
516 | name = Debug;
517 | };
518 | F83B242D2CA80E4500352C32 /* Release */ = {
519 | isa = XCBuildConfiguration;
520 | buildSettings = {
521 | CODE_SIGN_STYLE = Automatic;
522 | CURRENT_PROJECT_VERSION = 1;
523 | DEVELOPMENT_TEAM = TH9FL7J7ZY;
524 | GENERATE_INFOPLIST_FILE = YES;
525 | MARKETING_VERSION = 1.0;
526 | PRODUCT_BUNDLE_IDENTIFIER = congth504.TrashSweepUITests;
527 | PRODUCT_NAME = "$(TARGET_NAME)";
528 | SWIFT_EMIT_LOC_STRINGS = NO;
529 | SWIFT_VERSION = 5.0;
530 | TEST_TARGET_NAME = TrashSweep;
531 | };
532 | name = Release;
533 | };
534 | /* End XCBuildConfiguration section */
535 |
536 | /* Begin XCConfigurationList section */
537 | F83B23FB2CA80E4400352C32 /* Build configuration list for PBXProject "TrashSweep" */ = {
538 | isa = XCConfigurationList;
539 | buildConfigurations = (
540 | F83B24232CA80E4500352C32 /* Debug */,
541 | F83B24242CA80E4500352C32 /* Release */,
542 | );
543 | defaultConfigurationIsVisible = 0;
544 | defaultConfigurationName = Release;
545 | };
546 | F83B24252CA80E4500352C32 /* Build configuration list for PBXNativeTarget "TrashSweep" */ = {
547 | isa = XCConfigurationList;
548 | buildConfigurations = (
549 | F83B24262CA80E4500352C32 /* Debug */,
550 | F83B24272CA80E4500352C32 /* Release */,
551 | );
552 | defaultConfigurationIsVisible = 0;
553 | defaultConfigurationName = Release;
554 | };
555 | F83B24282CA80E4500352C32 /* Build configuration list for PBXNativeTarget "TrashSweepTests" */ = {
556 | isa = XCConfigurationList;
557 | buildConfigurations = (
558 | F83B24292CA80E4500352C32 /* Debug */,
559 | F83B242A2CA80E4500352C32 /* Release */,
560 | );
561 | defaultConfigurationIsVisible = 0;
562 | defaultConfigurationName = Release;
563 | };
564 | F83B242B2CA80E4500352C32 /* Build configuration list for PBXNativeTarget "TrashSweepUITests" */ = {
565 | isa = XCConfigurationList;
566 | buildConfigurations = (
567 | F83B242C2CA80E4500352C32 /* Debug */,
568 | F83B242D2CA80E4500352C32 /* Release */,
569 | );
570 | defaultConfigurationIsVisible = 0;
571 | defaultConfigurationName = Release;
572 | };
573 | /* End XCConfigurationList section */
574 |
575 | /* Begin XCRemoteSwiftPackageReference section */
576 | F847534F2CAD5C4300558564 /* XCRemoteSwiftPackageReference "Sparkle" */ = {
577 | isa = XCRemoteSwiftPackageReference;
578 | repositoryURL = "https://github.com/sparkle-project/Sparkle";
579 | requirement = {
580 | kind = upToNextMajorVersion;
581 | minimumVersion = 2.6.4;
582 | };
583 | };
584 | /* End XCRemoteSwiftPackageReference section */
585 |
586 | /* Begin XCSwiftPackageProductDependency section */
587 | F84753502CAD5C4300558564 /* Sparkle */ = {
588 | isa = XCSwiftPackageProductDependency;
589 | package = F847534F2CAD5C4300558564 /* XCRemoteSwiftPackageReference "Sparkle" */;
590 | productName = Sparkle;
591 | };
592 | /* End XCSwiftPackageProductDependency section */
593 | };
594 | rootObject = F83B23F82CA80E4400352C32 /* Project object */;
595 | }
596 |
--------------------------------------------------------------------------------