1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
use crate::{errors::YsError, snapshot::{directory::SnapShotDirectory, SnapShot, SnapShotData}, Ignores, LocalObjectStore, ObjectID, ObjectStore, BRANCHES_DIRECTORY, CURRENT_BRANCH_FILE};
use serde::{Deserialize, Serialize};
use serde_json::{ser::PrettyFormatter, Serializer};
use std::{
    borrow::Cow,
    collections::BTreeSet,
    fmt::Debug,
    fs::{create_dir, create_dir_all, read_dir, read_to_string, try_exists, File},
    future::Future,
    io::Write,
    path::{Path, PathBuf},
};

/// `.ys` 文件夹
#[derive(Debug)]
pub struct DotYuanShen {
    root: PathBuf,
}
#[derive(Debug)]
pub struct InitializeConfig {
    pub current: PathBuf,
    pub initial_branch: Cow<'static, str>,
}

impl InitializeConfig {
    pub async fn new(&self) -> Result<DotYuanShen, YsError> {
        let root = self.current.join(".ys");
        if read_dir(&root).is_ok() {
            return Ok(DotYuanShen { root });
        }
        create_dir_all(&root)?;
        self.generate_branches()?;

        // 创建初始提交
        let mut store = LocalObjectStore::new(self.join("store"))?;
        let directory = SnapShotDirectory::default();
        let directory = store.insert_json(&directory).await?;
        let snapshot = SnapShot {
            directory,
            previous: BTreeSet::new(),
            data: SnapShotData { kind: 0, message: "Project initialized!".to_string(), authors: Default::default() },
        };
        let snapshot_id = store.insert_json(&snapshot).await?;
        write_json(&snapshot_id, &root.join("branches").join(self.initial_branch.as_ref()))?;
        let ignores = Ignores::default();
        write_json(&ignores, &root.join("ignores"))?;

        Ok(DotYuanShen { root })
    }

    fn generate_branches(&self) -> std::io::Result<()> {
        // Specify the current branch
        let mut file = File::options().create(true).write(true).open(self.join("branch"))?;
        file.write(self.initial_branch.as_bytes())?;
        // Create the default branch
        create_dir(self.join("branches"))
    }

    fn join(&self, path: &str) -> PathBuf {
        self.current.join(crate::DOT_YUAN_SHEN).join(path)
    }
}
impl DotYuanShen {
    /// 打开一个已经存在的 `.ys` 文件夹
    pub fn open(root: PathBuf) -> Result<Self, YsError> {
        if root.exists() {
            // TODO: check valid
            Ok(Self { root })
        }
        else {
            Err(YsError::path_error(std::io::Error::new(std::io::ErrorKind::NotFound, "Directory does not exist"), root))
        }
    }
}

impl DotYuanShen {
    /// Get the root path of the `.ys` folder
    pub fn root(&self) -> &Path {
        &self.root
    }

    /// Get the current branch
    pub fn get_branch(&self) -> Result<String, YsError> {
        Ok(read_to_string(&self.root.join(CURRENT_BRANCH_FILE))?)
    }

    /// Set current branch to given name
    pub fn set_branch(&self, new: &str) -> Result<(), YsError> {
        let mut file = File::options().write(true).truncate(true).open(&self.root.join(CURRENT_BRANCH_FILE))?;
        file.write(new.as_bytes())?;
        Ok(())
    }

    pub fn get_branch_snapshot_id(&self, branch: &str) -> Result<ObjectID, YsError> {
        read_json(&self.root.join(BRANCHES_DIRECTORY).join(&branch))
    }

    pub fn set_branch_snapshot_id(&self, branch: &str, object_id: ObjectID) -> Result<(), YsError> {
        write_json(&object_id, &self.root.join(BRANCHES_DIRECTORY).join(&branch))
    }

    pub fn current_snapshot_id(&self) -> Result<ObjectID, YsError> {
        let branch = self.get_branch()?;
        self.get_branch_snapshot_id(&branch)
    }

    pub fn create_branch(&self, new_branch: &str) -> Result<(), YsError> {
        if !self.branch_exists(&new_branch)? {
            let snapshot_id = self.current_snapshot_id()?;
            return write_json(&snapshot_id, &self.root.join(BRANCHES_DIRECTORY).join(&new_branch));
        }
        Ok(())
    }

    /// Checks whether a branch with a given name exists
    pub fn branch_exists(&self, branch: &str) -> Result<bool, YsError> {
        Ok(try_exists(self.root.join(BRANCHES_DIRECTORY).join(&branch))?)
    }

    pub fn store(&self) -> Result<LocalObjectStore, YsError> {
        Ok(LocalObjectStore::new(self.root.clone())?)
    }

    pub fn ignores(&self) -> Result<Ignores, YsError> {
        Ok(read_json(&self.root.join("ignores"))?)
    }
}

pub trait InsertJson {
    fn insert_json<A: Serialize + Send + Sync>(&mut self, thing: &A) -> impl Future<Output = Result<ObjectID, YsError>> + Send;

    fn read_json<A: for<'de> Deserialize<'de>>(&mut self, id: ObjectID) -> impl Future<Output = Result<A, YsError>> + Send;
}

impl InsertJson for LocalObjectStore {
    async fn insert_json<A: Serialize + Send + Sync>(&mut self, thing: &A) -> Result<ObjectID, YsError> {
        Ok(self.insert(&serde_json::to_vec_pretty(thing)?).await?)
    }

    async fn read_json<A: for<'de> Deserialize<'de>>(&mut self, object_id: ObjectID) -> Result<A, YsError> {
        let object = self.read(object_id).await?;
        Ok(serde_json::from_slice(&object)?)
    }
}

fn read_json<A: for<'de> Deserialize<'de>>(path: &Path) -> Result<A, YsError> {
    Ok(serde_json::from_reader(File::options().read(true).open(path)?)?)
}

fn write_json<A: Serialize>(thing: &A, path: &Path) -> Result<(), YsError> {
    let file = File::options().write(true).create(true).open(path)?;
    let mut ser = Serializer::with_formatter(file, PrettyFormatter::with_indent(b"    "));
    Ok(thing.serialize(&mut ser)?)
}