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
//! The filesystem SDK module.

mod error;
mod file;

pub use file::MenmosFile;

use futures::TryStreamExt;

use snafu::prelude::*;

use crate::util;
use crate::{ClientRC, FileMetadata};

pub use error::FsError;
use error::*;

/// The entrypoint structure of the filesystem SDK.
#[derive(Clone)]
pub struct MenmosFs {
    client: ClientRC,
}

impl MenmosFs {
    #[doc(hidden)]
    pub fn new(client: ClientRC) -> Self {
        Self { client }
    }

    /// Create a new file with the provided metadata.
    ///
    /// This function will return a handle to the created file, at offset 0.
    ///
    /// # Examples
    /// ```no_run
    /// use menmos::FileMetadata;
    /// # use menmos::fs::MenmosFs;
    ///
    /// # #[tokio::main]
    /// # async fn main() {
    /// # let client = menmos_client::Client::new("a", "b", "c").await.unwrap();
    /// # let fs = MenmosFs::new(std::sync::Arc::new(client));
    /// let handle = fs.create_file(FileMetadata::new("test.txt").with_tag("sdk_file"))
    ///     .await
    ///     .unwrap();
    /// # }
    /// ```
    pub async fn create_file(&self, metadata: FileMetadata) -> Result<MenmosFile> {
        MenmosFile::create(self.client.clone(), metadata).await
    }

    async fn remove_blob_unchecked<S: AsRef<str>>(&self, id: S) -> Result<()> {
        // TODO: Update the menmos client so that Client::delete takes a ref.
        self.client
            .delete(String::from(id.as_ref()))
            .await
            .with_context(|_| BlobDeleteSnafu {
                blob_id: String::from(id.as_ref()),
            })?;
        Ok(())
    }

    /// Remove a blob by its ID.
    ///
    /// If the specified blob ID does not exist, no error is returned and no operation
    /// is performed.
    ///
    /// # Errors
    ///
    /// If this function is called with an ID corresponding to a blob that is _not_
    /// a file, an error variant will be returned.
    ///
    /// # Examples
    /// ```no_run
    /// # use menmos::fs::MenmosFs;
    /// # #[tokio::main]
    /// # async fn main() {
    /// # let client = menmos_client::Client::new("a", "b", "c").await.unwrap();
    /// # let fs = MenmosFs::new(std::sync::Arc::new(client));
    /// fs.remove("<a file blob ID>").await.unwrap();
    /// # }
    /// ```
    pub async fn remove<S: AsRef<str>>(&self, id: S) -> Result<()> {
        match util::get_meta_if_exists(&self.client, id.as_ref())
            .await
            .context(FileRemoveSnafu {
                blob_id: String::from(id.as_ref()),
            })? {
            Some(_) => self.remove_blob_unchecked(id).await,
            None => Ok(()),
        }
    }

    /// Recursively remove a blob along with all its children.
    ///
    /// If the specified blob ID does not exist, no error is returned and no operation
    /// is performed.
    ///
    /// # Errors
    ///
    /// If this function is called with an ID corresponding to a blob that is _not_
    /// a directory, an error variant will be returned.
    ///
    /// # Examples
    /// ```no_run
    /// # use menmos::fs::MenmosFs;
    /// # #[tokio::main]
    /// # async fn main() {
    /// # let client = menmos_client::Client::new("a", "b", "c").await.unwrap();
    /// # let fs = MenmosFs::new(std::sync::Arc::new(client));
    /// fs.remove_all("<a dir blob ID>").await.unwrap();
    /// # }
    /// ```
    pub async fn remove_all<S: AsRef<str>>(&self, id: S) -> Result<()> {
        match util::get_meta_if_exists(&self.client, id.as_ref())
            .await
            .context(DirRemoveSnafu)?
        {
            Some(_) => {
                // We don't do the deletion recursively because recursivity + async requires a lot of indirection.
                let mut delete_stack: Vec<MenmosFile> = vec![];
                while let Some(target) = delete_stack.pop() {
                    let children = target.list().try_collect::<Vec<_>>().await?;

                    delete_stack.extend(children.into_iter());
                    self.remove_blob_unchecked(target.id()).await?;
                }

                Ok(())
            }

            None => Ok(()),
        }
    }
}