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
use std::ffi::OsString;

use crate::error::Error;
use crate::{Result, XvcDependency};
use serde::{Deserialize, Serialize};
use xvc_core::types::diff::Diffable;
use xvc_core::{
    ContentDigest, Diff, HashAlgorithm, TextOrBinary, XvcMetadata, XvcPath,
    XvcPathMetadataMap, XvcRoot,
};
use xvc_ecs::persist;


#[derive(Debug, PartialOrd, Ord, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct FileDep {
    /// The path in the workspace
    pub path: XvcPath,
    pub xvc_metadata: Option<XvcMetadata>,
    pub content_digest: Option<ContentDigest>,
}
persist!(FileDep, "file-dependency");

impl Into<XvcDependency> for FileDep {
    fn into(self) -> XvcDependency {
        XvcDependency::File(self)
    }
}

impl FileDep {
    pub fn new(path: XvcPath) -> Self {
        Self {
            path,
            xvc_metadata: None,
            content_digest: None,
        }
    }

    pub fn from_pmm(path: &XvcPath, pmm: &XvcPathMetadataMap) -> Result<Self> {
        let path = path.clone();
        let xvc_metadata = pmm.get(&path).cloned();
        if xvc_metadata.is_none() {
            return Err(Error::PathNotFound {
                path: OsString::from(path.as_str()),
            });
        }

        Ok(FileDep {
            path,
            xvc_metadata,
            content_digest: None,
        })
    }

    pub fn calculate_content_digest(
        self,
        xvc_root: &XvcRoot,
        algorithm: HashAlgorithm,
        text_or_binary: TextOrBinary,
    ) -> Result<Self> {
        let path = self.path.to_absolute_path(xvc_root);
        let content_digest = ContentDigest::new(&path, algorithm, text_or_binary)?;
        Ok(Self {
            content_digest: Some(content_digest),
            ..self
        })
    }

    pub fn calculate_content_digest_if_changed(
        self,
        xvc_root: &XvcRoot,
        record: &Self,
        algorithm: HashAlgorithm,
        text_or_binary: TextOrBinary,
    ) -> Result<Self> {
        if Self::diff_superficial(record, &self).changed() {
            self.calculate_content_digest(xvc_root, algorithm, text_or_binary)
        } else {
            Ok(Self {
                content_digest: record.content_digest.clone(),
                ..self
            })
        }
    }
}

impl Diffable for FileDep {
    type Item = Self;

    fn diff_superficial(record: &Self::Item, actual: &Self::Item) -> Diff<Self::Item> {
        assert!(record.path == actual.path);
        match (record.xvc_metadata, actual.xvc_metadata) {
            (None, None) => unreachable!("Both record and actual are None"),
            (None, Some(_)) => Diff::RecordMissing {
                actual: actual.clone(),
            },
            (Some(_), None) => Diff::ActualMissing {
                record: record.clone(),
            },
            (Some(rec_metadata_digest), Some(act_metadata_digest)) => {
                if rec_metadata_digest == act_metadata_digest {
                    Diff::Identical
                } else {
                    Diff::Different {
                        record: record.clone(),
                        actual: actual.clone(),
                    }
                }
            }
        }
    }
    /// ⚠️ Call actual.update_metadata and actual.calculate_content_digest_if_changed before calling this ⚠️
    fn diff_thorough(record: &Self::Item, actual: &Self::Item) -> Diff<Self::Item> {
        assert!(record.path == actual.path);
        match (record.content_digest, actual.content_digest) {
            (None, None) => unreachable!("Both record and actual content digests are None"),
            (None, Some(_)) => Diff::RecordMissing {
                actual: actual.clone(),
            },
            (Some(_), None) => Diff::ActualMissing {
                record: record.clone(),
            },
            (Some(rec_content_digest), Some(act_content_digest)) => {
                if rec_content_digest == act_content_digest {
                    Diff::Identical
                } else {
                    Diff::Different {
                        record: record.clone(),
                        actual: actual.clone(),
                    }
                }
            }
        }
    }
}