Skip to content
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
30 changes: 20 additions & 10 deletions App/Services/Messages.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import AppKit
import OSLog
import os
import SQLite3
import iMessage

Expand All @@ -12,8 +13,9 @@ private let defaultLimit = 30
final class MessageService: NSObject, Service, NSOpenSavePanelDelegate {
static let shared = MessageService()

/// Logged once per launch when the stored grant covers `chat.db` alone.
private var warnedAboutFileGrant = false
/// Whether the once-per-launch warning about a grant on `chat.db` alone has been logged.
/// Tool calls run concurrently, so the check-and-set is guarded.
private let fileGrantWarningLogged = OSAllocatedUnfairLock(initialState: false)

func activate() async throws {
try await activate(offeringUpgrade: true)
Expand Down Expand Up @@ -49,8 +51,11 @@ final class MessageService: NSObject, Service, NSOpenSavePanelDelegate {
}

if upgrading, !offeringUpgrade {
if !warnedAboutFileGrant {
warnedAboutFileGrant = true
let isFirstWarning = fileGrantWarningLogged.withLock { logged in
defer { logged = true }
return !logged
}
if isFirstWarning {
log.warning(
"The Messages grant covers chat.db alone, so messages since its last checkpoint are not visible. Switch Messages off and on in the iMCP menu to grant the Messages folder."
)
Expand All @@ -65,7 +70,11 @@ final class MessageService: NSObject, Service, NSOpenSavePanelDelegate {
throw DatabaseAccessError.userDeclinedAccess
}

let selectedURL = try await showFolderPicker()
guard let selectedURL = try await showFolderPicker() else {
// Dismissing the picker is the same answer as Cancel on the alert.
if upgrading { return }
throw DatabaseAccessError.userDeclinedAccess
}

guard FileManager.default.isReadableFile(atPath: databaseURL(in: selectedURL).path) else {
throw DatabaseAccessError.fileNotReadable
Expand Down Expand Up @@ -407,8 +416,9 @@ final class MessageService: NSObject, Service, NSOpenSavePanelDelegate {
return alert.runModal() == .alertFirstButtonReturn
}

/// Returns the selected folder, or nil when the user dismisses the panel.
@MainActor
private func showFolderPicker() async throws -> URL {
private func showFolderPicker() async throws -> URL? {
let openPanel = NSOpenPanel()
openPanel.delegate = self
openPanel.message = "Please select your Messages folder (~/Library/Messages)"
Expand All @@ -420,10 +430,10 @@ final class MessageService: NSObject, Service, NSOpenSavePanelDelegate {
openPanel.canChooseFiles = false
openPanel.showsHiddenFiles = true

guard openPanel.runModal() == .OK,
let url = openPanel.url,
isMessagesDirectory(url)
else {
guard openPanel.runModal() == .OK, let url = openPanel.url else {
return nil
}
guard isMessagesDirectory(url) else {
throw DatabaseAccessError.invalidFileSelected
}

Expand Down
16 changes: 10 additions & 6 deletions App/Services/Phone.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,10 @@ final class PhoneService: NSObject, Service, NSOpenSavePanelDelegate {
throw DatabaseAccessError.userDeclinedAccess
}

let selectedURL = try await showFolderPicker()
guard let selectedURL = try await showFolderPicker() else {
// Dismissing the picker is the same answer as Cancel on the alert.
throw DatabaseAccessError.userDeclinedAccess
}

guard FileManager.default.isReadableFile(atPath: databaseURL(in: selectedURL).path) else {
throw DatabaseAccessError.fileNotReadable
Expand Down Expand Up @@ -407,8 +410,9 @@ final class PhoneService: NSObject, Service, NSOpenSavePanelDelegate {
return alert.runModal() == .alertFirstButtonReturn
}

/// Returns the selected folder, or nil when the user dismisses the panel.
@MainActor
private func showFolderPicker() async throws -> URL {
private func showFolderPicker() async throws -> URL? {
let openPanel = NSOpenPanel()
openPanel.delegate = self
openPanel.message =
Expand All @@ -421,10 +425,10 @@ final class PhoneService: NSObject, Service, NSOpenSavePanelDelegate {
openPanel.canChooseFiles = false
openPanel.showsHiddenFiles = true

guard openPanel.runModal() == .OK,
let url = openPanel.url,
isCallHistoryDirectory(url)
else {
guard openPanel.runModal() == .OK, let url = openPanel.url else {
return nil
}
guard isCallHistoryDirectory(url) else {
throw DatabaseAccessError.invalidFileSelected
}

Expand Down