Skip to main content

nifty_rt/
download.rs

1//! `download(…)` and `format_bytes(…)` in compiled apps. A download call names the state
2//! each of its bindings is bound to as paths from a state, which the reports are written
3//! through later.
4
5use std::slice;
6
7use nifty_download::{DownloadId, Report, Written, follow_id};
8use nifty_runtime::{DownloadCommand, DownloadField, Failure};
9
10use crate::abi::borrow_context;
11use crate::context::{AppError, Context};
12use crate::place::{Location, PlaceDesc, StateOf};
13use crate::value::{self, INT, STRING, Slot};
14
15/// One binding of a `download(…)` call: the report field, and the state it goes to.
16#[derive(Debug)]
17#[repr(C)]
18pub struct DownloadBinding {
19    pub field: DownloadField,
20    pub place: *const PlaceDesc,
21}
22
23/// A `download(…)` call as compiled code builds it: the address, folder, name and list of
24/// headers, borrowed, its other settings, and `len` bindings at `bindings`.
25#[derive(Debug)]
26#[repr(C)]
27pub struct DownloadDesc {
28    pub url: Slot,
29    pub folder: Slot,
30    pub name: Slot,
31    pub connections: i64,
32    pub headers: Slot,
33    pub retries: i64,
34    pub bindings: *const DownloadBinding,
35    pub len: usize,
36}
37
38/// Where a download's reports go, and the span of its call, which errors writing them
39/// point at.
40#[derive(Debug)]
41pub(crate) struct Targets {
42    id: DownloadId,
43    bindings: Vec<(DownloadField, Location)>,
44    span: u32,
45}
46
47pub(crate) type Request = nifty_download::Request<Targets>;
48
49impl Context {
50    /// Writes a download's report into the state its call bound.
51    pub(crate) fn report_download(
52        &self,
53        targets: &Targets,
54        report: &Report,
55    ) -> Result<(), AppError> {
56        self.write_report(targets, report)
57            .map_err(|failure| self.error(&failure, targets.span))
58    }
59
60    /// Moves a download's targets to where the item holding its id is now; `false` when
61    /// nothing holds it any more.
62    pub(crate) fn follow_download(&self, targets: &mut Targets) -> bool {
63        follow_id(
64            &StateOf(self),
65            &mut targets.bindings,
66            &targets.id.to_string(),
67        )
68    }
69
70    fn write_report(&self, targets: &Targets, report: &Report) -> Result<(), Failure> {
71        for (field, written) in report.writes() {
72            let bound = targets.bindings.iter().find(|(bound, _)| *bound == field);
73            if let Some((_, location)) = bound {
74                match written {
75                    Written::Int(number) => {
76                        self.write_location(location, value::int_slot(number), &INT)?;
77                    }
78                    Written::Text(text) => {
79                        self.write_location(location, value::string_slot(text), &STRING)?;
80                    }
81                }
82            }
83        }
84        Ok(())
85    }
86}
87
88/// Asks for the download `desc` describes once the current event finishes, and shows it
89/// has begun in the state it binds. Its reports later go to the same places.
90///
91/// # Safety
92/// See the `abi` module's contract; `desc.bindings` points to `desc.len` bindings, and
93/// each binding's place and its steps are readable.
94#[unsafe(no_mangle)]
95pub(crate) unsafe extern "C" fn nf_download(
96    context: *const Context,
97    desc: &DownloadDesc,
98    span: u32,
99) -> bool {
100    // SAFETY: by the caller's contract.
101    let context = unsafe { borrow_context(context) };
102    let bindings = if desc.len == 0 {
103        &[]
104    } else {
105        // SAFETY: by the caller's contract.
106        unsafe { slice::from_raw_parts(desc.bindings, desc.len) }
107    };
108    let started = bindings
109        .iter()
110        .map(|binding| {
111            // SAFETY: by the caller's contract.
112            let place = unsafe { Location::from_place(&*binding.place) }?;
113            Ok((binding.field, place))
114        })
115        .collect::<Result<Vec<_>, Failure>>()
116        .and_then(|bindings| {
117            let id = DownloadId::next();
118            let targets = Targets { id, bindings, span };
119            context.write_report(&targets, &Report::Started(id))?;
120            Ok(targets)
121        });
122    match started {
123        Ok(targets) => {
124            context.request_download(Request {
125                id: targets.id,
126                url: value::as_str(desc.url).to_owned(),
127                folder: value::as_str(desc.folder).to_owned(),
128                name: value::as_str(desc.name).to_owned(),
129                connections: desc.connections,
130                headers: value::as_list(desc.headers)
131                    .items
132                    .iter()
133                    .map(|header| value::as_str(*header).to_owned())
134                    .collect(),
135                retries: desc.retries,
136                targets,
137            });
138            true
139        }
140        Err(failure) => {
141            context.fail(failure, span);
142            false
143        }
144    }
145}
146
147/// Fails a `download` with a binding to a variable rather than state.
148///
149/// # Safety
150/// See the `abi` module's contract.
151#[unsafe(no_mangle)]
152pub(crate) unsafe extern "C" fn nf_fail_download_into_variable(context: *const Context, span: u32) {
153    // SAFETY: by the caller's contract.
154    unsafe { borrow_context(context) }.fail(Failure::DownloadIntoVariable, span);
155}
156
157/// Asks the download whose id is `id`, borrowed, for `command` once the current event
158/// finishes.
159///
160/// # Safety
161/// See the `abi` module's contract.
162#[unsafe(no_mangle)]
163pub(crate) unsafe extern "C" fn nf_download_command(
164    context: *const Context,
165    command: DownloadCommand,
166    id: Slot,
167) {
168    // SAFETY: by the caller's contract.
169    let context = unsafe { borrow_context(context) };
170    context.request_download_command(command, value::as_str(id).to_owned());
171}
172
173/// `format_bytes(bytes)`: the size as text, owned by the caller.
174#[unsafe(no_mangle)]
175pub(crate) extern "C" fn nf_format_bytes(bytes: i64) -> Slot {
176    value::string_slot(nifty_download::format_bytes(bytes))
177}