amadeus_core/
file.rs

1// TODO: Use WTF-8 rather than UTF-16
2
3#![allow(clippy::type_complexity)]
4
5mod local;
6
7use async_trait::async_trait;
8use futures::{future::LocalBoxFuture, ready};
9use pin_project::pin_project;
10use std::{
11	convert::TryFrom, error::Error, ffi, fmt, future::Future, io, pin::Pin, sync::Arc, task::{Context, Poll}
12};
13use widestring::U16String;
14
15use crate::pool::ProcessSend;
16
17pub use local::LocalFile;
18
19const PAGE_SIZE: usize = 10 * 1024 * 1024; // `Reader` reads this many bytes at a time
20
21#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
22pub struct OsString {
23	buf: U16String,
24}
25impl OsString {
26	pub fn new() -> Self {
27		Self {
28			buf: U16String::new(),
29		}
30	}
31	pub fn to_string_lossy(&self) -> String {
32		self.buf.to_string_lossy()
33	}
34	pub fn display(&self) -> impl fmt::Display + '_ {
35		struct Display<'a>(&'a OsString);
36		impl<'a> fmt::Display for Display<'a> {
37			fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38				self.0.to_string_lossy().fmt(f)
39			}
40		}
41		Display(self)
42	}
43}
44impl From<Vec<u8>> for OsString {
45	fn from(from: Vec<u8>) -> Self {
46		Self {
47			buf: String::from_utf8(from)
48				.expect("Not yet imlemented: Handling non-UTF-8")
49				.into(),
50		} // TODO
51	}
52}
53impl From<String> for OsString {
54	fn from(from: String) -> Self {
55		Self { buf: from.into() }
56	}
57}
58impl From<&str> for OsString {
59	fn from(from: &str) -> Self {
60		Self {
61			buf: U16String::from_str(from),
62		}
63	}
64}
65impl From<ffi::OsString> for OsString {
66	fn from(from: ffi::OsString) -> Self {
67		Self {
68			buf: U16String::from_os_str(&from),
69		}
70	}
71}
72impl From<&ffi::OsStr> for OsString {
73	fn from(from: &ffi::OsStr) -> Self {
74		Self {
75			buf: U16String::from_os_str(from),
76		}
77	}
78}
79pub struct InvalidOsString;
80impl TryFrom<OsString> for ffi::OsString {
81	type Error = InvalidOsString;
82
83	fn try_from(from: OsString) -> Result<Self, Self::Error> {
84		Ok(from.buf.to_os_string()) // TODO: this is lossy but it should error
85	}
86}
87impl PartialEq<Vec<u8>> for OsString {
88	fn eq(&self, other: &Vec<u8>) -> bool {
89		self == &OsString::from(other.clone())
90	}
91}
92impl PartialEq<String> for OsString {
93	fn eq(&self, other: &String) -> bool {
94		self == &OsString::from(other.clone())
95	}
96}
97impl PartialEq<str> for OsString {
98	fn eq(&self, other: &str) -> bool {
99		self == &OsString::from(other)
100	}
101}
102impl PartialEq<ffi::OsString> for OsString {
103	fn eq(&self, other: &ffi::OsString) -> bool {
104		self == &OsString::from(other.clone())
105	}
106}
107impl PartialEq<ffi::OsStr> for OsString {
108	fn eq(&self, other: &ffi::OsStr) -> bool {
109		self == &OsString::from(other)
110	}
111}
112impl fmt::Debug for OsString {
113	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114		write!(f, "{}", self.display())
115	}
116}
117
118#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
119pub struct PathBuf {
120	components: Vec<OsString>,
121	file_name: Option<OsString>,
122}
123impl PathBuf {
124	pub fn new() -> Self {
125		Self {
126			components: Vec::new(),
127			file_name: None,
128		}
129	}
130	pub fn push<S>(&mut self, component: S)
131	where
132		S: Into<OsString>,
133	{
134		assert!(self.file_name.is_none());
135		self.components.push(component.into());
136	}
137	pub fn pop(&mut self) -> Option<OsString> {
138		assert!(self.file_name.is_none());
139		self.components.pop()
140	}
141	pub fn last(&self) -> Option<&OsString> {
142		assert!(self.file_name.is_none());
143		self.components.last()
144	}
145	pub fn set_file_name<S>(&mut self, file_name: Option<S>)
146	where
147		S: Into<OsString>,
148	{
149		self.file_name = file_name.map(Into::into);
150	}
151	pub fn is_file(&self) -> bool {
152		self.file_name.is_some()
153	}
154	pub fn file_name(&self) -> Option<&OsString> {
155		self.file_name.as_ref()
156	}
157	pub fn depth(&self) -> usize {
158		self.components.len()
159	}
160	pub fn iter<'a>(&'a self) -> impl Iterator<Item = &OsString> + 'a {
161		self.components.iter()
162	}
163	pub fn display(&self) -> impl fmt::Display + '_ {
164		struct Display<'a>(&'a PathBuf);
165		impl<'a> fmt::Display for Display<'a> {
166			fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167				let mut res: fmt::Result = self
168					.0
169					.iter()
170					.try_for_each(|component| write!(f, "{}/", component.to_string_lossy()));
171				if let Some(file_name) = self.0.file_name() {
172					res = res.and_then(|()| write!(f, "{}", file_name.to_string_lossy()));
173				}
174				res
175			}
176		}
177		Display(self)
178	}
179}
180impl fmt::Debug for PathBuf {
181	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182		write!(f, "{}", self.display())
183	}
184}
185
186#[async_trait(?Send)]
187pub trait Directory: File {
188	async fn partitions_filter<F>(
189		self, f: F,
190	) -> Result<Vec<<Self as File>::Partition>, <Self as File>::Error>
191	where
192		F: FnMut(&PathBuf) -> bool;
193}
194
195#[async_trait(?Send)]
196pub trait File {
197	type Partition: Partition;
198	type Error: Error + Clone + PartialEq + 'static;
199
200	async fn partitions(self) -> Result<Vec<Self::Partition>, Self::Error>;
201}
202#[async_trait(?Send)]
203pub trait Partition: Clone + fmt::Debug + ProcessSend + 'static {
204	type Page: Page;
205	type Error: Error + Clone + PartialEq + ProcessSend + 'static;
206
207	async fn pages(self) -> Result<Vec<Self::Page>, Self::Error>;
208}
209#[allow(clippy::len_without_is_empty)]
210pub trait Page {
211	type Error: Error + Clone + PartialEq + Into<io::Error> + ProcessSend + 'static;
212
213	fn len(&self) -> LocalBoxFuture<'static, Result<u64, Self::Error>>;
214	fn read(
215		&self, offset: u64, len: usize,
216	) -> LocalBoxFuture<'static, Result<Box<[u8]>, Self::Error>>;
217	fn write(
218		&self, offset: u64, buf: Box<[u8]>,
219	) -> LocalBoxFuture<'static, Result<(), Self::Error>>;
220
221	fn reader(self) -> Reader<Self>
222	where
223		Self: Sized,
224	{
225		Reader::new(self)
226	}
227}
228
229impl<T: ?Sized> Page for &T
230where
231	T: Page,
232{
233	type Error = T::Error;
234
235	fn len(&self) -> LocalBoxFuture<'static, Result<u64, Self::Error>> {
236		(**self).len()
237	}
238	fn read(
239		&self, offset: u64, len: usize,
240	) -> LocalBoxFuture<'static, Result<Box<[u8]>, Self::Error>> {
241		(**self).read(offset, len)
242	}
243	fn write(
244		&self, offset: u64, buf: Box<[u8]>,
245	) -> LocalBoxFuture<'static, Result<(), Self::Error>> {
246		(**self).write(offset, buf)
247	}
248}
249impl<T: ?Sized> Page for Arc<T>
250where
251	T: Page,
252{
253	type Error = T::Error;
254
255	fn len(&self) -> LocalBoxFuture<'static, Result<u64, Self::Error>> {
256		(**self).len()
257	}
258	fn read(
259		&self, offset: u64, len: usize,
260	) -> LocalBoxFuture<'static, Result<Box<[u8]>, Self::Error>> {
261		(**self).read(offset, len)
262	}
263	fn write(
264		&self, offset: u64, buf: Box<[u8]>,
265	) -> LocalBoxFuture<'static, Result<(), Self::Error>> {
266		(**self).write(offset, buf)
267	}
268}
269
270#[pin_project]
271pub struct Reader<P>
272where
273	P: Page,
274{
275	#[pin]
276	page: P,
277	#[pin]
278	pending: Option<LocalBoxFuture<'static, Result<Box<[u8]>, P::Error>>>,
279	offset: u64,
280}
281#[allow(clippy::len_without_is_empty)]
282impl<P> Reader<P>
283where
284	P: Page,
285{
286	fn new(page: P) -> Self {
287		Self {
288			page,
289			pending: None,
290			offset: 0,
291		}
292	}
293}
294impl<P> futures::io::AsyncRead for Reader<P>
295where
296	P: Page,
297{
298	fn poll_read(
299		self: Pin<&mut Self>, cx: &mut Context, buf: &mut [u8],
300	) -> Poll<io::Result<usize>> {
301		let mut self_ = self.project();
302		if self_.pending.is_none() {
303			let start = *self_.offset;
304			let len = buf.len();
305			let len = len.min(PAGE_SIZE);
306			let pending = self_.page.read(start, len);
307			*self_.pending = Some(pending);
308		}
309		let ret = ready!(self_.pending.as_mut().as_pin_mut().unwrap().poll(cx));
310		*self_.pending = None;
311		let ret = ret
312			.map(|buf_| {
313				buf[..buf_.len()].copy_from_slice(&buf_);
314				buf_.len()
315			})
316			.map_err(Into::into);
317		*self_.offset += u64::try_from(ret.as_ref().ok().copied().unwrap_or(0)).unwrap();
318		Poll::Ready(ret)
319	}
320}
321
322// impl<P> io::Seek for Reader<P>
323// where
324// 	P: Page,
325// {
326// 	fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
327// 		let len = self.page.len();
328// 		self.offset = match pos {
329// 			io::SeekFrom::Start(n) => Some(n),
330// 			io::SeekFrom::End(n) if n >= 0 => len.checked_add(u64::try_from(n).unwrap()),
331// 			io::SeekFrom::End(n) => {
332// 				let n = u64::try_from(-(n + 1)).unwrap() + 1;
333// 				len.checked_sub(n)
334// 			}
335// 			io::SeekFrom::Current(n) if n >= 0 => {
336// 				self.offset.checked_add(u64::try_from(n).unwrap())
337// 			}
338// 			io::SeekFrom::Current(n) => {
339// 				let n = u64::try_from(-(n + 1)).unwrap() + 1;
340// 				self.offset.checked_sub(n)
341// 			}
342// 		}
343// 		.ok_or_else(|| {
344// 			io::Error::new(
345// 				io::ErrorKind::InvalidInput,
346// 				"invalid seek to a negative or overflowing position",
347// 			)
348// 		})?;
349// 		Ok(self.offset)
350// 	}
351// }