1use std::ops::Deref;
6use std::ops::DerefMut;
7use std::path::Path;
8use std::pin::Pin;
9#[cfg(feature = "async")]
10use std::task::Context;
11#[cfg(feature = "async")]
12use std::task::Poll;
13
14#[cfg(feature = "async")]
15use pin_project::pin_project;
16
17use crate::error::VfsResult;
18use crate::prelude::*;
19#[cfg(feature = "async")]
20use crate::traits::async_vfs::VfsAsync;
21#[cfg(feature = "async")]
22use crate::traits::async_vfs::WriteSupportingVfsAsync;
23#[cfg(feature = "resolve-path")]
24use crate::traits::resolve::DynamicHasField;
25#[cfg(feature = "resolve-path")]
26use crate::traits::resolve::HAS_FIELD_MAX_LEN;
27#[cfg(feature = "resolve-path")]
28use crate::traits::resolve::HasField;
29use crate::traits::vfs;
30#[cfg(feature = "resolve-path")]
31use crate::traits::vfs::OwnedPathType;
32use crate::traits::vfs::PathType;
33#[cfg(feature = "async")]
34use crate::traits::vfs::VfsCore;
35
36#[derive(Debug, Hash, PartialEq, Eq)]
62#[cfg_attr(feature = "assert_eq", derive(assert_eq::AssertEq))]
63pub struct Versioned<T, P: PathType + ?Sized = Path> {
64 value: T,
65 version: usize,
66 path: P::OwnedPath,
67}
68
69impl<T, P: PathType + ?Sized> Clone for Versioned<T, P>
70where
71 T: Clone,
72 P::OwnedPath: Clone,
73{
74 fn clone(&self) -> Self {
75 Self {
76 value: self.value.clone(),
77 version: self.version,
78 path: self.path.clone(),
79 }
80 }
81}
82
83impl<T, P: PathType + ?Sized> Versioned<T, P> {
84 const DEFAULT_VERSION: usize = 0;
85
86 pub fn new(value: T, path: impl Into<P::OwnedPath>) -> Self {
90 Self {
91 value,
92 version: Self::DEFAULT_VERSION,
93 path: path.into(),
94 }
95 }
96
97 pub fn new_dirty(value: T, path: impl Into<P::OwnedPath>) -> Self {
109 Self {
110 value,
111 version: Self::DEFAULT_VERSION + 1,
112 path: path.into(),
113 }
114 }
115
116 pub fn is_dirty(&self) -> bool {
130 !self.is_clean()
131 }
132
133 pub fn is_clean(&self) -> bool {
147 self.version == Self::DEFAULT_VERSION
148 }
149
150 pub fn edit_eq_check(&mut self, f: impl FnOnce(&mut T))
167 where
168 T: Eq + Clone,
169 {
170 let copy = self.value.clone();
171
172 f(&mut self.value);
173
174 if copy != self.value {
175 self.version += 1;
176 }
177 }
178
179 #[expect(unsafe_code, reason = "This function is unsafe by design")]
212 pub unsafe fn reset(&mut self) {
213 self.version = Self::DEFAULT_VERSION;
217 }
218}
219
220impl<'a, Vfs: vfs::Vfs<'a>, T> ReadFrom<'a, Vfs> for Versioned<T, Vfs::Path>
221where
222 T: ReadFrom<'a, Vfs>,
223{
224 fn read_from(path: &Vfs::Path, vfs: Pin<&'a Vfs>) -> VfsResult<Self, Vfs>
225 where
226 Self: Sized,
227 {
228 T::read_from(path, vfs).map(|it| Self::new(it, path.owned()))
229 }
230}
231
232#[cfg(feature = "async")]
233#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
234#[pin_project]
235#[doc(hidden)]
236pub struct VersionedReadFuture<'a, Vfs: VfsAsync, T: ReadFromAsync<'a, Vfs> + Send + 'static> {
237 #[pin]
238 inner: T::Future,
239 path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
240}
241
242#[cfg(feature = "async")]
243#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
244impl<'a, Vfs: VfsAsync + 'static, T> Future for VersionedReadFuture<'a, Vfs, T>
245where
246 T: ReadFromAsync<'a, Vfs> + Send + 'static,
247{
248 type Output = VfsResult<Versioned<T, Vfs::Path>, Vfs>;
249
250 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
251 let projection = self.project();
252 <T::Future as Future>::poll(projection.inner, cx)
253 .map_ok(|value| Versioned::new(value, projection.path.clone()))
254 }
255}
256
257#[cfg(feature = "async")]
258#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
259impl<'a, Vfs: VfsAsync + 'static, T: ReadFromAsync<'a, Vfs> + Send + 'static> ReadFromAsync<'a, Vfs>
260 for Versioned<T, Vfs::Path>
261{
262 type Future = VersionedReadFuture<'a, Vfs, T>;
263
264 fn read_from_async(
265 path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
266 vfs: Pin<&'a Vfs>,
267 ) -> Self::Future {
268 VersionedReadFuture {
269 inner: T::read_from_async(path.clone(), vfs),
270 path,
271 }
272 }
273}
274
275impl<'a, Vfs: vfs::WriteSupportingVfs<'a>, T: WriteTo<'a, Vfs>> WriteTo<'a, Vfs>
276 for Versioned<T, Vfs::Path>
277where
278 Vfs::Path: PartialEq,
279{
280 fn write_to(&self, path: &Vfs::Path, vfs: Pin<&'a Vfs>) -> VfsResult<(), Vfs> {
281 if self.path.as_ref() == path && self.is_clean() {
282 return Ok(());
283 }
284
285 self.value.write_to(path, vfs)
286 }
287}
288
289#[cfg(feature = "async")]
290#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
291#[pin_project(project_replace = VersionedWriteFutureProj)]
292#[doc(hidden)]
293pub enum VersionedWriteFuture<'a, T, Vfs: WriteSupportingVfsAsync + 'a>
294where
295 T: WriteToAsync<'a, Vfs> + Send + Sync + 'static,
296 <T as WriteToAsync<'a, Vfs>>::Future: Future<Output = VfsResult<(), Vfs>> + Unpin + 'a,
297{
298 Poisson,
299 NotTouched,
300 Writing {
301 inner: <T as WriteToAsync<'a, Vfs>>::Future,
302 },
303}
304
305#[cfg(feature = "async")]
306#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
307impl<'a, T, Vfs: WriteSupportingVfsAsync + 'a> Future for VersionedWriteFuture<'a, T, Vfs>
308where
309 T: WriteToAsync<'a, Vfs> + Send + Sync + 'static,
310 <T as WriteToAsync<'a, Vfs>>::Future: Future<Output = VfsResult<(), Vfs>> + Unpin + 'a,
311{
312 type Output = VfsResult<(), Vfs>;
313
314 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
315 let this = self.as_mut().project_replace(Self::Poisson);
316 match this {
317 VersionedWriteFutureProj::NotTouched => Poll::Ready(Ok(())),
318 VersionedWriteFutureProj::Writing { mut inner } => {
319 match Pin::new(&mut inner).poll(cx) {
320 Poll::Ready(res) => Poll::Ready(res),
321 Poll::Pending => {
322 self.project_replace(Self::Writing { inner });
323 Poll::Pending
324 }
325 }
326 }
327 VersionedWriteFutureProj::Poisson => {
328 panic!("VersionedWriteFuture is in an invalid state. This is a bug in the code.");
329 }
330 }
331 }
332}
333
334#[cfg(feature = "async")]
335#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
336impl<'a, T, Vfs: WriteSupportingVfsAsync + 'static> WriteToAsync<'a, Vfs>
337 for Versioned<T, Vfs::Path>
338where
339 T: WriteToAsync<'a, Vfs> + Send + Sync + 'static,
340 <T as WriteToAsync<'a, Vfs>>::Future: Future<Output = VfsResult<(), Vfs>> + Unpin,
341 Vfs::Path: PartialEq,
342{
343 type Future = VersionedWriteFuture<'a, T, Vfs>;
344
345 fn write_to_async(
346 self,
347 path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
348 vfs: Pin<&'a Vfs>,
349 ) -> Self::Future {
350 if self.path.as_ref() == path.as_ref() && self.is_clean() {
351 return VersionedWriteFuture::NotTouched;
352 }
353
354 VersionedWriteFuture::Writing {
355 inner: self.value.write_to_async(path, vfs),
356 }
357 }
358}
359
360#[cfg(feature = "async")]
361#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
362#[pin_project(project_replace = VersionedWriteRefFutureProj)]
363#[doc(hidden)]
364pub enum VersionedWriteRefFuture<'a, 'f, T, Vfs: WriteSupportingVfsAsync + 'a>
365where
366 T: WriteToAsyncRef<'a, Vfs> + Send + Sync + 'static,
367 <T as WriteToAsyncRef<'a, Vfs>>::Future<'f>: Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
368 'a: 'f,
369{
370 Poisson,
371 NotTouched,
372 Writing {
373 inner: <T as WriteToAsyncRef<'a, Vfs>>::Future<'f>,
374 },
375}
376
377#[cfg(feature = "async")]
378#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
379impl<'a, 'f, T, Vfs: WriteSupportingVfsAsync + 'a> Future
380 for VersionedWriteRefFuture<'a, 'f, T, Vfs>
381where
382 T: WriteToAsyncRef<'a, Vfs> + Send + Sync + 'static,
383 <T as WriteToAsyncRef<'a, Vfs>>::Future<'f>: Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
384 'a: 'f,
385{
386 type Output = VfsResult<(), Vfs>;
387
388 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
389 let this = self.as_mut().project_replace(Self::Poisson);
390 match this {
391 VersionedWriteRefFutureProj::NotTouched => Poll::Ready(Ok(())),
392 VersionedWriteRefFutureProj::Writing { mut inner } => {
393 match Pin::new(&mut inner).poll(cx) {
394 Poll::Ready(res) => Poll::Ready(res),
395 Poll::Pending => {
396 self.project_replace(Self::Writing { inner });
397 Poll::Pending
398 }
399 }
400 }
401 VersionedWriteRefFutureProj::Poisson => {
402 panic!(
403 "VersionedWriteRefFuture is in an invalid state. This is a bug in the code."
404 );
405 }
406 }
407 }
408}
409
410#[cfg(feature = "async")]
411#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
412impl<'r, T, Vfs: WriteSupportingVfsAsync + 'static> WriteToAsyncRef<'r, Vfs>
413 for Versioned<T, Vfs::Path>
414where
415 T: WriteToAsyncRef<'r, Vfs> + Send + Sync + 'static,
416 for<'f> <T as WriteToAsyncRef<'r, Vfs>>::Future<'f>:
417 Future<Output = VfsResult<(), Vfs>> + Unpin + 'f,
418 Vfs::Path: PartialEq,
419{
420 type Future<'a>
421 = VersionedWriteRefFuture<'r, 'a, T, Vfs>
422 where
423 Self: 'a,
424 'r: 'a,
425 Vfs: 'a;
426
427 fn write_to_async_ref<'a>(
428 &'a self,
429 path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath,
430 vfs: Pin<&'a Vfs>,
431 ) -> <Self as WriteToAsyncRef<'r, Vfs>>::Future<'a>
432 where
433 'r: 'a,
434 {
435 if self.path.as_ref() == path.as_ref() && self.is_clean() {
436 return VersionedWriteRefFuture::NotTouched;
437 }
438
439 VersionedWriteRefFuture::Writing {
440 inner: self.value.write_to_async_ref(path, vfs),
441 }
442 }
443}
444
445#[cfg(feature = "resolve-path")]
446#[cfg_attr(docsrs, doc(cfg(feature = "resolve-path")))]
447impl<const NAME: [char; HAS_FIELD_MAX_LEN], T, P: PathType + ?Sized> HasField<NAME>
448 for Versioned<T, P>
449where
450 T: HasField<NAME>,
451{
452 type Inner = <T as HasField<NAME>>::Inner;
453
454 fn resolve_path<Pt: OwnedPathType>(p: Pt) -> Pt {
455 T::resolve_path(p)
456 }
457}
458
459#[cfg(feature = "resolve-path")]
460#[cfg_attr(docsrs, doc(cfg(feature = "resolve-path")))]
461impl<T, P: PathType + ?Sized> DynamicHasField for Versioned<T, P>
462where
463 T: DynamicHasField,
464{
465 type Inner = <T as DynamicHasField>::Inner;
466
467 fn resolve_path<Pt: OwnedPathType>(p: Pt, name: &str) -> Pt {
468 T::resolve_path(p, name)
469 }
470}
471
472impl<T, P: PathType + ?Sized> Deref for Versioned<T, P> {
473 type Target = T;
474
475 fn deref(&self) -> &Self::Target {
476 &self.value
477 }
478}
479
480impl<T, P: PathType + ?Sized> DerefMut for Versioned<T, P> {
481 fn deref_mut(&mut self) -> &mut Self::Target {
482 self.version += 1;
485
486 &mut self.value
487 }
488}
489
490#[expect(type_alias_bounds)]
492pub type VersionedString<P: PathType + ?Sized = Path> = Versioned<String, P>;
493#[expect(type_alias_bounds)]
495pub type VersionedBytes<P: PathType + ?Sized = Path> = Versioned<Vec<u8>, P>;
496
497#[cfg(test)]
498mod tests {
499 use std::path::Path;
500 use std::pin::Pin;
501 use std::sync::atomic::AtomicUsize;
502 use std::sync::atomic::Ordering;
503
504 use super::*;
505
506 struct WriteCounter<T> {
507 count: AtomicUsize,
508 inner: T,
509 }
510
511 impl<T> WriteCounter<T> {
512 fn write_count(&self) -> usize {
513 self.count.load(Ordering::SeqCst)
514 }
515 }
516
517 impl<T> Deref for WriteCounter<T> {
518 type Target = T;
519
520 fn deref(&self) -> &Self::Target {
521 &self.inner
522 }
523 }
524
525 impl<T> DerefMut for WriteCounter<T> {
526 fn deref_mut(&mut self) -> &mut Self::Target {
527 &mut self.inner
528 }
529 }
530
531 impl<'a, Vfs: vfs::Vfs<'a>, T: ReadFrom<'a, Vfs>> ReadFrom<'a, Vfs> for WriteCounter<T> {
532 fn read_from(path: &Vfs::Path, vfs: Pin<&'a Vfs>) -> VfsResult<Self, Vfs> {
533 Ok(Self {
534 count: AtomicUsize::new(0),
535 inner: T::read_from(path, vfs)?,
536 })
537 }
538 }
539
540 impl<'a, Vfs: vfs::WriteSupportingVfs<'a>, T: WriteTo<'a, Vfs>> WriteTo<'a, Vfs>
541 for WriteCounter<T>
542 {
543 fn write_to(&self, path: &Vfs::Path, vfs: Pin<&'a Vfs>) -> VfsResult<(), Vfs> {
544 self.inner.write_to(path, vfs)?;
545 self.count.fetch_add(1, Ordering::SeqCst);
546 Ok(())
547 }
548 }
549
550 #[test]
551 fn versioned_works() {
552 let s = VersionedString::<Path>::new("value".to_owned(), "path");
553 assert!(s.is_clean());
554 assert!(!s.is_dirty());
555
556 let mut s = s;
557 *s = "new value".to_owned();
558 assert!(s.is_dirty());
559 assert!(!s.is_clean());
560
561 s.edit_eq_check(|v| *v = "new value".to_owned());
562 assert!(s.is_dirty());
563 assert!(!s.is_clean());
564 s.edit_eq_check(|v| *v = "value".to_owned());
565 assert!(s.is_dirty());
566 assert!(!s.is_clean());
567
568 #[expect(unsafe_code, reason = "This function is unsafe by design")]
569 unsafe {
570 s.reset();
571 }
572
573 assert!(s.is_clean());
574 assert!(!s.is_dirty());
575
576 s.edit_eq_check(|v| *v = "value".to_owned());
577 assert!(s.is_clean());
578 assert!(!s.is_dirty());
579
580 s.edit_eq_check(|v| *v = "new value".to_owned());
581 assert!(s.is_dirty());
582 assert!(!s.is_clean());
583 }
584
585 #[test]
586 fn type_checks() {
587 crate::test_utils::assert_is_read_from::<crate::vfs::fs_vfs::FsVfs, VersionedString<Path>>(
588 );
589 crate::test_utils::assert_is_write_to::<crate::vfs::fs_vfs::FsVfs, VersionedString<Path>>();
590 }
591}