<commit_before>use std::cmp::min;

use {class, Id, IdVector};
use super::{INSCopying, INSObject};

pub trait INSDictionary<K: INSObject, V> : INSObject {
	fn count(&self) -> uint {
		let result = unsafe {
			msg_send![self count]
		};
		result as uint
	}

	fn object_for<'a>(&'a self, key: &K) -> Option<&'a V> {
		unsafe {
			let obj = msg_send![self objectForKey:key.as_ptr()] as *V;
			obj.to_option()
		}
	}

	unsafe fn from_refs<T: INSCopying<K>>(keys: &[&T], vals: &[&V]) -> Id<Self> {
		let cls = class::<Self>();
		let count = min(keys.len(), vals.len());
		let obj = msg_send![cls alloc];
		let obj = msg_send![obj initWithObjects:vals.as_ptr()
		                                forKeys:keys.as_ptr()
		                                  count:count];
		Id::from_retained_ptr(obj as *Self)
	}

	fn from_keys_and_objects<T: INSCopying<K>>(keys: &[&T], vals: Vec<Id<V>>) -> Id<Self> {
		let vals_refs = vals.as_refs_slice();
		unsafe {
			INSDictionary::from_refs(keys, vals_refs)
		}
	}
}

object_struct!(NSDictionary<K, V>)

impl<K: INSObject, V> INSDictionary<K, V> for NSDictionary<K, V> { }

impl<K: INSObject, V> Collection for NSDictionary<K, V> {
	fn len(&self) -> uint {
		self.count()
	}
}

impl<K: INSObject, V> Map<K, V> for NSDictionary<K, V> {
	fn find<'a>(&'a self, key: &K) -> Option<&'a V> {
		self.object_for(key)
	}
}

#[cfg(test)]
mod tests {
	use {Id};
	use foundation::{INSObject, INSString, NSObject, NSString};
	use super::{INSDictionary, NSDictionary};

	fn sample_dict(key: &str) -> Id<NSDictionary<NSString, NSObject>> {
		let string: Id<NSString> = INSString::from_str(key);
		let obj: Id<NSObject> = INSObject::new();
		INSDictionary::from_keys_and_objects(&[&*string], vec![obj])
	}

	#[test]
	fn test_count() {
		let dict = sample_dict("abcd");
		assert!(dict.count() == 1);
	}

	#[test]
	fn test_object_for() {
		let dict = sample_dict("abcd");

		let string: Id<NSString> = INSString::from_str("abcd");
		assert!(dict.object_for(&*string).is_some());

		let string: Id<NSString> = INSString::from_str("abcde");
		assert!(dict.object_for(&*string).is_none());
	}
}
<commit_msg>Added all_keys and all_values to NSDictionary.<commit_after>use std::cmp::min;

use {class, Id, IdVector};
use super::{INSArray, INSCopying, INSObject, NSArray};

pub trait INSDictionary<K: INSObject, V: INSObject> : INSObject {
	fn count(&self) -> uint {
		let result = unsafe {
			msg_send![self count]
		};
		result as uint
	}

	fn object_for<'a>(&'a self, key: &K) -> Option<&'a V> {
		unsafe {
			let obj = msg_send![self objectForKey:key.as_ptr()] as *V;
			obj.to_option()
		}
	}

	fn all_keys<'a>(&'a self) -> Vec<&'a K> {
		let keys = unsafe {
			&*(msg_send![self allKeys] as *NSArray<K>)
		};
		keys.to_vec()
	}

	fn all_values<'a>(&'a self) -> Vec<&'a V> {
		let vals = unsafe {
			&*(msg_send![self allValues] as *NSArray<V>)
		};
		vals.to_vec()
	}

	unsafe fn from_refs<T: INSCopying<K>>(keys: &[&T], vals: &[&V]) -> Id<Self> {
		let cls = class::<Self>();
		let count = min(keys.len(), vals.len());
		let obj = msg_send![cls alloc];
		let obj = msg_send![obj initWithObjects:vals.as_ptr()
		                                forKeys:keys.as_ptr()
		                                  count:count];
		Id::from_retained_ptr(obj as *Self)
	}

	fn from_keys_and_objects<T: INSCopying<K>>(keys: &[&T], vals: Vec<Id<V>>) -> Id<Self> {
		let vals_refs = vals.as_refs_slice();
		unsafe {
			INSDictionary::from_refs(keys, vals_refs)
		}
	}
}

object_struct!(NSDictionary<K, V>)

impl<K: INSObject, V: INSObject> INSDictionary<K, V> for NSDictionary<K, V> { }

impl<K: INSObject, V: INSObject> Collection for NSDictionary<K, V> {
	fn len(&self) -> uint {
		self.count()
	}
}

impl<K: INSObject, V: INSObject> Map<K, V> for NSDictionary<K, V> {
	fn find<'a>(&'a self, key: &K) -> Option<&'a V> {
		self.object_for(key)
	}
}

#[cfg(test)]
mod tests {
	use {Id};
	use foundation::{INSObject, INSString, NSObject, NSString};
	use super::{INSDictionary, NSDictionary};

	fn sample_dict(key: &str) -> Id<NSDictionary<NSString, NSObject>> {
		let string: Id<NSString> = INSString::from_str(key);
		let obj: Id<NSObject> = INSObject::new();
		INSDictionary::from_keys_and_objects(&[&*string], vec![obj])
	}

	#[test]
	fn test_count() {
		let dict = sample_dict("abcd");
		assert!(dict.count() == 1);
	}

	#[test]
	fn test_object_for() {
		let dict = sample_dict("abcd");

		let string: Id<NSString> = INSString::from_str("abcd");
		assert!(dict.object_for(&*string).is_some());

		let string: Id<NSString> = INSString::from_str("abcde");
		assert!(dict.object_for(&*string).is_none());
	}

	#[test]
	fn test_all_keys() {
		let dict = sample_dict("abcd");
		let keys = dict.all_keys();

		assert!(keys.len() == 1);
		assert!(keys.get(0).as_str() == "abcd");
	}

	#[test]
	fn test_all_values() {
		let dict = sample_dict("abcd");
		let vals = dict.all_values();

		assert!(vals.len() == 1);
	}
}
<|endoftext|>


<commit_before>//! Endpoints for joining rooms.

use std::convert::TryFrom;
use std::error::Error;

use bodyparser;
use iron::status::Status;
use iron::{Chain, Handler, IronError, IronResult, Plugin, Request, Response};
use ruma_identifiers::UserId;

use config::Config;
use db::DB;
use error::{ApiError, MapApiError};
use middleware::{AccessTokenAuth, JsonRequest, MiddlewareChain, RoomIdParam};
use modifier::SerializableResponse;
use room::Room;
use room_membership::{RoomMembership, RoomMembershipOptions};
use user::User;

/// The `/rooms/:room_id/join` endpoint.
pub struct JoinRoom;

#[derive(Debug, Serialize)]
struct JoinRoomResponse {
    room_id: String,
}

middleware_chain!(JoinRoom, [JsonRequest, RoomIdParam, AccessTokenAuth]);

impl Handler for JoinRoom {
    fn handle(&self, request: &mut Request) -> IronResult<Response> {
        let user = request.extensions
            .get::<User>()
            .expect("AccessTokenAuth should ensure a user")
            .clone();

        let connection = DB::from_request(request)?;
        let config = Config::from_request(request)?;

        let room_id = request.extensions.get::<RoomIdParam>()
            .expect("Should have been required by RoomIdParam.")
            .clone();

        let room_membership_options = RoomMembershipOptions {
            room_id: room_id.clone(),
            user_id: user.id.clone(),
            sender: user.id,
            membership: "join".to_string(),
        };

        let room_membership = RoomMembership::upsert(
            &connection,
            &config.domain,
            room_membership_options
        )?;

        let response = JoinRoomResponse { room_id: room_membership.room_id.to_string() };

        Ok(Response::with((Status::Ok, SerializableResponse(response))))
    }
}

/// The `/rooms/:room_id/invite` endpoint.
#[derive(Debug)]
pub struct InviteToRoom;

#[derive(Clone, Debug, Deserialize)]
struct InviteToRoomRequest {
    pub user_id: String,
}

middleware_chain!(InviteToRoom, [JsonRequest, RoomIdParam, AccessTokenAuth]);

impl Handler for InviteToRoom {
    fn handle(&self, request: &mut Request) -> IronResult<Response> {
        let room_id = request.extensions.get::<RoomIdParam>()
            .expect("RoomIdParam should ensure a room_id").clone();

        let inviter = request.extensions.get::<User>()
            .expect("AccessTokenAuth should ensure a user").clone();

        let invitee_id = match request.get::<bodyparser::Struct<InviteToRoomRequest>>() {
            Ok(Some(req)) => UserId::try_from(&req.user_id).map_api_err(|err| {
                ApiError::invalid_param("user_id", err.description())
            }),
            Ok(None) | Err(_) => Err(ApiError::missing_param("user_id"))
        }?;

        let connection = DB::from_request(request)?;
        let config = Config::from_request(request)?;

        // Check if the invitee exists.
        User::find_by_uid(&connection, &invitee_id)
            .map_err(IronError::from)?;

        // Check if the room exists.
        Room::find(&connection, &room_id)
            .map_err(IronError::from)?;

        // Check if the inviter has joined the room.
        let unauthorized_err = ApiError::unauthorized(
            Some("The inviter hasn't joined the room yet")
        );

        RoomMembership::find(&connection, &room_id, &inviter.id)
            .and_then(|membership| match membership {
                Some(entry) => match entry.membership.as_ref() {
                    "join" => Ok(()),
                    _ => Err(unauthorized_err)
                },
                None => Err(unauthorized_err)
            })?;

        let invitee_membership = RoomMembership::find(&connection, &room_id, &invitee_id)?;
        let new_membership_options = RoomMembershipOptions {
            room_id: room_id,
            user_id: invitee_id,
            sender: inviter.id,
            membership: "invite".to_string(),
        };

        match invitee_membership {
            Some(mut entry) => match entry.membership.as_ref() {
                "invite" => Ok(()),
                "ban" => Err(ApiError::unauthorized(
                    Some("The invited user is banned from the room")
                )),
                "join" => Err(ApiError::unauthorized(
                    Some("The invited user has already joined")
                )),
                _ => {
                    entry.update(
                        &connection,
                        &config.domain,
                        new_membership_options
                    )?;

                    Ok(())
                }
            },
            None => {
                RoomMembership::create(
                    &connection,
                    &config.domain,
                    new_membership_options
                )?;

                Ok(())
            }
        }?;

        Ok(Response::with(Status::Ok))
    }
}

#[cfg(test)]
mod tests {
    use test::Test;
    use iron::status::Status;

    #[test]
    fn join_own_public_room() {
        let test = Test::new();
        let access_token = test.create_access_token();
        let room_id = test.create_public_room(&access_token);

        let room_join_path = format!(
            "/_matrix/client/r0/rooms/{}/join?access_token={}",
            room_id,
            access_token
        );

        let response = test.post(&room_join_path, r"{}");

        assert_eq!(response.status, Status::Ok);
        assert!(response.json().find("room_id").unwrap().as_str().is_some());
    }

    #[test]
    fn join_other_public_room() {
        let test = Test::new();
        let carl_token = test.create_access_token_with_username("carl");
        let mark_token = test.create_access_token_with_username("mark");

        let room_id = test.create_public_room(&carl_token);

        let room_join_path = format!(
            "/_matrix/client/r0/rooms/{}/join?access_token={}",
            room_id,
            mark_token
        );

        let response = test.post(&room_join_path, r"{}");

        assert_eq!(response.status, Status::Ok);
        assert!(response.json().find("room_id").unwrap().as_str().is_some());
    }

