Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Directory watcher #4

Merged
merged 3 commits into from
Mar 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Brewed.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
CD6C478525FCF9E8004BA6DD /* RestartCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6C478425FCF9E8004BA6DD /* RestartCommand.swift */; };
CD6C478825FD0522004BA6DD /* GlobalAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6C478725FD0522004BA6DD /* GlobalAlert.swift */; };
CD6C478B25FD0B4F004BA6DD /* FileMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6C478A25FD0B4F004BA6DD /* FileMonitor.swift */; };
CD6C479525FD4721004BA6DD /* FolderMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6C479425FD4721004BA6DD /* FolderMonitor.swift */; };
CD85838725FABE8E00E3974F /* BrewedApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD85838625FABE8E00E3974F /* BrewedApp.swift */; };
CD85838925FABE8E00E3974F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD85838825FABE8E00E3974F /* ContentView.swift */; };
CD85838B25FABE9300E3974F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CD85838A25FABE9300E3974F /* Assets.xcassets */; };
Expand All @@ -38,6 +39,7 @@
CD6C478425FCF9E8004BA6DD /* RestartCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestartCommand.swift; sourceTree = "<group>"; };
CD6C478725FD0522004BA6DD /* GlobalAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalAlert.swift; sourceTree = "<group>"; };
CD6C478A25FD0B4F004BA6DD /* FileMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileMonitor.swift; sourceTree = "<group>"; };
CD6C479425FD4721004BA6DD /* FolderMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderMonitor.swift; sourceTree = "<group>"; };
CD85838325FABE8E00E3974F /* Brewed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Brewed.app; sourceTree = BUILT_PRODUCTS_DIR; };
CD85838625FABE8E00E3974F /* BrewedApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewedApp.swift; sourceTree = "<group>"; };
CD85838825FABE8E00E3974F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -186,6 +188,7 @@
CDCCCE4925FBED2C00C3590A /* Collection.swift */,
CDA7929025FBFBB3006FFAB1 /* Regex.swift */,
CD6C478A25FD0B4F004BA6DD /* FileMonitor.swift */,
CD6C479425FD4721004BA6DD /* FolderMonitor.swift */,
);
path = Helpers;
sourceTree = "<group>";
Expand Down Expand Up @@ -269,6 +272,7 @@
CDA7928D25FBFA58006FFAB1 /* AutostartService.swift in Sources */,
CD6C478525FCF9E8004BA6DD /* RestartCommand.swift in Sources */,
CD85838725FABE8E00E3974F /* BrewedApp.swift in Sources */,
CD6C479525FD4721004BA6DD /* FolderMonitor.swift in Sources */,
CDA7928725FBEFC9006FFAB1 /* ManagedServices.swift in Sources */,
CD6C477125FCE2AC004BA6DD /* ServiceInfo.swift in Sources */,
CDCCCE4725FBE6D500C3590A /* Service.swift in Sources */,
Expand Down
8 changes: 4 additions & 4 deletions Brewed/Helpers/FileMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
import Foundation

protocol FileMonitorDelegate: AnyObject {
func deleted(url: URL, event: DispatchSource.FileSystemEvent)
func fileEvent(url: URL, event: DispatchSource.FileSystemEvent)
}

final class FileMonitor {
final class FileMonitor: FilesystemMonitor {
let url: URL

let fileHandle: FileHandle
Expand All @@ -28,13 +28,13 @@ final class FileMonitor {

source = DispatchSource.makeFileSystemObjectSource(
fileDescriptor: fileHandle.fileDescriptor,
eventMask: .delete,
eventMask: [.write, .delete],
queue: DispatchQueue.main
)

source.setEventHandler {
let event = self.source.data
self.delegate?.deleted(url: url, event: event)
self.delegate?.fileEvent(url: url, event: event)
}

source.setCancelHandler {
Expand Down
60 changes: 60 additions & 0 deletions Brewed/Helpers/FolderMonitor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// FolderMonitor.swift
// Brewed
//
// Created by Rick Kerkhof on 13/03/2021.
//
// Code found from https://swiftrocks.com/dispatchsource-detecting-changes-in-files-and-folders-in-swift
// and altered.
//

import Foundation

protocol FolderMonitorDelegate: AnyObject {
func folderEvent(url: URL, event: DispatchSource.FileSystemEvent, additions: [URL])
}

protocol FilesystemMonitor {}

final class FolderMonitor: FilesystemMonitor {
let url: URL

let fileHandle: Int32
let source: DispatchSourceFileSystemObject

weak var delegate: FolderMonitorDelegate?

init(url: URL) throws {
self.url = url
fileHandle = open(url.path, O_EVTONLY)

source = DispatchSource.makeFileSystemObjectSource(
fileDescriptor: fileHandle,
eventMask: .write,
queue: DispatchQueue.main
)

var contents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)

source.setEventHandler {
let event = self.source.data
let oldContents = contents
contents = try! FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
self.delegate?.folderEvent(
url: url,
event: event,
additions: contents.filter { !oldContents.contains($0) }
)
}

source.setCancelHandler {
close(self.fileHandle)
}

source.resume()
}

deinit {
source.cancel()
}
}
16 changes: 16 additions & 0 deletions Brewed/Homebrew/Services/AutostartService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ struct AutostartDirectory {

return regex?.matches(url?.absoluteString ?? "") ?? false
}

static func urls() -> [URL] {
var urls = [
URL(fileURLWithPath: AutostartDirectory.global, isDirectory: true)
]

if let local = try? FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
urls.append(local.appendingPathComponent("LaunchAgents"))
}

return urls
}
}

extension Service {
Expand All @@ -30,4 +42,8 @@ extension Service {
var startsAtLogin: Bool {
AutostartDirectory.isLocalPlist(plist)
}

var plistName: String {
"homebrew.mxcl.\(self.id).plist"
}
}
27 changes: 23 additions & 4 deletions Brewed/Model/ManagedServices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@

import Foundation

class ManagedServices: ObservableObject, FileMonitorDelegate {

class ManagedServices: ObservableObject, FileMonitorDelegate, FolderMonitorDelegate {
@Published var services: [Service] = []

@Published var refreshing = false

private var monitors: [URL: FileMonitor] = [:]
private var monitors: [URL: FilesystemMonitor] = [:]

func update(service: Service) {
var services = self.services
Expand All @@ -34,6 +33,14 @@ class ManagedServices: ObservableObject, FileMonitorDelegate {
self.monitors.removeAll()

self.services = services

AutostartDirectory.urls().forEach { url in
if let monitor = try? FolderMonitor(url: url) {
self.monitors[url] = monitor
monitor.delegate = self
}
}

self.services.forEach { service in
guard let plist = service.plist else {
return
Expand All @@ -49,7 +56,19 @@ class ManagedServices: ObservableObject, FileMonitorDelegate {
}.cauterize()
}

func deleted(url: URL, event: DispatchSource.FileSystemEvent) {
func fileEvent(url: URL, event: DispatchSource.FileSystemEvent) {
DispatchQueue.main.async {
self.refresh()
}
}

func folderEvent(url: URL, event: DispatchSource.FileSystemEvent, additions: [URL]) {
let wantedPlistNames = services.map { $0.plistName }

if !additions.contains(where: { wantedPlistNames.contains($0.lastPathComponent) }) {
return
}

DispatchQueue.main.async {
self.refresh()
}
Expand Down