mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
refactor: crates (#4258)
* chore: rename flowy-folder2 to flowy-folder * chore: rename flowy-document2 to flowy-document * chore: fix test * chore: move lib-infra crate * chore: remove shared-lib * chore: fix clippy
This commit is contained in:
21
frontend/rust-lib/lib-infra/Cargo.toml
Normal file
21
frontend/rust-lib/lib-infra/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "lib-infra"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
chrono = { workspace = true, default-features = false, features = ["clock"] }
|
||||
bytes = { version = "1.5" }
|
||||
pin-project = "1.1.3"
|
||||
futures-core = { version = "0.3" }
|
||||
tokio = { workspace = true, features = ["time", "rt"] }
|
||||
rand = "0.8.5"
|
||||
async-trait.workspace = true
|
||||
md5 = "0.7.0"
|
||||
anyhow.workspace = true
|
||||
walkdir = "2.4.0"
|
||||
zip = "0.6.6"
|
||||
tempfile = "3.8.1"
|
||||
validator = "0.16.0"
|
53
frontend/rust-lib/lib-infra/src/box_any.rs
Normal file
53
frontend/rust-lib/lib-infra/src/box_any.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use std::any::Any;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
pub struct BoxAny(Box<dyn Any + Send + Sync + 'static>);
|
||||
|
||||
impl BoxAny {
|
||||
pub fn new<T>(value: T) -> Self
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
Self(Box::new(value))
|
||||
}
|
||||
|
||||
pub fn unbox_or_default<T>(self) -> T
|
||||
where
|
||||
T: Default + 'static,
|
||||
{
|
||||
match self.0.downcast::<T>() {
|
||||
Ok(value) => *value,
|
||||
Err(_) => T::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unbox_or_error<T>(self) -> Result<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
match self.0.downcast::<T>() {
|
||||
Ok(value) => Ok(*value),
|
||||
Err(e) => Err(anyhow::anyhow!(
|
||||
"downcast error to {} failed: {:?}",
|
||||
std::any::type_name::<T>(),
|
||||
e
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unbox_or_none<T>(self) -> Option<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
match self.0.downcast::<T>() {
|
||||
Ok(value) => Some(*value),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
|
||||
self.0.downcast_ref()
|
||||
}
|
||||
}
|
156
frontend/rust-lib/lib-infra/src/file_util.rs
Normal file
156
frontend/rust-lib/lib-infra/src/file_util.rs
Normal file
@ -0,0 +1,156 @@
|
||||
use anyhow::Context;
|
||||
use std::cmp::Ordering;
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
use std::{fs, io};
|
||||
|
||||
use tempfile::tempdir;
|
||||
use walkdir::WalkDir;
|
||||
use zip::write::FileOptions;
|
||||
use zip::ZipArchive;
|
||||
use zip::ZipWriter;
|
||||
|
||||
pub fn copy_dir_recursive(src: &Path, dst: &Path) -> io::Result<()> {
|
||||
for entry in WalkDir::new(src).into_iter().filter_map(|e| e.ok()) {
|
||||
let path = entry.path();
|
||||
let relative_path = path.strip_prefix(src).unwrap();
|
||||
let target_path = dst.join(relative_path);
|
||||
|
||||
if path.is_dir() {
|
||||
fs::create_dir_all(&target_path)?;
|
||||
} else {
|
||||
fs::copy(path, target_path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn find_and_sort_folders_at<P>(path: &str, pat: P, order: Ordering) -> Vec<PathBuf>
|
||||
where
|
||||
P: Fn(&str) -> bool,
|
||||
{
|
||||
let mut folders = Vec::new();
|
||||
|
||||
for entry in WalkDir::new(path)
|
||||
.min_depth(1)
|
||||
.max_depth(1)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
{
|
||||
let entry_path = entry.path().to_path_buf();
|
||||
|
||||
if entry_path.is_dir()
|
||||
&& entry_path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_str()
|
||||
.map(&pat)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let metadata = fs::metadata(&entry_path).ok();
|
||||
let modified_time = metadata
|
||||
.and_then(|m| m.modified().ok())
|
||||
.unwrap_or(SystemTime::UNIX_EPOCH);
|
||||
|
||||
folders.push((entry_path, modified_time));
|
||||
}
|
||||
}
|
||||
|
||||
// Sort folders based on the specified order
|
||||
folders.sort_by(|a, b| match order {
|
||||
Ordering::Less => a.1.cmp(&b.1),
|
||||
Ordering::Greater => b.1.cmp(&a.1),
|
||||
_ => a.1.cmp(&b.1), // Default case
|
||||
});
|
||||
|
||||
// Extract just the PathBufs, discarding the modification times
|
||||
folders.into_iter().map(|(path, _)| path).collect()
|
||||
}
|
||||
|
||||
pub fn zip_folder(src_path: impl AsRef<Path>, dest_path: &Path) -> io::Result<()> {
|
||||
if !src_path.as_ref().exists() {
|
||||
return Err(io::ErrorKind::NotFound.into());
|
||||
}
|
||||
|
||||
if src_path.as_ref() == dest_path {
|
||||
return Err(io::ErrorKind::InvalidInput.into());
|
||||
}
|
||||
|
||||
let file = File::create(dest_path)?;
|
||||
let mut zip = ZipWriter::new(file);
|
||||
let options = FileOptions::default().compression_method(zip::CompressionMethod::Stored);
|
||||
|
||||
for entry in WalkDir::new(&src_path) {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
let name = match path.strip_prefix(&src_path) {
|
||||
Ok(n) => n,
|
||||
Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "Invalid path")),
|
||||
};
|
||||
|
||||
if path.is_file() {
|
||||
zip.start_file(
|
||||
name
|
||||
.to_str()
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid file name"))?,
|
||||
options,
|
||||
)?;
|
||||
let mut f = File::open(path)?;
|
||||
io::copy(&mut f, &mut zip)?;
|
||||
} else if !name.as_os_str().is_empty() {
|
||||
zip.add_directory(
|
||||
name
|
||||
.to_str()
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid directory name"))?,
|
||||
options,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
zip.finish()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unzip_and_replace(
|
||||
zip_path: impl AsRef<Path>,
|
||||
target_folder: &Path,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// Create a temporary directory for unzipping
|
||||
let temp_dir = tempdir()?;
|
||||
|
||||
// Unzip the file
|
||||
let file = File::open(zip_path.as_ref())
|
||||
.context(format!("Can't find the zip file: {:?}", zip_path.as_ref()))?;
|
||||
let mut archive = ZipArchive::new(file).context("Unzip file fail")?;
|
||||
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive.by_index(i)?;
|
||||
let outpath = temp_dir.path().join(file.mangled_name());
|
||||
|
||||
if file.name().ends_with('/') {
|
||||
fs::create_dir_all(&outpath)?;
|
||||
} else {
|
||||
if let Some(p) = outpath.parent() {
|
||||
if !p.exists() {
|
||||
fs::create_dir_all(p)?;
|
||||
}
|
||||
}
|
||||
let mut outfile = File::create(&outpath)?;
|
||||
io::copy(&mut file, &mut outfile)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the contents of the target folder
|
||||
if target_folder.exists() {
|
||||
fs::remove_dir_all(target_folder)
|
||||
.context(format!("Remove all files in {:?}", target_folder))?;
|
||||
}
|
||||
|
||||
fs::create_dir_all(target_folder)?;
|
||||
for entry in fs::read_dir(temp_dir.path())? {
|
||||
let entry = entry?;
|
||||
fs::rename(entry.path(), target_folder.join(entry.file_name()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
65
frontend/rust-lib/lib-infra/src/future.rs
Normal file
65
frontend/rust-lib/lib-infra/src/future.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use futures_core::future::BoxFuture;
|
||||
use futures_core::ready;
|
||||
use pin_project::pin_project;
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
pub fn to_fut<T, O>(f: T) -> Fut<O>
|
||||
where
|
||||
T: Future<Output = O> + Send + Sync + 'static,
|
||||
{
|
||||
Fut { fut: Box::pin(f) }
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
pub struct Fut<T> {
|
||||
#[pin]
|
||||
pub fut: Pin<Box<dyn Future<Output = T> + Sync + Send>>,
|
||||
}
|
||||
|
||||
impl<T> Future for Fut<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.as_mut().project();
|
||||
Poll::Ready(ready!(this.fut.poll(cx)))
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
pub struct FutureResult<T, E> {
|
||||
#[pin]
|
||||
pub fut: Pin<Box<dyn Future<Output = Result<T, E>> + Sync + Send>>,
|
||||
}
|
||||
|
||||
impl<T, E> FutureResult<T, E> {
|
||||
pub fn new<F>(f: F) -> Self
|
||||
where
|
||||
F: Future<Output = Result<T, E>> + Send + Sync + 'static,
|
||||
{
|
||||
Self { fut: Box::pin(f) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Future for FutureResult<T, E>
|
||||
where
|
||||
T: Send + Sync,
|
||||
E: Debug,
|
||||
{
|
||||
type Output = Result<T, E>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.as_mut().project();
|
||||
let result = ready!(this.fut.poll(cx));
|
||||
Poll::Ready(result)
|
||||
}
|
||||
}
|
||||
|
||||
pub type BoxResultFuture<'a, T, E> = BoxFuture<'a, Result<T, E>>;
|
8
frontend/rust-lib/lib-infra/src/lib.rs
Normal file
8
frontend/rust-lib/lib-infra/src/lib.rs
Normal file
@ -0,0 +1,8 @@
|
||||
pub use async_trait;
|
||||
|
||||
pub mod box_any;
|
||||
pub mod file_util;
|
||||
pub mod future;
|
||||
pub mod ref_map;
|
||||
pub mod util;
|
||||
pub mod validator_fn;
|
92
frontend/rust-lib/lib-infra/src/ref_map.rs
Normal file
92
frontend/rust-lib/lib-infra/src/ref_map.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use async_trait::async_trait;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[async_trait]
|
||||
pub trait RefCountValue {
|
||||
async fn did_remove(&self) {}
|
||||
}
|
||||
|
||||
struct RefCountHandler<T> {
|
||||
ref_count: usize,
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl<T> RefCountHandler<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self {
|
||||
ref_count: 1,
|
||||
inner,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increase_ref_count(&mut self) {
|
||||
self.ref_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RefCountHashMap<T>(HashMap<String, RefCountHandler<T>>);
|
||||
|
||||
impl<T> std::default::Default for RefCountHashMap<T> {
|
||||
fn default() -> Self {
|
||||
Self(HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RefCountHashMap<T>
|
||||
where
|
||||
T: Clone + Send + Sync + RefCountValue + 'static,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &str) -> Option<T> {
|
||||
self.0.get(key).map(|handler| handler.inner.clone())
|
||||
}
|
||||
|
||||
pub fn values(&self) -> Vec<T> {
|
||||
self
|
||||
.0
|
||||
.values()
|
||||
.map(|value| value.inner.clone())
|
||||
.collect::<Vec<T>>()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: String, value: T) {
|
||||
if let Some(handler) = self.0.get_mut(&key) {
|
||||
handler.increase_ref_count();
|
||||
} else {
|
||||
let handler = RefCountHandler::new(value);
|
||||
self.0.insert(key, handler);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove(&mut self, key: &str) {
|
||||
let mut should_remove = false;
|
||||
if let Some(value) = self.0.get_mut(key) {
|
||||
if value.ref_count > 0 {
|
||||
value.ref_count -= 1;
|
||||
}
|
||||
should_remove = value.ref_count == 0;
|
||||
}
|
||||
|
||||
if should_remove {
|
||||
if let Some(handler) = self.0.remove(key) {
|
||||
tokio::spawn(async move {
|
||||
handler.inner.did_remove().await;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T> RefCountValue for Arc<T>
|
||||
where
|
||||
T: RefCountValue + Sync + Send,
|
||||
{
|
||||
async fn did_remove(&self) {
|
||||
(**self).did_remove().await
|
||||
}
|
||||
}
|
38
frontend/rust-lib/lib-infra/src/util.rs
Normal file
38
frontend/rust-lib/lib-infra/src/util.rs
Normal file
@ -0,0 +1,38 @@
|
||||
pub fn move_vec_element<T, F>(
|
||||
vec: &mut Vec<T>,
|
||||
filter: F,
|
||||
_from_index: usize,
|
||||
to_index: usize,
|
||||
) -> Result<bool, String>
|
||||
where
|
||||
F: FnMut(&T) -> bool,
|
||||
{
|
||||
match vec.iter().position(filter) {
|
||||
None => Ok(false),
|
||||
Some(index) => {
|
||||
if vec.len() > to_index {
|
||||
let removed_element = vec.remove(index);
|
||||
vec.insert(to_index, removed_element);
|
||||
Ok(true)
|
||||
} else {
|
||||
let msg = format!(
|
||||
"Move element to invalid index: {}, current len: {}",
|
||||
to_index,
|
||||
vec.len()
|
||||
);
|
||||
Err(msg)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn timestamp() -> i64 {
|
||||
chrono::Utc::now().timestamp()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn md5<T: AsRef<[u8]>>(data: T) -> String {
|
||||
let md5 = format!("{:x}", md5::compute(data));
|
||||
md5
|
||||
}
|
8
frontend/rust-lib/lib-infra/src/validator_fn.rs
Normal file
8
frontend/rust-lib/lib-infra/src/validator_fn.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use validator::ValidationError;
|
||||
|
||||
pub fn required_not_empty_str(s: &str) -> Result<(), ValidationError> {
|
||||
if s.is_empty() {
|
||||
return Err(ValidationError::new("should not be empty string"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
Reference in New Issue
Block a user