    #[test]
    fn join_own_private_room() {
        let test = Test::new();
        let access_token = test.create_access_token();
        let room_id = test.create_private_room(&access_token);

        let room_join_path = format!(
            "/_matrix/client/r0/rooms/{}/join?access_token={}",
            room_id,
            access_token
        );

        let response = test.post(&room_join_path, r"{}");

        assert_eq!(response.status, Status::Ok);
        assert!(response.json().find("room_id").unwrap().as_str().is_some());
    }

    #[test]
    fn join_other_private_room() {
        let test = Test::new();
        let carl_token = test.create_access_token_with_username("carl");
        let mark_token = test.create_access_token_with_username("mark");

        let body = r#"{"visibility": "private", "invite": ["@mark:ruma.test"]}"#;
        let room_id = test.create_room_with_params(&carl_token, body);

        let room_join_path = format!(
            "/_matrix/client/r0/rooms/{}/join?access_token={}",
            room_id,
            mark_token
        );

        let response = test.post(&room_join_path, r"{}");

        assert_eq!(response.status, Status::Ok);
        assert!(response.json().find("room_id").unwrap().as_str().is_some());
    }

    #[test]
    fn join_other_private_room_without_invite() {
        let test = Test::new();
        let bob_token = test.create_access_token_with_username("bob");
        let alice_token = test.create_access_token_with_username("alice");

        let room_id = test.create_private_room(&bob_token);

        let room_join_path = format!(
            "/_matrix/client/r0/rooms/{}/join?access_token={}",
            room_id,
            alice_token
        );

        let response = test.post(&room_join_path, r"{}");

        assert_eq!(response.status, Status::Forbidden);
    }

    #[test]
    fn invite_to_room() {
        let test = Test::new();
        let bob_token = test.create_access_token_with_username("bob");
        let alice_token = test.create_access_token_with_username("alice");

        let room_id = test.create_private_room(&bob_token);

        let response = test.invite(&bob_token, &room_id, "@alice:ruma.test");

        assert_eq!(response.status, Status::Ok);

        assert!(test.join_room(&alice_token, &room_id).status.is_success());
    }

    #[test]
    fn invite_before_joining() {
        let test = Test::new();

        let carl_token = test.create_access_token_with_username("carl");
        let bob_token = test.create_access_token_with_username("bob");
        let _ = test.create_access_token_with_username("alice");

        // Carl invites Bob.
        let body = r#"{"visibility": "private", "invite": ["@bob:ruma.test"]}"#;
        let room_id = test.create_room_with_params(&carl_token, body);

        // Bob invites Alice before joining.
        let response = test.invite(&bob_token, &room_id, "@alice:ruma.test");

        assert_eq!(response.status, Status::Forbidden);
        assert_eq!(
            response.json().find("error").unwrap().as_str().unwrap(),
            "The inviter hasn't joined the room yet"
        );
    }

    #[test]
    fn invite_without_user_id() {
        let test = Test::new();
        let carl_token = test.create_access_token_with_username("carl");

        let room_id = test.create_private_room(&carl_token);
        let invite_path = format!(
            "/_matrix/client/r0/rooms/{}/invite?access_token={}",
            room_id,
            carl_token
        );

        // Empty body.
        let response = test.post(&invite_path, "{}");

        assert_eq!(response.status, Status::BadRequest);
        assert_eq!(
            response.json().find("errcode").unwrap().as_str().unwrap(),
            "M_MISSING_PARAM"
        );
        assert_eq!(
            response.json().find("error").unwrap().as_str().unwrap(),
            "Missing value for required parameter: user_id."
        );
    }

    #[test]
    fn invitee_does_not_exist() {
        let test = Test::new();
        let carl_token = test.create_access_token_with_username("carl");

        let room_id = test.create_private_room(&carl_token);

        // User 'mark' does not exist.
        let response = test.invite(&carl_token, &room_id, "@mark:ruma.test");

        assert_eq!(response.status, Status::NotFound);
        assert_eq!(
            response.json().find("error").unwrap().as_str().unwrap(),
            "The user @mark:ruma.test was not found on this server"
        );
    }

    #[test]
    fn invitee_is_invalid() {
        let test = Test::new();
        let carl_token = test.create_access_token();
        let room_id = test.create_private_room(&carl_token);

        let response = test.invite(&carl_token, &room_id, "mark.ruma.test");

        assert_eq!(response.status, Status::BadRequest);
        assert_eq!(
            response.json().find("errcode").unwrap().as_str().unwrap(),
            "IO_RUMA_INVALID_PARAM"
        );
    }

    #[test]
    fn invitee_is_already_invited() {
        let test = Test::new();
        let bob_token = test.create_access_token_with_username("bob");
        let _ = test.create_access_token_with_username("alice");

        let room_id = test.create_room_with_params(
            &bob_token,
            r#"{"visibility": "private", "invite": ["@alice:ruma.test"]}"#
        );

        let response = test.invite(&bob_token, &room_id, "@alice:ruma.test");

        assert_eq!(response.status, Status::Ok);
    }

    #[test]
    fn invitee_has_already_joined() {
        let test = Test::new();
        let bob_token = test.create_access_token_with_username("bob");
        let alice_token = test.create_access_token_with_username("alice");

        let room_id = test.create_room_with_params(
            &bob_token,
            r#"{"visibility": "private", "invite": ["@alice:ruma.test"]}"#
        );

        assert!(test.join_room(&alice_token, &room_id).status.is_success());

        let response = test.invite(&bob_token, &room_id, "@alice:ruma.test");

        assert_eq!(response.status, Status::Forbidden);
        assert_eq!(
            response.json().find("error").unwrap().as_str().unwrap(),
            "The invited user has already joined"
        );
    }

    #[test]
    fn room_does_not_exist() {
        let test = Test::new();
        let bob_token = test.create_access_token_with_username("bob");
        let _ = test.create_access_token_with_username("alice");

        let response = test.invite(&bob_token, "!random:ruma.test", "@alice:ruma.test");

        assert_eq!(response.status, Status::NotFound);
        assert_eq!(
            response.json().find("error").unwrap().as_str().unwrap(),
            "The room was not found on this server"
        );
    }
}
<commit_msg>invite: Use a transaction to read the current room, user and membership<commit_after>//! Endpoints for joining rooms.

use std::convert::TryFrom;
use std::error::Error;

use bodyparser;
use diesel::Connection;
use iron::status::Status;
use iron::{Chain, Handler, IronResult, Plugin, Request, Response};
use ruma_identifiers::UserId;

use config::Config;
use db::DB;
use error::{ApiError, MapApiError};
use middleware::{AccessTokenAuth, JsonRequest, MiddlewareChain, RoomIdParam};
use modifier::SerializableResponse;
use room::Room;
use room_membership::{RoomMembership, RoomMembershipOptions};
use user::User;

/// The `/rooms/:room_id/join` endpoint.
pub struct JoinRoom;

#[derive(Debug, Serialize)]
struct JoinRoomResponse {
    room_id: String,
}

middleware_chain!(JoinRoom, [JsonRequest, RoomIdParam, AccessTokenAuth]);

impl Handler for JoinRoom {
    fn handle(&self, request: &mut Request) -> IronResult<Response> {
        let user = request.extensions
            .get::<User>()
            .expect("AccessTokenAuth should ensure a user")
            .clone();

        let connection = DB::from_request(request)?;
        let config = Config::from_request(request)?;

        let room_id = request.extensions.get::<RoomIdParam>()
            .expect("Should have been required by RoomIdParam.")
            .clone();

        let room_membership_options = RoomMembershipOptions {
            room_id: room_id.clone(),
            user_id: user.id.clone(),
            sender: user.id,
            membership: "join".to_string(),
        };

        let room_membership = RoomMembership::upsert(
            &connection,
            &config.domain,
            room_membership_options
        )?;

        let response = JoinRoomResponse { room_id: room_membership.room_id.to_string() };

        Ok(Response::with((Status::Ok, SerializableResponse(response))))
    }
}

/// The `/rooms/:room_id/invite` endpoint.
#[derive(Debug)]
pub struct InviteToRoom;

#[derive(Clone, Debug, Deserialize)]
struct InviteToRoomRequest {
    pub user_id: String,
}

middleware_chain!(InviteToRoom, [JsonRequest, RoomIdParam, AccessTokenAuth]);

impl Handler for InviteToRoom {
    fn handle(&self, request: &mut Request) -> IronResult<Response> {
        let room_id = request.extensions.get::<RoomIdParam>()
            .expect("RoomIdParam should ensure a room_id").clone();

        let inviter = request.extensions.get::<User>()
            .expect("AccessTokenAuth should ensure a user").clone();

        let invitee_id = match request.get::<bodyparser::Struct<InviteToRoomRequest>>() {
            Ok(Some(req)) => UserId::try_from(&req.user_id).map_api_err(|err| {
                ApiError::invalid_param("user_id", err.description())
            }),
            Ok(None) | Err(_) => Err(ApiError::missing_param("user_id"))
        }?;

        let connection = DB::from_request(request)?;
        let config = Config::from_request(request)?;

        let invitee_membership = connection.transaction::<Option<RoomMembership>, ApiError, _>(|| {
            // Check if the invitee exists.
            User::find_by_uid(&connection, &invitee_id)?;

            // Check if the room exists.
            Room::find(&connection, &room_id)?;

            let unauthorized_err = ApiError::unauthorized(
                Some("The inviter hasn't joined the room yet")
            );

            // Check if the inviter has joined the room.
            RoomMembership::find(&connection, &room_id, &inviter.id)
                .and_then(|membership| match membership {
                    Some(entry) => match entry.membership.as_ref() {
                        "join" => Ok(()),
                        _ => Err(unauthorized_err)
                    },
                    None => Err(unauthorized_err)
                })?;

            let membership = RoomMembership::find(&connection, &room_id, &invitee_id)?;

            Ok(membership)
        }).map_err(ApiError::from)?;

        let new_membership_options = RoomMembershipOptions {
            room_id: room_id,
            user_id: invitee_id,
            sender: inviter.id,
            membership: "invite".to_string(),
        };

        match invitee_membership {
            Some(mut entry) => match entry.membership.as_ref() {
                "invite" => Ok(()),
                "ban" => Err(ApiError::unauthorized(
                    Some("The invited user is banned from the room")
                )),
                "join" => Err(ApiError::unauthorized(
                    Some("The invited user has already joined")
                )),
                _ => {
                    entry.update(
                        &connection,
                        &config.domain,
                        new_membership_options
                    )?;

                    Ok(())
                }
            },
            None => {
                RoomMembership::create(
                    &connection,
                    &config.domain,
                    new_membership_options
                )?;

                Ok(())
            }
        }?;

        Ok(Response::with(Status::Ok))
    }
}

#[cfg(test)]
mod tests {
    use test::Test;
    use iron::status::Status;

    #[test]
    fn join_own_public_room() {
        let test = Test::new();
        let access_token = test.create_access_token();
        let room_id = test.create_public_room(&access_token);

        let room_join_path = format!(
            "/_matrix/client/r0/rooms/{}/join?access_token={}",
            room_id,
            access_token
        );

        let response = test.post(&room_join_path, r"{}");

        assert_eq!(response.status, Status::Ok);
        assert!(response.json().find("room_id").unwrap().as_str().is_some());
    }

