<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use extra::url;
use extra::url::Url;
use std::hashmap::HashMap;
use std::os;

/**
Create a URL object from a string. Does various helpful browsery things like

* If there's no current url and the path looks like a file then it will
  create a file url based of the current working directory
* If there's a current url and the new path is relative then the new url
  is based off the current url

*/
// TODO: about:failure->
pub fn make_url(str_url: ~str, current_url: Option<Url>) -> Url {
    let schm = url::get_scheme(str_url);
    let str_url = match schm {
        Err(_) => {
            if current_url.is_none() {
                // Assume we've been given a file path. If it's absolute just return
                // it, otherwise make it absolute with the cwd.
                if str_url.starts_with("/") {
                    ~"file://" + str_url
                } else {
                    ~"file://" + os::getcwd().push(str_url).to_str()
                }
            } else {
                let current_url = current_url.unwrap();
                debug!("make_url: current_url: %?", current_url);
                if str_url.starts_with("//") {
                    current_url.scheme + ":" + str_url
                } else if current_url.path.is_empty() ||
                    str_url.starts_with("/") {
                    current_url.scheme + "://" +
                        current_url.host + "/" +
                        str_url.trim_left_chars(&'/')
                } else {
                    let mut path = ~[];
                    for p in current_url.path.split_iter('/') {
                        path.push(p.to_str());
                    }
                    let path = path.init();
                    let mut path = path.iter().map(|x| (*x).clone()).collect::<~[~str]>();
                    path.push(str_url);
                    let path = path.connect("/");

                    current_url.scheme + "://" + current_url.host + path
                }
            }
        },
        Ok((scheme, page)) => {
            match scheme {
                ~"about" => {
                    match page {
                        ~"failure" => ~"file://" + os::getcwd().push("../src/test/html/failure.html").to_str(),
                        // TODO: handle the rest of the about: pages
                        _ => str_url
                    }
                },
                _ => str_url
            }
        }
    };

    // FIXME: Need to handle errors
    url::from_str(str_url).unwrap()
}

mod make_url_tests {

    #[test]
    fn should_create_absolute_file_url_if_current_url_is_none_and_str_url_looks_filey() {
        let file = ~"local.html";
        let url = make_url(file, None);
        debug!("url: %?", url);
        assert!(url.scheme == ~"file");
        assert!(url.path.contains(os::getcwd().to_str()));
    }

    #[test]
    fn should_create_url_based_on_old_url_1() {
        let old_str = ~"http://example.com";
        let old_url = make_url(old_str, None);
        let new_str = ~"index.html";
        let new_url = make_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/index.html");
    }

    #[test]
    fn should_create_url_based_on_old_url_2() {
        let old_str = ~"http://example.com/";
        let old_url = make_url(old_str, None);
        let new_str = ~"index.html";
        let new_url = make_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/index.html");
    }

    #[test]
    fn should_create_url_based_on_old_url_3() {
        let old_str = ~"http://example.com/index.html";
        let old_url = make_url(old_str, None);
        let new_str = ~"crumpet.html";
        let new_url = make_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/crumpet.html");
    }

    #[test]
    fn should_create_url_based_on_old_url_4() {
        let old_str = ~"http://example.com/snarf/index.html";
        let old_url = make_url(old_str, None);
        let new_str = ~"crumpet.html";
        let new_url = make_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/snarf/crumpet.html");
    }

}

pub type UrlMap<T> = @mut HashMap<Url, T>;

pub fn url_map<T: Clone + 'static>() -> UrlMap<T> {
    @mut HashMap::new()
}
<commit_msg>make_url: Drop whitespace within data: URLs<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use extra::url;
use extra::url::Url;
use std::hashmap::HashMap;
use std::os;

/**
Create a URL object from a string. Does various helpful browsery things like

* If there's no current url and the path looks like a file then it will
  create a file url based of the current working directory
* If there's a current url and the new path is relative then the new url
  is based off the current url

*/
// TODO: about:failure->
pub fn make_url(str_url: ~str, current_url: Option<Url>) -> Url {
    let schm = url::get_scheme(str_url);
    let str_url = match schm {
        Err(_) => {
            if current_url.is_none() {
                // Assume we've been given a file path. If it's absolute just return
                // it, otherwise make it absolute with the cwd.
                if str_url.starts_with("/") {
                    ~"file://" + str_url
                } else {
                    ~"file://" + os::getcwd().push(str_url).to_str()
                }
            } else {
                let current_url = current_url.unwrap();
                debug!("make_url: current_url: %?", current_url);
                if str_url.starts_with("//") {
                    current_url.scheme + ":" + str_url
                } else if current_url.path.is_empty() ||
                    str_url.starts_with("/") {
                    current_url.scheme + "://" +
                        current_url.host + "/" +
                        str_url.trim_left_chars(&'/')
                } else {
                    let mut path = ~[];
                    for p in current_url.path.split_iter('/') {
                        path.push(p.to_str());
                    }
                    let path = path.init();
                    let mut path = path.iter().map(|x| (*x).clone()).collect::<~[~str]>();
                    path.push(str_url);
                    let path = path.connect("/");

                    current_url.scheme + "://" + current_url.host + path
                }
            }
        },
        Ok((scheme, page)) => {
            match scheme {
                ~"about" => {
                    match page {
                        ~"failure" => ~"file://" + os::getcwd().push("../src/test/html/failure.html").to_str(),
                        // TODO: handle the rest of the about: pages
                        _ => str_url
                    }
                },
                ~"data" => {
                    // Drop whitespace within data: URLs, e.g. newlines within a base64
                    // src="..." block.  Whitespace intended as content should be
                    // %-encoded or base64'd.
                    str_url.iter().filter(|&c| !c.is_whitespace()).collect()
                },
                _ => str_url
            }
        }
    };

    // FIXME: Need to handle errors
    url::from_str(str_url).unwrap()
}

mod make_url_tests {

    #[test]
    fn should_create_absolute_file_url_if_current_url_is_none_and_str_url_looks_filey() {
        let file = ~"local.html";
        let url = make_url(file, None);
        debug!("url: %?", url);
        assert!(url.scheme == ~"file");
        assert!(url.path.contains(os::getcwd().to_str()));
    }

    #[test]
    fn should_create_url_based_on_old_url_1() {
        let old_str = ~"http://example.com";
        let old_url = make_url(old_str, None);
        let new_str = ~"index.html";
        let new_url = make_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/index.html");
    }

    #[test]
    fn should_create_url_based_on_old_url_2() {
        let old_str = ~"http://example.com/";
        let old_url = make_url(old_str, None);
        let new_str = ~"index.html";
        let new_url = make_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/index.html");
    }

    #[test]
    fn should_create_url_based_on_old_url_3() {
        let old_str = ~"http://example.com/index.html";
        let old_url = make_url(old_str, None);
        let new_str = ~"crumpet.html";
        let new_url = make_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/crumpet.html");
    }

    #[test]
    fn should_create_url_based_on_old_url_4() {
        let old_str = ~"http://example.com/snarf/index.html";
        let old_url = make_url(old_str, None);
        let new_str = ~"crumpet.html";
        let new_url = make_url(new_str, Some(old_url));
        assert!(new_url.scheme == ~"http");
        assert!(new_url.host == ~"example.com");
        assert!(new_url.path == ~"/snarf/crumpet.html");
    }

}

pub type UrlMap<T> = @mut HashMap<Url, T>;

pub fn url_map<T: Clone + 'static>() -> UrlMap<T> {
    @mut HashMap::new()
}
<|endoftext|>


<commit_before>extern crate chrono;
extern crate futures;
extern crate openssl_probe;
extern crate reqwest;
extern crate select;
extern crate serenity;
extern crate tokio;
extern crate tokio_core;

// For discord
use chrono::Timelike;
use serenity::client::Client;
use serenity::http::GuildPagination;
use serenity::model::event::ResumedEvent;
use serenity::model::id::ChannelId;
use serenity::model::id::GuildId;
use serenity::model::id::UserId;
use serenity::model::prelude::Ready;
use serenity::prelude::*;

use std::collections::HashSet;

// For file reading
use std::fs::File;
use std::fs::OpenOptions;

use std::io::Read;
use std::io::Write;

// For multithreading
use std::ops::DerefMut;
use std::sync::{Arc, Mutex};
use std::thread;

// Checks that a message successfully sent; if not, then logs why to stdout.
fn check_msg<T>(result: serenity::Result<T>) {
    if let Err(why) = result {
        println!("Discord error: {:?}", why);
    }
}

fn send_message(channel_id: ChannelId, message: String, ctx: &Context) {
    check_msg(channel_id.say(&ctx.http, message));
}

fn check_food(food: String) -> String {
    let client = reqwest::Client::new();
    client
        .get("http://localhost:8000/food/")
        .query(&[("food", food)])
        .send()
        .unwrap()
        .text()
        .unwrap()
}

// Get the discord token file from memory
fn load_discord_token() -> String {
    let mut token = String::new();
    let _ = File::open("discord_token")
        .expect("No token file")
        .read_to_string(&mut token);
    token
}

fn get_guilds(ctx: Context) -> Vec<String> {
    let guilds = ctx
        .http
        .get_guilds(&GuildPagination::After(GuildId(0)), 100)
        .unwrap();
    guilds.into_iter().map(|guild| guild.name).collect()
}

struct Handler {
    listeners: Arc<Mutex<Vec<(ChannelId, String)>>>,
}

struct User {
    id: UserId,
    // uniqueName is "name#discriminator"
    // It is not constant for a user
    unique_name: String,
    // Whether this user is an admin of the bot
    is_owner: bool,
}

impl User {
    fn from_discord_message(message: &serenity::model::channel::Message) -> User {
        User {
            id: message.author.id,
            unique_name: format!("{}#{}", message.author.name, message.author.discriminator),
            is_owner: message.author.id == 90_927_967_651_262_464,
        }
    }

    fn is_self(&self, ctx: &Context) -> serenity::Result<bool> {
        Ok(ctx.http.get_current_application_info()?.id == self.id)
    }
}

impl EventHandler for Handler {
    fn ready(&self, ctx: Context, ready: Ready) {
        println!("Connected to Discord as {}", ready.user.name);
        println!("Connected to servers: {}", get_guilds(ctx).join(", "));
    }

    fn resume(&self, _: Context, _: ResumedEvent) {
        println!("Resumed");
    }

