1
#![allow(dead_code)]
2
use crate::manifest::StoredArchive;
3
use crate::repository::backend::{
4
    self,
5
    common::{LockedFile, ManifestID, ManifestTransaction},
6
    BackendError, Result,
7
};
8
use crate::repository::{ChunkSettings, Key};
9

10
use async_trait::async_trait;
11
use chrono::prelude::*;
12
use futures::channel::oneshot;
13
use petgraph::Graph;
14
use semver::Version;
15
use serde_cbor as cbor;
16
use uuid::Uuid;
17

18
use std::collections::{HashMap, HashSet};
19
use std::fs::{create_dir, read_dir, File};
20
use std::io::{Seek, SeekFrom};
21
use std::path::{Path, PathBuf};
22
use std::thread;
23

24
#[derive(Debug)]
25
struct InternalManifest {
26
    known_entries: HashMap<ManifestID, ManifestTransaction>,
27
    verified_memo_pad: HashSet<ManifestID>,
28
    heads: Vec<ManifestID>,
29
    file: LockedFile,
30
    key: Key,
31
    chunk_settings: ChunkSettings,
32
    path: PathBuf,
33
    seen_versions: HashSet<(Version, Uuid)>,
34
}
35

36
impl InternalManifest {
37
    /// Internal function for opening the manifest
38
    ///
39
    /// The manifest this creates is not thread safe, see `Manifest` for the threadsafe
40
    /// implementation on top of this
41
    ///
42
    /// Optionally sets the chunk settings.
43
    ///
44
    /// Will return error if this is a new repository and the chunk settings are not set
45 1
    fn open(
46
        repository_path: impl AsRef<Path>,
47
        key: &Key,
48
        settings: Option<ChunkSettings>,
49
    ) -> Result<InternalManifest> {
50
        // Create a new seen versions set
51 1
        let mut seen_versions = HashSet::new();
52
        let current_version = semver::Version::new(
53 1
            crate::VERSION_STRUCT.major,
54 1
            crate::VERSION_STRUCT.minor,
55 1
            crate::VERSION_STRUCT.patch,
56
        );
57 1
        seen_versions.insert((current_version, *crate::IMPLEMENTATION_UUID));
58
        // Construct the path of the manifest folder
59 1
        let manifest_path = repository_path.as_ref().join("manifest");
60
        // Check to see if it exists
61 1
        if Path::exists(&manifest_path) {
62
            // If it is a file, return failure
63 1
            if Path::is_file(&manifest_path) {
64 0
                return Err(BackendError::ManifestError(format!(
65 0
                    "Failed to load manifest, {:?} is a file, not a directory",
66 0
                    manifest_path
67
                )));
68
            }
69
        } else {
70
            // Create the manifest directory
71 1
            create_dir(&manifest_path)?;
72
        }
73

74
        // Get the list of manifest files and sort them by ID
75 1
        let mut items = read_dir(&manifest_path)?
76 0
            .filter_map(std::result::Result::ok)
77 1
            .filter(|x| x.path().is_file())
78 1
            .filter_map(|x| {
79 1
                x.path()
80 1
                    .file_name()?
81 0
                    .to_str()
82 1
                    .and_then(|y| std::result::Result::ok(y.parse::<usize>()))
83 1
                    .map(|z| (z, x))
84
            })
85
            .collect::<Vec<_>>();
86 1
        items.sort_by(|a, b| a.0.cmp(&b.0));
87

88
        // Collect all known transactions
89 1
        let mut known_entries = HashMap::new();
90 1
        for (_, file) in &items {
91
            // Open the file
92 1
            let mut file = File::open(file.path())?;
93
            // Keep deserializing transactions until we encounter an error
94 1
            let de = cbor::Deserializer::from_reader(&mut file);
95 1
            let mut de = de.into_iter::<ManifestTransaction>();
96 1
            while let Some(tx) = de.next().and_then(std::result::Result::ok) {
97 1
                seen_versions.insert((tx.version(), tx.uuid()));
98 1
                known_entries.insert(tx.tag(), tx);
99
            }
100
        }
101

102 1
        let mut file = None;
103
        // Attempt to find an unlocked file
104 1
        for (_, f) in &items {
105 1
            let locked_file = LockedFile::open_read_write(f.path())?;
106 1
            if let Some(f) = locked_file {
107 1
                file = Some(f);
108 1
                break;
109
            }
110
        }
111

112
        // If we were unable to find an unlocked file, go ahead and make one
113 1
        let file = if let Some(file) = file {
114 1
            file
115
        } else {
116 1
            let id = if items.is_empty() {
117 1
                0
118
            } else {
119 1
                items[items.len() - 1].0 + 1
120
            };
121 1
            let path = manifest_path.join(id.to_string());
122 1
            LockedFile::open_read_write(path)?
123
                .expect("Somehow, our newly created manifest file is locked")
124
        };
125

126 1
        let chunk_settings = if let Some(chunk_settings) = settings {
127
            // Attempt to open the chunk settings file and update it
128 1
            let mut sfile = LockedFile::open_read_write(manifest_path.join("chunk.settings"))?
129 0
                .ok_or_else(|| {
130 0
                    BackendError::ManifestError("Unable to lock chunk.settings".to_string())
131
                })?;
132
            // Clear the file
133 1
            sfile.set_len(0)?;
134
            // Write our new chunksettings
135 1
            cbor::ser::to_writer(&mut sfile, &chunk_settings)?;
136 1
            chunk_settings
137
        } else {
138 0
            let mut sfile = File::open(manifest_path.join("chunk.settings"))?;
139 0
            cbor::de::from_reader(&mut sfile)?
140
        };
141

142
        // Construct the Internal Manifest
143
        let mut manifest = InternalManifest {
144
            known_entries,
145 1
            verified_memo_pad: HashSet::new(),
146 1
            heads: Vec::new(),
147
            file,
148 1
            key: key.clone(),
149
            chunk_settings,
150
            path: manifest_path,
151
            seen_versions,
152
        };
153
        // Build the list of heads
154 1
        manifest.build_heads();
155
        // Verify each head
156 1
        for head in manifest.heads.clone() {
157 1
            if !manifest.verify_tx(head) {
158 0
                return Err(BackendError::ManifestError(format!(
159 0
                    "Manifest Transaction failed verification! {:?}",
160 0
                    manifest.known_entries.get(&head).ok_or_else(|| BackendError::Unknown("Failed to get the head of the known entries list while reporting an error".to_string()))?
161
                )));
162
            }
163
        }
164

165
        // Return the manifest
166 1
        Ok(manifest)
167
    }
168

169
    /// Gets the heads from a list of transactions
170 1
    fn build_heads(&mut self) {
171
        // Create the graph
172 1
        let mut graph: Graph<ManifestID, ()> = Graph::new();
173 1
        let mut index_map = HashMap::new();
174
        // Add each transaction to our map
175 1
        for (id, tx) in &self.known_entries {
176 1
            let tag = tx.tag();
177 1
            let id = graph.add_node(tag);
178 1
            index_map.insert(tag, id);
179
        }
180
        // Go through each transaction in the graph, adding an edge in the new -> old direction
181
        // These unwraps are safe because we just added these entries to our hashmap
182 1
        for tx in self.known_entries.values() {
183 1
            let id = index_map.get(&tx.tag()).unwrap();
184 1
            for other_tx in tx.previous_heads() {
185 1
                let other_id = index_map.get(&other_tx).unwrap();
186 1
                graph.update_edge(*id, *other_id, ());
187
            }
188
        }
189
        // reverse all the nodes, so they now point from old to new
190 1
        graph.reverse();
191
        // Find all nodes with no outgoing edges, these are our heads
192 1
        let mut heads = Vec::new();
193 1
        for (tag, id) in &index_map {
194 1
            let mut edges = graph.edges(*id);
195 1
            if edges.next() == None {
196 1
                heads.push(*tag);
197
            }
198
        }
199

200 1
        self.heads = heads;
201
    }
202

203
    /// Recursivly verifies a transaction and all its parents
204 1
    fn verify_tx(&mut self, id: ManifestID) -> bool {
205 1
        if self.verified_memo_pad.contains(&id) {
206 0
            true
207
        } else {
208 1
            let tx = self
209 0
                .known_entries
210 0
                .get(&id)
211
                .expect("Item in verified memo pad was not in known_entries")
212
                .clone();
213 1
            if tx.verify(&self.key) {
214 1
                self.verified_memo_pad.insert(id);
215 1
                for parent in tx.previous_heads() {
216 1
                    if !self.verify_tx(*parent) {
217 0
                        return false;
218
                    }
219
                }
220 1
                true
221
            } else {
222 0
                false
223
            }
224
        }
225
    }
226

227
    /// Returns the last modification timestamp of the manifest
228
    ///
229
    /// Defaults to now if there are no heads
230 1
    fn last_modification(&self) -> Result<DateTime<FixedOffset>> {
231 1
        if self.heads.is_empty() {
232 1
            Ok(Local::now().with_timezone(Local::now().offset()))
233
        } else {
234 0
            let first_head = self
235 0
                .known_entries
236 0
                .get(&self.heads[0])
237
                .expect("Item in heads was not in known entries");
238 0
            let mut max = first_head.timestamp();
239 0
            for id in &self.heads {
240 0
                let tx = self.known_entries.get(id).ok_or_else(|| {
241 0
                    BackendError::ManifestError("Unable to load timestamp".to_string())
242
                })?;
243 0
                if tx.timestamp() > max {
244 0
                    max = tx.timestamp()
245
                }
246
            }
247 0
            Ok(max)
248
        }
249
    }
250

251
    /// Returns the default chunk settings in this manifest
252 0
    fn chunk_settings(&self) -> ChunkSettings {
253 0
        self.chunk_settings
254
    }
255

256
    /// Returns an iterator over the archives in this repository
257 1
    fn archive_iterator(&self) -> std::vec::IntoIter<StoredArchive> {
258 1
        let mut items = self.known_entries.values().cloned().collect::<Vec<_>>();
259 1
        items.sort_by_key(ManifestTransaction::timestamp);
260 1
        items.reverse();
261 1
        items
262
            .into_iter()
263 0
            .map(StoredArchive::from)
264
            .collect::<Vec<_>>()
265
            .into_iter()
266
    }
267

268
    /// Sets the chunk settings
269 1
    fn write_chunk_settings(&mut self, settings: ChunkSettings) -> Result<()> {
270 1
        let mut sfile =
271 0
            LockedFile::open_read_write(self.path.join("chunk.settings"))?.ok_or_else(|| {
272 0
                BackendError::Unknown("Failed to open chunk settings file for writing.".to_string())
273
            })?;
274
        // Clear the file
275 1
        sfile.set_len(0)?;
276
        // Write our new chunksettings
277 1
        cbor::ser::to_writer(&mut sfile, &settings)?;
278 1
        self.chunk_settings = settings;
279 1
        Ok(())
280
    }
281

282
    /// Adds an archive to the manifest
283
    #[allow(clippy::needless_pass_by_value)]
284 1
    fn write_archive(&mut self, archive: StoredArchive) -> Result<()> {
285
        // Create the transaction
286
        let tx = ManifestTransaction::new(
287 1
            &self.heads,
288 1
            archive.id(),
289 1
            archive.timestamp(),
290 1
            self.chunk_settings.hmac,
291 1
            &self.key,
292
        );
293
        // Write the transaction to the file
294 1
        let file = &mut self.file;
295 1
        file.seek(SeekFrom::End(0))?;
296 1
        cbor::ser::to_writer(file, &tx)?;
297
        // Add the transaction to our entries list
298 1
        let id = tx.tag();
299 1
        self.known_entries.insert(id, tx);
300
        // Update our heads to only contain this transaction
301 1
        self.heads = vec![id];
302 1
        Ok(())
303
    }
304
}
305