    #[test]
    fn join_other_public_room() {
        let test = Test::new();
        let carl_token = test.create_access_token_with_username("carl");
        let mark_token = test.create_access_token_with_username("mark");

        let room_id = test.create_public_room(&carl_token);

        let room_join_path = format!(
            "/_matrix/client/r0/rooms/{}/join?access_token={}",
            room_id,
            mark_token
        );

        let response = test.post(&room_join_path, r"{}");

        assert_eq!(response.status, Status::Ok);
        assert!(response.json().find("room_id").unwrap().as_str().is_some());
    }

    #[test]
    fn join_own_private_room() {
        let test = Test::new();
        let access_token = test.create_access_token();
        let room_id = test.create_private_room(&access_token);

        let room_join_path = format!(
            "/_matrix/client/r0/rooms/{}/join?access_token={}",
            room_id,
            access_token
        );

        let response = test.post(&room_join_path, r"{}");

        assert_eq!(response.status, Status::Ok);
        assert!(response.json().find("room_id").unwrap().as_str().is_some());
    }

    #[test]
    fn join_other_private_room() {
        let test = Test::new();
        let carl_token = test.create_access_token_with_username("carl");
        let mark_token = test.create_access_token_with_username("mark");

        let body = r#"{"visibility": "private", "invite": ["@mark:ruma.test"]}"#;
        let room_id = test.create_room_with_params(&carl_token, body);

        let room_join_path = format!(
            "/_matrix/client/r0/rooms/{}/join?access_token={}",
            room_id,
            mark_token
        );

        let response = test.post(&room_join_path, r"{}");

        assert_eq!(response.status, Status::Ok);
        assert!(response.json().find("room_id").unwrap().as_str().is_some());
    }

    #[test]
    fn join_other_private_room_without_invite() {
        let test = Test::new();
        let bob_token = test.create_access_token_with_username("bob");
        let alice_token = test.create_access_token_with_username("alice");

        let room_id = test.create_private_room(&bob_token);

        let room_join_path = format!(
            "/_matrix/client/r0/rooms/{}/join?access_token={}",
            room_id,
            alice_token
        );

        let response = test.post(&room_join_path, r"{}");

        assert_eq!(response.status, Status::Forbidden);
    }

    #[test]
    fn invite_to_room() {
        let test = Test::new();
        let bob_token = test.create_access_token_with_username("bob");
        let alice_token = test.create_access_token_with_username("alice");

        let room_id = test.create_private_room(&bob_token);

        let response = test.invite(&bob_token, &room_id, "@alice:ruma.test");

        assert_eq!(response.status, Status::Ok);

        assert!(test.join_room(&alice_token, &room_id).status.is_success());
    }

    #[test]
    fn invite_before_joining() {
        let test = Test::new();

        let carl_token = test.create_access_token_with_username("carl");
        let bob_token = test.create_access_token_with_username("bob");
        let _ = test.create_access_token_with_username("alice");

        // Carl invites Bob.
        let body = r#"{"visibility": "private", "invite": ["@bob:ruma.test"]}"#;
        let room_id = test.create_room_with_params(&carl_token, body);

        // Bob invites Alice before joining.
        let response = test.invite(&bob_token, &room_id, "@alice:ruma.test");

        assert_eq!(response.status, Status::Forbidden);
        assert_eq!(
            response.json().find("error").unwrap().as_str().unwrap(),
            "The inviter hasn't joined the room yet"
        );
    }

    #[test]
    fn invite_without_user_id() {
        let test = Test::new();
        let carl_token = test.create_access_token_with_username("carl");

        let room_id = test.create_private_room(&carl_token);
        let invite_path = format!(
            "/_matrix/client/r0/rooms/{}/invite?access_token={}",
            room_id,
            carl_token
        );

        // Empty body.
        let response = test.post(&invite_path, "{}");

        assert_eq!(response.status, Status::BadRequest);
        assert_eq!(
            response.json().find("errcode").unwrap().as_str().unwrap(),
            "M_MISSING_PARAM"
        );
        assert_eq!(
            response.json().find("error").unwrap().as_str().unwrap(),
            "Missing value for required parameter: user_id."
        );
    }

    #[test]
    fn invitee_does_not_exist() {
        let test = Test::new();
        let carl_token = test.create_access_token_with_username("carl");

        let room_id = test.create_private_room(&carl_token);

        // User 'mark' does not exist.
        let response = test.invite(&carl_token, &room_id, "@mark:ruma.test");

        assert_eq!(response.status, Status::NotFound);
        assert_eq!(
            response.json().find("error").unwrap().as_str().unwrap(),
            "The user @mark:ruma.test was not found on this server"
        );
    }

    #[test]
    fn invitee_is_invalid() {
        let test = Test::new();
        let carl_token = test.create_access_token();
        let room_id = test.create_private_room(&carl_token);

        let response = test.invite(&carl_token, &room_id, "mark.ruma.test");

        assert_eq!(response.status, Status::BadRequest);
        assert_eq!(
            response.json().find("errcode").unwrap().as_str().unwrap(),
            "IO_RUMA_INVALID_PARAM"
        );
    }

    #[test]
    fn invitee_is_already_invited() {
        let test = Test::new();
        let bob_token = test.create_access_token_with_username("bob");
        let _ = test.create_access_token_with_username("alice");

        let room_id = test.create_room_with_params(
            &bob_token,
            r#"{"visibility": "private", "invite": ["@alice:ruma.test"]}"#
        );

        let response = test.invite(&bob_token, &room_id, "@alice:ruma.test");

        assert_eq!(response.status, Status::Ok);
    }

    #[test]
    fn invitee_has_already_joined() {
        let test = Test::new();
        let bob_token = test.create_access_token_with_username("bob");
        let alice_token = test.create_access_token_with_username("alice");

        let room_id = test.create_room_with_params(
            &bob_token,
            r#"{"visibility": "private", "invite": ["@alice:ruma.test"]}"#
        );

        assert!(test.join_room(&alice_token, &room_id).status.is_success());

        let response = test.invite(&bob_token, &room_id, "@alice:ruma.test");

        assert_eq!(response.status, Status::Forbidden);
        assert_eq!(
            response.json().find("error").unwrap().as_str().unwrap(),
            "The invited user has already joined"
        );
    }

    #[test]
    fn room_does_not_exist() {
        let test = Test::new();
        let bob_token = test.create_access_token_with_username("bob");
        let _ = test.create_access_token_with_username("alice");

        let response = test.invite(&bob_token, "!random:ruma.test", "@alice:ruma.test");

        assert_eq!(response.status, Status::NotFound);
        assert_eq!(
            response.json().find("error").unwrap().as_str().unwrap(),
            "The room was not found on this server"
        );
    }
}
<|endoftext|>


<commit_before>use byteorder::{LittleEndian, WriteBytesExt};
use std::io::{self, Write};

use crate::color;
use crate::error::{ImageError, ImageResult};
use crate::image::ImageEncoder;

const BITMAPFILEHEADER_SIZE: u32 = 14;
const BITMAPINFOHEADER_SIZE: u32 = 40;
const BITMAPV4HEADER_SIZE: u32 = 108;

/// The representation of a BMP encoder.
pub struct BMPEncoder<'a, W: 'a> {
    writer: &'a mut W,
}

impl<'a, W: Write + 'a> BMPEncoder<'a, W> {
    /// Create a new encoder that writes its output to ```w```.
    pub fn new(w: &'a mut W) -> Self {
        BMPEncoder { writer: w }
    }

    /// Encodes the image ```image```
    /// that has dimensions ```width``` and ```height```
    /// and ```ColorType``` ```c```.
    pub fn encode(
        &mut self,
        image: &[u8],
        width: u32,
        height: u32,
        c: color::ColorType,
    ) -> ImageResult<()> {
        let bmp_header_size = BITMAPFILEHEADER_SIZE;

        let (dib_header_size, written_pixel_size, palette_color_count) = get_pixel_info(c)?;
        let row_pad_size = (4 - (width * written_pixel_size) % 4) % 4; // each row must be padded to a multiple of 4 bytes
        let image_size = width
            .checked_mul(height)
            .ok_or(ImageError::DimensionError)?
            .checked_mul(written_pixel_size)
            .ok_or(ImageError::DimensionError)?
            .checked_add(height * row_pad_size)
            .ok_or(ImageError::DimensionError)?;
        let palette_size = palette_color_count * 4; // all palette colors are BGRA
        let file_size = bmp_header_size + dib_header_size + palette_size + image_size;

        // write BMP header
        self.writer.write_u8(b'B')?;
        self.writer.write_u8(b'M')?;
        self.writer.write_u32::<LittleEndian>(file_size)?; // file size
        self.writer.write_u16::<LittleEndian>(0)?; // reserved 1
        self.writer.write_u16::<LittleEndian>(0)?; // reserved 2
        self.writer
            .write_u32::<LittleEndian>(bmp_header_size + dib_header_size + palette_size)?; // image data offset

        // write DIB header
        self.writer.write_u32::<LittleEndian>(dib_header_size)?;
        self.writer.write_i32::<LittleEndian>(width as i32)?;
        self.writer.write_i32::<LittleEndian>(height as i32)?;
        self.writer.write_u16::<LittleEndian>(1)?; // color planes
        self.writer
            .write_u16::<LittleEndian>((written_pixel_size * 8) as u16)?; // bits per pixel
        if dib_header_size >= BITMAPV4HEADER_SIZE {
            // Assume BGRA32
            self.writer.write_u32::<LittleEndian>(3)?; // compression method - bitfields
        } else {
            self.writer.write_u32::<LittleEndian>(0)?; // compression method - no compression
        }
        self.writer.write_u32::<LittleEndian>(image_size)?;
        self.writer.write_i32::<LittleEndian>(0)?; // horizontal ppm
        self.writer.write_i32::<LittleEndian>(0)?; // vertical ppm
        self.writer.write_u32::<LittleEndian>(palette_color_count)?;
        self.writer.write_u32::<LittleEndian>(0)?; // all colors are important
        if dib_header_size >= BITMAPV4HEADER_SIZE {
            // Assume BGRA32
            self.writer.write_u32::<LittleEndian>(0xff << 16)?; // red mask
            self.writer.write_u32::<LittleEndian>(0xff << 8)?; // green mask
            self.writer.write_u32::<LittleEndian>(0xff << 0)?; // blue mask
            self.writer.write_u32::<LittleEndian>(0xff << 24)?; // alpha mask
            self.writer.write_u32::<LittleEndian>(0x73524742)?; // colorspace - sRGB

            // endpoints (3x3) and gamma (3)
            for _ in 0..12 {
                self.writer.write_u32::<LittleEndian>(0)?;
            }
        }

        // write image data
        match c {
            color::ColorType::Rgb8 => {
                self.encode_rgb(image, width, height, row_pad_size, 3)?
            }
            color::ColorType::Rgba8 => {
                self.encode_rgba(image, width, height, row_pad_size, 4)?
            }
            color::ColorType::L8 => {
                self.encode_gray(image, width, height, row_pad_size, 1)?
            }
            color::ColorType::La8 => {
                self.encode_gray(image, width, height, row_pad_size, 2)?
            }
            _ => {
                return Err(ImageError::IoError(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    &get_unsupported_error_message(c)[..],
                )))
            }
        }

        Ok(())
    }

    fn encode_rgb(
        &mut self,
        image: &[u8],
        width: u32,
        height: u32,
        row_pad_size: u32,
        bytes_per_pixel: u32,
    ) -> io::Result<()> {
        let x_stride = bytes_per_pixel;
        let y_stride = width * x_stride;
        for row in 0..height {
            // from the bottom up
            let row_start = (height - row - 1) * y_stride;
            for col in 0..width {
                let pixel_start = (row_start + (col * x_stride)) as usize;
                let r = image[pixel_start];
                let g = image[pixel_start + 1];
                let b = image[pixel_start + 2];
                // written as BGR
                self.writer.write_u8(b)?;
                self.writer.write_u8(g)?;
                self.writer.write_u8(r)?;
                // alpha is never written as it's not widely supported
            }

            self.write_row_pad(row_pad_size)?;
        }

        Ok(())
    }

    fn encode_rgba(
        &mut self,
        image: &[u8],
        width: u32,
        height: u32,
        row_pad_size: u32,
        bytes_per_pixel: u32,
    ) -> io::Result<()> {
        let x_stride = bytes_per_pixel;
        let y_stride = width * x_stride;
        for row in 0..height {
            // from the bottom up
            let row_start = (height - row - 1) * y_stride;
            for col in 0..width {
                let pixel_start = (row_start + (col * x_stride)) as usize;
                let r = image[pixel_start];
                let g = image[pixel_start + 1];
                let b = image[pixel_start + 2];
                let a = image[pixel_start + 3];
                // written as BGRA
                self.writer.write_u8(b)?;
                self.writer.write_u8(g)?;
                self.writer.write_u8(r)?;
                self.writer.write_u8(a)?;
            }

            self.write_row_pad(row_pad_size)?;
        }

        Ok(())
    }

    fn encode_gray(
        &mut self,
        image: &[u8],
        width: u32,
        height: u32,
        row_pad_size: u32,
        bytes_per_pixel: u32,
    ) -> io::Result<()> {
        // write grayscale palette
        for val in 0..256 {
            // each color is written as BGRA, where A is always 0 and since only grayscale is being written, B = G = R = index
            let val = val as u8;
            self.writer.write_u8(val)?;
            self.writer.write_u8(val)?;
            self.writer.write_u8(val)?;
            self.writer.write_u8(0)?;
        }

        // write image data
        let x_stride = bytes_per_pixel;
        let y_stride = width * x_stride;
        for row in 0..height {
            // from the bottom up
            let row_start = (height - row - 1) * y_stride;
            for col in 0..width {
                let pixel_start = (row_start + (col * x_stride)) as usize;
                // color value is equal to the palette index
                self.writer.write_u8(image[pixel_start])?;
                // alpha is never written as it's not widely supported
            }

            self.write_row_pad(row_pad_size)?;
        }

        Ok(())
    }

    fn write_row_pad(&mut self, row_pad_size: u32) -> io::Result<()> {
        for _ in 0..row_pad_size {
            self.writer.write_u8(0)?;
        }

        Ok(())
    }
}