    // Discord specific
    fn message(&self, ctx: Context, message: serenity::model::channel::Message) {
        let listeners = Arc::clone(&self.listeners);
        let author = User::from_discord_message(&message);
        let content = message.content.clone();
        let channel = message.channel_id;

        println!("{}: {} says: {}", author.unique_name, author.id, content);
        if !content.starts_with('!') {
            // It's not a command, so we don't care about it
            return;
        }

        // We don't want to respond to ourselves
        // For instance, this would cause issues with !help
        if let Ok(val) = author.is_self(&ctx) {
            if val {
                return;
            }
        }

        if content.starts_with("!menu ") {
            let item: &str = &content[6..];

            let response = check_food(item.to_string());

            send_message(channel, format!("{}", response), &ctx);
        } else if content.starts_with("!echo ") {
            let input: String = content[5..].to_string();

            let client = reqwest::Client::new();
            let mut res = client
                .get("http://localhost:8000/echo/")
                .query(&[("input", input)])
                .send()
                .unwrap();

            send_message(channel, format!("{}", res.text().unwrap()), &ctx);
        } else if content.starts_with("!register ") {
            let item: String = content[10..].to_string();
            listeners
                .lock()
                .unwrap()
                .deref_mut()
                .push((channel.clone(), item.clone()));
            save_listeners(listeners.lock().unwrap().deref_mut());
            send_message(
                channel,
                format!("Will check for {}", item).to_string(),
                &ctx,
            );

            let response = check_food(item.to_string());

            send_message(channel, format!("{}", response), &ctx);
        } else if content == "!help" {
            send_message(
                channel,
                "```!menu [food name]     | tells you where that food is being served \
                 today```"
                    .to_string(),
                &ctx,
            );

            send_message(
                channel,
                "```!register [food name] | schedules it to tell you each day where that \
                 food is being served that day```"
                    .to_string(),
                &ctx,
            );
        } else if content.starts_with("!room ") {
            let room: String = content[6..].to_string();

            let client = reqwest::Client::new();
            let res = client
                .get("http://localhost:8000/room/")
                .query(&[("room", room)])
                .send()
                .unwrap();

            if res.status().is_success() {
                send_message(channel, format!("Some classes meet in that room",), &ctx);
            } else {
                send_message(channel, format!("No classes meet in that room",), &ctx);
            }
        } else if content == "!run" {
            send_message(
                channel,
                "Checking for preregistered foods".to_string(),
                &ctx,
            );
            check_for_foods(&listeners, &ctx);
        } else if (content.starts_with("!quit")) && author.is_owner {
            send_message(channel, "UMass Bot Quitting".to_string(), &ctx);
            std::process::exit(0);
        }
    }
}

fn read_listeners() -> Vec<(ChannelId, String)> {
    let mut listeners_string: String = String::new();
    let _ = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("discord_listeners.txt")
        .expect("No discord listeners file")
        .read_to_string(&mut listeners_string);

    let mut listeners: Vec<(ChannelId, String)> = vec![];

    for line in listeners_string.split('\n') {
        if line == "" {
            continue;
        }
        let sections: Vec<&str> = line.split(' ').collect();
        let app = sections[0];
        if app == "discord" {
            let id = ChannelId(
                sections[1]
                    .parse::<u64>()
                    .expect("Couldn't parse channel id"),
            );

            let food: String = sections[2..].join(" ").to_string();
            listeners.push((id, food));
        }
    }

    listeners
}

fn save_listeners(pairs: &[(ChannelId, String)]) {
    let mut listeners_string: String = String::new();
    pairs.iter().for_each(|x| {
        listeners_string = match *x {
            (ref id, ref food) => format!("{}discord {} {}\n", listeners_string, id, food),
        };
    });

    let listeners_string = listeners_string.trim();

    let _ = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .truncate(true)
        .open("discord_listeners.txt")
        .unwrap()
        .write_all(listeners_string.as_bytes());
}

// Runs at 6 AM in summer or 5 AM in winter
fn get_time_till_scheduled() -> std::time::Duration {
    let current_time_utc = chrono::prelude::Utc::now();
    let current_time: chrono::DateTime<chrono::offset::FixedOffset> = chrono::DateTime::from_utc(
        current_time_utc.naive_utc(),
        chrono::offset::FixedOffset::west(4 * 60 * 60),
        // Four hours west of the date line
        // Four instead of five because 5am/6am is a better default than 6am/7am
    );
    let next_run_date = if current_time.time().hour() < 6
        || (current_time.hour() == 6 && current_time.minute() < 5)
    {
        // We want to do it today (in UTC) if it is still yesterday in Eastern Time
        current_time
    } else {
        current_time + chrono::Duration::days(1)
    }
    .date();

    let next_run = next_run_date.and_hms(6, 5, 0);

    (next_run - current_time).to_std().unwrap()
}

fn check_for_foods(listeners: &Arc<Mutex<Vec<(ChannelId, String)>>>, ctx: &Context) {
    listeners
        .lock()
        .unwrap()
        .to_vec()
        .into_iter()
        .for_each(|(channel, food)| {
            println!("Checking on {:?} for {}", channel, food);
            let response = check_food(food);

            send_message(channel, format!("{}", response), ctx);
        });
}

fn main() {
    // Allow openssl crosscompiling to work
    openssl_probe::init_ssl_cert_env_vars();

    let listeners: Arc<Mutex<Vec<(ChannelId, String)>>> = Arc::new(Mutex::new(read_listeners()));

    // Setup discord
    let mut client = Client::new(load_discord_token().trim(), Handler { listeners })
        .expect("Error creating client");

    let owners = match client.cache_and_http.http.get_current_application_info() {
        Ok(info) => {
            let mut set = HashSet::new();
            set.insert(info.owner.id);

            set
        }
        Err(why) => panic!("Couldn't get application info: {:?}", why),
    };

    println!("Owners: {:?}", owners);

    // Listeners loop
    /*let listeners_clone = Arc::clone(&listeners);
    thread::spawn(move || {
        let listeners = listeners_clone;
        loop {
            println!("Seconds till scheduled: {:?}", get_time_till_scheduled());
            thread::sleep(get_time_till_scheduled());
            println!("Checking for foods now!");
            check_for_foods(
                &listeners, None, //TODO: this needs to be Some for Discord
            );
        }
    });*/

    if let Err(why) = client.start() {
        println!("Discord client error: {:?}", why);
    }
}
<commit_msg>Fix listeners on Discord client<commit_after>extern crate chrono;
extern crate futures;
extern crate openssl_probe;
extern crate reqwest;
extern crate select;
extern crate serenity;
extern crate tokio;
extern crate tokio_core;

// For discord
use serenity::http::raw::Http;
use chrono::Timelike;
use serenity::client::Client;
use serenity::http::GuildPagination;
use serenity::model::event::ResumedEvent;
use serenity::model::id::ChannelId;
use serenity::model::id::GuildId;
use serenity::model::id::UserId;
use serenity::model::prelude::Ready;
use serenity::prelude::*;

use std::collections::HashSet;

// For file reading
use std::fs::File;
use std::fs::OpenOptions;

use std::io::Read;
use std::io::Write;

// For multithreading
use std::ops::DerefMut;
use std::sync::{Arc, Mutex};
use std::thread;

// Checks that a message successfully sent; if not, then logs why to stdout.
fn check_msg<T>(result: serenity::Result<T>) {
    if let Err(why) = result {
        println!("Discord error: {:?}", why);
    }
}

fn send_message(channel_id: ChannelId, message: String, http: &Arc<Http>) {
    check_msg(channel_id.say(http, message));
}

fn check_food(food: String) -> String {
    let client = reqwest::Client::new();
    client
        .get("http://localhost:8000/food/")
        .query(&[("food", food)])
        .send()
        .unwrap()
        .text()
        .unwrap()
}

// Get the discord token file from memory
fn load_discord_token() -> String {
    let mut token = String::new();
    let _ = File::open("discord_token")
        .expect("No token file")
        .read_to_string(&mut token);
    token
}

fn get_guilds(http: &Arc<Http>) -> Vec<String> {
    let guilds = http
        .get_guilds(&GuildPagination::After(GuildId(0)), 100)
        .unwrap();
    guilds.into_iter().map(|guild| guild.name).collect()
}

struct Handler {
    listeners: Arc<Mutex<Vec<(ChannelId, String)>>>,
}

struct User {
    id: UserId,
    // uniqueName is "name#discriminator"
    // It is not constant for a user
    unique_name: String,
    // Whether this user is an admin of the bot
    is_owner: bool,
}

impl User {
    fn from_discord_message(message: &serenity::model::channel::Message) -> User {
        User {
            id: message.author.id,
            unique_name: format!("{}#{}", message.author.name, message.author.discriminator),
            is_owner: message.author.id == 90_927_967_651_262_464,
        }
    }

    fn is_self(&self, http: &Arc<Http>) -> serenity::Result<bool> {
        Ok(http.get_current_application_info()?.id == self.id)
    }
}

impl EventHandler for Handler {
    fn ready(&self, ctx: Context, ready: Ready) {
        println!("Connected to Discord as {}", ready.user.name);
        println!("Connected to servers: {}", get_guilds(&ctx.http).join(", "));
    }

    fn resume(&self, _: Context, _: ResumedEvent) {
        println!("Resumed");
    }

    // Discord specific
    fn message(&self, ctx: Context, message: serenity::model::channel::Message) {
        let listeners = Arc::clone(&self.listeners);
        let author = User::from_discord_message(&message);
        let content = message.content.clone();
        let channel = message.channel_id;

        println!("{}: {} says: {}", author.unique_name, author.id, content);
        if !content.starts_with('!') {
            // It's not a command, so we don't care about it
            return;
        }

        // We don't want to respond to ourselves
        // For instance, this would cause issues with !help
        if let Ok(val) = author.is_self(&ctx.http) {
            if val {
                return;
            }
        }

        if content.starts_with("!menu ") {
            let item: &str = &content[6..];

            let response = check_food(item.to_string());

            send_message(channel, format!("{}", response), &ctx.http);
        } else if content.starts_with("!echo ") {
            let input: String = content[5..].to_string();

            let client = reqwest::Client::new();
            let mut res = client
                .get("http://localhost:8000/echo/")
                .query(&[("input", input)])
                .send()
                .unwrap();

            send_message(channel, format!("{}", res.text().unwrap()), &ctx.http);
        } else if content.starts_with("!register ") {
            let item: String = content[10..].to_string();
            listeners
                .lock()
                .unwrap()
                .deref_mut()
                .push((channel.clone(), item.clone()));
            save_listeners(listeners.lock().unwrap().deref_mut());
            send_message(
                channel,
                format!("Will check for {}", item).to_string(),
                &ctx.http,
            );

            let response = check_food(item.to_string());

            send_message(channel, format!("{}", response), &ctx.http);
        } else if content == "!help" {
            send_message(
                channel,
                "```!menu [food name]     | tells you where that food is being served \
                 today```"
                    .to_string(),
                &ctx.http,
            );

            send_message(
                channel,
                "```!register [food name] | schedules it to tell you each day where that \
                 food is being served that day```"
                    .to_string(),
                &ctx.http,
            );
        } else if content.starts_with("!room ") {
            let room: String = content[6..].to_string();

            let client = reqwest::Client::new();
            let res = client
                .get("http://localhost:8000/room/")
                .query(&[("room", room)])
                .send()
                .unwrap();

            if res.status().is_success() {
                send_message(channel, format!("Some classes meet in that room",), &ctx.http);
            } else {
                send_message(channel, format!("No classes meet in that room",), &ctx.http);
            }
        } else if content == "!run" {
            send_message(
                channel,
                "Checking for preregistered foods".to_string(),
                &ctx.http,
            );
            check_for_foods(&listeners, &ctx.http);
        } else if (content.starts_with("!quit")) && author.is_owner {
            send_message(channel, "UMass Bot Quitting".to_string(), &ctx.http);
            std::process::exit(0);
        }
    }
}

fn read_listeners() -> Vec<(ChannelId, String)> {
    let mut listeners_string: String = String::new();
    let _ = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("discord_listeners.txt")
        .expect("No discord listeners file")
        .read_to_string(&mut listeners_string);

    let mut listeners: Vec<(ChannelId, String)> = vec![];

    for line in listeners_string.split('\n') {
        if line == "" {
            continue;
        }
        let sections: Vec<&str> = line.split(' ').collect();
        let app = sections[0];
        if app == "discord" {
            let id = ChannelId(
                sections[1]
                    .parse::<u64>()
                    .expect("Couldn't parse channel id"),
            );

            let food: String = sections[2..].join(" ").to_string();
            listeners.push((id, food));
        }
    }

    listeners
}

