generated from EmbarkStudios/opensource-template
-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathcp.rs
128 lines (108 loc) · 3.75 KB
/
cp.rs
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
use crate::util;
use anyhow::Context as _;
use std::{convert::TryFrom, path::PathBuf};
use tame_gcs::objects::{self, Metadata};
#[derive(clap::ValueEnum, Clone, Copy)]
enum Acl {
ProjectPrivate,
Private,
PublicRead,
AuthenticatedRead,
BucketOwnerRead,
BucketOwnerFullControl,
}
impl From<Acl> for tame_gcs::common::PredefinedAcl {
fn from(a: Acl) -> Self {
match a {
Acl::ProjectPrivate => Self::ProjectPrivate,
Acl::Private => Self::Private,
Acl::PublicRead => Self::PublicRead,
Acl::AuthenticatedRead => Self::AuthenticatedRead,
Acl::BucketOwnerRead => Self::BucketOwnerRead,
Acl::BucketOwnerFullControl => Self::BucketOwnerFullControl,
}
}
}
#[derive(clap::Parser)]
pub struct Args {
/// Predefined ACL to apply to the destination GCS object
#[clap(short = 'a')]
predef_acl: Option<Acl>,
/// A gs: URL or filepath for the source path to copy from,
/// wildcards are not currently supported
src_url: String,
/// A gs: URL or filepath for the destination to copy to,
/// wildcards are not currently supported
dest_url: String,
}
enum DataPath {
Gs(util::GsUrl),
Local(PathBuf),
}
impl DataPath {
#[inline]
fn is_local(&self) -> bool {
matches!(self, Self::Local(_))
}
}
impl TryFrom<String> for DataPath {
type Error = anyhow::Error;
fn try_from(s: String) -> anyhow::Result<Self> {
if s.starts_with("gs://") {
let url = url::Url::parse(&s)?;
Ok(Self::Gs(util::gs_url_to_object_id(&url)?))
} else {
Ok(Self::Local(PathBuf::from(s)))
}
}
}
// cp is probably gsutil's most complicated subcommand, so we only implement
// a bare minimum
pub async fn cmd(ctx: &util::RequestContext, args: Args) -> anyhow::Result<()> {
use std::fs;
let src = DataPath::try_from(args.src_url)?;
let dst = DataPath::try_from(args.dest_url)?;
// Just support gcs to local or vice versa, not local to local or gcs to gcs
if src.is_local() == dst.is_local() {
let location = if src.is_local() { "local disk" } else { "gcs" };
anyhow::bail!("source and destination are both located on {location}")
}
match (&src, &dst) {
(DataPath::Local(ref src), DataPath::Gs(dst)) => {
let src_file = fs::File::open(src).context("source path")?;
let src_len = src_file.metadata()?.len();
let optional = args.predef_acl.map(|acl| objects::InsertObjectOptional {
predefined_acl: Some(acl.into()),
..Default::default()
});
let insert_req = ctx.obj.insert_multipart(
dst.bucket(),
src_file,
src_len,
&Metadata {
name: dst.object().map(|obn| obn.to_string()),
content_encoding: Some("identity".to_owned()),
..Default::default()
},
optional,
)?;
let _insert_res: objects::InsertResponse = util::execute(ctx, insert_req).await?;
Ok(())
}
(DataPath::Gs(src), DataPath::Local(dst)) => {
let mut dst_file = fs::File::create(dst).context("destination path")?;
let dl_req = ctx.obj.download(
&(
src.bucket(),
src.object()
.context("must provide a full object name to copy from")?,
),
None,
)?;
let mut response: objects::DownloadObjectResponse = util::execute(ctx, dl_req).await?;
std::io::copy(&mut response, &mut dst_file)?;
Ok(())
}
_ => unreachable!(),
}
}