Commit partial changes
This commit is contained in:
		
							
								
								
									
										110
									
								
								Soyuz.xcodeproj/xcshareddata/xcschemes/Soyuz.xcscheme
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								Soyuz.xcodeproj/xcshareddata/xcschemes/Soyuz.xcscheme
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<Scheme
 | 
			
		||||
   LastUpgradeVersion = "1430"
 | 
			
		||||
   version = "2.2">
 | 
			
		||||
   <BuildAction
 | 
			
		||||
      parallelizeBuildables = "YES"
 | 
			
		||||
      buildImplicitDependencies = "YES">
 | 
			
		||||
      <BuildActionEntries>
 | 
			
		||||
         <BuildActionEntry
 | 
			
		||||
            buildForTesting = "YES"
 | 
			
		||||
            buildForRunning = "NO"
 | 
			
		||||
            buildForProfiling = "NO"
 | 
			
		||||
            buildForArchiving = "NO"
 | 
			
		||||
            buildForAnalyzing = "NO">
 | 
			
		||||
            <AutocreatedTestPlanReference>
 | 
			
		||||
            </AutocreatedTestPlanReference>
 | 
			
		||||
         </BuildActionEntry>
 | 
			
		||||
         <BuildActionEntry
 | 
			
		||||
            buildForTesting = "YES"
 | 
			
		||||
            buildForRunning = "YES"
 | 
			
		||||
            buildForProfiling = "YES"
 | 
			
		||||
            buildForArchiving = "YES"
 | 
			
		||||
            buildForAnalyzing = "YES">
 | 
			
		||||
            <BuildableReference
 | 
			
		||||
               BuildableIdentifier = "primary"
 | 
			
		||||
               BlueprintIdentifier = "E180B5E42992CD9100425DB0"
 | 
			
		||||
               BuildableName = "Soyuz.app"
 | 
			
		||||
               BlueprintName = "Soyuz"
 | 
			
		||||
               ReferencedContainer = "container:Soyuz.xcodeproj">
 | 
			
		||||
            </BuildableReference>
 | 
			
		||||
         </BuildActionEntry>
 | 
			
		||||
      </BuildActionEntries>
 | 
			
		||||
   </BuildAction>
 | 
			
		||||
   <TestAction
 | 
			
		||||
      buildConfiguration = "Debug"
 | 
			
		||||
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
 | 
			
		||||
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
 | 
			
		||||
      shouldUseLaunchSchemeArgsEnv = "YES"
 | 
			
		||||
      shouldAutocreateTestPlan = "YES">
 | 
			
		||||
      <Testables>
 | 
			
		||||
         <TestableReference
 | 
			
		||||
            skipped = "NO"
 | 
			
		||||
            parallelizable = "YES">
 | 
			
		||||
            <BuildableReference
 | 
			
		||||
               BuildableIdentifier = "primary"
 | 
			
		||||
               BlueprintIdentifier = "E180B5FA2992CD9300425DB0"
 | 
			
		||||
               BuildableName = "SoyuzTests.xctest"
 | 
			
		||||
               BlueprintName = "SoyuzTests"
 | 
			
		||||
               ReferencedContainer = "container:Soyuz.xcodeproj">
 | 
			
		||||
            </BuildableReference>
 | 
			
		||||
         </TestableReference>
 | 
			
		||||
         <TestableReference
 | 
			
		||||
            skipped = "NO"
 | 
			
		||||
            parallelizable = "YES">
 | 
			
		||||
            <BuildableReference
 | 
			
		||||
               BuildableIdentifier = "primary"
 | 
			
		||||
               BlueprintIdentifier = "E180B6042992CD9300425DB0"
 | 
			
		||||
               BuildableName = "SoyuzUITests.xctest"
 | 
			
		||||
               BlueprintName = "SoyuzUITests"
 | 
			
		||||
               ReferencedContainer = "container:Soyuz.xcodeproj">
 | 
			
		||||
            </BuildableReference>
 | 
			
		||||
         </TestableReference>
 | 
			
		||||
      </Testables>
 | 
			
		||||
   </TestAction>
 | 
			
		||||
   <LaunchAction
 | 
			
		||||
      buildConfiguration = "Debug"
 | 
			
		||||
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
 | 
			
		||||
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
 | 
			
		||||
      launchStyle = "0"
 | 
			
		||||
      useCustomWorkingDirectory = "NO"
 | 
			
		||||
      ignoresPersistentStateOnLaunch = "NO"
 | 
			
		||||
      debugDocumentVersioning = "YES"
 | 
			
		||||
      debugServiceExtension = "internal"
 | 
			
		||||
      allowLocationSimulation = "YES">
 | 
			
		||||
      <BuildableProductRunnable
 | 
			
		||||
         runnableDebuggingMode = "0">
 | 
			
		||||
         <BuildableReference
 | 
			
		||||
            BuildableIdentifier = "primary"
 | 
			
		||||
            BlueprintIdentifier = "E180B5E42992CD9100425DB0"
 | 
			
		||||
            BuildableName = "Soyuz.app"
 | 
			
		||||
            BlueprintName = "Soyuz"
 | 
			
		||||
            ReferencedContainer = "container:Soyuz.xcodeproj">
 | 
			
		||||
         </BuildableReference>
 | 
			
		||||
      </BuildableProductRunnable>
 | 
			
		||||
   </LaunchAction>
 | 
			
		||||
   <ProfileAction
 | 
			
		||||
      buildConfiguration = "Release"
 | 
			
		||||
      shouldUseLaunchSchemeArgsEnv = "YES"
 | 
			
		||||
      savedToolIdentifier = ""
 | 
			
		||||
      useCustomWorkingDirectory = "NO"
 | 
			
		||||
      debugDocumentVersioning = "YES">
 | 
			
		||||
      <BuildableProductRunnable
 | 
			
		||||
         runnableDebuggingMode = "0">
 | 
			
		||||
         <BuildableReference
 | 
			
		||||
            BuildableIdentifier = "primary"
 | 
			
		||||
            BlueprintIdentifier = "E180B5E42992CD9100425DB0"
 | 
			
		||||
            BuildableName = "Soyuz.app"
 | 
			
		||||
            BlueprintName = "Soyuz"
 | 
			
		||||
            ReferencedContainer = "container:Soyuz.xcodeproj">
 | 
			
		||||
         </BuildableReference>
 | 
			
		||||
      </BuildableProductRunnable>
 | 
			
		||||
   </ProfileAction>
 | 
			
		||||
   <AnalyzeAction
 | 
			
		||||
      buildConfiguration = "Debug">
 | 
			
		||||
   </AnalyzeAction>
 | 
			
		||||
   <ArchiveAction
 | 
			
		||||
      buildConfiguration = "Release"
 | 
			
		||||
      revealArchiveInOrganizer = "YES">
 | 
			
		||||
   </ArchiveAction>
 | 
			
		||||