fn save_listeners(pairs: &[(ChannelId, String)]) {
    let mut listeners_string: String = String::new();
    pairs.iter().for_each(|x| {
        listeners_string = match *x {
            (ref id, ref food) => format!("{}discord {} {}\n", listeners_string, id, food),
        };
    });

    let listeners_string = listeners_string.trim();

    let _ = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .truncate(true)
        .open("discord_listeners.txt")
        .unwrap()
        .write_all(listeners_string.as_bytes());
}

// Runs at 6 AM in summer or 5 AM in winter
fn get_time_till_scheduled() -> std::time::Duration {
    let current_time_utc = chrono::prelude::Utc::now();
    let current_time: chrono::DateTime<chrono::offset::FixedOffset> = chrono::DateTime::from_utc(
        current_time_utc.naive_utc(),
        chrono::offset::FixedOffset::west(4 * 60 * 60),
        // Four hours west of the date line
        // Four instead of five because 5am/6am is a better default than 6am/7am
    );
    let next_run_date = if current_time.time().hour() < 6
        || (current_time.hour() == 6 && current_time.minute() < 5)
    {
        // We want to do it today (in UTC) if it is still yesterday in Eastern Time
        current_time
    } else {
        current_time + chrono::Duration::days(1)
    }
    .date();

    let next_run = next_run_date.and_hms(6, 5, 0);

    (next_run - current_time).to_std().unwrap()
}

fn check_for_foods(listeners: &Arc<Mutex<Vec<(ChannelId, String)>>>, http: &Arc<Http>) {
    listeners
        .lock()
        .unwrap()
        .to_vec()
        .into_iter()
        .for_each(|(channel, food)| {
            println!("Checking on {:?} for {}", channel, food);
            let response = check_food(food);

            send_message(channel, format!("{}", response), http);
        });
}

fn main() {
    // Allow openssl crosscompiling to work
    openssl_probe::init_ssl_cert_env_vars();

    let listeners: Arc<Mutex<Vec<(ChannelId, String)>>> = Arc::new(Mutex::new(read_listeners()));

    // Setup discord
    let mut client = Client::new(load_discord_token().trim(), Handler { listeners: Arc::clone(&listeners) })
        .expect("Error creating client");

    let owners = match client.cache_and_http.http.get_current_application_info() {
        Ok(info) => {
            let mut set = HashSet::new();
            set.insert(info.owner.id);

            set
        }
        Err(why) => panic!("Couldn't get application info: {:?}", why),
    };

    println!("Owners: {:?}", owners);

    // Listeners loop
    let listeners_clone = Arc::clone(&listeners);
    let http = Arc::clone(&client.cache_and_http.http);
    thread::spawn(move || {
        let listeners = listeners_clone;
        loop {
            println!("Seconds till scheduled: {:?}", get_time_till_scheduled());
            thread::sleep(get_time_till_scheduled());
            println!("Checking for foods now!");
            check_for_foods(
                &listeners, &http,
            );
        }
    });

    if let Err(why) = client.start() {
        println!("Discord client error: {:?}", why);
    }
}
<|endoftext|>


<commit_before>// "Tifflin" Kernel
// - By John Hodge (thePowersGang)
//
// Modules/fs_fat/lib.rs
//! FAT (12/16/32) Filesystemd river
#![feature(no_std,core,linkage)]
#![no_std]
#[macro_use] extern crate core;
#[macro_use] extern crate kernel;
use kernel::prelude::*;

use kernel::vfs::{self, mount, node};
use kernel::metadevs::storage::{self,VolumeHandle,SizePrinter};
use kernel::lib::mem::aref::{ArefInner,ArefBorrow};

module_define!{FS_FAT, [VFS], init}

const FAT16_MIN_CLUSTERS: usize = 4085;
const FAT32_MIN_CLUSTERS: usize = 65525;

/// FAT Legacy (pre 32) root cluster base. Just has to be above the max cluster num for FAT16
const FATL_ROOT_CLUSTER: u32 = 0x00FF0000;

const FAT12_EOC: u16 = 0x0FFF;
const FAT16_EOC: u16 = 0xFFFF;
const FAT32_EOC: u32 = 0x00FFFFFF;

/// on-disk structures
mod on_disk;
/// Directory IO
mod dir;
/// File IO
mod file;

#[derive(Copy,Clone,Debug)]
enum Size
{
	Fat12,
	Fat16,
	Fat32,
}

/// Driver strucutre
struct Driver;

struct Filesystem
{
	inner: ArefInner<FilesystemInner>
}
impl ::core::ops::Deref for Filesystem {
	type Target = FilesystemInner;
	fn deref(&self) -> &FilesystemInner { &self.inner }
}
struct FilesystemInner
{
	vh: VolumeHandle,
	ty: Size,

	spc: usize,
	cluster_size: usize,
	/// Total number of data clusters
	cluster_count: usize,
	first_fat_sector: usize,
	first_data_sector: usize,

	root_first_cluster: u32,
	root_sector_count: u32,
}

/// Inodes IDs destrucure into two 28-bit cluster IDs, and a 16-bit dir offset
#[derive(Debug)]
struct InodeRef
{
	dir_first_cluster: u32,
	dir_offset: u16,
	first_cluster: u32,
}

/// Iterable cluster list
enum ClusterList {
	Range(::core::ops::Range<u32>),
	Chained(ArefBorrow<FilesystemInner>, u32),
}

static S_DRIVER: Driver = Driver;

fn init()
{
	let h = mount::DriverRegistration::new("fat", &S_DRIVER);
	// TODO: Remember the registration for unloading
	::core::mem::forget(h);
}

impl mount::Driver for Driver
{
	fn detect(&self, vol: &VolumeHandle) -> vfs::Result<usize> {
		let bs = {
			let mut bs = [0u8; 512];
			try!( vol.read_blocks(0, &mut bs) );
			on_disk::BootSect::read(&bs)
			};

		let bps = bs.common().bps;
		let spc = bs.common().spc;
		let media_desc = bs.common().media_descriptor;

		if bps == 0 || spc == 0 || media_desc < 0xf0 {
			Ok(0)
		}
		else {
			Ok(1)
		}
	}
	fn mount(&self, vol: VolumeHandle) -> vfs::Result<Box<mount::Filesystem>> {
		let bs = {
			let mut bs = [0u8; 512];
			try!( vol.read_blocks(0, &mut bs) );
			on_disk::BootSect::read(&mut &bs[..])
			};
		let bs_c = bs.common();
		if bs_c.bps != 512 {
			return Err(vfs::Error::Unknown("TODO: non 512-byte sector FAT"))
		}
		if bs_c.fat_count == 0 {
			return Err(vfs::Error::Unknown("FAT Count is 0"));
		}

		log_debug!("Label: {:?}", ::kernel::lib::RawString(&bs.tail_common().label));

		let bps = bs_c.bps as usize;
		let spc = bs_c.spc as usize;

		let root_dir_sectors = (bs_c.files_in_root as usize*32 + bps - 1) / bps;
		let fat_size = if bs_c.fat_size_16 > 0 {
				bs_c.fat_size_16 as usize
			}
			else {
				todo!("FAT32 FAT size field");
			};
		let total_sectors = if bs_c.total_sectors_16 > 0 {
				bs_c.total_sectors_16 as usize
			}
			else {
				bs_c.total_sectors_32 as usize
			};

		// Calcualte some quantities
		let spare_fat_sectors = (bs_c.fat_count as usize - 1) * fat_size;
		let first_data_sector = bs_c.reserved_sect_count as usize
			+ fat_size + spare_fat_sectors
			+ root_dir_sectors;
		let cluster_count = (total_sectors - first_data_sector - spare_fat_sectors) / spc;

		// Determine the FAT type
		let fat_type = if cluster_count < FAT16_MIN_CLUSTERS {
				Size::Fat12
			}
			else if cluster_count < FAT32_MIN_CLUSTERS {
				Size::Fat16
			}
			else {
				Size::Fat32
			};
		log_debug!("{:?} {} sectors, Size {}", fat_type, total_sectors,
			SizePrinter((total_sectors*bs_c.bps as usize) as u64));

		Ok(Box::new(Filesystem {
			// SAFE: Saving to a Box, so won't move
			inner: unsafe { ArefInner::new(FilesystemInner {
				ty: fat_type,
				spc: spc,
				cluster_size: spc * vol.block_size(),
				cluster_count: cluster_count,
				first_fat_sector: bs_c.reserved_sect_count as usize,
				first_data_sector: first_data_sector,
				root_first_cluster: match fat_type {
					Size::Fat32 => bs.info32().unwrap().root_cluster,
					_ => FATL_ROOT_CLUSTER as u32,
					},
				root_sector_count: root_dir_sectors as u32,

				vh: vol,
				}) },
			}))
	}
}

type Cluster = Vec<u8>;

impl FilesystemInner
{
	/// Load a cluster from disk
	// TODO: V V V
	// - Should this function lock the cluster somehow to prevent accidental overlap?
	// - Could also cache somehow (with a refcount) along with the 'writing' flag
	fn read_cluster(&self, cluster: u32, dst: &mut [u8]) -> Result<(), storage::IoError> {
		log_trace!("Filesystem::read_cluster({:#x})", cluster);
		assert_eq!(dst.len(), self.cluster_size);
		// For now, just read the bytes, screw caching
		let sector = if !is!(self.ty, Size::Fat32) && cluster >= FATL_ROOT_CLUSTER {
				// Root directory (for FAT12/16, where it was not a normal file)
				let rc = cluster - FATL_ROOT_CLUSTER;
				assert!( (rc as u64 * self.spc as u64) < self.root_sector_count as u64);
				(self.first_data_sector - self.root_sector_count as usize) as u64
				+ (rc * self.spc as u32) as u64
			}
			else {
				// Anything else
				assert!(cluster >= 2);
				assert!(cluster - 2 < self.cluster_count as u32);
				self.first_data_sector as u64 + (cluster as u64 - 2) * self.spc as u64
			};
		log_debug!("cluster = {:#x}, sector = 0x{:x}", cluster, sector);
		try!(self.vh.read_blocks(sector, dst));
		//::kernel::logging::hex_dump("FAT Cluster", &buf);
		Ok( () )
	}
	fn load_cluster(&self, cluster: u32) -> Result<Cluster, storage::IoError> {
		let mut buf: Vec<u8> = ::core::iter::repeat(0).take(self.spc * self.vh.block_size()).collect();
		try!(self.read_cluster(cluster, &mut buf));
		Ok( buf )
	}

