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
use std::fmt::Display;
use crate::Entry;

/// Timestamp
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct BlobRecordTimestamp(u64);

/// Result of read operations
#[derive(Debug, PartialEq, Eq)]
pub enum ReadResult<T> {
    /// Data was found
    Found(T),
    /// Data was deleted, contains creation timestamp
    Deleted(BlobRecordTimestamp),
    /// Data was not found
    NotFound,
}

impl BlobRecordTimestamp {
    /// Creates timestamp from user supplied number
    pub fn new(t: u64) -> Self {
        BlobRecordTimestamp(t)
    }

    /// Current UNIX timestamp
    pub fn now() -> Self {
        BlobRecordTimestamp(
            match std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH) {
                Ok(n) => n.as_secs(),
                Err(_) => 0,
            })
    }
}

impl Display for BlobRecordTimestamp {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl Into<u64> for BlobRecordTimestamp {
    fn into(self) -> u64 {
        self.0
    }
}

impl<T> ReadResult<T> {
    /// Is this found result
    pub fn is_found(&self) -> bool {
        matches!(self, ReadResult::Found(_))
    }

    /// Is this deleted result
    pub fn is_deleted(&self) -> bool {
        matches!(self, ReadResult::Deleted(_))
    }

    /// Is this not found result
    pub fn is_not_found(&self) -> bool {
        matches!(self, ReadResult::NotFound)
    }

    /// Convert to option if data exists
    pub fn into_option(self) -> Option<T> {
        if let ReadResult::Found(d) = self {
            Some(d)
        } else {
            None
        }
    }

    /// Map data if it exists
    pub fn map<Y>(self, f: impl FnOnce(T) -> Y) -> ReadResult<Y> {
        match self {
            ReadResult::Found(d) => ReadResult::Found(f(d)),
            ReadResult::Deleted(ts) => ReadResult::Deleted(ts),
            ReadResult::NotFound => ReadResult::NotFound,
        }
    }

    /// Cast result to type only if does not contain value
    /// Warning: panics if result contains value
    pub fn cast<Y>(self) -> ReadResult<Y> {
        match self {
            ReadResult::Found(_) => panic!("Attempt to cast non-empty read result"),
            ReadResult::Deleted(ts) => ReadResult::Deleted(ts),
            ReadResult::NotFound => ReadResult::NotFound,
        }
    }

    /// Map data if it exists
    pub async fn map_async<Y, F>(self, f: impl FnOnce(T) -> F) -> ReadResult<Y>
    where
        F: futures::Future<Output = Y>,
    {
        match self {
            ReadResult::Found(d) => ReadResult::Found(f(d).await),
            ReadResult::Deleted(ts) => ReadResult::Deleted(ts),
            ReadResult::NotFound => ReadResult::NotFound,
        }
    }

    /// Unwrap into data, panics if no data is set
    pub fn unwrap(self) -> T {
        match self {
            ReadResult::Found(d) => d,
            _ => panic!("Cannot unwrap empty result"),
        }
    }
}

impl ReadResult<BlobRecordTimestamp> {
    fn timestamp(&self) -> Option<BlobRecordTimestamp> {
        match &self {
            ReadResult::Found(ts) => Some(*ts),
            ReadResult::Deleted(ts) => Some(*ts),
            ReadResult::NotFound => None
        }
    }

    /// Returns [`ReadResult<BlobRecordTimestamp>`] with max timetamp.
    /// If timestamps are equal, then `self` is preserved
    pub fn latest(self, other: ReadResult<BlobRecordTimestamp>) -> ReadResult<BlobRecordTimestamp> {
        if other.timestamp() > self.timestamp() {
            other
        } else {
            self
        }
    }
}


impl ReadResult<Entry> {
    fn timestamp(&self) -> Option<BlobRecordTimestamp> {
        match &self {
            ReadResult::Found(entry) => Some(entry.timestamp()),
            ReadResult::Deleted(ts) => Some(*ts),
            ReadResult::NotFound => None
        }
    }

    /// Returns [`ReadResult<Entry>`] with max timetamp.
    /// If timestamps are equal, then `self` is preserved
    pub fn latest(self, other: ReadResult<Entry>) -> ReadResult<Entry> {
        if other.timestamp() > self.timestamp() {
            other
        } else {
            self
        }
    }
}