</Scheme>
 | 
			
		||||
@@ -0,0 +1,90 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<Bucket
 | 
			
		||||
   uuid = "DAF8C4E2-B59F-4B82-AB85-13AA83C729BF"
 | 
			
		||||
   type = "1"
 | 
			
		||||
   version = "2.0">
 | 
			
		||||
   <Breakpoints>
 | 
			
		||||
      <BreakpointProxy
 | 
			
		||||
         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
 | 
			
		||||
         <BreakpointContent
 | 
			
		||||
            uuid = "6CE8E079-8C71-49E3-9075-ED84DAB3B1D8"
 | 
			
		||||
            shouldBeEnabled = "No"
 | 
			
		||||
            ignoreCount = "0"
 | 
			
		||||
            continueAfterRunningActions = "No"
 | 
			
		||||
            filePath = "SoyuzTests/MoonrakerSocketManagerTests.swift"
 | 
			
		||||
            startingColumnNumber = "9223372036854775807"
 | 
			
		||||
            endingColumnNumber = "9223372036854775807"
 | 
			
		||||
            startingLineNumber = "71"
 | 
			
		||||
            endingLineNumber = "71"
 | 
			
		||||
            landmarkName = "setUp()"
 | 
			
		||||
            landmarkType = "7">
 | 
			
		||||
         </BreakpointContent>
 | 
			
		||||
      </BreakpointProxy>
 | 
			
		||||
      <BreakpointProxy
 | 
			
		||||
         BreakpointExtensionID = "Xcode.Breakpoint.RuntimeIssueBreakpoint">
 | 
			
		||||
         <BreakpointContent
 | 
			
		||||
            uuid = "01C24E12-6569-4AD6-87E9-63E4F37C4228"
 | 
			
		||||
            shouldBeEnabled = "Yes"
 | 
			
		||||
            ignoreCount = "0"
 | 
			
		||||
            continueAfterRunningActions = "No"
 | 
			
		||||
            breakpointStackSelectionBehavior = "1"
 | 
			
		||||
            type = "65535">
 | 
			
		||||
         </BreakpointContent>
 | 
			
		||||
      </BreakpointProxy>
 | 
			
		||||
      <BreakpointProxy
 | 
			
		||||
         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
 | 
			
		||||
         <BreakpointContent
 | 
			
		||||
            uuid = "E4DF5E8F-D6D9-450A-AD85-DB4722F31364"
 | 
			
		||||
            shouldBeEnabled = "No"
 | 
			
		||||
            ignoreCount = "0"
 | 
			
		||||
            continueAfterRunningActions = "No"
 | 
			
		||||
            filePath = "SoyuzTests/MoonrakerSocketManagerTests.swift"
 | 
			
		||||
            startingColumnNumber = "9223372036854775807"
 | 
			
		||||
            endingColumnNumber = "9223372036854775807"
 | 
			
		||||
            startingLineNumber = "73"
 | 
			
		||||
            endingLineNumber = "73"
 | 
			
		||||
            landmarkName = "setUp()"
 | 
			
		||||
            landmarkType = "7">
 | 
			
		||||
         </BreakpointContent>
 | 
			
		||||
      </BreakpointProxy>
 | 
			
		||||
      <BreakpointProxy
 | 
			
		||||
         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
 | 
			
		||||
         <BreakpointContent
 | 
			
		||||
            uuid = "A14A53F8-609B-4A4E-8B15-208CD58339E0"
 | 
			
		||||
            shouldBeEnabled = "No"
 | 
			
		||||
            ignoreCount = "0"
 | 
			
		||||
            continueAfterRunningActions = "No"
 | 
			
		||||
            filePath = "Soyuz/Views/SoyuzMenuBarExtraView.swift"
 | 
			
		||||
            startingColumnNumber = "9223372036854775807"
 | 
			
		||||
            endingColumnNumber = "9223372036854775807"
 | 
			
		||||
            startingLineNumber = "15"
 | 
			
		||||
            endingLineNumber = "15"
 | 
			
		||||
            landmarkName = "SoyuzMenuBarExtraView"
 | 
			
		||||
            landmarkType = "14">
 | 
			
		||||
         </BreakpointContent>
 | 
			
		||||
      </BreakpointProxy>
 | 
			
		||||
      <BreakpointProxy
 | 
			
		||||
         BreakpointExtensionID = "Xcode.Breakpoint.SymbolicBreakpoint">
 | 
			
		||||
         <BreakpointContent
 | 
			
		||||
            uuid = "BC514915-414C-4AB7-86D8-F96569D06369"
 | 
			
		||||
            shouldBeEnabled = "Yes"
 | 
			
		||||
            ignoreCount = "0"
 | 
			
		||||
            continueAfterRunningActions = "No"
 | 
			
		||||
            symbolName = "__nwlog_err_simulate_crash"
 | 
			
		||||
            moduleName = "">
 | 
			
		||||
            <Locations>
 | 
			
		||||
               <Location
 | 
			
		||||
                  uuid = "BC514915-414C-4AB7-86D8-F96569D06369 - 2fd95423b9cea3ab"
 | 
			
		||||
                  shouldBeEnabled = "Yes"
 | 
			
		||||
                  ignoreCount = "0"
 | 
			
		||||
                  continueAfterRunningActions = "No"
 | 
			
		||||
                  symbolName = "__nwlog_err_simulate_crash"
 | 
			
		||||
                  moduleName = "Network"
 | 
			
		||||
                  usesParentBreakpointCondition = "Yes"
 | 
			
		||||
                  offsetFromSymbolStart = "0">
 | 
			
		||||
               </Location>
 | 
			
		||||
            </Locations>
 | 
			
		||||
         </BreakpointContent>
 | 
			
		||||
      </BreakpointProxy>
 | 
			
		||||
   </Breakpoints>
 | 
			
		||||
