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
use crate::errors::*;
use crate::{Mark, HEADER};
use bczhc_lib::io::ReadText;
use bczhc_lib::time::current_time_millis;
use byteorder::{BigEndian, WriteBytesExt};
use cfg_if::cfg_if;
use clap::ArgMatches;
use std::ffi::OsStr;
use std::fs::File;
use std::io;
use std::io::{stdin, Cursor, Read, Write};
use std::net::TcpStream;
use std::path::Path;
use walkdir::WalkDir;

enum PipeSendMode {
    File,
    Text,
}

enum SendMode<'a> {
    Pipe(PipeSendMode),
    Path(&'a str),
}

pub fn main(matches: &ArgMatches) -> Result<()> {
    let ip = matches.value_of("ip").unwrap();
    let port = matches.value_of("port").unwrap();

    let pipe_mode = if matches.is_present("pipe-mode") {
        if matches.is_present("file-mode") {
            SendMode::Pipe(PipeSendMode::File)
        } else if matches.is_present("text-mode") {
            SendMode::Pipe(PipeSendMode::Text)
        } else {
            unreachable!();
        }
    } else {
        SendMode::Path(matches.value_of("file").unwrap())
    };

    let mut stream = TcpStream::connect(format!("{}:{}", ip, port))?;

    match pipe_mode {
        SendMode::Pipe(mode) => {
            let mut stdin = stdin().lock();
            match mode {
                PipeSendMode::File => {
                    send_file(&mut stream, &mut stdin, current_time_millis().to_string())?;
                }
                PipeSendMode::Text => {
                    send_text(&mut stream, &stdin.read_text()?)?;
                }
            }
        }
        SendMode::Path(path) => {
            let path = Path::new(path);
            if path.is_dir() {
                send_dir(&mut stream, path)?;
            } else {
                let filename = path
                    .file_name()
                    .map_or_else(|| Err(Error::UnknownFilename), Ok)?;
                send_file(&mut stream, &mut File::open(path)?, filename)?;
            }
        }
    }
    Ok(())
}

pub fn send_file<S, R, N>(stream: &mut S, reader: &mut R, filename: N) -> Result<()>
where
    S: Write,
    R: Read,
    N: AsRef<OsStr>,
{
    stream.write_header()?;
    stream.write_mark(Mark::File)?;

    let filename = filename.as_ref();
    #[allow(clippy::needless_late_init)]
    let filename_bytes;
    cfg_if! {
        if #[cfg(unix)] {
            use std::os::unix::prelude::OsStrExt;
            filename_bytes = filename.as_bytes();
        } else {
            let filename: &str = filename
                .to_str()
                .map_or_else(|| Err(Error::InvalidCharset), Ok)?;
            filename_bytes = filename.as_bytes();
        }
    }

    stream.write_u32::<BigEndian>(filename_bytes.len() as u32)?;
    stream.write_all(filename_bytes)?;

    std::io::copy(reader, stream)?;
    Ok(())
}

pub fn send_dir<S, P>(stream: &mut S, dir: P) -> Result<()>
where
    S: Write,
    P: AsRef<Path>,
{
    stream.write_header()?;
    stream.write_mark(Mark::Tar)?;

    use tar::Builder;
    let mut builder = Builder::new(stream);
    let files = WalkDir::new(&dir)
        .into_iter()
        .map(|x| x.unwrap())
        .filter(|x| x.path().is_file());
    for entry in files {
        let relative_path = pathdiff::diff_paths(entry.path(), &dir).unwrap();

        builder.append_file(&relative_path, &mut File::open(entry.path())?)?;
        println!("{:?}", entry.path());
    }
    builder.finish()?;
    Ok(())
}

pub fn send_text<S>(stream: &mut S, text: &str) -> Result<()>
where
    S: Write,
{
    stream.write_header()?;
    stream.write_mark(Mark::Text)?;

    let mut cursor = Cursor::new(text);
    io::copy(&mut cursor, stream)?;
    Ok(())
}

trait Ext<W>
where
    W: Write,
    Self: Write,
{
    fn write_header(&mut self) -> io::Result<()> {
        self.write_all(HEADER)
    }

    fn write_mark(&mut self, mark: Mark) -> io::Result<()> {
        self.write_u8(mark as u8)
    }
}

impl<W: Write> Ext<W> for W {}