diff --git a/Box42/Preferences/Controller/PreferencesViewController.swift b/Box42/Preferences/Controller/PreferencesViewController.swift index 1fede64..7dc8692 100644 --- a/Box42/Preferences/Controller/PreferencesViewController.swift +++ b/Box42/Preferences/Controller/PreferencesViewController.swift @@ -9,30 +9,21 @@ import Cocoa import SnapKit class PreferencesViewController: NSViewController { - var prefTableView : NSTableView? + var prefTableView : PreferencesTableView? override func loadView() { self.view = NSView() self.view.wantsLayer = true self.view.layer?.backgroundColor = NSColor.blue.cgColor - prefTableView = NSTableView(frame: .zero) - // Column 추가 - let column1 = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("Column1")) - column1.width = 100.0 - column1.title = "Column 1" - prefTableView?.addTableColumn(column1) + prefTableView = PreferencesTableView(frame: .zero) + prefTableView?.setup() +// prefTableView?.viewModel = viewModel - // delegate와 dataSource 설정 - prefTableView?.delegate = self - prefTableView?.dataSource = self - - // TableView를 스크롤 뷰에 추가 (일반적으로 NSTableView는 NSScrollView 안에 위치합니다) let scrollView = NSScrollView() scrollView.documentView = prefTableView self.view.addSubview(scrollView) - scrollView.snp.makeConstraints({ make in make.edges.equalToSuperview() }) @@ -42,20 +33,3 @@ class PreferencesViewController: NSViewController { }) } } - -extension PreferencesViewController: NSTableViewDelegate, NSTableViewDataSource { - func numberOfRows(in tableView: NSTableView) -> Int { - return 10 // 총 로우 수 - } - - func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { - let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("MyCell"), owner: self) as? NSTableCellView ?? NSTableCellView() - cell.textField?.stringValue = "Row \(row), Column \(tableColumn?.identifier ?? NSUserInterfaceItemIdentifier(""))" - return cell - } - - func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { - return 44.0 // 셀 높이를 44로 설정 - } -} - diff --git a/Box42/Preferences/View/PreferencesTableView.swift b/Box42/Preferences/View/PreferencesTableView.swift new file mode 100644 index 0000000..e4b862a --- /dev/null +++ b/Box42/Preferences/View/PreferencesTableView.swift @@ -0,0 +1,78 @@ +// +// PreferencesTableView.swift +// Box42 +// +// Created by Chanhee Kim on 8/31/23. +// + +import AppKit +import SnapKit +import Combine + +enum PreferencesCellList: Int, CaseIterable { + case requestAccessView = 1 + case cpu = 2 + case my = 3 + + var height: CGFloat { + switch self { + case .requestAccessView: + return 100.0 + case .cpu: + return 40.0 + case .my: + return 50.0 + } + } +} + +class PreferencesTableView: NSTableView { + let requestAccessView = RequestAccessView() + + func setup() { + self.delegate = self + self.dataSource = self + + let column1 = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("Preferences")) + column1.width = 100.0 + column1.title = "Preferences" + self.addTableColumn(column1) + } + +} + +extension PreferencesTableView: NSTableViewDelegate, NSTableViewDataSource { + func getCellForRow(at row: Int) -> NSView { + let allCases = PreferencesCellList.allCases + if row >= 0 && row < allCases.count { + switch allCases[row] { + case .requestAccessView: + return requestAccessView + case .cpu: + // Return the view for the CPU cell + return NSView() // Placeholder + case .my: + // Return the view for the "my" cell + return NSView() // Placeholder + } + } + return NSView() // Default view if out of bounds or undefined + } + + func numberOfRows(in tableView: NSTableView) -> Int { + return PreferencesCellList.allCases.count + } + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + return getCellForRow(at: row) + } + + func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { + let allCases = PreferencesCellList.allCases + if row >= 0 && row < allCases.count { + return allCases[row].height + } + + return 44.0 // Default height + } +} diff --git a/Box42/Preferences/View/RequestAccessView.swift b/Box42/Preferences/View/RequestAccessView.swift new file mode 100644 index 0000000..0062579 --- /dev/null +++ b/Box42/Preferences/View/RequestAccessView.swift @@ -0,0 +1,136 @@ +// +// RequestAccessView.swift +// Box42 +// +// Created by Chanhee Kim on 8/31/23. +// + +import AppKit +import SnapKit + +class RequestAccessView: NSView { + var requestAccessTextField: NSTextField = NSTextField() + var grantAccessButton: NSButton = NSButton() + var revokeAccessButton: NSButton = NSButton() + var directoryNameTextField: NSTextField = NSTextField() + + override init(frame frameRect: NSRect) { + super.init(frame: .zero) + self.wantsLayer = true + self.layer?.backgroundColor = NSColor(hex: "#7FFFFFFF").cgColor + self.layer?.cornerRadius = 13 + + // Add subviews + self.addSubview(requestAccessTextField) + self.addSubview(grantAccessButton) + self.addSubview(revokeAccessButton) + self.addSubview(directoryNameTextField) + + // Initialize UI elements + textfieldInit() + buttonInit() + directoryNameTextFieldInit() + + // Set constraints + textfieldConstraints() + buttonConstraints() + directoryNameTextFieldConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func textfieldInit() { + requestAccessTextField.stringValue = "Script 및 기능들을 실행하기 위해서 루트 디렉토리의 권한이 필요합니다." + requestAccessTextField.font = NSFont.systemFont(ofSize: 15) + requestAccessTextField.isEditable = false + requestAccessTextField.isBordered = false + requestAccessTextField.backgroundColor = NSColor.clear + requestAccessTextField.lineBreakMode = .byWordWrapping + requestAccessTextField.maximumNumberOfLines = 3 + } + + func buttonInit() { + grantAccessButton.title = "권한 부여" + grantAccessButton.target = self + grantAccessButton.action = #selector(requestFolderAccess(_:)) + + revokeAccessButton.title = "권한 취소" + revokeAccessButton.target = self + revokeAccessButton.action = #selector(revokeFolderAccess(_:)) + } + + func directoryNameTextFieldInit() { + directoryNameTextField.stringValue = "선택된 디렉터리: 없음" + directoryNameTextField.font = NSFont.systemFont(ofSize: 15) + directoryNameTextField.isEditable = false + directoryNameTextField.isBordered = false + directoryNameTextField.backgroundColor = NSColor.clear + directoryNameTextField.lineBreakMode = .byWordWrapping + } + + func directoryNameTextFieldConstraints() { + directoryNameTextField.snp.makeConstraints { make in + make.top.equalTo(revokeAccessButton.snp.bottom).offset(10) + make.leading.equalToSuperview().offset(17) + make.trailing.equalToSuperview().offset(-17) + } + } + + func textfieldConstraints() { + requestAccessTextField.snp.makeConstraints { make in + make.top.equalToSuperview().offset(10) + make.leading.equalToSuperview().offset(17) + make.trailing.equalToSuperview().offset(-17) + } + } + + func buttonConstraints() { + grantAccessButton.snp.makeConstraints { make in + make.top.equalTo(requestAccessTextField.snp.bottom).offset(10) + make.leading.equalToSuperview().offset(17) + } + + revokeAccessButton.snp.makeConstraints { make in + make.top.equalTo(requestAccessTextField.snp.bottom).offset(10) + make.leading.equalTo(grantAccessButton.snp.trailing).offset(10) + } + } + + @objc func requestFolderAccess(_ sender: NSButton) { + let openPanel = NSOpenPanel() + openPanel.title = "Choose a folder" + openPanel.showsResizeIndicator = true + openPanel.showsHiddenFiles = false + openPanel.canChooseDirectories = true + openPanel.canCreateDirectories = true + openPanel.allowsMultipleSelection = false + openPanel.canChooseFiles = false + + if openPanel.runModal() == NSApplication.ModalResponse.OK { + let result = openPanel.url + + if let result = result { + print("Selected folder is \(result.path)") + + directoryNameTextField.stringValue = "선택된 디렉터리: \(result.path)" + + do { + let bookmarkData = try result.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) + UserDefaults.standard.set(bookmarkData, forKey: "bookmarkData") + } catch { + print("Error creating bookmark: \(error)") + } + } + } else { + // User clicked on "Cancel" + return + } + } + + @objc func revokeFolderAccess(_ sender: NSButton) { + // TODO: Add code to revoke folder access + } + +} diff --git a/Box42/QuickSlot/Controller/QuickSlotScriptsLogicController.swift b/Box42/QuickSlot/Controller/QuickSlotScriptsLogicController.swift index 517a789..6c2ada1 100644 --- a/Box42/QuickSlot/Controller/QuickSlotScriptsLogicController.swift +++ b/Box42/QuickSlot/Controller/QuickSlotScriptsLogicController.swift @@ -17,7 +17,7 @@ class ScriptsLogicController { @objc func handleButtonTapped(notification: NSNotification) { if let button = notification.object as? NSButton { - ExcuteScripts.executeShellScript(path: button.associatedString ?? "") + SecurityScopedResourceAccess.accessResourceExecuteShellScript(scriptPath: button.associatedString ?? "") } } diff --git a/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift b/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift index 0d16006..d636599 100644 --- a/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift +++ b/Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift @@ -13,7 +13,8 @@ class QuickSlotViewModel { @Published var buttons: [QuickSlotButtonModel] = [] private init() { - let button1 = QuickSlotButtonModel(title: QuickSlotUI.title.clean) + let button1 = QuickSlotButtonModel(title: QuickSlotUI.title.clean, + path: Bundle.main.path(forResource: "cleanCache", ofType: "sh")) let button2 = QuickSlotButtonModel(title: QuickSlotUI.title.preferences) let button3 = QuickSlotButtonModel(title: QuickSlotUI.title.scripts) let button4 = QuickSlotButtonModel(title: QuickSlotUI.title.user) @@ -21,8 +22,8 @@ class QuickSlotViewModel { buttons = [button1, button2, button3, button4] } + // 퀵슬롯 안에 해당 버튼이 없으면 추가 func addButton(_ button: QuickSlotButtonModel) { - if buttons.count > 7 { return } if !buttons.contains(where: { $0.id == button.id }) { buttons.append(button) } diff --git a/Box42/Resources/Info.plist b/Box42/Resources/Info.plist index 1520f65..59cd59d 100644 --- a/Box42/Resources/Info.plist +++ b/Box42/Resources/Info.plist @@ -2,6 +2,8 @@ + NSDocumentsFolderUsageDescription + 원활한 앱 구동을 위해 유저 디렉토리의 권한을 요청합니다. NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/Box42/Scripts/Controller/ScriptsFileManager.swift b/Box42/Scripts/Controller/ScriptsFileManager.swift index 5b4c69f..7cae68f 100644 --- a/Box42/Scripts/Controller/ScriptsFileManager.swift +++ b/Box42/Scripts/Controller/ScriptsFileManager.swift @@ -16,7 +16,7 @@ class ScriptsFileManager { if let savedURL = savedURL, fileManager.fileExists(atPath: savedURL.path) { print("File already exists, executing...") - ExcuteScripts.executeShellScript(path: savedURL.path) + SecurityScopedResourceAccess.accessResourceExecuteShellScript(scriptPath: savedURL.path) return } @@ -37,8 +37,7 @@ class ScriptsFileManager { try fileManager.moveItem(at: location, to: savedURL) print("Saved URL: ", savedURL) - - ExcuteScripts.executeShellScript(path: savedURL.path) + SecurityScopedResourceAccess.accessResourceExecuteShellScript(scriptPath: savedURL.path) } catch { print("File error: \(error)") diff --git a/Box42/Shared/SecurityScopedResourceAccess.swift b/Box42/Shared/SecurityScopedResourceAccess.swift new file mode 100644 index 0000000..a919d15 --- /dev/null +++ b/Box42/Shared/SecurityScopedResourceAccess.swift @@ -0,0 +1,59 @@ +// +// SecurityScopedResourceAccess.swift +// Box42 +// +// Created by Chanhee Kim on 8/31/23. +// + +import Foundation + +class SecurityScopedResourceAccess { + private static let queue = DispatchQueue(label: "com.yourApp.securityAccessQueue", attributes: .concurrent) + private static var isAccessing = false + static var bookmarkData: Data? { + get { + return UserDefaults.standard.data(forKey: "bookmarkData") + } + set { + UserDefaults.standard.set(newValue, forKey: "bookmarkData") + } + } + + static func accessResourceExecuteShellScript(scriptPath: String) { + queue.async(flags: .barrier) { + var url: URL? = nil // 이 부분을 추가하여 url 변수의 스코프를 확장합니다. + do { + var staleBookmarkData = false + guard let bookmarkData = self.bookmarkData else { + print("Bookmark data not available.") + return + } + + print("Stored bookmark data: \(String(describing: UserDefaults.standard.data(forKey: "bookmarkData")))") + + + url = try URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &staleBookmarkData) + + if staleBookmarkData { + // Refresh the bookmark data and save it. + } + + isAccessing = url?.startAccessingSecurityScopedResource() ?? false + + // Perform work here + if isAccessing { + ExecuteScripts.executeShellScript(path: scriptPath) + } + + } catch { + print("An error occurred: \(error)") + } + + // Cleanup + if isAccessing { + // Make sure to match this with a call to startAccessingSecurityScopedResource() + url?.stopAccessingSecurityScopedResource() + } + } + } +}