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
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
#[cfg(not(target_arch = "wasm32"))]
use std::path::Path;
use std::path::PathBuf;

use failure::Error;
use reqwest::{
    header::{RANGE, USER_AGENT},
    Client, Url,
};

/// The source from where the Root file is read. Construct it using
/// `.into()` on a `Url` or `Path`. The latter is not availible for
/// the `wasm32` target.
#[derive(Debug, Clone)]
pub struct Source(SourceInner);

// This inner enum hides the differentiation between the local and
// remote files from the public API
#[derive(Debug, Clone)]
enum SourceInner {
    /// A local source, i.e. a file on disc.
    Local(PathBuf),
    Remote {
        client: Client,
        url: Url,
    },
}

impl Source {
    pub fn new<T: Into<Self>>(thing: T) -> Self {
        thing.into()
    }

    pub async fn fetch(&self, start: u64, len: u64) -> Result<Vec<u8>, Error> {
        match &self.0 {
            SourceInner::Local(path) => {
                let mut f = File::open(&path)?;
                f.seek(SeekFrom::Start(start))?;
                let mut buf = vec![0; len as usize];
                f.read_exact(&mut buf)?;
                Ok(buf)
            }
            SourceInner::Remote { client, url } => {
                let rsp = client
                    .get(url.clone())
                    .header(USER_AGENT, "alice-rs")
                    .header(RANGE, format!("bytes={}-{}", start, start + len - 1))
                    .send()
                    .await?
                    .error_for_status()?;
                let bytes = rsp.bytes().await?;
                Ok(bytes.as_ref().to_vec())
            }
        }
    }
}

impl From<Url> for Source {
    fn from(url: Url) -> Self {
        Self(SourceInner::Remote {
            client: Client::new(),
            url,
        })
    }
}

// Disallow the construction of a local source object on wasm since
// wasm does not have a (proper) file system.
#[cfg(not(target_arch = "wasm32"))]
impl From<&Path> for Source {
    fn from(path: &Path) -> Self {
        path.to_path_buf().into()
    }
}

#[cfg(not(target_arch = "wasm32"))]
impl From<PathBuf> for Source {
    fn from(path_buf: PathBuf) -> Self {
        Self(SourceInner::Local(path_buf))
    }
}