avatar.rs

 1// Copyright (c) 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
 2//
 3// This Source Code Form is subject to the terms of the Mozilla Public
 4// License, v. 2.0. If a copy of the MPL was not distributed with this
 5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
 6
 7use super::Agent;
 8use crate::Event;
 9use std::convert::TryFrom;
10use std::fs::{self, File};
11use std::io::{self, Write};
12use xmpp_parsers::{
13    avatar::{Data, Metadata},
14    iq::Iq,
15    ns,
16    pubsub::{
17        event::Item,
18        pubsub::{Items, PubSub},
19        NodeName,
20    },
21    Jid,
22};
23
24pub(crate) async fn handle_metadata_pubsub_event(
25    from: &Jid,
26    agent: &mut Agent,
27    items: Vec<Item>,
28) -> Vec<Event> {
29    let mut events = Vec::new();
30    for item in items {
31        let payload = item.payload.clone().unwrap();
32        if payload.is("metadata", ns::AVATAR_METADATA) {
33            let metadata = Metadata::try_from(payload).unwrap();
34            for info in metadata.infos {
35                let filename = format!("data/{}/{}", from, &*info.id.to_hex());
36                let file_length = match fs::metadata(filename.clone()) {
37                    Ok(metadata) => metadata.len(),
38                    Err(_) => 0,
39                };
40                // TODO: Also check the hash.
41                if info.bytes as u64 == file_length {
42                    events.push(Event::AvatarRetrieved(from.clone(), filename));
43                } else {
44                    let iq = download_avatar(from);
45                    let _ = agent.client.send_stanza(iq.into()).await;
46                }
47            }
48        }
49    }
50    events
51}
52
53fn download_avatar(from: &Jid) -> Iq {
54    Iq::from_get(
55        "coucou",
56        PubSub::Items(Items {
57            max_items: None,
58            node: NodeName(String::from(ns::AVATAR_DATA)),
59            subid: None,
60            items: Vec::new(),
61        }),
62    )
63    .with_to(from.clone())
64}
65
66// The return value of this function will be simply pushed to a Vec in the caller function,
67// so it makes no sense to allocate a Vec here - we're lazy instead
68pub(crate) fn handle_data_pubsub_iq<'a>(
69    from: &'a Jid,
70    items: &'a Items,
71) -> impl IntoIterator<Item = Event> + 'a {
72    let from = from.clone();
73    items
74        .items
75        .iter()
76        .filter_map(move |item| match (&item.id, &item.payload) {
77            (Some(id), Some(payload)) => {
78                let data = Data::try_from(payload.clone()).unwrap();
79                let filename = save_avatar(&from, id.0.clone(), &data.data).unwrap();
80                Some(Event::AvatarRetrieved(from.clone(), filename))
81            }
82            _ => None,
83        })
84}
85
86fn save_avatar(from: &Jid, id: String, data: &[u8]) -> io::Result<String> {
87    let directory = format!("data/{}", from);
88    let filename = format!("data/{}/{}", from, id);
89    fs::create_dir_all(directory)?;
90    let mut file = File::create(&filename)?;
91    file.write_all(data)?;
92    Ok(filename)
93}