diff --git a/TablePro/Core/Services/SQL/SQLFolderWatcher.swift b/TablePro/Core/Services/SQL/SQLFolderWatcher.swift index 0f8498e9c..3abbb7f30 100644 --- a/TablePro/Core/Services/SQL/SQLFolderWatcher.swift +++ b/TablePro/Core/Services/SQL/SQLFolderWatcher.swift @@ -141,27 +141,17 @@ internal final class SQLFolderWatcher { return } - guard let enumerator = fileManager.enumerator( - at: folderURL, - includingPropertiesForKeys: [.isRegularFileKey, .contentModificationDateKey, .fileSizeKey], - options: [.skipsHiddenFiles] - ) else { - return - } - var indexed: [LinkedSQLIndex.IndexedFile] = [] - for case let url as URL in enumerator { - guard SQLFileService.supportedExtensions.contains(url.pathExtension.lowercased()) else { continue } - + enumerateSQLFileURLs(in: folderURL, fileManager: fileManager) { url in let resourceValues = try? url.resourceValues(forKeys: [ .isRegularFileKey, .contentModificationDateKey, .fileSizeKey ]) - guard resourceValues?.isRegularFile == true else { continue } + guard resourceValues?.isRegularFile == true else { return } let mtime = resourceValues?.contentModificationDate ?? Date() let fileSize = Int64(resourceValues?.fileSize ?? 0) - guard let relativePath = relativePathFor(url: url, base: folderURL) else { continue } + guard let relativePath = relativePathFor(url: url, base: folderURL) else { return } let header = FileTextLoader.loadHeader(url) let metadata = header.map { SQLFrontmatter.parse($0.content) } ?? SQLFrontmatter.Metadata() let encoding = header?.encoding ?? .utf8 @@ -184,6 +174,25 @@ internal final class SQLFolderWatcher { await LinkedSQLIndex.shared.replaceAll(folderId: folder.id, files: indexed, folderURL: folderURL) } + private static func enumerateSQLFileURLs( + in folderURL: URL, + fileManager: FileManager, + handle: (URL) -> Void + ) { + guard let enumerator = fileManager.enumerator( + at: folderURL, + includingPropertiesForKeys: [.isRegularFileKey, .contentModificationDateKey, .fileSizeKey], + options: [.skipsHiddenFiles] + ) else { + return + } + + for case let url as URL in enumerator { + guard SQLFileService.supportedExtensions.contains(url.pathExtension.lowercased()) else { continue } + handle(url) + } + } + private static func pruneRemovedFolders(stillKnownIds: Set) async { let indexedIds = await LinkedSQLIndex.shared.allFolderIds() let stale = indexedIds.subtracting(stillKnownIds) diff --git a/TablePro/ViewModels/WelcomeViewModel.swift b/TablePro/ViewModels/WelcomeViewModel.swift index 2d2f3b17e..e3187036b 100644 --- a/TablePro/ViewModels/WelcomeViewModel.swift +++ b/TablePro/ViewModels/WelcomeViewModel.swift @@ -163,7 +163,11 @@ final class WelcomeViewModel { // MARK: - Initialization - init(services: AppServices = .live) { + convenience init() { + self.init(services: .live) + } + + init(services: AppServices) { self.services = services self.showOnboarding = !services.appSettingsStorage.hasCompletedOnboarding() } diff --git a/TablePro/Views/Components/SQLReviewSheet.swift b/TablePro/Views/Components/SQLReviewSheet.swift index 4e1bc09e0..8d80b5d03 100644 --- a/TablePro/Views/Components/SQLReviewSheet.swift +++ b/TablePro/Views/Components/SQLReviewSheet.swift @@ -33,9 +33,9 @@ struct SQLReviewSheet: View { } /// Past this many characters the display is truncated; the full text stays available via Copy All. - static let maxDisplayChars = 20_000 + nonisolated static let maxDisplayChars = 20_000 /// Past this many characters tree-sitter is skipped in favour of a plain monospaced view. - static let treeSitterCutoff = 8_000 + nonisolated static let treeSitterCutoff = 8_000 var body: some View { VStack(spacing: 0) { @@ -75,18 +75,23 @@ struct SQLReviewSheet: View { private func prepare() async { guard prepared == nil, !statements.isEmpty else { return } - let result = await Task.detached(priority: .userInitiated) { [statements, databaseType] in - Self.build(statements: statements, databaseType: databaseType) + let isJavaScript = PluginManager.shared.editorLanguage(for: databaseType) == .javascript + let result = await Task.detached(priority: .userInitiated) { [statements, isJavaScript] in + Self.build(statements: statements, isJavaScript: isJavaScript) }.value prepared = result } static func build(statements: [String], databaseType: DatabaseType) -> Prepared { - let isJS = PluginManager.shared.editorLanguage(for: databaseType) == .javascript + let isJavaScript = PluginManager.shared.editorLanguage(for: databaseType) == .javascript + return build(statements: statements, isJavaScript: isJavaScript) + } + + private nonisolated static func build(statements: [String], isJavaScript: Bool) -> Prepared { var full = statements .map { $0.hasSuffix(";") ? $0 : $0 + ";" } .joined(separator: "\n\n") - if isJS { + if isJavaScript { full = convertExtendedJsonToShellSyntax(full) } @@ -112,7 +117,7 @@ struct SQLReviewSheet: View { ) } - static func convertExtendedJsonToShellSyntax(_ mql: String) -> String { + nonisolated static func convertExtendedJsonToShellSyntax(_ mql: String) -> String { let pattern = #"\{"\$oid":\s*"([0-9a-fA-F]{24})"\}"# guard let regex = try? NSRegularExpression(pattern: pattern) else { return mql } let nsString = mql as NSString diff --git a/TablePro/Views/Connection/ImportFromApp/ImportFromAppSourcePicker.swift b/TablePro/Views/Connection/ImportFromApp/ImportFromAppSourcePicker.swift index bfa00dd19..3a600835f 100644 --- a/TablePro/Views/Connection/ImportFromApp/ImportFromAppSourcePicker.swift +++ b/TablePro/Views/Connection/ImportFromApp/ImportFromAppSourcePicker.swift @@ -164,11 +164,10 @@ struct ImportFromAppSourcePicker: View { private func loadStates() { Task.detached(priority: .userInitiated) { let importers = ForeignAppImporterRegistry.all - var states: [(importer: any ForeignAppImporter, available: Bool, count: Int)] = [] - for importer in importers { + let states: [(importer: any ForeignAppImporter, available: Bool, count: Int)] = importers.map { importer in let available = importer.isAvailable() let count = available ? importer.connectionCount() : 0 - states.append((importer: importer, available: available, count: count)) + return (importer: importer, available: available, count: count) } await MainActor.run { importerStates = states