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

feat: 유저에게 권한을 받습니다. #111

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
34 changes: 4 additions & 30 deletions Box42/Preferences/Controller/PreferencesViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
Expand All @@ -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로 설정
}
}

78 changes: 78 additions & 0 deletions Box42/Preferences/View/PreferencesTableView.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
136 changes: 136 additions & 0 deletions Box42/Preferences/View/RequestAccessView.swift
Original file line number Diff line number Diff line change
@@ -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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? "")
}
}

Expand Down
5 changes: 3 additions & 2 deletions Box42/QuickSlot/ViewModel/QuickSlotViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ 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)

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)
}
Expand Down
2 changes: 2 additions & 0 deletions Box42/Resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSDocumentsFolderUsageDescription</key>
<string>원활한 앱 구동을 위해 유저 디렉토리의 권한을 요청합니다.</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
Expand Down
5 changes: 2 additions & 3 deletions Box42/Scripts/Controller/ScriptsFileManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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)")
Expand Down
59 changes: 59 additions & 0 deletions Box42/Shared/SecurityScopedResourceAccess.swift
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
}