impl<'a, W: Write> ImageEncoder for BMPEncoder<'a, W> {
    fn write_image(
        mut self,
        buf: &[u8],
        width: u32,
        height: u32,
        color_type: color::ColorType,
    ) -> ImageResult<()> {
        self.encode(buf, width, height, color_type)
    }
}

fn get_unsupported_error_message(c: color::ColorType) -> String {
    format!(
        "Unsupported color type {:?}.  Supported types: RGB(8), RGBA(8), Gray(8), GrayA(8).",
        c
    )
}

/// Returns a tuple representing: (dib header size, written pixel size, palette color count).
fn get_pixel_info(c: color::ColorType) -> io::Result<(u32, u32, u32)> {
    let sizes = match c {
        color::ColorType::Rgb8 => (BITMAPINFOHEADER_SIZE, 3, 0),
        color::ColorType::Rgba8 => (BITMAPV4HEADER_SIZE, 4, 0),
        color::ColorType::L8 => (BITMAPINFOHEADER_SIZE, 1, 256),
        color::ColorType::La8 => (BITMAPINFOHEADER_SIZE, 1, 256),
        _ => {
            return Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                &get_unsupported_error_message(c)[..],
            ))
        }
    };

    Ok(sizes)
}

#[cfg(test)]
mod tests {
    use super::super::BmpDecoder;
    use super::BMPEncoder;
    use crate::color::ColorType;
    use crate::image::ImageDecoder;
    use std::io::Cursor;

    fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec<u8> {
        let mut encoded_data = Vec::new();
        {
            let mut encoder = BMPEncoder::new(&mut encoded_data);
            encoder
                .encode(&image, width, height, c)
                .expect("could not encode image");
        }

        let decoder = BmpDecoder::new(Cursor::new(&encoded_data)).expect("failed to decode");

        let mut buf = vec![0; decoder.total_bytes() as usize];
        decoder.read_image(&mut buf).expect("failed to decode");
        buf
    }

    #[test]
    fn round_trip_single_pixel_rgb() {
        let image = [255u8, 0, 0]; // single red pixel
        let decoded = round_trip_image(&image, 1, 1, ColorType::Rgb8);
        assert_eq!(3, decoded.len());
        assert_eq!(255, decoded[0]);
        assert_eq!(0, decoded[1]);
        assert_eq!(0, decoded[2]);
    }

    #[test]
    fn huge_files_return_error() {
        let mut encoded_data = Vec::new();
        let image = vec![0u8; 3 * 40_000 * 40_000]; // 40_000x40_000 pixels, 3 bytes per pixel, allocated on the heap
        let mut encoder = BMPEncoder::new(&mut encoded_data);
        let result = encoder.encode(&image, 40_000, 40_000, ColorType::Rgb8);
        assert!(result.is_err());
    }

    #[test]
    fn round_trip_single_pixel_rgba() {
        let image = [1, 2, 3, 4];
        let decoded = round_trip_image(&image, 1, 1, ColorType::Rgba8);
        assert_eq!(&decoded[..], &image[..]);
    }

    #[test]
    fn round_trip_3px_rgb() {
        let image = [0u8; 3 * 3 * 3]; // 3x3 pixels, 3 bytes per pixel
        let _decoded = round_trip_image(&image, 3, 3, ColorType::Rgb8);
    }

    #[test]
    fn round_trip_gray() {
        let image = [0u8, 1, 2]; // 3 pixels
        let decoded = round_trip_image(&image, 3, 1, ColorType::L8);
        // should be read back as 3 RGB pixels
        assert_eq!(9, decoded.len());
        assert_eq!(0, decoded[0]);
        assert_eq!(0, decoded[1]);
        assert_eq!(0, decoded[2]);
        assert_eq!(1, decoded[3]);
        assert_eq!(1, decoded[4]);
        assert_eq!(1, decoded[5]);
        assert_eq!(2, decoded[6]);
        assert_eq!(2, decoded[7]);
        assert_eq!(2, decoded[8]);
    }

    #[test]
    fn round_trip_graya() {
        let image = [0u8, 0, 1, 0, 2, 0]; // 3 pixels, each with an alpha channel
        let decoded = round_trip_image(&image, 1, 3, ColorType::La8);
        // should be read back as 3 RGB pixels
        assert_eq!(9, decoded.len());
        assert_eq!(0, decoded[0]);
        assert_eq!(0, decoded[1]);
        assert_eq!(0, decoded[2]);
        assert_eq!(1, decoded[3]);
        assert_eq!(1, decoded[4]);
        assert_eq!(1, decoded[5]);
        assert_eq!(2, decoded[6]);
        assert_eq!(2, decoded[7]);
        assert_eq!(2, decoded[8]);
    }
}
<commit_msg>Use detailed errors for BMP encoder<commit_after>use byteorder::{LittleEndian, WriteBytesExt};
use std::io::{self, Write};

use crate::color;
use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind};
use crate::image::ImageEncoder;

const BITMAPFILEHEADER_SIZE: u32 = 14;
const BITMAPINFOHEADER_SIZE: u32 = 40;
const BITMAPV4HEADER_SIZE: u32 = 108;

/// The representation of a BMP encoder.
pub struct BMPEncoder<'a, W: 'a> {
    writer: &'a mut W,
}