</Bucket>
 | 
			
		||||
							
								
								
									
										207
									
								
								Soyuz/ViewModels/MoonrakerSocketManagerNative.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								Soyuz/ViewModels/MoonrakerSocketManagerNative.swift
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,207 @@
 | 
			
		||||
//
 | 
			
		||||
//  MoonrakerSocketManagerNative.swift
 | 
			
		||||
//  Soyuz
 | 
			
		||||
//
 | 
			
		||||
//  Created by Madeline Pace on 6/27/23.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import Foundation
 | 
			
		||||
 | 
			
		||||
import Foundation
 | 
			
		||||
import Network
 | 
			
		||||
import AppKit
 | 
			
		||||
//import Starscream
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MoonrakerSocketManagerNative: ObservableObject {
 | 
			
		||||
    let WEBSOCKET_TIMEOUT_INTERVAL: TimeInterval = 60.0
 | 
			
		||||
    
 | 
			
		||||
    // Websocket JSON-RPC published data
 | 
			
		||||
    @Published var state: String
 | 
			
		||||
    @Published var progress: Double
 | 
			
		||||
    @Published var extruderTemperature: Double
 | 
			
		||||
    @Published var bedTemperature: Double
 | 
			
		||||
    
 | 
			
		||||
    // Active connection published data
 | 
			
		||||
    @Published var isConnected = false
 | 
			
		||||
    @Published var socketHost: String
 | 
			
		||||
    @Published var socketPort: String
 | 
			
		||||
    
 | 
			
		||||
    // Published NWConnection for listing connection information
 | 
			
		||||
    @Published var connection: NWConnection?
 | 
			
		||||
    @Published var friendlyHostname: String = ""
 | 
			
		||||
    
 | 
			
		||||
    var notification = UserNotificationHandler.shared
 | 
			
		||||
    
 | 
			
		||||
    private var socket: NWConnection?
 | 
			
		||||
    private var lastPingDate = Date()
 | 
			
		||||
    
 | 
			
		||||
    // MARK: PRM init()
 | 
			
		||||
    init() {
 | 
			
		||||
        state = ""
 | 
			
		||||
        progress = 0.0
 | 
			
		||||
        extruderTemperature = 0.0
 | 
			
		||||
        bedTemperature = 0.0
 | 
			
		||||
        socketHost = ""
 | 
			
		||||
        socketPort = ""
 | 
			
		||||
        
 | 
			
		||||
        // Set up sleep/wake notification observers
 | 
			
		||||
        let center = NSWorkspace.shared.notificationCenter;
 | 
			
		||||
        let mainQueue = OperationQueue.main
 | 
			
		||||
        
 | 
			
		||||
        center.addObserver(forName: NSWorkspace.screensDidWakeNotification, object: nil, queue: mainQueue) { notification in
 | 
			
		||||
            self.screenChangedSleepState(notification)
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        center.addObserver(forName: NSWorkspace.screensDidSleepNotification, object: nil, queue: mainQueue) { notification in
 | 
			
		||||
            self.screenChangedSleepState(notification)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Called from the UI with an endpoint.
 | 
			
		||||
    // Momentarily connect/disconnects from the endpoint to retrieve the host/port
 | 
			
		||||
    // calls private function openWebsocket to process the host/port
 | 
			
		||||
    func connectToBonjourEndpoint(_ endpoint: NWEndpoint) {
 | 
			
		||||
        // Debug stuff
 | 
			
		||||
        endpoint.txtRecord?.forEach({ (key: String, value: NWTXTRecord.Entry) in
 | 
			
		||||
            print("\(key): \(value)")
 | 
			
		||||
        })
 | 
			
		||||
        
 | 
			
		||||
        print("About to connect to endpoint \(endpoint.debugDescription)")
 | 
			
		||||
        
 | 
			
		||||
        if connection == nil || connection?.state == .cancelled {
 | 
			
		||||
            //            //let parameters = NWParameters.tcp
 | 
			
		||||
            //            //let options = NWProtocolWebSocket.Options()
 | 
			
		||||
            //            //parameters.defaultProtocolStack.applicationProtocols.insert(options, at: 0)
 | 
			
		||||
            //            let paramet
 | 
			
		||||
            connection = NWConnection(to: endpoint, using: .tcp)
 | 
			
		||||
        }
 | 
			
		||||
        //
 | 
			
		||||
        //        connection!.stateUpdateHandler = { [self] state in
 | 
			
		||||
        //            switch state {
 | 
			
		||||
        //            case .setup:
 | 
			
		||||
        //                break
 | 
			
		||||
        //            case .ready:
 | 
			
		||||
        //                self.isConnected = true
 | 
			
		||||
        //            case .failed(let error):
 | 
			
		||||
        //                self.isConnected = false
 | 
			
		||||
        //                print("[NWConnection websocket] connection failed: \(error)")
 | 
			
		||||
        //            case .cancelled:
 | 
			
		||||
        //                self.isConnected = false
 | 
			
		||||
        //                print("Connection cancelled.")
 | 
			
		||||
        //            default:
 | 
			
		||||
        //                break
 | 
			
		||||
        //            }
 | 
			
		||||
        //        }
 | 
			
		||||
        //
 | 
			
		||||
        connection?.stateUpdateHandler = { [self] state in
 | 
			
		||||
            switch state {
 | 
			
		||||
            case .ready:
 | 
			
		||||
                if let innerEndpoint = connection?.currentPath?.remoteEndpoint, case .hostPort(let host, let port) = innerEndpoint {
 | 
			
		||||
                    let hostPortDebugOutput = "Connected to \(host):\(port)"
 | 
			
		||||
                    
 | 
			
		||||
                    print(hostPortDebugOutput)
 | 
			
		||||
                    
 | 
			
		||||
                    let hostString = "\(host)"
 | 
			
		||||
                    let regex = try! Regex("%(.+)")
 | 
			
		||||
                    let match = hostString.firstMatch(of: regex)
 | 
			
		||||
                    
 | 
			
		||||
                    let sanitizedHost = hostString.replacingOccurrences(of: match?.0 ?? "", with: "")
 | 
			
		||||
                    
 | 
			
		||||
                    print("[sanitized] Resolved \(sanitizedHost):\(port)")
 | 
			
		||||
                    
 | 
			
		||||
                    connection?.cancel()
 | 
			
		||||
                    
 | 
			
		||||
                    DispatchQueue.main.async {
 | 
			
		||||
                        self.friendlyHostname = endpoint.toFriendlyString()
 | 
			
		||||
                        self.socketHost = sanitizedHost
 | 
			
		||||
                        self.socketPort = "\(port)"
 | 
			
		||||
                        self.openWebsocket()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            default:
 | 
			
		||||
                break
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        connection?.start(queue: .global())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func disconnect() {
 | 
			
		||||
        print("disconnect() called")
 | 
			
		||||
        self.isConnected = false
 | 
			
		||||
        //socket?.disconnect()
 | 
			
		||||
        //socket = nil
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func openWebsocket() {
 | 
			
		||||
        let parameters = NWParameters.tcp
 | 
			
		||||
        let socketUrl = URL(string: "ws://\(socketHost):\(socketPort)/websocket")
 | 
			
		||||
        let options = NWProtocolWebSocket.Options()
 | 
			
		||||
        parameters.defaultProtocolStack.applicationProtocols.insert(options, at: 0)
 | 
			
		||||
        socket = NWConnection(to: .url(socketUrl!), using: parameters)
 | 
			
		||||
        socket?.stateUpdateHandler = { state in
 | 
			
		||||
            switch state {
 | 
			
		||||
            case .setup:
 | 
			
		||||
                print("[websocket] Connection setup.")
 | 
			
		||||
            case .ready:
 | 
			
		||||
                print("[websocket] Connection ready.")
 | 
			
		||||
            case .failed(let error):
 | 
			
		||||
                print("[websocket] Connection failed: \(error)")
 | 
			
		||||
            case .cancelled:
 | 
			
		||||
                print("[websocket] Connection cancelled.")
 | 
			
		||||
            default:
 | 
			
		||||
                break
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        socket?.start(queue: .global())
 | 
			
		||||
   }
 | 
			
		||||
    
 | 
			
		||||
    func socketConnectionChangedState() {
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    // TODO: This may not work properly when already connected to the socket
 | 
			
		||||
    private func reconnectWebsocket() {
 | 
			
		||||
        if socket == nil {
 | 
			
		||||
            print("Socket doesn't exist. Fail-safe triggered.")
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        //socket!.disconnect()
 | 
			
		||||
        //self.openWebsocket()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // MARK: Callbacks
 | 
			
		||||
    func screenChangedSleepState(_ notification: Notification) {
 | 
			
		||||
        switch(notification.name) {
 | 
			
		||||
        case NSWorkspace.screensDidSleepNotification:
 | 
			
		||||
            print("Screen slept. Disconnecting..")
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
            //socket?.disconnect()
 | 
			
		||||
        case NSWorkspace.screensDidWakeNotification:
 | 
			
		||||
            print("Screen awoke. Opening websocket..")
 | 
			
		||||
            //self.openWebsocket()
 | 
			
		||||
        default:
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Properly formatted JSON-RPC Request for use with Starscream
 | 
			
		||||
// MARK: JSON-RPC Request Codable
 | 
			
		||||
//struct JsonRpcRequest: Codable {
 | 
			
		||||
//    var jsonrpc = "2.0"
 | 
			
		||||
//    let method: String
 | 
			
		||||
//    let params: [String: [String: String?]]
 | 
			
		||||
//    var id = 1
 | 
			
		||||
//
 | 
			
		||||
//    func encode(to encoder: Encoder) throws {
 | 
			
		||||
//        var container = encoder.container(keyedBy: CodingKeys.self)
 | 
			
		||||
//        try container.encode(jsonrpc, forKey: .jsonrpc)
 | 
			
		||||
//        try container.encode(method, forKey: .method)
 | 
			
		||||
//        try container.encode(params, forKey: .params)
 | 
			
		||||
//        try container.encode(id, forKey: .id)
 | 
			
		||||
//    }
 | 
			
		||||
//}
 | 
			
		||||
		Reference in New Issue
	
	Block a user