306
#[derive(Debug)]
307
enum ManifestCommand {
308
    LastMod(oneshot::Sender<Result<DateTime<FixedOffset>>>),
309
    ChunkSettings(oneshot::Sender<ChunkSettings>),
310
    ArchiveIterator(oneshot::Sender<std::vec::IntoIter<StoredArchive>>),
311
    WriteChunkSettings(ChunkSettings, oneshot::Sender<Result<()>>),
312
    WriteArchive(StoredArchive, oneshot::Sender<Result<()>>),
313
    SeenVersions(oneshot::Sender<HashSet<(Version, Uuid)>>),
314
    Close(oneshot::Sender<()>),
315
}
316

317
/// A message-passing handle to a running manifest
318
///
319
/// # Warnings
320
///
321
/// 1. In order to ensure that file locks are freed and data is writeen properly,
322
///    you must ensure that you call the close method on the manifest before your
323
///    program terminates
324
#[derive(Clone)]
325
pub struct Manifest {
326
    input: flume::Sender<ManifestCommand>,
327
    path: String,
328
}
329

330
impl Manifest {
331
    /// Opens and reads the manifest, creating it if it does not exist
332
    ///
333
    /// Note that the repository path is the root path of the repository, not the path of the index
334
    /// folder.
335
    ///
336
    /// This method will create the manifest folder if it does not exist.
337
    ///
338
    /// Files whose names are not strictly base 10 integers are ignored, and will not be added to
339
    /// the state or written to.
340
    ///
341
    /// This method only creates the event loop, the actual manifest is created by
342
    /// `InternalManifest::open`
343
    ///
344
    /// This method can optinally set the chunksettings for the manifest, but it is an error to not
345
    /// provide chunk settings if the manifest has not been created yet
346
    ///
347
    /// # Errors
348
    ///
349
    /// Will return Err if
350
    ///
351
    /// 1. The manifest folder does not exist and creating it failed
352
    /// 2. There are no unlocked manifest folders and creating one fails
353
    /// 3. There is a file called "manifest" in the repository folder
354
    /// 4. Some other IO error (shuch as lack of permissions) occurs
355
    /// 5. The path contains non-utf8 characters
356
    ///
357
    /// # TODOs:
358
    /// 1. Return an error if deserializing a transaciton fails before the end of the file is reached
359
    /// 2. This function can currently panic if we have to create a new manifest file, but someone
360
    ///    else creates the same file we are trying to first.
361 1
    pub fn open(
362
        repository_path: impl AsRef<Path>,
363
        chunk_settings: Option<ChunkSettings>,
364
        key: &Key,
365
        queue_depth: usize,
366
    ) -> Result<Manifest> {
367 1
        let mut manifest = InternalManifest::open(repository_path.as_ref(), key, chunk_settings)?;
368 1
        let (input, output) = flume::bounded(queue_depth);
369 1
        thread::spawn(move || {
370 1
            let mut final_ret = None;
371 1
            while let Ok(command) = output.recv() {
372 1
                match command {
373 1
                    ManifestCommand::LastMod(ret) => {
374 1
                        ret.send(manifest.last_modification()).unwrap();
375
                    }
376 0
                    ManifestCommand::ChunkSettings(ret) => {
377 0
                        ret.send(manifest.chunk_settings()).unwrap();
378
                    }
379 1
                    ManifestCommand::ArchiveIterator(ret) => {
380 1
                        ret.send(manifest.archive_iterator()).unwrap();
381
                    }
382 1
                    ManifestCommand::WriteChunkSettings(settings, ret) => {
383 1
                        ret.send(manifest.write_chunk_settings(settings)).unwrap();
384
                    }
385 1
                    ManifestCommand::WriteArchive(archive, ret) => {
386 1
                        ret.send(manifest.write_archive(archive)).unwrap();
387
                    }
388 1
                    ManifestCommand::Close(ret) => {
389 1
                        final_ret = Some(ret);
390 1
                        break;
391
                    }
392 0
                    ManifestCommand::SeenVersions(ret) => {
393 0
                        ret.send(manifest.seen_versions.clone()).unwrap();
394
                    }
395
                }
396
            }
397
            // Make sure that our internals are dropped before sending the signal to a possible
398
            // close call
399 1
            std::mem::drop(manifest);
400 1
            std::mem::drop(output);
401 1
            if let Some(ret) = final_ret {
402 1
                ret.send(()).unwrap();
403
            };
404
        });
405

406 1
        Ok(Manifest {
407 1
            input,
408 1
            path: repository_path
409 0
                .as_ref()
410 0
                .join("manifest")
411 0
                .to_str()
412 0
                .unwrap()
413 0
                .to_string(),
414
        })
415
    }
416

417 1
    pub async fn close(&mut self) {
418 1
        let (i, o) = oneshot::channel();
419 1
        self.input
420 1
            .send_async(ManifestCommand::Close(i))
421 0
            .await
422
            .unwrap();
423 1
        o.await.unwrap();
424
    }
425
}
426