	/// Obtain the next cluster in a chain
	fn get_next_cluster(&self, cluster: u32) -> Option<u32> {
		use kernel::lib::byteorder::{ReadBytesExt,LittleEndian};
		// - Determine what sector contains the requested FAT entry
		let bs = self.vh.block_size();
		let (fat_sector,ofs) = match self.ty
			{
			Size::Fat12 => {
				let cps = bs / 3 * 2;	// 2 per 3 bytes
				(cluster as usize / cps, (cluster as usize % cps) / 2 * 3 )
				},
			Size::Fat16 => {
				let cps = bs / 2;
				(cluster as usize / cps, (cluster as usize % cps) * 2)
				},
			Size::Fat32 => {
				let cps = bs / 4;
				(cluster as usize / cps, (cluster as usize % cps) * 2)
				},
			};
		// - Read a single sector from the FAT
		let mut sector_data = Vec::from_fn(bs, |_|0u8);
		match self.vh.read_blocks( (self.first_fat_sector + fat_sector) as u64, &mut sector_data )
		{
		Ok(_) => {},
		Err(_) => return None,
		}
		// - Extract the entry
		match self.ty
		{
		Size::Fat12 => {
			// FAT12 has special handling because it packs 2 entries into 24 bytes
			let val = {
				let v24 = (&sector_data[ofs..]).read_uint::<LittleEndian>(3).unwrap();
				if cluster % 2 == 0 { v24 & 0xFFF } else { v24 >> 12 }
				} as u16;
			if val == FAT12_EOC { None } else { Some(val as u32) }
			},
		Size::Fat16 => {
			let val = (&sector_data[ofs..]).read_u16::<LittleEndian>().unwrap();
			if val == FAT16_EOC { None } else { Some(val as u32) }
			},
		Size::Fat32 => {
			let val = (&sector_data[ofs..]).read_u32::<LittleEndian>().unwrap();
			if val == FAT32_EOC { None } else { Some(val as u32) }
			},
		}
	}
}

impl mount::Filesystem for Filesystem
{
	fn root_inode(&self) -> node::InodeId {
		(InodeRef {
			first_cluster: self.root_first_cluster,
			dir_first_cluster: 0,
			dir_offset: 0,
			}).to_id()
	}
	fn get_node_by_inode(&self, id: node::InodeId) -> Option<node::Node> {
		let r = InodeRef::from(id);
		if r.first_cluster == self.root_first_cluster {
			Some(node::Node::Dir(dir::DirNode::new_boxed(self.inner.borrow(), r.first_cluster)))
		}
		else {
			// Reading from the directory starting at r.dir_first_cluster
			// locate the file with cluster equal to r.first_cluster.
			// And use that to create the node
			let dn = dir::DirNode::new(self.inner.borrow(), r.dir_first_cluster);
			dn.find_node(r.first_cluster)
		}
	}
}

impl InodeRef
{
	fn new(c: u32, dir_c: u32) -> InodeRef {
		assert!(c     <= 0x00FF_FFFF);
		assert!(dir_c <= 0x00FF_FFFF);
		InodeRef {
			first_cluster: c,
			dir_first_cluster: dir_c,
			dir_offset: 0,
		}
	}
	fn to_id(&self) -> node::InodeId {
		assert!(self.first_cluster <= 0x00FF_FFFF);
		assert!(self.dir_first_cluster <= 0x00FF_FFFF);
		//assert!(v.dir_offset <= 0xFFFF);
		(self.first_cluster as u64)
		| (self.dir_first_cluster as u64) << 24
		| (self.dir_offset as u64) << 48
	}
}

impl From<node::InodeId> for InodeRef {
	fn from(v: node::InodeId) -> InodeRef {
		InodeRef {
			first_cluster: (v & 0x00FF_FFFF) as u32,
			dir_first_cluster: ((v >> 24) & 0x00FF_FFFF) as u32,
			dir_offset: (v >> 48) as u16,
		}
	}
}

impl ClusterList {
	pub fn chained(fs: ArefBorrow<FilesystemInner>, start: u32) -> ClusterList {
		ClusterList::Chained(fs, start)
	}
}
impl ::core::iter::Iterator for ClusterList {
	type Item = u32;
	fn next(&mut self) -> Option<u32> {
		match *self
		{
		ClusterList::Range(ref mut r) => r.next(),
		ClusterList::Chained(ref fs, ref mut next) =>
			if *next == 0 {
				None
			}
			else {
				let rv = *next;
				*next = fs.get_next_cluster(*next).unwrap_or(0);
				Some( rv )
			},
		}
	}
}
<commit_msg>Modules/FAT - (minor) Plan out generalised caching<commit_after>// "Tifflin" Kernel
// - By John Hodge (thePowersGang)
//
// Modules/fs_fat/lib.rs
//! FAT (12/16/32) Filesystemd river
#![feature(no_std,core,linkage)]
#![no_std]
#[macro_use] extern crate core;
#[macro_use] extern crate kernel;
use kernel::prelude::*;

use kernel::vfs::{self, mount, node};
use kernel::metadevs::storage::{self,VolumeHandle,SizePrinter};
use kernel::lib::mem::aref::{ArefInner,ArefBorrow};

module_define!{FS_FAT, [VFS], init}

const FAT16_MIN_CLUSTERS: usize = 4085;
const FAT32_MIN_CLUSTERS: usize = 65525;

/// FAT Legacy (pre 32) root cluster base. Just has to be above the max cluster num for FAT16
const FATL_ROOT_CLUSTER: u32 = 0x00FF0000;

const FAT12_EOC: u16 = 0x0FFF;
const FAT16_EOC: u16 = 0xFFFF;
const FAT32_EOC: u32 = 0x00FFFFFF;

/// on-disk structures
mod on_disk;
/// Directory IO
mod dir;
/// File IO
mod file;

#[derive(Copy,Clone,Debug)]
enum Size
{
	Fat12,
	Fat16,
	Fat32,
}

/// Driver strucutre
struct Driver;

struct Filesystem
{
	inner: ArefInner<FilesystemInner>
}
impl ::core::ops::Deref for Filesystem {
	type Target = FilesystemInner;
	fn deref(&self) -> &FilesystemInner { &self.inner }
}
const FAT_CACHE_BLOCK_SIZE: usize = 512;
struct FilesystemInner
{
	vh: VolumeHandle,
	ty: Size,

	spc: usize,
	cluster_size: usize,
	/// Total number of data clusters
	cluster_count: usize,
	first_fat_sector: usize,
	first_data_sector: usize,

	root_first_cluster: u32,
	root_sector_count: u32,

	//fat_cache: vfs::Cache<[u32; FAT_CACHE_BLOCK_SIZE]>,
}

/// Inodes IDs destrucure into two 28-bit cluster IDs, and a 16-bit dir offset
#[derive(Debug)]
struct InodeRef
{
	dir_first_cluster: u32,
	dir_offset: u16,
	first_cluster: u32,
}

/// Iterable cluster list
enum ClusterList {
	Range(::core::ops::Range<u32>),
	Chained(ArefBorrow<FilesystemInner>, u32),
}

static S_DRIVER: Driver = Driver;

fn init()
{
	let h = mount::DriverRegistration::new("fat", &S_DRIVER);
	// TODO: Remember the registration for unloading
	::core::mem::forget(h);
}

impl mount::Driver for Driver
{
	fn detect(&self, vol: &VolumeHandle) -> vfs::Result<usize> {
		let bs = {
			let mut bs = [0u8; 512];
			try!( vol.read_blocks(0, &mut bs) );
			on_disk::BootSect::read(&bs)
			};

		let bps = bs.common().bps;
		let spc = bs.common().spc;
		let media_desc = bs.common().media_descriptor;

		if bps == 0 || spc == 0 || media_desc < 0xf0 {
			Ok(0)
		}
		else {
			Ok(1)
		}
	}
	fn mount(&self, vol: VolumeHandle) -> vfs::Result<Box<mount::Filesystem>> {
		let bs = {
			let mut bs = [0u8; 512];
			try!( vol.read_blocks(0, &mut bs) );
			on_disk::BootSect::read(&mut &bs[..])
			};
		let bs_c = bs.common();
		if bs_c.bps != 512 {
			return Err(vfs::Error::Unknown("TODO: non 512-byte sector FAT"))
		}
		if bs_c.fat_count == 0 {
			return Err(vfs::Error::Unknown("FAT Count is 0"));
		}

		log_debug!("Label: {:?}", ::kernel::lib::RawString(&bs.tail_common().label));

		let bps = bs_c.bps as usize;
		let spc = bs_c.spc as usize;

		let root_dir_sectors = (bs_c.files_in_root as usize*32 + bps - 1) / bps;
		let fat_size = if bs_c.fat_size_16 > 0 {
				bs_c.fat_size_16 as usize
			}
			else {
				todo!("FAT32 FAT size field");
			};
		let total_sectors = if bs_c.total_sectors_16 > 0 {
				bs_c.total_sectors_16 as usize
			}
			else {
				bs_c.total_sectors_32 as usize
			};

		// Calcualte some quantities
		let spare_fat_sectors = (bs_c.fat_count as usize - 1) * fat_size;
		let first_data_sector = bs_c.reserved_sect_count as usize
			+ fat_size + spare_fat_sectors
			+ root_dir_sectors;
		let cluster_count = (total_sectors - first_data_sector - spare_fat_sectors) / spc;

		// Determine the FAT type
		let fat_type = if cluster_count < FAT16_MIN_CLUSTERS {
				Size::Fat12
			}
			else if cluster_count < FAT32_MIN_CLUSTERS {
				Size::Fat16
			}
			else {
				Size::Fat32
			};
		log_debug!("{:?} {} sectors, Size {}", fat_type, total_sectors,
			SizePrinter((total_sectors*bs_c.bps as usize) as u64));

		Ok(Box::new(Filesystem {
			// SAFE: Saving to a Box, so won't move
			inner: unsafe { ArefInner::new(FilesystemInner {
				ty: fat_type,
				spc: spc,
				cluster_size: spc * vol.block_size(),
				cluster_count: cluster_count,
				first_fat_sector: bs_c.reserved_sect_count as usize,
				first_data_sector: first_data_sector,
				root_first_cluster: match fat_type {
					Size::Fat32 => bs.info32().unwrap().root_cluster,
					_ => FATL_ROOT_CLUSTER as u32,
					},
				root_sector_count: root_dir_sectors as u32,

				vh: vol,
				}) },
			}))
	}
}

type Cluster = Vec<u8>;

impl FilesystemInner
{
	/// Load a cluster from disk
	// TODO: V V V
	// - Should this function lock the cluster somehow to prevent accidental overlap?
	// - Could also cache somehow (with a refcount) along with the 'writing' flag
	fn read_cluster(&self, cluster: u32, dst: &mut [u8]) -> Result<(), storage::IoError> {
		log_trace!("Filesystem::read_cluster({:#x})", cluster);
		assert_eq!(dst.len(), self.cluster_size);
		// For now, just read the bytes, screw caching
		let sector = if !is!(self.ty, Size::Fat32) && cluster >= FATL_ROOT_CLUSTER {
				// Root directory (for FAT12/16, where it was not a normal file)
				let rc = cluster - FATL_ROOT_CLUSTER;
				assert!( (rc as u64 * self.spc as u64) < self.root_sector_count as u64);
				(self.first_data_sector - self.root_sector_count as usize) as u64
				+ (rc * self.spc as u32) as u64
			}
			else {
				// Anything else
				assert!(cluster >= 2);
				assert!(cluster - 2 < self.cluster_count as u32);
				self.first_data_sector as u64 + (cluster as u64 - 2) * self.spc as u64
			};
		log_debug!("cluster = {:#x}, sector = 0x{:x}", cluster, sector);
		try!(self.vh.read_blocks(sector, dst));
		//::kernel::logging::hex_dump("FAT Cluster", &buf);
		Ok( () )
	}
	fn load_cluster(&self, cluster: u32) -> Result<Cluster, storage::IoError> {
		let mut buf: Vec<u8> = ::core::iter::repeat(0).take(self.spc * self.vh.block_size()).collect();
		try!(self.read_cluster(cluster, &mut buf));
		Ok( buf )
	}

