This commit is contained in:
Michael Zhang 2019-12-05 01:06:49 -06:00
parent e9f87764fb
commit 22fbd032dd
No known key found for this signature in database
GPG key ID: 5BAEFE5D04F0CE6C
13 changed files with 481 additions and 104 deletions

38
Cargo.lock generated
View file

@ -40,6 +40,11 @@ name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cc"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.10"
@ -89,6 +94,8 @@ dependencies = [
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"libmount 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -123,6 +130,16 @@ name = "libc"
version = "0.2.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libmount"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "log"
version = "0.4.8"
@ -136,6 +153,18 @@ name = "memchr"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "nix"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-integer"
version = "0.1.41"
@ -321,6 +350,11 @@ name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "walkdir"
version = "2.2.9"
@ -379,6 +413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
@ -387,8 +422,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8"
"checksum libmount 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "23c4c2ad2d5cbd2f5a05620c3daf45930add53ec207fa99ce5eec971089dc35f"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
"checksum proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aeccfe4d5d8ea175d5f0e4a2ad0637e0f4121d63bd99d356fb1f39ab2e7c6097"
@ -413,6 +450,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"

View file

@ -15,6 +15,8 @@ anyhow = "1.0"
chrono = "0.4"
env_logger = "0.7"
lazy_static = "1.0"
libc = "0.2"
libmount = "0.1"
log = "0.4"
regex = "1.1"
structopt = "0.3"

View file

@ -5,6 +5,8 @@ rust ver of trash-cli, basic functionality is in, code is probably shit
full free-desktop compliance when
* **Windows Recycle Bin not supported**
Installation
------------

1
run-tests.sh Executable file
View file

@ -0,0 +1 @@
#!/bin/bash

View file

@ -1,5 +1,5 @@
use std::fs;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use walkdir::{DirEntry, WalkDir};
@ -7,14 +7,18 @@ use crate::errors::Error;
use crate::info::TrashInfo;
use crate::XDG;
#[derive(Debug)]
pub struct TrashDir(PathBuf);
#[derive(Clone, Debug)]
pub struct TrashDir(pub PathBuf);
impl TrashDir {
pub fn get_home_trash() -> Self {
TrashDir(XDG.get_data_home().join("Trash"))
}
pub fn path(&self) -> &Path {
self.0.as_ref()
}
pub fn files_dir(&self) -> Result<PathBuf, Error> {
let target = self.0.join("files");
if !target.exists() {

32
src/lib.rs Normal file
View file

@ -0,0 +1,32 @@
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
#[macro_use]
extern crate anyhow;
#[macro_use]
extern crate thiserror;
mod dir;
mod errors;
mod info;
mod mounts;
pub mod ops;
mod utils;
use std::io;
use std::path::PathBuf;
use xdg::BaseDirectories;
pub use crate::dir::TrashDir;
pub use crate::errors::Error;
pub use crate::info::TrashInfo;
use crate::mounts::Mounts;
lazy_static! {
static ref XDG: BaseDirectories = BaseDirectories::new().unwrap();
pub static ref MOUNTS: Mounts = Mounts::read().unwrap();
static ref HOME_TRASH: TrashDir = TrashDir::get_home_trash();
static ref HOME_MOUNT: PathBuf = MOUNTS.get_mount_point(HOME_TRASH.path()).unwrap();
}

View file

@ -1,28 +1,14 @@
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
#[macro_use]
extern crate thiserror;
mod errors;
mod ops;
mod dir;
mod info;
extern crate anyhow;
use std::fs;
use std::io;
use std::path::PathBuf;
use anyhow::Error;
use structopt::StructOpt;
use xdg::BaseDirectories;
use crate::errors::Error;
use crate::dir::TrashDir;
lazy_static! {
static ref XDG: BaseDirectories = BaseDirectories::new().unwrap();
}
use garbage::*;
#[derive(StructOpt)]
enum Command {
@ -63,11 +49,13 @@ enum Command {
fn main() -> Result<(), Error> {
env_logger::init();
// println!("{:?}", *garbage::MOUNTS);
let cmd = Command::from_args();
match cmd {
Command::Empty { dry, days } => match crate::ops::empty(dry, days) {
Command::Empty { dry, days } => match ops::empty(dry, days) {
Ok(_) => (),
Err(err) => error!("error: {:?}", err),
Err(err) => eprintln!("error: {:?}", err),
},
Command::List => {
let home_trash = TrashDir::get_home_trash();
@ -75,7 +63,7 @@ fn main() -> Result<(), Error> {
let info = match info {
Ok(info) => info,
Err(err) => {
warn!("failed to get file info: {:?}", err);
eprintln!("failed to get file info: {:?}", err);
continue;
}
};
@ -85,26 +73,22 @@ fn main() -> Result<(), Error> {
Command::Put {
paths, recursive, ..
} => {
for path in paths {
match crate::ops::put(path, recursive) {
Ok(_) => (),
Err(err) => error!("error: {:?}", err),
}
}
ops::put(paths, recursive);
}
Command::Restore => {
let home_trash = TrashDir::get_home_trash();
let files = home_trash
let mut files = home_trash
.iter()
.unwrap()
.filter_map(|entry| match entry {
Ok(info) => Some(info),
Err(err) => {
warn!("failed to get file info: {:?}", err);
eprintln!("failed to get file info: {:?}", err);
None
}
})
.collect::<Vec<_>>();
files.sort_unstable_by_key(|info| info.deletion_date);
for (i, info) in files.iter().enumerate() {
println!(
"[{}]\t{}\t{}",

77
src/mounts.rs Normal file
View file

@ -0,0 +1,77 @@
use std::borrow::Cow;
use std::env;
use std::ffi::OsStr;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use anyhow::Error;
use libmount::mountinfo::Parser;
use crate::utils;
#[derive(Debug)]
pub struct MountPoint {
pub mount_id: u64,
pub parent_id: u64,
pub major: u64,
pub minor: u64,
pub root: PathBuf,
pub mount_point: PathBuf,
}
fn get_path(s: Cow<OsStr>) -> PathBuf {
Path::new(&s).to_path_buf()
}
impl From<libmount::mountinfo::MountPoint<'_>> for MountPoint {
fn from(mp: libmount::mountinfo::MountPoint) -> Self {
MountPoint {
mount_id: mp.mount_id,
parent_id: mp.parent_id,
major: mp.major,
minor: mp.minor,
root: get_path(mp.root),
mount_point: get_path(mp.mount_point),
}
}
}
#[derive(Debug)]
pub struct Mounts(Vec<MountPoint>);
impl Mounts {
pub fn read() -> Result<Mounts, Error> {
let pid = unsafe { libc::getpid() };
let path = Path::new("/")
.join("proc")
.join(pid.to_string())
.join("mountinfo");
let mut buf = Vec::new();
{
let mut file = File::open(path)?;
file.read_to_end(&mut buf)?;
}
let parser = Parser::new(&buf);
let mut mounts = Vec::new();
for mp in parser {
let mp = mp?;
mounts.push(MountPoint::from(mp));
}
Ok(Mounts(mounts))
}
pub fn get_mount_point(&self, path: impl AsRef<Path>) -> Option<PathBuf> {
let path = utils::into_absolute(path).ok()?;
self.0
.iter()
.filter(|mp| path.starts_with(&mp.mount_point))
.max_by_key(|mp| mp.mount_point.components().count())
.map(|mp| mp.mount_point.to_path_buf())
}
}

View file

@ -1,73 +0,0 @@
use std::fs::{self, File};
use std::path::Path;
use chrono::{Duration, Local};
use crate::errors::Error;
use crate::dir::TrashDir;
use crate::info::TrashInfo;
pub fn empty(dry: bool, days: Option<u32>) -> Result<(), Error> {
let home_trash = TrashDir::get_home_trash();
let cutoff = if let Some(days) = days {
Local::now() - Duration::days(days.into())
} else {
Local::now()
};
for file in home_trash.iter()? {
let file = file?;
// ignore files that were deleted after the cutoff (younger)
let ignore = file.deletion_date > cutoff;
if !ignore {
if dry {
println!("{:?}", file.path);
} else {
fs::remove_file(file.info_path)?;
fs::remove_file(file.deleted_path)?;
}
}
}
Ok(())
}
pub fn put(path: impl AsRef<Path>, recursive: bool) -> Result<(), Error> {
let path = path.as_ref().canonicalize()?;
if path.is_dir() && !recursive {
error!("cannot trash directories without --recursive");
return Ok(());
}
let now = Local::now();
let elapsed = now.timestamp_millis();
let home_trash = TrashDir::get_home_trash();
let file_name = format!(
"{}.{}",
elapsed,
path.file_name().unwrap().to_str().unwrap()
);
let trash_file_path = home_trash.files_dir()?.join(&file_name);
let trash_info_path = home_trash.info_dir()?.join(file_name + ".trashinfo");
let trash_info = TrashInfo {
path: path.clone(),
deletion_date: now,
deleted_path: trash_file_path.clone(),
info_path: trash_info_path.clone(),
};
{
let trash_info_file = File::create(trash_info_path)?;
trash_info.write(&trash_info_file)?;
}
let result = fs::rename(&path, &trash_file_path);
if result.is_err() {
fs::copy(&path, &trash_file_path)?;
}
Ok(())
}

33
src/ops/empty.rs Normal file
View file

@ -0,0 +1,33 @@
use std::fs;
use anyhow::Error;
use chrono::{Duration, Local};
use crate::TrashDir;
use crate::TrashInfo;
pub fn empty(dry: bool, days: Option<u32>) -> Result<(), Error> {
let home_trash = TrashDir::get_home_trash();
let cutoff = if let Some(days) = days {
Local::now() - Duration::days(days.into())
} else {
Local::now()
};
for file in home_trash.iter()? {
let file = file?;
// ignore files that were deleted after the cutoff (younger)
let ignore = file.deletion_date > cutoff;
if !ignore {
if dry {
println!("{:?}", file.path);
} else {
fs::remove_file(file.info_path)?;
fs::remove_file(file.deleted_path)?;
}
}
}
Ok(())
}

5
src/ops/mod.rs Normal file
View file

@ -0,0 +1,5 @@
mod empty;
mod put;
pub use self::empty::empty;
pub use self::put::put;

224
src/ops/put.rs Normal file
View file

@ -0,0 +1,224 @@
use std::env;
use std::fs::{self, File};
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use anyhow::Result;
use chrono::{Duration, Local};
use crate::utils;
use crate::TrashDir;
use crate::TrashInfo;
use crate::{HOME_MOUNT, HOME_TRASH, MOUNTS};
#[derive(Debug, Error)]
pub enum Error {
#[error("Refusing to remove '.' or '..', skipping...")]
CannotTrashDotDirs,
}
pub fn put(paths: Vec<PathBuf>, recursive: bool) -> Result<()> {
// println!("HOME MOUNT: {:?}", *HOME_MOUNT);
let strategy = DeletionStrategy::Copy;
for path in paths {
println!(
"PATH: {:?}, MOUNTPOINT: {:?}",
&path,
MOUNTS.get_mount_point(&path)
);
if let Err(err) = strategy.delete(path) {
eprintln!("{:?}", err);
}
}
Ok(())
}
// fn put_single(path: impl AsRef<Path>, recursive: bool) -> Result<()> {
// let current_dir = env::current_dir()?;
// let path = path.as_ref().canonicalize()?;
// ensure!(
// path == current_dir
// || (current_dir.parent().is_some() && path == current_dir.parent().unwrap()),
// Error::CannotTrashDotDirs
// );
// // if path.is_dir() && !recursive {
// // error!("cannot trash directories without --recursive");
// // return Ok(());
// // }
// let now = Local::now();
// let elapsed = now.timestamp_millis();
// let home_trash = TrashDir::get_home_trash();
// let file_name = format!(
// "{}.{}",
// elapsed,
// path.file_name().unwrap().to_str().unwrap()
// );
// let trash_file_path = home_trash.files_dir()?.join(&file_name);
// let trash_info_path = home_trash.info_dir()?.join(file_name + ".trashinfo");
// let trash_info = TrashInfo {
// path: path.clone(),
// deletion_date: now,
// deleted_path: trash_file_path.clone(),
// info_path: trash_info_path.clone(),
// };
// {
// let trash_info_file = File::create(trash_info_path)?;
// trash_info.write(&trash_info_file)?;
// }
// let result = fs::rename(&path, &trash_file_path);
// if result.is_err() {
// fs::copy(&path, &trash_file_path)?;
// }
// Ok(())
// }
pub enum DeletionStrategy {
Copy,
Topdir,
TopdirOrCopy,
}
impl DeletionStrategy {
fn get_target_trash(
&self,
mount: impl AsRef<Path>,
path: impl AsRef<Path>,
) -> Option<(TrashDir, bool)> {
let mount = mount.as_ref();
let path = path.as_ref();
// first, are we on the home mount?
if mount == *HOME_MOUNT {
return Some((HOME_TRASH.clone(), false));
}
// are we just copying?
if let DeletionStrategy::Copy = self {
return Some((HOME_TRASH.clone(), true));
}
// try to use the $topdir/.Trash directory
let topdir_trash = mount.join(".Trash");
if self.should_use_topdir_trash(&topdir_trash) {
return Some((
TrashDir(topdir_trash.join(utils::get_uid().to_string())),
false,
));
}
// try to use the $topdir/.Trash-$uid directory
let topdir_trash_uid = mount.join(format!(".Trash-{}", utils::get_uid()));
if self.should_use_topdir_trash_uid(&topdir_trash_uid) {
return Some((TrashDir(topdir_trash_uid), false));
}
// do we have the copy option
if let DeletionStrategy::TopdirOrCopy = self {
return Some((HOME_TRASH.clone(), true));
}
None
}
fn should_use_topdir_trash(&self, path: impl AsRef<Path>) -> bool {
let path = path.as_ref();
if !path.exists() {
return false;
}
let dir = match File::open(path) {
Ok(file) => file,
Err(_) => return false,
};
let meta = match dir.metadata() {
Ok(meta) => meta,
Err(_) => return false,
};
if meta.file_type().is_symlink() {
return false;
}
let perms = meta.permissions();
perms.mode() & 0o1000 > 0
}
fn should_use_topdir_trash_uid(&self, path: impl AsRef<Path>) -> bool {
let path = path.as_ref();
if !path.exists() {
match fs::create_dir(path) {
Ok(_) => (),
Err(_) => return false,
};
}
return true;
}
pub fn delete(&self, target: impl AsRef<Path>) -> Result<()> {
let target = target.as_ref();
// don't allow deleting '.' or '..'
let current_dir = env::current_dir()?;
println!("target: {:?}", target);
println!("current_dir: {:?}", current_dir);
println!("current_dir parent: {:?}", current_dir.parent());
ensure!(
!(target == current_dir
|| (current_dir.parent().is_some() && target == current_dir.parent().unwrap())),
Error::CannotTrashDotDirs
);
let target_mount = MOUNTS
.get_mount_point(target)
.ok_or_else(|| anyhow!("couldn't get mount point"))?;
let (trash_dir, copy) = match self.get_target_trash(target_mount, target) {
Some(x) => x,
None => bail!("no trash dir could be selected, u suck"),
};
println!("Trash dir: {:?}", trash_dir);
println!("Copying?: {:?}", copy);
// preparing metadata
let now = Local::now();
let elapsed = now.timestamp_millis();
let file_name = format!(
"{}.{}",
elapsed,
target.file_name().unwrap().to_str().unwrap()
);
let trash_file_path = trash_dir.files_dir()?.join(&file_name);
let trash_info_path = trash_dir.info_dir()?.join(file_name + ".trashinfo");
let trash_info = TrashInfo {
path: target.to_path_buf(),
deletion_date: now,
deleted_path: trash_file_path.clone(),
info_path: trash_info_path.clone(),
};
{
let trash_info_file = File::create(trash_info_path)?;
trash_info.write(&trash_info_file)?;
}
// copy the file over
if copy {
utils::recursive_copy(&target, &trash_file_path)?;
fs::remove_dir_all(&target);
} else {
fs::rename(&target, &trash_file_path)?;
}
Ok(())
}
}

48
src/utils.rs Normal file
View file

@ -0,0 +1,48 @@
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
use anyhow::Error;
pub fn into_absolute(path: impl AsRef<Path>) -> Result<PathBuf, Error> {
let path = path.as_ref();
Ok(if !path.is_absolute() {
env::current_dir()?.canonicalize()?.join(path)
} else {
path.to_path_buf()
})
}
pub fn get_uid() -> u64 {
unsafe { libc::getuid().into() }
}
pub fn recursive_copy(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Error> {
let src = src.as_ref();
let dst = dst.as_ref();
for entry in WalkDir::new(src).contents_first(false).follow_links(false).same_file_system(true) {
let entry = entry?;
let path = entry.path();
let relative_path = path.strip_prefix(src)?;
// this must be the root
if let None = relative_path.file_name() {
fs::create_dir(dst);
continue;
}
let target_name = dst.join(relative_path);
if path.is_dir() {
fs::create_dir(&target_name);
} else {
fs::copy(path, &target_name);
}
println!("entry path: {:?}", relative_path);
println!("> copied to: {:?}", target_name);
}
Ok(())
}