diff --git a/Cargo.toml b/Cargo.toml index e0c03202b..dec0c59e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,15 +57,15 @@ sha1 = "0.10.6" futures = "0.3.30" futures-util = "0.3.30" go-defer = "0.1.0" -russh = "0.45.0" -russh-keys = "0.45.0" +russh = "0.46.0" +russh-keys = "0.46.0" axum = "0.7.7" axum-extra = "0.9.4" axum-server = "0.7.1" tower-http = "0.6.1" tower = "0.5.1" hex = "0.4.3" -sea-orm = "1.1.0" +sea-orm = "1.1.1" flate2 = "1.0.34" bstr = "1.10.0" colored = "2.1.0" diff --git a/ceres/src/lfs/handler.rs b/ceres/src/lfs/handler.rs index d2cc861da..c72474071 100644 --- a/ceres/src/lfs/handler.rs +++ b/ceres/src/lfs/handler.rs @@ -422,6 +422,46 @@ pub async fn lfs_download_object( } } +/// Download a chunk from a large object. +/// It's used when server didn't have splited chunk, but client request a chunk. +/// If the server enable split, then the chunk must be a splited chunk, rather than a random part of the object. +pub async fn lfs_download_chunk( + context: Context, + origin_oid: &str, + chunk_oid: &String, + offset: u64, + size: u64, +) -> Result { + let config = context.config.lfs; + let stg = context.services.lfs_db_storage.clone(); + let lfs_stg = context.services.lfs_storage.clone(); + + // check if the chunk is already exist. + if config.enable_split { + let relations = stg.get_lfs_relations(origin_oid.to_owned()).await.unwrap(); + let chunk_relation = relations.iter().find(|r| &r.sub_oid == chunk_oid); + if chunk_relation.is_none() { + return Err(GitLFSError::GeneralError( + "Chunk not found in split object".to_string(), + )); + } + let chunk = lfs_stg.get_object(chunk_oid).await.unwrap(); + Ok(chunk) + } else { + // return part of the original object. + let bytes = lfs_stg.get_object(origin_oid).await.unwrap(); + let chunk_bytes = bytes[offset as usize..(offset + size) as usize].to_vec(); + // check hash + let chunk_hash = hex::encode(ring::digest::digest(&ring::digest::SHA256, &chunk_bytes)); + if chunk_hash != *chunk_oid { + return Err(GitLFSError::GeneralError( + "Chunk hash not match".to_string(), + )); + } + Ok(Bytes::from(chunk_bytes)) + } +} + pub async fn represent( rv: &RequestVars, meta: &MetaObject, diff --git a/common/src/config.rs b/common/src/config.rs index 71054f0cd..72605388f 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -265,7 +265,7 @@ impl Default for LFSConfig { url: "http://localhost:8000".to_string(), lfs_obj_local_path: PathBuf::from("/tmp/.mega/lfs"), enable_split: true, - split_size: 1024 * 1024 * 1024, + split_size: 20 * 1024 * 1024, // 20MB } } } diff --git a/libra/Cargo.toml b/libra/Cargo.toml index 1911f6731..70071d08d 100644 --- a/libra/Cargo.toml +++ b/libra/Cargo.toml @@ -10,6 +10,7 @@ path = "src/main.rs" [dependencies] mercury = { workspace = true } ceres = { workspace = true } +gemini = { workspace = true } sea-orm = { workspace = true, features = [ "sqlx-sqlite", diff --git a/libra/src/internal/protocol/lfs_client.rs b/libra/src/internal/protocol/lfs_client.rs index de38ba84b..8b7105403 100644 --- a/libra/src/internal/protocol/lfs_client.rs +++ b/libra/src/internal/protocol/lfs_client.rs @@ -3,7 +3,7 @@ use crate::internal::config::Config; use crate::internal::protocol::https_client::BasicAuth; use crate::internal::protocol::ProtocolClient; use crate::utils::lfs; -use ceres::lfs::lfs_structs::{BatchRequest, ChunkRepresentation, FetchchunkResponse, LockList, LockListQuery, LockRequest, Ref, Representation, RequestVars, UnlockRequest, VerifiableLockList, VerifiableLockRequest}; +use ceres::lfs::lfs_structs::{BatchRequest, ChunkRepresentation, FetchchunkResponse, LockList, LockListQuery, LockRequest, ObjectError, Ref, Representation, RequestVars, UnlockRequest, VerifiableLockList, VerifiableLockRequest}; use futures_util::StreamExt; use mercury::internal::object::types::ObjectType; use mercury::internal::pack::entry::Entry; @@ -22,6 +22,7 @@ pub struct LFSClient { pub batch_url: Url, pub lfs_url: Url, pub client: Client, + pub bootstrap: Option<(String, u16)> // for p2p: (bootstrap_node, ztm_agent_port) } static LFS_CLIENT: OnceCell = OnceCell::const_new(); impl LFSClient { @@ -58,6 +59,7 @@ impl ProtocolClient for LFSClient { batch_url: lfs_server.join("objects/batch").unwrap(), lfs_url: lfs_server, client, + bootstrap: None, } } } @@ -72,6 +74,21 @@ impl LFSClient { } } + // TODO add one method that both support Server & P2P + /// Only for p2p + pub fn from_bootstrap_node(bootstrap_node: &str, ztm_agent_port: u16) -> Self { + let client = Client::builder() + .default_headers(lfs::LFS_HEADERS.clone()) + .build() + .unwrap(); + Self { + batch_url: Url::parse("https://invalid.com").unwrap(), + lfs_url: Url::parse("https://invalid.com").unwrap(), + client, + bootstrap: Some((bootstrap_node.to_string(), ztm_agent_port)), + } + } + /// push LFS objects to remote server pub async fn push_objects<'a, I>(&self, objs: I, auth: Option) -> Result<(), ()> where @@ -171,14 +188,53 @@ impl LFSClient { // TODO: parallel upload for obj in resp.objects { - self.upload_object(obj).await?; + let file_path = lfs::lfs_object_path(&obj.oid); + self.upload_object(obj, &file_path).await?; } println!("LFS objects push completed."); Ok(()) } + /// push LFS object to remote server, didn't need local lfs storage + pub async fn push_object(&self, oid: &str, file: &Path) -> Result<(), ()> { + let batch_request = BatchRequest { + operation: "upload".to_string(), + transfers: vec![lfs::LFS_TRANSFER_API.to_string()], + objects: vec![RequestVars { + oid: oid.to_owned(), + size: file.metadata().unwrap().len() as i64, + ..Default::default() + }], + hash_algo: lfs::LFS_HASH_ALGO.to_string(), + }; + + let request = self + .client + .post(self.batch_url.clone()) + .json(&batch_request) + .headers(lfs::LFS_HEADERS.clone()); + + let response = request.send().await.unwrap(); + + let resp = response.json::().await.unwrap(); + tracing::debug!( + "LFS push response:\n {:#?}", + serde_json::to_value(&resp).unwrap() + ); + assert!( + resp.objects.len() == 1, + "fatal: LFS push failed. No object found." + ); + + // self.upload_object(resp.objects).await?; + let obj = resp.objects.into_iter().next().unwrap(); + self.upload_object(obj, file).await?; + println!("LFS objects push completed."); + Ok(()) + } + /// upload (PUT) one LFS file to remote server - async fn upload_object(&self, object: Representation) -> Result<(), ()> { + async fn upload_object(&self, object: Representation, file: &Path) -> Result<(), ()> { if let Some(err) = object.error { eprintln!("fatal: LFS upload failed. Code: {}, Message: {}", err.code, err.message); return Err(()); @@ -197,11 +253,12 @@ impl LFSClient { request = request.header(k, v); } - let file_path = lfs::lfs_object_path(&object.oid); - let file = tokio::fs::File::open(file_path).await.unwrap(); + let content = tokio::fs::File::open(file).await.unwrap(); println!("Uploading LFS file: {}", object.oid); let resp = request - .body(reqwest::Body::wrap_stream(tokio_util::io::ReaderStream::new(file))) + .body(reqwest::Body::wrap_stream( + tokio_util::io::ReaderStream::new(content), + )) .send() .await .unwrap(); @@ -261,8 +318,18 @@ impl LFSClient { let text = response.text().await?; tracing::debug!("LFS download response:\n {:#?}", serde_json::from_str::(&text)?); let resp = serde_json::from_str::(&text)?; + let obj = resp.objects.first().expect("No object"); // Only get first + if obj.error.is_some() || obj.actions.is_none() { + let unknown_err = ObjectError { + code: 0, + message: "Unknown error".to_string(), + }; + let err = obj.error.as_ref().unwrap_or(&unknown_err); + eprintln!("fatal: LFS download failed (BatchRequest). Code: {}, Message: {}", err.code, err.message); + return Err(anyhow!("LFS download failed.")); + } - let link = resp.objects[0].actions.as_ref().unwrap().get("download").unwrap(); + let link = obj.actions.as_ref().unwrap().get("download").unwrap(); let mut is_chunked = false; // Chunk API @@ -361,6 +428,125 @@ impl LFSClient { } } + #[allow(clippy::type_complexity)] + /// download (GET) one LFS file peer-to-peer + pub async fn download_object_p2p( + &self, + file_uri: &str, // p2p protocol + path: impl AsRef, + mut reporter: Option<( + &mut (dyn FnMut(f64) -> anyhow::Result<()> + Send), // progress callback + f64 // step + )>) -> anyhow::Result<()> + { + let (bootstrap_node, ztm_agent_port) = match &self.bootstrap { + Some(value) => value, + None => return Err(anyhow!("fatal: No bootstrap node set for P2P download.")), + }; + + let hash = gemini::lfs::get_file_hash_from_origin(file_uri.to_owned()).unwrap(); + tracing::info!("Downloading LFS file: {}", hash); + let peer_ports = gemini::lfs::create_lfs_download_tunnel( + bootstrap_node.clone(), + *ztm_agent_port, + file_uri.to_owned() + ).await.unwrap(); + if peer_ports.is_empty() { + eprintln!("fatal: No peer online, download failed"); + return Err(anyhow!("fatal: No peer online.")); + } + tracing::debug!("P2P download tunnel ports: {:?}", peer_ports); + + let lfs_info = match gemini::lfs::get_lfs_chunks_info(bootstrap_node.clone(), hash.clone()).await { + Some(chunks) => chunks, + None => return Err(anyhow!("fatal: LFS Chunk API failed.")) + }; + let mut chunks = lfs_info.chunks; + if chunks.is_empty() { + eprintln!("fatal: LFS Chunk API failed. No chunks found."); + return Err(anyhow!("fatal: No chunks found.")); + } + chunks.sort_by(|a, b| a.offset.cmp(&b.offset)); + tracing::debug!("LFS chunks: {:?}", chunks.len()); + + // infer that all chunks share same size! (except last one) + let chunk_size = chunks.first().unwrap().size as usize; + let mut checksum = Context::new(&SHA256); + let mut file = tokio::fs::File::create(path).await?; + for (i, chunk) in chunks.iter().enumerate() { // TODO parallel download + println!("- part: {}/{}", i + 1, chunks.len()); + let mut retry = 0; + let data = loop { // retry + let mut downloaded = i * chunk_size; // TODO support resume + let mut last_progress = downloaded as f64 / lfs_info.size as f64 * 100.0; + let url = format!("http://localhost:{}/objects/{}/{}", peer_ports[(i + retry) % peer_ports.len()], hash, chunk.sub_oid); + let data = self.download_chunk(&url, &chunk.sub_oid, chunk.size as usize, chunk.offset as usize, |size| { + if let Some((ref mut report_fn, step)) = reporter { + downloaded += size; + let progress = (downloaded as f64 / lfs_info.size as f64) * 100.0; + if progress >= last_progress + step { + last_progress = progress; + report_fn(progress).unwrap(); + } + } + }).await; + match data { + Ok(data) => break data, + Err(e) => { + eprintln!("fatal: LFS download failed. Error: {}. Retry", e); + retry += 1; + if retry > 5 { + eprintln!("fatal: LFS download failed. Retry limit exceeded."); + return Err(anyhow!("LFS download failed.")); + } + } + } + }; + checksum.update(&data); + file.write_all(&data).await?; + } + let checksum = hex::encode(checksum.finish().as_ref()); + if checksum == hash { + println!("Downloaded(p2p)."); + Ok(()) + } else { + eprintln!("fatal: LFS download failed. Checksum mismatch: {} != {}. Fallback to pointer file.", checksum, hash); + file.set_len(0).await?; // clear + file.rewind().await?; // == seek(0) + let pointer = lfs::format_pointer_string(&hash, lfs_info.size as u64); + file.write_all(pointer.as_bytes()).await?; + Err(anyhow!("Checksum mismatch, fallback to pointer file.")) + } + } + + async fn download_chunk(&self, url: &str, hash: &str, size: usize, offset: usize, mut callback: impl FnMut(usize)) -> anyhow::Result> { + let response = self.client + .get(url) + .query(&[("offset", offset), ("size", size)]) + .send().await?; + if !response.status().is_success() { + eprintln!("fatal: LFS download failed. Status: {}, Message: {}", response.status(), response.text().await?); + return Err(anyhow!("LFS download failed.")); + } + let mut buffer = Vec::with_capacity(size); + let mut checksum = Context::new(&SHA256); + let mut stream = response.bytes_stream(); + while let Some(chunk) = stream.next().await { + let chunk = chunk?; + buffer.write_all(&chunk).await?; + checksum.update(&chunk); + + // report progress + callback(chunk.len()); + } + let checksum = hex::encode(checksum.finish().as_ref()); + if checksum != hash { + eprintln!("fatal: chunk download failed. Chunk checksum mismatch: {} != {}", checksum, hash); + return Err(anyhow!("Chunk checksum mismatch.")); + } + Ok(buffer) + } + /// Only for MonoRepo (mega) async fn fetch_chunks(&self, obj_link: &str) -> Result, ()> { let mut url = Url::parse(obj_link).unwrap(); @@ -472,6 +658,10 @@ impl LFSClient { #[cfg(test)] mod tests { + use std::path::PathBuf; + + use crate::utils; + use super::*; #[test] fn test_request_vars() { @@ -506,4 +696,37 @@ mod tests { println!("Text {:?}", text); let _resp = serde_json::from_str::(&text).unwrap(); } -} \ No newline at end of file + + #[tokio::test] + #[ignore] // need to start local mega server + async fn test_push_object() { + let client = LFSClient::from_url(&Url::parse("http://localhost:8000").unwrap()); + let file = + PathBuf::from("../tests/data/packs/git-2d187177923cd618a75da6c6db45bb89d92bd504.pack"); + let oid = utils::lfs::calc_lfs_file_hash(&file).unwrap(); + + match client.push_object(&oid, &file).await { + Ok(_) => println!("Pushed successfully."), + Err(err) => eprintln!("Push failed: {:?}", err), + } + } + + #[tokio::test] + #[ignore] // need to start local mega server + async fn test_download_chunk() { + let client = LFSClient::from_url(&Url::parse("http://localhost:8000").unwrap()); + let file = + PathBuf::from("../tests/data/packs/git-2d187177923cd618a75da6c6db45bb89d92bd504.pack"); + let oid = utils::lfs::calc_lfs_file_hash(&file).unwrap(); + let sub_oid = + "ee225720cc31599c749fbe9b18f6c8346fa3246839f0dea7ffd3224dbb067952".to_string(); // offset 83886080 size 20971520 + let url = format!("http://localhost:8000/objects/{}/{}", oid, sub_oid); + let size = 20971520; + let offset = 83886080; + let data = client + .download_chunk(&url, &sub_oid, size, offset, |_| {}) + .await + .unwrap(); + assert_eq!(data.len(), size); + } +} diff --git a/libra/src/lib.rs b/libra/src/lib.rs index 7e06d37fb..65937a947 100644 --- a/libra/src/lib.rs +++ b/libra/src/lib.rs @@ -2,7 +2,7 @@ use mercury::errors::GitError; mod command; pub mod internal; -mod utils; +pub mod utils; pub mod cli; /// Execute the Libra command in `sync` way. diff --git a/libra/src/main.rs b/libra/src/main.rs index 83518c1b7..7b000adb6 100644 --- a/libra/src/main.rs +++ b/libra/src/main.rs @@ -1,12 +1,8 @@ //! This is the main entry point for the Libra. +use libra::cli; use mercury::errors::GitError; -mod command; -mod internal; -mod utils; -mod cli; - fn main() { #[cfg(debug_assertions)] { diff --git a/libra/src/utils/mod.rs b/libra/src/utils/mod.rs index 5427ad3f1..21d01c6b6 100644 --- a/libra/src/utils/mod.rs +++ b/libra/src/utils/mod.rs @@ -4,4 +4,4 @@ pub(crate) mod path; pub(crate) mod object_ext; pub(crate) mod path_ext; pub(crate) mod client_storage; -pub(crate) mod lfs; \ No newline at end of file +pub mod lfs; \ No newline at end of file diff --git a/lunar/src-tauri/gen/schemas/desktop-schema.json b/lunar/src-tauri/gen/schemas/desktop-schema.json index 875195533..19a86b0e9 100644 --- a/lunar/src-tauri/gen/schemas/desktop-schema.json +++ b/lunar/src-tauri/gen/schemas/desktop-schema.json @@ -37,7 +37,7 @@ ], "definitions": { "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```", "type": "object", "required": [ "identifier", diff --git a/lunar/src-tauri/gen/schemas/linux-schema.json b/lunar/src-tauri/gen/schemas/linux-schema.json index 875195533..19a86b0e9 100644 --- a/lunar/src-tauri/gen/schemas/linux-schema.json +++ b/lunar/src-tauri/gen/schemas/linux-schema.json @@ -37,7 +37,7 @@ ], "definitions": { "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```", "type": "object", "required": [ "identifier", diff --git a/mono/src/api/lfs/lfs_router.rs b/mono/src/api/lfs/lfs_router.rs index b62bbd8a4..69d5cee66 100644 --- a/mono/src/api/lfs/lfs_router.rs +++ b/mono/src/api/lfs/lfs_router.rs @@ -41,6 +41,8 @@ //! //! Ensure proper authentication and authorization mechanisms are implemented //! when using these handlers in a web application to prevent unauthorized access. +use std::collections::HashMap; + use axum::{ body::Body, extract::{Path, Query, State}, @@ -69,6 +71,7 @@ const LFS_CONTENT_TYPE: &str = "application/vnd.git-lfs+json"; pub fn routers() -> Router { Router::new() .route("/objects/:object_id", get(lfs_download_object)) + .route("/objects/:object_id/:chunk_id", get(lfs_download_chunk)) .route("/objects/:object_id", put(lfs_upload_object)) .route("/locks", get(list_locks)) .route("/locks", post(create_lock)) @@ -252,6 +255,45 @@ pub async fn lfs_download_object( } } +pub async fn lfs_download_chunk( + state: State, + Path((origin_object_id, chunk_id)): Path<(String, String)>, + Query(query_params): Query>, +) -> Result { + let offset = query_params + .get("offset") + .and_then(|offset| offset.parse::().ok()); + let size = query_params + .get("size") + .and_then(|size| size.parse::().ok()); + if offset.is_none() || size.is_none() { + return Err(( + StatusCode::BAD_REQUEST, + "Valid offset and size query parameters are required".to_string(), + )); + } + let result = handler::lfs_download_chunk( + state.context.clone(), + &origin_object_id, + &chunk_id, + offset.unwrap(), + size.unwrap(), + ) + .await; + match result { + Ok(bytes) => Ok(Response::builder() + .header("Content-Type", LFS_CONTENT_TYPE) + .body(Body::from(bytes)) + .unwrap()), + Err(err) => Ok({ + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::from(format!("Error: {}", err))) + .unwrap() + }), + } +} + pub async fn lfs_upload_object( state: State, Path(oid): Path, diff --git a/mono/src/server/ssh_server.rs b/mono/src/server/ssh_server.rs index 710dbd044..04c9f77e2 100644 --- a/mono/src/server/ssh_server.rs +++ b/mono/src/server/ssh_server.rs @@ -80,7 +80,7 @@ pub fn load_key() -> KeyPair { KeyPair::Ed25519(keypair) } else { // generate a keypair if not exists - let keys = KeyPair::generate_ed25519().unwrap(); + let keys = KeyPair::generate_ed25519(); if let KeyPair::Ed25519(inner_pair) = &keys { let secret = serde_json::json!({ "secret_key": *inner_pair.to_pkcs8_pem(LineEnding::CR).unwrap() diff --git a/moon/src/app/(dashboard)/blob/[...path]/page.tsx b/moon/src/app/(dashboard)/blob/[...path]/page.tsx index 6efa76f90..c68309222 100644 --- a/moon/src/app/(dashboard)/blob/[...path]/page.tsx +++ b/moon/src/app/(dashboard)/blob/[...path]/page.tsx @@ -51,7 +51,7 @@ export default function BlobPage({ params }: { params: Params }) {
- + {/* */} diff --git a/moon/src/app/(dashboard)/page.tsx b/moon/src/app/(dashboard)/page.tsx index 8defa5a21..b87d9ce04 100644 --- a/moon/src/app/(dashboard)/page.tsx +++ b/moon/src/app/(dashboard)/page.tsx @@ -1,9 +1,26 @@ +'use client' + import CodeTable from '@/components/CodeTable' -export const revalidate = 0 +import { useEffect, useState } from 'react'; + +export default function HomePage() { + const [directory, setDirectory] = useState([]); + const [readmeContent, setReadmeContent] = useState(""); + + const fetchData = async () => { + try { + let directory = await getDirectory("/"); + setDirectory(directory); + let readmeContent = await getReadmeContent("/", directory); + setReadmeContent(readmeContent); + } catch (error) { + console.error('Error fetching data:', error); + } + }; -export default async function HomePage() { - let directory = await getDirectory() - let readmeContent = await getReadmeContent(directory) + useEffect(() => { + fetchData(); + }, []); return (
@@ -11,27 +28,22 @@ export default async function HomePage() {
); } - -async function getDirectory() { - const res = await fetch(`http://localhost:3000/api/tree/commit-info?path=/`); - +async function getDirectory(pathname: string) { + const res = await fetch(`/api/tree/commit-info?path=${pathname}`); const response = await res.json(); const directory = response.data.data; - return directory } -async function getReadmeContent(directory) { - let readmeContent = ''; - +async function getReadmeContent(pathname, directory) { + var readmeContent = ''; for (const project of directory || []) { if (project.name === 'README.md' && project.content_type === 'file') { - const res = await fetch(`http://localhost:3000/api/blob?path=/README.md`); + const res = await fetch(`/api/blob?path=${pathname}/README.md`); const response = await res.json(); readmeContent = response.data.data; break; } } - return readmeContent } diff --git a/moon/src/app/(dashboard)/tree/[...path]/page.tsx b/moon/src/app/(dashboard)/tree/[...path]/page.tsx index 45453d0aa..f7a80ae0f 100644 --- a/moon/src/app/(dashboard)/tree/[...path]/page.tsx +++ b/moon/src/app/(dashboard)/tree/[...path]/page.tsx @@ -23,7 +23,7 @@ export default function Page({ params }: { params: Params }) { try { let directory = await getDirectory(new_path); setDirectory(directory); - let readmeContent = await getReadmeContent(path, directory); + let readmeContent = await getReadmeContent(new_path, directory); setReadmeContent(readmeContent); let shown_clone_btn = await pathCanClone(new_path); setCloneBtn(shown_clone_btn); @@ -34,7 +34,7 @@ export default function Page({ params }: { params: Params }) { } }; fetchData(); - }, [path]); + }, [new_path]); const treeStyle = { borderRadius: 8, diff --git a/moon/src/components/BreadCrumb.tsx b/moon/src/components/BreadCrumb.tsx index 4218bddc5..b021353b5 100644 --- a/moon/src/components/BreadCrumb.tsx +++ b/moon/src/components/BreadCrumb.tsx @@ -1,10 +1,8 @@ import 'github-markdown-css/github-markdown-light.css' -import { useRouter } from 'next/navigation' import { Breadcrumb } from 'antd/lib' import styles from './BreadCrumb.module.css' const Bread = ({ path }) => { - const router = useRouter(); const breadCrumbItems = path.map((sub_path, index) => { if (index == path.length - 1) { return { diff --git a/moon/src/components/CodeTable.tsx b/moon/src/components/CodeTable.tsx index 266ebeb80..22f53f394 100644 --- a/moon/src/components/CodeTable.tsx +++ b/moon/src/components/CodeTable.tsx @@ -92,7 +92,8 @@ const CodeTable = ({ directory, readmeContent }) => {
{ return { onClick: (event) => { handleRowClick(record) } diff --git a/neptune/src/lib.rs b/neptune/src/lib.rs index 3fb51e93b..115c912c3 100644 --- a/neptune/src/lib.rs +++ b/neptune/src/lib.rs @@ -22,7 +22,8 @@ pub fn start_agent(database: &str, listen_port: u16) { CString::new("--args").unwrap(), CString::new("--data").unwrap(), CString::new(database).unwrap(), - // CString::new(format!("--listen 0.0.0.0:{}", listen_port)).unwrap(), + CString::new("--listen").unwrap(), + CString::new(format!("127.0.0.1:{}", listen_port)).unwrap(), ]; let c_args: Vec<*const c_char> = args.iter().map(|arg| arg.as_ptr()).collect(); unsafe { @@ -40,7 +41,8 @@ pub fn start_hub(listen_port: u16, name: Vec, _ca: &str) { CString::new("ztm-pipy").unwrap(), CString::new("repo://ztm/hub").unwrap(), CString::new("--args").unwrap(), - // CString::new(format!("--listen=0.0.0.0:{}", listen_port)).unwrap(), + CString::new("--listen").unwrap(), + CString::new(format!("127.0.0.1:{}", listen_port)).unwrap(), // CString::new(format!("--ca={}", ca)).unwrap(), ]; let c_args: Vec<*const c_char> = args.iter().map(|arg| arg.as_ptr()).collect(); @@ -59,6 +61,7 @@ pub fn exit_ztm() { #[cfg(test)] mod tests { use super::*; + #[tokio::test] async fn test_start_agent() { let port = 7776; diff --git a/scorpio/Cargo.toml b/scorpio/Cargo.toml index e840378a9..2fcac108d 100644 --- a/scorpio/Cargo.toml +++ b/scorpio/Cargo.toml @@ -37,3 +37,8 @@ async-io = [] [workspace] +[package.metadata.docs.rs] +all-features = true +targets = [ + "x86_64-unknown-linux-gnu", +] diff --git a/scorpio/diff.log b/scorpio/diff.log new file mode 100644 index 000000000..c4ae7620b --- /dev/null +++ b/scorpio/diff.log @@ -0,0 +1,2243 @@ +2d1 +< // 2024 From [fuse_backend_rs](https://github.com/cloud-hypervisor/fuse-backend-rs) +7a7 +> pub mod sync_io; +9,10d8 +< mod async_io; +< mod layer; +12,13d9 +< +< mod tempfile; +16,36c12,31 +< use std::ffi::OsStr; +< use std::future::Future; +< use std::io::{Error, ErrorKind, Result}; +< +< use std::sync::{Arc, Weak}; +< use config::Config; +< use fuse3::raw::reply::{DirectoryEntry, DirectoryEntryPlus, ReplyAttr, ReplyEntry, ReplyOpen, ReplyStatFs}; +< use fuse3::raw::{Filesystem, Request}; +< +< +< use fuse3::{mode_from_kind_and_perm, Errno, FileType}; +< use fuse_backend_rs::api::{SLASH_ASCII, VFS_MAX_INO}; +< use futures::future::join_all; +< use futures::stream::iter; +< use futures::StreamExt; +< use inode_store::InodeStore; +< use layer::Layer; +< +< use tokio::sync::{Mutex, RwLock}; +< use crate::passthrough::PassthroughFs; +< use crate::util::atomic::*; +--- +> use std::ffi::{CStr, CString}; +> use std::fs::File; +> use std::io::{Error, ErrorKind, Result, Seek, SeekFrom}; +> use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +> use std::sync::{Arc, Mutex, RwLock, Weak}; +> +> use crate::abi::fuse_abi::{stat64, statvfs64, CreateIn, ROOT_ID as FUSE_ROOT_ID}; +> use crate::api::filesystem::{ +> Context, DirEntry, Entry, Layer, OpenOptions, ZeroCopyReader, ZeroCopyWriter, +> }; +> #[cfg(not(feature = "async-io"))] +> use crate::api::BackendFileSystem; +> use crate::api::{SLASH_ASCII, VFS_MAX_INO}; +> +> use crate::common::file_buf::FileVolatileSlice; +> use crate::common::file_traits::FileReadWriteVolatile; +> use vmm_sys_util::tempfile::TempFile; +> +> use self::config::Config; +> use self::inode_store::InodeStore; +39a35,38 +> pub const MAXNAMELEN: usize = 256; +> pub const CURRENT_DIR: &str = "."; +> pub const PARENT_DIR: &str = ".."; +> pub const MAXBUFSIZE: usize = 1 << 20; +41d39 +< type BoxedLayer = PassthroughFs; +43c41,42 +< const INODE_ALLOC_BATCH:u64 = 0x1_0000_0000; +--- +> pub type BoxedLayer = Box + Send + Sync>; +> +47d45 +< #[derive(Clone)] +49c47 +< pub layer: Arc, +--- +> pub layer: Arc, +56c54 +< pub stat: Option, +--- +> pub stat: Option, +79d76 +< #[allow(unused)] +88,89c85,86 +< lower_layers: Vec>, +< upper_layer: Option>, +--- +> lower_layers: Vec>, +> upper_layer: Option>, +100d96 +< root_inodes: u64, +104c100 +< layer: Arc, +--- +> layer: Arc, +121,122c117,118 +< async fn new( +< layer: Arc, +--- +> fn new( +> layer: Arc, +136c132 +< match ri.stat64_ignore_enoent(&Request::default()).await { +--- +> match ri.stat64_ignore_enoent(&Context::default()) { +147c143 +< async fn stat64(&self, req: &Request) -> Result { +--- +> fn stat64(&self, ctx: &Context) -> Result { +152c148,152 +< layer.getattr(*req, self.inode, None, 0).await.map_err(|e| e.into()) +--- +> +> match layer.getattr(ctx, self.inode, None) { +> Ok((v1, _v2)) => Ok(v1), +> Err(e) => Err(e), +> } +155,156c155,156 +< async fn stat64_ignore_enoent(&self, req: &Request) -> Result> { +< match self.stat64(req).await { +--- +> fn stat64_ignore_enoent(&self, ctx: &Context) -> Result> { +> match self.stat64(ctx) { +171,172c171,172 +< async fn lookup_child_ignore_enoent(&self, ctx: Request, name: &str) -> Result> { +< let cname = OsStr::new(name); +--- +> fn lookup_child_ignore_enoent(&self, ctx: &Context, name: &str) -> Result> { +> let cname = CString::new(name).map_err(|e| Error::new(ErrorKind::InvalidData, e))?; +175c175 +< match layer.lookup(ctx, self.inode, cname).await { +--- +> match layer.lookup(ctx, self.inode, cname.as_c_str()) { +178c178 +< if v.attr.ino == 0 { +--- +> if v.inode == 0 { +184c184,190 +< Err(e.into()) +--- +> if let Some(raw_error) = e.raw_os_error() { +> if raw_error == libc::ENOENT || raw_error == libc::ENAMETOOLONG { +> return Ok(None); +> } +> } +> +> Err(e) +191c197 +< async fn lookup_child(&self, ctx: Request, name: &str) -> Result> { +--- +> fn lookup_child(&self, ctx: &Context, name: &str) -> Result> { +199c205 +< match self.lookup_child_ignore_enoent(ctx, name).await? { +--- +> match self.lookup_child_ignore_enoent(ctx, name)? { +202,203c208,209 +< let (whiteout, opaque) = if v.attr.kind==FileType::Directory { +< (false, layer.is_opaque(ctx, v.attr.ino).await?) +--- +> let (whiteout, opaque) = if utils::is_dir(v.attr) { +> (false, layer.is_opaque(ctx, v.inode)?) +205c211 +< (layer.is_whiteout(ctx, v.attr.ino).await?, false) +--- +> (layer.is_whiteout(ctx, v.inode)?, false) +211c217 +< inode: v.attr.ino, +--- +> inode: v.inode, +214c220 +< stat: Some(ReplyAttr { ttl: v.ttl, attr: v.attr }), +--- +> stat: Some(v.attr), +222c228 +< async fn readdir(&self, ctx: Request) -> Result> { +--- +> fn readdir(&self, ctx: &Context) -> Result> { +228c234 +< let stat = match self.stat.clone() { +--- +> let stat = match self.stat { +230c236 +< None => self.stat64(&ctx).await?, +--- +> None => self.stat64(ctx)?, +234c240 +< if stat.attr.kind!=FileType::Directory { +--- +> if !utils::is_dir(stat) { +239c245 +< let opendir_res = self.layer.opendir(ctx, self.inode, libc::O_RDONLY as u32).await; +--- +> let opendir_res = self.layer.opendir(ctx, self.inode, libc::O_RDONLY as u32); +241,242c247,250 +< Ok(handle) => handle, +< +--- +> Ok((handle, _)) => match handle { +> Some(h) => h, +> _ => 0, +> }, +245c253,265 +< return Err(e.into()); +--- +> match e.raw_os_error() { +> Some(raw_error) => { +> if raw_error == libc::ENOSYS { +> // We can still call readdir with inode if opendir is not supported in this layer. +> 0 +> } else { +> return Err(e); +> } +> } +> None => { +> return Err(e); +> } +> } +249c269,275 +< let child_names =self.layer.readdir( +--- +> let mut child_names = vec![]; +> let mut more = true; +> let mut offset = 0; +> let bufsize = 1024; +> while more { +> more = false; +> self.layer.readdir( +252,259c278,296 +< handle.fh, +< 0, +< ).await?; +< // Non-zero handle indicates successful 'open', we should 'release' it. +< if handle.fh > 0 { +< self.layer +< .releasedir(ctx, self.inode, handle.fh, handle.flags).await? +< +--- +> handle, +> bufsize, +> offset, +> &mut |d| -> Result { +> more = true; +> offset = d.offset; +> let child_name = String::from_utf8_lossy(d.name).into_owned(); +> +> trace!("entry: {}", child_name.as_str()); +> +> if child_name.eq(CURRENT_DIR) || child_name.eq(PARENT_DIR) { +> return Ok(1); +> } +> +> child_names.push(child_name); +> +> Ok(1) +> }, +> )?; +262,271c299,309 +< // Lookup all child and construct "RealInode"s. +< let child_real_inodes = Arc::new(Mutex::new(HashMap::new())); +< +< let a_map = child_names.entries.map(|entery| +< async { +< match entery{ +< Ok(dire) => { +< let dname = dire.name.into_string().unwrap(); +< if let Some(child) = self.lookup_child(ctx, &dname).await.unwrap() { +< child_real_inodes.lock().await.insert(dname, child); +--- +> // Non-zero handle indicates successful 'open', we should 'release' it. +> if handle > 0 { +> if let Err(e) = self +> .layer +> .releasedir(ctx, self.inode, libc::O_RDONLY as u32, handle) +> { +> // ignore ENOSYS +> match e.raw_os_error() { +> Some(raw_error) => { +> if raw_error != libc::ENOSYS { +> return Err(e); +273,275c311,314 +< Ok(()) +< }, +< Err(err) => Err(err), +--- +> } +> None => { +> return Err(e); +> } +278,284c317,325 +< ); +< let k = join_all(a_map.collect::>().await).await ; +< drop(k); +< // 现在可以安全地调用 into_inner +< let re = Arc::try_unwrap(child_real_inodes) +< .map_err(|_|Errno::new_not_exist())? +< .into_inner() ; +--- +> } +> +> // Lookup all child and construct "RealInode"s. +> let mut child_real_inodes = HashMap::new(); +> for name in child_names { +> if let Some(child) = self.lookup_child(ctx, name.as_str())? { +> child_real_inodes.insert(name, child); +> } +> } +286c327 +< Ok(re) +--- +> Ok(child_real_inodes) +289c330 +< async fn create_whiteout(&self, ctx: Request, name: &str) -> Result { +--- +> fn create_whiteout(&self, ctx: &Context, name: &str) -> Result { +294,298c335,338 +< // 将 &str 转换为 &OsStr +< let name_osstr = OsStr::new(name); +< let entry = self +< .layer +< .create_whiteout(ctx, self.inode, name_osstr).await?; +--- +> let cname = utils::to_cstring(name)?; +> let entry = self +> .layer +> .create_whiteout(ctx, self.inode, cname.as_c_str())?; +304c344 +< inode: entry.attr.ino, +--- +> inode: entry.inode, +307c347 +< stat: Some(ReplyAttr { ttl: entry.ttl, attr: entry.attr }), +--- +> stat: Some(entry.attr), +311c351 +< async fn mkdir(&self, ctx: Request, name: &str, mode: u32, umask: u32) -> Result { +--- +> fn mkdir(&self, ctx: &Context, name: &str, mode: u32, umask: u32) -> Result { +316c356 +< let name_osstr = OsStr::new(name); +--- +> let cname = utils::to_cstring(name)?; +319c359 +< .mkdir(ctx, self.inode, name_osstr, mode, umask).await?; +--- +> .mkdir(ctx, self.inode, cname.as_c_str(), mode, umask)?; +325c365 +< inode: entry.attr.ino, +--- +> inode: entry.inode, +328c368 +< stat: Some(ReplyAttr { ttl: entry.ttl, attr: entry.attr }), +--- +> stat: Some(entry.attr), +332c372 +< async fn create( +--- +> fn create( +334c374 +< ctx: Request, +--- +> ctx: &Context, +336,337c376 +< mode: u32, +< flags: u32, +--- +> args: CreateIn, +342,343c381,382 +< let name = OsStr::new(name); +< let create_rep = +--- +> +> let (entry, h, _, _) = +345c384 +< .create(ctx, self.inode, name, mode,flags).await?; +--- +> .create(ctx, self.inode, utils::to_cstring(name)?.as_c_str(), args)?; +351c390 +< inode: create_rep.attr.ino, +--- +> inode: entry.inode, +354c393 +< stat: Some(ReplyAttr { ttl:create_rep.ttl, attr: create_rep.attr }), +--- +> stat: Some(entry.attr), +356c395 +< Some(create_rep.fh), +--- +> h, +360c399 +< async fn mknod( +--- +> fn mknod( +362,363c401,402 +< ctx: Request, +< name: &str, +--- +> ctx: &Context, +> name: &str, +366c405 +< _umask: u32, +--- +> umask: u32, +371,372c410,411 +< let name = OsStr::new(name); +< let rep = self.layer.mknod( +--- +> +> let entry = self.layer.mknod( +375c414 +< name, +--- +> utils::to_cstring(name)?.as_c_str(), +378c417,418 +< ).await?; +--- +> umask, +> )?; +382c422 +< inode: rep.attr.ino, +--- +> inode: entry.inode, +385,386c425,426 +< stat: Some(ReplyAttr { ttl:rep.ttl, attr: rep.attr }), +< },) +--- +> stat: Some(entry.attr), +> }) +389c429 +< async fn link(&self, ctx: Request, ino: u64, name: &str) -> Result { +--- +> fn link(&self, ctx: &Context, ino: u64, name: &str) -> Result { +393c433 +< let name = OsStr::new(name); +--- +> +396c436 +< .link(ctx, ino, self.inode, name).await?; +--- +> .link(ctx, ino, self.inode, utils::to_cstring(name)?.as_c_str())?; +398,399c438,439 +< let opaque = if utils::is_dir(&entry.attr.kind) { +< self.layer.is_opaque(ctx, entry.attr.ino).await? +--- +> let opaque = if utils::is_dir(entry.attr) { +> self.layer.is_opaque(ctx, entry.inode)? +406c446 +< inode: entry.attr.ino, +--- +> inode: entry.inode, +409c449 +< stat: Some(ReplyAttr { ttl: entry.ttl, attr: entry.attr }), +--- +> stat: Some(entry.attr), +414c454 +< async fn symlink(&self, ctx: Request, link_name: &str, filename: &str) -> Result { +--- +> fn symlink(&self, ctx: &Context, link_name: &str, filename: &str) -> Result { +418,419c458 +< let link_name = OsStr::new(link_name); +< let filename = OsStr::new(filename); +--- +> +421a461 +> utils::to_cstring(link_name)?.as_c_str(), +423,425c463,464 +< filename, +< link_name, +< ).await?; +--- +> utils::to_cstring(filename)?.as_c_str(), +> )?; +429,430c468,469 +< in_upper_layer: true, +< inode: entry.attr.ino, +--- +> in_upper_layer: self.in_upper_layer, +> inode: entry.inode, +432,433c471,472 +< opaque:false, +< stat: Some(ReplyAttr { ttl: entry.ttl, attr: entry.attr }), +--- +> opaque: false, +> stat: Some(entry.attr), +438,440c477,478 +< #[allow(unused)] +< impl RealInode { +< async fn drop(&mut self) { +--- +> impl Drop for RealInode { +> fn drop(&mut self) { +442c480 +< let ctx = Request::default(); +--- +> let ctx = Context::default(); +446c484 +< layer.forget(ctx, inode, 1).await; +--- +> layer.forget(&ctx, inode, 1); +453a492 +> +456c495 +< pub async fn new_from_real_inode(name: &str, ino: u64, path: String, real_inode: RealInode) -> Self { +--- +> pub fn new_from_real_inode(name: &str, ino: u64, path: String, real_inode: RealInode) -> Self { +459c498 +< new.path = path; +--- +> new.path = path.clone(); +461c500 +< new.whiteout.store(real_inode.whiteout).await; +--- +> new.whiteout.store(real_inode.whiteout, Ordering::Relaxed); +467c506 +< pub async fn new_from_real_inodes( +--- +> pub fn new_from_real_inodes( +483,485c522,524 +< let stat = match &ri.stat { +< Some(v) => v.clone(), +< None => ri.stat64(&Request::default()).await?, +--- +> let stat = match ri.stat { +> Some(v) => v, +> None => ri.stat64(&Context::default())?, +490c529 +< new = Self::new_from_real_inode(name, ino, path.clone(), ri).await; +--- +> new = Self::new_from_real_inode(name, ino, path.clone(), ri); +498c537 +< if !utils::is_dir(&stat.attr.kind) { +--- +> if !utils::is_dir(stat) { +514c553 +< if !utils::is_dir(&stat.attr.kind) { +--- +> if !utils::is_dir(stat) { +520c559 +< new.real_inodes.lock().await.push(ri); +--- +> new.real_inodes.lock().unwrap().push(ri); +530c569 +< pub async fn stat64(&self, ctx: Request) -> Result { +--- +> pub fn stat64(&self, ctx: &Context) -> Result { +532,533c571,572 +< for l in self.real_inodes.lock().await.iter() { +< if let Some(v) = l.stat64_ignore_enoent(&ctx).await? { +--- +> for l in self.real_inodes.lock().unwrap().iter() { +> if let Some(v) = l.stat64_ignore_enoent(ctx)? { +542c581 +< pub async fn count_entries_and_whiteout(&self, ctx: Request) -> Result<(u64, u64)> { +--- +> pub fn count_entries_and_whiteout(&self, ctx: &Context) -> Result<(u64, u64)> { +546c585 +< let st = self.stat64(ctx).await?; +--- +> let st = self.stat64(ctx)?; +549c588 +< if !utils::is_dir(&st.attr.kind) { +--- +> if !utils::is_dir(st) { +553,554c592,593 +< for (_, child) in self.childrens.lock().await.iter() { +< if child.whiteout.load().await{ +--- +> for (_, child) in self.childrens.lock().unwrap().iter() { +> if child.whiteout.load(Ordering::Relaxed) { +564c603 +< pub async fn open( +--- +> pub fn open( +566c605 +< ctx: Request, +--- +> ctx: &Context, +568,572c607,611 +< _fuse_flags: u32, +< ) -> Result<(Arc, ReplyOpen)> { +< let (layer, _, inode) = self.first_layer_inode().await; +< let ro = layer.as_ref().open(ctx, inode, flags).await?; +< Ok((layer, ro)) +--- +> fuse_flags: u32, +> ) -> Result<(Arc, Option, OpenOptions)> { +> let (layer, _, inode) = self.first_layer_inode(); +> let (h, o, _) = layer.as_ref().open(ctx, inode, flags, fuse_flags)?; +> Ok((layer, h, o)) +576,578c615,617 +< pub async fn scan_childrens(self: &Arc, ctx: Request) -> Result> { +< let st = self.stat64(ctx).await?; +< if !utils::is_dir(&st.attr.kind) { +--- +> pub fn scan_childrens(self: &Arc, ctx: &Context) -> Result> { +> let st = self.stat64(ctx)?; +> if !utils::is_dir(st) { +585c624 +< let layers_count = self.real_inodes.lock().await.len(); +--- +> let layers_count = self.real_inodes.lock().unwrap().len(); +587c626 +< for ri in self.real_inodes.lock().await.iter() { +--- +> for ri in self.real_inodes.lock().unwrap().iter() { +602,604c641,643 +< let stat = match &ri.stat { +< Some(v) => v.clone(), +< None => ri.stat64(&ctx).await?, +--- +> let stat = match ri.stat { +> Some(v) => v, +> None => ri.stat64(ctx)?, +607c646 +< if !utils::is_dir(&stat.attr.kind) { +--- +> if !utils::is_dir(stat) { +614c653 +< let entries = ri.readdir(ctx).await?; +--- +> let entries = ri.readdir(ctx)?; +641c680 +< let new = Self::new_from_real_inodes(name.as_str(), 0, path, real_inodes).await?; +--- +> let new = Self::new_from_real_inodes(name.as_str(), 0, path, real_inodes)?; +649c688 +< pub async fn create_upper_dir( +--- +> pub fn create_upper_dir( +651c690 +< ctx: Request, +--- +> ctx: &Context, +654,655c693,694 +< let st = self.stat64(ctx).await?; +< if !utils::is_dir(&st.attr.kind) { +--- +> let st = self.stat64(ctx)?; +> if !utils::is_dir(st) { +660c699 +< if self.in_upper_layer().await { +--- +> if self.in_upper_layer() { +665c704 +< let pnode = if let Some(n) = self.parent.lock().await.upgrade() { +--- +> let pnode = if let Some(n) = self.parent.lock().unwrap().upgrade() { +671,672c710,711 +< if !pnode.in_upper_layer().await { +< Box::pin(pnode.create_upper_dir(ctx, None)).await?; // recursive call +--- +> if !pnode.in_upper_layer() { +> pnode.create_upper_dir(ctx, None)?; // recursive call +674,675c713,714 +< let child: Arc>> = Arc::new(Mutex::new(None)); +< let _ =pnode.handle_upper_inode_locked(&mut |parent_upper_inode: Option| async { +--- +> let mut child = None; +> pnode.handle_upper_inode_locked(&mut |parent_upper_inode| -> Result { +680c719 +< parent_ri.mkdir(ctx, self.name.as_str(), mode, umask).await? +--- +> parent_ri.mkdir(ctx, self.name.as_str(), mode, umask)? +682c721 +< None => parent_ri.mkdir(ctx, self.name.as_str(), mode_from_kind_and_perm(st.attr.kind, st.attr.perm), 0).await?, +--- +> None => parent_ri.mkdir(ctx, self.name.as_str(), st.st_mode, 0)?, +685c724 +< child.lock().await.replace(ri); +--- +> child.replace(ri); +696c735 +< }).await?; +--- +> })?; +698c737 +< if let Some(ri) = child.lock().await.take() { +--- +> if let Some(ri) = child { +700c739 +< self.add_upper_inode(ri, false).await; +--- +> self.add_upper_inode(ri, false); +707,708c746,747 +< async fn add_upper_inode(self: &Arc, ri: RealInode, clear_lowers: bool) { +< let mut inodes = self.real_inodes.lock().await; +--- +> fn add_upper_inode(self: &Arc, ri: RealInode, clear_lowers: bool) { +> let mut inodes = self.real_inodes.lock().unwrap(); +710c749 +< self.whiteout.store(ri.whiteout).await; +--- +> self.whiteout.store(ri.whiteout, Ordering::Relaxed); +723,724c762,763 +< pub async fn in_upper_layer(&self) -> bool { +< let all_inodes = self.real_inodes.lock().await; +--- +> pub fn in_upper_layer(&self) -> bool { +> let all_inodes = self.real_inodes.lock().unwrap(); +732,733c771,772 +< pub async fn upper_layer_only(&self) -> bool { +< let real_inodes = self.real_inodes.lock().await; +--- +> pub fn upper_layer_only(&self) -> bool { +> let real_inodes = self.real_inodes.lock().unwrap(); +747,748c786,787 +< pub async fn first_layer_inode(&self) -> (Arc, bool, u64) { +< let all_inodes = self.real_inodes.lock().await; +--- +> pub fn first_layer_inode(&self) -> (Arc, bool, u64) { +> let all_inodes = self.real_inodes.lock().unwrap(); +756,757c795,796 +< pub async fn child(&self, name: &str) -> Option> { +< self.childrens.lock().await.get(name).cloned() +--- +> pub fn child(&self, name: &str) -> Option> { +> self.childrens.lock().unwrap().get(name).cloned() +760,761c799,800 +< pub async fn remove_child(&self, name: &str) { +< self.childrens.lock().await.remove(name); +--- +> pub fn remove_child(&self, name: &str) { +> self.childrens.lock().unwrap().remove(name); +764c803 +< pub async fn insert_child(&self, name: &str, node: Arc) { +--- +> pub fn insert_child(&self, name: &str, node: Arc) { +767c806 +< .await +--- +> .unwrap() +771c810 +< pub async fn handle_upper_inode_locked( +--- +> pub fn handle_upper_inode_locked( +773,778c812,814 +< mut f: F, +< ) -> Result +< where +< F: FnMut(Option) -> Fut, +< Fut: Future>, { +< let all_inodes = self.real_inodes.lock().await; +--- +> f: &mut dyn FnMut(Option<&RealInode>) -> Result, +> ) -> Result { +> let all_inodes = self.real_inodes.lock().unwrap(); +783c819 +< f(Some(v.clone())).await +--- +> f(Some(v)) +785c821 +< f(None).await +--- +> f(None) +798c834 +< #[allow(unused)] +--- +> +811c847 +< #[allow(unused)] +--- +> +817d852 +< root_inode:u64, +832d866 +< root_inodes: root_inode, +837c871 +< self.root_inodes +--- +> FUSE_ROOT_ID +840,841c874,875 +< async fn alloc_inode(&self, path: &str) -> Result { +< self.inodes.write().await.alloc_inode(path) +--- +> fn alloc_inode(&self, path: &String) -> Result { +> self.inodes.write().unwrap().alloc_inode(path) +844c878 +< pub async fn import(&self) -> Result<()> { +--- +> pub fn import(&self) -> Result<()> { +846c880 +< root.inode = self.root_inode(); +--- +> root.inode = FUSE_ROOT_ID; +851c885 +< let ctx = Request::default(); +--- +> let ctx = Context::default(); +856,862c890,891 +< let real = RealInode::new( +< layer.clone(), +< true, ino, +< false, +< layer.is_opaque(ctx, ino).await? +< ).await; +< root.real_inodes.lock().await.push(real); +--- +> let real = RealInode::new(layer.clone(), true, ino, false, layer.is_opaque(&ctx, ino)?); +> root.real_inodes.lock().unwrap().push(real); +868c897 +< let real: RealInode = RealInode::new( +--- +> let real = RealInode::new( +873,875c902,904 +< layer.is_opaque(ctx, ino).await?, +< ).await; +< root.real_inodes.lock().await.push(real); +--- +> layer.is_opaque(&ctx, ino)?, +> ); +> root.real_inodes.lock().unwrap().push(real); +880c909 +< self.insert_inode(self.root_inode(), Arc::clone(&root_node)).await; +--- +> self.insert_inode(FUSE_ROOT_ID, Arc::clone(&root_node)); +883c912 +< self.load_directory(ctx, &root_node).await?; +--- +> self.load_directory(&ctx, &root_node)?; +888c917 +< async fn root_node(&self) -> Arc { +--- +> fn root_node(&self) -> Arc { +890c919 +< self.get_active_inode(self.root_inode()).await.unwrap() +--- +> self.get_active_inode(FUSE_ROOT_ID).unwrap() +893,894c922,923 +< async fn insert_inode(&self, inode: u64, node: Arc) { +< self.inodes.write().await.insert_inode(inode, node); +--- +> fn insert_inode(&self, inode: u64, node: Arc) { +> self.inodes.write().unwrap().insert_inode(inode, node); +897,898c926,927 +< async fn get_active_inode(&self, inode: u64) -> Option> { +< self.inodes.read().await.get_inode(inode) +--- +> fn get_active_inode(&self, inode: u64) -> Option> { +> self.inodes.read().unwrap().get_inode(inode) +902,903c931,932 +< async fn get_all_inode(&self, inode: u64) -> Option> { +< let inode_store = self.inodes.read().await; +--- +> fn get_all_inode(&self, inode: u64) -> Option> { +> let inode_store = self.inodes.read().unwrap(); +911c940 +< async fn remove_inode(&self, inode: u64, path_removed: Option) -> Option> { +--- +> fn remove_inode(&self, inode: u64, path_removed: Option) -> Option> { +914,915c943,944 +< .await +< .remove_inode(inode, path_removed).await +--- +> .unwrap() +> .remove_inode(inode, path_removed) +921c950 +< async fn lookup_node(&self, ctx: Request, parent: Inode, name: &str) -> Result> { +--- +> fn lookup_node(&self, ctx: &Context, parent: Inode, name: &str) -> Result> { +927c956 +< let pnode = match self.get_active_inode(parent).await { +--- +> let pnode = match self.get_active_inode(parent) { +933c962 +< if pnode.whiteout.load().await { +--- +> if pnode.whiteout.load(Ordering::Relaxed) { +937,938c966,967 +< let st = pnode.stat64(ctx).await?; +< if utils::is_dir(&st.attr.kind) && !pnode.loaded.load().await { +--- +> let st = pnode.stat64(ctx)?; +> if utils::is_dir(st) && !pnode.loaded.load(Ordering::Relaxed) { +940c969 +< self.load_directory(ctx, &pnode).await?; +--- +> self.load_directory(ctx, &pnode)?; +946c975 +< || (parent == self.root_inode() && name.eq("..")) +--- +> || (parent == FUSE_ROOT_ID && name.eq("..")) +953c982 +< match pnode.child(name).await { +--- +> match pnode.child(name) { +962,963c991,992 +< async fn debug_print_all_inodes(&self) { +< self.inodes.read().await.debug_print_all_inodes(); +--- +> fn debug_print_all_inodes(&self) { +> self.inodes.read().unwrap().debug_print_all_inodes(); +966c995 +< async fn lookup_node_ignore_enoent( +--- +> fn lookup_node_ignore_enoent( +968c997 +< ctx: Request, +--- +> ctx: &Context, +972c1001 +< match self.lookup_node(ctx, parent, name).await { +--- +> match self.lookup_node(ctx, parent, name) { +986,987c1015,1016 +< async fn load_directory(&self, ctx: Request, node: &Arc) -> Result<()> { +< if node.loaded.load().await { +--- +> fn load_directory(&self, ctx: &Context, node: &Arc) -> Result<()> { +> if node.loaded.load(Ordering::Relaxed) { +992c1021 +< let childrens = node.scan_childrens(ctx).await?; +--- +> let childrens = node.scan_childrens(ctx)?; +996c1025 +< let mut inode_store = self.inodes.write().await; +--- +> let mut inode_store = self.inodes.write().unwrap(); +998c1027 +< let mut node_children = node.childrens.lock().await; +--- +> let mut node_children = node.childrens.lock().unwrap(); +1001c1030 +< if node.loaded.load().await { +--- +> if node.loaded.load(Ordering::Relaxed) { +1021c1050 +< node.loaded.store(true).await; +--- +> node.loaded.store(true, Ordering::Relaxed); +1026c1055 +< async fn forget_one(&self, inode: Inode, count: u64) { +--- +> fn forget_one(&self, inode: Inode, count: u64) { +1031c1060 +< let v = match self.get_all_inode(inode).await { +--- +> let v = match self.get_all_inode(inode) { +1040c1069 +< let mut lookups = v.lookups.load().await; +--- +> let mut lookups = v.lookups.load(Ordering::Relaxed); +1047c1076 +< v.lookups.store(lookups).await; +--- +> v.lookups.store(lookups, Ordering::Relaxed); +1054,1055c1083,1084 +< let _ = self.remove_inode(inode, None).await; +< let parent = v.parent.lock().await; +--- +> let _ = self.remove_inode(inode, None); +> let parent = v.parent.lock().unwrap(); +1059c1088 +< p.remove_child(v.name.as_str()).await; +--- +> p.remove_child(v.name.as_str()); +1064,1065c1093,1094 +< async fn do_lookup(&self, ctx: Request, parent: Inode, name: &str) -> Result { +< let node = self.lookup_node(ctx, parent, name).await?; +--- +> fn do_lookup(&self, ctx: &Context, parent: Inode, name: &str) -> Result { +> let node = self.lookup_node(ctx, parent, name)?; +1067c1096 +< if node.whiteout.load().await { +--- +> if node.whiteout.load(Ordering::Relaxed) { +1071,1074c1100,1103 +< let mut st = node.stat64(ctx).await?; +< st.attr.ino = node.inode; +< if utils::is_dir(&st.attr.kind) && !node.loaded.load().await { +< self.load_directory(ctx, &node).await?; +--- +> let st = node.stat64(ctx)?; +> +> if utils::is_dir(st) && !node.loaded.load(Ordering::Relaxed) { +> self.load_directory(ctx, &node)?; +1078c1107 +< let tmp = node.lookups.fetch_add(1).await; +--- +> let tmp = node.lookups.fetch_add(1, Ordering::Relaxed); +1080,1082c1109,1110 +< Ok(ReplyEntry{ +< ttl: st.ttl, +< attr: st.attr, +--- +> Ok(Entry { +> inode: node.inode, +1083a1112,1115 +> attr: st, +> attr_flags: 0, +> attr_timeout: self.config.attr_timeout, +> entry_timeout: self.config.entry_timeout, +1085,1092d1116 +< // Ok(Entry { +< // inode: node.inode, +< // generation: 0, +< // attr: st, +< // attr_flags: 0, +< // attr_timeout: self.config.attr_timeout, +< // entry_timeout: self.config.entry_timeout, +< // }) +1095,1096c1119,1120 +< async fn do_statvfs(&self, ctx: Request, inode: Inode) -> Result { +< match self.get_active_inode(inode).await { +--- +> fn do_statvfs(&self, ctx: &Context, inode: Inode) -> Result { +> match self.get_active_inode(inode) { +1098c1122 +< let all_inodes = ovi.real_inodes.lock().await; +--- +> let all_inodes = ovi.real_inodes.lock().unwrap(); +1102c1126 +< Ok(real_inode.layer.statfs(ctx, real_inode.inode).await?) +--- +> real_inode.layer.statfs(ctx, real_inode.inode) +1109c1133 +< async fn do_readdir<'a>( +--- +> fn do_readdir( +1111c1135 +< ctx: Request, +--- +> ctx: &Context, +1113a1138 +> size: u32, +1116,1117c1141,1151 +< ) -> Result<::DirEntryStream<'a>> { +< +--- +> add_entry: &mut dyn FnMut(DirEntry, Option) -> Result, +> ) -> Result<()> { +> trace!( +> "do_readir: handle: {}, size: {}, offset: {}", +> handle, +> size, +> offset +> ); +> if size == 0 { +> return Ok(()); +> } +1120c1154 +< let ovl_inode = match self.handles.lock().await.get(&handle) { +--- +> let ovl_inode = match self.handles.lock().unwrap().get(&handle) { +1124c1158 +< let node = self.lookup_node(ctx, inode, ".").await?; +--- +> let node = self.lookup_node(ctx, inode, ".")?; +1126,1127c1160,1161 +< let st = node.stat64(ctx).await?; +< if !utils::is_dir(&st.attr.kind) { +--- +> let st = node.stat64(ctx)?; +> if !utils::is_dir(st) { +1140c1174 +< let parent_node = match ovl_inode.parent.lock().await.upgrade() { +--- +> let parent_node = match ovl_inode.parent.lock().unwrap().upgrade() { +1142c1176 +< None => self.root_node().await, +--- +> None => self.root_node(), +1146c1180 +< for (_, child) in ovl_inode.childrens.lock().await.iter() { +--- +> for (_, child) in ovl_inode.childrens.lock().unwrap().iter() { +1148c1182 +< if child.whiteout.load().await { +--- +> if child.whiteout.load(Ordering::Relaxed) { +1156c1190 +< return Ok(iter(vec![].into_iter())); +--- +> return Ok(()); +1158d1191 +< let mut d:Vec> = Vec::new(); +1163,1168c1196,1201 +< let st = child.stat64(ctx).await?; +< let dir_entry = DirectoryEntry { +< inode: child.inode, +< kind: st.attr.kind, +< name: name.into(), +< offset: (index + 1) as i64, +--- +> let st = child.stat64(ctx)?; +> let dir_entry = DirEntry { +> ino: st.st_ino, +> offset: index + 1, +> type_: entry_type_from_mode(st.st_mode) as u32, +> name: name.as_bytes(), +1170,1195d1202 +< // let pentry = DirectoryEntryPlus{ +< // inode, +< // generation: 0, +< // kind: st.attr.kind, +< // name: name.into(), +< // offset: (index + 1) as i64, +< // attr: st.attr, +< // entry_ttl: todo!(), +< // attr_ttl: todo!(), +< // } +< d.push(Ok(dir_entry)); +< } +< } +< +< Ok(iter(d.into_iter())) +< } +< +< #[allow(clippy::too_many_arguments)] +< async fn do_readdirplus<'a>( +< &self, +< ctx: Request, +< inode: Inode, +< handle: u64, +< offset: u64, +< is_readdirplus: bool, +< ) -> Result<::DirEntryPlusStream<'a>> { +1196a1204,1225 +> let entry = if is_readdirplus { +> child.lookups.fetch_add(1, Ordering::Relaxed); +> Some(Entry { +> inode: child.inode, +> generation: 0, +> attr: st, +> attr_flags: 0, +> attr_timeout: self.config.attr_timeout, +> entry_timeout: self.config.entry_timeout, +> }) +> } else { +> None +> }; +> match add_entry(dir_entry, entry) { +> Ok(0) => break, +> Ok(l) => { +> len += l; +> if len as u32 >= size { +> // no more space, stop here +> return Ok(()); +> } +> } +1198,1207c1227,1234 +< // lookup the directory +< let ovl_inode = match self.handles.lock().await.get(&handle) { +< Some(dir) => dir.node.clone(), +< None => { +< // Try to get data with inode. +< let node = self.lookup_node(ctx, inode, ".").await?; +< +< let st = node.stat64(ctx).await?; +< if !utils::is_dir(&st.attr.kind) { +< return Err(Error::from_raw_os_error(libc::ENOTDIR)); +--- +> Err(e) => { +> // when the buffer is still empty, return error, otherwise return the entry already added +> if len == 0 { +> return Err(e); +> } else { +> return Ok(()); +> } +> } +1209,1228d1235 +< +< node.clone() +< } +< }; +< +< let mut childrens = Vec::new(); +< //add myself as "." +< childrens.push((".".to_string(), ovl_inode.clone())); +< +< //add parent +< let parent_node = match ovl_inode.parent.lock().await.upgrade() { +< Some(p) => p.clone(), +< None => self.root_node().await, +< }; +< childrens.push(("..".to_string(), parent_node)); +< +< for (_, child) in ovl_inode.childrens.lock().await.iter() { +< // skip whiteout node +< if child.whiteout.load().await { +< continue; +1230d1236 +< childrens.push((child.name.clone(), child.clone())); +1233,1270c1239 +< let mut len: usize = 0; +< if offset >= childrens.len() as u64 { +< return Ok(iter(vec![].into_iter())); +< } +< let mut d:Vec> = Vec::new(); +< +< for (index, (name, child)) in (0_u64..).zip(childrens.into_iter()) { +< if index >= offset { +< // make struct DireEntry and Entry +< let mut st = child.stat64(ctx).await?; +< child.lookups.fetch_add(1).await; +< st.attr.ino = child.inode; +< println!("--entry name:{}",name); +< let dir_entry = DirectoryEntryPlus { +< inode:child.inode, +< generation: 0, +< kind: st.attr.kind, +< name: name.into(), +< offset: (index + 1) as i64, +< attr: st.attr, +< entry_ttl: st.ttl, +< attr_ttl:st.ttl +< }; +< // let pentry = DirectoryEntryPlus{ +< // inode, +< // generation: 0, +< // kind: st.attr.kind, +< // name: name.into(), +< // offset: (index + 1) as i64, +< // attr: st.attr, +< // entry_ttl: todo!(), +< // attr_ttl: todo!(), +< // } +< d.push(Ok(dir_entry)); +< } +< } +< +< Ok(iter(d.into_iter())) +--- +> Ok(()) +1272c1241,1242 +< async fn do_mkdir( +--- +> +> fn do_mkdir( +1274c1244 +< ctx: Request, +--- +> ctx: &Context, +1285c1255 +< if parent_node.whiteout.load().await { +--- +> if parent_node.whiteout.load(Ordering::Relaxed) { +1291c1261 +< if let Some(n) = self.lookup_node_ignore_enoent(ctx, parent_node.inode, name).await? { +--- +> if let Some(n) = self.lookup_node_ignore_enoent(ctx, parent_node.inode, name)? { +1293c1263 +< if !n.whiteout.load().await { +--- +> if !n.whiteout.load(Ordering::Relaxed) { +1297c1267 +< if n.in_upper_layer().await { +--- +> if n.in_upper_layer() { +1302c1272 +< if !n.upper_layer_only().await { +--- +> if !n.upper_layer_only() { +1308c1278 +< let pnode = self.copy_node_up(ctx, Arc::clone(parent_node)).await?; +--- +> let pnode = self.copy_node_up(ctx, Arc::clone(parent_node))?; +1310c1280 +< +--- +> let mut new_node = None; +1312,1314c1282 +< let path_ref = &path; +< let new_node = Arc::new(Mutex::new(None)); +< pnode.handle_upper_inode_locked( &mut |parent_real_inode: Option| async { +--- +> pnode.handle_upper_inode_locked(&mut |parent_real_inode| -> Result { +1322c1290 +< let osstr = OsStr::new(name); +--- +> +1327,1328c1295,1296 +< osstr, +< ).await; +--- +> utils::to_cstring(name)?.as_c_str(), +> ); +1330d1297 +< +1332,1333c1299,1300 +< let ino = self.alloc_inode(path_ref).await?; +< let child_dir = parent_real_inode.mkdir(ctx, name, mode, umask).await?; +--- +> let ino = self.alloc_inode(&path)?; +> let child_dir = parent_real_inode.mkdir(ctx, name, mode, umask)?; +1336c1303 +< parent_real_inode.layer.set_opaque(ctx, child_dir.inode).await?; +--- +> parent_real_inode.layer.set_opaque(ctx, child_dir.inode)?; +1338,1341c1305 +< let ovi = OverlayInode::new_from_real_inode(name, ino, path_ref.clone(), child_dir).await; +< new_node.lock().await.replace(ovi); +< Ok(false) +< }).await?; +--- +> let ovi = OverlayInode::new_from_real_inode(name, ino, path.clone(), child_dir); +1342a1307,1309 +> new_node.replace(ovi); +> Ok(false) +> })?; +1345,1348c1312,1314 +< let nn = new_node.lock().await.take(); +< let arc_node = Arc::new(nn.unwrap()); +< self.insert_inode(arc_node.inode, arc_node.clone()).await; +< pnode.insert_child(name, arc_node).await; +--- +> let arc_node = Arc::new(new_node.unwrap()); +> self.insert_inode(arc_node.inode, arc_node.clone()); +> pnode.insert_child(name, arc_node); +1352c1318 +< async fn do_mknod( +--- +> fn do_mknod( +1354c1320 +< ctx: Request, +--- +> ctx: &Context, +1366c1332 +< if parent_node.whiteout.load().await { +--- +> if parent_node.whiteout.load(Ordering::Relaxed) { +1370c1336 +< match self.lookup_node_ignore_enoent(ctx, parent_node.inode, name).await? { +--- +> match self.lookup_node_ignore_enoent(ctx, parent_node.inode, name)? { +1373c1339 +< if !n.whiteout.load().await { +--- +> if !n.whiteout.load(Ordering::Relaxed) { +1378,1379c1344,1345 +< let pnode = self.copy_node_up(ctx, Arc::clone(parent_node)).await?; +< pnode.handle_upper_inode_locked(&mut |parent_real_inode: Option| async { +--- +> let pnode = self.copy_node_up(ctx, Arc::clone(parent_node))?; +> pnode.handle_upper_inode_locked(&mut |parent_real_inode| -> Result { +1387,1388c1353,1354 +< let osstr = OsStr::new(name); +< if n.in_upper_layer().await { +--- +> +> if n.in_upper_layer() { +1392,1393c1358,1359 +< osstr, +< ).await; +--- +> utils::to_cstring(name)?.as_c_str(), +> ); +1396c1362 +< let child_ri = parent_real_inode.mknod(ctx, name, mode, rdev, umask).await?; +--- +> let child_ri = parent_real_inode.mknod(ctx, name, mode, rdev, umask)?; +1399c1365 +< n.add_upper_inode(child_ri, true).await; +--- +> n.add_upper_inode(child_ri, true); +1401c1367 +< }).await?; +--- +> })?; +1405,1406c1371,1372 +< let pnode = self.copy_node_up(ctx, Arc::clone(parent_node)).await?; +< let mut new_node = Arc::new(Mutex::new(None)); +--- +> let pnode = self.copy_node_up(ctx, Arc::clone(parent_node))?; +> let mut new_node = None; +1408c1374 +< pnode.handle_upper_inode_locked(&mut |parent_real_inode: Option| async { +--- +> pnode.handle_upper_inode_locked(&mut |parent_real_inode| -> Result { +1418,1420c1384,1386 +< let ino = self.alloc_inode(&path).await?; +< let child_ri = parent_real_inode.mknod(ctx, name, mode, rdev, umask).await?; +< let ovi = OverlayInode::new_from_real_inode(name, ino, path.clone(), child_ri).await; +--- +> let ino = self.alloc_inode(&path)?; +> let child_ri = parent_real_inode.mknod(ctx, name, mode, rdev, umask)?; +> let ovi = OverlayInode::new_from_real_inode(name, ino, path.clone(), child_ri); +1422c1388 +< new_node.lock().await.replace(ovi); +--- +> new_node.replace(ovi); +1424c1390 +< }).await?; +--- +> })?; +1426,1429c1392,1395 +< let nn = new_node.lock().await.take(); +< let arc_node = Arc::new(nn.unwrap()); +< self.insert_inode(arc_node.inode, arc_node.clone()).await; +< pnode.insert_child(name, arc_node).await; +--- +> // new_node is always 'Some' +> let arc_node = Arc::new(new_node.unwrap()); +> self.insert_inode(arc_node.inode, arc_node.clone()); +> pnode.insert_child(name, arc_node); +1436c1402 +< async fn do_create( +--- +> fn do_create( +1438c1404 +< ctx: Request, +--- +> ctx: &Context, +1440,1442c1406,1407 +< name: &OsStr, +< mode: u32, +< flags: u32, +--- +> name: &str, +> args: CreateIn, +1444d1408 +< let name_str = name.to_str().unwrap(); +1452c1416 +< if parent_node.whiteout.load().await { +--- +> if parent_node.whiteout.load(Ordering::Relaxed) { +1456,1458c1420,1422 +< let mut handle: Arc>> = Arc::new(Mutex::new(None)); +< let mut real_ino : Arc>> = Arc::new(Mutex::new(None));; +< let new_ovi = match self.lookup_node_ignore_enoent(ctx, parent_node.inode, name_str).await? { +--- +> let mut handle = None; +> let mut real_ino = 0u64; +> let new_ovi = match self.lookup_node_ignore_enoent(ctx, parent_node.inode, name)? { +1461c1425 +< if !n.whiteout.load().await { +--- +> if !n.whiteout.load(Ordering::Relaxed) { +1466,1467c1430,1431 +< let pnode = self.copy_node_up(ctx, Arc::clone(parent_node)).await?; +< pnode.handle_upper_inode_locked(&mut |parent_real_inode:Option| async { +--- +> let pnode = self.copy_node_up(ctx, Arc::clone(parent_node))?; +> pnode.handle_upper_inode_locked(&mut |parent_real_inode| -> Result { +1476c1440 +< if n.in_upper_layer().await { +--- +> if n.in_upper_layer() { +1480,1481c1444,1445 +< name, +< ).await; +--- +> utils::to_cstring(name)?.as_c_str(), +> ); +1484,1486c1448,1450 +< let (child_ri, hd) = parent_real_inode.create(ctx, name_str, mode,flags).await?; +< real_ino.lock().await.replace(child_ri.inode); +< handle.lock().await.replace(hd.unwrap()); +--- +> let (child_ri, hd) = parent_real_inode.create(ctx, name, args)?; +> real_ino = child_ri.inode; +> handle = hd; +1489c1453 +< n.add_upper_inode(child_ri, true).await; +--- +> n.add_upper_inode(child_ri, true); +1491c1455 +< }).await?; +--- +> })?; +1496,1499c1460,1463 +< let pnode = self.copy_node_up(ctx, Arc::clone(parent_node)).await?; +< let mut new_node = Arc::new(Mutex::new(None)); +< let path = format!("{}/{}", pnode.path, name_str); +< pnode.handle_upper_inode_locked(&mut |parent_real_inode:Option| async { +--- +> let pnode = self.copy_node_up(ctx, Arc::clone(parent_node))?; +> let mut new_node = None; +> let path = format!("{}/{}", pnode.path, name); +> pnode.handle_upper_inode_locked(&mut |parent_real_inode| -> Result { +1508,1510c1472,1474 +< let (child_ri, hd) = parent_real_inode.create(ctx, name_str, mode,flags).await?; +< real_ino.lock().await.replace(child_ri.inode); +< handle.lock().await.replace(hd.unwrap()); +--- +> let (child_ri, hd) = parent_real_inode.create(ctx, name, args)?; +> real_ino = child_ri.inode; +> handle = hd; +1512,1513c1476,1477 +< let ino = self.alloc_inode(&path).await?; +< let ovi = OverlayInode::new_from_real_inode(name_str, ino, path.clone(), child_ri).await; +--- +> let ino = self.alloc_inode(&path)?; +> let ovi = OverlayInode::new_from_real_inode(name, ino, path.clone(), child_ri); +1515c1479 +< new_node.lock().await.replace(ovi); +--- +> new_node.replace(ovi); +1517c1481 +< }).await?; +--- +> })?; +1520,1523c1484,1486 +< let nn = new_node.lock().await.take(); +< let arc_node = Arc::new(nn.unwrap()); +< self.insert_inode(arc_node.inode, arc_node.clone()).await; +< pnode.insert_child(name_str, arc_node.clone()).await; +--- +> let arc_node = Arc::new(new_node.unwrap()); +> self.insert_inode(arc_node.inode, arc_node.clone()); +> pnode.insert_child(name, arc_node.clone()); +1528c1491 +< let final_handle = match *handle.lock().await { +--- +> let final_handle = match handle { +1530c1493 +< if self.no_open.load().await { +--- +> if self.no_open.load(Ordering::Relaxed) { +1533c1496 +< let handle = self.next_handle.fetch_add(1).await; +--- +> let handle = self.next_handle.fetch_add(1, Ordering::Relaxed); +1539c1502 +< inode: real_ino.lock().await.unwrap(), +--- +> inode: real_ino, +1545c1508 +< .await +--- +> .unwrap() +1555c1518 +< async fn do_link( +--- +> fn do_link( +1557c1520 +< ctx: Request, +--- +> ctx: &Context, +1562d1524 +< let name_os = OsStr::new(name); +1568c1530 +< if src_node.whiteout.load().await || new_parent.whiteout.load().await +--- +> if src_node.whiteout.load(Ordering::Relaxed) || new_parent.whiteout.load(Ordering::Relaxed) +1573,1574c1535,1536 +< let st = src_node.stat64(ctx).await?; +< if utils::is_dir(&st.attr.kind) { +--- +> let st = src_node.stat64(ctx)?; +> if utils::is_dir(st) { +1579,1581c1541,1543 +< let src_node = self.copy_node_up(ctx, Arc::clone(src_node)).await?; +< let new_parent = self.copy_node_up(ctx, Arc::clone(new_parent)).await?; +< let src_ino = src_node.first_layer_inode().await.2; +--- +> let src_node = self.copy_node_up(ctx, Arc::clone(src_node))?; +> let new_parent = self.copy_node_up(ctx, Arc::clone(new_parent))?; +> let src_ino = src_node.first_layer_inode().2; +1583c1545 +< match self.lookup_node_ignore_enoent(ctx, new_parent.inode, name).await? { +--- +> match self.lookup_node_ignore_enoent(ctx, new_parent.inode, name)? { +1586c1548 +< if !n.whiteout.load().await { +--- +> if !n.whiteout.load(Ordering::Relaxed) { +1591c1553 +< new_parent.handle_upper_inode_locked(&mut |parent_real_inode:Option | async { +--- +> new_parent.handle_upper_inode_locked(&mut |parent_real_inode| -> Result { +1601c1563 +< if n.in_upper_layer().await { +--- +> if n.in_upper_layer() { +1605,1606c1567,1568 +< name_os, +< ).await; +--- +> utils::to_cstring(name)?.as_c_str(), +> ); +1609c1571 +< let child_ri = parent_real_inode.link(ctx, src_ino, name).await?; +--- +> let child_ri = parent_real_inode.link(ctx, src_ino, name)?; +1612c1574 +< n.add_upper_inode(child_ri, true).await; +--- +> n.add_upper_inode(child_ri, true); +1614c1576 +< }).await?; +--- +> })?; +1618,1619c1580,1581 +< let mut new_node: Arc>> = Arc::new(Mutex::new(None)); +< new_parent.handle_upper_inode_locked(&mut |parent_real_inode: Option | async { +--- +> let mut new_node = None; +> new_parent.handle_upper_inode_locked(&mut |parent_real_inode| -> Result { +1630,1632c1592,1594 +< let ino = self.alloc_inode(&path).await?; +< let child_ri = parent_real_inode.link(ctx, src_ino, name).await?; +< let ovi = OverlayInode::new_from_real_inode(name, ino, path, child_ri).await; +--- +> let ino = self.alloc_inode(&path)?; +> let child_ri = parent_real_inode.link(ctx, src_ino, name)?; +> let ovi = OverlayInode::new_from_real_inode(name, ino, path, child_ri); +1634c1596 +< new_node.lock().await.replace(ovi); +--- +> new_node.replace(ovi); +1636c1598 +< }).await?; +--- +> })?; +1639,1641c1601,1603 +< let arc_node = Arc::new(new_node.lock().await.take().unwrap()); +< self.insert_inode(arc_node.inode, arc_node.clone()).await; +< new_parent.insert_child(name, arc_node).await; +--- +> let arc_node = Arc::new(new_node.unwrap()); +> self.insert_inode(arc_node.inode, arc_node.clone()); +> new_parent.insert_child(name, arc_node); +1648c1610 +< async fn do_symlink( +--- +> fn do_symlink( +1650c1612 +< ctx: Request, +--- +> ctx: &Context, +1655d1616 +< let name_os = OsStr::new(name); +1661c1622 +< if parent_node.whiteout.load().await { +--- +> if parent_node.whiteout.load(Ordering::Relaxed) { +1665c1626 +< match self.lookup_node_ignore_enoent(ctx, parent_node.inode, name).await? { +--- +> match self.lookup_node_ignore_enoent(ctx, parent_node.inode, name)? { +1668c1629 +< if !n.whiteout.load().await { +--- +> if !n.whiteout.load(Ordering::Relaxed) { +1673,1674c1634,1635 +< let pnode = self.copy_node_up(ctx, Arc::clone(parent_node)).await?; +< pnode.handle_upper_inode_locked(&mut |parent_real_inode:Option| async { +--- +> let pnode = self.copy_node_up(ctx, Arc::clone(parent_node))?; +> pnode.handle_upper_inode_locked(&mut |parent_real_inode| -> Result { +1683c1644 +< if n.in_upper_layer().await { +--- +> if n.in_upper_layer() { +1687,1688c1648,1649 +< name_os, +< ).await; +--- +> utils::to_cstring(name)?.as_c_str(), +> ); +1691c1652 +< let child_ri = parent_real_inode.symlink(ctx, linkname, name).await?; +--- +> let child_ri = parent_real_inode.symlink(ctx, linkname, name)?; +1694c1655 +< n.add_upper_inode(child_ri, true).await; +--- +> n.add_upper_inode(child_ri, true); +1696c1657 +< }).await?; +--- +> })?; +1700,1701c1661,1662 +< let pnode = self.copy_node_up(ctx, Arc::clone(parent_node)).await?; +< let mut new_node: Arc>> = Arc::new(Mutex::new(None)); +--- +> let pnode = self.copy_node_up(ctx, Arc::clone(parent_node))?; +> let mut new_node = None; +1703c1664 +< pnode.handle_upper_inode_locked(&mut |parent_real_inode:Option | async { +--- +> pnode.handle_upper_inode_locked(&mut |parent_real_inode| -> Result { +1713,1715c1674,1676 +< let ino = self.alloc_inode(&path).await?; +< let child_ri = parent_real_inode.symlink(ctx, linkname, name).await?; +< let ovi = OverlayInode::new_from_real_inode(name, ino, path.clone(), child_ri).await; +--- +> let ino = self.alloc_inode(&path)?; +> let child_ri = parent_real_inode.symlink(ctx, linkname, name)?; +> let ovi = OverlayInode::new_from_real_inode(name, ino, path.clone(), child_ri); +1717c1678 +< new_node.lock().await.replace(ovi); +--- +> new_node.replace(ovi); +1719c1680 +< }).await?; +--- +> })?; +1722,1724c1683,1685 +< let arc_node = Arc::new(new_node.lock().await.take().unwrap()); +< self.insert_inode(arc_node.inode, arc_node.clone()).await; +< pnode.insert_child(name, arc_node).await; +--- +> let arc_node = Arc::new(new_node.unwrap()); +> self.insert_inode(arc_node.inode, arc_node.clone()); +> pnode.insert_child(name, arc_node); +1731,1732c1692,1693 +< async fn copy_symlink_up(&self, ctx: Request, node: Arc) -> Result> { +< if node.in_upper_layer().await { +--- +> fn copy_symlink_up(&self, ctx: &Context, node: Arc) -> Result> { +> if node.in_upper_layer() { +1736c1697 +< let parent_node = if let Some(ref n) = node.parent.lock().await.upgrade() { +--- +> let parent_node = if let Some(ref n) = node.parent.lock().unwrap().upgrade() { +1742c1703 +< let (self_layer, _, self_inode) = node.first_layer_inode().await; +--- +> let (self_layer, _, self_inode) = node.first_layer_inode(); +1744,1745c1705,1706 +< if !parent_node.in_upper_layer().await { +< parent_node.create_upper_dir(ctx, None).await?; +--- +> if !parent_node.in_upper_layer() { +> parent_node.create_upper_dir(ctx, None)?; +1749c1710 +< let reply_data = self_layer.readlink(ctx, self_inode).await?; +--- +> let path = self_layer.readlink(ctx, self_inode)?; +1752c1713 +< std::str::from_utf8(&reply_data.data).map_err(|_| Error::from_raw_os_error(libc::EINVAL))?; +--- +> std::str::from_utf8(&path).map_err(|_| Error::from_raw_os_error(libc::EINVAL))?; +1754,1756c1715,1716 +< +< let mut new_upper_real: Arc>> = Arc::new(Mutex::new(None)); +< parent_node.handle_upper_inode_locked(&mut |parent_upper_inode:Option| async { +--- +> let mut new_upper_real = None; +> parent_node.handle_upper_inode_locked(&mut |parent_upper_inode| -> Result { +1760c1720 +< new_upper_real.lock().await.replace(parent_real_inode.symlink(ctx, path, node.name.as_str()).await?); +--- +> new_upper_real.replace(parent_real_inode.symlink(ctx, path, node.name.as_str())?); +1762c1722 +< }).await?; +--- +> })?; +1764c1724 +< if let Some(real_inode) = new_upper_real.lock().await.take() { +--- +> if let Some(real_inode) = new_upper_real { +1766c1726 +< node.add_upper_inode(real_inode, true).await; +--- +> node.add_upper_inode(real_inode, true); +1774,1775c1734,1735 +< async fn copy_regfile_up(&self, ctx: Request, node: Arc) -> Result> { +< if node.in_upper_layer().await { +--- +> fn copy_regfile_up(&self, ctx: &Context, node: Arc) -> Result> { +> if node.in_upper_layer() { +1779c1739 +< let parent_node = if let Some(ref n) = node.parent.lock().await.upgrade() { +--- +> let parent_node = if let Some(ref n) = node.parent.lock().unwrap().upgrade() { +1785,1786c1745,1746 +< let st = node.stat64(ctx).await?; +< let (lower_layer, _, lower_inode) = node.first_layer_inode().await; +--- +> let st = node.stat64(ctx)?; +> let (lower_layer, _, lower_inode) = node.first_layer_inode(); +1788,1789c1748,1749 +< if !parent_node.in_upper_layer().await { +< parent_node.create_upper_dir(ctx, None).await?; +--- +> if !parent_node.in_upper_layer() { +> parent_node.create_upper_dir(ctx, None)?; +1793,1800c1753,1762 +< +< let flags = libc::O_WRONLY ; +< let mode = mode_from_kind_and_perm(st.attr.kind,st.attr.perm) ; +< +< +< let mut upper_handle = Arc::new(Mutex::new(0)); +< let mut upper_real_inode =Arc::new(Mutex::new(None)); +< parent_node.handle_upper_inode_locked(&mut |parent_upper_inode:Option | async { +--- +> let args = CreateIn { +> flags: libc::O_WRONLY as u32, +> mode: st.st_mode, +> umask: 0, +> fuse_flags: 0, +> }; +> +> let mut upper_handle = 0u64; +> let mut upper_real_inode = None; +> parent_node.handle_upper_inode_locked(&mut |parent_upper_inode| -> Result { +1806,1808c1768,1770 +< let (inode, h) = parent_real_inode.create(ctx, node.name.as_str(), mode,flags.try_into().unwrap()).await?; +< *upper_handle.lock().await = h.unwrap_or(0); +< upper_real_inode.lock().await.replace(inode); +--- +> let (inode, h) = parent_real_inode.create(ctx, node.name.as_str(), args)?; +> upper_handle = h.unwrap_or(0); +> upper_real_inode.replace(inode); +1810c1772 +< }).await?; +--- +> })?; +1812c1774 +< let rep = lower_layer.open(ctx, lower_inode, libc::O_RDONLY as u32).await?; +--- +> let (h, _, _) = lower_layer.open(ctx, lower_inode, libc::O_RDONLY as u32, 0)?; +1814c1776 +< let lower_handle = rep.fh; +--- +> let lower_handle = h.unwrap_or(0); +1822c1784 +< +--- +> let mut file = TempFile::new().unwrap().into_file(); +1825,1835c1787,1803 +< +< let ret = lower_layer.read( +< ctx, +< lower_inode, +< lower_handle, +< offset as u64, +< size, +< ).await?; +< +< offset += ret.data.len(); +< +--- +> loop { +> let ret = lower_layer.read( +> ctx, +> lower_inode, +> lower_handle, +> &mut file, +> size, +> offset as u64, +> None, +> 0, +> )?; +> if ret == 0 { +> break; +> } +> +> offset += ret; +> } +1837c1805 +< lower_layer.release(ctx, lower_inode, lower_handle, 0,0, true).await?; +--- +> lower_layer.release(ctx, lower_inode, 0, lower_handle, true, true, None)?; +1838a1807 +> file.seek(SeekFrom::Start(0))?; +1840,1841c1809,1810 +< let u_handle = *upper_handle.lock().await; +< while let Some(ref ri) = upper_real_inode.lock().await.take() { +--- +> +> while let Some(ref ri) = upper_real_inode { +1845c1814,1816 +< *upper_handle.lock().await, +--- +> upper_handle, +> &mut file, +> size, +1847c1818,1819 +< &ret.data, +--- +> None, +> false, +1850,1851c1822,1823 +< ).await?; +< if ret.written == 0 { +--- +> )?; +> if ret == 0 { +1855c1827 +< offset += ret.written as usize; +--- +> offset += ret; +1857a1830,1831 +> // Drop will remove file automatically. +> drop(file); +1859,1860c1833 +< +< if let Some(ri) = upper_real_inode.lock().await.take() { +--- +> if let Some(ri) = upper_real_inode { +1863,1865c1836,1837 +< .release(ctx, ri.inode, u_handle,0, 0, true).await +< { +< let e:std::io::Error = e.into(); +--- +> .release(ctx, ri.inode, 0, upper_handle, true, true, None) +> { +1873c1845 +< node.add_upper_inode(ri, true).await; +--- +> node.add_upper_inode(ri, true); +1879,1880c1851,1852 +< async fn copy_node_up(&self, ctx: Request, node: Arc) -> Result> { +< if node.in_upper_layer().await { +--- +> fn copy_node_up(&self, ctx: &Context, node: Arc) -> Result> { +> if node.in_upper_layer() { +1884c1856 +< let st = node.stat64(ctx).await?; +--- +> let st = node.stat64(ctx)?; +1886,1887c1858,1859 +< if utils::is_dir(&st.attr.kind) { +< node.create_upper_dir(ctx, None).await?; +--- +> if utils::is_dir(st) { +> node.create_upper_dir(ctx, None)?; +1892,1893c1864,1865 +< if st.attr.kind.const_into_mode_t() & libc::S_IFMT == libc::S_IFLNK { +< return self.copy_symlink_up(ctx, Arc::clone(&node)).await; +--- +> if st.st_mode & libc::S_IFMT == libc::S_IFLNK { +> return self.copy_symlink_up(ctx, Arc::clone(&node)); +1897c1869 +< self.copy_regfile_up(ctx, Arc::clone(&node)).await +--- +> self.copy_regfile_up(ctx, Arc::clone(&node)) +1900c1872 +< async fn do_rm(&self, ctx: Request, parent: u64, name: &OsStr, dir: bool) -> Result<()> { +--- +> fn do_rm(&self, ctx: &Context, parent: u64, name: &CStr, dir: bool) -> Result<()> { +1906,1907c1878,1879 +< let pnode = self.lookup_node(ctx, parent, "").await?; +< if pnode.whiteout.load().await{ +--- +> let pnode = self.lookup_node(ctx, parent, "")?; +> if pnode.whiteout.load(Ordering::Relaxed) { +1910c1882 +< let to_name = name.to_str().unwrap(); +--- +> +1912,1913c1884,1886 +< let node = self.lookup_node(ctx, parent, to_name).await?; +< if node.whiteout.load().await { +--- +> let sname = name.to_string_lossy().to_string(); +> let node = self.lookup_node(ctx, parent, sname.as_str())?; +> if node.whiteout.load(Ordering::Relaxed) { +1919,1920c1892,1893 +< self.load_directory(ctx, &node).await?; +< let (count, whiteouts) = node.count_entries_and_whiteout(ctx).await?; +--- +> self.load_directory(ctx, &node)?; +> let (count, whiteouts) = node.count_entries_and_whiteout(ctx)?; +1927,1928c1900,1901 +< if whiteouts > 0 && node.in_upper_layer().await { +< self.empty_node_directory(ctx, Arc::clone(&node)).await?; +--- +> if whiteouts > 0 && node.in_upper_layer() { +> self.empty_node_directory(ctx, Arc::clone(&node))?; +1934,1935c1907,1908 +< let mut need_whiteout = Arc::new(Mutex::new(true)); +< let pnode = self.copy_node_up(ctx, Arc::clone(&pnode)).await?; +--- +> let mut need_whiteout = true; +> let pnode = self.copy_node_up(ctx, Arc::clone(&pnode))?; +1937,1938c1910,1911 +< if node.upper_layer_only().await { +< *need_whiteout.lock().await = false; +--- +> if node.upper_layer_only() { +> need_whiteout = false; +1942,1943c1915,1916 +< if node.in_upper_layer().await { +< pnode.handle_upper_inode_locked(&mut |parent_upper_inode:Option| async { +--- +> if node.in_upper_layer() { +> pnode.handle_upper_inode_locked(&mut |parent_upper_inode| -> Result { +1954c1927 +< *need_whiteout.lock().await = false; +--- +> need_whiteout = false; +1959c1932 +< .rmdir(ctx, parent_real_inode.inode, name).await?; +--- +> .rmdir(ctx, parent_real_inode.inode, name)?; +1963c1936 +< .unlink(ctx, parent_real_inode.inode, name).await?; +--- +> .unlink(ctx, parent_real_inode.inode, name)?; +1967c1940 +< }).await?; +--- +> })?; +1978c1951 +< node.lookups.fetch_sub(1).await; +--- +> node.lookups.fetch_sub(1, Ordering::Relaxed); +1981,1982c1954,1955 +< self.remove_inode(node.inode, path_removed).await; +< pnode.remove_child(node.name.as_str()).await; +--- +> self.remove_inode(node.inode, path_removed); +> pnode.remove_child(node.name.as_str()); +1984c1957 +< if *need_whiteout.lock().await { +--- +> if need_whiteout { +1987c1960 +< pnode.handle_upper_inode_locked(&mut |parent_upper_inode: Option| async { +--- +> pnode.handle_upper_inode_locked(&mut |parent_upper_inode| -> Result { +1996,1998c1969,1971 +< let child_ri = parent_real_inode.create_whiteout(ctx, to_name).await?; +< let path = format!("{}/{}", pnode.path, to_name); +< let ino = self.alloc_inode(&path).await?; +--- +> let child_ri = parent_real_inode.create_whiteout(ctx, sname.as_str())?; +> let path = format!("{}/{}", pnode.path, sname); +> let ino = self.alloc_inode(&path)?; +2000c1973 +< to_name, +--- +> sname.as_str(), +2004c1977 +< ).await); +--- +> )); +2006,2007c1979,1980 +< self.insert_inode(ino, ovi.clone()).await; +< pnode.insert_child(to_name, ovi.clone()).await; +--- +> self.insert_inode(ino, ovi.clone()); +> pnode.insert_child(sname.as_str(), ovi.clone()); +2009c1982 +< }).await?; +--- +> })?; +2015c1988 +< async fn do_fsync( +--- +> fn do_fsync( +2017c1990 +< ctx: Request, +--- +> ctx: &Context, +2024c1997 +< let data = self.get_data(ctx, Some(handle), inode, libc::O_RDONLY as u32).await?; +--- +> let data = self.get_data(ctx, Some(handle), inode, libc::O_RDONLY as u32)?; +2030c2003 +< let real_handle = rh.handle.load().await; +--- +> let real_handle = rh.handle.load(Ordering::Relaxed); +2033c2006 +< rh.layer.fsyncdir(ctx, rh.inode, real_handle, datasync).await.map_err(|e| e.into()) +--- +> rh.layer.fsyncdir(ctx, rh.inode, datasync, real_handle) +2035c2008 +< rh.layer.fsync(ctx, rh.inode, real_handle, datasync).await.map_err(|e| e.into()) +--- +> rh.layer.fsync(ctx, rh.inode, datasync, real_handle) +2042,2044c2015,2017 +< async fn empty_node_directory(&self, ctx: Request, node: Arc) -> Result<()> { +< let st = node.stat64(ctx).await?; +< if !utils::is_dir(&st.attr.kind) { +--- +> fn empty_node_directory(&self, ctx: &Context, node: Arc) -> Result<()> { +> let st = node.stat64(ctx)?; +> if !utils::is_dir(st) { +2049c2022 +< let (layer, in_upper, inode) = node.first_layer_inode().await; +--- +> let (layer, in_upper, inode) = node.first_layer_inode(); +2059c2032 +< .await +--- +> .unwrap() +2066,2068c2039,2040 +< if child.in_upper_layer().await { +< if child.whiteout.load().await { +< let child_name_os = OsStr::new(child.name.as_str()); +--- +> if child.in_upper_layer() { +> if child.whiteout.load(Ordering::Relaxed) { +2072,2073c2044,2045 +< child_name_os, +< ).await? +--- +> utils::to_cstring(child.name.as_str())?.as_c_str(), +> )? +2075,2078c2047,2050 +< let s = child.stat64(ctx).await?; +< let cname: &OsStr = OsStr::new(&child.name); +< if utils::is_dir(&s.attr.kind) { +< let (count, whiteouts) = child.count_entries_and_whiteout(ctx).await?; +--- +> let s = child.stat64(ctx)?; +> let cname = utils::to_cstring(&child.name)?; +> if utils::is_dir(s) { +> let (count, whiteouts) = child.count_entries_and_whiteout(ctx)?; +2080,2083c2052 +< let cb = child.clone(); +< Box::pin(async move { +< self.empty_node_directory(ctx, cb).await +< }).await?; +--- +> self.empty_node_directory(ctx, Arc::clone(&child))?; +2085c2054,2055 +< layer.rmdir(ctx, inode, cname).await? +--- +> +> layer.rmdir(ctx, inode, cname.as_c_str())? +2087c2057 +< layer.unlink(ctx, inode, cname).await?; +--- +> layer.unlink(ctx, inode, cname.as_c_str())?; +2092,2093c2062,2063 +< self.remove_inode(child.inode, Some(child.path.clone())).await; +< node.remove_child(child.name.as_str()).await; +--- +> self.remove_inode(child.inode, Some(child.path.clone())); +> node.remove_child(child.name.as_str()); +2100c2070 +< async fn find_real_info_from_handle( +--- +> fn find_real_info_from_handle( +2104c2074 +< match self.handles.lock().await.get(&handle) { +--- +> match self.handles.lock().unwrap().get(&handle) { +2109c2079 +< rhd.handle.load().await, +--- +> rhd.handle.load(Ordering::Relaxed), +2118,2120c2088,2090 +< async fn find_real_inode(&self, inode: Inode) -> Result<(Arc, Inode)> { +< if let Some(n) = self.get_active_inode(inode).await { +< let (first_layer, _, first_inode) = n.first_layer_inode().await; +--- +> fn find_real_inode(&self, inode: Inode) -> Result<(Arc, Inode)> { +> if let Some(n) = self.get_active_inode(inode) { +> let (first_layer, _, first_inode) = n.first_layer_inode(); +2127c2097 +< async fn get_data( +--- +> fn get_data( +2129c2099 +< ctx: Request, +--- +> ctx: &Context, +2134c2104 +< let no_open = self.no_open.load().await; +--- +> let no_open = self.no_open.load(Ordering::Relaxed); +2137c2107 +< if let Some(v) = self.handles.lock().await.get(&h) { +--- +> if let Some(v) = self.handles.lock().unwrap().get(&h) { +2150c2120 +< let node = self.lookup_node(ctx, inode, "").await?; +--- +> let node = self.lookup_node(ctx, inode, "")?; +2153c2123 +< if node.whiteout.load().await { +--- +> if node.whiteout.load(Ordering::Relaxed) { +2164c2134 +< self.copy_node_up(ctx, Arc::clone(&node)).await?; +--- +> self.copy_node_up(ctx, Arc::clone(&node))?; +2167c2137 +< let (layer, in_upper_layer, inode) = node.first_layer_inode().await; +--- +> let (layer, in_upper_layer, inode) = node.first_layer_inode(); +2181a2152 +> } +2183,2188c2154,2174 +< +< // extend or init the inodes number to one overlay if the current number is done. +< pub async fn extend_inode_alloc(&self,key:u64){ +< let next_inode = key * INODE_ALLOC_BATCH; +< let limit_inode = next_inode + INODE_ALLOC_BATCH -1; +< self.inodes.write().await.extend_inode_number(next_inode, limit_inode); +--- +> impl ZeroCopyReader for File { +> // Copies at most count bytes from self directly into f at offset off +> // without storing it in any intermediate buffers. +> fn read_to( +> &mut self, +> f: &mut dyn FileReadWriteVolatile, +> count: usize, +> off: u64, +> ) -> Result { +> let mut buf = vec![0_u8; count]; +> let slice = unsafe { FileVolatileSlice::from_raw_ptr(buf.as_mut_ptr(), count) }; +> +> // Read from self to slice. +> let ret = self.read_volatile(slice)?; +> if ret > 0 { +> let slice = unsafe { FileVolatileSlice::from_raw_ptr(buf.as_mut_ptr(), ret) }; +> // Write from slice to f at offset off. +> f.write_at_volatile(slice, off) +> } else { +> Ok(0) +> } +2191a2178,2199 +> impl ZeroCopyWriter for File { +> // Copies at most count bytes from f at offset off directly into self +> // without storing it in any intermediate buffers. +> fn write_from( +> &mut self, +> f: &mut dyn FileReadWriteVolatile, +> count: usize, +> off: u64, +> ) -> Result { +> let mut buf = vec![0_u8; count]; +> let slice = unsafe { FileVolatileSlice::from_raw_ptr(buf.as_mut_ptr(), count) }; +> // Read from f at offset off to slice. +> let ret = f.read_at_volatile(slice, off)?; +> +> if ret > 0 { +> let slice = unsafe { FileVolatileSlice::from_raw_ptr(buf.as_mut_ptr(), ret) }; +> // Write from slice to self. +> self.write_volatile(slice) +> } else { +> Ok(0) +> } +> } +2193,2195c2201,2204 +< #[cfg(test)] +< mod tests { +< +--- +> fn available_bytes(&self) -> usize { +> // Max usize +> usize::MAX +> } +2197a2207,2215 +> #[cfg(not(feature = "async-io"))] +> impl BackendFileSystem for OverlayFs { +> /// mount returns the backend file system root inode entry and +> /// the largest inode number it has. +> fn mount(&self) -> Result<(Entry, u64)> { +> let ctx = Context::default(); +> let entry = self.do_lookup(&ctx, self.root_inode(), "")?; +> Ok((entry, VFS_MAX_INO)) +> } +2198a2217,2223 +> /// Provides a reference to the Any trait. This is useful to let +> /// the caller have access to the underlying type behind the +> /// trait. +> fn as_any(&self) -> &dyn std::any::Any { +> self +> } +> } diff --git a/scorpio/src/dicfuse/abi.rs b/scorpio/src/dicfuse/abi.rs new file mode 100644 index 000000000..558153b7e --- /dev/null +++ b/scorpio/src/dicfuse/abi.rs @@ -0,0 +1,68 @@ +use std::time::Duration; + +use fuse3::{raw::reply::{FileAttr, ReplyEntry}, FileType, Timestamp}; + + +pub fn default_file_entry(inode:u64) -> ReplyEntry { + ReplyEntry{ + ttl: Duration::new(500, 0), + attr: FileAttr{ + ino: inode, + size: 0, + blocks: 0, + atime: Timestamp::new(0, 0), + mtime: Timestamp::new(0, 0), + ctime: Timestamp::new(0, 0), + kind: FileType::RegularFile, + perm: 0o755, + nlink: 0, + uid: 0, + gid: 0, + rdev: 0, + blksize: 0, + }, + generation: 0, + } +} + +pub fn default_dic_entry(inode:u64) -> ReplyEntry { + ReplyEntry{ + ttl: Duration::new(500, 0), + attr: FileAttr{ + ino: inode, + size: 0, + blocks: 0, + atime: Timestamp::new(0, 0), + mtime: Timestamp::new(0, 0), + ctime: Timestamp::new(0, 0), + kind: FileType::Directory, + perm: 0o755, + nlink: 0, + uid: 0, + gid: 0, + rdev: 0, + blksize: 0, + }, + generation: 0, + } +} +// pub struct stat64 { +// pub st_dev: ::dev_t, // Device ID of the device containing the file +// pub st_ino: ::ino64_t, // Inode number of the file +// pub st_nlink: ::nlink_t, // Number of hard links to the file +// pub st_mode: ::mode_t, // File type and mode (permissions) +// pub st_uid: ::uid_t, // User ID of the file's owner +// pub st_gid: ::gid_t, // Group ID of the file's owner +// __pad0: ::c_int, // Padding for alignment (not used) +// pub st_rdev: ::dev_t, // Device ID (if the file is a special file) +// pub st_size: ::off_t, // Total size of the file in bytes +// pub st_blksize: ::blksize_t, // Block size for filesystem I/O +// pub st_blocks: ::blkcnt64_t, // Number of blocks allocated for the file +// pub st_atime: ::time_t, // Time of last access +// pub st_atime_nsec: i64, // Nanoseconds of last access time +// pub st_mtime: ::time_t, // Time of last modification +// pub st_mtime_nsec: i64, // Nanoseconds of last modification time +// pub st_ctime: ::time_t, // Time of last status change +// pub st_ctime_nsec: i64, // Nanoseconds of last status change time +// __reserved: [i64; 3], // Reserved for future use (not used) +// } \ No newline at end of file diff --git a/scorpio/src/dicfuse/async_io.rs b/scorpio/src/dicfuse/async_io.rs new file mode 100644 index 000000000..0575f5227 --- /dev/null +++ b/scorpio/src/dicfuse/async_io.rs @@ -0,0 +1,170 @@ + +use std::ffi::OsStr; +use std::num::NonZeroU32; + +use bytes::Bytes; +use fuse3::raw::reply::DirectoryEntry; +use fuse3::raw::prelude::*; +use fuse3::{Errno, Inode, Result}; + +use futures::stream::{iter, Iter}; +use std::vec::IntoIter; + +use crate::dicfuse::store::IntoEntry; + +use super::Dicfuse; + +impl Filesystem for Dicfuse { + /// dir entry stream given by [`readdir`][Filesystem::readdir]. + type DirEntryStream<'a> + =Iter>> + where + Self: 'a; + /// dir entry stream given by [`readdir`][Filesystem::readdir]. + type DirEntryPlusStream<'a> + = Iter>> + where + Self: 'a; + + + /// look up a directory entry by name and get its attributes. + async fn lookup(&self, _req: Request, parent: Inode, name: &OsStr) -> Result { + let store = self.store.clone(); + let mut ppath = store.find_path(parent).await.ok_or_else(|| std::io::Error::from_raw_os_error(libc::ENODATA))?; + //let pitem = store.get_inode(parent).await?; + ppath.push(name.to_string_lossy().into_owned()); + let chil = store.get_by_path(&ppath.to_string()).await?; + Ok(chil.into_reply().await) + } + /// initialize filesystem. Called before any other filesystem method. + async fn init(&self, _req: Request) -> Result{ + + let s = self.store.clone(); + s.import().await; + Ok(ReplyInit { + max_write: NonZeroU32::new(128 * 1024).unwrap(), + }) + } + + + /// clean up filesystem. Called on filesystem exit which is fuseblk, in normal fuse filesystem, + /// kernel may call forget for root. There is some discuss for this + /// , + /// + async fn destroy(&self, _req: Request){ + } + + /// get file attributes. If `fh` is None, means `fh` is not set. + async fn getattr( + &self, + _req: Request, + inode: Inode, + _fh: Option, + _flags: u32, + ) -> Result { + let store = self.store.clone(); + let _i = store.find_path(inode).await.ok_or_else(|| std::io::Error::from_raw_os_error(libc::ENODATA))?; + let item = store.get_inode(inode).await?; + let e =item.get_stat().await; + Ok(ReplyAttr { ttl: e.ttl, attr: e.attr }) + } + /// open a directory. Filesystem may store an arbitrary file handle (pointer, index, etc) in + /// `fh`, and use this in other all other directory stream operations + /// ([`readdir`][Filesystem::readdir], [`releasedir`][Filesystem::releasedir], + /// [`fsyncdir`][Filesystem::fsyncdir]). Filesystem may also implement stateless directory + /// I/O and not store anything in `fh`. A file system need not implement this method if it + /// sets [`MountOptions::no_open_dir_support`][crate::MountOptions::no_open_dir_support] and + /// if the kernel supports `FUSE_NO_OPENDIR_SUPPORT`. + async fn opendir(&self, _req: Request, _inode: Inode, _flags: u32) -> Result { + Ok(ReplyOpen { fh: 0, flags: 0 }) + } + + /// open a file. Open flags (with the exception of `O_CREAT`, `O_EXCL` and `O_NOCTTY`) are + /// available in flags. Filesystem may store an arbitrary file handle (pointer, index, etc) in + /// fh, and use this in other all other file operations (read, write, flush, release, fsync). + /// Filesystem may also implement stateless file I/O and not store anything in fh. There are + /// also some flags (`direct_io`, `keep_cache`) which the filesystem may set, to change the way + /// the file is opened. A filesystem need not implement this method if it + /// sets [`MountOptions::no_open_support`][crate::MountOptions::no_open_support] and if the + /// kernel supports `FUSE_NO_OPEN_SUPPORT`. + /// + /// # Notes: + /// + /// See `fuse_file_info` structure in + /// [fuse_common.h](https://libfuse.github.io/doxygen/include_2fuse__common_8h_source.html) for + /// more details. + async fn open(&self, _req: Request, _inode: Inode, _flags: u32) -> Result { + Ok(ReplyOpen { fh: 0, flags: 0 }) + } + /// read data. Read should send exactly the number of bytes requested except on EOF or error, + /// otherwise the rest of the data will be substituted with zeroes. An exception to this is + /// when the file has been opened in `direct_io` mode, in which case the return value of the + /// read system call will reflect the return value of this operation. `fh` will contain the + /// value set by the open method, or will be undefined if the open method didn't set any value. + async fn read( + &self, + _req: Request, + _inode: Inode, + _fh: u64, + _offset: u64, + _size: u32, + ) -> Result { + Ok(ReplyData{ + data: Bytes::new(), + }) + } + async fn access(&self,_req:Request,inode:Inode,_mask:u32) -> Result<()> { + self.store.get_inode(inode).await?; + Ok(()) + } + + async fn write(&self,_req:Request,_inode:Inode,_fh:u64,_offset:u64,data: &[u8],_write_flags:u32,_flags:u32,) -> Result { + Ok(ReplyWrite { written: data.len() as u32 }) + } + async fn readdir(& self,_req:Request,parent:Inode,fh:u64,offset:i64,) -> Result > > { + let items = self.store.do_readdir(parent, fh, offset as u64).await?; + let mut d:Vec> = Vec::new(); + for (index,item) in items.into_iter().enumerate(){ + d.push(Ok( + DirectoryEntry{ + inode: item.get_inode(), + kind: item.get_filetype().await, + name: item.get_name().into(), + offset: (index+1) as i64, + } + )); + } + Ok(ReplyDirectory { entries: iter(d.into_iter()) }) + } + async fn readdirplus(&self,_req:Request,parent:Inode,fh:u64,offset:u64,_lock_owner:u64,) -> Result > > { + let items = self.store.do_readdir(parent, fh, offset).await?; + let mut d:Vec> = Vec::new(); + for (index,item) in items.into_iter().enumerate(){ + if index >= offset.try_into().unwrap() { + let attr = item.get_stat().await; + let e_name = + if index ==0{ + String::from(".") + }else if index==1{ + String::from("..") + }else{ + item.get_name() + }; + d.push(Ok( + DirectoryEntryPlus{ + inode: item.get_inode(), + kind: item.get_filetype().await, + name: e_name.into(), + offset: (index+1) as i64, + generation: 0, + attr: attr.attr, + entry_ttl: attr.ttl, + attr_ttl: attr.ttl, + } + )); + } + + } + Ok(ReplyDirectoryPlus { entries: iter(d.into_iter()) }) + } +} \ No newline at end of file diff --git a/scorpio/src/dicfuse/fuse.rs b/scorpio/src/dicfuse/fuse.rs deleted file mode 100644 index 3a27ad046..000000000 --- a/scorpio/src/dicfuse/fuse.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::time::Duration; - -use libc::stat64; -use fuse_backend_rs::{abi::fuse_abi::Attr, api::filesystem::Entry}; -const BLOCK_SIZE: u32 = 512; -fn default_stat64(inode:u64) -> stat64 { - let t = Attr{ - ino: inode, // Default inode number - size: 512 , // Default file size - blocks: 8, // Default number of blocks - atime: 0, // Default last access time - mtime: 0, // Default last modification time - ctime: 0, // Default last status change time - atimensec: 0, // Default nanoseconds of last access time - mtimensec: 0, // Default nanoseconds of last modification time - ctimensec: 0, // Default nanoseconds of last status change time - mode: 0o100444, // Default file mode (r--r--r--) - //mode: 0o0040755, // Default file mode (r-xr-xr-x) - nlink: 2, // Default number of hard links - uid: 1000, // Default user ID - gid: 1000, // Default group ID - rdev: 0, // Default device ID - blksize: BLOCK_SIZE, // Default block size - flags: 0, // Default flags - }; - t.into() -} - -pub fn default_file_entry(inode:u64) -> Entry { - Entry{ - inode, - generation: 0, - attr: default_stat64(inode), - attr_flags: 0, - attr_timeout: Duration::from_secs(u64::MAX), - entry_timeout: Duration::from_secs(u64::MAX), - } // Return a default Entry instance -} - -pub fn default_dic_entry(inode:u64) -> Entry { - let mut d = default_stat64(inode); - d.st_mode = 0o0040755; - Entry{ - inode, - generation: 0, - attr: d, - attr_flags: 0, - attr_timeout: Duration::from_secs(u64::MAX), - entry_timeout: Duration::from_secs(u64::MAX), - } // Return a default Dictionary Entry instance -} -// pub struct stat64 { -// pub st_dev: ::dev_t, // Device ID of the device containing the file -// pub st_ino: ::ino64_t, // Inode number of the file -// pub st_nlink: ::nlink_t, // Number of hard links to the file -// pub st_mode: ::mode_t, // File type and mode (permissions) -// pub st_uid: ::uid_t, // User ID of the file's owner -// pub st_gid: ::gid_t, // Group ID of the file's owner -// __pad0: ::c_int, // Padding for alignment (not used) -// pub st_rdev: ::dev_t, // Device ID (if the file is a special file) -// pub st_size: ::off_t, // Total size of the file in bytes -// pub st_blksize: ::blksize_t, // Block size for filesystem I/O -// pub st_blocks: ::blkcnt64_t, // Number of blocks allocated for the file -// pub st_atime: ::time_t, // Time of last access -// pub st_atime_nsec: i64, // Nanoseconds of last access time -// pub st_mtime: ::time_t, // Time of last modification -// pub st_mtime_nsec: i64, // Nanoseconds of last modification time -// pub st_ctime: ::time_t, // Time of last status change -// pub st_ctime_nsec: i64, // Nanoseconds of last status change time -// __reserved: [i64; 3], // Reserved for future use (not used) -// } \ No newline at end of file diff --git a/scorpio/src/dicfuse/mod.rs b/scorpio/src/dicfuse/mod.rs index 1345fc779..7ea02da17 100644 --- a/scorpio/src/dicfuse/mod.rs +++ b/scorpio/src/dicfuse/mod.rs @@ -1,332 +1,335 @@ mod store; -mod fuse; +mod abi; +mod async_io; +use std::sync::Arc; -use std::{sync::Arc, time::Duration}; -use std::io::Result; -use fuse_backend_rs::{abi::fuse_abi::FsOptions, api::filesystem::{Context, Entry, FileSystem}}; -use tokio::task::JoinHandle; - -use store::{DictionaryStore, IntoEntry}; +use store::DictionaryStore; pub struct Dicfuse{ pub store: Arc, } #[allow(unused)] impl Dicfuse{ - pub fn new() -> Self { + pub async fn new() -> Self { Self { - store: DictionaryStore::new().into(), // Assuming DictionaryStore has a new() method + store: DictionaryStore::new().await.into(), // Assuming DictionaryStore has a new() method } } - fn spawn(&self, f: F) -> JoinHandle - where - F: FnOnce(Arc) -> Fut, - Fut: std::future::Future + Send + 'static, - O: Send + 'static, - { - let inner = self.store.clone(); - tokio::task::spawn(f(inner)) - } - } -#[allow(unused)] -impl FileSystem for Dicfuse{ - type Inode = u64; +// #[allow(unused)] +// impl FileSystem for Dicfuse{ +// type Inode = u64; - type Handle = u64; +// type Handle = u64; - fn init(&self, capable:FsOptions) -> Result { - println!("Dicfuse init...."); - let s = self.store.clone(); - s.import(); +// fn init(&self, capable:FsOptions) -> Result { +// println!("Dicfuse init...."); +// let s = self.store.clone(); +// s.import(); - //let mut ops = FsOptions::DO_READDIRPLUS | FsOptions::READDIRPLUS_AUTO; - Ok(fuse_backend_rs::abi::fuse_abi::FsOptions::empty()) - } +// //let mut ops = FsOptions::DO_READDIRPLUS | FsOptions::READDIRPLUS_AUTO; +// Ok(fuse_backend_rs::abi::fuse_abi::FsOptions::empty()) +// } - fn destroy(&self) {} +// fn destroy(&self) {} - fn lookup(&self, ctx: &Context, parent: Self::Inode, name: &std::ffi::CStr) -> Result { - println!("[lookup]: ctx:{}, parnet inode:{},name :{:?}",ctx.pid,parent,name); - let store = self.store.clone(); - let mut ppath = store.find_path(parent).ok_or_else(|| std::io::Error::from_raw_os_error(libc::ENODATA))?; - let pitem = store.get_inode(parent)?; - ppath.push(name.to_string_lossy().into_owned()); - let chil = store.get_by_path(&ppath.to_string())?; - let ree = chil.into_entry(); - println!("[lookup-out]: entry:{:?}",ree); - Ok(ree) - } +// fn lookup(&self, ctx: &Context, parent: Self::Inode, name: &std::ffi::CStr) -> Result { +// println!("[lookup]: ctx:{}, parnet inode:{},name :{:?}",ctx.pid,parent,name); +// let store = self.store.clone(); +// let mut ppath = store.find_path(parent).ok_or_else(|| std::io::Error::from_raw_os_error(libc::ENODATA))?; +// let pitem = store.get_inode(parent)?; +// ppath.push(name.to_string_lossy().into_owned()); +// let chil = store.get_by_path(&ppath.to_string())?; +// let ree = chil.into_entry(); +// println!("[lookup-out]: entry:{:?}",ree); +// Ok(ree) +// } - fn forget(&self, ctx: &Context, inode: Self::Inode, count: u64) { - println!("[forget]: ctx:{}, inode:{},count :{}",ctx.pid,inode,count); - } +// fn forget(&self, ctx: &Context, inode: Self::Inode, count: u64) { +// println!("[forget]: ctx:{}, inode:{},count :{}",ctx.pid,inode,count); +// } - fn batch_forget(&self, ctx: &Context, requests: Vec<(Self::Inode, u64)>) { - println!("[batch-forget]: ctx:{}",ctx.pid); - for (inode, count) in requests { - self.forget(ctx, inode, count) - } - } +// fn batch_forget(&self, ctx: &Context, requests: Vec<(Self::Inode, u64)>) { +// println!("[batch-forget]: ctx:{}",ctx.pid); +// for (inode, count) in requests { +// self.forget(ctx, inode, count) +// } +// } - fn getattr( - &self, - ctx: &Context, - inode: Self::Inode, - handle: Option, - ) -> std::io::Result<(libc::stat64, std::time::Duration)> { - println!("[getattr]: ctx:{}, inode:{},handle :{:?}",ctx.pid,inode ,handle); - let store = self.store.clone(); - let i = store.find_path(inode).ok_or_else(|| std::io::Error::from_raw_os_error(libc::ENODATA))?; - let item = store.get_inode(inode)?; - let mut entry = item.get_stat(); +// fn getattr( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// handle: Option, +// ) -> std::io::Result<(libc::stat64, std::time::Duration)> { +// println!("[getattr]: ctx:{}, inode:{},handle :{:?}",ctx.pid,inode ,handle); +// let store = self.store.clone(); +// let i = store.find_path(inode).ok_or_else(|| std::io::Error::from_raw_os_error(libc::ENODATA))?; +// let item = store.get_inode(inode)?; +// let mut entry = item.get_stat(); - println!("[getattr-out]:entry ->{:?}",entry.attr); - Ok((entry.attr,Duration::from_secs(0))) - } +// println!("[getattr-out]:entry ->{:?}",entry.attr); +// Ok((entry.attr,Duration::from_secs(0))) +// } - fn setattr( - &self, - ctx: &Context, - inode: Self::Inode, - attr: libc::stat64, - handle: Option, - valid: fuse_backend_rs::abi::fuse_abi::SetattrValid, - ) -> std::io::Result<(libc::stat64, std::time::Duration)> { - println!("[setattr]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn setattr( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// attr: libc::stat64, +// handle: Option, +// valid: fuse_backend_rs::abi::fuse_abi::SetattrValid, +// ) -> std::io::Result<(libc::stat64, std::time::Duration)> { +// println!("[setattr]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn mknod( - &self, - ctx: &Context, - inode: Self::Inode, - name: &std::ffi::CStr, - mode: u32, - rdev: u32, - umask: u32, - ) -> std::io::Result { - println!("[mknod]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn mknod( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// name: &std::ffi::CStr, +// mode: u32, +// rdev: u32, +// umask: u32, +// ) -> std::io::Result { +// println!("[mknod]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn mkdir( - &self, - ctx: &Context, - parent: Self::Inode, - name: &std::ffi::CStr, - mode: u32, - umask: u32, - ) -> std::io::Result { - println!("[mkdir]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn mkdir( +// &self, +// ctx: &Context, +// parent: Self::Inode, +// name: &std::ffi::CStr, +// mode: u32, +// umask: u32, +// ) -> std::io::Result { +// println!("[mkdir]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn unlink(&self, ctx: &Context, parent: Self::Inode, name: &std::ffi::CStr) -> std::io::Result<()> { - println!("[unlink]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn unlink(&self, ctx: &Context, parent: Self::Inode, name: &std::ffi::CStr) -> std::io::Result<()> { +// println!("[unlink]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn rmdir(&self, ctx: &Context, parent: Self::Inode, name: &std::ffi::CStr) -> std::io::Result<()> { - println!("[rmdir]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn rmdir(&self, ctx: &Context, parent: Self::Inode, name: &std::ffi::CStr) -> std::io::Result<()> { +// println!("[rmdir]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn rename( - &self, - ctx: &Context, - olddir: Self::Inode, - oldname: &std::ffi::CStr, - newdir: Self::Inode, - newname: &std::ffi::CStr, - flags: u32, - ) -> std::io::Result<()> { - println!("[rename]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn rename( +// &self, +// ctx: &Context, +// olddir: Self::Inode, +// oldname: &std::ffi::CStr, +// newdir: Self::Inode, +// newname: &std::ffi::CStr, +// flags: u32, +// ) -> std::io::Result<()> { +// println!("[rename]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn link( - &self, - ctx: &Context, - inode: Self::Inode, - newparent: Self::Inode, - newname: &std::ffi::CStr, - ) -> std::io::Result { - println!("[link]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn link( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// newparent: Self::Inode, +// newname: &std::ffi::CStr, +// ) -> std::io::Result { +// println!("[link]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn open( - &self, - ctx: &Context, - inode: Self::Inode, - flags: u32, - fuse_flags: u32, - ) -> std::io::Result<(Option, fuse_backend_rs::abi::fuse_abi::OpenOptions, Option)> { - println!("[open]: not implement."); - // Matches the behavior of libfuse. - Ok((None, fuse_backend_rs::abi::fuse_abi::OpenOptions::empty(), None)) - } +// fn open( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// flags: u32, +// fuse_flags: u32, +// ) -> std::io::Result<(Option, fuse_backend_rs::abi::fuse_abi::OpenOptions, Option)> { +// println!("[open]: not implement."); +// // Matches the behavior of libfuse. +// Ok((None, fuse_backend_rs::abi::fuse_abi::OpenOptions::empty(), None)) +// } - fn create( - &self, - ctx: &Context, - parent: Self::Inode, - name: &std::ffi::CStr, - args: fuse_backend_rs::abi::fuse_abi::CreateIn, - ) -> std::io::Result<(Entry, Option, fuse_backend_rs::abi::fuse_abi::OpenOptions, Option)> { - println!("[create]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn create( +// &self, +// ctx: &Context, +// parent: Self::Inode, +// name: &std::ffi::CStr, +// args: fuse_backend_rs::abi::fuse_abi::CreateIn, +// ) -> std::io::Result<(Entry, Option, fuse_backend_rs::abi::fuse_abi::OpenOptions, Option)> { +// println!("[create]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn flush( - &self, - ctx: &Context, - inode: Self::Inode, - handle: Self::Handle, - lock_owner: u64, - ) -> std::io::Result<()> { - println!("[flush]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn flush( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// handle: Self::Handle, +// lock_owner: u64, +// ) -> std::io::Result<()> { +// println!("[flush]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn fsync( - &self, - ctx: &Context, - inode: Self::Inode, - datasync: bool, - handle: Self::Handle, - ) -> std::io::Result<()> { - println!("[fsync]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn fsync( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// datasync: bool, +// handle: Self::Handle, +// ) -> std::io::Result<()> { +// println!("[fsync]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn fallocate( - &self, - ctx: &Context, - inode: Self::Inode, - handle: Self::Handle, - mode: u32, - offset: u64, - length: u64, - ) -> std::io::Result<()> { - println!("[fallocate]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn fallocate( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// handle: Self::Handle, +// mode: u32, +// offset: u64, +// length: u64, +// ) -> std::io::Result<()> { +// println!("[fallocate]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn release( - &self, - ctx: &Context, - inode: Self::Inode, - flags: u32, - handle: Self::Handle, - flush: bool, - flock_release: bool, - lock_owner: Option, - ) -> std::io::Result<()> { - println!("[release]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn release( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// flags: u32, +// handle: Self::Handle, +// flush: bool, +// flock_release: bool, +// lock_owner: Option, +// ) -> std::io::Result<()> { +// println!("[release]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn statfs(&self, ctx: &Context, inode: Self::Inode) -> std::io::Result { - println!("[statfs]: not implement."); - // Safe because we are zero-initializing a struct with only POD fields. - let mut st: libc::statvfs64 = unsafe { std::mem::zeroed() }; - // This matches the behavior of libfuse as it returns these values if the - // filesystem doesn't implement this method. - st.f_namemax = 255; - st.f_bsize = 512; - Ok(st) - } +// fn statfs(&self, ctx: &Context, inode: Self::Inode) -> std::io::Result { +// println!("[statfs]: not implement."); +// // Safe because we are zero-initializing a struct with only POD fields. +// let mut st: libc::statvfs64 = unsafe { std::mem::zeroed() }; +// // This matches the behavior of libfuse as it returns these values if the +// // filesystem doesn't implement this method. +// st.f_namemax = 255; +// st.f_bsize = 512; +// Ok(st) +// } - fn setxattr( - &self, - ctx: &Context, - inode: Self::Inode, - name: &std::ffi::CStr, - value: &[u8], - flags: u32, - ) -> std::io::Result<()> { - println!("[setxattr]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn setxattr( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// name: &std::ffi::CStr, +// value: &[u8], +// flags: u32, +// ) -> std::io::Result<()> { +// println!("[setxattr]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn getxattr( - &self, - ctx: &Context, - inode: Self::Inode, - name: &std::ffi::CStr, - size: u32, - ) -> std::io::Result { - println!("[getxattr]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn getxattr( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// name: &std::ffi::CStr, +// size: u32, +// ) -> std::io::Result { +// println!("[getxattr]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn listxattr( - &self, - ctx: &Context, - inode: Self::Inode, - size: u32, - ) -> std::io::Result { - println!("[listxattr]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn listxattr( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// size: u32, +// ) -> std::io::Result { +// println!("[listxattr]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn opendir( - &self, - ctx: &Context, - inode: Self::Inode, - flags: u32, - ) -> std::io::Result<(Option, fuse_backend_rs::abi::fuse_abi::OpenOptions)> { - // Matches the behavior of libfuse. - println!("[opendir]: not implement."); - Ok((Some(inode), fuse_backend_rs::abi::fuse_abi::OpenOptions::empty())) - } +// fn opendir( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// flags: u32, +// ) -> std::io::Result<(Option, fuse_backend_rs::abi::fuse_abi::OpenOptions)> { +// // Matches the behavior of libfuse. +// println!("[opendir]: not implement."); +// Ok((Some(inode), fuse_backend_rs::abi::fuse_abi::OpenOptions::empty())) +// } - fn readdir( - &self, - ctx: &Context, - inode: Self::Inode, - handle: Self::Handle, - size: u32, - offset: u64, - add_entry: &mut dyn FnMut(fuse_backend_rs::api::filesystem::DirEntry) -> std::io::Result, - ) -> std::io::Result<()> { - println!("[readdir]: inode:{},handle:{},size:{},offset:{}",inode,handle,size,offset); - self.store.do_readdir(ctx, inode, handle, size, offset, add_entry) - } +// fn readdir( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// handle: Self::Handle, +// size: u32, +// offset: u64, +// add_entry: &mut dyn FnMut(fuse_backend_rs::api::filesystem::DirEntry) -> std::io::Result, +// ) -> std::io::Result<()> { +// println!("[readdir]: inode:{},handle:{},size:{},offset:{}",inode,handle,size,offset); +// self.store.do_readdir(ctx, inode, handle, size, offset, add_entry) +// } - fn readdirplus( - &self, - ctx: &Context, - inode: Self::Inode, - handle: Self::Handle, - size: u32, - offset: u64, - add_entry: &mut dyn FnMut(fuse_backend_rs::api::filesystem::DirEntry, Entry) -> std::io::Result, - ) -> std::io::Result<()> { - println!("[readdirplus]: not implement."); - Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) - } +// fn readdirplus( +// &self, +// ctx: &Context, +// inode: Self::Inode, +// handle: Self::Handle, +// size: u32, +// offset: u64, +// add_entry: &mut dyn FnMut(fuse_backend_rs::api::filesystem::DirEntry, Entry) -> std::io::Result, +// ) -> std::io::Result<()> { +// println!("[readdirplus]: not implement."); +// Err(std::io::Error::from_raw_os_error(libc::ENOSYS)) +// } - fn access(&self, ctx: &Context, inode: Self::Inode, mask: u32) -> std::io::Result<()> { - println!("[access]: not implement."); - Ok(()) - } +// fn access(&self, ctx: &Context, inode: Self::Inode, mask: u32) -> std::io::Result<()> { +// println!("[access]: not implement."); +// Ok(()) +// } -} +// } #[cfg(test)] mod tests { - use std::{path::Path, sync::Arc,thread}; + use std::ffi::OsStr; + + use tokio::signal; - use fuse_backend_rs::{ api::server::Server, transport::{FuseChannel, FuseSession}}; - use signal_hook::{consts::TERM_SIGNALS, iterator::Signals}; + use crate::dicfuse::Dicfuse; + #[tokio::test] + async fn test_mount_dic(){ + let fs = Dicfuse::new().await; + let mountpoint =OsStr::new("/home/luxian/dic") ; + let mut mount_handle = crate::server::mount_filesystem(fs, mountpoint).await; + let handle = &mut mount_handle; + tokio::select! { + res = handle => res.unwrap(), + _ = signal::ctrl_c() => { + mount_handle.unmount().await.unwrap() + } + } + + } + } diff --git a/scorpio/src/dicfuse/model.rs b/scorpio/src/dicfuse/model.rs deleted file mode 100644 index e69de29bb..000000000 diff --git a/scorpio/src/dicfuse/store.rs b/scorpio/src/dicfuse/store.rs index 8db3710f2..4768f5890 100644 --- a/scorpio/src/dicfuse/store.rs +++ b/scorpio/src/dicfuse/store.rs @@ -1,22 +1,23 @@ -use fuse_backend_rs::api::filesystem::{Context, Entry}; +use fuse3::raw::reply::{FileAttr, ReplyEntry}; +use fuse3::{FileType, Timestamp}; /// Read only file system for obtaining and displaying monorepo directory information use reqwest::Client; // Import Response explicitly use serde::{Deserialize, Serialize}; +use tokio::sync::Mutex; use std::io; use std::sync::atomic::AtomicU64; +use std::time::Duration; use std::{collections::HashMap, error::Error}; use std::collections::VecDeque; use once_cell::sync::Lazy; use radix_trie::{self, TrieCommon}; -use std::sync::{Arc,Mutex}; -use fuse_backend_rs::api::filesystem::DirEntry; -use crate::fuse::READONLY_INODE; -use crate::get_handle; +use std::sync::Arc; +use crate::READONLY_INODE; -use super::fuse::{self, default_dic_entry, default_file_entry}; +use super::abi::{default_dic_entry, default_file_entry}; use crate::util::GPath; const MEGA_TREE_URL: &str = "localhost:8000";//TODO: make it configable const UNKNOW_INODE: u64 = 0; // illegal inode number; @@ -68,22 +69,29 @@ impl DicItem { self.path_name.name() } // add a children item - pub fn push_children(&self,children:Arc){ - self.children.lock().unwrap().insert(children.get_name(), children); + pub async fn push_children(&self,children:Arc){ + self.children.lock().await.insert(children.get_name(), children); } // get the inode pub fn get_inode(&self)-> u64{ self.inode } - fn get_tyep(&self) -> ContentType{ - let t = self.content_type.lock().unwrap(); + async fn get_tyep(&self) -> ContentType{ + let t = self.content_type.lock().await; match *t{ ContentType::File => ContentType::File, ContentType::Dictionary(a) => ContentType::Dictionary(a), } } - pub fn get_stat(&self) ->Entry{ - match self.get_tyep(){ + pub async fn get_filetype(&self)-> FileType{ + let t = self.content_type.lock().await; + match *t{ + ContentType::File => FileType::RegularFile, + ContentType::Dictionary(_) => FileType::Directory, + } + } + pub async fn get_stat(&self) ->ReplyEntry{ + match self.get_tyep().await{ ContentType::File => default_file_entry(self.inode), ContentType::Dictionary(_) => default_dic_entry(self.inode), } @@ -91,14 +99,35 @@ impl DicItem { } pub trait IntoEntry { - fn into_entry(self) -> Entry; + // async fn into_entry(self) -> Entry; + async fn into_reply(self) -> ReplyEntry; } impl IntoEntry for Arc { - fn into_entry(self) -> Entry { - match *self.content_type.lock().unwrap() { - ContentType::File => fuse::default_file_entry(self.inode), - ContentType::Dictionary(_) => fuse::default_dic_entry(self.inode), + async fn into_reply(self) -> ReplyEntry { + ReplyEntry{ + ttl: Duration::new(500, 0), + attr: FileAttr{ + ino: self.get_inode(), + size: 0, + blocks: 0, + atime: Timestamp::new(0, 0), + mtime: Timestamp::new(0, 0), + ctime: Timestamp::new(0, 0), + kind: { + match *self.content_type.lock().await { + ContentType::File => FileType::RegularFile, + ContentType::Dictionary(_) => FileType::Directory, + } + }, + perm: 0o755, + nlink: 0, + uid: 0, + gid: 0, + rdev: 0, + blksize: 0, + }, + generation: 0, } } } @@ -143,20 +172,20 @@ impl DictionaryStore { let items = fetch_tree("").await.unwrap().data.clone() ; - let root_inode = self.inodes.lock().unwrap().get(&1).unwrap().clone(); + let root_inode = self.inodes.lock().await.get(&1).unwrap().clone(); for it in items{ println!("root item:{:?}",it); - self.update_inode(root_inode.clone(),it); + self.update_inode(root_inode.clone(),it).await; } loop {//BFS to look up all dictionary - if self.queue.lock().unwrap().is_empty(){ + if self.queue.lock().await.is_empty(){ break; } - let one_inode = self.queue.lock().unwrap().pop_front().unwrap(); + let one_inode = self.queue.lock().await.pop_front().unwrap(); let mut new_items = Vec::new(); { - let it = self.inodes.lock().unwrap().get(&one_inode).unwrap().clone(); - let mut ct =it.content_type.lock().unwrap(); + let it = self.inodes.lock().await.get(&one_inode).unwrap().clone(); + let mut ct =it.content_type.lock().await; let path=String::new(); if let ContentType::Dictionary(load) = *ct{ if !load{ @@ -176,7 +205,7 @@ impl DictionaryStore { let mut pc = it.clone(); for newit in new_items { println!("import item :{:?}",newit); - self.update_inode(pc.clone(),newit); // Await the update_inode call + self.update_inode(pc.clone(),newit).await; // Await the update_inode call } } @@ -185,7 +214,7 @@ impl DictionaryStore { //queue.clear(); } - pub fn new() -> Self { + pub async fn new() -> Self { let mut init = DictionaryStore { next_inode: AtomicU64::new(2), inodes: Arc::new(Mutex::new(HashMap::new())), @@ -199,64 +228,53 @@ impl DictionaryStore { children: Mutex::new(HashMap::new()), parent: UNKNOW_INODE, // root dictory has no parent }; - init.inodes.lock().unwrap().insert(1, root_item.into()); + init.inodes.lock().await.insert(1, root_item.into()); init } - fn update_inode(&self,pitem:Arc,item:Item){ + async fn update_inode(&self,pitem:Arc,item:Item){ self.next_inode.fetch_add(1, std::sync::atomic::Ordering::Relaxed); let alloc_inode = self.next_inode.load(std::sync::atomic::Ordering::Relaxed); assert!(alloc_inode < READONLY_INODE ); if item.content_type=="directory"{ - self.queue.lock().unwrap().push_back(alloc_inode); + self.queue.lock().await.push_back(alloc_inode); } let parent = pitem; let newitem = Arc::new(DicItem::new(alloc_inode, parent.get_inode(),item)); - parent.push_children(newitem.clone()); - self.radix_trie.lock().unwrap().insert(newitem.get_path(), alloc_inode); - self.inodes.lock().unwrap().insert(alloc_inode, newitem); + parent.push_children(newitem.clone()).await; + self.radix_trie.lock().await.insert(newitem.get_path(), alloc_inode); + self.inodes.lock().await.insert(alloc_inode, newitem); } - pub fn import(&self){ - let handler = get_handle(); + pub async fn import(&self){ // 在阻塞线程中运行异步任务 - let items = futures::executor::block_on(async move { - handler.spawn(async move { - fetch_tree("").await.unwrap().data - }).await.unwrap() - }); - - + let items = fetch_tree("").await.unwrap().data; - let root_inode = self.inodes.lock().unwrap().get(&1).unwrap().clone(); + let root_inode = self.inodes.lock().await.get(&1).unwrap().clone(); for it in items{ println!("root item:{:?}",it); - self.update_inode(root_inode.clone(),it); + self.update_inode(root_inode.clone(),it).await; } loop {//BFS to look up all dictionary - if self.queue.lock().unwrap().is_empty(){ + if self.queue.lock().await.is_empty(){ break; } - let one_inode = self.queue.lock().unwrap().pop_front().unwrap(); + let one_inode = self.queue.lock().await.pop_front().unwrap(); let mut new_items = Vec::new(); { - let it = self.inodes.lock().unwrap().get(&one_inode).unwrap().clone(); - if let ContentType::Dictionary(load) = *it.content_type.lock().unwrap(){ + let it = self.inodes.lock().await.get(&one_inode).unwrap().clone(); + if let ContentType::Dictionary(load) = *it.content_type.lock().await{ if !load{ let path = it.get_path(); println!("fetch path :{}",path); - let handler = get_handle(); + // 在阻塞线程中运行异步任务 - new_items =futures::executor::block_on(async move { - handler.spawn(async move { - fetch_tree(&path).await.unwrap().data - }).await.unwrap() - }); + new_items =fetch_tree(&path).await.unwrap().data; } @@ -264,9 +282,9 @@ impl DictionaryStore { let mut pc = it.clone(); for newit in new_items { println!("import item :{:?}",newit); - self.update_inode(pc.clone(),newit); // Await the update_inode call + self.update_inode(pc.clone(),newit).await; // Await the update_inode call } - let mut content_type = pc.content_type.lock().unwrap(); + let mut content_type = pc.content_type.lock().await; *content_type = ContentType::Dictionary(true); } new_items = Vec::new(); @@ -274,113 +292,54 @@ impl DictionaryStore { //queue.clear(); } - pub fn find_path(&self,inode :u64)-> Option{ - self.inodes.lock().unwrap().get(&inode).map(|item| item.path_name.clone()) + pub async fn find_path(&self,inode :u64)-> Option{ + self.inodes.lock().await.get(&inode).map(|item| item.path_name.clone()) } - pub fn get_inode(&self,inode: u64) -> Result, io::Error> { - match self.inodes.lock().unwrap().get(&inode) { + pub async fn get_inode(&self,inode: u64) -> Result, io::Error> { + match self.inodes.lock().await.get(&inode) { Some(item) => Ok(item.clone()), None=>Err(io::Error::new(io::ErrorKind::NotFound, "inode not found")) } } - pub fn get_by_path(&self, path: &str) -> Result, io::Error> { - let binding = self.radix_trie.lock().unwrap(); + pub async fn get_by_path(&self, path: &str) -> Result, io::Error> { + let binding = self.radix_trie.lock().await; let inode = binding.get(path).ok_or(io::Error::new(io::ErrorKind::NotFound, "path not found"))?; - self.get_inode(*inode) + self.get_inode(*inode).await } - fn find_children(&self,parent: u64) -> Result{ - let path = self.inodes.lock().unwrap().get(&parent).map(|item| item.path_name.clone()); + async fn find_children(&self,parent: u64) -> Result{ + let path = self.inodes.lock().await.get(&parent).map(|item| item.path_name.clone()); if let Some(parent_path) = path{ - let l = self.radix_trie.lock().unwrap(); + let l = self.radix_trie.lock().await; let pathstr:String =parent_path.name(); let u = l.subtrie(&pathstr).unwrap(); let c = u.children(); } todo!() } - - pub fn do_readdir(&self, - ctx: &Context, - inode: u64, - handle: u64, - size: u32, - offset: u64, - add_entry: &mut dyn FnMut(DirEntry) -> std::io::Result, - ) -> std::io::Result<()> { - // 1. 获取目录项 - let directory = self.get_inode(inode).unwrap(); - - - // add_entry(DirEntry { - // ino: directory.get_inode(), - // offset:0, - // name:b".", - // type_: entry_type_from_mode(directory.get_stat().attr.st_mode).into(), - // }); - // add_entry(DirEntry { - // ino: directory.parent, - // offset:1, - // name:b"..", - // type_: entry_type_from_mode(directory.get_stat().attr.st_mode).into(), - // }); + pub async fn do_readdir(&self,parent:u64,fh:u64,offset:u64) -> Result>, io::Error>{ + // 1. 获取目录项 + let dictionary = self.get_inode(parent).await?; + let p_dictionary = self.get_inode(dictionary.get_inode()).await?; + let mut re = vec![dictionary.clone(),p_dictionary.clone()]; + //let mut re = vec![]; // 2. 确保目录项是一个目录 - if let ContentType::Dictionary(_) = directory.get_tyep() { + if let ContentType::Dictionary(_) = dictionary.get_tyep().await { // 3. 获取子目录项 - let children = directory.children.lock().unwrap(); + let children = dictionary.children.lock().await; let mut total_bytes_written = 0; let mut current_offset = 0; // 4. 遍历子目录项 for (i, (name, child)) in children.iter().enumerate() { - - if current_offset as u64 >= offset { - // 获取每个目录项的 stat 信息 - let entry = child.get_stat(); - // 计算目录项的大小 - let entry_size = name.len() + std::mem::size_of::(); // name 字节数 + inode 信息的字节数 - if total_bytes_written + entry_size as u64 > size as u64 { - break; - } - println!("do_readir name:{},child inode:{},type:{:?},mode:{}",name, child.get_inode(),child.get_tyep(),entry_type_from_mode(entry.attr.st_mode)); - //entry_type_from_mode(entry.attr.st_mode) - // 使用回调函数添加目录项 - let result = add_entry(DirEntry { - ino: child.get_inode(), - offset: (i+2) as u64, - name:name.as_bytes(), - type_: entry_type_from_mode(entry.attr.st_mode).into(), - }); - - match result { - Ok(len) => { - total_bytes_written += len as u64; - current_offset += 1; - } - Err(e) => return Err(e), - } - } + re.push(child.clone()); } - - // 5. 返回结果 - Ok(()) + Ok(re) } else { Err(io::Error::new(io::ErrorKind::NotFound, "Not a directory")) } } } -fn entry_type_from_mode(mode: libc::mode_t) -> u8 { - match mode & libc::S_IFMT { - libc::S_IFBLK => libc::DT_BLK, - libc::S_IFCHR => libc::DT_CHR, - libc::S_IFDIR => libc::DT_DIR, - libc::S_IFIFO => libc::DT_FIFO, - libc::S_IFLNK => libc::DT_LNK, - libc::S_IFREG => libc::DT_REG, - libc::S_IFSOCK => libc::DT_SOCK, - _ => libc::DT_UNKNOWN, - } -} #[cfg(test)] mod tests { use super::*; diff --git a/scorpio/src/fuse/inode_alloc.rs b/scorpio/src/fuse/inode_alloc.rs index c25cb944f..0b091a9b2 100644 --- a/scorpio/src/fuse/inode_alloc.rs +++ b/scorpio/src/fuse/inode_alloc.rs @@ -2,8 +2,7 @@ use std::{collections::HashMap, sync::{atomic::{AtomicU64, Ordering}, Mutex}}; #[allow(unused)] -const VFS_MAX_INO: u64 = 0xff_ffff_ffff_ffff; -pub const READONLY_INODE :u64 = 0xffff_ffff; + // Alloc inode numbers at one batch #[allow(unused)] const INODE_ALLOC_BATCH:u64 = 0x1_0000_0000; diff --git a/scorpio/src/lib.rs b/scorpio/src/lib.rs index 5c0c28a0a..2977cba46 100644 --- a/scorpio/src/lib.rs +++ b/scorpio/src/lib.rs @@ -6,26 +6,10 @@ mod passthrough; mod overlayfs; //mod store; // pub mod fuse; -// mod dicfuse; +mod dicfuse; mod util; // pub mod manager; -// pub mod server; +pub mod server; // pub mod deamon; -use once_cell::sync::OnceCell; -use tokio::runtime::Handle; - -// 定义一个全局的 OnceCell 来存储 Tokio 运行时的句柄 -static RUNTIME_HANDLE: OnceCell = OnceCell::new(); - -// 初始化运行时并存储句柄 -pub fn init_runtime(rt:Handle) { - - RUNTIME_HANDLE - .set(rt) - .expect("Failed to set runtime handle"); -} - -// 获取全局的运行时句柄 -pub fn get_handle() -> &'static Handle { - RUNTIME_HANDLE.get().expect("Runtime not initialized") -} \ No newline at end of file +//const VFS_MAX_INO: u64 = 0xff_ffff_ffff_ffff; +pub const READONLY_INODE :u64 = 0xffff_ffff; diff --git a/scorpio/src/overlayfs/mod.rs b/scorpio/src/overlayfs/mod.rs index 7507c8ea2..30dc9ea3d 100644 --- a/scorpio/src/overlayfs/mod.rs +++ b/scorpio/src/overlayfs/mod.rs @@ -1183,7 +1183,7 @@ impl OverlayFs { let mut d:Vec> = Vec::new(); for (index, (name, child)) in (0_u64..).zip(childrens.into_iter()) { - if index >= offset { + // make struct DireEntry and Entry let st = child.stat64(ctx).await?; let dir_entry = DirectoryEntry { @@ -1203,7 +1203,7 @@ impl OverlayFs { // attr_ttl: todo!(), // } d.push(Ok(dir_entry)); - } + } Ok(iter(d.into_iter())) diff --git a/scorpio/src/server/mod.rs b/scorpio/src/server/mod.rs index 323d2492f..4855a25cd 100644 --- a/scorpio/src/server/mod.rs +++ b/scorpio/src/server/mod.rs @@ -1,53 +1,86 @@ -use std::{path::Path, sync::Arc, thread::JoinHandle}; - -use fuse_backend_rs::{api::{filesystem::FileSystem, server::Server}, transport::{FuseChannel, FuseSession}}; -#[allow(unused)] -pub struct FuseServer { - pub server: Arc>, - pub ch: FuseChannel, -} -pub fn run(fuse:Arc,path:&str )->JoinHandle>{ - let mut se = FuseSession::new(Path::new(path), "dic", "", false).unwrap(); - se.mount().unwrap(); - let ch: FuseChannel = se.new_channel().unwrap(); - let server = Arc::new(Server::new(fuse)); - let mut fuse_server = FuseServer { server, ch }; - // Spawn server thread - std::thread::spawn( move || { - fuse_server.svc_loop() - }) - -} -#[allow(unused)] -impl FuseServer { - pub fn svc_loop(&mut self) -> Result<(), std::io::Error> { - let _ebadf = std::io::Error::from_raw_os_error(libc::EBADF); - println!("entering server loop"); - loop { - if let Some((reader, writer)) = self - .ch - .get_request() - .map_err(|_| std::io::Error::from_raw_os_error(libc::EINVAL))? - { - if let Err(e) = self - .server - .handle_message(reader, writer.into(), None, None) - { - match e { - fuse_backend_rs::Error::EncodeMessage(_ebadf) => { - break; - } - _ => { - print!("Handling fuse message failed"); - continue; - } - } - } - } else { - print!("fuse server exits"); - break; - } - } - Ok(()) - } -} +// use std::{path::Path, sync::Arc, thread::JoinHandle}; + +// use fuse_backend_rs::{api::{filesystem::FileSystem, server::Server}, transport::{FuseChannel, FuseSession}}; +// #[allow(unused)] +// pub struct FuseServer { +// pub server: Arc>, +// pub ch: FuseChannel, +// } +// pub fn run(fuse:Arc,path:&str )->JoinHandle>{ +// let mut se = FuseSession::new(Path::new(path), "dic", "", false).unwrap(); +// se.mount().unwrap(); +// let ch: FuseChannel = se.new_channel().unwrap(); +// let server = Arc::new(Server::new(fuse)); +// let mut fuse_server = FuseServer { server, ch }; +// // Spawn server thread +// std::thread::spawn( move || { +// fuse_server.svc_loop() +// }) + +// } +// #[allow(unused)] +// impl FuseServer { +// pub fn svc_loop(&mut self) -> Result<(), std::io::Error> { +// let _ebadf = std::io::Error::from_raw_os_error(libc::EBADF); +// println!("entering server loop"); +// loop { +// if let Some((reader, writer)) = self +// .ch +// .get_request() +// .map_err(|_| std::io::Error::from_raw_os_error(libc::EINVAL))? +// { +// if let Err(e) = self +// .server +// .handle_message(reader, writer.into(), None, None) +// { +// match e { +// fuse_backend_rs::Error::EncodeMessage(_ebadf) => { +// break; +// } +// _ => { +// print!("Handling fuse message failed"); +// continue; +// } +// } +// } +// } else { +// print!("fuse server exits"); +// break; +// } +// } +// Ok(()) +// } +// } + +use fuse3::raw::{Filesystem, MountHandle}; + +use crate::passthrough::logfs::LoggingFileSystem; +use std::ffi::{OsStr, OsString}; + +use fuse3::{raw::Session, MountOptions}; + + + +pub async fn mount_filesystem(fs:F,mountpoint:&OsStr) -> MountHandle { + env_logger::init(); + let logfs = LoggingFileSystem::new(fs); + + let mount_path: OsString = OsString::from(mountpoint); + + let uid = unsafe { libc::getuid() }; + let gid = unsafe { libc::getgid() }; + + let mut mount_options = MountOptions::default(); + // .allow_other(true) + mount_options + .force_readdir_plus(true) + .uid(uid) + .gid(gid); + + + Session::new(mount_options) + .mount_with_unprivileged(logfs, mount_path) + .await + .unwrap() + +} \ No newline at end of file diff --git a/scripts/zed-intergration/build.sh b/scripts/zed-intergration/build.sh index 41d4775ec..f2a9a48a0 100755 --- a/scripts/zed-intergration/build.sh +++ b/scripts/zed-intergration/build.sh @@ -3,7 +3,7 @@ # Clone zed and switch to the specific version ZED_ROOT='./zed' ZED_REPO='https://github.com/zed-industries/zed.git' -TARGET_PATCH_VERSION='04ee5e3e6e563fbcc6ea37f8733aaf6897428773' +TARGET_PATCH_VERSION='a56f946a7d0734839f820d2943dabe7fa09a4b22' # Place patch files here PATCH_DIR='./patches/' diff --git a/scripts/zed-intergration/patches/0001-add-new-module-mega-and-mega_panel.patch b/scripts/zed-intergration/patches/0001-add-new-module-mega-and-mega_panel.patch new file mode 100644 index 000000000..24cba31b3 --- /dev/null +++ b/scripts/zed-intergration/patches/0001-add-new-module-mega-and-mega_panel.patch @@ -0,0 +1,262 @@ +From 006bd0e01b5deba6792af824a5a73c32e96efa2c Mon Sep 17 00:00:00 2001 +From: Neon +Date: Sat, 19 Oct 2024 21:24:00 +0800 +Subject: [PATCH 01/10] add new module mega and mega_panel + +--- + Cargo.lock | 23 ++++++ + Cargo.toml | 4 + + crates/mega/LICENSE-GPL | 1 + + crates/mega/src/delegate.rs | 0 + crates/mega_panel/LICENSE-GPL | 1 + + crates/mega_panel/src/mega_panel_settings.rs | 80 ++++++++++++++++++++ + crates/zed/Cargo.toml | 1 + + crates/zed/src/zed.rs | 12 +++ + 8 files changed, 122 insertions(+) + create mode 120000 crates/mega/LICENSE-GPL + create mode 100644 crates/mega/src/delegate.rs + create mode 120000 crates/mega_panel/LICENSE-GPL + create mode 100644 crates/mega_panel/src/mega_panel_settings.rs + +diff --git a/Cargo.lock b/Cargo.lock +index 6f1d7b2c9c..d39aae09ee 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -6884,6 +6884,28 @@ dependencies = [ + "objc", + ] + ++[[package]] ++name = "mega" ++version = "0.1.0" ++dependencies = [ ++ "reqwest 0.12.8", ++ "serde", ++] ++ ++[[package]] ++name = "mega_panel" ++version = "0.1.0" ++dependencies = [ ++ "anyhow", ++ "editor", ++ "file_icons", ++ "gpui", ++ "schemars", ++ "serde", ++ "settings", ++ "workspace", ++] ++ + [[package]] + name = "memchr" + version = "2.7.4" +@@ -14631,6 +14653,7 @@ dependencies = [ + "libc", + "log", + "markdown_preview", ++ "mega_panel", + "menu", + "mimalloc", + "nix", +diff --git a/Cargo.toml b/Cargo.toml +index 4a4ddb4424..6a02b98478 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -64,6 +64,8 @@ members = [ + "crates/markdown", + "crates/markdown_preview", + "crates/media", ++ "crates/mega", ++ "crates/mega_panel", + "crates/menu", + "crates/multi_buffer", + "crates/node_runtime", +@@ -240,6 +242,8 @@ lsp = { path = "crates/lsp" } + markdown = { path = "crates/markdown" } + markdown_preview = { path = "crates/markdown_preview" } + media = { path = "crates/media" } ++mega = { path = "crates/mega" } ++mega_panel = { path = "crates/mega_panel" } + menu = { path = "crates/menu" } + multi_buffer = { path = "crates/multi_buffer" } + node_runtime = { path = "crates/node_runtime" } +diff --git a/crates/mega/LICENSE-GPL b/crates/mega/LICENSE-GPL +new file mode 120000 +index 0000000000..89e542f750 +--- /dev/null ++++ b/crates/mega/LICENSE-GPL +@@ -0,0 +1 @@ ++../../LICENSE-GPL +\ No newline at end of file +diff --git a/crates/mega/src/delegate.rs b/crates/mega/src/delegate.rs +new file mode 100644 +index 0000000000..e69de29bb2 +diff --git a/crates/mega_panel/LICENSE-GPL b/crates/mega_panel/LICENSE-GPL +new file mode 120000 +index 0000000000..89e542f750 +--- /dev/null ++++ b/crates/mega_panel/LICENSE-GPL +@@ -0,0 +1 @@ ++../../LICENSE-GPL +\ No newline at end of file +diff --git a/crates/mega_panel/src/mega_panel_settings.rs b/crates/mega_panel/src/mega_panel_settings.rs +new file mode 100644 +index 0000000000..1ca1149b01 +--- /dev/null ++++ b/crates/mega_panel/src/mega_panel_settings.rs +@@ -0,0 +1,80 @@ ++use schemars::JsonSchema; ++use gpui::Pixels; ++use gpui::private::serde::{Deserialize, Serialize}; ++use settings::{Settings, SettingsSources}; ++ ++#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)] ++#[serde(rename_all = "snake_case")] ++pub enum MegaPanelDockPosition { ++ Left, ++ Right, ++} ++ ++#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] ++pub struct MegaPanelSettings { ++ pub button: bool, ++ pub default_width: Pixels, ++ pub dock: MegaPanelDockPosition, ++ pub file_icons: bool, ++ pub folder_icons: bool, ++ pub git_status: bool, ++ pub indent_size: f32, ++ pub auto_reveal_entries: bool, ++ pub auto_fold_dirs: bool, ++} ++ ++#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] ++pub struct MegaPanelSettingsContent { ++ /// Whether to show the outline panel button in the status bar. ++ /// ++ /// Default: true ++ pub button: Option, ++ /// Customize default width (in pixels) taken by outline panel ++ /// ++ /// Default: 240 ++ pub default_width: Option, ++ /// The position of outline panel ++ /// ++ /// Default: left ++ pub dock: Option, ++ /// Whether to show file icons in the outline panel. ++ /// ++ /// Default: true ++ pub file_icons: Option, ++ /// Whether to show folder icons or chevrons for directories in the outline panel. ++ /// ++ /// Default: true ++ pub folder_icons: Option, ++ /// Whether to show the git status in the outline panel. ++ /// ++ /// Default: true ++ pub git_status: Option, ++ /// Amount of indentation (in pixels) for nested items. ++ /// ++ /// Default: 20 ++ pub indent_size: Option, ++ /// Whether to reveal it in the outline panel automatically, ++ /// when a corresponding project entry becomes active. ++ /// Gitignored entries are never auto revealed. ++ /// ++ /// Default: true ++ pub auto_reveal_entries: Option, ++ /// Whether to fold directories automatically ++ /// when directory has only one directory inside. ++ /// ++ /// Default: true ++ pub auto_fold_dirs: Option, ++} ++ ++impl Settings for MegaPanelSettings { ++ const KEY: Option<&'static str> = Some("outline_panel"); ++ ++ type FileContent = MegaPanelSettingsContent; ++ ++ fn load( ++ sources: SettingsSources, ++ _: &mut gpui::AppContext, ++ ) -> anyhow::Result { ++ sources.json_merge() ++ } ++} +\ No newline at end of file +diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml +index 69ca3aa98d..81243cf62f 100644 +--- a/crates/zed/Cargo.toml ++++ b/crates/zed/Cargo.toml +@@ -66,6 +66,7 @@ languages = { workspace = true, features = ["load-grammars"] } + libc.workspace = true + log.workspace = true + markdown_preview.workspace = true ++mega_panel.workspace = true + menu.workspace = true + mimalloc = { version = "0.1", optional = true } + nix = { workspace = true, features = ["pthread", "signal"] } +diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs +index c33cef4a4b..545e42551a 100644 +--- a/crates/zed/src/zed.rs ++++ b/crates/zed/src/zed.rs +@@ -27,6 +27,7 @@ use anyhow::Context as _; + use assets::Assets; + use futures::{channel::mpsc, select_biased, StreamExt}; + use outline_panel::OutlinePanel; ++use mega_panel::MegaPanel; + use project::Item; + use project_panel::ProjectPanel; + use quick_action_bar::QuickActionBar; +@@ -238,6 +239,7 @@ pub fn initialize_workspace( + + let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone()); + let outline_panel = OutlinePanel::load(workspace_handle.clone(), cx.clone()); ++ let mega_panel = MegaPanel::load(workspace_handle.clone(), cx.clone()); + let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()); + let channels_panel = + collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone()); +@@ -250,6 +252,7 @@ pub fn initialize_workspace( + + let ( + project_panel, ++ mega_panel, + outline_panel, + terminal_panel, + assistant_panel, +@@ -258,6 +261,7 @@ pub fn initialize_workspace( + notification_panel, + ) = futures::try_join!( + project_panel, ++ mega_panel, + outline_panel, + terminal_panel, + assistant_panel, +@@ -269,6 +273,7 @@ pub fn initialize_workspace( + workspace_handle.update(&mut cx, |workspace, cx| { + workspace.add_panel(assistant_panel, cx); + workspace.add_panel(project_panel, cx); ++ workspace.add_panel(mega_panel, cx); + workspace.add_panel(outline_panel, cx); + workspace.add_panel(terminal_panel, cx); + workspace.add_panel(channels_panel, cx); +@@ -467,6 +472,13 @@ pub fn initialize_workspace( + workspace.toggle_panel_focus::(cx); + }, + ) ++ .register_action( ++ |workspace: &mut Workspace, ++ _: &mega_panel::ToggleFocus, ++ cx: &mut ViewContext| { ++ workspace.toggle_panel_focus::(cx); ++ }, ++ ) + .register_action( + |workspace: &mut Workspace, + _: &outline_panel::ToggleFocus, +-- +2.43.0 + diff --git a/scripts/zed-intergration/patches/0001-feat-basic-support-for-working-with-mega.patch b/scripts/zed-intergration/patches/0001-feat-basic-support-for-working-with-mega.patch deleted file mode 100644 index a9e27c6e1..000000000 --- a/scripts/zed-intergration/patches/0001-feat-basic-support-for-working-with-mega.patch +++ /dev/null @@ -1,407 +0,0 @@ -From afb90731865925957bc817414907f8070a5c3b6a Mon Sep 17 00:00:00 2001 -From: Neon -Date: Sun, 8 Sep 2024 20:19:20 +0800 -Subject: [PATCH] feat: basic support for working with mega - ---- - Cargo.lock | 9 ++ - Cargo.toml | 2 + - crates/mega_delegator/Cargo.toml | 13 +++ - crates/mega_delegator/src/mega_delegator.rs | 114 ++++++++++++++++++++ - crates/project_panel/Cargo.toml | 1 + - crates/project_panel/src/project_panel.rs | 111 ++++++++++++++----- - 6 files changed, 223 insertions(+), 27 deletions(-) - create mode 100644 crates/mega_delegator/Cargo.toml - create mode 100644 crates/mega_delegator/src/mega_delegator.rs - -diff --git a/Cargo.lock b/Cargo.lock -index 818c258aef..8a3597c041 100644 ---- a/Cargo.lock -+++ b/Cargo.lock -@@ -6534,6 +6534,14 @@ dependencies = [ - "objc", - ] - -+[[package]] -+name = "mega_delegator" -+version = "0.1.0" -+dependencies = [ -+ "isahc", -+ "lazy_static", -+] -+ - [[package]] - name = "memchr" - version = "2.7.4" -@@ -8105,6 +8113,7 @@ dependencies = [ - "git", - "gpui", - "language", -+ "mega_delegator", - "menu", - "pretty_assertions", - "project", -diff --git a/Cargo.toml b/Cargo.toml -index 740aaa9c1a..9e37dc9522 100644 ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -60,6 +60,7 @@ members = [ - "crates/markdown", - "crates/markdown_preview", - "crates/media", -+ "crates/mega_delegator", - "crates/menu", - "crates/multi_buffer", - "crates/node_runtime", -@@ -229,6 +230,7 @@ lsp = { path = "crates/lsp" } - markdown = { path = "crates/markdown" } - markdown_preview = { path = "crates/markdown_preview" } - media = { path = "crates/media" } -+mega_delegator ={ path = "crates/mega_delegator" } - menu = { path = "crates/menu" } - multi_buffer = { path = "crates/multi_buffer" } - node_runtime = { path = "crates/node_runtime" } -diff --git a/crates/mega_delegator/Cargo.toml b/crates/mega_delegator/Cargo.toml -new file mode 100644 -index 0000000000..3d423e81ae ---- /dev/null -+++ b/crates/mega_delegator/Cargo.toml -@@ -0,0 +1,13 @@ -+[package] -+name = "mega_delegator" -+version = "0.1.0" -+edition = "2021" -+publish = false -+license = "GPL-3.0-or-later" -+ -+[lib] -+path = "src/mega_delegator.rs" -+ -+[dependencies] -+lazy_static.workspace = true -+isahc.workspace = true -diff --git a/crates/mega_delegator/src/mega_delegator.rs b/crates/mega_delegator/src/mega_delegator.rs -new file mode 100644 -index 0000000000..fd6188c204 ---- /dev/null -+++ b/crates/mega_delegator/src/mega_delegator.rs -@@ -0,0 +1,114 @@ -+use std::{cell::RefCell, fs::File, process::{Child, Command, Stdio}, time::Duration}; -+ -+use isahc::config::Configurable; -+ -+// In this module, we made these assumption to mega: -+// 1. running on port 8000 -+// 2. -+ -+pub enum MegaEvent { -+ MegaStart, -+ MegaStarted, -+ MegaStop, -+} -+ -+#[derive(Debug, PartialEq)] -+pub enum MegaStatus { -+ MegaIdle, -+ MegaRunning, -+ MegaError, -+} -+ -+pub struct MegaDelegator{ -+ http_client: isahc::HttpClient, -+ holding: RefCell> -+} -+ -+impl MegaDelegator { -+ pub fn new() -> Self { -+ // TODO: In dev environment, asuuming mega and its config -+ // are right in the $PATH. -+ -+ let client = isahc::HttpClient::builder().connect_timeout(Duration::from_secs(1)).build().unwrap(); -+ -+ Self { -+ http_client: client, -+ holding: RefCell::from(None) -+ } -+ } -+ -+ pub fn status(&self) -> MegaStatus { -+ match *self.holding.borrow() { -+ Some(_) => { -+ match self.http_client.get("localhost:8000/api/v1/mono/status") { -+ Ok(_) => MegaStatus::MegaRunning, -+ Err(_) => MegaStatus::MegaIdle -+ } -+ } -+ None => MegaStatus::MegaError -+ } -+ } -+ -+ pub fn start(&self) -> &Self { -+ match self.status() { -+ MegaStatus::MegaRunning -+ | MegaStatus::MegaIdle => return self, -+ _ => {} -+ } -+ -+ let output_file = File::create("output.txt").expect("Unable to create file"); -+ let mega_root = match std::env::var("MEGA_ROOT") { -+ Ok(s) => s, -+ Err(_) => String::new() -+ }; -+ -+ let child = Command::new(format!("{}/target/debug/mega", mega_root)) -+ .args([ -+ "--config", -+ format!("{}/mega/config.toml", mega_root).as_str(), -+ "service", "http" -+ ]) -+ .stdout(Stdio::from(output_file.try_clone().expect("Failed to clone file"))) -+ .stderr(Stdio::from(output_file)) -+ .spawn() -+ .unwrap(); -+ -+ *self.holding.borrow_mut() = Some(child); -+ -+ self -+ } -+ -+ pub fn stop(&self) -> &Self { -+ if let Some(mut child) = self.holding.take() { -+ // There may be so many unexpected errors, -+ // so we simply ignore and leave. -+ if let Ok(()) = child.kill() { -+ let _ = child.wait(); -+ } -+ } -+ -+ *self.holding.borrow_mut() = None; -+ self -+ } -+ -+ pub fn get_fuse_path(&self) -> &str { -+ "/tmp" -+ } -+} -+ -+#[cfg(test)] -+mod test { -+ use std::{thread::sleep, time::Duration}; -+ -+ use crate::{MegaDelegator, MegaStatus}; -+ -+ #[test] -+ fn test_mega_start() { -+ let delegator = MegaDelegator::new(); -+ delegator.start(); -+ -+ sleep(Duration::from_secs(10)); -+ -+ assert_eq!(delegator.status(), MegaStatus::MegaRunning); -+ } -+} -diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml -index 5e11b60477..d6705f91b9 100644 ---- a/crates/project_panel/Cargo.toml -+++ b/crates/project_panel/Cargo.toml -@@ -36,6 +36,7 @@ util.workspace = true - client.workspace = true - worktree.workspace = true - workspace.workspace = true -+mega_delegator.workspace = true - - [dev-dependencies] - client = { workspace = true, features = ["test-support"] } -diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs -index d9202fab07..598924e86e 100644 ---- a/crates/project_panel/src/project_panel.rs -+++ b/crates/project_panel/src/project_panel.rs -@@ -1,6 +1,7 @@ - mod project_panel_settings; - mod scrollbar; - use client::{ErrorCode, ErrorExt}; -+use mega_delegator::{MegaDelegator, MegaEvent}; - use scrollbar::ProjectPanelScrollbar; - use settings::{Settings, SettingsStore}; - -@@ -34,6 +35,7 @@ use std::{ - ops::Range, - path::{Path, PathBuf}, - rc::Rc, -+ str::FromStr, - sync::Arc, - time::Duration, - }; -@@ -74,6 +76,7 @@ pub struct ProjectPanel { - show_scrollbar: bool, - scrollbar_drag_thumb_offset: Rc>>, - hide_scrollbar_task: Option>, -+ mega_delegator: MegaDelegator, - } - - #[derive(Clone, Debug)] -@@ -148,6 +151,7 @@ actions!( - UnfoldDirectory, - FoldDirectory, - SelectParent, -+ ToggleMega - ] - ); - -@@ -290,6 +294,7 @@ impl ProjectPanel { - show_scrollbar: !Self::should_autohide_scrollbar(cx), - hide_scrollbar_task: None, - scrollbar_drag_thumb_offset: Default::default(), -+ mega_delegator: MegaDelegator::new(), - }; - this.update_visible_entries(None, cx); - -@@ -2392,6 +2397,7 @@ impl Render for ProjectPanel { - fn render(&mut self, cx: &mut gpui::ViewContext) -> impl IntoElement { - let has_worktree = self.visible_entries.len() != 0; - let project = self.project.read(cx); -+ let ui_font = ThemeSettings::get_global(cx).ui_font.clone(); - - if has_worktree { - let items_count = self -@@ -2400,7 +2406,7 @@ impl Render for ProjectPanel { - .map(|(_, worktree_entries, _)| worktree_entries.len()) - .sum(); - -- h_flex() -+ v_flex() - .id("project-panel") - .group("project-panel") - .size_full() -@@ -2481,29 +2487,68 @@ impl Render for ProjectPanel { - ) - .track_focus(&self.focus_handle) - .child( -- uniform_list(cx.view().clone(), "entries", items_count, { -- |this, range, cx| { -- let mut items = Vec::new(); -- this.for_each_visible_entry(range, cx, |id, details, cx| { -- items.push(this.render_entry(id, details, cx)); -- }); -- items -- } -- }) -- .size_full() -- .with_sizing_behavior(ListSizingBehavior::Infer) -- .track_scroll(self.scroll_handle.clone()), -+ h_flex() -+ .m_1() -+ .border_2() -+ .border_color(cx.theme().colors().border) -+ .rounded_md() -+ .font(ui_font) -+ .child( -+ Button::new("stop_mega_button", "Stop Mega") -+ .icon(IconName::Stop) -+ .full_width() -+ .style(ButtonStyle::Filled) -+ .tooltip(move |cx| { -+ Tooltip::text("Stop Mega and unmount FUSE directories", cx) -+ }) -+ .on_click(cx.listener(|this, _, cx| { -+ this.mega_delegator.stop(); -+ -+ if let Some(entry_id) = this.last_worktree_root_id { -+ let project = this.project.read(cx); -+ -+ let worktree_id = if let Some(worktree) = -+ project.worktree_for_entry(entry_id, cx) -+ { -+ worktree.read(cx).id() -+ } else { -+ return; -+ }; -+ -+ this.project.update(cx, |project, cx| { -+ project.remove_worktree(worktree_id, cx) -+ }); -+ } -+ })), -+ ), -+ ) -+ .child( -+ h_flex() -+ .child( -+ uniform_list(cx.view().clone(), "entries", items_count, { -+ |this, range, cx| { -+ let mut items = Vec::new(); -+ this.for_each_visible_entry(range, cx, |id, details, cx| { -+ items.push(this.render_entry(id, details, cx)); -+ }); -+ items -+ } -+ }) -+ .size_full() -+ .with_sizing_behavior(ListSizingBehavior::Infer) -+ .track_scroll(self.scroll_handle.clone()), -+ ) -+ .children(self.render_scrollbar(items_count, cx)) -+ .children(self.context_menu.as_ref().map(|(menu, position, _)| { -+ deferred( -+ anchored() -+ .position(*position) -+ .anchor(gpui::AnchorCorner::TopLeft) -+ .child(menu.clone()), -+ ) -+ .with_priority(1) -+ })), - ) -- .children(self.render_scrollbar(items_count, cx)) -- .children(self.context_menu.as_ref().map(|(menu, position, _)| { -- deferred( -- anchored() -- .position(*position) -- .anchor(gpui::AnchorCorner::TopLeft) -- .child(menu.clone()), -- ) -- .with_priority(1) -- })) - } else { - v_flex() - .id("empty-project_panel") -@@ -2511,14 +2556,24 @@ impl Render for ProjectPanel { - .p_4() - .track_focus(&self.focus_handle) - .child( -- Button::new("open_project", "Open a project") -+ Button::new("start_mega", "Start Mega") - .style(ButtonStyle::Filled) - .full_width() -+ .icon(IconName::ArrowRight) - .key_binding(KeyBinding::for_action(&workspace::Open, cx)) - .on_click(cx.listener(|this, _, cx| { -- this.workspace -- .update(cx, |workspace, cx| workspace.open(&workspace::Open, cx)) -- .log_err(); -+ if let Some(task) = this -+ .workspace -+ .update(cx, |workspace, cx| { -+ let path = this.mega_delegator.start().get_fuse_path(); -+ let buf = PathBuf::from_str(path).unwrap(); -+ println!("Open workspace {}", path); -+ workspace.open_workspace_for_paths(true, vec![buf], cx) -+ }) -+ .log_err() -+ { -+ task.detach_and_log_err(cx); -+ } - })), - ) - .drag_over::(|style, _, cx| { -@@ -2580,6 +2635,8 @@ impl EventEmitter for ProjectPanel {} - - impl EventEmitter for ProjectPanel {} - -+impl EventEmitter for ProjectPanel {} -+ - impl Panel for ProjectPanel { - fn position(&self, cx: &WindowContext) -> DockPosition { - match ProjectPanelSettings::get_global(cx).dock { --- -2.46.0 - diff --git a/scripts/zed-intergration/patches/0002-mega-integrate-basic-panel-code-with-zed.patch b/scripts/zed-intergration/patches/0002-mega-integrate-basic-panel-code-with-zed.patch new file mode 100644 index 000000000..a3990194b --- /dev/null +++ b/scripts/zed-intergration/patches/0002-mega-integrate-basic-panel-code-with-zed.patch @@ -0,0 +1,581 @@ +From afca3aee776df6674d0c6d17858d944c64ec56ae Mon Sep 17 00:00:00 2001 +From: Neon +Date: Mon, 21 Oct 2024 09:18:40 +0800 +Subject: [PATCH 02/10] mega: integrate basic panel code with zed + +--- + Cargo.lock | 7 +- + crates/mega/Cargo.toml | 18 ++ + crates/mega/src/fuse.rs | 0 + crates/mega/src/mega.rs | 52 +++++ + crates/mega_panel/Cargo.toml | 24 +++ + crates/mega_panel/src/mega_panel.rs | 195 +++++++++++++++++++ + crates/mega_panel/src/mega_panel_settings.rs | 41 +--- + crates/workspace/Cargo.toml | 1 + + crates/workspace/src/workspace.rs | 10 +- + crates/zed/Cargo.toml | 1 + + crates/zed/src/main.rs | 6 +- + 11 files changed, 315 insertions(+), 40 deletions(-) + create mode 100644 crates/mega/Cargo.toml + create mode 100644 crates/mega/src/fuse.rs + create mode 100644 crates/mega/src/mega.rs + create mode 100644 crates/mega_panel/Cargo.toml + create mode 100644 crates/mega_panel/src/mega_panel.rs + +diff --git a/Cargo.lock b/Cargo.lock +index d39aae09ee..6503eb5359 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -6888,6 +6888,7 @@ dependencies = [ + name = "mega" + version = "0.1.0" + dependencies = [ ++ "gpui", + "reqwest 0.12.8", + "serde", + ] +@@ -6897,12 +6898,14 @@ name = "mega_panel" + version = "0.1.0" + dependencies = [ + "anyhow", +- "editor", ++ "db", + "file_icons", + "gpui", ++ "mega", + "schemars", + "serde", + "settings", ++ "util", + "workspace", + ] + +@@ -14314,6 +14317,7 @@ dependencies = [ + "itertools 0.13.0", + "language", + "log", ++ "mega", + "node_runtime", + "parking_lot", + "postage", +@@ -14653,6 +14657,7 @@ dependencies = [ + "libc", + "log", + "markdown_preview", ++ "mega", + "mega_panel", + "menu", + "mimalloc", +diff --git a/crates/mega/Cargo.toml b/crates/mega/Cargo.toml +new file mode 100644 +index 0000000000..dec6093220 +--- /dev/null ++++ b/crates/mega/Cargo.toml +@@ -0,0 +1,18 @@ ++[package] ++name = "mega" ++version = "0.1.0" ++edition = "2021" ++publish = false ++license = "GPL-3.0-or-later" ++ ++[lints] ++workspace = true ++ ++[lib] ++path = 'src/mega.rs' ++ ++[dependencies] ++gpui.workspace = true ++ ++reqwest.workspace = true ++serde.workspace = true +diff --git a/crates/mega/src/fuse.rs b/crates/mega/src/fuse.rs +new file mode 100644 +index 0000000000..e69de29bb2 +diff --git a/crates/mega/src/mega.rs b/crates/mega/src/mega.rs +new file mode 100644 +index 0000000000..4b51004907 +--- /dev/null ++++ b/crates/mega/src/mega.rs +@@ -0,0 +1,52 @@ ++// This crate delegate mega and its fuse daemon. ++// The following requirements should be met: ++// ++// TODO: ++// 1. Only one daemon on this machine. ++// 2. At least one daemon on this machine when zed startup. ++// 3. Complete docs. ++ ++use std::sync::Arc; ++use gpui::{AppContext, Context, EventEmitter, Model, ModelContext}; ++ ++mod delegate; ++mod fuse; ++ ++pub fn init(cx: &mut AppContext) { ++ // let reservation = cx.reserve_model(); ++ // cx.insert_model(reservation, |cx| { ++ // cx.new_model(|_cx| { Mega::new() }) ++ // }); ++} ++ ++#[derive(Clone, Debug, PartialEq)] ++pub enum Event {} ++pub struct Mega {} ++ ++pub struct MegaFuse {} ++ ++impl EventEmitter for Mega {} ++ ++ ++impl Mega { ++ pub fn init_settings(cx: &mut AppContext) { ++ ++ } ++ ++ pub fn init(cx: &mut AppContext) { ++ // let reservation = cx.reserve_model(); ++ // cx.insert_model(reservation, |cx| { ++ // cx.new_model(|_cx| { Mega::new() }) ++ // }); ++ } ++ ++ pub fn new(cx: &mut AppContext) -> Self { ++ Mega {} ++ } ++ ++ pub fn toggle_mega(&self) { todo!() } ++ ++ pub fn toggle_fuse(&self) { todo!() } ++ ++ pub fn checkout_path(&self) { todo!() } ++} +diff --git a/crates/mega_panel/Cargo.toml b/crates/mega_panel/Cargo.toml +new file mode 100644 +index 0000000000..9a8ae896a2 +--- /dev/null ++++ b/crates/mega_panel/Cargo.toml +@@ -0,0 +1,24 @@ ++[package] ++name = "mega_panel" ++version = "0.1.0" ++edition = "2021" ++publish = false ++license = "GPL-3.0-or-later" ++[lib] ++path = 'src/mega_panel.rs' ++ ++[lints] ++workspace = true ++ ++[dependencies] ++mega.workspace = true ++workspace.workspace = true ++gpui.workspace = true ++file_icons.workspace = true ++settings.workspace = true ++db.workspace = true ++util.workspace = true ++ ++anyhow.workspace = true ++serde.workspace = true ++schemars.workspace = true +diff --git a/crates/mega_panel/src/mega_panel.rs b/crates/mega_panel/src/mega_panel.rs +new file mode 100644 +index 0000000000..7e38d88752 +--- /dev/null ++++ b/crates/mega_panel/src/mega_panel.rs +@@ -0,0 +1,195 @@ ++use anyhow::{anyhow, Context}; ++use db::kvp::KEY_VALUE_STORE; ++use file_icons::FileIcons; ++use gpui::{actions, Action, AppContext, AssetSource, AsyncWindowContext, Entity, EventEmitter, FocusHandle, FocusableView, IntoElement, Model, Pixels, Render, Subscription, Task, UniformListScrollHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WindowContext}; ++use gpui::private::serde_derive::{Deserialize, Serialize}; ++use gpui::private::serde_json; ++use mega::{Mega, MegaFuse}; ++use settings::{Settings, SettingsStore}; ++use util::{ResultExt, TryFutureExt}; ++use workspace::dock::{DockPosition, Panel, PanelEvent, PanelId}; ++use workspace::ui::IconName; ++use workspace::{Pane, Workspace}; ++use crate::mega_panel_settings::MegaPanelSettings; ++ ++mod mega_panel_settings; ++ ++const MEGA_PANEL_KEY: &str = "MegaPanel"; ++ ++actions!( ++ mega_panel, ++ [ ++ Open, ++ ToggleFocus, ++ ToggleFuseMount, ++ ] ++); ++ ++pub struct MegaPanel { ++ mega: WeakModel, ++ workspace: WeakView, ++ focus_handle: FocusHandle, ++ pending_serialization: Task>, ++ width: Option, ++} ++ ++#[derive(Serialize, Deserialize)] ++struct SerializedMegaPanel { ++ width: Option, ++} ++ ++#[derive(Debug)] ++pub enum Event { ++ Focus, ++} ++ ++pub fn init_settings(cx: &mut AppContext) { ++ MegaPanelSettings::register(cx); ++} ++ ++pub fn init(assets: impl AssetSource, cx: &mut AppContext) { ++ init_settings(cx); ++ file_icons::init(assets, cx); ++ ++ cx.observe_new_views(|workspace: &mut Workspace, _| { ++ workspace.register_action(|workspace, _: &ToggleFocus, cx| { ++ workspace.toggle_panel_focus::(cx); ++ }); ++ }) ++ .detach(); ++} ++ ++impl EventEmitter for MegaPanel {} ++ ++impl EventEmitter for MegaPanel {} ++ ++impl Render for MegaPanel { ++ fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { ++ todo!(); ++ "" ++ } ++} ++ ++impl FocusableView for MegaPanel { ++ fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { ++ self.focus_handle.clone() ++ } ++} ++ ++impl Panel for MegaPanel { ++ fn persistent_name() -> &'static str { ++ todo!() ++ } ++ ++ fn position(&self, cx: &WindowContext) -> DockPosition { ++ todo!() ++ } ++ ++ fn position_is_valid(&self, position: DockPosition) -> bool { ++ todo!() ++ } ++ ++ fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { ++ todo!() ++ } ++ ++ fn size(&self, cx: &WindowContext) -> Pixels { ++ todo!() ++ } ++ ++ fn set_size(&mut self, size: Option, cx: &mut ViewContext) { ++ todo!() ++ } ++ ++ fn icon(&self, cx: &WindowContext) -> Option { ++ todo!() ++ } ++ ++ fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str> { ++ todo!() ++ } ++ ++ fn toggle_action(&self) -> Box { ++ todo!() ++ } ++} ++ ++impl MegaPanel { ++ pub async fn load( ++ workspace: WeakView, ++ mut cx: AsyncWindowContext, ++ ) -> anyhow::Result> { ++ let serialized_panel = cx ++ .background_executor() ++ .spawn(async move { KEY_VALUE_STORE.read_kvp(MEGA_PANEL_KEY) }) ++ .await ++ .map_err(|e| anyhow!("Failed to load mega panel: {}", e)) ++ .context("loading mega panel") ++ .log_err() ++ .flatten() ++ .map(|panel| serde_json::from_str::(&panel)) ++ .transpose() ++ .log_err() ++ .flatten(); ++ ++ workspace.update( ++ &mut cx, ++ |workspace, cx| { ++ let panel = MegaPanel::new(workspace, cx); ++ if let Some(serialized_panel) = serialized_panel { ++ panel.update(cx, |panel, cx| { ++ panel.width = serialized_panel.width.map(|px| px.round()); ++ cx.notify(); ++ }); ++ } ++ panel ++ } ++ ) ++ } ++ ++ fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> View { ++ let mega_panel = cx.new_view(|cx| { ++ let mega = workspace.mega(); ++ ++ let focus_handle = cx.focus_handle(); ++ cx.on_focus(&focus_handle, Self::focus_in).detach(); ++ ++ cx.subscribe(mega, |this, mega, event, cx| { ++ // TODO: listen for user operations ++ }).detach(); ++ ++ Self { ++ mega: mega.downgrade(), ++ workspace: workspace.weak_handle(), ++ focus_handle, ++ pending_serialization: Task::ready(None), ++ width: None, ++ ++ } ++ }); ++ ++ mega_panel ++ } ++ ++ fn serialize(&mut self, cx: &mut ViewContext) { ++ let width = self.width; ++ self.pending_serialization = cx.background_executor().spawn( ++ async move { ++ KEY_VALUE_STORE ++ .write_kvp( ++ MEGA_PANEL_KEY.into(), ++ serde_json::to_string(&SerializedMegaPanel { width })?, ++ ) ++ .await?; ++ anyhow::Ok(()) ++ } ++ .log_err(), ++ ); ++ } ++ ++ fn focus_in(&mut self, cx: &mut ViewContext) { ++ if !self.focus_handle.contains_focused(cx) { ++ cx.emit(Event::Focus); ++ } ++ } ++} +\ No newline at end of file +diff --git a/crates/mega_panel/src/mega_panel_settings.rs b/crates/mega_panel/src/mega_panel_settings.rs +index 1ca1149b01..bfd4b337d0 100644 +--- a/crates/mega_panel/src/mega_panel_settings.rs ++++ b/crates/mega_panel/src/mega_panel_settings.rs +@@ -15,59 +15,26 @@ pub struct MegaPanelSettings { + pub button: bool, + pub default_width: Pixels, + pub dock: MegaPanelDockPosition, +- pub file_icons: bool, +- pub folder_icons: bool, +- pub git_status: bool, +- pub indent_size: f32, +- pub auto_reveal_entries: bool, +- pub auto_fold_dirs: bool, + } + + #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] + pub struct MegaPanelSettingsContent { +- /// Whether to show the outline panel button in the status bar. ++ /// Whether to show the mega panel button in the status bar. + /// + /// Default: true + pub button: Option, +- /// Customize default width (in pixels) taken by outline panel ++ /// Customize default width (in pixels) taken by mega panel + /// + /// Default: 240 + pub default_width: Option, +- /// The position of outline panel ++ /// The position of mega panel + /// + /// Default: left + pub dock: Option, +- /// Whether to show file icons in the outline panel. +- /// +- /// Default: true +- pub file_icons: Option, +- /// Whether to show folder icons or chevrons for directories in the outline panel. +- /// +- /// Default: true +- pub folder_icons: Option, +- /// Whether to show the git status in the outline panel. +- /// +- /// Default: true +- pub git_status: Option, +- /// Amount of indentation (in pixels) for nested items. +- /// +- /// Default: 20 +- pub indent_size: Option, +- /// Whether to reveal it in the outline panel automatically, +- /// when a corresponding project entry becomes active. +- /// Gitignored entries are never auto revealed. +- /// +- /// Default: true +- pub auto_reveal_entries: Option, +- /// Whether to fold directories automatically +- /// when directory has only one directory inside. +- /// +- /// Default: true +- pub auto_fold_dirs: Option, + } + + impl Settings for MegaPanelSettings { +- const KEY: Option<&'static str> = Some("outline_panel"); ++ const KEY: Option<&'static str> = Some("mega_panel"); + + type FileContent = MegaPanelSettingsContent; + +diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml +index 47f6c138c8..83686bb100 100644 +--- a/crates/workspace/Cargo.toml ++++ b/crates/workspace/Cargo.toml +@@ -45,6 +45,7 @@ http_client.workspace = true + itertools.workspace = true + language.workspace = true + log.workspace = true ++mega.workspace = true + node_runtime.workspace = true + parking_lot.workspace = true + postage.workspace = true +diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs +index ec4079ba9f..af4f47e5d3 100644 +--- a/crates/workspace/src/workspace.rs ++++ b/crates/workspace/src/workspace.rs +@@ -97,6 +97,7 @@ use ui::{ + }; + use util::{maybe, ResultExt, TryFutureExt}; + use uuid::Uuid; ++use mega::Mega; + pub use workspace_settings::{ + AutosaveSetting, RestoreOnStartupBehavior, TabBarSettings, WorkspaceSettings, + }; +@@ -559,6 +560,7 @@ pub struct AppState { + pub build_window_options: fn(Option, &mut AppContext) -> WindowOptions, + pub node_runtime: NodeRuntime, + pub session: Model, ++ pub mega: Model, + } + + struct GlobalAppState(Weak); +@@ -609,7 +611,8 @@ impl AppState { + let session = cx.new_model(|cx| AppSession::new(Session::test(), cx)); + let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx)); + let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx)); +- ++ let mega = cx.new_model(|cx| { Mega::new(cx) }); ++ + theme::init(theme::LoadThemes::JustBase, cx); + client::init(&client, cx); + crate::init_settings(cx); +@@ -623,6 +626,7 @@ impl AppState { + node_runtime: NodeRuntime::unavailable(), + build_window_options: |_, _| Default::default(), + session, ++ mega, + }) + } + } +@@ -1266,6 +1270,8 @@ impl Workspace { + pub fn project(&self) -> &Model { + &self.project + } ++ ++ pub fn mega(&self) -> &Model { &self.app_state.mega } + + pub fn recent_navigation_history( + &self, +@@ -4426,6 +4432,7 @@ impl Workspace { + + let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx)); + let session = cx.new_model(|cx| AppSession::new(Session::test(), cx)); ++ let mega = cx.new_model(|cx| Mega::new(cx) ); + cx.activate_window(); + let app_state = Arc::new(AppState { + languages: project.read(cx).languages().clone(), +@@ -4436,6 +4443,7 @@ impl Workspace { + build_window_options: |_, _| Default::default(), + node_runtime: NodeRuntime::unavailable(), + session, ++ mega, + }); + let workspace = Self::new(Default::default(), project, app_state, cx); + workspace.active_pane.update(cx, |pane, cx| pane.focus(cx)); +diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml +index 81243cf62f..abc36527ec 100644 +--- a/crates/zed/Cargo.toml ++++ b/crates/zed/Cargo.toml +@@ -66,6 +66,7 @@ languages = { workspace = true, features = ["load-grammars"] } + libc.workspace = true + log.workspace = true + markdown_preview.workspace = true ++mega.workspace = true + mega_panel.workspace = true + menu.workspace = true + mimalloc = { version = "0.1", optional = true } +diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs +index 25baf74c68..3e3094ecb0 100644 +--- a/crates/zed/src/main.rs ++++ b/crates/zed/src/main.rs +@@ -53,6 +53,7 @@ use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings}; + use time::UtcOffset; + use util::{maybe, parse_env_output, ResultExt, TryFutureExt}; + use uuid::Uuid; ++use mega::Mega; + use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN}; + use workspace::{ + notifications::{simple_message_notification::MessageNotification, NotificationId}, +@@ -521,6 +522,7 @@ fn main() { + Client::set_global(client.clone(), cx); + + zed::init(cx); ++ mega::init(cx); + project::Project::init(&client, cx); + client::init(&client, cx); + language::init(cx); +@@ -548,7 +550,8 @@ fn main() { + } + } + let app_session = cx.new_model(|cx| AppSession::new(session, cx)); +- ++ let mega = cx.new_model(|cx| Mega::new(cx)); ++ + let app_state = Arc::new(AppState { + languages: languages.clone(), + client: client.clone(), +@@ -558,6 +561,7 @@ fn main() { + workspace_store, + node_runtime: node_runtime.clone(), + session: app_session, ++ mega, + }); + AppState::set_global(Arc::downgrade(&app_state), cx); + +-- +2.43.0 + diff --git a/scripts/zed-intergration/patches/0003-add-mega-panel-trait-implementation.patch b/scripts/zed-intergration/patches/0003-add-mega-panel-trait-implementation.patch new file mode 100644 index 000000000..e0aeca8b9 --- /dev/null +++ b/scripts/zed-intergration/patches/0003-add-mega-panel-trait-implementation.patch @@ -0,0 +1,162 @@ +From a6636b00e11cfa284e2e152cb2ac0a6d92508a34 Mon Sep 17 00:00:00 2001 +From: Neon +Date: Mon, 21 Oct 2024 09:33:24 +0800 +Subject: [PATCH 03/10] add: mega `panel` trait implementation + +--- + Cargo.lock | 1 + + crates/mega_panel/Cargo.toml | 1 + + crates/mega_panel/src/mega_panel.rs | 41 +++++++++++++++++------ + crates/project_panel/src/project_panel.rs | 8 ++--- + 4 files changed, 37 insertions(+), 14 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index 6503eb5359..80c72872e4 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -6900,6 +6900,7 @@ dependencies = [ + "anyhow", + "db", + "file_icons", ++ "fs", + "gpui", + "mega", + "schemars", +diff --git a/crates/mega_panel/Cargo.toml b/crates/mega_panel/Cargo.toml +index 9a8ae896a2..d139d51f0d 100644 +--- a/crates/mega_panel/Cargo.toml ++++ b/crates/mega_panel/Cargo.toml +@@ -17,6 +17,7 @@ gpui.workspace = true + file_icons.workspace = true + settings.workspace = true + db.workspace = true ++fs.workspace = true + util.workspace = true + + anyhow.workspace = true +diff --git a/crates/mega_panel/src/mega_panel.rs b/crates/mega_panel/src/mega_panel.rs +index 7e38d88752..e10ef55f0a 100644 +--- a/crates/mega_panel/src/mega_panel.rs ++++ b/crates/mega_panel/src/mega_panel.rs +@@ -1,6 +1,8 @@ ++use std::sync::Arc; + use anyhow::{anyhow, Context}; + use db::kvp::KEY_VALUE_STORE; + use file_icons::FileIcons; ++use fs::Fs; + use gpui::{actions, Action, AppContext, AssetSource, AsyncWindowContext, Entity, EventEmitter, FocusHandle, FocusableView, IntoElement, Model, Pixels, Render, Subscription, Task, UniformListScrollHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WindowContext}; + use gpui::private::serde_derive::{Deserialize, Serialize}; + use gpui::private::serde_json; +@@ -10,7 +12,7 @@ use util::{ResultExt, TryFutureExt}; + use workspace::dock::{DockPosition, Panel, PanelEvent, PanelId}; + use workspace::ui::IconName; + use workspace::{Pane, Workspace}; +-use crate::mega_panel_settings::MegaPanelSettings; ++use crate::mega_panel_settings::{MegaPanelDockPosition, MegaPanelSettings}; + + mod mega_panel_settings; + +@@ -29,6 +31,7 @@ pub struct MegaPanel { + mega: WeakModel, + workspace: WeakView, + focus_handle: FocusHandle, ++ fs: Arc, + pending_serialization: Task>, + width: Option, + } +@@ -78,39 +81,57 @@ impl FocusableView for MegaPanel { + + impl Panel for MegaPanel { + fn persistent_name() -> &'static str { +- todo!() ++ "Mega Panel" + } + + fn position(&self, cx: &WindowContext) -> DockPosition { +- todo!() ++ match MegaPanelSettings::get_global(cx).dock { ++ MegaPanelDockPosition::Left => DockPosition::Left, ++ MegaPanelDockPosition::Right => DockPosition::Right, ++ } + } + + fn position_is_valid(&self, position: DockPosition) -> bool { +- todo!() ++ matches!(position, DockPosition::Left | DockPosition::Right) + } + + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { +- todo!() ++ settings::update_settings_file::( ++ self.fs.clone(), ++ cx, ++ move |settings, _| { ++ let dock = match position { ++ DockPosition::Left | DockPosition::Bottom => MegaPanelDockPosition::Left, ++ DockPosition::Right => MegaPanelDockPosition::Right, ++ }; ++ settings.dock = Some(dock); ++ }, ++ ); + } + + fn size(&self, cx: &WindowContext) -> Pixels { +- todo!() ++ self.width ++ .unwrap_or_else(|| MegaPanelSettings::get_global(cx).default_width) + } + + fn set_size(&mut self, size: Option, cx: &mut ViewContext) { +- todo!() ++ self.width = size; ++ self.serialize(cx); ++ cx.notify(); + } + + fn icon(&self, cx: &WindowContext) -> Option { +- todo!() ++ MegaPanelSettings::get_global(cx) ++ .button ++ .then_some(IconName::FileGit) + } + + fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str> { +- todo!() ++ Some("Mega Panel") + } + + fn toggle_action(&self) -> Box { +- todo!() ++ Box::new(ToggleFocus) + } + } + +diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs +index 08a0ef4b40..c4fb39f587 100644 +--- a/crates/project_panel/src/project_panel.rs ++++ b/crates/project_panel/src/project_panel.rs +@@ -3060,6 +3060,10 @@ impl EventEmitter for ProjectPanel {} + impl EventEmitter for ProjectPanel {} + + impl Panel for ProjectPanel { ++ fn persistent_name() -> &'static str { ++ "Project Panel" ++ } ++ + fn position(&self, cx: &WindowContext) -> DockPosition { + match ProjectPanelSettings::get_global(cx).dock { + ProjectPanelDockPosition::Left => DockPosition::Left, +@@ -3110,10 +3114,6 @@ impl Panel for ProjectPanel { + Box::new(ToggleFocus) + } + +- fn persistent_name() -> &'static str { +- "Project Panel" +- } +- + fn starts_open(&self, cx: &WindowContext) -> bool { + let project = &self.project.read(cx); + project.dev_server_project_id().is_some() +-- +2.43.0 + diff --git a/scripts/zed-intergration/patches/0004-fix-make-mega_panel-runnable.patch b/scripts/zed-intergration/patches/0004-fix-make-mega_panel-runnable.patch new file mode 100644 index 000000000..f743cc1d0 --- /dev/null +++ b/scripts/zed-intergration/patches/0004-fix-make-mega_panel-runnable.patch @@ -0,0 +1,87 @@ +From 5e676b9b1669d078a25bcee9a0d1b87743862a30 Mon Sep 17 00:00:00 2001 +From: Neon +Date: Mon, 21 Oct 2024 10:00:36 +0800 +Subject: [PATCH 04/10] fix: make mega_panel runnable + +--- + assets/settings/default.json | 8 ++++++++ + crates/mega_panel/src/mega_panel.rs | 8 ++++---- + crates/zed/src/main.rs | 1 + + 3 files changed, 13 insertions(+), 4 deletions(-) + +diff --git a/assets/settings/default.json b/assets/settings/default.json +index 4bde5ee174..147f80a12b 100644 +--- a/assets/settings/default.json ++++ b/assets/settings/default.json +@@ -371,6 +371,14 @@ + "show": null + } + }, ++ "mega_panel": { ++ // Whether to show the mega panel button in the status bar ++ "button": true, ++ // Default width of the mega panel. ++ "default_width": 240, ++ // Where to dock the mega panel. Can be 'left' or 'right'. ++ "dock": "left", ++ }, + "outline_panel": { + // Whether to show the outline panel button in the status bar + "button": true, +diff --git a/crates/mega_panel/src/mega_panel.rs b/crates/mega_panel/src/mega_panel.rs +index e10ef55f0a..03c41f06ad 100644 +--- a/crates/mega_panel/src/mega_panel.rs ++++ b/crates/mega_panel/src/mega_panel.rs +@@ -10,7 +10,7 @@ use mega::{Mega, MegaFuse}; + use settings::{Settings, SettingsStore}; + use util::{ResultExt, TryFutureExt}; + use workspace::dock::{DockPosition, Panel, PanelEvent, PanelId}; +-use workspace::ui::IconName; ++use workspace::ui::{v_flex, IconName}; + use workspace::{Pane, Workspace}; + use crate::mega_panel_settings::{MegaPanelDockPosition, MegaPanelSettings}; + +@@ -52,6 +52,7 @@ pub fn init_settings(cx: &mut AppContext) { + + pub fn init(assets: impl AssetSource, cx: &mut AppContext) { + init_settings(cx); ++ println!("Mega settings should be registered"); + file_icons::init(assets, cx); + + cx.observe_new_views(|workspace: &mut Workspace, _| { +@@ -68,8 +69,7 @@ impl EventEmitter for MegaPanel {} + + impl Render for MegaPanel { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { +- todo!(); +- "" ++ v_flex() + } + } + +@@ -183,9 +183,9 @@ impl MegaPanel { + mega: mega.downgrade(), + workspace: workspace.weak_handle(), + focus_handle, ++ fs: workspace.app_state().fs.clone(), + pending_serialization: Task::ready(None), + width: None, +- + } + }); + +diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs +index 3e3094ecb0..005d086f48 100644 +--- a/crates/zed/src/main.rs ++++ b/crates/zed/src/main.rs +@@ -257,6 +257,7 @@ fn init_ui( + outline::init(cx); + project_symbols::init(cx); + project_panel::init(Assets, cx); ++ mega_panel::init(Assets, cx); + outline_panel::init(Assets, cx); + tasks_ui::init(cx); + snippets_ui::init(cx); +-- +2.43.0 + diff --git a/scripts/zed-intergration/patches/0005-mega_panel-very-basic-info-render-support.patch b/scripts/zed-intergration/patches/0005-mega_panel-very-basic-info-render-support.patch new file mode 100644 index 000000000..5bea9ecc0 --- /dev/null +++ b/scripts/zed-intergration/patches/0005-mega_panel-very-basic-info-render-support.patch @@ -0,0 +1,185 @@ +From e04d4b01af7991ed4b40fcd9553f20e67f2ec53f Mon Sep 17 00:00:00 2001 +From: Neon +Date: Tue, 22 Oct 2024 16:15:01 +0800 +Subject: [PATCH 05/10] mega_panel: very basic info render support + +--- + Cargo.lock | 4 +- + crates/mega_panel/Cargo.toml | 2 + + crates/mega_panel/src/mega_panel.rs | 61 +++++++++++++++++++++++++---- + 3 files changed, 58 insertions(+), 9 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index 80c72872e4..0474f53d0a 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -6889,7 +6889,7 @@ name = "mega" + version = "0.1.0" + dependencies = [ + "gpui", +- "reqwest 0.12.8", ++ "reqwest_client", + "serde", + ] + +@@ -6906,8 +6906,10 @@ dependencies = [ + "schemars", + "serde", + "settings", ++ "text", + "util", + "workspace", ++ "worktree", + ] + + [[package]] +diff --git a/crates/mega_panel/Cargo.toml b/crates/mega_panel/Cargo.toml +index d139d51f0d..2238a405c1 100644 +--- a/crates/mega_panel/Cargo.toml ++++ b/crates/mega_panel/Cargo.toml +@@ -13,11 +13,13 @@ workspace = true + [dependencies] + mega.workspace = true + workspace.workspace = true ++worktree.workspace = true + gpui.workspace = true + file_icons.workspace = true + settings.workspace = true + db.workspace = true + fs.workspace = true ++text.workspace = true + util.workspace = true + + anyhow.workspace = true +diff --git a/crates/mega_panel/src/mega_panel.rs b/crates/mega_panel/src/mega_panel.rs +index 03c41f06ad..b055d76fcf 100644 +--- a/crates/mega_panel/src/mega_panel.rs ++++ b/crates/mega_panel/src/mega_panel.rs +@@ -3,15 +3,17 @@ use anyhow::{anyhow, Context}; + use db::kvp::KEY_VALUE_STORE; + use file_icons::FileIcons; + use fs::Fs; +-use gpui::{actions, Action, AppContext, AssetSource, AsyncWindowContext, Entity, EventEmitter, FocusHandle, FocusableView, IntoElement, Model, Pixels, Render, Subscription, Task, UniformListScrollHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WindowContext}; ++use gpui::{actions, anchored, deferred, div, impl_actions, px, uniform_list, Action, AnyElement, AppContext, AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight, HighlightStyle, InteractiveElement, IntoElement, KeyContext, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Render, SharedString, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WindowContext}; + use gpui::private::serde_derive::{Deserialize, Serialize}; + use gpui::private::serde_json; + use mega::{Mega, MegaFuse}; + use settings::{Settings, SettingsStore}; ++use text::BufferId; + use util::{ResultExt, TryFutureExt}; + use workspace::dock::{DockPosition, Panel, PanelEvent, PanelId}; +-use workspace::ui::{v_flex, IconName}; ++use workspace::ui::{v_flex, IconName, Label, LabelCommon, LabelSize}; + use workspace::{Pane, Workspace}; ++use worktree::{Entry, ProjectEntryId, WorktreeId}; + use crate::mega_panel_settings::{MegaPanelDockPosition, MegaPanelSettings}; + + mod mega_panel_settings; +@@ -21,18 +23,18 @@ const MEGA_PANEL_KEY: &str = "MegaPanel"; + actions!( + mega_panel, + [ +- Open, + ToggleFocus, + ToggleFuseMount, ++ CheckoutPath, + ] + ); + + pub struct MegaPanel { +- mega: WeakModel, ++ mega_handle: WeakModel, + workspace: WeakView, + focus_handle: FocusHandle, + fs: Arc, +- pending_serialization: Task>, ++ pending_serialization: Task>, // TODO check how to use it + width: Option, + } + +@@ -41,6 +43,12 @@ struct SerializedMegaPanel { + width: Option, + } + ++#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] ++enum MegaEntry { ++ Dir(WorktreeId, ProjectEntryId), ++ File(WorktreeId, BufferId), ++} ++ + #[derive(Debug)] + pub enum Event { + Focus, +@@ -52,7 +60,6 @@ pub fn init_settings(cx: &mut AppContext) { + + pub fn init(assets: impl AssetSource, cx: &mut AppContext) { + init_settings(cx); +- println!("Mega settings should be registered"); + file_icons::init(assets, cx); + + cx.observe_new_views(|workspace: &mut Workspace, _| { +@@ -69,7 +76,27 @@ impl EventEmitter for MegaPanel {} + + impl Render for MegaPanel { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { +- v_flex() ++ let mega_panel = v_flex() ++ .id("mega_panel") ++ .size_full() ++ .relative() ++ .on_action(cx.listener(Self::toggle_fuse_mount)) ++ .on_action(cx.listener(Self::checkout_path)) ++ .track_focus(&self.focus_handle) ++ .gap_6() ++ .p_4() ++ .child( ++ Label::new("Mega Control Panel") ++ .single_line() ++ .weight(FontWeight::BOLD) ++ .size(LabelSize::Large) ++ ) ++ .gap_4() ++ .p_4() ++ .child(self.render_status_panel(cx)) ++ .child(self.render_control_panel(cx)); ++ ++ mega_panel + } + } + +@@ -180,7 +207,7 @@ impl MegaPanel { + }).detach(); + + Self { +- mega: mega.downgrade(), ++ mega_handle: mega.downgrade(), + workspace: workspace.weak_handle(), + focus_handle, + fs: workspace.app_state().fs.clone(), +@@ -213,4 +240,22 @@ impl MegaPanel { + cx.emit(Event::Focus); + } + } ++ ++ pub fn toggle_fuse_mount(&mut self, _: &ToggleFuseMount, cx: &mut ViewContext) { ++ // let mega = self.mega_handle.upgrade() ++ // .unwrap_or_else() ++ todo!() ++ } ++ ++ pub fn checkout_path(&mut self, _: &CheckoutPath, cx: &mut ViewContext) { ++ todo!() ++ } ++ ++ fn render_status_panel(&mut self, cx: &mut ViewContext) -> Div { ++ v_flex().child(Label::new("I am a status panel")) ++ } ++ ++ fn render_control_panel(&mut self, cx: &mut ViewContext) -> Div { ++ v_flex().child(Label::new("I am a control panel")) ++ } + } +\ No newline at end of file +-- +2.43.0 + diff --git a/scripts/zed-intergration/patches/0006-mega-basic-rest-api-support.patch b/scripts/zed-intergration/patches/0006-mega-basic-rest-api-support.patch new file mode 100644 index 000000000..da1f1f21f --- /dev/null +++ b/scripts/zed-intergration/patches/0006-mega-basic-rest-api-support.patch @@ -0,0 +1,228 @@ +From 8b57a9f693c95e92ee5299ff30b6cd791c08da71 Mon Sep 17 00:00:00 2001 +From: Neon +Date: Tue, 22 Oct 2024 19:12:08 +0800 +Subject: [PATCH 06/10] mega: basic rest api support + +--- + Cargo.lock | 1 + + crates/mega/Cargo.toml | 3 +- + crates/mega/src/delegate.rs | 58 ++++++++++++++++++++ + crates/mega/src/mega.rs | 84 ++++++++++++++++++++++++++--- + crates/mega_panel/src/mega_panel.rs | 1 + + 5 files changed, 139 insertions(+), 8 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index 0474f53d0a..1ffec64d85 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -6888,6 +6888,7 @@ dependencies = [ + name = "mega" + version = "0.1.0" + dependencies = [ ++ "bytes 0.4.12", + "gpui", + "reqwest_client", + "serde", +diff --git a/crates/mega/Cargo.toml b/crates/mega/Cargo.toml +index dec6093220..1572c73b90 100644 +--- a/crates/mega/Cargo.toml ++++ b/crates/mega/Cargo.toml +@@ -13,6 +13,7 @@ path = 'src/mega.rs' + + [dependencies] + gpui.workspace = true ++reqwest_client.workspace = true + +-reqwest.workspace = true + serde.workspace = true ++bytes = "0.4.12" +diff --git a/crates/mega/src/delegate.rs b/crates/mega/src/delegate.rs +index e69de29bb2..c767060048 100644 +--- a/crates/mega/src/delegate.rs ++++ b/crates/mega/src/delegate.rs +@@ -0,0 +1,58 @@ ++use serde::{Deserialize, Serialize}; ++ ++#[derive(Debug, Deserialize, Serialize)] ++pub(crate) struct MountRequest { ++ pub(crate) path: String, ++} ++ ++#[derive(Debug, Deserialize, Serialize)] ++struct MountResponse { ++ status: String, ++ mount: MountInfo, ++ message: String, ++} ++ ++#[derive(Debug, Deserialize, Serialize)] ++struct MountInfo { ++ hash: String, ++ path: String, ++ inode: u64, ++} ++ ++#[derive(Debug, Deserialize, Serialize)] ++struct MountsResponse { ++ status: String, ++ mounts: Vec, ++} ++ ++#[derive(Debug, Deserialize, Serialize)] ++struct UmountRequest { ++ path: Option, ++ inode: Option, ++} ++ ++#[derive(Debug, Deserialize, Serialize)] ++struct UmountResponse { ++ status: String, ++ message: String, ++} ++ ++#[derive(Debug, Deserialize, Serialize)] ++struct ConfigResponse { ++ status: String, ++ config: ConfigInfo, ++} ++ ++#[derive(Debug, Deserialize, Serialize)] ++struct ConfigInfo { ++ mega_url: String, ++ mount_path: String, ++ store_path: String, ++} ++ ++#[derive(Debug, Deserialize, Serialize)] ++struct ConfigRequest { ++ mega_url: Option, ++ mount_path: Option, ++ store_path: Option, ++} +\ No newline at end of file +diff --git a/crates/mega/src/mega.rs b/crates/mega/src/mega.rs +index 4b51004907..f24e2d9e5d 100644 +--- a/crates/mega/src/mega.rs ++++ b/crates/mega/src/mega.rs +@@ -6,8 +6,10 @@ + // 2. At least one daemon on this machine when zed startup. + // 3. Complete docs. + +-use std::sync::Arc; +-use gpui::{AppContext, Context, EventEmitter, Model, ModelContext}; ++use gpui::http_client::{AsyncBody, HttpClient}; ++use gpui::{AppContext, Context, EventEmitter, WindowContext}; ++use reqwest_client::ReqwestClient; ++use serde::Serialize; + + mod delegate; + mod fuse; +@@ -21,7 +23,10 @@ pub fn init(cx: &mut AppContext) { + + #[derive(Clone, Debug, PartialEq)] + pub enum Event {} +-pub struct Mega {} ++pub struct Mega { ++ mega_running: bool, ++ fuse_running: bool, ++} + + pub struct MegaFuse {} + +@@ -41,12 +46,77 @@ impl Mega { + } + + pub fn new(cx: &mut AppContext) -> Self { +- Mega {} ++ Mega { ++ fuse_running: false, ++ mega_running: false, ++ } + } + +- pub fn toggle_mega(&self) { todo!() } ++ pub fn toggle_mega(&self, cx: &mut WindowContext) { todo!() } + +- pub fn toggle_fuse(&self) { todo!() } ++ pub fn toggle_fuse(&self, cx: &mut WindowContext) { todo!() } ++ ++ pub fn toggle_mount(&self, cx: &mut WindowContext) { ++ // let req_body = delegate::MountRequest { ++ // path: "".parse().unwrap() ++ // }; ++ ++ cx.spawn(|_cx| async { ++ let client = ReqwestClient::new(); ++ let req = client.get( ++ "localhost:2725/api/fs/mount", ++ AsyncBody::empty(), ++ false ++ ).await; ++ }).detach(); ++ } + +- pub fn checkout_path(&self) { todo!() } ++ pub fn checkout_path(&self, cx: &mut WindowContext) { ++ cx.spawn(|_cx| async { ++ let client = ReqwestClient::new(); ++ let req = client.get( ++ "localhost:2725/api/fs/mount", ++ AsyncBody::empty(), ++ false ++ ).await; ++ }).detach(); ++ } ++ ++ pub fn get_fuse_config(&self, cx: &mut WindowContext) { ++ cx.spawn(|_cx| async { ++ let client = ReqwestClient::new(); ++ let req = client.get( ++ "localhost:2725/api/fs/mount", ++ AsyncBody::empty(), ++ false ++ ).await; ++ }).detach(); ++ } ++ ++ pub fn set_fuse_config(&self, cx: &mut WindowContext) { ++ cx.spawn(|_cx| async { ++ let client = ReqwestClient::new(); ++ let req = client.post_json( ++ "localhost:2725/api/config", ++ AsyncBody::empty(), ++ ).await; ++ }).detach(); ++ } ++ ++ pub fn get_fuse_mpoint(&self, cx: &mut WindowContext) { ++ cx.spawn(|_cx| async { ++ let client = ReqwestClient::new(); ++ let req = client.get( ++ "localhost:2725/api/config", ++ AsyncBody::empty(), ++ false ++ ).await; ++ }).detach(); ++ } ++ ++} ++ ++#[cfg(test)] ++mod test { ++ + } +diff --git a/crates/mega_panel/src/mega_panel.rs b/crates/mega_panel/src/mega_panel.rs +index b055d76fcf..76ac003f31 100644 +--- a/crates/mega_panel/src/mega_panel.rs ++++ b/crates/mega_panel/src/mega_panel.rs +@@ -244,6 +244,7 @@ impl MegaPanel { + pub fn toggle_fuse_mount(&mut self, _: &ToggleFuseMount, cx: &mut ViewContext) { + // let mega = self.mega_handle.upgrade() + // .unwrap_or_else() ++ + todo!() + } + +-- +2.43.0 + diff --git a/scripts/zed-intergration/patches/0007-mega_panel-control-pad-and-information-display.patch b/scripts/zed-intergration/patches/0007-mega_panel-control-pad-and-information-display.patch new file mode 100644 index 000000000..963b15cd2 --- /dev/null +++ b/scripts/zed-intergration/patches/0007-mega_panel-control-pad-and-information-display.patch @@ -0,0 +1,350 @@ +From 0ae6bfb40025aca376878982579a322eaa231143 Mon Sep 17 00:00:00 2001 +From: Neon +Date: Sat, 26 Oct 2024 22:02:19 +0800 +Subject: [PATCH 07/10] mega_panel: control pad and information display + +--- + crates/mega/src/mega.rs | 56 ++++++++--- + crates/mega_panel/src/mega_panel.rs | 150 +++++++++++++++++++++------- + 2 files changed, 154 insertions(+), 52 deletions(-) + +diff --git a/crates/mega/src/mega.rs b/crates/mega/src/mega.rs +index f24e2d9e5d..4bf4aa27d6 100644 +--- a/crates/mega/src/mega.rs ++++ b/crates/mega/src/mega.rs +@@ -6,8 +6,9 @@ + // 2. At least one daemon on this machine when zed startup. + // 3. Complete docs. + ++use std::path::{Path, PathBuf}; + use gpui::http_client::{AsyncBody, HttpClient}; +-use gpui::{AppContext, Context, EventEmitter, WindowContext}; ++use gpui::{AppContext, Context, EntityId, EventEmitter, ModelContext, WindowContext}; + use reqwest_client::ReqwestClient; + use serde::Serialize; + +@@ -22,10 +23,18 @@ pub fn init(cx: &mut AppContext) { + } + + #[derive(Clone, Debug, PartialEq)] +-pub enum Event {} ++pub enum Event { ++ MegaRunning(bool), ++ FuseRunning(bool), ++ FuseMounted(bool), ++} + pub struct Mega { + mega_running: bool, + fuse_running: bool, ++ fuse_mounted: bool, ++ ++ checkout_path: Option, ++ panel_id: Option, + } + + pub struct MegaFuse {} +@@ -49,19 +58,36 @@ impl Mega { + Mega { + fuse_running: false, + mega_running: false, ++ fuse_mounted: false, ++ checkout_path: None, ++ panel_id: None, + } +- } ++ } + +- pub fn toggle_mega(&self, cx: &mut WindowContext) { todo!() } ++ pub fn update_status(&mut self, cx: &mut ModelContext) { ++ if let None = self.panel_id { ++ return; ++ } ++ ++ cx.notify(); ++ } + +- pub fn toggle_fuse(&self, cx: &mut WindowContext) { todo!() } ++ pub fn status(&self) -> (bool, bool, bool) { ++ (self.mega_running, self.fuse_running, self.fuse_mounted) ++ } ++ ++ pub fn toggle_mega(&self, cx: &mut ModelContext) { todo!() } ++ ++ pub fn toggle_fuse(&self, cx: &mut ModelContext) { ++ ++ } + +- pub fn toggle_mount(&self, cx: &mut WindowContext) { ++ pub fn toggle_mount(&self, cx: &mut ModelContext) { + // let req_body = delegate::MountRequest { + // path: "".parse().unwrap() + // }; + +- cx.spawn(|_cx| async { ++ cx.spawn(|_this, _cx| async { + let client = ReqwestClient::new(); + let req = client.get( + "localhost:2725/api/fs/mount", +@@ -71,8 +97,8 @@ impl Mega { + }).detach(); + } + +- pub fn checkout_path(&self, cx: &mut WindowContext) { +- cx.spawn(|_cx| async { ++ pub fn checkout_path(&self, cx: &mut ModelContext) { ++ cx.spawn(|_this, _cx| async { + let client = ReqwestClient::new(); + let req = client.get( + "localhost:2725/api/fs/mount", +@@ -82,8 +108,8 @@ impl Mega { + }).detach(); + } + +- pub fn get_fuse_config(&self, cx: &mut WindowContext) { +- cx.spawn(|_cx| async { ++ pub fn get_fuse_config(&self, cx: &mut ModelContext) { ++ cx.spawn(|_this, _cx| async { + let client = ReqwestClient::new(); + let req = client.get( + "localhost:2725/api/fs/mount", +@@ -93,8 +119,8 @@ impl Mega { + }).detach(); + } + +- pub fn set_fuse_config(&self, cx: &mut WindowContext) { +- cx.spawn(|_cx| async { ++ pub fn set_fuse_config(&self, cx: &mut ModelContext) { ++ cx.spawn(|_this, _cx| async { + let client = ReqwestClient::new(); + let req = client.post_json( + "localhost:2725/api/config", +@@ -103,8 +129,8 @@ impl Mega { + }).detach(); + } + +- pub fn get_fuse_mpoint(&self, cx: &mut WindowContext) { +- cx.spawn(|_cx| async { ++ pub fn get_fuse_mpoint(&self, cx: &mut ModelContext) { ++ cx.spawn(|_this, _cx| async { + let client = ReqwestClient::new(); + let req = client.get( + "localhost:2725/api/config", +diff --git a/crates/mega_panel/src/mega_panel.rs b/crates/mega_panel/src/mega_panel.rs +index 76ac003f31..80a26cf4c2 100644 +--- a/crates/mega_panel/src/mega_panel.rs ++++ b/crates/mega_panel/src/mega_panel.rs +@@ -1,20 +1,19 @@ +-use std::sync::Arc; ++use crate::mega_panel_settings::{MegaPanelDockPosition, MegaPanelSettings}; + use anyhow::{anyhow, Context}; + use db::kvp::KEY_VALUE_STORE; +-use file_icons::FileIcons; + use fs::Fs; +-use gpui::{actions, anchored, deferred, div, impl_actions, px, uniform_list, Action, AnyElement, AppContext, AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight, HighlightStyle, InteractiveElement, IntoElement, KeyContext, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Render, SharedString, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WindowContext}; + use gpui::private::serde_derive::{Deserialize, Serialize}; + use gpui::private::serde_json; +-use mega::{Mega, MegaFuse}; +-use settings::{Settings, SettingsStore}; ++use gpui::{actions, div, Action, AppContext, AssetSource, AsyncWindowContext, Div, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Pixels, PromptLevel, Render, SharedString, Stateful, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext}; ++use mega::Mega; ++use settings::Settings; ++use std::sync::Arc; + use text::BufferId; + use util::{ResultExt, TryFutureExt}; +-use workspace::dock::{DockPosition, Panel, PanelEvent, PanelId}; +-use workspace::ui::{v_flex, IconName, Label, LabelCommon, LabelSize}; +-use workspace::{Pane, Workspace}; +-use worktree::{Entry, ProjectEntryId, WorktreeId}; +-use crate::mega_panel_settings::{MegaPanelDockPosition, MegaPanelSettings}; ++use workspace::dock::{DockPosition, Panel, PanelEvent}; ++use workspace::ui::{h_flex, v_flex, Button, Clickable, Color, FixedWidth, IconName, IconPosition, Label, LabelCommon, LabelSize, StyledExt, StyledTypography}; ++use workspace::Workspace; ++use worktree::{ProjectEntryId, WorktreeId}; + + mod mega_panel_settings; + +@@ -30,9 +29,10 @@ actions!( + ); + + pub struct MegaPanel { +- mega_handle: WeakModel, ++ mega_handle: Model, + workspace: WeakView, + focus_handle: FocusHandle, ++ scroll_handle: UniformListScrollHandle, + fs: Arc, + pending_serialization: Task>, // TODO check how to use it + width: Option, +@@ -86,16 +86,17 @@ impl Render for MegaPanel { + .gap_6() + .p_4() + .child( +- Label::new("Mega Control Panel") +- .single_line() +- .weight(FontWeight::BOLD) +- .size(LabelSize::Large) ++ h_flex().justify_center().child( ++ Label::new("Mega Control Panel") ++ .single_line() ++ .weight(FontWeight::BOLD) ++ .size(LabelSize::Large)) + ) +- .gap_4() +- .p_4() +- .child(self.render_status_panel(cx)) +- .child(self.render_control_panel(cx)); +- ++ .child(horizontal_separator(cx)) ++ .child(self.render_status(cx)) ++ .child(horizontal_separator(cx)) ++ .child(self.render_buttons(cx)); ++ + mega_panel + } + } +@@ -191,25 +192,26 @@ impl MegaPanel { + }); + } + panel +- } ++ }, + ) + } + + fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> View { + let mega_panel = cx.new_view(|cx| { + let mega = workspace.mega(); +- ++ + let focus_handle = cx.focus_handle(); + cx.on_focus(&focus_handle, Self::focus_in).detach(); +- ++ + cx.subscribe(mega, |this, mega, event, cx| { +- // TODO: listen for user operations ++ // TODO: listen for mega events + }).detach(); +- ++ + Self { +- mega_handle: mega.downgrade(), ++ mega_handle: mega.clone(), + workspace: workspace.weak_handle(), + focus_handle, ++ scroll_handle: UniformListScrollHandle::new(), + fs: workspace.app_state().fs.clone(), + pending_serialization: Task::ready(None), + width: None, +@@ -240,23 +242,97 @@ impl MegaPanel { + cx.emit(Event::Focus); + } + } +- ++ + pub fn toggle_fuse_mount(&mut self, _: &ToggleFuseMount, cx: &mut ViewContext) { + // let mega = self.mega_handle.upgrade() + // .unwrap_or_else() +- +- todo!() ++ ++ self.warn_unimplemented(cx); + } +- ++ + pub fn checkout_path(&mut self, _: &CheckoutPath, cx: &mut ViewContext) { +- todo!() ++ self.warn_unimplemented(cx); + } +- +- fn render_status_panel(&mut self, cx: &mut ViewContext) -> Div { +- v_flex().child(Label::new("I am a status panel")) ++ ++ fn render_status(&mut self, cx: &mut ViewContext) -> Div { ++ let ( ++ mega_running, ++ fuse_running, ++ fuse_mounted ++ ) = self.mega_handle.read(cx).status(); ++ ++ v_flex() ++ .gap_1() ++ .children([ ++ self.status_unit(cx, "Mega Backend:", mega_running), ++ self.status_unit(cx, "Scorpio Backend:", fuse_running), ++ self.status_unit(cx, "Fuse Mounted:", fuse_mounted), ++ ]) + } +- +- fn render_control_panel(&mut self, cx: &mut ViewContext) -> Div { +- v_flex().child(Label::new("I am a control panel")) ++ ++ fn render_buttons(&mut self, cx: &mut ViewContext) -> impl IntoElement { ++ ++ fn encap_btn(btn: Button) -> Div { ++ div() ++ .m_1() ++ .border_1() ++ .child(btn) ++ } ++ ++ v_flex() ++ .id("mega-control-pad") ++ .size_full() ++ .children([ ++ encap_btn(Button::new("btn_toggle_scorpio", "Toggle Scorpio") ++ .full_width() ++ .icon(IconName::Plus) ++ .icon_position(IconPosition::Start) ++ .on_click(cx.listener(|this, _, cx| { ++ this.mega_handle.update(cx, |mega, cx| mega.toggle_fuse(cx)); ++ })) ++ ), ++ encap_btn(Button::new("btn_toggle_mount", "Toggle Mount") ++ .full_width() ++ .icon(IconName::Context) ++ .icon_position(IconPosition::Start) ++ .on_click(cx.listener(|this, _, cx| { ++ this.mega_handle.update(cx, |mega, cx| mega.toggle_mount(cx)); ++ })) ++ ), ++ encap_btn(Button::new("btn_checkout", "Checkout Path") ++ .full_width() ++ .icon(IconName::Check) ++ .icon_position(IconPosition::Start) ++ .on_click(cx.listener(|this, _, cx| { ++ // TODO: should get the path here ++ this.mega_handle.update(cx, |mega, cx| mega.checkout_path(cx)); ++ })) ++ ), ++ ]) + } ++ ++ fn status_unit(&self, cx: &mut ViewContext, name: &'static str, state: bool) -> Stateful
{ ++ let unit_id = ElementId::from(SharedString::from(format!("status_{}", name.clone()))); ++ div() ++ .text_ui(cx) ++ .id(unit_id) ++ .child( ++ h_flex() ++ .justify_between() ++ .child(Label::new(name)) ++ .child(match state { ++ true => Label::new("Active").color(Color::Success), ++ false => Label::new("Inactive").color(Color::Error) ++ }) ++ ) ++ } ++ ++ fn warn_unimplemented(&self, cx: &mut ViewContext) { ++ let message = String::from("This operation is not implemented yet"); ++ let _ = cx.prompt(PromptLevel::Warning, "Unimplemented", Some(&message), &["Got it"]); ++ } ++} ++ ++fn horizontal_separator(cx: &mut WindowContext) -> Div { ++ div().mx_2().border_primary(cx).border_t_1() + } +\ No newline at end of file +-- +2.43.0 + diff --git a/scripts/zed-intergration/patches/0008-mega-feat-toggle-fuse-mount.patch b/scripts/zed-intergration/patches/0008-mega-feat-toggle-fuse-mount.patch new file mode 100644 index 000000000..9e3a072ae --- /dev/null +++ b/scripts/zed-intergration/patches/0008-mega-feat-toggle-fuse-mount.patch @@ -0,0 +1,579 @@ +From 8f8d3a30a45fa4f3b2359060d63a51f065c327d9 Mon Sep 17 00:00:00 2001 +From: Neon +Date: Tue, 29 Oct 2024 18:50:39 +0800 +Subject: [PATCH 08/10] mega: feat toggle fuse mount + +--- + Cargo.lock | 6 +- + assets/settings/default.json | 12 +++ + crates/mega/Cargo.toml | 5 +- + crates/mega/src/mega.rs | 116 +++++++++++++--------- + crates/mega/src/mega_settings.rs | 50 ++++++++++ + crates/mega_panel/src/mega_panel.rs | 66 +++++++++--- + crates/project_panel/Cargo.toml | 1 + + crates/project_panel/src/project_panel.rs | 31 ++++++ + crates/zed/src/main.rs | 3 +- + 9 files changed, 225 insertions(+), 65 deletions(-) + create mode 100644 crates/mega/src/mega_settings.rs + +diff --git a/Cargo.lock b/Cargo.lock +index 1ffec64d85..c25acea09d 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -6888,10 +6888,13 @@ dependencies = [ + name = "mega" + version = "0.1.0" + dependencies = [ +- "bytes 0.4.12", ++ "anyhow", ++ "bytes 1.7.2", + "gpui", + "reqwest_client", ++ "schemars", + "serde", ++ "settings", + ] + + [[package]] +@@ -8521,6 +8524,7 @@ dependencies = [ + "gpui", + "indexmap 1.9.3", + "language", ++ "mega", + "menu", + "pretty_assertions", + "project", +diff --git a/assets/settings/default.json b/assets/settings/default.json +index 147f80a12b..6ea0fff594 100644 +--- a/assets/settings/default.json ++++ b/assets/settings/default.json +@@ -379,6 +379,18 @@ + // Where to dock the mega panel. Can be 'left' or 'right'. + "dock": "left", + }, ++ "mega": { ++ // Url to communicate with mega ++ "mega_url": "http://localhost:8000", ++ // Url to communicate with fuse ++ "fuse_url": "http://localhost:2725", ++ // Default mount point for fuse ++ "mount_point": "/home/neon/projects", ++ // Path for mega executable ++ "mega_executable": "mega", ++ // Path for fuse executable ++ "fuse_executable": "scorpio" ++ }, + "outline_panel": { + // Whether to show the outline panel button in the status bar + "button": true, +diff --git a/crates/mega/Cargo.toml b/crates/mega/Cargo.toml +index 1572c73b90..658ed87476 100644 +--- a/crates/mega/Cargo.toml ++++ b/crates/mega/Cargo.toml +@@ -14,6 +14,9 @@ path = 'src/mega.rs' + [dependencies] + gpui.workspace = true + reqwest_client.workspace = true ++settings.workspace = true + + serde.workspace = true +-bytes = "0.4.12" ++bytes.workspace = true ++schemars.workspace = true ++anyhow.workspace = true +diff --git a/crates/mega/src/mega.rs b/crates/mega/src/mega.rs +index 4bf4aa27d6..84314de815 100644 +--- a/crates/mega/src/mega.rs ++++ b/crates/mega/src/mega.rs +@@ -2,39 +2,46 @@ + // The following requirements should be met: + // + // TODO: +-// 1. Only one daemon on this machine. ++// 1. Only one daemon on this machine. ++// This should be both warrantied by this module and scorpio + // 2. At least one daemon on this machine when zed startup. + // 3. Complete docs. ++// 4. Add settings for this module + + use std::path::{Path, PathBuf}; ++use std::sync::Arc; + use gpui::http_client::{AsyncBody, HttpClient}; +-use gpui::{AppContext, Context, EntityId, EventEmitter, ModelContext, WindowContext}; ++use gpui::{AppContext, Context, EntityId, EventEmitter, ModelContext, SharedString, WindowContext}; + use reqwest_client::ReqwestClient; + use serde::Serialize; ++use settings::Settings; ++use crate::mega_settings::MegaSettings; + + mod delegate; + mod fuse; ++mod mega_settings; + + pub fn init(cx: &mut AppContext) { +- // let reservation = cx.reserve_model(); +- // cx.insert_model(reservation, |cx| { +- // cx.new_model(|_cx| { Mega::new() }) +- // }); ++ Mega::init(cx); + } + + #[derive(Clone, Debug, PartialEq)] + pub enum Event { + MegaRunning(bool), + FuseRunning(bool), +- FuseMounted(bool), ++ FuseMounted(Option), ++ FuseCheckout(Option), + } + pub struct Mega { + mega_running: bool, + fuse_running: bool, + fuse_mounted: bool, + ++ mount_point: Option, + checkout_path: Option, +- panel_id: Option, ++ ++ mega_url: String, ++ fuse_url: String, + } + + pub struct MegaFuse {} +@@ -43,69 +50,93 @@ impl EventEmitter for Mega {} + + + impl Mega { +- pub fn init_settings(cx: &mut AppContext) { +- +- } ++ pub fn init_settings(cx: &mut AppContext) { MegaSettings::register(cx); } + + pub fn init(cx: &mut AppContext) { +- // let reservation = cx.reserve_model(); +- // cx.insert_model(reservation, |cx| { +- // cx.new_model(|_cx| { Mega::new() }) +- // }); ++ Self::init_settings(cx); + } + + pub fn new(cx: &mut AppContext) -> Self { ++ let mount_point = PathBuf::from(MegaSettings::get_global(cx).mount_point.clone()); ++ let mega_url = MegaSettings::get_global(cx).mega_url.clone(); ++ let fuse_url = MegaSettings::get_global(cx).fuse_url.clone(); ++ + Mega { + fuse_running: false, + mega_running: false, + fuse_mounted: false, ++ ++ mount_point: None, + checkout_path: None, +- panel_id: None, ++ ++ mega_url, ++ fuse_url, + } + } +- ++ + pub fn update_status(&mut self, cx: &mut ModelContext) { +- if let None = self.panel_id { +- return; +- } ++ ++ + + cx.notify(); + } +- ++ + pub fn status(&self) -> (bool, bool, bool) { + (self.mega_running, self.fuse_running, self.fuse_mounted) + } + +- pub fn toggle_mega(&self, cx: &mut ModelContext) { todo!() } ++ pub fn toggle_mega(&mut self, cx: &mut ModelContext) { ++ self.mega_running = !self.mega_running; ++ cx.emit(Event::MegaRunning(self.mega_running)); ++ } + +- pub fn toggle_fuse(&self, cx: &mut ModelContext) { +- ++ pub fn toggle_fuse(&mut self, cx: &mut ModelContext) { ++ self.fuse_running = !self.fuse_running; ++ cx.emit(Event::FuseRunning(self.fuse_running)); + } + +- pub fn toggle_mount(&self, cx: &mut ModelContext) { ++ pub fn toggle_mount(&mut self, cx: &mut ModelContext) { + // let req_body = delegate::MountRequest { + // path: "".parse().unwrap() + // }; +- +- cx.spawn(|_this, _cx| async { ++ ++ cx.spawn(|this, mut cx| async move { + let client = ReqwestClient::new(); + let req = client.get( + "localhost:2725/api/fs/mount", + AsyncBody::empty(), + false + ).await; ++ ++ if let Some(mega) = this.upgrade() { ++ let _ = mega.update(&mut cx, |this, cx| { ++ if this.fuse_mounted { ++ this.fuse_mounted = false; ++ } else { ++ // FIXME just pretending that we've got something from fuse response ++ this.fuse_mounted = true; ++ this.mount_point = Some(PathBuf::from("/home/neon/projects")); ++ } ++ cx.emit(Event::FuseMounted(this.mount_point.clone())); ++ }); ++ } + }).detach(); + } + +- pub fn checkout_path(&self, cx: &mut ModelContext) { +- cx.spawn(|_this, _cx| async { +- let client = ReqwestClient::new(); +- let req = client.get( +- "localhost:2725/api/fs/mount", +- AsyncBody::empty(), +- false +- ).await; +- }).detach(); ++ pub fn checkout_path(&mut self, cx: &mut ModelContext) { ++ // for now, we assume there's only one path being checkout at a time. ++ if self.checkout_path.is_none() { ++ cx.spawn(|_this, _cx| async { ++ let client = ReqwestClient::new(); ++ let req = client.get( ++ "localhost:2725/api/fs/mount", ++ AsyncBody::empty(), ++ false ++ ).await; ++ }).detach(); ++ } ++ ++ + } + + pub fn get_fuse_config(&self, cx: &mut ModelContext) { +@@ -128,18 +159,7 @@ impl Mega { + ).await; + }).detach(); + } +- +- pub fn get_fuse_mpoint(&self, cx: &mut ModelContext) { +- cx.spawn(|_this, _cx| async { +- let client = ReqwestClient::new(); +- let req = client.get( +- "localhost:2725/api/config", +- AsyncBody::empty(), +- false +- ).await; +- }).detach(); +- } +- ++ + } + + #[cfg(test)] +diff --git a/crates/mega/src/mega_settings.rs b/crates/mega/src/mega_settings.rs +new file mode 100644 +index 0000000000..82721208a1 +--- /dev/null ++++ b/crates/mega/src/mega_settings.rs +@@ -0,0 +1,50 @@ ++use std::path::PathBuf; ++use schemars::JsonSchema; ++use gpui::private::serde_derive::{Deserialize, Serialize}; ++use settings::{Settings, SettingsSources}; ++ ++#[derive(Default, Deserialize, Debug, Clone, PartialEq)] ++pub struct MegaSettings { ++ pub mega_url: String, ++ pub fuse_url: String, ++ pub mount_point: PathBuf, ++ pub mega_executable: PathBuf, ++ pub fuse_executable: PathBuf, ++} ++ ++#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] ++pub struct MegaSettingsContent { ++ /// Url to communicate with mega ++ /// ++ /// Default: http://localhost:8000 ++ pub mega_url: String, ++ /// Url to communicate with fuse ++ /// ++ /// Default: http://localhost:2725 ++ pub fuse_url: String, ++ /// Default mount point for fuse ++ /// ++ /// Default: "/" (for now) ++ pub mount_point: PathBuf, ++ /// Path for mega executable ++ /// ++ /// Default: "mega" (for now) ++ pub mega_executable: PathBuf, ++ /// Path for fuse executable ++ /// ++ /// Default: "scorpio" (for now) ++ pub fuse_executable: PathBuf, ++} ++ ++impl Settings for MegaSettings { ++ const KEY: Option<&'static str> = Some("mega"); ++ ++ type FileContent = MegaSettingsContent; ++ ++ fn load( ++ sources: SettingsSources, ++ _: &mut gpui::AppContext, ++ ) -> anyhow::Result { ++ sources.json_merge() ++ } ++} +\ No newline at end of file +diff --git a/crates/mega_panel/src/mega_panel.rs b/crates/mega_panel/src/mega_panel.rs +index 80a26cf4c2..82e6047244 100644 +--- a/crates/mega_panel/src/mega_panel.rs ++++ b/crates/mega_panel/src/mega_panel.rs +@@ -1,10 +1,11 @@ ++use std::path::PathBuf; + use crate::mega_panel_settings::{MegaPanelDockPosition, MegaPanelSettings}; + use anyhow::{anyhow, Context}; + use db::kvp::KEY_VALUE_STORE; + use fs::Fs; + use gpui::private::serde_derive::{Deserialize, Serialize}; + use gpui::private::serde_json; +-use gpui::{actions, div, Action, AppContext, AssetSource, AsyncWindowContext, Div, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Pixels, PromptLevel, Render, SharedString, Stateful, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext}; ++use gpui::{actions, div, Action, AppContext, AssetSource, AsyncWindowContext, Div, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight, InteractiveElement, IntoElement, Model, ParentElement, PathPromptOptions, Pixels, PromptLevel, Render, SharedString, Stateful, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext}; + use mega::Mega; + use settings::Settings; + use std::sync::Arc; +@@ -154,7 +155,7 @@ impl Panel for MegaPanel { + .then_some(IconName::FileGit) + } + +- fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str> { ++ fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { + Some("Mega Panel") + } + +@@ -203,6 +204,7 @@ impl MegaPanel { + let focus_handle = cx.focus_handle(); + cx.on_focus(&focus_handle, Self::focus_in).detach(); + ++ #[allow(unused)] + cx.subscribe(mega, |this, mega, event, cx| { + // TODO: listen for mega events + }).detach(); +@@ -243,16 +245,22 @@ impl MegaPanel { + } + } + +- pub fn toggle_fuse_mount(&mut self, _: &ToggleFuseMount, cx: &mut ViewContext) { +- // let mega = self.mega_handle.upgrade() +- // .unwrap_or_else() +- +- self.warn_unimplemented(cx); +- } +- + pub fn checkout_path(&mut self, _: &CheckoutPath, cx: &mut ViewContext) { + self.warn_unimplemented(cx); + } ++ ++ pub fn toggle_fuse_mount(&mut self, _: &ToggleFuseMount, cx: &mut ViewContext) { ++ // if let Some(workspace) = self.workspace.upgrade() { ++ // workspace.model.update(cx, |this, mx| { ++ // ++ // }); ++ // } ++ ++ self.mega_handle.update(cx, |this, cx | { ++ this.toggle_mount(cx); ++ ++ }); ++ } + + fn render_status(&mut self, cx: &mut ViewContext) -> Div { + let ( +@@ -283,12 +291,22 @@ impl MegaPanel { + .id("mega-control-pad") + .size_full() + .children([ ++ encap_btn(Button::new("btn_toggle_mega", "Toggle Mega") ++ .full_width() ++ .icon(IconName::Plus) ++ .icon_position(IconPosition::Start) ++ .on_click(cx.listener(|this, _, cx| { ++ this.mega_handle.update(cx, |mega, cx| mega.toggle_mega(cx)); ++ this.warn_unimplemented(cx); ++ })) ++ ), + encap_btn(Button::new("btn_toggle_scorpio", "Toggle Scorpio") + .full_width() + .icon(IconName::Plus) + .icon_position(IconPosition::Start) + .on_click(cx.listener(|this, _, cx| { + this.mega_handle.update(cx, |mega, cx| mega.toggle_fuse(cx)); ++ this.warn_unimplemented(cx); + })) + ), + encap_btn(Button::new("btn_toggle_mount", "Toggle Mount") +@@ -297,6 +315,9 @@ impl MegaPanel { + .icon_position(IconPosition::Start) + .on_click(cx.listener(|this, _, cx| { + this.mega_handle.update(cx, |mega, cx| mega.toggle_mount(cx)); ++ ++ ++ this.warn_unimplemented(cx); + })) + ), + encap_btn(Button::new("btn_checkout", "Checkout Path") +@@ -304,15 +325,34 @@ impl MegaPanel { + .icon(IconName::Check) + .icon_position(IconPosition::Start) + .on_click(cx.listener(|this, _, cx| { +- // TODO: should get the path here +- this.mega_handle.update(cx, |mega, cx| mega.checkout_path(cx)); ++ this.warn_unimplemented(cx); ++ // TODO: should read the path here ++ let options = PathPromptOptions { ++ files: true, ++ directories: true, ++ multiple: false, ++ }; ++ ++ let abs_path = cx.prompt_for_paths(options); ++ // if let Some(workspace_view) = this.workspace.upgrade() { ++ // let mut workspace = workspace_view.read(cx); ++ // workspace.open_workspace_for_paths(false, vec![], cx); ++ // } ++ cx.spawn(|this, mut cx| async move { ++ let Ok(Ok(Some(result))) = abs_path.await else { ++ return; ++ }; ++ ++ ++ }).detach(); ++ // mega.update(cx, |mega, cx| mega.checkout_path(cx)); + })) + ), + ]) + } + + fn status_unit(&self, cx: &mut ViewContext, name: &'static str, state: bool) -> Stateful
{ +- let unit_id = ElementId::from(SharedString::from(format!("status_{}", name.clone()))); ++ let unit_id = ElementId::from(SharedString::from(format!("status_{}", name))); + div() + .text_ui(cx) + .id(unit_id) +@@ -328,7 +368,7 @@ impl MegaPanel { + } + + fn warn_unimplemented(&self, cx: &mut ViewContext) { +- let message = String::from("This operation is not implemented yet"); ++ let message = String::from("This operation is not implemented yet, functions may not behave correctly"); + let _ = cx.prompt(PromptLevel::Warning, "Unimplemented", Some(&message), &["Got it"]); + } + } +diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml +index 11c7364e58..cc9e0ba8f6 100644 +--- a/crates/project_panel/Cargo.toml ++++ b/crates/project_panel/Cargo.toml +@@ -21,6 +21,7 @@ file_icons.workspace = true + indexmap.workspace = true + git.workspace = true + gpui.workspace = true ++mega.workspace = true + menu.workspace = true + pretty_assertions.workspace = true + project.workspace = true +diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs +index c4fb39f587..34f3bf0f54 100644 +--- a/crates/project_panel/src/project_panel.rs ++++ b/crates/project_panel/src/project_panel.rs +@@ -43,6 +43,7 @@ use std::{ + sync::Arc, + time::Duration, + }; ++use db::write_and_log; + use theme::ThemeSettings; + use ui::{prelude::*, v_flex, ContextMenu, Icon, KeyBinding, Label, ListItem, Tooltip}; + use util::{maybe, ResultExt, TryFutureExt}; +@@ -223,6 +224,7 @@ struct DraggedProjectEntryView { + impl ProjectPanel { + fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> View { + let project = workspace.project().clone(); ++ let mega = workspace.mega().clone(); + let project_panel = cx.new_view(|cx: &mut ViewContext| { + let focus_handle = cx.focus_handle(); + cx.on_focus(&focus_handle, Self::focus_in).detach(); +@@ -282,6 +284,34 @@ impl ProjectPanel { + ) + .detach(); + ++ cx.subscribe(&mega, |this, mega, mega_event, cx| match mega_event { ++ mega::Event::FuseMounted(Some(path)) => { ++ let path = path.to_owned(); ++ this.workspace ++ .update(cx, |workspace, cx| { ++ cx.spawn(|this, mut cx| async move { ++ if let Some(task) = this ++ .update(&mut cx, |this, cx| { ++ this.open_workspace_for_paths(false, vec!(path), cx) ++ }) ++ .log_err() ++ { ++ task.await.log_err(); ++ } ++ }) ++ .detach() ++ }) ++ .log_err(); ++ ++ ++ } ++ mega::Event::FuseCheckout(path) => { ++ // FIXME: impl it. ++ println!("Fuse Checkout NOT implemented in project for now!"); ++ } ++ _ => {} ++ }).detach(); ++ + cx.observe_global::(|_, cx| { + cx.notify(); + }) +@@ -491,6 +521,7 @@ impl ProjectPanel { + entry_id, + }); + ++ // FIXME add fuse dir specific behaviors + if let Some((worktree, entry)) = self.selected_sub_entry(cx) { + let auto_fold_dirs = ProjectPanelSettings::get_global(cx).auto_fold_dirs; + let is_root = Some(entry) == worktree.root_entry(); +diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs +index 005d086f48..1e5e9a6ac7 100644 +--- a/crates/zed/src/main.rs ++++ b/crates/zed/src/main.rs +@@ -523,10 +523,9 @@ fn main() { + Client::set_global(client.clone(), cx); + + zed::init(cx); +- mega::init(cx); + project::Project::init(&client, cx); + client::init(&client, cx); +- language::init(cx); ++ mega::init(cx); + let telemetry = client.telemetry(); + telemetry.start( + system_id.as_ref().map(|id| id.to_string()), +-- +2.43.0 + diff --git a/scripts/zed-intergration/patches/0009-Fix-dont-restore-previous-workspace.patch b/scripts/zed-intergration/patches/0009-Fix-dont-restore-previous-workspace.patch new file mode 100644 index 000000000..81b5ddfba --- /dev/null +++ b/scripts/zed-intergration/patches/0009-Fix-dont-restore-previous-workspace.patch @@ -0,0 +1,254 @@ +From 69f92bb2dbb1b2facf7b60cb6c6e1c0d211c4f4d Mon Sep 17 00:00:00 2001 +From: Neon +Date: Tue, 29 Oct 2024 21:08:56 +0800 +Subject: [PATCH 09/10] Fix: dont restore previous workspace + +--- + crates/project/src/project.rs | 106 ++++++++++++++++++++++ + crates/project_panel/src/project_panel.rs | 5 +- + crates/zed/src/main.rs | 85 +++++++++-------- + 3 files changed, 155 insertions(+), 41 deletions(-) + +diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs +index 6e731e9cba..6795d005d7 100644 +--- a/crates/project/src/project.rs ++++ b/crates/project/src/project.rs +@@ -576,6 +576,112 @@ impl Project { + TaskStore::init(Some(&client)); + } + ++ pub fn local_new ( ++ client: Arc, ++ node: NodeRuntime, ++ user_store: Model, ++ languages: Arc, ++ fs: Arc, ++ env: Option>, ++ cx: &mut AppContext, ++ ) -> Model { ++ cx.new_model(|cx: &mut ModelContext| { ++ let (tx, rx) = mpsc::unbounded(); ++ cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx)) ++ .detach(); ++ let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx); ++ let worktree_store = cx.new_model(|_| WorktreeStore::local(false, fs.clone())); ++ cx.subscribe(&worktree_store, Self::on_worktree_store_event) ++ .detach(); ++ ++ let buffer_store = cx.new_model(|cx| BufferStore::local(worktree_store.clone(), cx)); ++ cx.subscribe(&buffer_store, Self::on_buffer_store_event) ++ .detach(); ++ ++ let prettier_store = cx.new_model(|cx| { ++ PrettierStore::new( ++ node.clone(), ++ fs.clone(), ++ languages.clone(), ++ worktree_store.clone(), ++ cx, ++ ) ++ }); ++ ++ let environment = ProjectEnvironment::new(&worktree_store, env, cx); ++ ++ let task_store = cx.new_model(|cx| { ++ TaskStore::local( ++ fs.clone(), ++ buffer_store.downgrade(), ++ worktree_store.clone(), ++ environment.clone(), ++ cx, ++ ) ++ }); ++ ++ let settings_observer = cx.new_model(|cx| { ++ SettingsObserver::new_local( ++ fs.clone(), ++ worktree_store.clone(), ++ task_store.clone(), ++ cx, ++ ) ++ }); ++ cx.subscribe(&settings_observer, Self::on_settings_observer_event) ++ .detach(); ++ ++ let lsp_store = cx.new_model(|cx| { ++ LspStore::new_local( ++ buffer_store.clone(), ++ worktree_store.clone(), ++ prettier_store.clone(), ++ environment.clone(), ++ languages.clone(), ++ client.http_client(), ++ fs.clone(), ++ cx, ++ ) ++ }); ++ cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach(); ++ ++ Self { ++ buffer_ordered_messages_tx: tx, ++ collaborators: Default::default(), ++ worktree_store, ++ buffer_store, ++ lsp_store, ++ join_project_response_message_id: 0, ++ client_state: ProjectClientState::Local, ++ client_subscriptions: Vec::new(), ++ _subscriptions: vec![cx.on_release(Self::release)], ++ active_entry: None, ++ snippets, ++ languages, ++ client, ++ task_store, ++ user_store, ++ settings_observer, ++ fs, ++ ssh_client: None, ++ buffers_needing_diff: Default::default(), ++ git_diff_debouncer: DebouncedDelay::new(), ++ terminals: Terminals { ++ local_handles: Vec::new(), ++ }, ++ node: Some(node), ++ hosted_project_id: None, ++ dev_server_project_id: None, ++ search_history: Self::new_search_history(), ++ environment, ++ remotely_created_models: Default::default(), ++ ++ search_included_history: Self::new_search_history(), ++ search_excluded_history: Self::new_search_history(), ++ } ++ }) ++ } ++ + pub fn local( + client: Arc, + node: NodeRuntime, +diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs +index 34f3bf0f54..8b16829834 100644 +--- a/crates/project_panel/src/project_panel.rs ++++ b/crates/project_panel/src/project_panel.rs +@@ -286,6 +286,7 @@ impl ProjectPanel { + + cx.subscribe(&mega, |this, mega, mega_event, cx| match mega_event { + mega::Event::FuseMounted(Some(path)) => { ++ + let path = path.to_owned(); + this.workspace + .update(cx, |workspace, cx| { +@@ -302,8 +303,8 @@ impl ProjectPanel { + .detach() + }) + .log_err(); +- +- ++ ++ + } + mega::Event::FuseCheckout(path) => { + // FIXME: impl it. +diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs +index 1e5e9a6ac7..9df417f3e5 100644 +--- a/crates/zed/src/main.rs ++++ b/crates/zed/src/main.rs +@@ -867,49 +867,56 @@ async fn installation_id() -> Result { + Ok(IdType::New(installation_id)) + } + ++/// **Mega Fuse Integration:** ++/// ++/// This function is used for detect if there is an existing workspace opened ++/// in the last run. ++/// ++/// Whenever the case is, create an empty workspace ++/// because fuse dir cannot be warrantied to be available at zed startup. + async fn restore_or_create_workspace( + app_state: Arc, + cx: &mut AsyncAppContext, + ) -> Result<()> { +- if let Some(locations) = restorable_workspace_locations(cx, &app_state).await { +- for location in locations { +- match location { +- SerializedWorkspaceLocation::Local(location, _) => { +- let task = cx.update(|cx| { +- workspace::open_paths( +- location.paths().as_ref(), +- app_state.clone(), +- workspace::OpenOptions::default(), +- cx, +- ) +- })?; +- task.await?; +- } +- SerializedWorkspaceLocation::Ssh(ssh_project) => { +- let connection_options = SshConnectionOptions { +- host: ssh_project.host.clone(), +- username: ssh_project.user.clone(), +- port: ssh_project.port, +- password: None, +- }; +- let app_state = app_state.clone(); +- cx.spawn(move |mut cx| async move { +- recent_projects::open_ssh_project( +- connection_options, +- ssh_project.paths.into_iter().map(PathBuf::from).collect(), +- app_state, +- workspace::OpenOptions::default(), +- &mut cx, +- ) +- .await +- .log_err(); +- }) +- .detach(); +- } +- SerializedWorkspaceLocation::DevServer(_) => {} +- } +- } +- } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { ++ // if let Some(locations) = restorable_workspace_locations(cx, &app_state).await { ++ // for location in locations { ++ // match location { ++ // SerializedWorkspaceLocation::Local(location, _) => { ++ // let task = cx.update(|cx| { ++ // workspace::open_paths( ++ // location.paths().as_ref(), ++ // app_state.clone(), ++ // workspace::OpenOptions::default(), ++ // cx, ++ // ) ++ // })?; ++ // task.await?; ++ // } ++ // SerializedWorkspaceLocation::Ssh(ssh_project) => { ++ // let connection_options = SshConnectionOptions { ++ // host: ssh_project.host.clone(), ++ // username: ssh_project.user.clone(), ++ // port: ssh_project.port, ++ // password: None, ++ // }; ++ // let app_state = app_state.clone(); ++ // cx.spawn(move |mut cx| async move { ++ // recent_projects::open_ssh_project( ++ // connection_options, ++ // ssh_project.paths.into_iter().map(PathBuf::from).collect(), ++ // app_state, ++ // workspace::OpenOptions::default(), ++ // &mut cx, ++ // ) ++ // .await ++ // .log_err(); ++ // }) ++ // .detach(); ++ // } ++ // SerializedWorkspaceLocation::DevServer(_) => {} ++ // } ++ // } ++ if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) { + cx.update(|cx| show_welcome_view(app_state, cx))?.await?; + } else { + cx.update(|cx| { +-- +2.43.0 + diff --git a/scripts/zed-intergration/patches/0010-Fix-checkout-in-project_panel.patch b/scripts/zed-intergration/patches/0010-Fix-checkout-in-project_panel.patch new file mode 100644 index 000000000..5498ebc3d --- /dev/null +++ b/scripts/zed-intergration/patches/0010-Fix-checkout-in-project_panel.patch @@ -0,0 +1,242 @@ +From f2551513e804e8f43f6fac39d58befffd9dcda39 Mon Sep 17 00:00:00 2001 +From: Neon +Date: Wed, 30 Oct 2024 10:53:59 +0800 +Subject: [PATCH 10/10] Fix checkout in project_panel + +--- + crates/mega/src/mega.rs | 18 +++++++++- + crates/mega_panel/src/mega_panel.rs | 28 +++++++-------- + crates/project_panel/src/project_panel.rs | 44 +++++++++++++++++------ + 3 files changed, 63 insertions(+), 27 deletions(-) + +diff --git a/crates/mega/src/mega.rs b/crates/mega/src/mega.rs +index 84314de815..c27c40d7f4 100644 +--- a/crates/mega/src/mega.rs ++++ b/crates/mega/src/mega.rs +@@ -123,7 +123,7 @@ impl Mega { + }).detach(); + } + +- pub fn checkout_path(&mut self, cx: &mut ModelContext) { ++ pub fn checkout_path(&mut self, cx: &mut ModelContext, mut path: PathBuf) { + // for now, we assume there's only one path being checkout at a time. + if self.checkout_path.is_none() { + cx.spawn(|_this, _cx| async { +@@ -137,6 +137,22 @@ impl Mega { + } + + ++ } ++ ++ pub fn checkout_multi_path(&mut self, cx: &mut ModelContext, mut path: Vec) { ++ // for now, we assume there's only one path being checkout at a time. ++ if self.checkout_path.is_none() { ++ cx.spawn(|_this, _cx| async { ++ let client = ReqwestClient::new(); ++ let req = client.get( ++ "localhost:2725/api/fs/mount", ++ AsyncBody::empty(), ++ false ++ ).await; ++ }).detach(); ++ } ++ ++ + } + + pub fn get_fuse_config(&self, cx: &mut ModelContext) { +diff --git a/crates/mega_panel/src/mega_panel.rs b/crates/mega_panel/src/mega_panel.rs +index 82e6047244..8b141b57fd 100644 +--- a/crates/mega_panel/src/mega_panel.rs ++++ b/crates/mega_panel/src/mega_panel.rs +@@ -5,7 +5,7 @@ use db::kvp::KEY_VALUE_STORE; + use fs::Fs; + use gpui::private::serde_derive::{Deserialize, Serialize}; + use gpui::private::serde_json; +-use gpui::{actions, div, Action, AppContext, AssetSource, AsyncWindowContext, Div, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight, InteractiveElement, IntoElement, Model, ParentElement, PathPromptOptions, Pixels, PromptLevel, Render, SharedString, Stateful, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext}; ++use gpui::{actions, div, Action, AppContext, AssetSource, AsyncWindowContext, Div, ElementId, EventEmitter, Flatten, FocusHandle, FocusableView, FontWeight, InteractiveElement, IntoElement, Model, ParentElement, PathPromptOptions, Pixels, PromptLevel, Render, SharedString, Stateful, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext}; + use mega::Mega; + use settings::Settings; + use std::sync::Arc; +@@ -257,6 +257,7 @@ impl MegaPanel { + // } + + self.mega_handle.update(cx, |this, cx | { ++ // FIXME when there's already a visible worktree, this call will create a new window. + this.toggle_mount(cx); + + }); +@@ -315,8 +316,6 @@ impl MegaPanel { + .icon_position(IconPosition::Start) + .on_click(cx.listener(|this, _, cx| { + this.mega_handle.update(cx, |mega, cx| mega.toggle_mount(cx)); +- +- + this.warn_unimplemented(cx); + })) + ), +@@ -326,7 +325,6 @@ impl MegaPanel { + .icon_position(IconPosition::Start) + .on_click(cx.listener(|this, _, cx| { + this.warn_unimplemented(cx); +- // TODO: should read the path here + let options = PathPromptOptions { + files: true, + directories: true, +@@ -334,18 +332,18 @@ impl MegaPanel { + }; + + let abs_path = cx.prompt_for_paths(options); +- // if let Some(workspace_view) = this.workspace.upgrade() { +- // let mut workspace = workspace_view.read(cx); +- // workspace.open_workspace_for_paths(false, vec![], cx); +- // } +- cx.spawn(|this, mut cx| async move { +- let Ok(Ok(Some(result))) = abs_path.await else { +- return; +- }; +- ++ ++ // Why so annoying... ++ let mega = this.mega_handle.clone(); ++ cx.spawn(|this, mut cx|async move { ++ // mega.update(&mut cx, |this, cx| async move { ++ // if let Ok(Ok(Some(result))) = abs_path.await { ++ // this.checkout_multi_path(cx, result); ++ // } ++ // }).log_err(); + +- }).detach(); +- // mega.update(cx, |mega, cx| mega.checkout_path(cx)); ++ }) .detach(); ++ + })) + ), + ]) +diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs +index 8b16829834..d69d682b8f 100644 +--- a/crates/project_panel/src/project_panel.rs ++++ b/crates/project_panel/src/project_panel.rs +@@ -44,6 +44,7 @@ use std::{ + time::Duration, + }; + use db::write_and_log; ++use mega::Mega; + use theme::ThemeSettings; + use ui::{prelude::*, v_flex, ContextMenu, Icon, KeyBinding, Label, ListItem, Tooltip}; + use util::{maybe, ResultExt, TryFutureExt}; +@@ -58,7 +59,9 @@ const PROJECT_PANEL_KEY: &str = "ProjectPanel"; + const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX; + + pub struct ProjectPanel { ++ // TODO: use segment tree to fastly store multiple checkout path, + project: Model, ++ mega: Model, + fs: Arc, + scroll_handle: UniformListScrollHandle, + focus_handle: FocusHandle, +@@ -67,6 +70,7 @@ pub struct ProjectPanel { + /// Relevant only for auto-fold dirs, where a single project panel entry may actually consist of several + /// project entries (and all non-leaf nodes are guaranteed to be directories). + ancestors: HashMap, ++ checkout_entry_id: Option, + last_worktree_root_id: Option, + last_external_paths_drag_over_entry: Option, + expanded_dir_ids: HashMap>, +@@ -164,6 +168,7 @@ actions!( + UnfoldDirectory, + FoldDirectory, + SelectParent, ++ CheckoutPath, + ] + ); + +@@ -303,8 +308,8 @@ impl ProjectPanel { + .detach() + }) + .log_err(); +- +- ++ ++ this.focus_in(cx); + } + mega::Event::FuseCheckout(path) => { + // FIXME: impl it. +@@ -330,11 +335,13 @@ impl ProjectPanel { + + let mut this = Self { + project: project.clone(), ++ mega: mega.clone(), + fs: workspace.app_state().fs.clone(), + scroll_handle: UniformListScrollHandle::new(), + focus_handle, + visible_entries: Default::default(), + ancestors: Default::default(), ++ checkout_entry_id: Default::default(), + last_worktree_root_id: Default::default(), + last_external_paths_drag_over_entry: None, + expanded_dir_ids: Default::default(), +@@ -541,7 +548,10 @@ impl ProjectPanel { + menu.action("Search Inside", Box::new(NewSearchInDirectory)) + }) + } else { +- menu.action("New File", Box::new(NewFile)) ++ menu ++ .action("Checkout Path", Box::new(CheckoutPath)) ++ .separator() ++ .action("New File", Box::new(NewFile)) + .action("New Folder", Box::new(NewDirectory)) + .separator() + .when(is_local && cfg!(target_os = "macos"), |menu| { +@@ -1324,6 +1334,15 @@ impl ProjectPanel { + } + } + ++ fn checkout_specific_path(&mut self, _: &CheckoutPath, cx: &mut ViewContext) { ++ if let Some((worktree, entry)) = self.selected_entry_handle(cx) { ++ let path = entry.path.clone(); ++ self.mega.update(cx, |this, cx| { ++ this.checkout_path(cx, path.to_path_buf()) ++ }); ++ } ++ } ++ + fn autoscroll(&mut self, cx: &mut ViewContext) { + if let Some((_, _, index)) = self.selection.and_then(|s| self.index_for_selection(s)) { + self.scroll_handle.scroll_to_item(index); +@@ -2937,6 +2956,7 @@ impl Render for ProjectPanel { + .on_action(cx.listener(Self::new_search_in_directory)) + .on_action(cx.listener(Self::unfold_directory)) + .on_action(cx.listener(Self::fold_directory)) ++ .on_action(cx.listener(Self::checkout_specific_path)) + .when(!project.is_read_only(cx), |el| { + el.on_action(cx.listener(Self::new_file)) + .on_action(cx.listener(Self::new_directory)) +@@ -3023,14 +3043,16 @@ impl Render for ProjectPanel { + .p_4() + .track_focus(&self.focus_handle) + .child( +- Button::new("open_project", "Open a project") +- .full_width() +- .key_binding(KeyBinding::for_action(&workspace::Open, cx)) +- .on_click(cx.listener(|this, _, cx| { +- this.workspace +- .update(cx, |workspace, cx| workspace.open(&workspace::Open, cx)) +- .log_err(); +- })), ++ // Button::new("open_project", "Open a project") ++ // .full_width() ++ // .key_binding(KeyBinding::for_action(&workspace::Open, cx)) ++ // .on_click(cx.listener(|this, _, cx| { ++ // this.workspace ++ // .update(cx, |workspace, cx| workspace.open(&workspace::Open, cx)) ++ // .log_err(); ++ // })), ++ Label::new("Run mega daemon to mount the working directories") ++ + ) + .drag_over::(|style, _, cx| { + style.bg(cx.theme().colors().drop_target_background) +-- +2.43.0 +