	/// Obtain the next cluster in a chain
	fn get_next_cluster(&self, cluster: u32) -> Option<u32> {
		use kernel::lib::byteorder::{ReadBytesExt,LittleEndian};
		// - Determine what sector contains the requested FAT entry
		let bs = self.vh.block_size();
		let (fat_sector,ofs) = match self.ty
			{
			Size::Fat12 => {
				let cps = bs / 3 * 2;	// 2 per 3 bytes
				(cluster as usize / cps, (cluster as usize % cps) / 2 * 3 )
				},
			Size::Fat16 => {
				let cps = bs / 2;
				(cluster as usize / cps, (cluster as usize % cps) * 2)
				},
			Size::Fat32 => {
				let cps = bs / 4;
				(cluster as usize / cps, (cluster as usize % cps) * 2)
				},
			};
		// - Read a single sector from the FAT
		let mut sector_data = Vec::from_fn(bs, |_|0u8);
		match self.vh.read_blocks( (self.first_fat_sector + fat_sector) as u64, &mut sector_data )
		{
		Ok(_) => {},
		Err(_) => return None,
		}
		// - Extract the entry
		match self.ty
		{
		Size::Fat12 => {
			// FAT12 has special handling because it packs 2 entries into 24 bytes
			let val = {
				let v24 = (&sector_data[ofs..]).read_uint::<LittleEndian>(3).unwrap();
				if cluster % 2 == 0 { v24 & 0xFFF } else { v24 >> 12 }
				} as u16;
			if val == FAT12_EOC { None } else { Some(val as u32) }
			},
		Size::Fat16 => {
			let val = (&sector_data[ofs..]).read_u16::<LittleEndian>().unwrap();
			if val == FAT16_EOC { None } else { Some(val as u32) }
			},
		Size::Fat32 => {
			let val = (&sector_data[ofs..]).read_u32::<LittleEndian>().unwrap();
			if val == FAT32_EOC { None } else { Some(val as u32) }
			},
		}
	}
}

impl mount::Filesystem for Filesystem
{
	fn root_inode(&self) -> node::InodeId {
		(InodeRef {
			first_cluster: self.root_first_cluster,
			dir_first_cluster: 0,
			dir_offset: 0,
			}).to_id()
	}
	fn get_node_by_inode(&self, id: node::InodeId) -> Option<node::Node> {
		let r = InodeRef::from(id);
		if r.first_cluster == self.root_first_cluster {
			Some(node::Node::Dir(dir::DirNode::new_boxed(self.inner.borrow(), r.first_cluster)))
		}
		else {
			// Reading from the directory starting at r.dir_first_cluster
			// locate the file with cluster equal to r.first_cluster.
			// And use that to create the node
			let dn = dir::DirNode::new(self.inner.borrow(), r.dir_first_cluster);
			dn.find_node(r.first_cluster)
		}
	}
}

impl InodeRef
{
	fn new(c: u32, dir_c: u32) -> InodeRef {
		assert!(c     <= 0x00FF_FFFF);
		assert!(dir_c <= 0x00FF_FFFF);
		InodeRef {
			first_cluster: c,
			dir_first_cluster: dir_c,
			dir_offset: 0,
		}
	}
	fn to_id(&self) -> node::InodeId {
		assert!(self.first_cluster <= 0x00FF_FFFF);
		assert!(self.dir_first_cluster <= 0x00FF_FFFF);
		//assert!(v.dir_offset <= 0xFFFF);
		(self.first_cluster as u64)
		| (self.dir_first_cluster as u64) << 24
		| (self.dir_offset as u64) << 48
	}
}

impl From<node::InodeId> for InodeRef {
	fn from(v: node::InodeId) -> InodeRef {
		InodeRef {
			first_cluster: (v & 0x00FF_FFFF) as u32,
			dir_first_cluster: ((v >> 24) & 0x00FF_FFFF) as u32,
			dir_offset: (v >> 48) as u16,
		}
	}
}

impl ClusterList {
	pub fn chained(fs: ArefBorrow<FilesystemInner>, start: u32) -> ClusterList {
		ClusterList::Chained(fs, start)
	}
}
impl ::core::iter::Iterator for ClusterList {
	type Item = u32;
	fn next(&mut self) -> Option<u32> {
		match *self
		{
		ClusterList::Range(ref mut r) => r.next(),
		ClusterList::Chained(ref fs, ref mut next) =>
			if *next == 0 {
				None
			}
			else {
				let rv = *next;
				*next = fs.get_next_cluster(*next).unwrap_or(0);
				Some( rv )
			},
		}
	}
}
<|endoftext|>


<commit_before>//! When specifying SSR rule, you generally want to map one *kind* of thing to
//! the same kind of thing: path to path, expression to expression, type to
//! type.
//!
//! The problem is, while this *kind* is generally obvious to the human, the ide
//! needs to determine it somehow. We do this in a stupid way -- by pasting SSR
//! rule into different contexts and checking what works.

use syntax::{ast, AstNode, SyntaxNode};

pub(crate) fn ty(s: &str) -> Result<SyntaxNode, ()> {
    let template = "type T = {};";
    let input = template.replace("{}", s);
    let parse = syntax::SourceFile::parse(&input);
    if !parse.errors().is_empty() {
        return Err(());
    }
    let node = parse.tree().syntax().descendants().find_map(ast::Type::cast).ok_or(())?;
    if node.to_string() != s {
        return Err(());
    }
    Ok(node.syntax().clone_subtree())
}

pub(crate) fn item(s: &str) -> Result<SyntaxNode, ()> {
    let template = "{}";
    let input = template.replace("{}", s);
    let parse = syntax::SourceFile::parse(&input);
    if !parse.errors().is_empty() {
        return Err(());
    }
    let node = parse.tree().syntax().descendants().find_map(ast::Item::cast).ok_or(())?;
    if node.to_string() != s {
        return Err(());
    }
    Ok(node.syntax().clone_subtree())
}

pub(crate) fn pat(s: &str) -> Result<SyntaxNode, ()> {
    let template = "const _: () = {let {} = ();};";
    let input = template.replace("{}", s);
    let parse = syntax::SourceFile::parse(&input);
    if !parse.errors().is_empty() {
        return Err(());
    }
    let node = parse.tree().syntax().descendants().find_map(ast::Pat::cast).ok_or(())?;
    if node.to_string() != s {
        return Err(());
    }
    Ok(node.syntax().clone_subtree())
}

pub(crate) fn expr(s: &str) -> Result<SyntaxNode, ()> {
    let template = "const _: () = {};";
    let input = template.replace("{}", s);
    let parse = syntax::SourceFile::parse(&input);
    if !parse.errors().is_empty() {
        return Err(());
    }
    let node = parse.tree().syntax().descendants().find_map(ast::Expr::cast).ok_or(())?;
    if node.to_string() != s {
        return Err(());
    }
    Ok(node.syntax().clone_subtree())
}

pub(crate) fn stmt(s: &str) -> Result<SyntaxNode, ()> {
    let template = "const _: () = { {}; };";
    let input = template.replace("{}", s);
    let parse = syntax::SourceFile::parse(&input);
    if !parse.errors().is_empty() {
        return Err(());
    }
    let mut node =
        parse.tree().syntax().descendants().skip(2).find_map(ast::Stmt::cast).ok_or(())?;
    if !s.ends_with(';') && node.to_string().ends_with(';') {
        node = node.clone_for_update();
        node.syntax().last_token().map(|it| it.detach());
    }
    if node.to_string() != s {
        return Err(());
    }
    Ok(node.syntax().clone_subtree())
}
<commit_msg>compress<commit_after>//! When specifying SSR rule, you generally want to map one *kind* of thing to
//! the same kind of thing: path to path, expression to expression, type to
//! type.
//!
//! The problem is, while this *kind* is generally obvious to the human, the ide
//! needs to determine it somehow. We do this in a stupid way -- by pasting SSR
//! rule into different contexts and checking what works.

use syntax::{ast, AstNode, SyntaxNode};

pub(crate) fn ty(s: &str) -> Result<SyntaxNode, ()> {
    fragment::<ast::Type>("type T = {};", s)
}

pub(crate) fn item(s: &str) -> Result<SyntaxNode, ()> {
    fragment::<ast::Item>("{}", s)
}

pub(crate) fn pat(s: &str) -> Result<SyntaxNode, ()> {
    fragment::<ast::Pat>("const _: () = {let {} = ();};", s)
}

pub(crate) fn expr(s: &str) -> Result<SyntaxNode, ()> {
    fragment::<ast::Expr>("const _: () = {};", s)
}

pub(crate) fn stmt(s: &str) -> Result<SyntaxNode, ()> {
    let template = "const _: () = { {}; };";
    let input = template.replace("{}", s);
    let parse = syntax::SourceFile::parse(&input);
    if !parse.errors().is_empty() {
        return Err(());
    }
    let mut node =
        parse.tree().syntax().descendants().skip(2).find_map(ast::Stmt::cast).ok_or(())?;
    if !s.ends_with(';') && node.to_string().ends_with(';') {
        node = node.clone_for_update();
        node.syntax().last_token().map(|it| it.detach());
    }
    if node.to_string() != s {
        return Err(());
    }
    Ok(node.syntax().clone_subtree())
}

fn fragment<T: AstNode>(template: &str, s: &str) -> Result<SyntaxNode, ()> {
    let s = s.trim();
    let input = template.replace("{}", s);
    let parse = syntax::SourceFile::parse(&input);
    if !parse.errors().is_empty() {
        return Err(());
    }
    let node = parse.tree().syntax().descendants().find_map(T::cast).ok_or(())?;
    if node.syntax().text() != s {
        return Err(());
    }
    Ok(node.syntax().clone_subtree())
}
<|endoftext|>


<commit_before>use crate::spec::{StackProbeType, Target};

pub fn target() -> Target {
    let mut base = super::hermit_kernel_base::opts();
    base.cpu = "x86-64".to_string();
    base.max_atomic_width = Some(64);
    base.features =
        "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float"
            .to_string();
    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
    base.stack_probes = StackProbeType::Call;

    Target {
        llvm_target: "x86_64-unknown-none-elf".to_string(),
        pointer_width: 64,
        data_layout: "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
            .to_string(),
        arch: "x86_64".to_string(),
        options: base,
    }
}
<commit_msg>rename LLVM target for RustyHermit<commit_after>use crate::spec::{StackProbeType, Target};

pub fn target() -> Target {
    let mut base = super::hermit_kernel_base::opts();
    base.cpu = "x86-64".to_string();
    base.max_atomic_width = Some(64);
    base.features =
        "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float"
            .to_string();
    // don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
    base.stack_probes = StackProbeType::Call;

    Target {
        llvm_target: "x86_64-unknown-hermit".to_string(),
        pointer_width: 64,
        data_layout: "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
            .to_string(),
        arch: "x86_64".to_string(),
        options: base,
    }
}
<|endoftext|>


<commit_before>use crate::spec::{LinkArgs, LinkerFlavor, TargetOptions};
use std::default::Default;

