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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! # `remotefs-gcs`
//!
//! An asynchronous [`remotefs::AsyncRemoteFs`] client backed by Google Cloud
//! Storage. Every path is absolute and rooted at the bucket (`/` is the
//! bucket root); trailing-slash marker objects and implicit object prefixes
//! are presented as directories.
//!
//! ## Installation
//!
//! ```toml
//! [dependencies]
//! remotefs = "1"
//! remotefs-gcs = "1"
//! futures = "0.3"
//! tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
//! ```
//!
//! | Feature | Default | Purpose |
//! | ----------------- | :-----: | ----------------------------------------------------------- |
//! | `find` | yes | Enables `remotefs::find_async` |
//! | `no-log` | no | Disables logging from this crate |
//! | `tokio` | no | Enables `BlockingGoogleCloudStorageFs` for blocking callers |
//! | `with-containers` | no | Enables the local testbench integration suite |
//! | `with-gcs-ci` | no | Enables the optional live smoke test |
//!
//! ## Application Default Credentials
//!
//! [`GoogleCloudStorageFs::new`] uses Application Default Credentials (ADC):
//!
//! ```rust,no_run
//! use std::path::Path;
//!
//! use remotefs::AsyncRemoteFs;
//! use remotefs::fs::{ReadOptions, WriteOptions};
//! use remotefs_gcs::GoogleCloudStorageFs;
//!
//! # async fn run() -> remotefs::RemoteResult<()> {
//! let mut client = GoogleCloudStorageFs::new("my-bucket");
//! client.connect().await?;
//! let mut source = futures::io::Cursor::new(b"hello".to_vec());
//! client
//! .write_file(
//! Path::new("/docs/hello.txt"),
//! &WriteOptions::default().size_hint(5),
//! &mut source,
//! )
//! .await?;
//! let mut destination = futures::io::Cursor::new(Vec::new());
//! client
//! .read_file(
//! Path::new("/docs/hello.txt"),
//! &ReadOptions::default().offset(1).length(3),
//! &mut destination,
//! )
//! .await?;
//! assert_eq!(destination.into_inner(), b"ell");
//! client.disconnect().await?;
//! # Ok(())
//! # }
//! ```
//!
//! ## Explicit credentials
//!
//! Use [`GoogleCloudStorageCredentials::custom`] to pass any credentials
//! provider supported by `google-cloud-auth`:
//!
//! ```rust,no_run
//! use remotefs::AsyncRemoteFs;
//! use remotefs_gcs::credentials::Builder;
//! use remotefs_gcs::{GoogleCloudStorageCredentials, GoogleCloudStorageFs};
//!
//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
//! let credentials = Builder::default().build()?;
//! let mut client = GoogleCloudStorageFs::with_credentials(
//! "my-bucket",
//! GoogleCloudStorageCredentials::custom(credentials),
//! );
//! client.connect().await?;
//! client.disconnect().await?;
//! # Ok(())
//! # }
//! ```
//!
//! [`GoogleCloudStorageCredentials::anonymous`] is useful for public buckets
//! and local emulators. Pair it with [`GoogleCloudStorageFs::endpoint`] when
//! the service is not running at Google's default endpoint, and
//! [`GoogleCloudStorageFs::control_endpoint`] for emulators with separate
//! object-data and metadata endpoints.
//!
//! ## Blocking usage
//!
//! Enable the `tokio` feature and call [`GoogleCloudStorageFs::into_blocking`]
//! to get a [`BlockingGoogleCloudStorageFs`], which implements
//! [`remotefs::RemoteFs`] and can be stored as `Box<dyn RemoteFs>`. It must
//! not be called from inside an async context. The supplied handle must belong
//! to a multi-thread Tokio runtime.
//!
//! ```rust,no_run
//! use remotefs::RemoteFs;
//! use remotefs_gcs::GoogleCloudStorageFs;
//!
//! # #[cfg(feature = "tokio")]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let runtime = tokio::runtime::Runtime::new()?;
//! let mut client: Box<dyn RemoteFs> = Box::new(
//! GoogleCloudStorageFs::new("my-bucket").into_blocking(runtime.handle().clone()),
//! );
//! client.connect()?;
//! client.disconnect()?;
//! # Ok(())
//! # }
//! # #[cfg(not(feature = "tokio"))]
//! # fn main() {}
//! ```
//!
//! ## Filesystem semantics
//!
//! The root directory `/` always exists and is never an object. `list_dir`
//! consumes every page and merges objects with prefixes. `remove_dir_all`
//! removes every object below its path with one flat listing.
//!
//! `open` returns an owned read stream over a ranged `ReadObject` request;
//! offsets and lengths are honored natively (`Capabilities::RANGE_READ`).
//! `create` returns an owned write stream that feeds a resumable upload; the
//! object is committed only when `finish` succeeds, and a dropped stream
//! creates nothing. `copy` uses rewrite requests until completion; `rename`
//! copies and then deletes the source and is not atomic. `append`,
//! `set_metadata`, `symlink`, and `exec` return
//! [`remotefs::RemoteErrorType::UnsupportedFeature`].
//!
//! [`GoogleCloudStorageFs::new`]: client::GoogleCloudStorageFs::new
//! [`GoogleCloudStorageFs::endpoint`]: client::GoogleCloudStorageFs::endpoint
//! [`GoogleCloudStorageFs::control_endpoint`]:
//! client::GoogleCloudStorageFs::control_endpoint
//! [`GoogleCloudStorageFs::into_blocking`]:
//! client::GoogleCloudStorageFs::into_blocking
//! [`BlockingGoogleCloudStorageFs`]: client::BlockingGoogleCloudStorageFs
//! [`GoogleCloudStorageCredentials::anonymous`]:
//! credentials::GoogleCloudStorageCredentials::anonymous
//! [`GoogleCloudStorageCredentials::custom`]:
//! credentials::GoogleCloudStorageCredentials::custom
extern crate log;
pub use BlockingGoogleCloudStorageFs;
pub use GoogleCloudStorageFs;
pub use GoogleCloudStorageCredentials;