Coverage Report

Created: 2024-02-20 21:15

/builds/xfbs/cindy/src/server/api/file.rs
Line
Count
Source (jump to first uncovered line)
1
use crate::{hash::ArcHash, server::Error, Cindy};
2
use axum::{
3
    body::StreamBody,
4
    extract::{Path, Query, State},
5
    http::{header, HeaderValue},
6
    response::IntoResponse,
7
    routing::get,
8
    Json, Router,
9
};
10
use cindy_common::api::*;
11
use std::path::PathBuf;
12
use tokio::{fs::File, task::spawn_blocking};
13
use tokio_util::io::ReaderStream;
14
15
1
async fn stream_file(
16
1
    State(cindy): State<Cindy>,
17
1
    Path(hash): Path<ArcHash>,
18
1
) -> Result<impl IntoResponse, Error> {
19
    // get filenames
20
1
    let database = cindy.database().
await0
;
21
1
    let hash_clone = hash.clone();
22
1
    let tags =
23
1
        spawn_blocking(move || database.hash_tags(&hash_clone, Some("filename"), None)).await
?0
?0
;
24
25
    // pick content type based on whatever filename the file is tagged with,
26
    // defaulting to application/octet-stream.
27
1
    let content_type = tags
28
1
        .into_iter()
29
1
        .map(|name| PathBuf::from(name.value()))
30
1
        .find(|path| path.extension().is_some())
31
1
        .and_then(|path| {
32
1
            mime_guess::from_path(path)
33
1
                .first_raw()
34
1
                .map(HeaderValue::from_static)
35
1
        })
36
1
        .unwrap_or_else(|| 
HeaderValue::from_str(mime::APPLICATION_OCTET_STREAM.as_ref()).unwrap()0
);
37
1
38
1
    // get path to file on disk
39
1
    let path = cindy.hash_path(&hash);
40
41
    // open file and turn into stream
42
1
    let file = File::open(&path).await
?0
;
43
1
    let stream = ReaderStream::new(file);
44
1
    let body = StreamBody::new(stream);
45
1
46
1
    // return headers and stream
47
1
    let headers = [(header::CONTENT_TYPE, content_type)];
48
1
    Ok((headers, body))
49
1
}
50
51
3
async fn file_tags(
52
3
    State(cindy): State<Cindy>,
53
3
    Path(hash): Path<ArcHash>,
54
3
    Query(query): Query<TagQuery<String>>,
55
3
) -> Result<impl IntoResponse, Error> {
56
    // get filenames
57
3
    let database = cindy.database().
await0
;
58
3
    let tags = spawn_blocking(move || {
59
3
        database.hash_tags(&hash, query.name.as_deref(), query.value.as_deref())
60
3
    })
61
3
    .await
?0
?0
;
62
63
3
    Ok(Json(tags))
64
3
}
65
66
2
async fn file_tag_create(
67
2
    State(cindy): State<Cindy>,
68
2
    Path(hash): Path<ArcHash>,
69
2
    Json(request): Json<FileTagCreateBody<'static>>,
70
2
) -> Result<(), Error> {
71
    // get filenames
72
2
    let database = cindy.database().
await0
;
73
2
    spawn_blocking(move || database.hash_tag_add(&hash, &request.name, &request.value)).await
?0
?0
;
74
75
2
    Ok(())
76
2
}
77
78
1
async fn file_tag_delete(
79
1
    State(cindy): State<Cindy>,
80
1
    Query(query): Query<TagQuery<String>>,
81
1
    Path(hash): Path<ArcHash>,
82
1
) -> Result<(), Error> {
83
    // get filenames
84
1
    let database = cindy.database().
await0
;
85
1
    let hash_clone = hash.clone();
86
1
    spawn_blocking(move || {
87
1
        database.hash_tag_remove(&hash_clone, query.name.as_deref(), query.value.as_deref())
88
1
    })
89
1
    .await
?0
?0
;
90
91
1
    Ok(())
92
1
}
93
94
0
async fn file_labels(
95
0
    State(cindy): State<Cindy>,
96
0
    Path(hash): Path<ArcHash>,
97
0
    Query(query): Query<TagQuery<String>>,
98
0
) -> Result<impl IntoResponse, Error> {
99
    // get filenames
100
0
    let database = cindy.database().await;
101
0
    let labels = spawn_blocking(move || {
102
0
        database.label_get(
103
0
            Some(&hash),
104
0
            query.name.as_deref(),
105
0
            query.value.as_deref(),
106
0
            None,
107
0
        )
108
0
    })
109
0
    .await??;
110
111
0
    Ok(Json(labels))
112
0
}
113
114
0
async fn file_label_delete() {}
115
116
0
async fn file_label_create() {}
117
118
16
pub fn router() -> Router<Cindy> {
119
16
    Router::new()
120
16
        .route("/:hash", get(stream_file))
121
16
        .route(
122
16
            "/:hash/tags",
123
16
            get(file_tags).delete(file_tag_delete).post(file_tag_create),
124
16
        )
125
16
        .route(
126
16
            "/:hash/labels",
127
16
            get(file_labels)
128
16
                .delete(file_label_delete)
129
16
                .post(file_label_create),
130
16
        )
131
16
}