aboutsummaryrefslogtreecommitdiff
path: root/client/src/lib.rs
blob: b80f7051b382f409fda4f072d0ffacdbc395adf1 (plain)
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
/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2023 metamuffin <metamuffin.org>
*/
use anyhow::Result;
use jellycommon::user::UserPermission;
use log::{debug, info};
use reqwest::{
    header::{HeaderMap, HeaderValue},
    Client,
};
use serde::Serialize;
use std::collections::HashSet;
use stream::StreamSpec;
use tokio::io::AsyncWriteExt;

pub use jellycommon::*;

#[derive(Debug, Clone)]
pub struct Instance {
    pub host: String,
    pub tls: bool,
}

#[derive(Serialize)]
pub struct LoginDetails {
    pub username: String,
    pub password: String,
    pub expire: Option<i64>,
    pub drop_permissions: Option<HashSet<UserPermission>>,
}

impl Instance {
    pub fn new(host: String, tls: bool) -> Self {
        Self { host, tls }
    }
    pub fn base(&self) -> String {
        format!(
            "{}://{}",
            if self.tls { "https" } else { "http" },
            self.host
        )
    }
    pub async fn login(self, data: LoginDetails) -> anyhow::Result<Session> {
        let session_token = Client::builder()
            .build()?
            .post(format!("{}/api/create_session", self.base()))
            .json(&data)
            .send()
            .await?
            .json()
            .await?;

        let mut headers = HeaderMap::new();
        headers.insert(
            "Cookie",
            HeaderValue::from_str(&format!("session={session_token}")).unwrap(),
        );
        headers.insert("Accept", HeaderValue::from_static("application/json"));

        Ok(Session {
            instance: self,
            session_token,
            client: Client::builder().default_headers(headers).build().unwrap(),
        })
    }
}

pub struct Session {
    client: Client,
    instance: Instance,
    session_token: String,
}

impl Session {
    fn session_param(&self) -> String {
        format!("session={}", self.session_token)
    }

    pub async fn node(&self, id: &str) -> Result<NodePublic> {
        debug!("downloading node {id:?}");
        Ok(self
            .client
            .get(format!("{}/n/{id}", self.instance.base()))
            .send()
            .await?
            .json()
            .await?)
    }

    // TODO use AssetRole instead of str
    pub async fn node_asset(
        &self,
        id: &str,
        role: &str,
        width: usize,
        mut writer: impl tokio::io::AsyncWrite + std::marker::Unpin,
    ) -> Result<()> {
        debug!("downloading asset {role:?} for {id:?}");
        let mut r = self
            .client
            .get(format!(
                "{}/n/{id}/asset?role={role}&width={width}",
                self.instance.base()
            ))
            .send()
            .await?;
        while let Some(chunk) = r.chunk().await? {
            writer.write_all(&chunk).await?;
        }
        Ok(())
    }

    pub async fn reimport(&self) -> Result<()> {
        // TODO error handling
        info!("reimport");
        self.client
            .post(format!("{}/admin/import", self.instance.base()))
            .send()
            .await?;
        info!("done");
        Ok(())
    }

    pub fn stream(&self, id: &str, stream_spec: &StreamSpec) -> String {
        format!(
            "{}/n/{}/stream?{}&{}",
            self.instance.base(),
            id,
            stream_spec.to_query(),
            self.session_param()
        )
    }
}