impl<'a, W: Write + 'a> BMPEncoder<'a, W> {
    /// Create a new encoder that writes its output to ```w```.
    pub fn new(w: &'a mut W) -> Self {
        BMPEncoder { writer: w }
    }

    /// Encodes the image ```image```
    /// that has dimensions ```width``` and ```height```
    /// and ```ColorType``` ```c```.
    pub fn encode(
        &mut self,
        image: &[u8],
        width: u32,
        height: u32,
        c: color::ColorType,
    ) -> ImageResult<()> {
        let bmp_header_size = BITMAPFILEHEADER_SIZE;

        let (dib_header_size, written_pixel_size, palette_color_count) = get_pixel_info(c)?;
        let row_pad_size = (4 - (width * written_pixel_size) % 4) % 4; // each row must be padded to a multiple of 4 bytes
        let image_size = width
            .checked_mul(height)
            .and_then(|v| v.checked_mul(written_pixel_size))
            .and_then(|v| v.checked_add(height * row_pad_size))
            .ok_or_else(|| {
                ImageError::Parameter(ParameterError::from_kind(
                    ParameterErrorKind::DimensionMismatch,
                ))
            })?;
        let palette_size = palette_color_count * 4; // all palette colors are BGRA
        let file_size = bmp_header_size + dib_header_size + palette_size + image_size;

        // write BMP header
        self.writer.write_u8(b'B')?;
        self.writer.write_u8(b'M')?;
        self.writer.write_u32::<LittleEndian>(file_size)?; // file size
        self.writer.write_u16::<LittleEndian>(0)?; // reserved 1
        self.writer.write_u16::<LittleEndian>(0)?; // reserved 2
        self.writer
            .write_u32::<LittleEndian>(bmp_header_size + dib_header_size + palette_size)?; // image data offset

        // write DIB header
        self.writer.write_u32::<LittleEndian>(dib_header_size)?;
        self.writer.write_i32::<LittleEndian>(width as i32)?;
        self.writer.write_i32::<LittleEndian>(height as i32)?;
        self.writer.write_u16::<LittleEndian>(1)?; // color planes
        self.writer
            .write_u16::<LittleEndian>((written_pixel_size * 8) as u16)?; // bits per pixel
        if dib_header_size >= BITMAPV4HEADER_SIZE {
            // Assume BGRA32
            self.writer.write_u32::<LittleEndian>(3)?; // compression method - bitfields
        } else {
            self.writer.write_u32::<LittleEndian>(0)?; // compression method - no compression
        }
        self.writer.write_u32::<LittleEndian>(image_size)?;
        self.writer.write_i32::<LittleEndian>(0)?; // horizontal ppm
        self.writer.write_i32::<LittleEndian>(0)?; // vertical ppm
        self.writer.write_u32::<LittleEndian>(palette_color_count)?;
        self.writer.write_u32::<LittleEndian>(0)?; // all colors are important
        if dib_header_size >= BITMAPV4HEADER_SIZE {
            // Assume BGRA32
            self.writer.write_u32::<LittleEndian>(0xff << 16)?; // red mask
            self.writer.write_u32::<LittleEndian>(0xff << 8)?; // green mask
            self.writer.write_u32::<LittleEndian>(0xff << 0)?; // blue mask
            self.writer.write_u32::<LittleEndian>(0xff << 24)?; // alpha mask
            self.writer.write_u32::<LittleEndian>(0x73524742)?; // colorspace - sRGB

            // endpoints (3x3) and gamma (3)
            for _ in 0..12 {
                self.writer.write_u32::<LittleEndian>(0)?;
            }
        }

        // write image data
        match c {
            color::ColorType::Rgb8 => {
                self.encode_rgb(image, width, height, row_pad_size, 3)?
            }
            color::ColorType::Rgba8 => {
                self.encode_rgba(image, width, height, row_pad_size, 4)?
            }
            color::ColorType::L8 => {
                self.encode_gray(image, width, height, row_pad_size, 1)?
            }
            color::ColorType::La8 => {
                self.encode_gray(image, width, height, row_pad_size, 2)?
            }
            _ => {
                return Err(ImageError::IoError(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    &get_unsupported_error_message(c)[..],
                )))
            }
        }

        Ok(())
    }

    fn encode_rgb(
        &mut self,
        image: &[u8],
        width: u32,
        height: u32,
        row_pad_size: u32,
        bytes_per_pixel: u32,
    ) -> io::Result<()> {
        let x_stride = bytes_per_pixel;
        let y_stride = width * x_stride;
        for row in 0..height {
            // from the bottom up
            let row_start = (height - row - 1) * y_stride;
            for col in 0..width {
                let pixel_start = (row_start + (col * x_stride)) as usize;
                let r = image[pixel_start];
                let g = image[pixel_start + 1];
                let b = image[pixel_start + 2];
                // written as BGR
                self.writer.write_u8(b)?;
                self.writer.write_u8(g)?;
                self.writer.write_u8(r)?;
                // alpha is never written as it's not widely supported
            }

            self.write_row_pad(row_pad_size)?;
        }

        Ok(())
    }

    fn encode_rgba(
        &mut self,
        image: &[u8],
        width: u32,
        height: u32,
        row_pad_size: u32,
        bytes_per_pixel: u32,
    ) -> io::Result<()> {
        let x_stride = bytes_per_pixel;
        let y_stride = width * x_stride;
        for row in 0..height {
            // from the bottom up
            let row_start = (height - row - 1) * y_stride;
            for col in 0..width {
                let pixel_start = (row_start + (col * x_stride)) as usize;
                let r = image[pixel_start];
                let g = image[pixel_start + 1];
                let b = image[pixel_start + 2];
                let a = image[pixel_start + 3];
                // written as BGRA
                self.writer.write_u8(b)?;
                self.writer.write_u8(g)?;
                self.writer.write_u8(r)?;
                self.writer.write_u8(a)?;
            }

            self.write_row_pad(row_pad_size)?;
        }

        Ok(())
    }

    fn encode_gray(
        &mut self,
        image: &[u8],
        width: u32,
        height: u32,
        row_pad_size: u32,
        bytes_per_pixel: u32,
    ) -> io::Result<()> {
        // write grayscale palette
        for val in 0..256 {
            // each color is written as BGRA, where A is always 0 and since only grayscale is being written, B = G = R = index
            let val = val as u8;
            self.writer.write_u8(val)?;
            self.writer.write_u8(val)?;
            self.writer.write_u8(val)?;
            self.writer.write_u8(0)?;
        }

        // write image data
        let x_stride = bytes_per_pixel;
        let y_stride = width * x_stride;
        for row in 0..height {
            // from the bottom up
            let row_start = (height - row - 1) * y_stride;
            for col in 0..width {
                let pixel_start = (row_start + (col * x_stride)) as usize;
                // color value is equal to the palette index
                self.writer.write_u8(image[pixel_start])?;
                // alpha is never written as it's not widely supported
            }

            self.write_row_pad(row_pad_size)?;
        }

        Ok(())
    }

    fn write_row_pad(&mut self, row_pad_size: u32) -> io::Result<()> {
        for _ in 0..row_pad_size {
            self.writer.write_u8(0)?;
        }

        Ok(())
    }
}

impl<'a, W: Write> ImageEncoder for BMPEncoder<'a, W> {
    fn write_image(
        mut self,
        buf: &[u8],
        width: u32,
        height: u32,
        color_type: color::ColorType,
    ) -> ImageResult<()> {
        self.encode(buf, width, height, color_type)
    }
}

fn get_unsupported_error_message(c: color::ColorType) -> String {
    format!(
        "Unsupported color type {:?}.  Supported types: RGB(8), RGBA(8), Gray(8), GrayA(8).",
        c
    )
}

/// Returns a tuple representing: (dib header size, written pixel size, palette color count).
fn get_pixel_info(c: color::ColorType) -> io::Result<(u32, u32, u32)> {
    let sizes = match c {
        color::ColorType::Rgb8 => (BITMAPINFOHEADER_SIZE, 3, 0),
        color::ColorType::Rgba8 => (BITMAPV4HEADER_SIZE, 4, 0),
        color::ColorType::L8 => (BITMAPINFOHEADER_SIZE, 1, 256),
        color::ColorType::La8 => (BITMAPINFOHEADER_SIZE, 1, 256),
        _ => {
            return Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                &get_unsupported_error_message(c)[..],
            ))
        }
    };

    Ok(sizes)
}

#[cfg(test)]
mod tests {
    use super::super::BmpDecoder;
    use super::BMPEncoder;
    use crate::color::ColorType;
    use crate::image::ImageDecoder;
    use std::io::Cursor;

    fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec<u8> {
        let mut encoded_data = Vec::new();
        {
            let mut encoder = BMPEncoder::new(&mut encoded_data);
            encoder
                .encode(&image, width, height, c)
                .expect("could not encode image");
        }

        let decoder = BmpDecoder::new(Cursor::new(&encoded_data)).expect("failed to decode");

        let mut buf = vec![0; decoder.total_bytes() as usize];
        decoder.read_image(&mut buf).expect("failed to decode");
        buf
    }

    #[test]
    fn round_trip_single_pixel_rgb() {
        let image = [255u8, 0, 0]; // single red pixel
        let decoded = round_trip_image(&image, 1, 1, ColorType::Rgb8);
        assert_eq!(3, decoded.len());
        assert_eq!(255, decoded[0]);
        assert_eq!(0, decoded[1]);
        assert_eq!(0, decoded[2]);
    }

    #[test]
    fn huge_files_return_error() {
        let mut encoded_data = Vec::new();
        let image = vec![0u8; 3 * 40_000 * 40_000]; // 40_000x40_000 pixels, 3 bytes per pixel, allocated on the heap
        let mut encoder = BMPEncoder::new(&mut encoded_data);
        let result = encoder.encode(&image, 40_000, 40_000, ColorType::Rgb8);
        assert!(result.is_err());
    }

    #[test]
    fn round_trip_single_pixel_rgba() {
        let image = [1, 2, 3, 4];
        let decoded = round_trip_image(&image, 1, 1, ColorType::Rgba8);
        assert_eq!(&decoded[..], &image[..]);
    }

    #[test]
    fn round_trip_3px_rgb() {
        let image = [0u8; 3 * 3 * 3]; // 3x3 pixels, 3 bytes per pixel
        let _decoded = round_trip_image(&image, 3, 3, ColorType::Rgb8);
    }

    #[test]
    fn round_trip_gray() {
        let image = [0u8, 1, 2]; // 3 pixels
        let decoded = round_trip_image(&image, 3, 1, ColorType::L8);
        // should be read back as 3 RGB pixels
        assert_eq!(9, decoded.len());
        assert_eq!(0, decoded[0]);
        assert_eq!(0, decoded[1]);
        assert_eq!(0, decoded[2]);
        assert_eq!(1, decoded[3]);
        assert_eq!(1, decoded[4]);
        assert_eq!(1, decoded[5]);
        assert_eq!(2, decoded[6]);
        assert_eq!(2, decoded[7]);
        assert_eq!(2, decoded[8]);
    }