pub fn opts() -> TargetOptions {
    let mut pre_link_args = LinkArgs::new();
    pre_link_args.insert(
        LinkerFlavor::Gcc,
        vec![
            // Tell GCC to avoid linker plugins, because we are not bundling
            // them with Windows installer, and Rust does its own LTO anyways.
            "-fno-use-linker-plugin".to_string(),
            // Always enable DEP (NX bit) when it is available
            "-Wl,--nxcompat".to_string(),
            // Do not use the standard system startup files or libraries when linking
            "-nostdlib".to_string(),
        ],
    );

    let mut late_link_args = LinkArgs::new();
    let mut late_link_args_dynamic = LinkArgs::new();
    let mut late_link_args_static = LinkArgs::new();
    late_link_args.insert(
        LinkerFlavor::Gcc,
        vec![
            "-lmingwex".to_string(),
            "-lmingw32".to_string(),
            "-lmsvcrt".to_string(),
            // mingw's msvcrt is a weird hybrid import library and static library.
            // And it seems that the linker fails to use import symbols from msvcrt
            // that are required from functions in msvcrt in certain cases. For example
            // `_fmode` that is used by an implementation of `__p__fmode` in x86_64.
            // Listing the library twice seems to fix that, and seems to also be done
            // by mingw's gcc (Though not sure if it's done on purpose, or by mistake).
            //
            // See https://github.com/rust-lang/rust/pull/47483
            "-lmsvcrt".to_string(),
            "-luser32".to_string(),
            "-lkernel32".to_string(),
        ],
    );
    late_link_args_dynamic.insert(
        LinkerFlavor::Gcc,
        vec![
            // If any of our crates are dynamically linked then we need to use
            // the shared libgcc_s-dw2-1.dll. This is required to support
            // unwinding across DLL boundaries.
            "-lgcc_s".to_string(),
            "-lgcc".to_string(),
            "-lkernel32".to_string(),
        ],
    );
    late_link_args_static.insert(
        LinkerFlavor::Gcc,
        vec![
            // If all of our crates are statically linked then we can get away
            // with statically linking the libgcc unwinding code. This allows
            // binaries to be redistributed without the libgcc_s-dw2-1.dll
            // dependency, but unfortunately break unwinding across DLL
            // boundaries when unwinding across FFI boundaries.
            "-lgcc".to_string(),
            "-lgcc_eh".to_string(),
            "-lpthread".to_string(),
            // libpthread depends on libmsvcrt, so we need to link it *again*.
            "-lmsvcrt".to_string(),
            "-lkernel32".to_string(),
        ],
    );

    TargetOptions {
        // FIXME(#13846) this should be enabled for windows
        function_sections: false,
        linker: Some("gcc".to_string()),
        dynamic_linking: true,
        executables: true,
        dll_prefix: String::new(),
        dll_suffix: ".dll".to_string(),
        exe_suffix: ".exe".to_string(),
        staticlib_prefix: String::new(),
        staticlib_suffix: ".lib".to_string(),
        target_family: Some("windows".to_string()),
        is_like_windows: true,
        allows_weak_linkage: false,
        pre_link_args,
        pre_link_objects_exe: vec![
            "crt2.o".to_string(),    // mingw C runtime initialization for executables
            "rsbegin.o".to_string(), // Rust compiler runtime initialization, see rsbegin.rs
        ],
        pre_link_objects_dll: vec![
            "dllcrt2.o".to_string(), // mingw C runtime initialization for dlls
            "rsbegin.o".to_string(),
        ],
        late_link_args,
        late_link_args_dynamic,
        late_link_args_static,
        post_link_objects: vec!["rsend.o".to_string()],
        abi_return_struct_as_int: true,
        emit_debug_gdb_scripts: false,
        requires_uwtable: true,

        ..Default::default()
    }
}
<commit_msg>[windows-gnu] Link pthread statically<commit_after>use crate::spec::{LinkArgs, LinkerFlavor, TargetOptions};
use std::default::Default;

pub fn opts() -> TargetOptions {
    let mut pre_link_args = LinkArgs::new();
    pre_link_args.insert(
        LinkerFlavor::Gcc,
        vec![
            // Tell GCC to avoid linker plugins, because we are not bundling
            // them with Windows installer, and Rust does its own LTO anyways.
            "-fno-use-linker-plugin".to_string(),
            // Always enable DEP (NX bit) when it is available
            "-Wl,--nxcompat".to_string(),
            // Do not use the standard system startup files or libraries when linking
            "-nostdlib".to_string(),
        ],
    );

    let mut late_link_args = LinkArgs::new();
    let mut late_link_args_dynamic = LinkArgs::new();
    let mut late_link_args_static = LinkArgs::new();
    late_link_args.insert(
        LinkerFlavor::Gcc,
        vec![
            "-lmingwex".to_string(),
            "-lmingw32".to_string(),
            "-lmsvcrt".to_string(),
            // mingw's msvcrt is a weird hybrid import library and static library.
            // And it seems that the linker fails to use import symbols from msvcrt
            // that are required from functions in msvcrt in certain cases. For example
            // `_fmode` that is used by an implementation of `__p__fmode` in x86_64.
            // Listing the library twice seems to fix that, and seems to also be done
            // by mingw's gcc (Though not sure if it's done on purpose, or by mistake).
            //
            // See https://github.com/rust-lang/rust/pull/47483
            "-lmsvcrt".to_string(),
            "-luser32".to_string(),
            "-lkernel32".to_string(),
        ],
    );
    late_link_args_dynamic.insert(
        LinkerFlavor::Gcc,
        vec![
            // If any of our crates are dynamically linked then we need to use
            // the shared libgcc_s-dw2-1.dll. This is required to support
            // unwinding across DLL boundaries.
            "-lgcc_s".to_string(),
            "-lgcc".to_string(),
            "-lkernel32".to_string(),
        ],
    );
    late_link_args_static.insert(
        LinkerFlavor::Gcc,
        vec![
            // If all of our crates are statically linked then we can get away
            // with statically linking the libgcc unwinding code. This allows
            // binaries to be redistributed without the libgcc_s-dw2-1.dll
            // dependency, but unfortunately break unwinding across DLL
            // boundaries when unwinding across FFI boundaries.
            "-lgcc_eh".to_string(),
            "-l:libpthread.a".to_string(),
            "-lgcc".to_string(),
            // libpthread depends on libmsvcrt, so we need to link it *again*.
            "-lmsvcrt".to_string(),
            "-lkernel32".to_string(),
        ],
    );

    TargetOptions {
        // FIXME(#13846) this should be enabled for windows
        function_sections: false,
        linker: Some("gcc".to_string()),
        dynamic_linking: true,
        executables: true,
        dll_prefix: String::new(),
        dll_suffix: ".dll".to_string(),
        exe_suffix: ".exe".to_string(),
        staticlib_prefix: String::new(),
        staticlib_suffix: ".lib".to_string(),
        target_family: Some("windows".to_string()),
        is_like_windows: true,
        allows_weak_linkage: false,
        pre_link_args,
        pre_link_objects_exe: vec![
            "crt2.o".to_string(),    // mingw C runtime initialization for executables
            "rsbegin.o".to_string(), // Rust compiler runtime initialization, see rsbegin.rs
        ],
        pre_link_objects_dll: vec![
            "dllcrt2.o".to_string(), // mingw C runtime initialization for dlls
            "rsbegin.o".to_string(),
        ],
        late_link_args,
        late_link_args_dynamic,
        late_link_args_static,
        post_link_objects: vec!["rsend.o".to_string()],
        abi_return_struct_as_int: true,
        emit_debug_gdb_scripts: false,
        requires_uwtable: true,

        ..Default::default()
    }
}
<|endoftext|>


<commit_before>//! Data struture serialization related stuff for RPC
//!
//! Defines all necessary rpc serialization data structures,
//! which includes `ra_tt` related data and some task messages.
//! Although adding `Serialize` and `Deserialize` traits to `ra_tt` directly seems
//! to be much easier, we deliberately duplicate `ra_tt` structs with `#[serde(with = "XXDef")]`
//! for separation of code responsibility.

use ra_tt::{
    Delimiter, DelimiterKind, Ident, Leaf, Literal, Punct, SmolStr, Spacing, Subtree, TokenId,
    TokenTree,
};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub struct ListMacrosTask {
    pub lib: PathBuf,
}

#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub enum ProcMacroKind {
    CustomDerive,
    FuncLike,
    Attr,
}

#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
pub struct ListMacrosResult {
    pub macros: Vec<(String, ProcMacroKind)>,
}

#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub struct ExpansionTask {
    /// Argument of macro call.
    ///
    /// In custom derive this will be a struct or enum; in attribute-like macro - underlying
    /// item; in function-like macro - the macro body.
    #[serde(with = "SubtreeDef")]
    pub macro_body: Subtree,

    /// Names of macros to expand. // TODO: are they comma-separated?
    ///
    /// In custom derive those are names of derived traits (`Serialize`, `Getters`, etc.). In
    /// attribute-like and function-like macros - single name of macro itself (`show_streams`).
    pub macro_name: String,

    /// Possible attributes for the attribute-like macros.
    #[serde(with = "opt_subtree_def")]
    pub attributes: Option<Subtree>,

    pub lib: PathBuf,
}

#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
pub struct ExpansionResult {
    #[serde(with = "SubtreeDef")]
    pub expansion: Subtree,
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "DelimiterKind")]
enum DelimiterKindDef {
    Parenthesis,
    Brace,
    Bracket,
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "TokenId")]
struct TokenIdDef(u32);

#[derive(Serialize, Deserialize)]
#[serde(remote = "Delimiter")]
struct DelimiterDef {
    #[serde(with = "TokenIdDef")]
    pub id: TokenId,
    #[serde(with = "DelimiterKindDef")]
    pub kind: DelimiterKind,
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "Subtree")]
struct SubtreeDef {
    #[serde(default, with = "opt_delimiter_def")]
    pub delimiter: Option<Delimiter>,
    #[serde(with = "vec_token_tree")]
    pub token_trees: Vec<TokenTree>,
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "TokenTree")]
enum TokenTreeDef {
    #[serde(with = "LeafDef")]
    Leaf(Leaf),
    #[serde(with = "SubtreeDef")]
    Subtree(Subtree),
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "Leaf")]
enum LeafDef {
    #[serde(with = "LiteralDef")]
    Literal(Literal),
    #[serde(with = "PunctDef")]
    Punct(Punct),
    #[serde(with = "IdentDef")]
    Ident(Ident),
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "Literal")]
struct LiteralDef {
    pub text: SmolStr,
    #[serde(with = "TokenIdDef")]
    pub id: TokenId,
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "Punct")]
struct PunctDef {
    pub char: char,
    #[serde(with = "SpacingDef")]
    pub spacing: Spacing,
    #[serde(with = "TokenIdDef")]
    pub id: TokenId,
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "Spacing")]
enum SpacingDef {
    Alone,
    Joint,
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "Ident")]
struct IdentDef {
    pub text: SmolStr,
    #[serde(with = "TokenIdDef")]
    pub id: TokenId,
}

mod opt_delimiter_def {
    use super::{Delimiter, DelimiterDef};
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    pub fn serialize<S>(value: &Option<Delimiter>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        #[derive(Serialize)]
        struct Helper<'a>(#[serde(with = "DelimiterDef")] &'a Delimiter);
        value.as_ref().map(Helper).serialize(serializer)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Delimiter>, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct Helper(#[serde(with = "DelimiterDef")] Delimiter);
        let helper = Option::deserialize(deserializer)?;
        Ok(helper.map(|Helper(external)| external))
    }
}

