154 lines · 4.8 KB · Rust
RawDownload
// Copyright 2021-2022 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. #![expect(missing_docs)] use std::fmt::Debug;use std::fmt::Formatter;use std::fs;use std::io;use std::path::Path;use std::path::PathBuf; use async_trait::async_trait;use thiserror::Error; use crate::backend::BackendInitError;use crate::file_util::IoResultExt as _;use crate::file_util::PathError;use crate::hex_util;use crate::lock::FileLock;use crate::object_id::ObjectId as _;use crate::op_heads_store::OpHeadsStore;use crate::op_heads_store::OpHeadsStoreError;use crate::op_heads_store::OpHeadsStoreLock;use crate::op_store::OperationId; /// Error that may occur during [`SimpleOpHeadsStore`] initialization.#[derive(Debug, Error)]#[error("Failed to initialize simple operation heads store")]pub struct SimpleOpHeadsStoreInitError(#[from] pub PathError); impl From<SimpleOpHeadsStoreInitError> for BackendInitError {    fn from(err: SimpleOpHeadsStoreInitError) -> Self {        Self(err.into())    }} pub struct SimpleOpHeadsStore {    dir: PathBuf,} impl Debug for SimpleOpHeadsStore {    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {        f.debug_struct("SimpleOpHeadsStore")            .field("dir", &self.dir)            .finish()    }} impl SimpleOpHeadsStore {    pub fn name() -> &'static str {        "simple_op_heads_store"    }     pub fn init(dir: &Path) -> Result<Self, SimpleOpHeadsStoreInitError> {        let op_heads_dir = dir.join("heads");        fs::create_dir(&op_heads_dir).context(&op_heads_dir)?;        Ok(Self { dir: op_heads_dir })    }     pub fn load(dir: &Path) -> Self {        let op_heads_dir = dir.join("heads");        Self { dir: op_heads_dir }    }     fn add_op_head(&self, id: &OperationId) -> io::Result<()> {        std::fs::write(self.dir.join(id.hex()), "")    }     fn remove_op_head(&self, id: &OperationId) -> io::Result<()> {        std::fs::remove_file(self.dir.join(id.hex())).or_else(|err| {            if err.kind() == io::ErrorKind::NotFound {                // It's fine if the old head was not found. It probably means                // that we're on a distributed file system where the locking                // doesn't work. We'll probably end up with two current                // heads. We'll detect that next time we load the view.                Ok(())            } else {                Err(err)            }        })    }} struct SimpleOpHeadsStoreLock {    _lock: FileLock,} impl OpHeadsStoreLock for SimpleOpHeadsStoreLock {} #[async_trait]impl OpHeadsStore for SimpleOpHeadsStore {    fn name(&self) -> &str {        Self::name()    }     async fn update_op_heads(        &self,        old_ids: &[OperationId],        new_id: &OperationId,    ) -> Result<(), OpHeadsStoreError> {        assert!(!old_ids.contains(new_id));        self.add_op_head(new_id)            .map_err(|err| OpHeadsStoreError::Write {                new_op_id: new_id.clone(),                source: err.into(),            })?;        for old_id in old_ids {            self.remove_op_head(old_id)                .map_err(|err| OpHeadsStoreError::Write {                    new_op_id: new_id.clone(),                    source: err.into(),                })?;        }        Ok(())    }     async fn get_op_heads(&self) -> Result<Vec<OperationId>, OpHeadsStoreError> {        let mut op_heads = vec![];        for op_head_entry in            std::fs::read_dir(&self.dir).map_err(|err| OpHeadsStoreError::Read(err.into()))?        {            let op_head_file_name = op_head_entry                .map_err(|err| OpHeadsStoreError::Read(err.into()))?                .file_name();            let op_head_file_name = op_head_file_name.to_str().ok_or_else(|| {                OpHeadsStoreError::Read(                    format!("Non-utf8 in op head file name: {op_head_file_name:?}").into(),                )            })?;            if let Some(op_head) = hex_util::decode_hex(op_head_file_name) {                op_heads.push(OperationId::new(op_head));            }        }        Ok(op_heads)    }     async fn lock(&self) -> Result<Box<dyn OpHeadsStoreLock + '_>, OpHeadsStoreError> {        let lock = FileLock::lock(self.dir.join("lock"))            .map_err(|err| OpHeadsStoreError::Lock(err.into()))?;        Ok(Box::new(SimpleOpHeadsStoreLock { _lock: lock }))    }}