    #[test]
    fn round_trip_graya() {
        let image = [0u8, 0, 1, 0, 2, 0]; // 3 pixels, each with an alpha channel
        let decoded = round_trip_image(&image, 1, 3, ColorType::La8);
        // should be read back as 3 RGB pixels
        assert_eq!(9, decoded.len());
        assert_eq!(0, decoded[0]);
        assert_eq!(0, decoded[1]);
        assert_eq!(0, decoded[2]);
        assert_eq!(1, decoded[3]);
        assert_eq!(1, decoded[4]);
        assert_eq!(1, decoded[5]);
        assert_eq!(2, decoded[6]);
        assert_eq!(2, decoded[7]);
        assert_eq!(2, decoded[8]);
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;

use failure::Error;
use ggez::graphics;
use ggez::nalgebra::Vector2;
use ggez::Context;
use warmy::{SimpleKey, Store, StoreOpt};

use crate::input;

#[derive(Debug)]
pub enum SceneState {
    Menu,
    Practice,
}

pub struct Game {
    pub input: input::InputState,
    pub store: Store<Context, SimpleKey>,
    pub images: HashMap<String, graphics::Image>,

    pub scene: SceneState,
    pub next_scene: SceneState,

    pub gore_on: bool,
    pub num_players: i32,

    pub encounter_starting_position: u32,
    pub practice_encounter: u32,

    pub screen_scale: Vector2<f32>,
}

impl Game {
    pub fn new() -> Result<Game, Error> {
        let store = Store::new(StoreOpt::default()).expect("error creating store");
        Ok(Game {
            input: input::InputState::new(),
            images: HashMap::new(),
            store: store,

            scene: SceneState::Menu,
            next_scene: SceneState::Practice,

            gore_on: true,
            num_players: 2,
            encounter_starting_position: 0,
            practice_encounter: 1,

            screen_scale: Vector2::new(3.0, 3.0),
        })
    }
}
<commit_msg>[#10] remove failure from game.rs<commit_after>use std::collections::HashMap;

use ggez::graphics;
use ggez::nalgebra::Vector2;
use ggez::Context;
use warmy::{SimpleKey, Store, StoreOpt};

use crate::error::{LoadError, MoonstoneError};
use crate::input;

#[derive(Debug)]
pub enum SceneState {
    Menu,
    Practice,
}

pub struct Game {
    pub input: input::InputState,
    pub store: Store<Context, SimpleKey>,
    pub images: HashMap<String, graphics::Image>,

    pub scene: SceneState,
    pub next_scene: SceneState,

    pub gore_on: bool,
    pub num_players: i32,

    pub encounter_starting_position: u32,
    pub practice_encounter: u32,

    pub screen_scale: Vector2<f32>,
}

impl Game {
    pub fn new() -> Result<Game, MoonstoneError> {
        let store = Store::new(StoreOpt::default()).expect("error creating store");
        Ok(Game {
            input: input::InputState::new(),
            images: HashMap::new(),
            store: store,

            scene: SceneState::Menu,
            next_scene: SceneState::Practice,

            gore_on: true,
            num_players: 2,
            encounter_starting_position: 0,
            practice_encounter: 1,

            screen_scale: Vector2::new(2.0, 2.0),
        })
    }
}
<|endoftext|>


<commit_before>// Copyright 2016 The xi-editor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#[macro_use]
extern crate log;
extern crate chrono;
extern crate fern;

extern crate dirs;

extern crate xi_core_lib;
extern crate xi_rpc;

use std::collections::HashMap;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};

use xi_core_lib::XiCore;
use xi_rpc::RpcLoop;

const XI_LOG_DIR: &str = "xi-core";
const XI_LOG_FILE: &str = "xi-core.log";

fn get_logging_directory_path<P: AsRef<Path>>(directory: P) -> Result<PathBuf, io::Error> {
    match dirs::data_local_dir() {
        Some(mut log_dir) => {
            log_dir.push(directory);
            Ok(log_dir)
        }
        None => Err(
            io::Error::new(
                io::ErrorKind::NotFound,
                "No standard logging directory known for this platform",
            )
        ),
    }
}

fn path_for_log_file<P: AsRef<Path>>(filename: P) -> Result<PathBuf, io::Error> {
    let mut logging_directory = get_logging_directory_path(get_logfile_directory_name())?;
    // Create the logging directory
    fs::create_dir_all(&logging_directory)?;
    // Pushing the filename to the end
    logging_directory.push(filename.as_ref());
    Ok(logging_directory)
}

fn setup_logging(logging_path_result: Result<PathBuf, io::Error>) -> Result<(), fern::InitError> {
    let level_filter = match std::env::var("XI_LOG") {
        Ok(level) => match level.to_lowercase().as_ref() {
            "trace" => log::LevelFilter::Trace,
            "debug" => log::LevelFilter::Debug,
            _ => log::LevelFilter::Info,
        },
        // Default to info
        Err(_) => log::LevelFilter::Info,
    };

    let mut fern_dispatch = fern::Dispatch::new()
        .format(|out, message, record| {
            out.finish(format_args!(
                "{}[{}][{}] {}",
                chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
                record.target(),
                record.level(),
                message,
            ))
        }).level(level_filter)
        .chain(io::stderr());

    if let Ok(logging_file_path) = &logging_path_result {
        // Ensure the logging directory is created
        let parent_path = logging_file_path.parent().ok_or(
            io::Error::new(
                io::ErrorKind::InvalidInput,
                format!(
                    "Unable to get the parent of the following Path: {}", logging_file_path.display(),
                )
            )
        )?;
        fs::create_dir_all(parent_path)?;
        // Attach it to fern
        fern_dispatch = fern_dispatch.chain(fern::log_file(logging_file_path)?);
    };

    // Start fern
    fern_dispatch.apply()?;

    // Log details of the fern setup using fern/log
    match &path_result {
        Err(e) => {
            let message = "There was an issue getting the path for the log file";
            warn!("{}: {:?}, falling back to stderr.", message, e);
        }
        Ok(logging_file_path) => info!("Logging to the following file: {:?}", logging_file_path),
    }

    info!("Logging with fern is setup");
    Ok(())
}

fn prepare_logging_path(logfile_config: LogfileConfig) -> Result<PathBuf, io::Error> {
    // Use the file name set in logfile_config or fallback to the default
    let logfile_file_name = match logfile_config.file {
        Some(file_name) => file_name,
        None => XI_LOG_FILE.to_string(),
    };
    // Use the directory name set in logfile_config or fallback to the default
    let logfile_directory_name = match logfile_config.directory {
        Some(dir) => dir,
        None => XI_LOG_DIR.to_string(),
    };

    let mut logging_directory_path = get_logging_directory_path(logfile_directory_name)?;

    // Add the file name & return the full path
    logging_directory_path.push(logfile_file_name);
    Ok(logging_directory_path)
}

struct LogfileConfig {
    directory: Option<String>,
    file: Option<String>,
}

fn get_flags() -> HashMap<String, Option<String>> {
    let mut flags: HashMap<String, Option<String>> = HashMap::new();

    let flag_prefix = "-";
    let mut args_itterator = std::env::args().peekable();
    while let Some(arg) = args_itterator.next() {
        if arg.starts_with(flag_prefix) {
            let key = arg.trim_left_matches(flag_prefix).to_string();

            // Check the next argument doesn't start with the flag prefix
            // map_or accounts for peek returning an Option
            let next_arg_not_a_flag: bool = args_itterator.peek().map_or(false, |val| !val.starts_with(flag_prefix));
            if next_arg_not_a_flag {
                flags.insert(key, args_itterator.next());
            }
        }
    }
    flags
}

fn main() {
    let mut state = XiCore::new();
    let stdin = io::stdin();
    let stdout = io::stdout();
    let mut rpc_looper = RpcLoop::new(stdout);

    let flags: HashMap<String, Option<String>> = get_flags();

    // If the key is set, get the Option within
    let log_dir_flag_option = flags.get("log-dir").cloned().unwrap_or(None);
    let log_file_flag_option = flags.get("log-file").cloned().unwrap_or(None);

    let logfile_config = LogfileConfig {
        directory: log_dir_flag_option,
        file: log_file_flag_option,
    };

    let logging_path: Result<PathBuf, io::Error> = prepare_logging_path(logfile_config);
    if let Err(e) = setup_logging(logging_path) {
        eprintln!(
            "[ERROR] setup_logging returned error, logging disabled: {:?}",
            e
        );
    }

    match rpc_looper.mainloop(|| stdin.lock(), &mut state) {
        Ok(_) => (),
        Err(err) => error!("xi-core exited with error:\n{:?}", err),
    }
}
<commit_msg>No longer need path_for_log_file<commit_after>// Copyright 2016 The xi-editor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#[macro_use]
extern crate log;
extern crate chrono;
extern crate fern;

extern crate dirs;

extern crate xi_core_lib;
extern crate xi_rpc;

use std::collections::HashMap;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};

use xi_core_lib::XiCore;
use xi_rpc::RpcLoop;

const XI_LOG_DIR: &str = "xi-core";
const XI_LOG_FILE: &str = "xi-core.log";

fn get_logging_directory_path<P: AsRef<Path>>(directory: P) -> Result<PathBuf, io::Error> {
    match dirs::data_local_dir() {
        Some(mut log_dir) => {
            log_dir.push(directory);
            Ok(log_dir)
        }
        None => Err(
            io::Error::new(
                io::ErrorKind::NotFound,
                "No standard logging directory known for this platform",
            )
        ),
    }
}

fn setup_logging(logging_path_result: Result<PathBuf, io::Error>) -> Result<(), fern::InitError> {
    let level_filter = match std::env::var("XI_LOG") {
        Ok(level) => match level.to_lowercase().as_ref() {
            "trace" => log::LevelFilter::Trace,
            "debug" => log::LevelFilter::Debug,
            _ => log::LevelFilter::Info,
        },
        // Default to info
        Err(_) => log::LevelFilter::Info,
    };

    let mut fern_dispatch = fern::Dispatch::new()
        .format(|out, message, record| {
            out.finish(format_args!(
                "{}[{}][{}] {}",
                chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
                record.target(),
                record.level(),
                message,
            ))
        }).level(level_filter)
        .chain(io::stderr());

    if let Ok(logging_file_path) = &logging_path_result {
        // Ensure the logging directory is created
        let parent_path = logging_file_path.parent().ok_or(
            io::Error::new(
                io::ErrorKind::InvalidInput,
                format!(
                    "Unable to get the parent of the following Path: {}", logging_file_path.display(),
                )
            )
        )?;
        fs::create_dir_all(parent_path)?;
        // Attach it to fern
        fern_dispatch = fern_dispatch.chain(fern::log_file(logging_file_path)?);
    };

    // Start fern
    fern_dispatch.apply()?;

    // Log details of the fern setup using fern/log
    match &path_result {
        Err(e) => {
            let message = "There was an issue getting the path for the log file";
            warn!("{}: {:?}, falling back to stderr.", message, e);
        }
        Ok(logging_file_path) => info!("Logging to the following file: {:?}", logging_file_path),
    }

    info!("Logging with fern is setup");
    Ok(())
}

fn prepare_logging_path(logfile_config: LogfileConfig) -> Result<PathBuf, io::Error> {
    // Use the file name set in logfile_config or fallback to the default
    let logfile_file_name = match logfile_config.file {
        Some(file_name) => file_name,
        None => XI_LOG_FILE.to_string(),
    };
    // Use the directory name set in logfile_config or fallback to the default
    let logfile_directory_name = match logfile_config.directory {
        Some(dir) => dir,
        None => XI_LOG_DIR.to_string(),
    };

    let mut logging_directory_path = get_logging_directory_path(logfile_directory_name)?;

    // Add the file name & return the full path
    logging_directory_path.push(logfile_file_name);
    Ok(logging_directory_path)
}

struct LogfileConfig {
    directory: Option<String>,
    file: Option<String>,
}

fn get_flags() -> HashMap<String, Option<String>> {
    let mut flags: HashMap<String, Option<String>> = HashMap::new();

    let flag_prefix = "-";
    let mut args_itterator = std::env::args().peekable();
    while let Some(arg) = args_itterator.next() {
        if arg.starts_with(flag_prefix) {
            let key = arg.trim_left_matches(flag_prefix).to_string();

            // Check the next argument doesn't start with the flag prefix
            // map_or accounts for peek returning an Option
            let next_arg_not_a_flag: bool = args_itterator.peek().map_or(false, |val| !val.starts_with(flag_prefix));
            if next_arg_not_a_flag {
                flags.insert(key, args_itterator.next());
            }
        }
    }
    flags
}

fn main() {
    let mut state = XiCore::new();
    let stdin = io::stdin();
    let stdout = io::stdout();
    let mut rpc_looper = RpcLoop::new(stdout);

    let flags: HashMap<String, Option<String>> = get_flags();

    // If the key is set, get the Option within
    let log_dir_flag_option = flags.get("log-dir").cloned().unwrap_or(None);
    let log_file_flag_option = flags.get("log-file").cloned().unwrap_or(None);

    let logfile_config = LogfileConfig {
        directory: log_dir_flag_option,
        file: log_file_flag_option,
    };

    let logging_path: Result<PathBuf, io::Error> = prepare_logging_path(logfile_config);
    if let Err(e) = setup_logging(logging_path) {
        eprintln!(
            "[ERROR] setup_logging returned error, logging disabled: {:?}",
            e
        );
    }

    match rpc_looper.mainloop(|| stdin.lock(), &mut state) {
        Ok(_) => (),
        Err(err) => error!("xi-core exited with error:\n{:?}", err),
    }
}
<|endoftext|>


<commit_before>extern crate fuse;
extern crate libc;
extern crate time;
extern crate argparse;

use std::string::String;
use std::fmt;
use std::env;
use std::path::Path;
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;

use libc::{ENOSYS,c_int,ENOENT};
use time::Timespec;
use argparse::{ArgumentParser, StoreTrue, Store};
use fuse::{FileType, FileAttr, Filesystem, Request, ReplyData, ReplyEntry, ReplyAttr, ReplyDirectory};

// FIXME these are ugly as fuck and it seems like a BAD DECISION to have these kind of macros in rust.
// FIXME in any case, clean up our usage of these later.
// from: http://stackoverflow.com/questions/28392008/more-concise-hashmap-initialization
// auto-converts to String
macro_rules! hashmap {
    ($( $key: expr => $val: expr ),*) => {{
         let mut map = ::std::collections::HashMap::new();
         $( map.insert($key.to_string(), $val); )*
         map
    }}
}
// similar to above, jesus christ this is terrible
macro_rules! hashset {
    ($( $x: expr ),* ) => {{
            let mut set = HashSet::new();
            $( set.insert($x.to_string()); )*
            set
    }}
}

const TTL: Timespec = Timespec { sec: 1, nsec: 0 };                 // 1 second

const CREATE_TIME: Timespec = Timespec { sec: 1381237736, nsec: 0 };    // 2013-10-08 08:56

const HELLO_DIR_ATTR: FileAttr = FileAttr {
    ino: 1,
    size: 0,
    blocks: 0,
    atime: CREATE_TIME,
    mtime: CREATE_TIME,
    ctime: CREATE_TIME,
    crtime: CREATE_TIME,
    kind: FileType::Directory,
    perm: 0o755,
    nlink: 2,
    uid: 501,
    gid: 20,
    rdev: 0,
    flags: 0,
};

const HELLO_TXT_CONTENT: &'static str = "Hello World!\n";

const HELLO_TXT_ATTR: FileAttr = FileAttr {
    ino: 2,
    size: 13,
    blocks: 1,
    atime: CREATE_TIME,
    mtime: CREATE_TIME,
    ctime: CREATE_TIME,
    crtime: CREATE_TIME,
    kind: FileType::RegularFile,
    perm: 0o644,
    nlink: 1,
    uid: 501,
    gid: 20,
    rdev: 0,
    flags: 0,
};

struct UrchinFSEntry {
    display_name: String,
    destination: String,
    metadata: HashMap<String, HashSet<String>>
}

struct UrchinFS {
    entries: Vec<UrchinFSEntry>
}

impl Filesystem for UrchinFS {
    fn lookup (&mut self, _req: &Request, parent: u64, name: &Path, reply: ReplyEntry) {
        if parent == 1 && name.to_str() == Some("hello.txt") {
            reply.entry(&TTL, &HELLO_TXT_ATTR, 0);
        } else {
            reply.error(ENOENT);
        }
    }

