1use crate::error::Error;
3use pyo3::prelude::*;
4use pyo3::types::PyDict;
5use std::path::{Path, PathBuf};
6
7pub struct Transport(Py<PyAny>);
9
10impl Transport {
11 pub fn new(obj: Py<PyAny>) -> Self {
13 Transport(obj)
14 }
15
16 pub(crate) fn as_pyobject(&self) -> &Py<PyAny> {
18 &self.0
19 }
20
21 pub fn base(&self) -> url::Url {
23 pyo3::Python::attach(|py| {
24 self.as_pyobject()
25 .getattr(py, "base")
26 .unwrap()
27 .extract::<String>(py)
28 .unwrap()
29 .parse()
30 .unwrap()
31 })
32 }
33
34 pub fn is_local(&self) -> bool {
36 pyo3::import_exception!(breezy.errors, NotLocalUrl);
37 pyo3::Python::attach(|py| {
38 self.0
39 .call_method1(py, "local_abspath", (".",))
40 .map(|_| true)
41 .or_else(|e| {
42 if e.is_instance_of::<NotLocalUrl>(py) {
43 Ok::<_, PyErr>(false)
44 } else {
45 panic!("Unexpected error: {:?}", e)
46 }
47 })
48 .unwrap()
49 })
50 }
51
52 pub fn local_abspath(&self, path: &Path) -> Result<PathBuf, Error> {
54 pyo3::Python::attach(|py| {
55 Ok(self
56 .0
57 .call_method1(py, "local_abspath", (path.to_string_lossy().as_ref(),))?
58 .extract::<PathBuf>(py)?)
59 })
60 }
61
62 pub fn has(&self, path: &str) -> Result<bool, Error> {
64 pyo3::Python::attach(|py| {
65 Ok(self
66 .0
67 .call_method1(py, "has", (path,))?
68 .extract::<bool>(py)
69 .unwrap())
70 })
71 }
72
73 pub fn ensure_base(&self) -> Result<(), Error> {
75 pyo3::Python::attach(|py| {
76 self.0.call_method0(py, "ensure_base")?;
77 Ok(())
78 })
79 }
80
81 pub fn create_prefix(&self) -> Result<(), Error> {
83 pyo3::Python::attach(|py| {
84 self.0.call_method0(py, "create_prefix")?;
85 Ok(())
86 })
87 }
88
89 pub fn clone(&self, path: &str) -> Result<Transport, Error> {
91 pyo3::Python::attach(|py| {
92 let o = self.0.call_method1(py, "clone", (path,))?;
93 Ok(Transport(o))
94 })
95 }
96}
97
98impl<'a, 'py> FromPyObject<'a, 'py> for Transport {
99 type Error = PyErr;
100
101 fn extract(obj: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
102 Ok(Transport(obj.to_owned().unbind()))
103 }
104}
105
106impl<'py> IntoPyObject<'py> for Transport {
107 type Target = PyAny;
108 type Output = Bound<'py, Self::Target>;
109 type Error = std::convert::Infallible;
110
111 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
112 Ok(self.0.into_bound(py))
113 }
114}
115
116pub fn get_transport(
122 url: &url::Url,
123 possible_transports: Option<&mut Vec<Transport>>,
124) -> Result<Transport, Error> {
125 pyo3::Python::attach(|py| {
126 let urlutils = py.import("breezy.transport").unwrap();
127 let kwargs = PyDict::new(py);
128 kwargs.set_item(
129 "possible_transports",
130 possible_transports.map(|t| {
131 t.iter()
132 .map(|t| t.0.clone_ref(py))
133 .collect::<Vec<Py<PyAny>>>()
134 }),
135 )?;
136 let o = urlutils.call_method("get_transport", (url.to_string(),), Some(&kwargs))?;
137 Ok(Transport(o.unbind()))
138 })
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use std::path::Path;
145
146 #[test]
147 fn test_get_transport() {
148 let td = tempfile::tempdir().unwrap();
149 let url = url::Url::from_file_path(td.path()).unwrap();
150 let transport = get_transport(&url, None).unwrap();
151
152 let base = transport.base();
154 assert!(base.to_string().starts_with("file://"));
155 }
156
157 #[test]
158 fn test_transport_is_local() {
159 let td = tempfile::tempdir().unwrap();
160 let url = url::Url::from_file_path(td.path()).unwrap();
161 let transport = get_transport(&url, None).unwrap();
162
163 assert!(transport.is_local());
164 }
165
166 #[test]
167 fn test_transport_local_abspath() {
168 let td = tempfile::tempdir().unwrap();
169 let url = url::Url::from_file_path(td.path()).unwrap();
170 let transport = get_transport(&url, None).unwrap();
171
172 let path = Path::new("test.txt");
173 let abspath = transport.local_abspath(path).unwrap();
174 assert!(abspath.is_absolute());
175 }
176
177 #[test]
178 fn test_transport_has() {
179 let td = tempfile::tempdir().unwrap();
180 let url = url::Url::from_file_path(td.path()).unwrap();
181 let transport = get_transport(&url, None).unwrap();
182
183 let exists = transport.has("nonexistent.txt").unwrap();
185 assert!(!exists);
186 }
187
188 #[test]
189 fn test_transport_ensure_base() {
190 let td = tempfile::tempdir().unwrap();
191 let url = url::Url::from_file_path(td.path()).unwrap();
192 let transport = get_transport(&url, None).unwrap();
193
194 let result = transport.ensure_base();
195 assert!(result.is_ok());
196 }
197
198 #[test]
199 fn test_transport_create_prefix() {
200 let td = tempfile::tempdir().unwrap();
201 let url = url::Url::from_file_path(td.path()).unwrap();
202 let transport = get_transport(&url, None).unwrap();
203
204 let result = transport.create_prefix();
205 assert!(result.is_ok());
206 }
207
208 #[test]
209 fn test_transport_clone() {
210 let td = tempfile::tempdir().unwrap();
211 let url = url::Url::from_file_path(td.path()).unwrap();
212 let transport = get_transport(&url, None).unwrap();
213
214 let cloned = transport.clone("subdir").unwrap();
215 let cloned_base = cloned.base();
216 assert!(cloned_base.to_string().contains("subdir"));
217 }
218
219 #[test]
220 fn test_transport_into_pyobject() {
221 let td = tempfile::tempdir().unwrap();
222 let url = url::Url::from_file_path(td.path()).unwrap();
223 let transport = get_transport(&url, None).unwrap();
224
225 Python::attach(|py| {
226 let _pyobj = transport.into_pyobject(py).unwrap();
227 });
228 }
229
230 #[test]
231 fn test_get_transport_with_possible_transports() {
232 let td = tempfile::tempdir().unwrap();
233 let url = url::Url::from_file_path(td.path()).unwrap();
234
235 let mut possible_transports = vec![];
236 let transport = get_transport(&url, Some(&mut possible_transports)).unwrap();
237
238 let base = transport.base();
239 assert!(base.to_string().starts_with("file://"));
240 }
241}