This commit is contained in:
Alin
2023-11-12 20:59:30 -07:00
parent 84fe1c48b6
commit fd652e4c64
56 changed files with 5711 additions and 0 deletions
+285
View File
@@ -0,0 +1,285 @@
//
// main.swift
// PearcleanerSentinel
//
// Created by Alin Lupascu on 11/9/23.
//
import Foundation
import FileWatcher
import AppKit
// Start Trash monitoring
fileWatcher()
// Keep alive indefinitely
while true {
sleep(1)
}
func fileWatcher() {
let home = FileManager.default.homeDirectoryForCurrentUser.path
let filewatcher = FileWatcher(["\(home)/.Trash"])
filewatcher.queue = DispatchQueue.global()
filewatcher.callback = { event in
checkApp(file: event.path)
}
filewatcher.start()
}
func checkApp(file: String) {
let app = URL(fileURLWithPath: file)
let appExt = app.pathExtension
if appExt == "app" {
if FileManager.default.isInTrash(app) {
NSWorkspace.shared.open(URL(string: "pear://com.alienator88.Pearcleaner?path=\(file)")!)
}
}
}
// --- Trash Relationship ---
extension FileManager {
public func isInTrash(_ file: URL) -> Bool {
var relationship: URLRelationship = .other
do {
try getRelationship(&relationship, of: .trashDirectory, in: .userDomainMask, toItemAt: file)
return relationship == .contains
} catch {
return false
}
}
}
// For testing and outputing logging to file from cmd line tool
func writeLog(string: String) {
let fileManager = FileManager.default
let home = fileManager.homeDirectoryForCurrentUser.path
let logFilePath = "\(home)/Downloads/monitor.txt"
// Check if the log file exists, and create it if it doesn't
if !fileManager.fileExists(atPath: logFilePath) {
if !fileManager.createFile(atPath: logFilePath, contents: nil, attributes: nil) {
print("Failed to create the log file.")
return
}
}
do {
if let fileHandle = FileHandle(forWritingAtPath: logFilePath) {
let ns = "\(string)\n"
fileHandle.seekToEndOfFile()
fileHandle.write(ns.data(using: .utf8)!)
fileHandle.closeFile()
} else {
print("Error opening file for appending")
}
}
}
// let trashContents = getTrashContents()
// if !trashContents.isEmpty && trashContents.contains(event.path) {
// NSWorkspace.shared.open(URL(string: "pear://com.alienator88.Pearcleaner?path=\(event.path)")!)
// }
//func getTrashContents() -> [String] {
// let fileManager = FileManager.default
// let trashURLs = fileManager.urls(for: .trashDirectory, in: .userDomainMask)
// do {
// let trashContents = try fileManager.contentsOfDirectory(at: trashURLs.first!, includingPropertiesForKeys: nil, options: [])
// let appFiles = trashContents.filter { $0.pathExtension == "app" }
// writeLog(string: appFiles.first!.path)
//
// return appFiles.map { $0.path }
// } catch {
// print("Failed to get contents of trash directory: \(error)")
// return []
// }
//}
//func hasAccessToTrashFolder() -> Bool {
// let fileManager = FileManager.default
// let trashURLs = fileManager.urls(for: .trashDirectory, in: .userDomainMask)
//
// if let trashURL = trashURLs.first {
// return fileManager.isReadableFile(atPath: trashURL.path) && fileManager.isWritableFile(atPath: trashURL.path)
// }
//
// return false
//}
//if hasAccessToTrashFolder() {
// writeLog(string: "Your tool has access to the Trash folder.")
// print("Your tool has access to the Trash folder.")
//} else {
// if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles") {
// NSWorkspace.shared.open(url)
// }
// writeLog(string: "Your tool does not have access to the Trash folder.")
// print("Your tool does not have access to the Trash folder.")
//}
//var dirWatcher: DirMonitor?
//func fileW2() {
// let home = FileManager.default.homeDirectoryForCurrentUser.path
// let url = URL(fileURLWithPath: "\(home)/.Trash")
// dirWatcher = DirMonitor(dir: url, queue: .global())
// if dirWatcher?.start() == true {
// NSLog("Started directory monitoring")
// } else {
// NSLog("Failed to start directory monitoring")
// }
//}
//
//class DirMonitor {
//
// init(dir: URL, queue: DispatchQueue) {
// self.dir = dir
// self.queue = queue
// }
//
// deinit {
// // The stream has a reference to us via its `info` pointer. If the
// // client releases their reference to us without calling `stop`, that
// // results in a dangling pointer. We detect this as a programming error.
// // There are other approaches to take here (for example, messing around
// // with weak, or forming a retain cycle thats broken on `stop`), but
// // this approach:
// //
// // * Has clear rules
// // * Is easy to implement
// // * Generate a sensible debug message if the client gets things wrong
// precondition(self.stream == nil, "released a running monitor")
// // I added this log line as part of my testing of the deallocation path.
// NSLog("did deinit")
// }
//
// let dir: URL
// let queue: DispatchQueue
//
// private var stream: FSEventStreamRef? = nil
//
// func start() -> Bool {
// precondition(self.stream == nil, "started a running monitor")
//
// // Set up our context.
// //
// // `FSEventStreamCallback` is a C function, so we pass `self` to the
// // `info` pointer so that it get call our `handleUnsafeEvents()`
// // method. This involves the standard `Unmanaged` dance:
// //
// // * Here we set `info` to an unretained pointer to `self`.
// // * Inside the function we extract that pointer as `obj` and then use
// // that to call `handleUnsafeEvents()`.
//
// var context = FSEventStreamContext()
// context.info = Unmanaged.passUnretained(self).toOpaque()
//
// // Create the stream.
// //
// // In this example I wanted to show how to deal with raw string paths,
// // so Im not taking advantage of `kFSEventStreamCreateFlagUseCFTypes`
// // or the even cooler `kFSEventStreamCreateFlagUseExtendedData`.
//
// guard let stream = FSEventStreamCreate(nil, { (stream, info, numEvents, eventPaths, eventFlags, eventIds) in
// let obj = Unmanaged<DirMonitor>.fromOpaque(info!).takeUnretainedValue()
// obj.handleUnsafeEvents(numEvents: numEvents, eventPaths: eventPaths, eventFlags: eventFlags, eventIDs: eventIds)
// },
// &context,
// [self.dir.path as NSString] as NSArray,
// UInt64(kFSEventStreamEventIdSinceNow),
// 1.0,
// FSEventStreamCreateFlags(kFSEventStreamCreateFlagNone)
// ) else {
// return false
// }
// self.stream = stream
//
// // Now that we have a stream, schedule it on our target queue.
//
// FSEventStreamSetDispatchQueue(stream, queue)
// guard FSEventStreamStart(stream) else {
// FSEventStreamInvalidate(stream)
// self.stream = nil
// return false
// }
// return true
// }
//
// private func handleUnsafeEvents(numEvents: Int, eventPaths: UnsafeMutableRawPointer, eventFlags: UnsafePointer<FSEventStreamEventFlags>, eventIDs: UnsafePointer<FSEventStreamEventId>) {
// // This takes the low-level goo from the C callback, converts it to
// // something that makes sense for Swift, and then passes that to
// // `handle(events:)`.
// //
// // Note that we dont need to do any rebinding here because this data is
// // coming C as the right type.
// let pathsBase = eventPaths.assumingMemoryBound(to: UnsafePointer<CChar>.self)
// let pathsBuffer = UnsafeBufferPointer(start: pathsBase, count: numEvents)
// let flagsBuffer = UnsafeBufferPointer(start: eventFlags, count: numEvents)
// let eventIDsBuffer = UnsafeBufferPointer(start: eventIDs, count: numEvents)
// // As `zip(_:_:)` only handles two sequences, I map over the index.
// let events = (0..<numEvents).map { i -> (url: URL, flags: FSEventStreamEventFlags, eventIDs: FSEventStreamEventId) in
// let path = pathsBuffer[i]
// // We set `isDirectory` to true because we only generate directory
// // events (that is, we dont pass
// // `kFSEventStreamCreateFlagFileEvents` to `FSEventStreamCreate`.
// // This is generally the best way to use FSEvents, but if you decide
// // to take advantage of `kFSEventStreamCreateFlagFileEvents` then
// // youll need to code to `isDirectory` correctly.
// let url: URL = URL(fileURLWithFileSystemRepresentation: path, isDirectory: true, relativeTo: nil)
// return (url, flagsBuffer[i], eventIDsBuffer[i])
// }
// self.handle(events: events)
// }
//
// private func handle(events: [(url: URL, flags: FSEventStreamEventFlags, eventIDs: FSEventStreamEventId)]) {
// // In this example we just print the events with get, prefixed by a
// // count so that we can see the batching in action.
// NSLog("%d", events.count)
// for (url, flags, eventID) in events {
// NSLog("%16x %8x %@", eventID, flags, url.path)
// }
// for (url, flags, _) in events {
// if flags & FSEventStreamEventFlags(kFSEventStreamEventFlagItemRemoved) != 0 {
// NSLog("Removed: \(url.path)")
// }
// if flags & FSEventStreamEventFlags(kFSEventStreamEventFlagItemCreated) != 0 {
// NSLog("Created: \(url.path)")
// }
// }
// getTrashContents()
// }
//
// func stop() {
// guard let stream = self.stream else {
// return // We accept redundant calls to `stop`.
// }
// FSEventStreamStop(stream)
// FSEventStreamInvalidate(stream)
// self.stream = nil
// }
//
// func getTrashContents() {
// let fileManager = FileManager.default
// let trashURLs = fileManager.urls(for: .trashDirectory, in: .userDomainMask)
// let trashContents = try? fileManager.contentsOfDirectory(at: trashURLs.first!, includingPropertiesForKeys: nil, options: [])
// print(trashContents as Any)
// }
//}