    fn getattr (&mut self, _req: &Request, ino: u64, reply: ReplyAttr) {
        match ino {
            1 => reply.attr(&TTL, &HELLO_DIR_ATTR),
            2 => reply.attr(&TTL, &HELLO_TXT_ATTR),
            _ => reply.error(ENOENT),
        }
    }

    fn read (&mut self, _req: &Request, ino: u64, _fh: u64, offset: u64, _size: u32, reply: ReplyData) {
        if ino == 2 {
            reply.data(&HELLO_TXT_CONTENT.as_bytes()[offset as usize..]);
        } else {
            reply.error(ENOENT);
        }
    }

    fn readdir (&mut self, _req: &Request, ino: u64, _fh: u64, offset: u64, mut reply: ReplyDirectory) {
        if ino == 1 {
            if offset == 0 {
                reply.add(1, 0, FileType::Directory, ".");
                reply.add(1, 1, FileType::Directory, "..");
                reply.add(2, 2, FileType::RegularFile, "hello.txt");
            }
            reply.ok();
        } else {
            reply.error(ENOENT);
        }
    }
}

fn main () {
    let mut mountpoint = String::new();
    let mut optstr = String::new();
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("faceted-search FUSE filesystem");
        ap.refer(&mut optstr).add_option(&["-o", "--options"], Store, "comma-separated mount options");
        ap.refer(&mut mountpoint).add_argument("mountpoint", Store, "the mountpoint").required();
        ap.parse_args_or_exit();
    }

    // FIXME seems weird to have to have two variable refs
    let vec;
    let mut options : &[&OsStr] = &[];
    if optstr!= "" {
        vec = optstr.split(",").map(|s| OsStr::new(s)).collect::<Vec<_>>();
        options = &vec;
    }

    // FIMXE do this better
    let easter = UrchinFSEntry {
        display_name: "Easter Parade (1948) color".to_string(),
        destination: "/home/kellen/test".to_string(),
        metadata: hashmap!["year" => hashset!["1948"], "color" => hashset!["color"]]
    };

    let entries = vec![easter];

    //
    fuse::mount(UrchinFS {entries: entries}, &mountpoint, options);
}
<commit_msg>Added test data<commit_after>extern crate fuse;
extern crate libc;
extern crate time;
extern crate argparse;

use std::string::String;
use std::fmt;
use std::env;
use std::path::Path;
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;

use libc::{ENOSYS,c_int,ENOENT};
use time::Timespec;
use argparse::{ArgumentParser, StoreTrue, Store};
use fuse::{FileType, FileAttr, Filesystem, Request, ReplyData, ReplyEntry, ReplyAttr, ReplyDirectory};

// FIXME these are ugly as fuck and it seems like a BAD DECISION to have these kind of macros in rust.
// FIXME in any case, clean up our usage of these later.
// from: http://stackoverflow.com/questions/28392008/more-concise-hashmap-initialization
// auto-converts to String
macro_rules! hashmap {
    ($( $key: expr => $val: expr ),*) => {{
         let mut map = ::std::collections::HashMap::new();
         $( map.insert($key.to_string(), $val); )*
         map
    }}
}
// similar to above, jesus christ this is terrible
macro_rules! hashset {
    ($( $x: expr ),* ) => {{
            let mut set = HashSet::new();
            $( set.insert($x.to_string()); )*
            set
    }}
}

const TTL: Timespec = Timespec { sec: 1, nsec: 0 };                 // 1 second

const CREATE_TIME: Timespec = Timespec { sec: 1381237736, nsec: 0 };    // 2013-10-08 08:56

const HELLO_DIR_ATTR: FileAttr = FileAttr {
    ino: 1,
    size: 0,
    blocks: 0,
    atime: CREATE_TIME,
    mtime: CREATE_TIME,
    ctime: CREATE_TIME,
    crtime: CREATE_TIME,
    kind: FileType::Directory,
    perm: 0o755,
    nlink: 2,
    uid: 501,
    gid: 20,
    rdev: 0,
    flags: 0,
};

const HELLO_TXT_CONTENT: &'static str = "Hello World!\n";

const HELLO_TXT_ATTR: FileAttr = FileAttr {
    ino: 2,
    size: 13,
    blocks: 1,
    atime: CREATE_TIME,
    mtime: CREATE_TIME,
    ctime: CREATE_TIME,
    crtime: CREATE_TIME,
    kind: FileType::RegularFile,
    perm: 0o644,
    nlink: 1,
    uid: 501,
    gid: 20,
    rdev: 0,
    flags: 0,
};

struct UrchinFSEntry {
    display_name: String,
    destination: String,
    metadata: HashMap<String, HashSet<String>>
}

struct UrchinFS {
    entries: Vec<UrchinFSEntry>
}

impl Filesystem for UrchinFS {
    fn lookup (&mut self, _req: &Request, parent: u64, name: &Path, reply: ReplyEntry) {
        if parent == 1 && name.to_str() == Some("hello.txt") {
            reply.entry(&TTL, &HELLO_TXT_ATTR, 0);
        } else {
            reply.error(ENOENT);
        }
    }

    fn getattr (&mut self, _req: &Request, ino: u64, reply: ReplyAttr) {
        match ino {
            1 => reply.attr(&TTL, &HELLO_DIR_ATTR),
            2 => reply.attr(&TTL, &HELLO_TXT_ATTR),
            _ => reply.error(ENOENT),
        }
    }

    fn read (&mut self, _req: &Request, ino: u64, _fh: u64, offset: u64, _size: u32, reply: ReplyData) {
        if ino == 2 {
            reply.data(&HELLO_TXT_CONTENT.as_bytes()[offset as usize..]);
        } else {
            reply.error(ENOENT);
        }
    }

    fn readdir (&mut self, _req: &Request, ino: u64, _fh: u64, offset: u64, mut reply: ReplyDirectory) {
        if ino == 1 {
            if offset == 0 {
                reply.add(1, 0, FileType::Directory, ".");
                reply.add(1, 1, FileType::Directory, "..");
                reply.add(2, 2, FileType::RegularFile, "hello.txt");
            }
            reply.ok();
        } else {
            reply.error(ENOENT);
        }
    }
}

fn main () {
    let mut mountpoint = String::new();
    let mut optstr = String::new();
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("faceted-search FUSE filesystem");
        ap.refer(&mut optstr).add_option(&["-o", "--options"], Store, "comma-separated mount options");
        ap.refer(&mut mountpoint).add_argument("mountpoint", Store, "the mountpoint").required();
        ap.parse_args_or_exit();
    }

    // FIXME seems weird to have to have two variable refs
    let vec;
    let mut options : &[&OsStr] = &[];
    if optstr!= "" {
        vec = optstr.split(",").map(|s| OsStr::new(s)).collect::<Vec<_>>();
        options = &vec;
    }

    // FIMXE do this better
    let easter = UrchinFSEntry {
        display_name: "Easter Parade (1948, color)".to_string(),
        destination: "/home/kellen/test".to_string(),
        metadata: hashmap!["year" => hashset!["1948"], "color" => hashset!["color"]]
    };
    let city = UrchinFSEntry {
        display_name: "The Naked City (1948, bw)".to_string(),
        destination: "/home/kellen/test".to_string(),
        metadata: hashmap!["year" => hashset!["1948"], "color" => hashset!["black-and-white"]]
    };
    let vanish = UrchinFSEntry {
        display_name: "The Lady Vanishes (1938, bw)".to_string(),
        destination: "/home/kellen/test".to_string(),
        metadata: hashmap!["year" => hashset!["1938"], "color" => hashset!["black-and-white"]]
    };
    let kiss = UrchinFSEntry {
        display_name: "Kiss Me Deadly (1955, bw)".to_string(),
        destination: "/home/kellen/test".to_string(),
        metadata: hashmap!["year" => hashset!["1955"], "color" => hashset!["black-and-white"]]
    };

    fuse::mount(UrchinFS {entries: vec![easter, city, vanish, kiss]}, &mountpoint, options);
}
<|endoftext|>


<commit_before>extern crate fuse;
extern crate libc;
extern crate time;
extern crate argparse;

use std::string::String;
use std::fmt;
use std::env;
use std::path::Path;
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;

use libc::{ENOSYS,c_int,ENOENT};
use time::Timespec;
use argparse::{ArgumentParser, StoreTrue, Store};
use fuse::{FileType, FileAttr, Filesystem, Request, ReplyData, ReplyEntry, ReplyAttr, ReplyEmpty, ReplyDirectory};

const DEBUG : bool = true;
// FIXME I hate macros. Basically the same as println! but with an IF!!!!!
macro_rules! log {
    ($fmt:expr) => (if DEBUG {println!(concat!($fmt, "\n"))});
    ($fmt:expr, $($arg:tt)*) => (if DEBUG {println!(concat!($fmt, "\n"), $($arg)*)});
}

// FIXME these are ugly as fuck and it seems like a BAD DECISION to have these kind of macros in rust.
// FIXME in any case, clean up our usage of these later.
// from: http://stackoverflow.com/questions/28392008/more-concise-hashmap-initialization
// auto-converts to String
macro_rules! hashmap {
    ($( $key: expr => $val: expr ),*) => {{
         let mut map = ::std::collections::HashMap::new();
         $( map.insert($key.to_string(), $val); )*
         map
    }}
}
// similar to above, jesus christ this is terrible
macro_rules! hashset {
    ($( $x: expr ),* ) => {{
            let mut set = HashSet::new();
            $( set.insert($x.to_string()); )*
            set
    }}
}

const TTL: Timespec = Timespec { sec: 1, nsec: 0 };                 // 1 second

const CREATE_TIME: Timespec = Timespec { sec: 1381237736, nsec: 0 };    // 2013-10-08 08:56

const HELLO_DIR_ATTR: FileAttr = FileAttr {
    ino: 1,
    size: 0,
    blocks: 0,
    atime: CREATE_TIME,
    mtime: CREATE_TIME,
    ctime: CREATE_TIME,
    crtime: CREATE_TIME,
    kind: FileType::Directory,
    perm: 0o755,
    nlink: 2,
    uid: 501,
    gid: 20,
    rdev: 0,
    flags: 0,
};