427
impl std::fmt::Debug for Manifest {
428 1
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
429 1
        write!(f, "Manifest: {:?}", self.path)
430
    }
431
}
432

433
#[async_trait]
434
impl backend::Manifest for Manifest {
435
    type Iterator = std::vec::IntoIter<StoredArchive>;
436 1
    async fn last_modification(&mut self) -> Result<DateTime<FixedOffset>> {
437 1
        let (i, o) = oneshot::channel();
438 1
        self.input
439 1
            .send_async(ManifestCommand::LastMod(i))
440 0
            .await
441
            .unwrap();
442 1
        o.await?
443
    }
444 0
    async fn chunk_settings(&mut self) -> ChunkSettings {
445 0
        let (i, o) = oneshot::channel();
446 0
        self.input
447 0
            .send_async(ManifestCommand::ChunkSettings(i))
448 0
            .await
449
            .unwrap();
450 0
        o.await.unwrap()
451
    }
452 1
    async fn archive_iterator(&mut self) -> Self::Iterator {
453 1
        let (i, o) = oneshot::channel();
454 1
        self.input
455 1
            .send_async(ManifestCommand::ArchiveIterator(i))
456 0
            .await
457
            .unwrap();
458 1
        o.await.unwrap()
459
    }
460 1
    async fn write_chunk_settings(&mut self, settings: ChunkSettings) -> Result<()> {
461 1
        let (i, o) = oneshot::channel();
462 1
        self.input
463 1
            .send_async(ManifestCommand::WriteChunkSettings(settings, i))
464 0
            .await
465
            .unwrap();
466 1
        o.await?
467
    }
468 1
    async fn write_archive(&mut self, archive: StoredArchive) -> Result<()> {
469 1
        let (i, o) = oneshot::channel();
470 1
        self.input
471 1
            .send_async(ManifestCommand::WriteArchive(archive, i))
472 0
            .await
473
            .unwrap();
474 1
        o.await??;
475 1
        Ok(())
476
    }
477
    // This does nothing with this implementation
478 0
    async fn touch(&mut self) -> Result<()> {
479 0
        Ok(())
480
    }
481 0
    async fn seen_versions(&mut self) -> HashSet<(Version, Uuid)> {
482 0
        let (i, o) = oneshot::channel();
483 0
        self.input
484 0
            .send_async(ManifestCommand::SeenVersions(i))
485 0
            .await
486
            .unwrap();
487 0
        o.await.unwrap()
488
    }
489
}
490

