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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
pub mod filesystem;

pub use filesystem::FileSystemTarget;

pub use asuran_core::manifest::listing::*;

use async_trait::async_trait;

use std::collections::HashMap;
use std::io::{Read, Write};

/// Representation of a `Read`/`Write` for an object, and the range of bytes within
/// that object it is responsible for
pub struct ByteRange<T> {
    pub start: u64,
    pub end: u64,
    pub object: T,
}

/// A collection of `Read`s and the byte ranges that they are associated with, in an
/// object to be committed to a repository.
///
/// The `ranges` list may contain zero, one, or many ranges, in the case of an empty
/// file, a dense file, or a sparse file respectively.
pub struct BackupObject<T: Read> {
    /// The ranges of bytes that compose this object
    ranges: Vec<ByteRange<T>>,
    /// Total size of the object in bytes, including any holes
    total_size: u64,
}

impl<T: Read> BackupObject<T> {
    /// Create a new, empty `BackupObject` with a predefined total size
    pub fn new(total_size: u64) -> BackupObject<T> {
        let ranges = Vec::new();
        BackupObject { ranges, total_size }
    }

    /// Add a new range to the list
    ///
    /// TODO (#13): Store the ranges in sorted order
    pub fn add_range(&mut self, range: ByteRange<T>) {
        self.ranges.push(range);
    }

    #[cfg(not(tarpaulin_include))]
    /// Returns the `total_size` of the object
    pub fn total_size(&self) -> u64 {
        self.total_size
    }

    #[cfg(not(tarpaulin_include))]
    /// Sets the total size of the object
    pub fn set_total_size(&mut self, total_size: u64) {
        self.total_size = total_size;
    }

    /// Returns the ranges in the object, consuming this struct
    pub fn ranges(self) -> Vec<ByteRange<T>> {
        self.ranges
    }

    /// Adds a range without the caller needing to construct the objec themself
    pub fn direct_add_range(&mut self, start: u64, end: u64, read: T) {
        self.add_range(ByteRange {
            start,
            end,
            object: read,
        });
    }
}

/// A collection of `Write`s and their associated byte ranges with in an object to
/// be restored from a repository.
///
/// The `ranges` list may contain zero, one, or many ranges, in the case of an empty
/// file, a dense file, or a sparse file, respectively
pub struct RestoreObject<T: Write> {
    /// The list of writers and extents used to restore an object
    ranges: Vec<ByteRange<T>>,
    /// Total size of the resulting object, including any holes
    total_size: u64,
}

impl<T: Write> RestoreObject<T> {
    /// Create a new, empty `RestoreObject` with a defined size
    pub fn new(total_size: u64) -> RestoreObject<T> {
        let ranges = Vec::new();
        RestoreObject { ranges, total_size }
    }

    /// Add a new range to the list
    ///
    /// TODO  (#13): Store the ranges in sorted order
    pub fn add_range(&mut self, range: ByteRange<T>) {
        self.ranges.push(range);
    }

    #[cfg(not(tarpaulin_include))]
    /// Returns the `total_size` of the object
    pub fn total_size(&self) -> u64 {
        self.total_size
    }

    #[cfg(not(tarpaulin_include))]
    /// Sets the total size of the object
    pub fn set_total_size(&mut self, total_size: u64) {
        self.total_size = total_size;
    }

    /// Returns the ranges in the object, consuming this struct
    pub fn ranges(self) -> Vec<ByteRange<T>> {
        self.ranges
    }

    /// Adds a range without the caller needing to construct the objec themself
    pub fn direct_add_range(&mut self, start: u64, end: u64, write: T) {
        self.add_range(ByteRange {
            start,
            end,
            object: write,
        });
    }
}

/// Collection of methods that a backup driver has to implement in order for a
/// generic backup driver to be able to commit its objects to a repository
///
/// As the work of commiting objects to an archive may be split among several
/// threads, it is important that the target use a shared state among clones
/// and be tread safe
#[async_trait]
pub trait BackupTarget<T: Read>: Clone + Send + Sync {
    /// Returns a listing of all the backup-able objects in the target's domain
    ///
    /// This function does not do anything to the internal listing, and
    async fn backup_paths(&self) -> Listing;

    /// Takes a path and returns a reader for the path this object represents
    ///
    /// Returns a hash-map of namespaces and Objects to be inserted in each namespace
    ///
    /// The "raw data" for a backup target shuold be stored in the root
    /// namespace, represented here as the empty string. This is to allow
    /// almost any coherent data to be restored directly onto the filesystem
    ///
    /// Additional pieces of metatdata, such as filesystem permissions
    /// should be stored in a namespace roughly matching the path of the
    /// datastructure that represents them, e.g. filesystem:permissions:
    async fn backup_object(&self, node: Node) -> HashMap<String, BackupObject<T>>;

    /// Returns a serialized listing that should be stored in an archive at
    /// archive:listing
    async fn backup_listing(&self) -> Listing;
}

/// Collection of methods that a restore target has to implement in order for a
/// generic restore driver to be able to load and properly restore its objects
/// from a repository.
///
/// As the work of restoring an archive should be split among serveral threads,
/// it is important that targets be thread-aware and thread safe.Into
#[async_trait]
pub trait RestoreTarget<T: Write>: Clone + Send + Sync {
    /// Loads an object listing and creates a new restore target from it
    async fn load_listing(root_path: &str, listing: Listing) -> Self;

    /// Returns a copy of the internal listing object
    ///
    /// This should almost always be a clone of the object you fed into load_listing
    async fn restore_listing(&self) -> Listing;

    /// Takes an object path
    ///
    /// Returns a hashmap, keyed by namespace, of the various parts of this object
    async fn restore_object(&self, path: Node) -> HashMap<String, RestoreObject<T>>;
}