const HELLO_TXT_CONTENT: &'static str = "Hello World!\n";

const HELLO_TXT_ATTR: FileAttr = FileAttr {
    ino: 2,
    size: 13,
    blocks: 1,
    atime: CREATE_TIME,
    mtime: CREATE_TIME,
    ctime: CREATE_TIME,
    crtime: CREATE_TIME,
    kind: FileType::RegularFile,
    perm: 0o644,
    nlink: 1,
    uid: 501,
    gid: 20,
    rdev: 0,
    flags: 0,
};

struct UrchinFSEntry {
    display_name: String,
    destination: String,
    metadata: HashMap<String, HashSet<String>>
}

struct UrchinFS {
    entries: Vec<UrchinFSEntry>
}

impl Filesystem for UrchinFS {
    fn lookup (&mut self, _req: &Request, parent: u64, name: &Path, reply: ReplyEntry) {
        if parent == 1 && name.to_str() == Some("hello.txt") {
            reply.entry(&TTL, &HELLO_TXT_ATTR, 0);
        } else {
            reply.error(ENOENT);
        }
    }

    fn getattr (&mut self, _req: &Request, ino: u64, reply: ReplyAttr) {
        match ino {
            1 => reply.attr(&TTL, &HELLO_DIR_ATTR),
            2 => reply.attr(&TTL, &HELLO_TXT_ATTR),
            _ => reply.error(ENOENT),
        }
    }

    fn read (&mut self, _req: &Request, ino: u64, _fh: u64, offset: u64, _size: u32, reply: ReplyData) {
        if ino == 2 {
            reply.data(&HELLO_TXT_CONTENT.as_bytes()[offset as usize..]);
        } else {
            reply.error(ENOENT);
        }
    }

    fn readdir (&mut self, _req: &Request, ino: u64, _fh: u64, offset: u64, mut reply: ReplyDirectory) {
        if ino == 1 {
            if offset == 0 {
                reply.add(1, 0, FileType::Directory, ".");
                reply.add(1, 1, FileType::Directory, "..");
                reply.add(2, 2, FileType::RegularFile, "hello.txt");
            }
            reply.ok();
        } else {
            reply.error(ENOENT);
        }
    }

    fn access (&mut self, _req: &Request, _ino: u64, _mask: u32, reply: ReplyEmpty) {
        reply.error(ENOSYS);
    }
}

fn main () {
    let mut mountpoint = String::new();
    let mut optstr = String::new();
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("faceted-search FUSE filesystem");
        ap.refer(&mut optstr).add_option(&["-o", "--options"], Store, "comma-separated mount options");
        ap.refer(&mut mountpoint).add_argument("mountpoint", Store, "the mountpoint").required();
        ap.parse_args_or_exit();
    }

// -o plugin=foo,plugin=bar,source=/home/kellen/films/,glob=*.json,format=json,watch=true,type=directory,formatter=generic
// FIXME dynamically load plugins for globbing/formatting see:
// http://stackoverflow.com/questions/22461457/does-rust-have-a-dlopen-equivalent

    // FIXME seems weird to have to have two variable refs
    let vec;
    let mut options : &[&OsStr] = &[];
    if optstr!= "" {
        vec = optstr.split(",").map(|s| OsStr::new(s)).collect::<Vec<_>>();
        options = &vec;
    }

    // FIMXE do this better
    let easter = UrchinFSEntry {
        display_name: "Easter Parade (1948, color)".to_string(),
        destination: "/home/kellen/test".to_string(),
        metadata: hashmap!["year" => hashset!["1948"], "color" => hashset!["color"]]
    };
    let city = UrchinFSEntry {
        display_name: "The Naked City (1948, bw)".to_string(),
        destination: "/home/kellen/test".to_string(),
        metadata: hashmap!["year" => hashset!["1948"], "color" => hashset!["black-and-white"]]
    };
    let vanish = UrchinFSEntry {
        display_name: "The Lady Vanishes (1938, bw)".to_string(),
        destination: "/home/kellen/test".to_string(),
        metadata: hashmap!["year" => hashset!["1938"], "color" => hashset!["black-and-white"]]
    };
    let kiss = UrchinFSEntry {
        display_name: "Kiss Me Deadly (1955, bw)".to_string(),
        destination: "/home/kellen/test".to_string(),
        metadata: hashmap!["year" => hashset!["1955"], "color" => hashset!["black-and-white"]]
    };

    fuse::mount(UrchinFS {entries: vec![easter, city, vanish, kiss]}, &mountpoint, options);
}
<commit_msg>debugging info<commit_after>extern crate fuse;
extern crate libc;
extern crate time;
extern crate argparse;

use std::string::String;
use std::fmt;
use std::env;
use std::path::Path;
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;

use libc::{ENOSYS,c_int,ENOENT};
use time::Timespec;
use argparse::{ArgumentParser, StoreTrue, Store};
use fuse::{FileType, FileAttr, Filesystem, Request, ReplyData, ReplyEntry, ReplyAttr, ReplyEmpty, ReplyDirectory};

const DEBUG : bool = true;
// FIXME I hate macros. Basically the same as println! but with an IF!!!!!
macro_rules! log {
    ($fmt:expr) => (if DEBUG {println!(concat!($fmt, "\n"))});
    ($fmt:expr, $($arg:tt)*) => (if DEBUG {println!(concat!($fmt, "\n"), $($arg)*)});
}

// FIXME these are ugly as fuck and it seems like a BAD DECISION to have these kind of macros in rust.
// FIXME in any case, clean up our usage of these later.
// from: http://stackoverflow.com/questions/28392008/more-concise-hashmap-initialization
// auto-converts to String
macro_rules! hashmap {
    ($( $key: expr => $val: expr ),*) => {{
         let mut map = ::std::collections::HashMap::new();
         $( map.insert($key.to_string(), $val); )*
         map
    }}
}
// similar to above, jesus christ this is terrible
macro_rules! hashset {
    ($( $x: expr ),* ) => {{
            let mut set = HashSet::new();
            $( set.insert($x.to_string()); )*
            set
    }}
}

const TTL: Timespec = Timespec { sec: 1, nsec: 0 };                 // 1 second

const CREATE_TIME: Timespec = Timespec { sec: 1381237736, nsec: 0 };    // 2013-10-08 08:56

const HELLO_DIR_ATTR: FileAttr = FileAttr {
    ino: 1,
    size: 0,
    blocks: 0,
    atime: CREATE_TIME,
    mtime: CREATE_TIME,
    ctime: CREATE_TIME,
    crtime: CREATE_TIME,
    kind: FileType::Directory,
    perm: 0o755,
    nlink: 2,
    uid: 501,
    gid: 20,
    rdev: 0,
    flags: 0,
};

const HELLO_TXT_CONTENT: &'static str = "Hello World!\n";

const HELLO_TXT_ATTR: FileAttr = FileAttr {
    ino: 2,
    size: 13,
    blocks: 1,
    atime: CREATE_TIME,
    mtime: CREATE_TIME,
    ctime: CREATE_TIME,
    crtime: CREATE_TIME,
    kind: FileType::RegularFile,
    perm: 0o644,
    nlink: 1,
    uid: 501,
    gid: 20,
    rdev: 0,
    flags: 0,
};

struct UrchinFSEntry {
    display_name: String,
    destination: String,
    metadata: HashMap<String, HashSet<String>>
}

struct UrchinFS {
    entries: Vec<UrchinFSEntry>
}

impl Filesystem for UrchinFS {
    fn lookup (&mut self, _req: &Request, parent: u64, name: &Path, reply: ReplyEntry) {
        if parent == 1 && name.to_str() == Some("hello.txt") {
            reply.entry(&TTL, &HELLO_TXT_ATTR, 0);
        } else {
            reply.error(ENOENT);
        }
    }

    fn getattr (&mut self, _req: &Request, ino: u64, reply: ReplyAttr) {
        match ino {
            1 => reply.attr(&TTL, &HELLO_DIR_ATTR),
            2 => reply.attr(&TTL, &HELLO_TXT_ATTR),
            _ => reply.error(ENOENT),
        }
    }

    fn read (&mut self, _req: &Request, ino: u64, _fh: u64, offset: u64, _size: u32, reply: ReplyData) {
        if ino == 2 {
            reply.data(&HELLO_TXT_CONTENT.as_bytes()[offset as usize..]);
        } else {
            reply.error(ENOENT);
        }
    }

    fn readdir (&mut self, _req: &Request, ino: u64, _fh: u64, offset: u64, mut reply: ReplyDirectory) {
        if ino == 1 {
            if offset == 0 {
                reply.add(1, 0, FileType::Directory, ".");
                reply.add(1, 1, FileType::Directory, "..");
                reply.add(2, 2, FileType::RegularFile, "hello.txt");
            }
            reply.ok();
        } else {
            reply.error(ENOENT);
        }
    }

    fn access (&mut self, _req: &Request, _ino: u64, _mask: u32, reply: ReplyEmpty) {
        reply.error(ENOSYS);
    }
}

fn main () {
    let mut mountpoint = String::new();
    let mut optstr = String::new();
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("faceted-search FUSE filesystem");
        ap.refer(&mut optstr).add_option(&["-o", "--options"], Store, "comma-separated mount options");
        ap.refer(&mut mountpoint).add_argument("mountpoint", Store, "the mountpoint").required();
        ap.parse_args_or_exit();
    }

// -o plugin=foo,plugin=bar,source=/home/kellen/films/,glob=*.json,format=json,watch=true,type=directory,formatter=generic
// FIXME dynamically load plugins for globbing/formatting see:
// http://stackoverflow.com/questions/22461457/does-rust-have-a-dlopen-equivalent

    // FIXME seems weird to have to have two variable refs
    let vec;
    let mut options : &[&OsStr] = &[];
    if optstr.trim() != "" {
        vec = optstr.trim().split(",").map(|s| OsStr::new(s)).collect::<Vec<_>>();
        options = &vec;
    }

    if DEBUG {
        if options.len() > 0 {
            println!("using options:");
            for s in options {
                match s.to_str() {
                    Some(y) => {println!("\t{}", y);}
                    None => {}
                }
            }
        }
    }

    // FIMXE do this better
    let easter = UrchinFSEntry {
        display_name: "Easter Parade (1948, color)".to_string(),
        destination: "/home/kellen/test".to_string(),
        metadata: hashmap!["year" => hashset!["1948"], "color" => hashset!["color"]]
    };
    let city = UrchinFSEntry {
        display_name: "The Naked City (1948, bw)".to_string(),
        destination: "/home/kellen/test".to_string(),
        metadata: hashmap!["year" => hashset!["1948"], "color" => hashset!["black-and-white"]]
    };
    let vanish = UrchinFSEntry {
        display_name: "The Lady Vanishes (1938, bw)".to_string(),
        destination: "/home/kellen/test".to_string(),
        metadata: hashmap!["year" => hashset!["1938"], "color" => hashset!["black-and-white"]]
    };
    let kiss = UrchinFSEntry {
        display_name: "Kiss Me Deadly (1955, bw)".to_string(),
        destination: "/home/kellen/test".to_string(),
        metadata: hashmap!["year" => hashset!["1955"], "color" => hashset!["black-and-white"]]
    };

    fuse::mount(UrchinFS {entries: vec![easter, city, vanish, kiss]}, &mountpoint, options);
}
<|endoftext|>