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
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

mod command;
mod config;

#[cfg(test)]
mod tests;

use crate::{
    storage::{
        command_adapter::{
            command::Command,
            config::{CommandAdapterConfig, EnvVar},
        },
        BackupHandle, BackupHandleRef, BackupStorage, FileHandle, FileHandleRef, ShellSafeName,
        TextLine,
    },
    utils::error_notes::ErrorNotes,
};
use anyhow::Result;
use async_trait::async_trait;
use std::path::PathBuf;
use structopt::StructOpt;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};

#[derive(StructOpt)]
pub struct CommandAdapterOpt {
    #[structopt(
        long = "config",
        help = "Config file for the command adapter backup store."
    )]
    config: PathBuf,
}

/// A BackupStorage that delegates required APIs to configured command lines.
/// see `CommandAdapterConfig`.
pub struct CommandAdapter {
    config: CommandAdapterConfig,
}

impl CommandAdapter {
    pub fn new(config: CommandAdapterConfig) -> Self {
        Self { config }
    }

    pub async fn new_with_opt(opt: CommandAdapterOpt) -> Result<Self> {
        let config = CommandAdapterConfig::load_from_file(&opt.config).await?;

        Ok(Self::new(config))
    }

    fn cmd(&self, cmd_str: &str, env_vars: Vec<EnvVar>) -> Command {
        Command::new(cmd_str, env_vars, self.config.env_vars.clone())
    }
}

#[async_trait]
impl BackupStorage for CommandAdapter {
    async fn create_backup(&self, name: &ShellSafeName) -> Result<BackupHandle> {
        let mut child = self
            .cmd(
                &self.config.commands.create_backup,
                vec![EnvVar::backup_name(name.to_string())],
            )
            .spawn()?;
        let mut backup_handle = BackupHandle::new();
        child
            .stdout()
            .read_to_string(&mut backup_handle)
            .await
            .err_notes((file!(), line!(), name))?;
        child.join().await?;
        backup_handle.truncate(backup_handle.trim_end().len());

        Ok(backup_handle)
    }

    async fn create_for_write(
        &self,
        backup_handle: &BackupHandleRef,
        name: &ShellSafeName,
    ) -> Result<(FileHandle, Box<dyn AsyncWrite + Send + Unpin>)> {
        let mut child = self
            .cmd(
                &self.config.commands.create_for_write,
                vec![
                    EnvVar::backup_handle(backup_handle.to_string()),
                    EnvVar::file_name(name.to_string()),
                ],
            )
            .spawn()?;
        let mut file_handle = FileHandle::new();
        child
            .stdout()
            .read_to_string(&mut file_handle)
            .await
            .err_notes(backup_handle)?;
        file_handle.truncate(file_handle.trim_end().len());
        Ok((file_handle, Box::new(child.into_data_sink())))
    }

    async fn open_for_read(
        &self,
        file_handle: &FileHandleRef,
    ) -> Result<Box<dyn AsyncRead + Send + Unpin>> {
        let child = self
            .cmd(
                &self.config.commands.open_for_read,
                vec![EnvVar::file_handle(file_handle.to_string())],
            )
            .spawn()?;
        Ok(Box::new(child.into_data_source()))
    }

    async fn save_metadata_line(&self, name: &ShellSafeName, content: &TextLine) -> Result<()> {
        let mut child = self
            .cmd(
                &self.config.commands.save_metadata_line,
                vec![EnvVar::file_name(name.to_string())],
            )
            .spawn()?;

        child
            .stdin()
            .write_all(content.as_ref().as_bytes())
            .await
            .err_notes(name)?;
        child.join().await?;
        Ok(())
    }

    async fn list_metadata_files(&self) -> Result<Vec<FileHandle>> {
        let child = self
            .cmd(&self.config.commands.list_metadata_files, vec![])
            .spawn()?;

        let mut buf = FileHandle::new();
        child
            .into_data_source()
            .read_to_string(&mut buf)
            .await
            .err_notes((file!(), line!(), &buf))?;
        Ok(buf.lines().map(str::to_string).collect())
    }
}