mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
refactor: update AppHandle usage to use Arc<AppHandle> for improved memory management (#4491)
* refactor: update AppHandle usage to use Arc<AppHandle> for improved memory management * fix: clippy ci * fix: ensure default_latency_test is safely accessed with non-null assertion
This commit is contained in:
10
.github/workflows/lint-clippy.yml
vendored
10
.github/workflows/lint-clippy.yml
vendored
@@ -42,17 +42,17 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Pnpm install and check
|
||||
run: |
|
||||
pnpm i
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
utils::{dirs, logging::Type},
|
||||
wrap_err,
|
||||
};
|
||||
use tauri::Manager;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
/// 打开应用程序所在目录
|
||||
#[tauri::command]
|
||||
@@ -36,7 +36,7 @@ pub fn open_web_url(url: String) -> CmdResult<()> {
|
||||
|
||||
/// 打开/关闭开发者工具
|
||||
#[tauri::command]
|
||||
pub fn open_devtools(app_handle: tauri::AppHandle) {
|
||||
pub fn open_devtools(app_handle: AppHandle) {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
if !window.is_devtools_open() {
|
||||
window.open_devtools();
|
||||
|
||||
@@ -668,10 +668,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
|
||||
|
||||
/// 根据profile name修改profiles
|
||||
#[tauri::command]
|
||||
pub async fn patch_profiles_config_by_profile_index(
|
||||
_app_handle: tauri::AppHandle,
|
||||
profile_index: String,
|
||||
) -> CmdResult<bool> {
|
||||
pub async fn patch_profiles_config_by_profile_index(profile_index: String) -> CmdResult<bool> {
|
||||
logging!(info, Type::Cmd, true, "切换配置到: {}", profile_index);
|
||||
|
||||
let profiles = IProfiles {
|
||||
@@ -718,7 +715,7 @@ pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
|
||||
|
||||
/// 查看配置文件
|
||||
#[tauri::command]
|
||||
pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult {
|
||||
pub fn view_profile(index: String) -> CmdResult {
|
||||
let file = {
|
||||
wrap_err!(Config::profiles().latest_ref().get_item(&index))?
|
||||
.file
|
||||
@@ -731,7 +728,7 @@ pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult {
|
||||
ret_err!("the file not found");
|
||||
}
|
||||
|
||||
wrap_err!(help::open_file(app_handle, path))
|
||||
wrap_err!(help::open_file(path))
|
||||
}
|
||||
|
||||
/// 读取配置文件内容
|
||||
|
||||
@@ -255,7 +255,7 @@ impl NotificationSystem {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Handle {
|
||||
pub app_handle: Arc<RwLock<Option<AppHandle>>>,
|
||||
pub app_handle: Arc<RwLock<Option<Arc<AppHandle>>>>,
|
||||
pub is_exiting: Arc<RwLock<bool>>,
|
||||
startup_errors: Arc<RwLock<Vec<ErrorMessage>>>,
|
||||
startup_completed: Arc<RwLock<bool>>,
|
||||
@@ -282,10 +282,10 @@ impl Handle {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn init(&self, app_handle: &AppHandle) {
|
||||
pub fn init(&self, app_handle: Arc<AppHandle>) {
|
||||
{
|
||||
let mut handle = self.app_handle.write();
|
||||
*handle = Some(app_handle.clone());
|
||||
*handle = Some(Arc::clone(&app_handle));
|
||||
}
|
||||
|
||||
let mut system_opt = self.notification_system.write();
|
||||
@@ -294,8 +294,9 @@ impl Handle {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn app_handle(&self) -> Option<AppHandle> {
|
||||
self.app_handle.read().clone()
|
||||
/// 获取 AppHandle
|
||||
pub fn app_handle(&self) -> Option<Arc<AppHandle>> {
|
||||
self.app_handle.read().as_ref().map(Arc::clone)
|
||||
}
|
||||
|
||||
pub fn get_window(&self) -> Option<WebviewWindow> {
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
use anyhow::{bail, Result};
|
||||
use parking_lot::Mutex;
|
||||
use std::{collections::HashMap, fmt, str::FromStr, sync::Arc};
|
||||
use tauri::Manager;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState};
|
||||
|
||||
/// Enum representing all available hotkey functions
|
||||
@@ -103,7 +103,7 @@ impl Hotkey {
|
||||
}
|
||||
|
||||
/// Execute the function associated with a hotkey function enum
|
||||
fn execute_function(function: HotkeyFunction, app_handle: &tauri::AppHandle) {
|
||||
fn execute_function(function: HotkeyFunction, app_handle: Arc<AppHandle>) {
|
||||
match function {
|
||||
HotkeyFunction::OpenOrCloseDashboard => {
|
||||
logging!(
|
||||
@@ -218,7 +218,7 @@ impl Hotkey {
|
||||
manager.unregister(hotkey)?;
|
||||
}
|
||||
|
||||
let app_handle_clone = app_handle.clone();
|
||||
let app_handle_clone = Arc::clone(&app_handle);
|
||||
let is_quit = matches!(function, HotkeyFunction::Quit);
|
||||
|
||||
let _ = manager.on_shortcut(hotkey, move |app_handle, hotkey_event, event| {
|
||||
@@ -229,7 +229,7 @@ impl Hotkey {
|
||||
if let Some(window) = app_handle.get_webview_window("main") {
|
||||
if window.is_focused().unwrap_or(false) {
|
||||
logging!(debug, Type::Hotkey, "Executing quit function");
|
||||
Self::execute_function(function, &app_handle_clone);
|
||||
Self::execute_function(function, Arc::clone(&app_handle_clone));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -241,14 +241,14 @@ impl Hotkey {
|
||||
.unwrap_or(true);
|
||||
|
||||
if is_enable_global_hotkey {
|
||||
Self::execute_function(function, &app_handle_clone);
|
||||
Self::execute_function(function, Arc::clone(&app_handle_clone));
|
||||
} else {
|
||||
use crate::utils::window_manager::WindowManager;
|
||||
let is_visible = WindowManager::is_main_window_visible();
|
||||
let is_focused = WindowManager::is_main_window_focused();
|
||||
|
||||
if is_focused && is_visible {
|
||||
Self::execute_function(function, &app_handle_clone);
|
||||
Self::execute_function(function, Arc::clone(&app_handle_clone));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use crate::{
|
||||
|
||||
use anyhow::Result;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
fs,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
@@ -244,7 +245,7 @@ impl Tray {
|
||||
// 设置更新状态
|
||||
self.menu_updating.store(true, Ordering::Release);
|
||||
|
||||
let result = self.update_menu_internal(&app_handle);
|
||||
let result = self.update_menu_internal(app_handle);
|
||||
|
||||
{
|
||||
let mut last_update = self.last_menu_update.lock();
|
||||
@@ -255,7 +256,7 @@ impl Tray {
|
||||
result
|
||||
}
|
||||
|
||||
fn update_menu_internal(&self, app_handle: &AppHandle) -> Result<()> {
|
||||
fn update_menu_internal(&self, app_handle: Arc<AppHandle>) -> Result<()> {
|
||||
let verge = Config::verge().latest_ref().clone();
|
||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
|
||||
@@ -277,7 +278,7 @@ impl Tray {
|
||||
match app_handle.tray_by_id("main") {
|
||||
Some(tray) => {
|
||||
let _ = tray.set_menu(Some(create_tray_menu(
|
||||
app_handle,
|
||||
&app_handle,
|
||||
Some(mode.as_str()),
|
||||
*system_proxy,
|
||||
*tun_mode,
|
||||
@@ -451,7 +452,7 @@ impl Tray {
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn unsubscribe_traffic(&self) {}
|
||||
|
||||
pub fn create_tray_from_handle(&self, app_handle: &AppHandle) -> Result<()> {
|
||||
pub fn create_tray_from_handle(&self, app_handle: Arc<AppHandle>) -> Result<()> {
|
||||
log::info!(target: "app", "正在从AppHandle创建系统托盘");
|
||||
|
||||
// 获取图标
|
||||
@@ -477,7 +478,7 @@ impl Tray {
|
||||
}
|
||||
}
|
||||
|
||||
let tray = builder.build(app_handle)?;
|
||||
let tray = builder.build(app_handle.as_ref())?;
|
||||
|
||||
tray.on_tray_icon_event(|_, event| {
|
||||
let tray_event = { Config::verge().latest_ref().tray_event.clone() };
|
||||
|
||||
@@ -11,15 +11,7 @@ use anyhow::{bail, Result};
|
||||
/// Toggle proxy profile
|
||||
pub fn toggle_proxy_profile(profile_index: String) {
|
||||
AsyncHandler::spawn(|| async move {
|
||||
let Some(app_handle) = handle::Handle::global().app_handle() else {
|
||||
logging!(
|
||||
error,
|
||||
Type::Config,
|
||||
"Failed to get app handle for profile toggle"
|
||||
);
|
||||
return;
|
||||
};
|
||||
match cmd::patch_profiles_config_by_profile_index(app_handle, profile_index).await {
|
||||
match cmd::patch_profiles_config_by_profile_index(profile_index).await {
|
||||
Ok(_) => {
|
||||
let _ = tray::Tray::global().update_menu();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::{
|
||||
};
|
||||
use config::Config;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use tauri::AppHandle;
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::Manager;
|
||||
@@ -28,7 +29,7 @@ use utils::logging::Type;
|
||||
|
||||
/// A global singleton handle to the application.
|
||||
pub struct AppHandleManager {
|
||||
handle: Mutex<Option<AppHandle>>,
|
||||
handle: Mutex<Option<Arc<AppHandle>>>,
|
||||
}
|
||||
|
||||
impl AppHandleManager {
|
||||
@@ -40,7 +41,7 @@ impl AppHandleManager {
|
||||
}
|
||||
|
||||
/// Initialize the app handle manager with an app handle.
|
||||
pub fn init(&self, handle: AppHandle) {
|
||||
pub fn init(&self, handle: Arc<AppHandle>) {
|
||||
let mut app_handle = self.handle.lock();
|
||||
if app_handle.is_none() {
|
||||
*app_handle = Some(handle);
|
||||
@@ -54,12 +55,12 @@ impl AppHandleManager {
|
||||
}
|
||||
|
||||
/// Get the app handle if it has been initialized.
|
||||
pub fn get(&self) -> Option<AppHandle> {
|
||||
fn get(&self) -> Option<Arc<AppHandle>> {
|
||||
self.handle.lock().clone()
|
||||
}
|
||||
|
||||
/// Get the app handle, panics if it hasn't been initialized.
|
||||
pub fn get_handle(&self) -> AppHandle {
|
||||
pub fn get_handle(&self) -> Arc<AppHandle> {
|
||||
if let Some(handle) = self.get() {
|
||||
handle
|
||||
} else {
|
||||
@@ -246,12 +247,12 @@ mod app_init {
|
||||
}
|
||||
|
||||
/// Initialize core components asynchronously
|
||||
pub fn init_core_async(app_handle: tauri::AppHandle) {
|
||||
pub fn init_core_async(app_handle: Arc<AppHandle>) {
|
||||
AsyncHandler::spawn(move || async move {
|
||||
logging!(info, Type::Setup, true, "异步执行应用设置...");
|
||||
match timeout(
|
||||
Duration::from_secs(30),
|
||||
resolve::resolve_setup_async(&app_handle),
|
||||
resolve::resolve_setup_async(app_handle),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -271,12 +272,12 @@ mod app_init {
|
||||
}
|
||||
|
||||
/// Initialize core components synchronously
|
||||
pub fn init_core_sync(app_handle: &tauri::AppHandle) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub fn init_core_sync(app_handle: Arc<AppHandle>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
logging!(info, Type::Setup, true, "初始化AppHandleManager...");
|
||||
AppHandleManager::global().init(app_handle.clone());
|
||||
AppHandleManager::global().init(Arc::clone(&app_handle));
|
||||
|
||||
logging!(info, Type::Setup, true, "初始化核心句柄...");
|
||||
core::handle::Handle::global().init(app_handle);
|
||||
core::handle::Handle::global().init(Arc::clone(&app_handle));
|
||||
|
||||
logging!(info, Type::Setup, true, "初始化配置...");
|
||||
utils::init::init_config()?;
|
||||
@@ -482,13 +483,16 @@ pub fn run() {
|
||||
);
|
||||
}
|
||||
|
||||
let app_handle = app.handle().clone();
|
||||
let app_handle = Arc::new(app_handle);
|
||||
|
||||
// Initialize core components asynchronously
|
||||
app_init::init_core_async(app.handle().clone());
|
||||
app_init::init_core_async(Arc::clone(&app_handle));
|
||||
|
||||
logging!(info, Type::Setup, true, "执行主要设置操作...");
|
||||
|
||||
// Initialize core components synchronously
|
||||
if let Err(e) = app_init::init_core_sync(app.handle()) {
|
||||
if let Err(e) = app_init::init_core_sync(Arc::clone(&app_handle)) {
|
||||
logging!(
|
||||
error,
|
||||
Type::Setup,
|
||||
@@ -509,9 +513,9 @@ pub fn run() {
|
||||
use super::*;
|
||||
|
||||
/// Handle application ready/resumed events
|
||||
pub fn handle_ready_resumed(app_handle: &tauri::AppHandle) {
|
||||
pub fn handle_ready_resumed(app_handle: Arc<AppHandle>) {
|
||||
logging!(info, Type::System, true, "应用就绪或恢复");
|
||||
AppHandleManager::global().init(app_handle.clone());
|
||||
AppHandleManager::global().init(Arc::clone(&app_handle));
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
@@ -524,7 +528,7 @@ pub fn run() {
|
||||
|
||||
/// Handle application reopen events (macOS)
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn handle_reopen(app_handle: &tauri::AppHandle, has_visible_windows: bool) {
|
||||
pub fn handle_reopen(app_handle: Arc<AppHandle>, has_visible_windows: bool) {
|
||||
logging!(
|
||||
info,
|
||||
Type::System,
|
||||
@@ -533,7 +537,7 @@ pub fn run() {
|
||||
has_visible_windows
|
||||
);
|
||||
|
||||
AppHandleManager::global().init(app_handle.clone());
|
||||
AppHandleManager::global().init(Arc::clone(&app_handle));
|
||||
|
||||
if !has_visible_windows {
|
||||
// 当没有可见窗口时,设置为 regular 模式并显示主窗口
|
||||
@@ -685,9 +689,11 @@ pub fn run() {
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
app.run(|app_handle, e| match e {
|
||||
app.run(|app_handle, e| {
|
||||
let app_handle = Arc::new(app_handle.clone());
|
||||
match e {
|
||||
tauri::RunEvent::Ready | tauri::RunEvent::Resumed => {
|
||||
event_handlers::handle_ready_resumed(app_handle);
|
||||
event_handlers::handle_ready_resumed(Arc::clone(&app_handle));
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
tauri::RunEvent::Reopen {
|
||||
@@ -725,5 +731,6 @@ pub fn run() {
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,22 +1,63 @@
|
||||
#[cfg(feature = "tokio-trace")]
|
||||
use std::any::type_name;
|
||||
use std::future::Future;
|
||||
#[cfg(feature = "tokio-trace")]
|
||||
use std::panic::Location;
|
||||
use tauri::{async_runtime, async_runtime::JoinHandle};
|
||||
|
||||
pub struct AsyncHandler;
|
||||
|
||||
impl AsyncHandler {
|
||||
#[track_caller]
|
||||
pub fn spawn<F, Fut>(f: F) -> JoinHandle<()>
|
||||
where
|
||||
F: FnOnce() -> Fut + Send + 'static,
|
||||
Fut: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
#[cfg(feature = "tokio-trace")]
|
||||
Self::log_task_info(&f);
|
||||
async_runtime::spawn(f())
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn spawn_blocking<T, F>(f: F) -> JoinHandle<T>
|
||||
where
|
||||
F: FnOnce() -> T + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
#[cfg(feature = "tokio-trace")]
|
||||
Self::log_task_info(&f);
|
||||
async_runtime::spawn_blocking(f)
|
||||
}
|
||||
|
||||
#[cfg(feature = "tokio-trace")]
|
||||
#[track_caller]
|
||||
fn log_task_info<F>(f: &F)
|
||||
where
|
||||
F: ?Sized,
|
||||
{
|
||||
const TRACE_MINI_SIZE: usize = 0;
|
||||
let size = std::mem::size_of_val(f);
|
||||
if size <= TRACE_MINI_SIZE {
|
||||
return;
|
||||
}
|
||||
|
||||
let location = Location::caller();
|
||||
let type_str = type_name::<F>();
|
||||
let size_str = format!("{} bytes", size);
|
||||
let loc_str = format!(
|
||||
"{}:{}:{}",
|
||||
location.file(),
|
||||
location.line(),
|
||||
location.column()
|
||||
);
|
||||
|
||||
println!("┌────────────────────┬─────────────────────────────────────────────────────────────────────────────┐");
|
||||
println!("│ {:<18} │ {:<80} │", "Field", "Value");
|
||||
println!("├────────────────────┼─────────────────────────────────────────────────────────────────────────────┤");
|
||||
println!("│ {:<18} │ {:<80} │", "Type of task", type_str);
|
||||
println!("│ {:<18} │ {:<80} │", "Size of task", size_str);
|
||||
println!("│ {:<18} │ {:<80} │", "Called from", loc_str);
|
||||
println!("└────────────────────┴─────────────────────────────────────────────────────────────────────────────┘");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ pub fn get_last_part_and_decode(url: &str) -> Option<String> {
|
||||
}
|
||||
|
||||
/// open file
|
||||
pub fn open_file(_: tauri::AppHandle, path: PathBuf) -> Result<()> {
|
||||
pub fn open_file(path: PathBuf) -> Result<()> {
|
||||
open::that_detached(path.as_os_str())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tauri::AppHandle;
|
||||
use tauri_plugin_notification::NotificationExt;
|
||||
|
||||
@@ -14,7 +16,7 @@ pub enum NotificationEvent<'a> {
|
||||
AppHidden,
|
||||
}
|
||||
|
||||
fn notify(app: &AppHandle, title: &str, body: &str) {
|
||||
fn notify(app: Arc<AppHandle>, title: &str, body: &str) {
|
||||
app.notification()
|
||||
.builder()
|
||||
.title(title)
|
||||
@@ -23,7 +25,7 @@ fn notify(app: &AppHandle, title: &str, body: &str) {
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn notify_event(app: &AppHandle, event: NotificationEvent) {
|
||||
pub fn notify_event(app: Arc<AppHandle>, event: NotificationEvent) {
|
||||
use crate::utils::i18n::t;
|
||||
match event {
|
||||
NotificationEvent::DashboardToggled => {
|
||||
|
||||
@@ -14,7 +14,10 @@ use once_cell::sync::OnceCell;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use percent_encoding::percent_decode_str;
|
||||
use scopeguard;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
use tauri::Url;
|
||||
@@ -106,7 +109,7 @@ pub fn reset_ui_ready() {
|
||||
}
|
||||
|
||||
/// 异步方式处理启动后的额外任务
|
||||
pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
||||
pub async fn resolve_setup_async(app_handle: Arc<AppHandle>) {
|
||||
let start_time = std::time::Instant::now();
|
||||
logging!(
|
||||
info,
|
||||
@@ -162,7 +165,7 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
||||
|
||||
if let Some(app_handle) = handle::Handle::global().app_handle() {
|
||||
logging!(info, Type::Tray, true, "创建系统托盘...");
|
||||
let result = tray::Tray::global().create_tray_from_handle(&app_handle);
|
||||
let result = tray::Tray::global().create_tray_from_handle(app_handle);
|
||||
if result.is_ok() {
|
||||
logging!(info, Type::Tray, true, "系统托盘创建成功");
|
||||
} else if let Err(e) = result {
|
||||
@@ -329,7 +332,7 @@ pub fn create_window(is_show: bool) -> bool {
|
||||
};
|
||||
|
||||
match tauri::WebviewWindowBuilder::new(
|
||||
&app_handle,
|
||||
&*app_handle,
|
||||
"main", /* the unique window label */
|
||||
tauri::WebviewUrl::App("index.html".into()),
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@ export const ProxyHead = (props: Props) => {
|
||||
useEffect(() => {
|
||||
delayManager.setUrl(
|
||||
groupName,
|
||||
testUrl || url || verge?.default_latency_test,
|
||||
testUrl || url || verge?.default_latency_test!,
|
||||
);
|
||||
}, [groupName, testUrl, verge?.default_latency_test]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user