Skip to content

Commit 9cd7dae

Browse files
author
Iakov Senatov
committed
Fix multi-file drag-and-drop and debounce FSEvents full rescans
- Properly create one NSItemProvider per dragged file - Expose dragItemProviders for multi-item external drag support - Clear providers on drag end - Add debounce protection against duplicate full rescans from bursty FSEvents - Suppress repeated full rescans within 1s window This fixes external drag copying only a single file and reduces UI freezes caused by duplicate full rescans.
1 parent 32b028f commit 9cd7dae

File tree

3 files changed

+33
-10
lines changed

3 files changed

+33
-10
lines changed

GUI/Sources/ContextMenu/Services/OpenWithService.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,14 @@ final class OpenWithService {
8282
func getApplications(for fileURL: URL) -> [AppInfo] {
8383
let cacheKey = fileURL.path as NSString
8484
if let cached = OpenWithService.appsCache.object(forKey: cacheKey) as? [AppInfo] {
85-
log.debug("\(#function) cache hit for '\(fileURL.lastPathComponent)' (\(cached.count) apps)")
85+
//log.debug("\(#function) cache hit for '\(fileURL.lastPathComponent)' (\(cached.count) apps)")
8686
return cached
8787
}
88-
log.debug("\(#function) file='\(fileURL.lastPathComponent)' ext=\(fileURL.pathExtension)")
89-
88+
//log.debug("\(#function) file='\(fileURL.lastPathComponent)' ext=\(fileURL.pathExtension)")
9089
guard UTType(filenameExtension: fileURL.pathExtension) != nil else {
91-
log.warning("\(#function) unknown UTType for ext='\(fileURL.pathExtension)', using fallback editors")
90+
//log.warning("\(#function) unknown UTType for ext='\(fileURL.pathExtension)', using fallback editors")
9291
return getAllEditors()
9392
}
94-
9593
let defaultApp = workspace.urlForApplication(toOpen: fileURL)
9694
var apps: [AppInfo] = []
9795
var seenBundles = Set<String>()
@@ -242,7 +240,7 @@ final class OpenWithService {
242240
return makeAppInfo(from: url, isDefault: false)
243241
}
244242

245-
log.debug("\(#function) returning \(editors.count) fallback editors")
243+
//log.debug("\(#function) returning \(editors.count) fallback editors")
246244
return editors
247245
}
248246
}

GUI/Sources/DragDrop/DragDropManager.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,31 @@ final class DragDropManager {
2323
/// Currently dragged files (for visual feedback)
2424
var draggedFiles: [CustomFile] = []
2525

26+
/// NSPasteboard providers for current drag session
27+
var dragItemProviders: [NSItemProvider] = []
28+
2629
/// Currently highlighted drop target (folder or panel)
2730
var dropTargetPath: URL?
2831

2932
// MARK: - Start dragging files
3033
func startDrag(files: [CustomFile], from panelSide: PanelSide) {
3134
log.debug("DragDropManager: started dragging \(files.count) items from \(panelSide)")
35+
3236
draggedFiles = files
37+
38+
// Create one NSItemProvider per file (required for multi-file drag)
39+
dragItemProviders = files.map { file in
40+
let provider = NSItemProvider(object: file.urlValue as NSURL)
41+
provider.suggestedName = file.nameStr
42+
return provider
43+
}
3344
}
3445

3546
// MARK: - End drag operation
3647
func endDrag() {
3748
log.debug("DragDropManager: drag ended")
3849
draggedFiles = []
50+
dragItemProviders = []
3951
dropTargetPath = nil
4052
}
4153

GUI/Sources/Services/Scanner/FSEventsDirectoryWatcher.swift

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ final class FSEventsDirectoryWatcher: @unchecked Sendable {
4747
private var pendingWork: DispatchWorkItem?
4848
private let throttleDelay: TimeInterval = 0.3
4949

50+
// Full-rescan protection (avoid duplicate rescans from bursty FSEvents)
51+
private var lastFullRescanDate: Date?
52+
private let fullRescanMinInterval: TimeInterval = 1.0
53+
5054
// FSEvents kernel-side coalescing latency
5155
private static let latency: CFTimeInterval = 0.5
5256

@@ -124,7 +128,7 @@ final class FSEventsDirectoryWatcher: @unchecked Sendable {
124128
/// Coalesces rapid bursts: cancels previous work item, schedules new one after throttleDelay.
125129
private func scheduleHandleEvents(paths: [String], flags: [FSEventStreamEventFlags]) {
126130
pendingWork?.cancel()
127-
log.debug("[FSEvents] scheduleHandleEvents: \(paths.count) paths for '\(watchedPath)': \(paths)")
131+
//log.debug("[FSEvents] scheduleHandleEvents: \(paths.count) paths for '\(watchedPath)': \(paths)")
128132
let work = DispatchWorkItem { [weak self] in
129133
self?.handleEvents(paths: paths, flags: flags)
130134
}
@@ -170,9 +174,18 @@ final class FSEventsDirectoryWatcher: @unchecked Sendable {
170174
// When dir-level event fires for watched dir itself, trigger full rescan.
171175
// Dir-level FSEvents cannot tell us which files were added/removed —
172176
// only that "something changed" in the directory.
173-
log.debug("[FSEvents] handleEvents: watchedDirChanged=\(watchedDirChanged) directChildren=\(directChildren.count) childCountUpdates=\(childCountUpdates.count)")
177+
//log.debug("[FSEvents] handleEvents: watchedDirChanged=\(watchedDirChanged) directChildren=\(directChildren.count) childCountUpdates=\(childCountUpdates.count)")
174178
if watchedDirChanged && directChildren.isEmpty {
175-
log.info("[FSEvents] watched dir event → full rescan for '\(watched)'")
179+
let now = Date()
180+
if let last = lastFullRescanDate,
181+
now.timeIntervalSince(last) < fullRescanMinInterval {
182+
log.debug("[FSEvents] full rescan suppressed (debounced) for '\(watched)'")
183+
return
184+
}
185+
186+
lastFullRescanDate = now
187+
log.info("[FSEvents] watched dir event → full rescan for '\(watched)' (debounced)")
188+
176189
let patch = DirectoryPatch(
177190
childCountUpdates: childCountUpdates,
178191
addedOrModified: [],
@@ -184,7 +197,7 @@ final class FSEventsDirectoryWatcher: @unchecked Sendable {
184197
return
185198
}
186199
guard !directChildren.isEmpty || !childCountUpdates.isEmpty else { return }
187-
200+
188201
var added: [CustomFile] = []
189202
var removed: [String] = []
190203

0 commit comments

Comments
 (0)