mod opt_subtree_def {
    use super::{Subtree, SubtreeDef};
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    pub fn serialize<S>(value: &Option<Subtree>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        #[derive(Serialize)]
        struct Helper<'a>(#[serde(with = "SubtreeDef")] &'a Subtree);
        value.as_ref().map(Helper).serialize(serializer)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Subtree>, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct Helper(#[serde(with = "SubtreeDef")] Subtree);
        let helper = Option::deserialize(deserializer)?;
        Ok(helper.map(|Helper(external)| external))
    }
}

mod vec_token_tree {
    use super::{TokenTree, TokenTreeDef};
    use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer};

    pub fn serialize<S>(value: &Vec<TokenTree>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        #[derive(Serialize)]
        struct Helper<'a>(#[serde(with = "TokenTreeDef")] &'a TokenTree);

        let items: Vec<_> = value.iter().map(Helper).collect();
        let mut seq = serializer.serialize_seq(Some(items.len()))?;
        for element in items {
            seq.serialize_element(&element)?;
        }
        seq.end()
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<TokenTree>, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct Helper(#[serde(with = "TokenTreeDef")] TokenTree);

        let helper = Vec::deserialize(deserializer)?;
        Ok(helper.into_iter().map(|Helper(external)| external).collect())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn fixture_token_tree() -> Subtree {
        let mut subtree = Subtree::default();
        subtree
            .token_trees
            .push(TokenTree::Leaf(Ident { text: "struct".into(), id: TokenId(0) }.into()));
        subtree
            .token_trees
            .push(TokenTree::Leaf(Ident { text: "Foo".into(), id: TokenId(1) }.into()));
        subtree.token_trees.push(TokenTree::Subtree(
            Subtree {
                delimiter: Some(Delimiter { id: TokenId(2), kind: DelimiterKind::Brace }),
                token_trees: vec![],
            }
            .into(),
        ));
        subtree
    }

    #[test]
    fn test_proc_macro_rpc_works() {
        let tt = fixture_token_tree();
        let task = ExpansionTask {
            macro_body: tt.clone(),
            macro_name: Default::default(),
            attributes: None,
            lib: Default::default(),
        };

        let json = serde_json::to_string(&task).unwrap();
        let back: ExpansionTask = serde_json::from_str(&json).unwrap();

        assert_eq!(task.macro_body, back.macro_body);

        let result = ExpansionResult { expansion: tt.clone() };
        let json = serde_json::to_string(&result).unwrap();
        let back: ExpansionResult = serde_json::from_str(&json).unwrap();

        assert_eq!(result, back);
    }
}
<commit_msg>Fix doc comment<commit_after>//! Data struture serialization related stuff for RPC
//!
//! Defines all necessary rpc serialization data structures,
//! which includes `ra_tt` related data and some task messages.
//! Although adding `Serialize` and `Deserialize` traits to `ra_tt` directly seems
//! to be much easier, we deliberately duplicate `ra_tt` structs with `#[serde(with = "XXDef")]`
//! for separation of code responsibility.

use ra_tt::{
    Delimiter, DelimiterKind, Ident, Leaf, Literal, Punct, SmolStr, Spacing, Subtree, TokenId,
    TokenTree,
};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub struct ListMacrosTask {
    pub lib: PathBuf,
}

#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub enum ProcMacroKind {
    CustomDerive,
    FuncLike,
    Attr,
}

#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
pub struct ListMacrosResult {
    pub macros: Vec<(String, ProcMacroKind)>,
}

#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub struct ExpansionTask {
    /// Argument of macro call.
    ///
    /// In custom derive this will be a struct or enum; in attribute-like macro - underlying
    /// item; in function-like macro - the macro body.
    #[serde(with = "SubtreeDef")]
    pub macro_body: Subtree,

    /// Name of macro to expand.
    ///
    /// In custom derive this is the name of the derived trait (`Serialize`, `Getters`, etc.).
    /// In attribute-like and function-like macros - single name of macro itself (`show_streams`).
    pub macro_name: String,

    /// Possible attributes for the attribute-like macros.
    #[serde(with = "opt_subtree_def")]
    pub attributes: Option<Subtree>,

    pub lib: PathBuf,
}

#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
pub struct ExpansionResult {
    #[serde(with = "SubtreeDef")]
    pub expansion: Subtree,
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "DelimiterKind")]
enum DelimiterKindDef {
    Parenthesis,
    Brace,
    Bracket,
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "TokenId")]
struct TokenIdDef(u32);

#[derive(Serialize, Deserialize)]
#[serde(remote = "Delimiter")]
struct DelimiterDef {
    #[serde(with = "TokenIdDef")]
    pub id: TokenId,
    #[serde(with = "DelimiterKindDef")]
    pub kind: DelimiterKind,
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "Subtree")]
struct SubtreeDef {
    #[serde(default, with = "opt_delimiter_def")]
    pub delimiter: Option<Delimiter>,
    #[serde(with = "vec_token_tree")]
    pub token_trees: Vec<TokenTree>,
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "TokenTree")]
enum TokenTreeDef {
    #[serde(with = "LeafDef")]
    Leaf(Leaf),
    #[serde(with = "SubtreeDef")]
    Subtree(Subtree),
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "Leaf")]
enum LeafDef {
    #[serde(with = "LiteralDef")]
    Literal(Literal),
    #[serde(with = "PunctDef")]
    Punct(Punct),
    #[serde(with = "IdentDef")]
    Ident(Ident),
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "Literal")]
struct LiteralDef {
    pub text: SmolStr,
    #[serde(with = "TokenIdDef")]
    pub id: TokenId,
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "Punct")]
struct PunctDef {
    pub char: char,
    #[serde(with = "SpacingDef")]
    pub spacing: Spacing,
    #[serde(with = "TokenIdDef")]
    pub id: TokenId,
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "Spacing")]
enum SpacingDef {
    Alone,
    Joint,
}

#[derive(Serialize, Deserialize)]
#[serde(remote = "Ident")]
struct IdentDef {
    pub text: SmolStr,
    #[serde(with = "TokenIdDef")]
    pub id: TokenId,
}

mod opt_delimiter_def {
    use super::{Delimiter, DelimiterDef};
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    pub fn serialize<S>(value: &Option<Delimiter>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        #[derive(Serialize)]
        struct Helper<'a>(#[serde(with = "DelimiterDef")] &'a Delimiter);
        value.as_ref().map(Helper).serialize(serializer)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Delimiter>, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct Helper(#[serde(with = "DelimiterDef")] Delimiter);
        let helper = Option::deserialize(deserializer)?;
        Ok(helper.map(|Helper(external)| external))
    }
}

mod opt_subtree_def {
    use super::{Subtree, SubtreeDef};
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    pub fn serialize<S>(value: &Option<Subtree>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        #[derive(Serialize)]
        struct Helper<'a>(#[serde(with = "SubtreeDef")] &'a Subtree);
        value.as_ref().map(Helper).serialize(serializer)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Subtree>, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct Helper(#[serde(with = "SubtreeDef")] Subtree);
        let helper = Option::deserialize(deserializer)?;
        Ok(helper.map(|Helper(external)| external))
    }
}

mod vec_token_tree {
    use super::{TokenTree, TokenTreeDef};
    use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer};

    pub fn serialize<S>(value: &Vec<TokenTree>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        #[derive(Serialize)]
        struct Helper<'a>(#[serde(with = "TokenTreeDef")] &'a TokenTree);

        let items: Vec<_> = value.iter().map(Helper).collect();
        let mut seq = serializer.serialize_seq(Some(items.len()))?;
        for element in items {
            seq.serialize_element(&element)?;
        }
        seq.end()
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<TokenTree>, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct Helper(#[serde(with = "TokenTreeDef")] TokenTree);

        let helper = Vec::deserialize(deserializer)?;
        Ok(helper.into_iter().map(|Helper(external)| external).collect())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn fixture_token_tree() -> Subtree {
        let mut subtree = Subtree::default();
        subtree
            .token_trees
            .push(TokenTree::Leaf(Ident { text: "struct".into(), id: TokenId(0) }.into()));
        subtree
            .token_trees
            .push(TokenTree::Leaf(Ident { text: "Foo".into(), id: TokenId(1) }.into()));
        subtree.token_trees.push(TokenTree::Subtree(
            Subtree {
                delimiter: Some(Delimiter { id: TokenId(2), kind: DelimiterKind::Brace }),
                token_trees: vec![],
            }
            .into(),
        ));
        subtree
    }

    #[test]
    fn test_proc_macro_rpc_works() {
        let tt = fixture_token_tree();
        let task = ExpansionTask {
            macro_body: tt.clone(),
            macro_name: Default::default(),
            attributes: None,
            lib: Default::default(),
        };

        let json = serde_json::to_string(&task).unwrap();
        let back: ExpansionTask = serde_json::from_str(&json).unwrap();

        assert_eq!(task.macro_body, back.macro_body);

        let result = ExpansionResult { expansion: tt.clone() };
        let json = serde_json::to_string(&result).unwrap();
        let back: ExpansionResult = serde_json::from_str(&json).unwrap();

        assert_eq!(result, back);
    }
}
<|endoftext|>


<commit_before>use ruma_common::MilliSecondsSinceUnixEpoch;
use ruma_events_macros::event_enum;
use ruma_identifiers::{EventId, RoomId, UserId};
use serde::{de, Serialize};
use serde_json::value::RawValue as RawJsonValue;

use crate::{from_raw_json_value, EventDeHelper};

event_enum! {
    /// Any global account data event.
    enum GlobalAccountData {
        "m.direct",
        "m.ignored_user_list",
        "m.push_rules",
    }

    /// Any room account data event.
    enum RoomAccountData {
        "m.fully_read",
        "m.tag",
    }

    /// Any ephemeral room event.
    enum EphemeralRoom {
        "m.receipt",
        "m.typing",
    }

    /// Any message event.
    enum Message {
        "m.call.answer",
        "m.call.invite",
        "m.call.hangup",
        "m.call.candidates",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.ready",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.start",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.cancel",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.accept",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.key",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.mac",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.done",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.reaction",
        "m.room.encrypted",
        "m.room.message",
        "m.room.message.feedback",
        "m.room.redaction",
        "m.sticker",
    }

    /// Any state event.
    enum State {
        "m.policy.rule.room",
        "m.policy.rule.server",
        "m.policy.rule.user",
        "m.room.aliases",
        "m.room.avatar",
        "m.room.canonical_alias",
        "m.room.create",
        "m.room.encryption",
        "m.room.guest_access",
        "m.room.history_visibility",
        "m.room.join_rules",
        "m.room.member",
        "m.room.name",
        "m.room.pinned_events",
        "m.room.power_levels",
        "m.room.server_acl",
        "m.room.third_party_invite",
        "m.room.tombstone",
        "m.room.topic",
    }

    /// Any to-device event.
    enum ToDevice {
        "m.dummy",
        "m.room_key",
        "m.room_key_request",
        "m.forwarded_room_key",
        "m.key.verification.request",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.ready",
        "m.key.verification.start",
        "m.key.verification.cancel",
        "m.key.verification.accept",
        "m.key.verification.key",
        "m.key.verification.mac",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.done",
        "m.room.encrypted",
    }
}