491
#[cfg(test)]
492
mod tests {
493
    use super::*;
494
    use crate::manifest::StoredArchive;
495
    use crate::repository::{ChunkSettings, Key};
496
    use backend::Manifest as OtherManifest;
497
    use std::path::PathBuf;
498
    use std::time;
499
    use tempfile::{tempdir, TempDir};
500
    use walkdir::WalkDir;
501

502
    // Utility function, gets a tempdir, its path, an executor, and a spawner
503
    fn setup() -> (TempDir, PathBuf) {
504
        let tempdir = tempdir().unwrap();
505
        let path = tempdir.path().to_path_buf();
506
        (tempdir, path)
507
    }
508

509
    // Test to make sure creating an manifest in an empty folder
510
    // 1. Doesn't Panic or error
511
    // 2. Creates the manifest directory
512
    // 3. Creates the initial manifest file (manifest/0)
513
    // 4. Locks the initial manifest file (manifest/0.lock)
514
    // 5. last_modification works on a new manifest
515
    #[test]
516
    fn creation_works() {
517
        smol::block_on(async {
518
            let (tempdir, path) = setup();
519
            let settings = ChunkSettings::lightweight();
520
            let key = Key::random(32);
521
            // Create the manifest
522
            let mut manifest =
523
                Manifest::open(&path, Some(settings), &key, 4).expect("Manifest creation failed");
524
            // Walk the directory and print some debugging info
525
            for entry in WalkDir::new(&path) {
526
                let entry = entry.unwrap();
527
                println!("{}", entry.path().display());
528
            }
529
            // Check for the manifest directory
530
            let manifest_dir = path.join("manifest");
531
            assert!(manifest_dir.exists());
532
            assert!(manifest_dir.is_dir());
533
            // Check for the initial manifest file
534
            let manifest_file = manifest_dir.join("0");
535
            assert!(manifest_file.exists());
536
            assert!(manifest_file.is_file());
537
            // Check for the initial manifest lock file
538
            let manifest_lock = manifest_dir.join("0.lock");
539
            assert!(manifest_lock.exists());
540
            assert!(manifest_lock.is_file());
541
            // Make sure last_modification works
542
            let _last_mod = manifest
543
                .last_modification()
544
                .await
545
                .expect("Last modification failed");
546
            manifest.close().await;
547
        });
548
    }
549

550
    // Test to make sure creating a second manifest while the first is open
551
    // 1. Doesn't panic or error
552
    // 2. Creates and locks a second manifest file
553
    #[test]
554
    fn double_creation_works() {
555
        smol::block_on(async {
556
            let (tempdir, path) = setup();
557
            // Create the first manifest
558
            let settings = ChunkSettings::lightweight();
559
            let key = Key::random(32);
560
            // Create the manifest
561
            let mut manifest1 =
562
                Manifest::open(&path, Some(settings), &key, 4).expect("Manifest 1 creation failed");
563
            let mut manifest2 =
564
                Manifest::open(&path, Some(settings), &key, 4).expect("Manifest 2 creation failed");
565
            // Walk the directory and print some debugging info
566
            for entry in WalkDir::new(&path) {
567
                let entry = entry.unwrap();
568
                println!("{}", entry.path().display());
569
            }
570
            // Get manifest dir and check for manifest files
571
            let manifest_dir = path.join("manifest");
572
            let mf1 = manifest_dir.join("0");
573
            let mf2 = manifest_dir.join("1");
574
            let ml1 = manifest_dir.join("0.lock");
575
            let ml2 = manifest_dir.join("1.lock");
576
            assert!(mf1.exists() && mf1.is_file());
577
            assert!(mf2.exists() && mf2.is_file());
578
            assert!(ml1.exists() && ml1.is_file());
579
            assert!(ml2.exists() && ml2.is_file());
580
            manifest1.close().await;
581
            manifest2.close().await;
582
        });
583
    }
584

585
    // Test to make sure that dropping an Manifest unlocks the manifest file
586
    // Note: since we are using a single threaded executor, we must manually run all tasks to
587
    // completion.
588
    #[test]
589
    fn unlock_on_drop() {
590
        smol::block_on(async {
591
            let (tempdir, path) = setup();
592
            // Open an manifest and drop it
593
            let settings = ChunkSettings::lightweight();
594
            let key = Key::random(32);
595
            // Create the manifest
596
            let mut manifest =
597
                Manifest::open(&path, Some(settings), &key, 4).expect("Manifest 1 creation failed");
598
            manifest.close().await;
599
            // check for the manifest file and the absense of the lock file
600
            let manifest_dir = path.join("manifest");
601
            let manifest_file = manifest_dir.join("0");
602
            let manifest_lock = manifest_dir.join("0.lock");
603
            assert!(manifest_file.exists() && manifest_file.is_file());
604
            assert!(!manifest_lock.exists());
605
        });
606
    }
607

608
    // Test to verify that:
609
    // 1. Writing to a proplerly setup manifest does not Err or Panic
610
    // 2. Reading transactions we have inserted into a properly setup manifest does not Err or Panic
611
    // 3. Writing transactions to the manifest, dropping it, and reopening it passes verification
612
    // 4. Transactions are still present in the manifest after dropping and reloading from the same
613
    //    directory
614
    #[test]
615
    fn write_drop_read() {
616
        use async_io::Timer;
617
        smol::block_on(async {
618
            let (tempdir, path) = setup();
619
            let settings = ChunkSettings::lightweight();
620
            let key = Key::random(32);
621
            // Create the manifest
622
            let mut manifest =
623
                Manifest::open(&path, Some(settings), &key, 4).expect("Manifest creation failed");
624

625
            // Create some dummy archives
626
            let len = 10;
627
            let mut archives = Vec::new();
628
            let mut archive_set = HashSet::new();
629
            for _ in 0..len {
630
                let archive = StoredArchive::dummy_archive();
631
                archives.push(archive.clone());
632
                archive_set.insert(archive);
633
                // Pause for a bit to make sure the next one has a sufficently differnt timestamp
634
                Timer::after(time::Duration::from_millis(5)).await;
635
            }
636

637
            // write them into the manifest
638
            for archive in archives {
639
                manifest.write_archive(archive).await.unwrap();
640
            }
641

642
            manifest.close().await;
643

644
            // Reopen the manifest
645
            let mut manifest =
646
                Manifest::open(&path, Some(settings), &key, 4).expect("Manifest reopen failed");
647
            // Pull the archives out of it
648
            let archives: Vec<StoredArchive> = manifest.archive_iterator().await.collect();
649
            // Make sure we have the correct number of archives
650
            assert_eq!(archives.len(), len);
651
            // Make sure we have all the correct archives
652
            for archive in archives {
653
                assert!(archive_set.contains(&archive));
654
            }
655
        });
656
    }
657

658
    // Test to verify that:
659
    // 1. Attempting to open a manifest with a path that points to an existing file Errs
660
    // 2. Attempting to create a manifest without chunk settings errors
661
    #[test]
662
    fn manifest_errors() {
663
        smol::block_on(async {
664
            let settings = ChunkSettings::lightweight();
665
            let key = Key::random(32);
666
            // First open a tempdir and create a file in
667
            let tempdir = tempdir().expect("unable to create tempdir");
668
            let file_path = tempdir.path().join("test.file");
669
            let _test_file = File::create(&file_path).expect("Unable to create test file");
670

671
            // Attempt to open a manifest at that location
672
            let mf = Manifest::open(&file_path, Some(settings), &key, 4);
673
            // This should error
674
            assert!(mf.is_err());
675

676
            // Attempt to open a manifest without setting chunk settings
677
            let mf = Manifest::open(&file_path, None, &key, 4);
678
            assert!(mf.is_err());
679
        });
680
    }
681
}

Read our documentation on viewing source code .

Loading