/// Declares an item with a doc attribute computed by some macro expression.
/// This allows documentation to be dynamically generated based on input.
/// Necessary to work around <https://github.com/rust-lang/rust/issues/52607>.
macro_rules! doc_concat {
    ( $( #[doc = $doc:expr] $( $thing:tt )* )* ) => ( $( #[doc = $doc] $( $thing )* )* );
}

macro_rules! room_ev_accessor {
    ($field:ident: $ty:ty) => {
        doc_concat! {
            #[doc = concat!("Returns this event's `", stringify!($field), "` field.")]
            pub fn $field(&self) -> $ty {
                match self {
                    Self::Message(ev) => ev.$field(),
                    Self::State(ev) => ev.$field(),
                    Self::RedactedMessage(ev) => ev.$field(),
                    Self::RedactedState(ev) => ev.$field(),
                }
            }
        }
    };
}

/// Any room event.
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Serialize)]
#[serde(untagged)]
pub enum AnyRoomEvent {
    /// Any message event.
    Message(AnyMessageEvent),

    /// Any state event.
    State(AnyStateEvent),

    /// Any message event that has been redacted.
    RedactedMessage(AnyRedactedMessageEvent),

    /// Any state event that has been redacted.
    RedactedState(AnyRedactedStateEvent),
}

impl AnyRoomEvent {
    room_ev_accessor!(origin_server_ts: &MilliSecondsSinceUnixEpoch);
    room_ev_accessor!(room_id: &RoomId);
    room_ev_accessor!(event_id: &EventId);
    room_ev_accessor!(sender: &UserId);
}

/// Any sync room event (room event without a `room_id`, as returned in `/sync` responses)
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Serialize)]
#[serde(untagged)]
pub enum AnySyncRoomEvent {
    /// Any sync message event
    Message(AnySyncMessageEvent),

    /// Any sync state event
    State(AnySyncStateEvent),

    /// Any sync message event that has been redacted.
    RedactedMessage(AnyRedactedSyncMessageEvent),

    /// Any sync state event that has been redacted.
    RedactedState(AnyRedactedSyncStateEvent),
}

impl AnySyncRoomEvent {
    room_ev_accessor!(origin_server_ts: &MilliSecondsSinceUnixEpoch);
    room_ev_accessor!(event_id: &EventId);
    room_ev_accessor!(sender: &UserId);

    /// Converts `self` to an `AnyRoomEvent` by adding the given a room ID.
    pub fn into_full_event(self, room_id: RoomId) -> AnyRoomEvent {
        match self {
            Self::Message(ev) => AnyRoomEvent::Message(ev.into_full_event(room_id)),
            Self::State(ev) => AnyRoomEvent::State(ev.into_full_event(room_id)),
            Self::RedactedMessage(ev) => AnyRoomEvent::RedactedMessage(ev.into_full_event(room_id)),
            Self::RedactedState(ev) => AnyRoomEvent::RedactedState(ev.into_full_event(room_id)),
        }
    }
}

impl<'de> de::Deserialize<'de> for AnyRoomEvent {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
        let EventDeHelper { state_key, unsigned, .. } = from_raw_json_value(&json)?;

        if state_key.is_some() {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnyRoomEvent::RedactedState(from_raw_json_value(&json)?)
                }
                _ => AnyRoomEvent::State(from_raw_json_value(&json)?),
            })
        } else {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnyRoomEvent::RedactedMessage(from_raw_json_value(&json)?)
                }
                _ => AnyRoomEvent::Message(from_raw_json_value(&json)?),
            })
        }
    }
}

impl<'de> de::Deserialize<'de> for AnySyncRoomEvent {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
        let EventDeHelper { state_key, unsigned, .. } = from_raw_json_value(&json)?;

        if state_key.is_some() {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnySyncRoomEvent::RedactedState(from_raw_json_value(&json)?)
                }
                _ => AnySyncRoomEvent::State(from_raw_json_value(&json)?),
            })
        } else {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnySyncRoomEvent::RedactedMessage(from_raw_json_value(&json)?)
                }
                _ => AnySyncRoomEvent::Message(from_raw_json_value(&json)?),
            })
        }
    }
}
<commit_msg>events: Add redact method to Any[Sync]RoomEvent<commit_after>use ruma_common::MilliSecondsSinceUnixEpoch;
use ruma_events_macros::event_enum;
use ruma_identifiers::{EventId, RoomId, RoomVersionId, UserId};
use serde::{de, Serialize};
use serde_json::value::RawValue as RawJsonValue;

use crate::{
    from_raw_json_value,
    room::redaction::{RedactionEvent, SyncRedactionEvent},
    EventDeHelper,
};

event_enum! {
    /// Any global account data event.
    enum GlobalAccountData {
        "m.direct",
        "m.ignored_user_list",
        "m.push_rules",
    }

    /// Any room account data event.
    enum RoomAccountData {
        "m.fully_read",
        "m.tag",
    }

    /// Any ephemeral room event.
    enum EphemeralRoom {
        "m.receipt",
        "m.typing",
    }

    /// Any message event.
    enum Message {
        "m.call.answer",
        "m.call.invite",
        "m.call.hangup",
        "m.call.candidates",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.ready",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.start",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.cancel",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.accept",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.key",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.mac",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.done",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.reaction",
        "m.room.encrypted",
        "m.room.message",
        "m.room.message.feedback",
        "m.room.redaction",
        "m.sticker",
    }

    /// Any state event.
    enum State {
        "m.policy.rule.room",
        "m.policy.rule.server",
        "m.policy.rule.user",
        "m.room.aliases",
        "m.room.avatar",
        "m.room.canonical_alias",
        "m.room.create",
        "m.room.encryption",
        "m.room.guest_access",
        "m.room.history_visibility",
        "m.room.join_rules",
        "m.room.member",
        "m.room.name",
        "m.room.pinned_events",
        "m.room.power_levels",
        "m.room.server_acl",
        "m.room.third_party_invite",
        "m.room.tombstone",
        "m.room.topic",
    }

    /// Any to-device event.
    enum ToDevice {
        "m.dummy",
        "m.room_key",
        "m.room_key_request",
        "m.forwarded_room_key",
        "m.key.verification.request",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.ready",
        "m.key.verification.start",
        "m.key.verification.cancel",
        "m.key.verification.accept",
        "m.key.verification.key",
        "m.key.verification.mac",
        #[cfg(feature = "unstable-pre-spec")]
        #[cfg_attr(docsrs, doc(cfg(feature = "unstable-pre-spec")))]
        "m.key.verification.done",
        "m.room.encrypted",
    }
}

/// Declares an item with a doc attribute computed by some macro expression.
/// This allows documentation to be dynamically generated based on input.
/// Necessary to work around <https://github.com/rust-lang/rust/issues/52607>.
macro_rules! doc_concat {
    ( $( #[doc = $doc:expr] $( $thing:tt )* )* ) => ( $( #[doc = $doc] $( $thing )* )* );
}

macro_rules! room_ev_accessor {
    ($field:ident: $ty:ty) => {
        doc_concat! {
            #[doc = concat!("Returns this event's `", stringify!($field), "` field.")]
            pub fn $field(&self) -> $ty {
                match self {
                    Self::Message(ev) => ev.$field(),
                    Self::State(ev) => ev.$field(),
                    Self::RedactedMessage(ev) => ev.$field(),
                    Self::RedactedState(ev) => ev.$field(),
                }
            }
        }
    };
}

/// Any room event.
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Serialize)]
#[serde(untagged)]
pub enum AnyRoomEvent {
    /// Any message event.
    Message(AnyMessageEvent),

    /// Any state event.
    State(AnyStateEvent),

    /// Any message event that has been redacted.
    RedactedMessage(AnyRedactedMessageEvent),

    /// Any state event that has been redacted.
    RedactedState(AnyRedactedStateEvent),
}

impl AnyRoomEvent {
    room_ev_accessor!(origin_server_ts: &MilliSecondsSinceUnixEpoch);
    room_ev_accessor!(room_id: &RoomId);
    room_ev_accessor!(event_id: &EventId);
    room_ev_accessor!(sender: &UserId);

    /// Redacts `self`, referencing the given event in `unsigned.redacted_because`.
    ///
    /// Does nothing for events that are already redacted.
    pub fn redact(self, redaction: RedactionEvent, version: &RoomVersionId) -> Self {
        match self {
            Self::Message(ev) => Self::RedactedMessage(ev.redact(redaction, version)),
            Self::State(ev) => Self::RedactedState(ev.redact(redaction, version)),
            Self::RedactedMessage(_) | Self::RedactedState(_) => self,
        }
    }
}

/// Any sync room event (room event without a `room_id`, as returned in `/sync` responses)
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Serialize)]
#[serde(untagged)]
pub enum AnySyncRoomEvent {
    /// Any sync message event
    Message(AnySyncMessageEvent),

    /// Any sync state event
    State(AnySyncStateEvent),

    /// Any sync message event that has been redacted.
    RedactedMessage(AnyRedactedSyncMessageEvent),

    /// Any sync state event that has been redacted.
    RedactedState(AnyRedactedSyncStateEvent),
}

impl AnySyncRoomEvent {
    room_ev_accessor!(origin_server_ts: &MilliSecondsSinceUnixEpoch);
    room_ev_accessor!(event_id: &EventId);
    room_ev_accessor!(sender: &UserId);

    /// Converts `self` to an `AnyRoomEvent` by adding the given a room ID.
    pub fn into_full_event(self, room_id: RoomId) -> AnyRoomEvent {
        match self {
            Self::Message(ev) => AnyRoomEvent::Message(ev.into_full_event(room_id)),
            Self::State(ev) => AnyRoomEvent::State(ev.into_full_event(room_id)),
            Self::RedactedMessage(ev) => AnyRoomEvent::RedactedMessage(ev.into_full_event(room_id)),
            Self::RedactedState(ev) => AnyRoomEvent::RedactedState(ev.into_full_event(room_id)),
        }
    }

    /// Redacts `self`, referencing the given event in `unsigned.redacted_because`.
    ///
    /// Does nothing for events that are already redacted.
    pub fn redact(self, redaction: SyncRedactionEvent, version: &RoomVersionId) -> Self {
        match self {
            Self::Message(ev) => Self::RedactedMessage(ev.redact(redaction, version)),
            Self::State(ev) => Self::RedactedState(ev.redact(redaction, version)),
            Self::RedactedMessage(_) | Self::RedactedState(_) => self,
        }
    }
}

impl<'de> de::Deserialize<'de> for AnyRoomEvent {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
        let EventDeHelper { state_key, unsigned, .. } = from_raw_json_value(&json)?;

        if state_key.is_some() {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnyRoomEvent::RedactedState(from_raw_json_value(&json)?)
                }
                _ => AnyRoomEvent::State(from_raw_json_value(&json)?),
            })
        } else {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnyRoomEvent::RedactedMessage(from_raw_json_value(&json)?)
                }
                _ => AnyRoomEvent::Message(from_raw_json_value(&json)?),
            })
        }
    }
}

impl<'de> de::Deserialize<'de> for AnySyncRoomEvent {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
        let EventDeHelper { state_key, unsigned, .. } = from_raw_json_value(&json)?;

        if state_key.is_some() {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnySyncRoomEvent::RedactedState(from_raw_json_value(&json)?)
                }
                _ => AnySyncRoomEvent::State(from_raw_json_value(&json)?),
            })
        } else {
            Ok(match unsigned {
                Some(unsigned) if unsigned.redacted_because.is_some() => {
                    AnySyncRoomEvent::RedactedMessage(from_raw_json_value(&json)?)
                }
                _ => AnySyncRoomEvent::Message(from_raw_json_value(&json)?),
            })
        }
    }
}
<|endoftext|>