<commit_before>use byteorder::{ReadBytesExt, LittleEndian};
use encoding::all::UTF_16LE;
use encoding::{Encoding, DecoderTrap};
use lnkpkg::errors::{LnkError};
use std::io::Read;
use std::io::Error;
use std::slice;

pub fn read_string_utf8<R: Read>(mut reader: R) -> Result<String,LnkError> {
    // Reads into a string till a null char is reached
    let utf8_buffer = get_u8_vec(&mut reader)?;
    Ok(
        String::from_utf8(utf8_buffer)?
    )
}

pub fn read_string_utf16<R: Read>(mut reader: R) -> Result<String,LnkError> {
    // Reads into a string till a null char is reached
    let utf16_buffer = get_u8_vec_utf16(&mut reader)?;
    let utf16_string = match UTF_16LE.decode(&utf16_buffer.as_slice(),DecoderTrap::Ignore) {
        Ok(utf16) => utf16,
        Err(error) => return Err(
            LnkError::utf16_decode_error(
                format!("Error decoding name in volume_label_unicode field. [{}]",error)
            )
        )
    };
    Ok(utf16_string)
}

pub fn get_u8_vec<R: Read>(mut reader: R) -> Result<Vec<u8>,Error> {
    let mut string_vec: Vec<u8> = Vec::new();

    loop {
        let u8_char = reader.read_u8()?;
        if u8_char == 0x00 {
            break
        } else {
            string_vec.push(u8_char);
        }
    }

    Ok(string_vec)
}

pub fn get_u8_vec_utf16<R: Read>(mut reader: R) -> Result<Vec<u8>,Error>{
    let mut u16_vec: Vec<u16> = Vec::new();

    loop {
        let u16_char = reader.read_u16::<LittleEndian>()?;
        if u16_char == 0x0000 {
            break
        } else {
            u16_vec.push(u16_char);
        }
    }

    let u8_slice: &[u8] = unsafe {
        slice::from_raw_parts(u16_vec.as_ptr() as *const u8, u16_vec.len() * 2)
    };

    Ok(u8_slice.to_vec())
}

pub fn get_u16_vec<R: Read>(mut reader: R) -> Result<Vec<u16>,Error> {
    let mut string_vec: Vec<u16> = Vec::new();

    loop {
        let u16_char = reader.read_u16::<LittleEndian>()?;
        if u16_char == 0x0000 {
            break
        } else {
            string_vec.push(u16_char);
        }
    }

    Ok(string_vec)
}
<commit_msg>Added and renamed string reading help methods<commit_after>use byteorder::{ReadBytesExt, LittleEndian};
use encoding::all::UTF_16LE;
use encoding::{Encoding, DecoderTrap};
use lnkpkg::errors::{LnkError};
use std::io::Read;
use std::io::Error;
use std::slice;

pub fn read_string_u8_w_size<R: Read>(mut reader: R) -> Result<String,LnkError> {
    // Reads into a string with the size preceeding the string
    let str_size = reader.read_u8()?;

    let mut utf8_buffer = vec![0;str_size as usize];
    reader.read_exact(utf8_buffer.as_mut_slice())?;

    Ok(
        String::from_utf8(utf8_buffer)?
    )
}

pub fn read_string_u16_w_size<R: Read>(mut reader: R) -> Result<String,LnkError> {
    // Reads into a string with the size preceeding the string
    // multiply size by two (wchar = 2 bytes)
    let str_size = reader.read_u16::<LittleEndian>()? * 2;

    let mut utf16_buffer = vec![0;str_size as usize];
    reader.read_exact(utf16_buffer.as_mut_slice())?;

    let utf16_string = match UTF_16LE.decode(&utf16_buffer.as_slice(),DecoderTrap::Ignore) {
        Ok(utf16) => utf16,
        Err(error) => return Err(
            LnkError::utf16_decode_error(
                format!("Error decoding name in volume_label_unicode field. [{}]",error)
            )
        )
    };

    Ok(utf16_string)
}

pub fn read_string_u8_till_null<R: Read>(mut reader: R) -> Result<String,LnkError> {
    // Reads into a string till a null char is reached
    let utf8_buffer = get_u8_vec(&mut reader)?;
    Ok(
        String::from_utf8(utf8_buffer)?
    )
}

pub fn read_string_u16_till_null<R: Read>(mut reader: R) -> Result<String,LnkError> {
    // Reads into a string till a null char is reached
    let utf16_buffer = get_u8_vec_utf16(&mut reader)?;
    let utf16_string = match UTF_16LE.decode(&utf16_buffer.as_slice(),DecoderTrap::Ignore) {
        Ok(utf16) => utf16,
        Err(error) => return Err(
            LnkError::utf16_decode_error(
                format!("Error decoding name in volume_label_unicode field. [{}]",error)
            )
        )
    };
    Ok(utf16_string)
}

pub fn get_u8_vec<R: Read>(mut reader: R) -> Result<Vec<u8>,Error> {
    let mut string_vec: Vec<u8> = Vec::new();

    loop {
        let u8_char = reader.read_u8()?;
        if u8_char == 0x00 {
            break
        } else {
            string_vec.push(u8_char);
        }
    }

    Ok(string_vec)
}

pub fn get_u8_vec_utf16<R: Read>(mut reader: R) -> Result<Vec<u8>,Error>{
    let mut u16_vec: Vec<u16> = Vec::new();

    loop {
        let u16_char = reader.read_u16::<LittleEndian>()?;
        if u16_char == 0x0000 {
            break
        } else {
            u16_vec.push(u16_char);
        }
    }

    let u8_slice: &[u8] = unsafe {
        slice::from_raw_parts(u16_vec.as_ptr() as *const u8, u16_vec.len() * 2)
    };

    Ok(u8_slice.to_vec())
}

pub fn get_u16_vec<R: Read>(mut reader: R) -> Result<Vec<u16>,Error> {
    let mut string_vec: Vec<u16> = Vec::new();

    loop {
        let u16_char = reader.read_u16::<LittleEndian>()?;
        if u16_char == 0x0000 {
            break
        } else {
            string_vec.push(u16_char);
        }
    }

    Ok(string_vec)
}
<|endoftext|>


<commit_before>use attrs::AttributeSpanWrapper;
use field::Field;
use model::Model;
use proc_macro2::TokenStream;
use quote::quote_spanned;
use syn::{DeriveInput, Expr, Path, Type};
use util::{inner_of_option_ty, is_option_ty, wrap_in_dummy_mod};

pub fn derive(item: DeriveInput) -> TokenStream {
    let model = Model::from_item(&item, false);

    let treat_none_as_default_value = model.treat_none_as_default_value();
    let table_name = &model.table_name();
    let struct_name = &item.ident;

    let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();

    let mut generate_borrowed_insert = true;

    let mut direct_field_ty = Vec::with_capacity(model.fields().len());
    let mut direct_field_assign = Vec::with_capacity(model.fields().len());
    let mut ref_field_ty = Vec::with_capacity(model.fields().len());
    let mut ref_field_assign = Vec::with_capacity(model.fields().len());

    for field in model.fields() {
        match (field.serialize_as.as_ref(), field.embed()) {
            (None, true) => {
                direct_field_ty.push(field_ty_embed(field, None));
                direct_field_assign.push(field_expr_embed(field, None));
                ref_field_ty.push(field_ty_embed(field, Some(quote!(&'insert))));
                ref_field_assign.push(field_expr_embed(field, Some(quote!(&))));
            }
            (None, false) => {
                direct_field_ty.push(field_ty(
                    field,
                    table_name,
                    None,
                    treat_none_as_default_value,
                ));
                direct_field_assign.push(field_expr(
                    field,
                    table_name,
                    None,
                    treat_none_as_default_value,
                ));
                ref_field_ty.push(field_ty(
                    field,
                    table_name,
                    Some(quote!(&'insert)),
                    treat_none_as_default_value,
                ));
                ref_field_assign.push(field_expr(
                    field,
                    table_name,
                    Some(quote!(&)),
                    treat_none_as_default_value,
                ));
            }
            (Some(AttributeSpanWrapper { item: ty, .. }), false) => {
                direct_field_ty.push(field_ty_serialize_as(
                    field,
                    table_name,
                    ty,
                    treat_none_as_default_value,
                ));
                direct_field_assign.push(field_expr_serialize_as(
                    field,
                    table_name,
                    ty,
                    treat_none_as_default_value,
                ));

                generate_borrowed_insert = false; // as soon as we hit one field with #[diesel(serialize_as)] there is no point in generating the impl of Insertable for borrowed structs
            }
            (Some(AttributeSpanWrapper { attribute_span, .. }), true) => {
                abort!(
                    attribute_span,
                    "`#[diesel(embed)]` cannot be combined with `#[diesel(serialize_as)]`"
                )
            }
        }
    }

    let insert_owned = quote! {
        impl #impl_generics Insertable<#table_name::table> for #struct_name #ty_generics
            #where_clause
        {
            type Values = <(#(#direct_field_ty,)*) as Insertable<#table_name::table>>::Values;

            fn values(self) -> <(#(#direct_field_ty,)*) as Insertable<#table_name::table>>::Values {
                (#(#direct_field_assign,)*).values()
            }
        }
    };

    let insert_borrowed = if generate_borrowed_insert {
        let mut impl_generics = item.generics.clone();
        impl_generics.params.push(parse_quote!('insert));
        let (impl_generics, ..) = impl_generics.split_for_impl();

        quote! {
            impl #impl_generics Insertable<#table_name::table>
                for &'insert #struct_name #ty_generics
            #where_clause
            {
                type Values = <(#(#ref_field_ty,)*) as Insertable<#table_name::table>>::Values;

                fn values(self) -> <(#(#ref_field_ty,)*) as Insertable<#table_name::table>>::Values {
                    (#(#ref_field_assign,)*).values()
                }
            }
        }
    } else {
        quote! {}
    };

    wrap_in_dummy_mod(quote! {
        use diesel::insertable::Insertable;
        use diesel::internal::derives::insertable::UndecoratedInsertRecord;
        use diesel::prelude::*;

        #[allow(unused_qualifications)]
        #insert_owned

	#[allow(unused_qualifications)]
        #insert_borrowed

        impl #impl_generics UndecoratedInsertRecord<#table_name::table>
                for #struct_name #ty_generics
            #where_clause
        {
        }
    })
}

fn field_ty_embed(field: &Field, lifetime: Option<TokenStream>) -> TokenStream {
    let field_ty = &field.ty;
    let span = field.span;
    quote_spanned!(span=> #lifetime #field_ty)
}

fn field_expr_embed(field: &Field, lifetime: Option<TokenStream>) -> TokenStream {
    let field_name = &field.name;
    quote!(#lifetime self.#field_name)
}

fn field_ty_serialize_as(
    field: &Field,
    table_name: &Path,
    ty: &Type,
    treat_none_as_default_value: bool,
) -> TokenStream {
    let column_name = field.column_name();
    let span = field.span;
    if treat_none_as_default_value {
        let inner_ty = inner_of_option_ty(ty);

        quote_spanned! {span=>
            std::option::Option<diesel::dsl::Eq<
                #table_name::#column_name,
                #inner_ty,
            >>
        }
    } else {
        quote_spanned! {span=>
            diesel::dsl::Eq<
                #table_name::#column_name,
                #ty,
            >
        }
    }
}

fn field_expr_serialize_as(
    field: &Field,
    table_name: &Path,
    ty: &Type,
    treat_none_as_default_value: bool,
) -> TokenStream {
    let field_name = &field.name;
    let column_name = field.column_name();
    let column = quote!(#table_name::#column_name);
    if treat_none_as_default_value {
        if is_option_ty(ty) {
            quote!(self.#field_name.map(|x| #column.eq(::std::convert::Into::<#ty>::into(x))))
        } else {
            quote!(std::option::Option::Some(#column.eq(::std::convert::Into::<#ty>::into(self.#field_name))))
        }
    } else {
        quote!(#column.eq(::std::convert::Into::<#ty>::into(self.#field_name)))
    }
}

fn field_ty(
    field: &Field,
    table_name: &Path,
    lifetime: Option<TokenStream>,
    treat_none_as_default_value: bool,
) -> TokenStream {
    let column_name = field.column_name();
    let span = field.span;
    if treat_none_as_default_value {
        let inner_ty = inner_of_option_ty(&field.ty);

        quote_spanned! {span=>
            std::option::Option<diesel::dsl::Eq<
                #table_name::#column_name,
                #lifetime #inner_ty,
            >>
        }
    } else {
        let inner_ty = &field.ty;

        quote_spanned! {span=>
            diesel::dsl::Eq<
                #table_name::#column_name,
                #lifetime #inner_ty,
            >
        }
    }
}

fn field_expr(
    field: &Field,
    table_name: &Path,
    lifetime: Option<TokenStream>,
    treat_none_as_default_value: bool,
) -> TokenStream {
    let field_name = &field.name;
    let column_name = field.column_name();

    let column: Expr = parse_quote!(#table_name::#column_name);
    if treat_none_as_default_value {
        if is_option_ty(&field.ty) {
            if lifetime.is_some() {
                quote!(self.#field_name.as_ref().map(|x| #column.eq(x)))
            } else {
                quote!(self.#field_name.map(|x| #column.eq(x)))
            }
        } else {
            quote!(std::option::Option::Some(#column.eq(#lifetime self.#field_name)))
        }
    } else {
        quote!(#column.eq(#lifetime self.#field_name))
    }
}
<commit_msg>fix fmt<commit_after>use attrs::AttributeSpanWrapper;
use field::Field;
use model::Model;
use proc_macro2::TokenStream;
use quote::quote_spanned;
use syn::{DeriveInput, Expr, Path, Type};
use util::{inner_of_option_ty, is_option_ty, wrap_in_dummy_mod};

pub fn derive(item: DeriveInput) -> TokenStream {
    let model = Model::from_item(&item, false);

    let treat_none_as_default_value = model.treat_none_as_default_value();
    let table_name = &model.table_name();
    let struct_name = &item.ident;

    let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();

    let mut generate_borrowed_insert = true;

    let mut direct_field_ty = Vec::with_capacity(model.fields().len());
    let mut direct_field_assign = Vec::with_capacity(model.fields().len());
    let mut ref_field_ty = Vec::with_capacity(model.fields().len());
    let mut ref_field_assign = Vec::with_capacity(model.fields().len());

    for field in model.fields() {
        match (field.serialize_as.as_ref(), field.embed()) {
            (None, true) => {
                direct_field_ty.push(field_ty_embed(field, None));
                direct_field_assign.push(field_expr_embed(field, None));
                ref_field_ty.push(field_ty_embed(field, Some(quote!(&'insert))));
                ref_field_assign.push(field_expr_embed(field, Some(quote!(&))));
            }
            (None, false) => {
                direct_field_ty.push(field_ty(
                    field,
                    table_name,
                    None,
                    treat_none_as_default_value,
                ));
                direct_field_assign.push(field_expr(
                    field,
                    table_name,
                    None,
                    treat_none_as_default_value,
                ));
                ref_field_ty.push(field_ty(
                    field,
                    table_name,
                    Some(quote!(&'insert)),
                    treat_none_as_default_value,
                ));
                ref_field_assign.push(field_expr(
                    field,
                    table_name,
                    Some(quote!(&)),
                    treat_none_as_default_value,
                ));
            }
            (Some(AttributeSpanWrapper { item: ty, .. }), false) => {
                direct_field_ty.push(field_ty_serialize_as(
                    field,
                    table_name,
                    ty,
                    treat_none_as_default_value,
                ));
                direct_field_assign.push(field_expr_serialize_as(
                    field,
                    table_name,
                    ty,
                    treat_none_as_default_value,
                ));

                generate_borrowed_insert = false; // as soon as we hit one field with #[diesel(serialize_as)] there is no point in generating the impl of Insertable for borrowed structs
            }
            (Some(AttributeSpanWrapper { attribute_span, .. }), true) => {
                abort!(
                    attribute_span,
                    "`#[diesel(embed)]` cannot be combined with `#[diesel(serialize_as)]`"
                )
            }
        }
    }

    let insert_owned = quote! {
        impl #impl_generics Insertable<#table_name::table> for #struct_name #ty_generics
            #where_clause
        {
            type Values = <(#(#direct_field_ty,)*) as Insertable<#table_name::table>>::Values;

            fn values(self) -> <(#(#direct_field_ty,)*) as Insertable<#table_name::table>>::Values {
                (#(#direct_field_assign,)*).values()
            }
        }
    };

    let insert_borrowed = if generate_borrowed_insert {
        let mut impl_generics = item.generics.clone();
        impl_generics.params.push(parse_quote!('insert));
        let (impl_generics, ..) = impl_generics.split_for_impl();

        quote! {
            impl #impl_generics Insertable<#table_name::table>
                for &'insert #struct_name #ty_generics
            #where_clause
            {
                type Values = <(#(#ref_field_ty,)*) as Insertable<#table_name::table>>::Values;

                fn values(self) -> <(#(#ref_field_ty,)*) as Insertable<#table_name::table>>::Values {
                    (#(#ref_field_assign,)*).values()
                }
            }
        }
    } else {
        quote! {}
    };

    wrap_in_dummy_mod(quote! {
        use diesel::insertable::Insertable;
        use diesel::internal::derives::insertable::UndecoratedInsertRecord;
        use diesel::prelude::*;

        #[allow(unused_qualifications)]
        #insert_owned

    #[allow(unused_qualifications)]
        #insert_borrowed

        impl #impl_generics UndecoratedInsertRecord<#table_name::table>
                for #struct_name #ty_generics
            #where_clause
        {
        }
    })
}

fn field_ty_embed(field: &Field, lifetime: Option<TokenStream>) -> TokenStream {
    let field_ty = &field.ty;
    let span = field.span;
    quote_spanned!(span=> #lifetime #field_ty)
}

fn field_expr_embed(field: &Field, lifetime: Option<TokenStream>) -> TokenStream {
    let field_name = &field.name;
    quote!(#lifetime self.#field_name)
}

fn field_ty_serialize_as(
    field: &Field,
    table_name: &Path,
    ty: &Type,
    treat_none_as_default_value: bool,
) -> TokenStream {
    let column_name = field.column_name();
    let span = field.span;
    if treat_none_as_default_value {
        let inner_ty = inner_of_option_ty(ty);

        quote_spanned! {span=>
            std::option::Option<diesel::dsl::Eq<
                #table_name::#column_name,
                #inner_ty,
            >>
        }
    } else {
        quote_spanned! {span=>
            diesel::dsl::Eq<
                #table_name::#column_name,
                #ty,
            >
        }
    }
}

fn field_expr_serialize_as(
    field: &Field,
    table_name: &Path,
    ty: &Type,
    treat_none_as_default_value: bool,
) -> TokenStream {
    let field_name = &field.name;
    let column_name = field.column_name();
    let column = quote!(#table_name::#column_name);
    if treat_none_as_default_value {
        if is_option_ty(ty) {
            quote!(self.#field_name.map(|x| #column.eq(::std::convert::Into::<#ty>::into(x))))
        } else {
            quote!(std::option::Option::Some(#column.eq(::std::convert::Into::<#ty>::into(self.#field_name))))
        }
    } else {
        quote!(#column.eq(::std::convert::Into::<#ty>::into(self.#field_name)))
    }
}

fn field_ty(
    field: &Field,
    table_name: &Path,
    lifetime: Option<TokenStream>,
    treat_none_as_default_value: bool,
) -> TokenStream {
    let column_name = field.column_name();
    let span = field.span;
    if treat_none_as_default_value {
        let inner_ty = inner_of_option_ty(&field.ty);

        quote_spanned! {span=>
            std::option::Option<diesel::dsl::Eq<
                #table_name::#column_name,
                #lifetime #inner_ty,
            >>
        }
    } else {
        let inner_ty = &field.ty;

        quote_spanned! {span=>
            diesel::dsl::Eq<
                #table_name::#column_name,
                #lifetime #inner_ty,
            >
        }
    }
}

fn field_expr(
    field: &Field,
    table_name: &Path,
    lifetime: Option<TokenStream>,
    treat_none_as_default_value: bool,
) -> TokenStream {
    let field_name = &field.name;
    let column_name = field.column_name();

    let column: Expr = parse_quote!(#table_name::#column_name);
    if treat_none_as_default_value {
        if is_option_ty(&field.ty) {
            if lifetime.is_some() {
                quote!(self.#field_name.as_ref().map(|x| #column.eq(x)))
            } else {
                quote!(self.#field_name.map(|x| #column.eq(x)))
            }
        } else {
            quote!(std::option::Option::Some(#column.eq(#lifetime self.#field_name)))
        }
    } else {
        quote!(#column.eq(#lifetime self.#field_name))
    }
}
<|endoftext|>


<commit_before>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![deny(warnings)]

#[macro_use]
extern crate build_helper;
extern crate gcc;

use std::env;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use std::process::Command;
use build_helper::{run, rerun_if_changed_anything_in_dir, up_to_date};

fn main() {
    println!("cargo:rustc-cfg=cargobuild");
    println!("cargo:rerun-if-changed=build.rs");

    // FIXME: This is a hack to support building targets that don't
    // support jemalloc alongside hosts that do. The jemalloc build is
    // controlled by a feature of the std crate, and if that feature
    // changes between targets, it invalidates the fingerprint of
    // std's build script (this is a cargo bug); so we must ensure
    // that the feature set used by std is the same across all
    // targets, which means we have to build the alloc_jemalloc crate
    // for targets like emscripten, even if we don't use it.
    let target = env::var("TARGET").expect("TARGET was not set");
    let host = env::var("HOST").expect("HOST was not set");
    if target.contains("rumprun") || target.contains("bitrig") || target.contains("openbsd") ||
       target.contains("msvc") || target.contains("emscripten") || target.contains("fuchsia") ||
       target.contains("redox") {
        println!("cargo:rustc-cfg=dummy_jemalloc");
        return;
    }

    if let Some(jemalloc) = env::var_os("JEMALLOC_OVERRIDE") {
        let jemalloc = PathBuf::from(jemalloc);
        println!("cargo:rustc-link-search=native={}",
                 jemalloc.parent().unwrap().display());
        let stem = jemalloc.file_stem().unwrap().to_str().unwrap();
        let name = jemalloc.file_name().unwrap().to_str().unwrap();
        let kind = if name.ends_with(".a") {
            "static"
        } else {
            "dylib"
        };
        println!("cargo:rustc-link-lib={}={}", kind, &stem[3..]);
        return;
    }

    let build_dir = env::var_os("RUSTBUILD_NATIVE_DIR").unwrap_or(env::var_os("OUT_DIR").unwrap());
    let build_dir = PathBuf::from(build_dir).join("jemalloc");
    let _ = fs::create_dir_all(&build_dir);

    if target.contains("windows") {
        println!("cargo:rustc-link-lib=static=jemalloc");
    } else {
        println!("cargo:rustc-link-lib=static=jemalloc_pic");
    }
    println!("cargo:rustc-link-search=native={}/lib", build_dir.display());
    if target.contains("android") {
        println!("cargo:rustc-link-lib=gcc");
    } else if !target.contains("windows") && !target.contains("musl") {
        println!("cargo:rustc-link-lib=pthread");
    }
    let src_dir = env::current_dir().unwrap().join("../jemalloc");
    rerun_if_changed_anything_in_dir(&src_dir);
    let timestamp = build_dir.join("rustbuild.timestamp");
    if up_to_date(&Path::new("build.rs"), &timestamp) && up_to_date(&src_dir, &timestamp) {
        return
    }

    let compiler = gcc::Config::new().get_compiler();
    // only msvc returns None for ar so unwrap is okay
    let ar = build_helper::cc2ar(compiler.path(), &target).unwrap();
    let cflags = compiler.args()
        .iter()
        .map(|s| s.to_str().unwrap())
        .collect::<Vec<_>>()
        .join(" ");

    let mut cmd = Command::new("sh");
    cmd.arg(src_dir.join("configure")
                   .to_str()
                   .unwrap()
                   .replace("C:\\", "/c/")
                   .replace("\\", "/"))
       .current_dir(&build_dir)
       .env("CC", compiler.path())
       .env("EXTRA_CFLAGS", cflags.clone())
       // jemalloc generates Makefile deps using GCC's "-MM" flag. This means
       // that GCC will run the preprocessor, and only the preprocessor, over
       // jemalloc's source files. If we don't specify CPPFLAGS, then at least
       // on ARM that step fails with a "Missing implementation for 32-bit
       // atomic operations" error. This is because no "-march" flag will be
       // passed to GCC, and then GCC won't define the
       // "__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4" macro that jemalloc needs to
       // select an atomic operation implementation.
       .env("CPPFLAGS", cflags.clone())
       .env("AR", &ar)
       .env("RANLIB", format!("{} s", ar.display()));

    if target.contains("windows") {
        // A bit of history here, this used to be --enable-lazy-lock added in
        // #14006 which was filed with jemalloc in jemalloc/jemalloc#83 which
        // was also reported to MinGW:
        //
        //  http://sourceforge.net/p/mingw-w64/bugs/395/
        //
        // When updating jemalloc to 4.0, however, it was found that binaries
        // would exit with the status code STATUS_RESOURCE_NOT_OWNED indicating
        // that a thread was unlocking a mutex it never locked. Disabling this
        // "lazy lock" option seems to fix the issue, but it was enabled by
        // default for MinGW targets in 13473c7 for jemalloc.
        //
        // As a result of all that, force disabling lazy lock on Windows, and
        // after reading some code it at least *appears* that the initialization
        // of mutexes is otherwise ok in jemalloc, so shouldn't cause problems
        // hopefully...
        //
        // tl;dr: make windows behave like other platforms by disabling lazy
        //        locking, but requires passing an option due to a historical
        //        default with jemalloc.
        cmd.arg("--disable-lazy-lock");
    } else if target.contains("ios") {
        cmd.arg("--disable-tls");
    } else if target.contains("android") {
        // We force android to have prefixed symbols because apparently
        // replacement of the libc allocator doesn't quite work. When this was
        // tested (unprefixed symbols), it was found that the `realpath`
        // function in libc would allocate with libc malloc (not jemalloc
        // malloc), and then the standard library would free with jemalloc free,
        // causing a segfault.
        //
        // If the test suite passes, however, without symbol prefixes then we
        // should be good to go!
        cmd.arg("--with-jemalloc-prefix=je_");
        cmd.arg("--disable-tls");
    } else if target.contains("dragonfly") {
        cmd.arg("--with-jemalloc-prefix=je_");
    }

    if cfg!(feature = "debug-jemalloc") {
        cmd.arg("--enable-debug");
    }

    // Turn off broken quarantine (see jemalloc/jemalloc#161)
    cmd.arg("--disable-fill");
    cmd.arg(format!("--host={}", build_helper::gnu_target(&target)));
    cmd.arg(format!("--build={}", build_helper::gnu_target(&host)));

    // for some reason, jemalloc configure doesn't detect this value
    // automatically for this target
    if target == "sparc64-unknown-linux-gnu" {
        cmd.arg("--with-lg-quantum=4");
    }

    run(&mut cmd);

    let mut make = Command::new(build_helper::make(&host));
    make.current_dir(&build_dir)
        .arg("build_lib_static");

    // mingw make seems... buggy? unclear...
    if !host.contains("windows") {
        make.arg("-j")
            .arg(env::var("NUM_JOBS").expect("NUM_JOBS was not set"));
    }

    run(&mut make);

    // The pthread_atfork symbols is used by jemalloc on android but the really
    // old android we're building on doesn't have them defined, so just make
    // sure the symbols are available.
    if target.contains("androideabi") {
        println!("cargo:rerun-if-changed=pthread_atfork_dummy.c");
        gcc::Config::new()
            .flag("-fvisibility=hidden")
            .file("pthread_atfork_dummy.c")
            .compile("libpthread_atfork_dummy.a");
    }

    t!(File::create(&timestamp));
}
<commit_msg>Rollup merge of #39481 - ishitatsuyuki:master, r=alexcrichton<commit_after>// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![deny(warnings)]

#[macro_use]
extern crate build_helper;
extern crate gcc;

use std::env;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
use std::process::Command;
use build_helper::{run, rerun_if_changed_anything_in_dir, up_to_date};

fn main() {
    println!("cargo:rustc-cfg=cargobuild");
    println!("cargo:rerun-if-changed=build.rs");

    // FIXME: This is a hack to support building targets that don't
    // support jemalloc alongside hosts that do. The jemalloc build is
    // controlled by a feature of the std crate, and if that feature
    // changes between targets, it invalidates the fingerprint of
    // std's build script (this is a cargo bug); so we must ensure
    // that the feature set used by std is the same across all
    // targets, which means we have to build the alloc_jemalloc crate
    // for targets like emscripten, even if we don't use it.
    let target = env::var("TARGET").expect("TARGET was not set");
    let host = env::var("HOST").expect("HOST was not set");
    if target.contains("rumprun") || target.contains("bitrig") || target.contains("openbsd") ||
       target.contains("msvc") || target.contains("emscripten") || target.contains("fuchsia") ||
       target.contains("redox") {
        println!("cargo:rustc-cfg=dummy_jemalloc");
        return;
    }

    if target.contains("android") {
        println!("cargo:rustc-link-lib=gcc");
    } else if !target.contains("windows") && !target.contains("musl") {
        println!("cargo:rustc-link-lib=pthread");
    }

    if let Some(jemalloc) = env::var_os("JEMALLOC_OVERRIDE") {
        let jemalloc = PathBuf::from(jemalloc);
        println!("cargo:rustc-link-search=native={}",
                 jemalloc.parent().unwrap().display());
        let stem = jemalloc.file_stem().unwrap().to_str().unwrap();
        let name = jemalloc.file_name().unwrap().to_str().unwrap();
        let kind = if name.ends_with(".a") {
            "static"
        } else {
            "dylib"
        };
        println!("cargo:rustc-link-lib={}={}", kind, &stem[3..]);
        return;
    }

    let build_dir = env::var_os("RUSTBUILD_NATIVE_DIR").unwrap_or(env::var_os("OUT_DIR").unwrap());
    let build_dir = PathBuf::from(build_dir).join("jemalloc");
    let _ = fs::create_dir_all(&build_dir);

    if target.contains("windows") {
        println!("cargo:rustc-link-lib=static=jemalloc");
    } else {
        println!("cargo:rustc-link-lib=static=jemalloc_pic");
    }
    println!("cargo:rustc-link-search=native={}/lib", build_dir.display());
    let src_dir = env::current_dir().unwrap().join("../jemalloc");
    rerun_if_changed_anything_in_dir(&src_dir);
    let timestamp = build_dir.join("rustbuild.timestamp");
    if up_to_date(&Path::new("build.rs"), &timestamp) && up_to_date(&src_dir, &timestamp) {
        return
    }

    let compiler = gcc::Config::new().get_compiler();
    // only msvc returns None for ar so unwrap is okay
    let ar = build_helper::cc2ar(compiler.path(), &target).unwrap();
    let cflags = compiler.args()
        .iter()
        .map(|s| s.to_str().unwrap())
        .collect::<Vec<_>>()
        .join(" ");

    let mut cmd = Command::new("sh");
    cmd.arg(src_dir.join("configure")
                   .to_str()
                   .unwrap()
                   .replace("C:\\", "/c/")
                   .replace("\\", "/"))
       .current_dir(&build_dir)
       .env("CC", compiler.path())
       .env("EXTRA_CFLAGS", cflags.clone())
       // jemalloc generates Makefile deps using GCC's "-MM" flag. This means
       // that GCC will run the preprocessor, and only the preprocessor, over
       // jemalloc's source files. If we don't specify CPPFLAGS, then at least
       // on ARM that step fails with a "Missing implementation for 32-bit
       // atomic operations" error. This is because no "-march" flag will be
       // passed to GCC, and then GCC won't define the
       // "__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4" macro that jemalloc needs to
       // select an atomic operation implementation.
       .env("CPPFLAGS", cflags.clone())
       .env("AR", &ar)
       .env("RANLIB", format!("{} s", ar.display()));

    if target.contains("windows") {
        // A bit of history here, this used to be --enable-lazy-lock added in
        // #14006 which was filed with jemalloc in jemalloc/jemalloc#83 which
        // was also reported to MinGW:
        //
        //  http://sourceforge.net/p/mingw-w64/bugs/395/
        //
        // When updating jemalloc to 4.0, however, it was found that binaries
        // would exit with the status code STATUS_RESOURCE_NOT_OWNED indicating
        // that a thread was unlocking a mutex it never locked. Disabling this
        // "lazy lock" option seems to fix the issue, but it was enabled by
        // default for MinGW targets in 13473c7 for jemalloc.
        //
        // As a result of all that, force disabling lazy lock on Windows, and
        // after reading some code it at least *appears* that the initialization
        // of mutexes is otherwise ok in jemalloc, so shouldn't cause problems
        // hopefully...
        //
        // tl;dr: make windows behave like other platforms by disabling lazy
        //        locking, but requires passing an option due to a historical
        //        default with jemalloc.
        cmd.arg("--disable-lazy-lock");
    } else if target.contains("ios") {
        cmd.arg("--disable-tls");
    } else if target.contains("android") {
        // We force android to have prefixed symbols because apparently
        // replacement of the libc allocator doesn't quite work. When this was
        // tested (unprefixed symbols), it was found that the `realpath`
        // function in libc would allocate with libc malloc (not jemalloc
        // malloc), and then the standard library would free with jemalloc free,
        // causing a segfault.
        //
        // If the test suite passes, however, without symbol prefixes then we
        // should be good to go!
        cmd.arg("--with-jemalloc-prefix=je_");
        cmd.arg("--disable-tls");
    } else if target.contains("dragonfly") {
        cmd.arg("--with-jemalloc-prefix=je_");
    }

    if cfg!(feature = "debug-jemalloc") {
        cmd.arg("--enable-debug");
    }

    // Turn off broken quarantine (see jemalloc/jemalloc#161)
    cmd.arg("--disable-fill");
    cmd.arg(format!("--host={}", build_helper::gnu_target(&target)));
    cmd.arg(format!("--build={}", build_helper::gnu_target(&host)));

    // for some reason, jemalloc configure doesn't detect this value
    // automatically for this target
    if target == "sparc64-unknown-linux-gnu" {
        cmd.arg("--with-lg-quantum=4");
    }

    run(&mut cmd);

    let mut make = Command::new(build_helper::make(&host));
    make.current_dir(&build_dir)
        .arg("build_lib_static");

    // mingw make seems... buggy? unclear...
    if !host.contains("windows") {
        make.arg("-j")
            .arg(env::var("NUM_JOBS").expect("NUM_JOBS was not set"));
    }

    run(&mut make);

    // The pthread_atfork symbols is used by jemalloc on android but the really
    // old android we're building on doesn't have them defined, so just make
    // sure the symbols are available.
    if target.contains("androideabi") {
        println!("cargo:rerun-if-changed=pthread_atfork_dummy.c");
        gcc::Config::new()
            .flag("-fvisibility=hidden")
            .file("pthread_atfork_dummy.c")
            .compile("libpthread_atfork_dummy.a");
    }

    t!(File::create(&timestamp));
}
<|endoftext|>


<commit_before>//! Representations of C types and arrays thereof.
//!
//! These are used to describe the types of the arguments and results of
//! functions. When we construct a CIF (“Call InterFace”), we provide a
//! sequence of argument types and a result type, and libffi uses this
//! to figure out how to set up a call to a function with those types.

use std::fmt;
use std::mem;
use std::ptr;
use libc;

use low;

#[cfg(feature = "unique")]
use std::ptr::Unique;
#[cfg(not (feature = "unique"))]
use super::util::Unique;

// Internally we represent types and type arrays using raw pointers,
// since this is what libffi understands. Below we wrap them with
// types that implement Drop and Clone.

type Type_      = *mut low::ffi_type;
type TypeArray_ = *mut Type_;

// Informal indication that the object should be considered owned by
// the given reference.
type Owned<T>      = T;

/// Represents a single C type.
///
/// # Example
///
/// Suppose we have a C struct:
///
/// ```c
/// struct my_struct {
///     uint16_t f1;
///     uint64_t f2;
/// };
/// ```
///
/// To pass the struct by value via libffi, we need to construct a
/// `Type` object describing its layout:
///
/// ```
/// use libffi::middle::Type;
///
/// let my_struct = Type::structure(vec![
///     Type::u64(),
///     Type::u16(),
/// ].into_iter());
/// ```
pub struct Type(Unique<low::ffi_type>);

/// Represents a sequence of C types.
///
/// This can be used to construct a struct type or as the arguments
/// when creating a [`Cif`](../middle/struct.Cif.html).
pub struct TypeArray(Unique<*mut low::ffi_type>);

impl fmt::Debug for Type {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_fmt(format_args!("Type({:?})", *self.0))
    }
}

impl fmt::Debug for TypeArray {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_fmt(format_args!("TypeArray({:?})", *self.0))
    }
}

/// Computes the length of a raw `TypeArray_` by searching for the
/// null terminator.
unsafe fn ffi_type_array_len(mut array: TypeArray_) -> usize {
    let mut count   = 0;
    while !(*array).is_null() {
        count += 1;
        array = array.offset(1);
    }
    count
}

/// Creates an empty `TypeArray_` with null terminator.
unsafe fn ffi_type_array_create_empty(len: usize) -> Owned<TypeArray_> {
    let array = libc::malloc((len + 1) * mem::size_of::<Type_>())
                    as TypeArray_;
    assert!(!array.is_null(),
            "ffi_type_array_create_empty: out of memory");
    *array.offset(len as isize) = ptr::null_mut::<low::ffi_type>() as Type_;
    array
}

/// Creates a null-terminated array of Type_. Takes ownership of
/// the elements.
unsafe fn ffi_type_array_create<I>(elements: I) -> Owned<TypeArray_>
    where I: ExactSizeIterator<Item=Type>
{
    let size = elements.len();
    let new  = ffi_type_array_create_empty(size);
    for (i, element) in elements.enumerate() {
        *new.offset(i as isize) = *element.0;
    }

    new
}

/// Creates a struct type from a raw array of element types.
unsafe fn ffi_type_struct_create_raw(elements: Owned<TypeArray_>)
    -> Owned<Type_>
{
    let new = libc::malloc(mem::size_of::<low::ffi_type>()) as Type_;
    assert!(!new.is_null(),
            "ffi_type_struct_create_raw: out of memory");

    (*new).size      = 0;
    (*new).alignment = 0;
    (*new).type_     = low::type_tag::STRUCT;
    (*new).elements  = elements;

    new
}

/// Creates a struct ffi_type with the given elements. Takes ownership
/// of the elements.
unsafe fn ffi_type_struct_create<I>(elements: I) -> Owned<Type_>
    where I: ExactSizeIterator<Item=Type>
{
    ffi_type_struct_create_raw(ffi_type_array_create(elements))
}

/// Makes a copy of a type array.
unsafe fn ffi_type_array_clone(old: TypeArray_) -> Owned<TypeArray_> {
    let size = ffi_type_array_len(old);
    let new  = ffi_type_array_create_empty(size);

    for i in 0 .. size {
        *new.offset(i as isize) = ffi_type_clone(*old.offset(i as isize));
    }

    new
}

/// Makes a copy of a type.
unsafe fn ffi_type_clone(old: Type_) -> Owned<Type_> {
    if (*old).type_ == low::type_tag::STRUCT {
        ffi_type_struct_create_raw(ffi_type_array_clone((*old).elements))
    } else {
        old
    }
}

/// Destroys a TypeArray_ and all of its elements.
unsafe fn ffi_type_array_destroy(victim: Owned<TypeArray_>) {
    let mut current = victim;
    while !(*current).is_null() {
        ffi_type_destroy(*current);
        current = current.offset(1);
    }

    libc::free(victim as *mut libc::c_void);
}

/// Destroys a Type_ if it was dynamically allocated.
unsafe fn ffi_type_destroy(victim: Owned<Type_>) {
    if (*victim).type_ == low::type_tag::STRUCT {
        ffi_type_array_destroy((*victim).elements);
        libc::free(victim as *mut libc::c_void);
    }
}

impl Drop for Type {
    fn drop(&mut self) {
        unsafe { ffi_type_destroy(*self.0) }
    }
}

impl Drop for TypeArray {
    fn drop(&mut self) {
        unsafe { ffi_type_array_destroy(*self.0) }
    }
}

impl Clone for Type {
    fn clone(&self) -> Self {
        unsafe { Type(Unique::new(ffi_type_clone(*self.0))) }
    }
}

impl Clone for TypeArray {
    fn clone(&self) -> Self {
        unsafe {
            TypeArray(Unique::new(ffi_type_array_clone(*self.0)))
        }
    }
}

macro_rules! match_size_signed {
    ( $name:ident ) => {
        match mem::size_of::<libc::$name>() {
            1 => Self::i8(),
            2 => Self::i16(),
            4 => Self::i32(),
            8 => Self::i64(),
            _  => panic!("Strange size for C type"),
        }
    }
}

macro_rules! match_size_unsigned {
    ( $name:ident ) => {
        match mem::size_of::<libc::$name>() {
            1 => Self::u8(),
            2 => Self::u16(),
            4 => Self::u32(),
            8 => Self::u64(),
            _  => panic!("Strange size for C type"),
        }
    }
}

impl Type {
    /// Returns the representation of the C `void` type.
    ///
    /// This is used only for the return type of a CIF, not for an
    /// argument or struct member.
    pub fn void() -> Self {
        Type(unsafe { Unique::new(&mut low::types::void) })
    }

    /// Returns the unsigned 8-bit numeric type.
    pub fn u8() -> Self {
        Type(unsafe { Unique::new(&mut low::types::uint8) })
    }

    /// Returns the signed 8-bit numeric type.
    pub fn i8() -> Self {
        Type(unsafe { Unique::new(&mut low::types::sint8) })
    }

    /// Returns the unsigned 16-bit numeric type.
    pub fn u16() -> Self {
        Type(unsafe { Unique::new(&mut low::types::uint16) })
    }

    /// Returns the signed 16-bit numeric type.
    pub fn i16() -> Self {
        Type(unsafe { Unique::new(&mut low::types::sint16) })
    }

    /// Returns the unsigned 32-bit numeric type.
    pub fn u32() -> Self {
        Type(unsafe { Unique::new(&mut low::types::uint32) })
    }

    /// Returns the signed 32-bit numeric type.
    pub fn i32() -> Self {
        Type(unsafe { Unique::new(&mut low::types::sint32) })
    }

    /// Returns the unsigned 64-bit numeric type.
    pub fn u64() -> Self {
        Type(unsafe { Unique::new(&mut low::types::uint64) })
    }

    /// Returns the signed 64-bit numeric type.
    pub fn i64() -> Self {
        Type(unsafe { Unique::new(&mut low::types::sint64) })
    }

    /// Returns the C equivalent of Rust `usize` (`u16`).
    #[cfg(target_pointer_width = "16")]
    pub fn usize() -> Self {
        Self::u16()
    }

    /// Returns the C equivalent of Rust `isize` (`i16`).
    #[cfg(target_pointer_width = "16")]
    pub fn isize() -> Self {
        Self::i16()
    }

    /// Returns the C equivalent of Rust `usize` (`u32`).
    #[cfg(target_pointer_width = "32")]
    pub fn usize() -> Self {
        Self::u32()
    }

    /// Returns the C equivalent of Rust `isize` (`i32`).
    #[cfg(target_pointer_width = "32")]
    pub fn isize() -> Self {
        Self::i32()
    }

    /// Returns the C equivalent of Rust `usize` (`u64`).
    #[cfg(target_pointer_width = "64")]
    pub fn usize() -> Self {
        Self::u64()
    }

    /// Returns the C equivalent of Rust `isize` (`i64`).
    #[cfg(target_pointer_width = "64")]
    pub fn isize() -> Self {
        Self::i64()
    }

    /// Returns the C `signed char` type.
    pub fn c_schar() -> Self {
        match_size_signed!(c_schar)
    }

    /// Returns the C `unsigned char` type.
    pub fn c_uchar() -> Self {
        match_size_unsigned!(c_uchar)
    }

    /// Returns the C `short` type.
    pub fn c_short() -> Self {
        match_size_signed!(c_short)
    }

    /// Returns the C `unsigned short` type.
    pub fn c_ushort() -> Self {
        match_size_unsigned!(c_ushort)
    }

    /// Returns the C `int` type.
    pub fn c_int() -> Self {
        match_size_signed!(c_int)
    }

    /// Returns the C `unsigned int` type.
    pub fn c_uint() -> Self {
        match_size_unsigned!(c_uint)
    }

    /// Returns the C `long` type.
    pub fn c_long() -> Self {
        match_size_signed!(c_long)
    }

    /// Returns the C `unsigned long` type.
    pub fn c_ulong() -> Self {
        match_size_unsigned!(c_ulong)
    }

    /// Returns the C `longlong` type.
    pub fn c_longlong() -> Self {
        match_size_signed!(c_longlong)
    }

    /// Returns the C `unsigned longlong` type.
    pub fn c_ulonglong() -> Self {
        match_size_unsigned!(c_ulonglong)
    }

    /// Returns the C `float` (32-bit floating point) type.
    pub fn f32() -> Self {
        Type(unsafe { Unique::new(&mut low::types::float) })
    }

    /// Returns the C `double` (64-bit floating point) type.
    pub fn f64() -> Self {
        Type(unsafe { Unique::new(&mut low::types::double) })
    }

    /// Returns the C `void*` type, for passing any kind of pointer.
    pub fn pointer() -> Self {
        Type(unsafe { Unique::new(&mut low::types::pointer) })
    }

    /// Returns the C `long double` (extended-precision floating point) type.
    pub fn longdouble() -> Self {
        Type(unsafe { Unique::new(&mut low::types::longdouble) })
    }

    /// Returns the C `_Complex float` type.
    pub fn c32() -> Self {
        Type(unsafe { Unique::new(&mut low::types::complex_float) })
    }

    /// Returns the C `_Complex double` type.
    pub fn c64() -> Self {
        Type(unsafe { Unique::new(&mut low::types::complex_double) })
    }

    /// Returns the C `_Complex long double` type.
    pub fn complex_longdouble() -> Self {
        Type(unsafe { Unique::new(&mut low::types::complex_longdouble) })
    }

    /// Constructs a structure type whose fields have the given types.
    pub fn structure<I>(fields: I) -> Self
        where I: ExactSizeIterator<Item=Type>
    {
        unsafe {
            Type(Unique::new(ffi_type_struct_create(fields)))
        }
    }

    /// Constructs a structure type whose fields have the given types.
    pub fn structure_from_array(fields: TypeArray) -> Self {
        unsafe {
            Type(Unique::new(ffi_type_struct_create_raw(*fields.0)))
        }
    }

    /// Gets a raw pointer to the underlying
    /// [`ffi_type`](../../low/struct.ffi_type.html).
    ///
    /// This method may be useful for interacting with the
    /// [`low`](../../low/index.html) and
    /// [`raw`](../../raw/index.html) layers.
    pub fn as_raw_ptr(&self) -> *mut low::ffi_type {
        *self.0
    }
}

impl TypeArray {
    /// Constructs an array the given `Type`s.
    pub fn new<I>(elements: I) -> Self
        where I: ExactSizeIterator<Item=Type>
    {
        unsafe { TypeArray(Unique::new(ffi_type_array_create(elements))) }
    }

    /// The length of this array of `Type`s.
    pub fn len(&self) -> usize {
        unsafe { ffi_type_array_len(*self.0) }
    }

    /// Gets a raw pointer to the underlying C array of
    /// [`ffi_type`](../../low/struct.ffi_type.html)s.
    ///
    /// The C array is null-terminated.
    ///
    /// This method may be useful for interacting with the
    /// [`low`](../../low/index.html) and
    /// [`raw`](../../raw/index.html) layers.
    pub fn as_raw_ptr(&self) -> *mut *mut low::ffi_type {
        *self.0
    }
}

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

    #[test]
    fn create_u64() {
        Type::u64();
    }

    #[test]
    fn clone_u64() {
        Type::u64().clone().clone();
    }

    #[test]
    fn create_struct() {
        Type::structure(vec![Type::i64(),
                             Type::i64(),
                             Type::u64()].into_iter());
    }

    #[test]
    fn clone_struct() {
        Type::structure(vec![Type::i64(),
                             Type::i64(),
                             Type::u64()].into_iter()).clone().clone();
    }

}
<commit_msg>removed stray pub reference to TypeArray.<commit_after>//! Representations of C types and arrays thereof.
//!
//! These are used to describe the types of the arguments and results of
//! functions. When we construct a CIF (“Call InterFace”), we provide a
//! sequence of argument types and a result type, and libffi uses this
//! to figure out how to set up a call to a function with those types.

use std::fmt;
use std::mem;
use std::ptr;
use libc;

use low;

#[cfg(feature = "unique")]
use std::ptr::Unique;
#[cfg(not (feature = "unique"))]
use super::util::Unique;

// Internally we represent types and type arrays using raw pointers,
// since this is what libffi understands. Below we wrap them with
// types that implement Drop and Clone.

type Type_      = *mut low::ffi_type;
type TypeArray_ = *mut Type_;

// Informal indication that the object should be considered owned by
// the given reference.
type Owned<T>      = T;

/// Represents a single C type.
///
/// # Example
///
/// Suppose we have a C struct:
///
/// ```c
/// struct my_struct {
///     uint16_t f1;
///     uint64_t f2;
/// };
/// ```
///
/// To pass the struct by value via libffi, we need to construct a
/// `Type` object describing its layout:
///
/// ```
/// use libffi::middle::Type;
///
/// let my_struct = Type::structure(vec![
///     Type::u64(),
///     Type::u16(),
/// ].into_iter());
/// ```
pub struct Type(Unique<low::ffi_type>);

/// Represents a sequence of C types.
///
/// This can be used to construct a struct type or as the arguments
/// when creating a [`Cif`](../middle/struct.Cif.html).
pub struct TypeArray(Unique<*mut low::ffi_type>);

impl fmt::Debug for Type {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_fmt(format_args!("Type({:?})", *self.0))
    }
}

impl fmt::Debug for TypeArray {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_fmt(format_args!("TypeArray({:?})", *self.0))
    }
}

/// Computes the length of a raw `TypeArray_` by searching for the
/// null terminator.
unsafe fn ffi_type_array_len(mut array: TypeArray_) -> usize {
    let mut count   = 0;
    while !(*array).is_null() {
        count += 1;
        array = array.offset(1);
    }
    count
}

/// Creates an empty `TypeArray_` with null terminator.
unsafe fn ffi_type_array_create_empty(len: usize) -> Owned<TypeArray_> {
    let array = libc::malloc((len + 1) * mem::size_of::<Type_>())
                    as TypeArray_;
    assert!(!array.is_null(),
            "ffi_type_array_create_empty: out of memory");
    *array.offset(len as isize) = ptr::null_mut::<low::ffi_type>() as Type_;
    array
}

/// Creates a null-terminated array of Type_. Takes ownership of
/// the elements.
unsafe fn ffi_type_array_create<I>(elements: I) -> Owned<TypeArray_>
    where I: ExactSizeIterator<Item=Type>
{
    let size = elements.len();
    let new  = ffi_type_array_create_empty(size);
    for (i, element) in elements.enumerate() {
        *new.offset(i as isize) = *element.0;
    }

    new
}

/// Creates a struct type from a raw array of element types.
unsafe fn ffi_type_struct_create_raw(elements: Owned<TypeArray_>)
    -> Owned<Type_>
{
    let new = libc::malloc(mem::size_of::<low::ffi_type>()) as Type_;
    assert!(!new.is_null(),
            "ffi_type_struct_create_raw: out of memory");

    (*new).size      = 0;
    (*new).alignment = 0;
    (*new).type_     = low::type_tag::STRUCT;
    (*new).elements  = elements;

    new
}

/// Creates a struct ffi_type with the given elements. Takes ownership
/// of the elements.
unsafe fn ffi_type_struct_create<I>(elements: I) -> Owned<Type_>
    where I: ExactSizeIterator<Item=Type>
{
    ffi_type_struct_create_raw(ffi_type_array_create(elements))
}

/// Makes a copy of a type array.
unsafe fn ffi_type_array_clone(old: TypeArray_) -> Owned<TypeArray_> {
    let size = ffi_type_array_len(old);
    let new  = ffi_type_array_create_empty(size);

    for i in 0 .. size {
        *new.offset(i as isize) = ffi_type_clone(*old.offset(i as isize));
    }

    new
}

/// Makes a copy of a type.
unsafe fn ffi_type_clone(old: Type_) -> Owned<Type_> {
    if (*old).type_ == low::type_tag::STRUCT {
        ffi_type_struct_create_raw(ffi_type_array_clone((*old).elements))
    } else {
        old
    }
}

/// Destroys a TypeArray_ and all of its elements.
unsafe fn ffi_type_array_destroy(victim: Owned<TypeArray_>) {
    let mut current = victim;
    while !(*current).is_null() {
        ffi_type_destroy(*current);
        current = current.offset(1);
    }

    libc::free(victim as *mut libc::c_void);
}

/// Destroys a Type_ if it was dynamically allocated.
unsafe fn ffi_type_destroy(victim: Owned<Type_>) {
    if (*victim).type_ == low::type_tag::STRUCT {
        ffi_type_array_destroy((*victim).elements);
        libc::free(victim as *mut libc::c_void);
    }
}

impl Drop for Type {
    fn drop(&mut self) {
        unsafe { ffi_type_destroy(*self.0) }
    }
}

impl Drop for TypeArray {
    fn drop(&mut self) {
        unsafe { ffi_type_array_destroy(*self.0) }
    }
}

impl Clone for Type {
    fn clone(&self) -> Self {
        unsafe { Type(Unique::new(ffi_type_clone(*self.0))) }
    }
}

impl Clone for TypeArray {
    fn clone(&self) -> Self {
        unsafe {
            TypeArray(Unique::new(ffi_type_array_clone(*self.0)))
        }
    }
}

macro_rules! match_size_signed {
    ( $name:ident ) => {
        match mem::size_of::<libc::$name>() {
            1 => Self::i8(),
            2 => Self::i16(),
            4 => Self::i32(),
            8 => Self::i64(),
            _  => panic!("Strange size for C type"),
        }
    }
}

macro_rules! match_size_unsigned {
    ( $name:ident ) => {
        match mem::size_of::<libc::$name>() {
            1 => Self::u8(),
            2 => Self::u16(),
            4 => Self::u32(),
            8 => Self::u64(),
            _  => panic!("Strange size for C type"),
        }
    }
}

impl Type {
    /// Returns the representation of the C `void` type.
    ///
    /// This is used only for the return type of a CIF, not for an
    /// argument or struct member.
    pub fn void() -> Self {
        Type(unsafe { Unique::new(&mut low::types::void) })
    }

    /// Returns the unsigned 8-bit numeric type.
    pub fn u8() -> Self {
        Type(unsafe { Unique::new(&mut low::types::uint8) })
    }

    /// Returns the signed 8-bit numeric type.
    pub fn i8() -> Self {
        Type(unsafe { Unique::new(&mut low::types::sint8) })
    }

    /// Returns the unsigned 16-bit numeric type.
    pub fn u16() -> Self {
        Type(unsafe { Unique::new(&mut low::types::uint16) })
    }

    /// Returns the signed 16-bit numeric type.
    pub fn i16() -> Self {
        Type(unsafe { Unique::new(&mut low::types::sint16) })
    }

    /// Returns the unsigned 32-bit numeric type.
    pub fn u32() -> Self {
        Type(unsafe { Unique::new(&mut low::types::uint32) })
    }

    /// Returns the signed 32-bit numeric type.
    pub fn i32() -> Self {
        Type(unsafe { Unique::new(&mut low::types::sint32) })
    }

    /// Returns the unsigned 64-bit numeric type.
    pub fn u64() -> Self {
        Type(unsafe { Unique::new(&mut low::types::uint64) })
    }

    /// Returns the signed 64-bit numeric type.
    pub fn i64() -> Self {
        Type(unsafe { Unique::new(&mut low::types::sint64) })
    }

    /// Returns the C equivalent of Rust `usize` (`u16`).
    #[cfg(target_pointer_width = "16")]
    pub fn usize() -> Self {
        Self::u16()
    }

    /// Returns the C equivalent of Rust `isize` (`i16`).
    #[cfg(target_pointer_width = "16")]
    pub fn isize() -> Self {
        Self::i16()
    }

    /// Returns the C equivalent of Rust `usize` (`u32`).
    #[cfg(target_pointer_width = "32")]
    pub fn usize() -> Self {
        Self::u32()
    }

    /// Returns the C equivalent of Rust `isize` (`i32`).
    #[cfg(target_pointer_width = "32")]
    pub fn isize() -> Self {
        Self::i32()
    }

    /// Returns the C equivalent of Rust `usize` (`u64`).
    #[cfg(target_pointer_width = "64")]
    pub fn usize() -> Self {
        Self::u64()
    }

    /// Returns the C equivalent of Rust `isize` (`i64`).
    #[cfg(target_pointer_width = "64")]
    pub fn isize() -> Self {
        Self::i64()
    }

    /// Returns the C `signed char` type.
    pub fn c_schar() -> Self {
        match_size_signed!(c_schar)
    }

    /// Returns the C `unsigned char` type.
    pub fn c_uchar() -> Self {
        match_size_unsigned!(c_uchar)
    }

    /// Returns the C `short` type.
    pub fn c_short() -> Self {
        match_size_signed!(c_short)
    }

    /// Returns the C `unsigned short` type.
    pub fn c_ushort() -> Self {
        match_size_unsigned!(c_ushort)
    }

    /// Returns the C `int` type.
    pub fn c_int() -> Self {
        match_size_signed!(c_int)
    }

    /// Returns the C `unsigned int` type.
    pub fn c_uint() -> Self {
        match_size_unsigned!(c_uint)
    }

    /// Returns the C `long` type.
    pub fn c_long() -> Self {
        match_size_signed!(c_long)
    }

    /// Returns the C `unsigned long` type.
    pub fn c_ulong() -> Self {
        match_size_unsigned!(c_ulong)
    }

    /// Returns the C `longlong` type.
    pub fn c_longlong() -> Self {
        match_size_signed!(c_longlong)
    }

    /// Returns the C `unsigned longlong` type.
    pub fn c_ulonglong() -> Self {
        match_size_unsigned!(c_ulonglong)
    }

    /// Returns the C `float` (32-bit floating point) type.
    pub fn f32() -> Self {
        Type(unsafe { Unique::new(&mut low::types::float) })
    }

    /// Returns the C `double` (64-bit floating point) type.
    pub fn f64() -> Self {
        Type(unsafe { Unique::new(&mut low::types::double) })
    }

    /// Returns the C `void*` type, for passing any kind of pointer.
    pub fn pointer() -> Self {
        Type(unsafe { Unique::new(&mut low::types::pointer) })
    }

    /// Returns the C `long double` (extended-precision floating point) type.
    pub fn longdouble() -> Self {
        Type(unsafe { Unique::new(&mut low::types::longdouble) })
    }

    /// Returns the C `_Complex float` type.
    pub fn c32() -> Self {
        Type(unsafe { Unique::new(&mut low::types::complex_float) })
    }

    /// Returns the C `_Complex double` type.
    pub fn c64() -> Self {
        Type(unsafe { Unique::new(&mut low::types::complex_double) })
    }

    /// Returns the C `_Complex long double` type.
    pub fn complex_longdouble() -> Self {
        Type(unsafe { Unique::new(&mut low::types::complex_longdouble) })
    }

    /// Constructs a structure type whose fields have the given types.
    pub fn structure<I>(fields: I) -> Self
        where I: ExactSizeIterator<Item=Type>
    {
        unsafe {
            Type(Unique::new(ffi_type_struct_create(fields)))
        }
    }

    /// Gets a raw pointer to the underlying
    /// [`ffi_type`](../../low/struct.ffi_type.html).
    ///
    /// This method may be useful for interacting with the
    /// [`low`](../../low/index.html) and
    /// [`raw`](../../raw/index.html) layers.
    pub fn as_raw_ptr(&self) -> *mut low::ffi_type {
        *self.0
    }
}

impl TypeArray {
    /// Constructs an array the given `Type`s.
    pub fn new<I>(elements: I) -> Self
        where I: ExactSizeIterator<Item=Type>
    {
        unsafe { TypeArray(Unique::new(ffi_type_array_create(elements))) }
    }

    /// Gets a raw pointer to the underlying C array of
    /// [`ffi_type`](../../low/struct.ffi_type.html)s.
    ///
    /// The C array is null-terminated.
    ///
    /// This method may be useful for interacting with the
    /// [`low`](../../low/index.html) and
    /// [`raw`](../../raw/index.html) layers.
    pub fn as_raw_ptr(&self) -> *mut *mut low::ffi_type {
        *self.0
    }
}

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

    #[test]
    fn create_u64() {
        Type::u64();
    }

    #[test]
    fn clone_u64() {
        Type::u64().clone().clone();
    }

    #[test]
    fn create_struct() {
        Type::structure(vec![Type::i64(),
                             Type::i64(),
                             Type::u64()].into_iter());
    }

    #[test]
    fn clone_struct() {
        Type::structure(vec![Type::i64(),
                             Type::i64(),
                             Type::u64()].into_iter()).clone().clone();
    }

}
<|endoftext|>


<commit_before>use std::{
    cmp::{max, min, Reverse},
    fmt,
    time::{Duration, SystemTime},
};

use bytes::{Buf, BufMut};
use rustc_hash::FxHashMap;
use shakmaty::{uci::Uci, Color, Outcome};

use crate::{
    api::{Limits, PlayerQueryFilter},
    model::{
        read_uint, write_uint, ByMode, BySpeed, GameId, LichessGroup, Mode, PreparedMove,
        PreparedResponse, RawUci, Speed, Stats,
    },
    util::sort_by_key_and_truncate,
};

const MAX_PLAYER_GAMES: usize = 8; // must fit into 4 bits

#[derive(Debug, Eq, PartialEq)]
enum Header {
    Group {
        mode: Mode,
        speed: Speed,
        num_games: usize,
    },
    End,
}

impl Header {
    fn read<B: Buf>(buf: &mut B) -> Header {
        let n = buf.get_u8();
        Header::Group {
            speed: match n & 7 {
                0 => return Header::End,
                1 => Speed::UltraBullet,
                2 => Speed::Bullet,
                3 => Speed::Blitz,
                4 => Speed::Rapid,
                5 => Speed::Classical,
                6 => Speed::Correspondence,
                _ => panic!("invalid player header"),
            },
            mode: Mode::from_rated((n >> 3) & 1 == 1),
            num_games: usize::from(n >> 4),
        }
    }

    fn write<B: BufMut>(&self, buf: &mut B) {
        buf.put_u8(match *self {
            Header::End => 0,
            Header::Group {
                mode,
                speed,
                num_games,
            } => {
                (match speed {
                    Speed::UltraBullet => 1,
                    Speed::Bullet => 2,
                    Speed::Blitz => 3,
                    Speed::Rapid => 4,
                    Speed::Classical => 5,
                    Speed::Correspondence => 6,
                }) | (u8::from(mode.is_rated()) << 3)
                    | ((num_games as u8) << 4)
            }
        });
    }
}

#[derive(Default, Debug)]
pub struct PlayerEntry {
    sub_entries: FxHashMap<RawUci, BySpeed<ByMode<LichessGroup>>>,
    max_game_idx: Option<u64>,
}

impl PlayerEntry {
    pub const SIZE_HINT: usize = 13;

    pub fn new_single(
        uci: Uci,
        speed: Speed,
        mode: Mode,
        game_id: GameId,
        outcome: Outcome,
        opponent_rating: u16,
    ) -> PlayerEntry {
        let mut sub_entry: BySpeed<ByMode<LichessGroup>> = Default::default();
        *sub_entry.by_speed_mut(speed).by_mode_mut(mode) = LichessGroup {
            stats: Stats::new_single(outcome, opponent_rating),
            games: vec![(0, game_id)],
        };
        let mut sub_entries = FxHashMap::with_capacity_and_hasher(1, Default::default());
        sub_entries.insert(RawUci::from(uci), sub_entry);

        PlayerEntry {
            sub_entries,
            max_game_idx: Some(0),
        }
    }

    pub fn extend_from_reader<B: Buf>(&mut self, buf: &mut B) {
        let base_game_idx = self.max_game_idx.map_or(0, |idx| idx + 1);

        while buf.has_remaining() {
            let uci = RawUci::read(buf);
            let sub_entry = self.sub_entries.entry(uci).or_default();

            while buf.has_remaining() {
                match Header::read(buf) {
                    Header::End => break,
                    Header::Group {
                        speed,
                        mode,
                        num_games,
                    } => {
                        let group = sub_entry.by_speed_mut(speed).by_mode_mut(mode);
                        group.stats += &Stats::read(buf);
                        group.games.extend((0..num_games).map(|_| {
                            let game_idx = base_game_idx + read_uint(buf);
                            self.max_game_idx = Some(max(self.max_game_idx.unwrap_or(0), game_idx));
                            let game = GameId::read(buf);
                            (game_idx, game)
                        }));
                    }
                }
            }
        }
    }

    pub fn write<B: BufMut>(&self, buf: &mut B) {
        for (i, (uci, sub_entry)) in self.sub_entries.iter().enumerate() {
            if i > 0 {
                Header::End.write(buf);
            }

            uci.write(buf);

            for (speed, by_mode) in sub_entry.as_ref().zip_speed() {
                for (mode, group) in by_mode.as_ref().zip_mode() {
                    if !group.games.is_empty() || !group.stats.is_empty() {
                        Header::Group {
                            speed,
                            mode,
                            num_games: min(group.games.len(), MAX_PLAYER_GAMES),
                        }
                        .write(buf);

                        group.stats.write(buf);

                        for (game_idx, game) in
                            &group.games[group.games.len().saturating_sub(MAX_PLAYER_GAMES)..]
                        {
                            write_uint(buf, *game_idx);
                            game.write(buf);
                        }
                    }
                }
            }
        }
    }

    pub fn prepare(
        self,
        color: Color,
        filter: &PlayerQueryFilter,
        limits: &Limits,
    ) -> PreparedResponse {
        let mut total = Stats::default();
        let mut moves = Vec::with_capacity(self.sub_entries.len());
        let mut recent_games: Vec<(u64, RawUci, GameId)> = Vec::new();

        for (uci, sub_entry) in self.sub_entries {
            let mut latest_game: Option<(u64, GameId)> = None;
            let mut stats = Stats::default();

            for (speed, group) in sub_entry.as_ref().zip_speed() {
                if filter
                    .speeds
                    .as_ref()
                    .map_or(true, |speeds| speeds.contains(&speed))
                {
                    for (mode, group) in group.as_ref().zip_mode() {
                        if filter
                            .modes
                            .as_ref()
                            .map_or(true, |modes| modes.contains(&mode))
                        {
                            stats += &group.stats;

                            for (idx, game) in group.games.iter().copied() {
                                if latest_game.map_or(true, |(latest_idx, _game)| latest_idx < idx)
                                {
                                    latest_game = Some((idx, game));
                                }
                            }

                            recent_games.extend(
                                group
                                    .games
                                    .iter()
                                    .copied()
                                    .map(|(idx, game)| (idx, uci, game)),
                            );
                        }
                    }
                }
            }

            if !stats.is_empty() || latest_game.is_some() {
                total += &stats;

                moves.push(PreparedMove {
                    uci: Uci::from(uci),
                    average_rating: None,
                    average_opponent_rating: stats.average_rating(),
                    performance: stats.performance(color),
                    game: latest_game.filter(|_| stats.is_single()).map(|(_, id)| id),
                    stats,
                });
            }
        }

        sort_by_key_and_truncate(&mut moves, limits.moves.unwrap_or(usize::MAX), |row| {
            Reverse(row.stats.total())
        });
        sort_by_key_and_truncate(&mut recent_games, MAX_PLAYER_GAMES, |(idx, _, _)| {
            Reverse(*idx)
        });

        PreparedResponse {
            total,
            moves,
            recent_games: recent_games
                .into_iter()
                .map(|(_, uci, game)| (Uci::from(uci), game))
                .collect(),
            top_games: Vec::new(),
        }
    }
}

#[derive(Debug)]
pub struct PlayerStatus {
    pub latest_created_at: u64,
    pub revisit_ongoing_created_at: Option<u64>,
    pub indexed_at: SystemTime,
    pub revisited_at: SystemTime,
}

impl Default for PlayerStatus {
    fn default() -> PlayerStatus {
        PlayerStatus {
            latest_created_at: 0,
            revisit_ongoing_created_at: None,
            indexed_at: SystemTime::UNIX_EPOCH,
            revisited_at: SystemTime::UNIX_EPOCH,
        }
    }
}

impl PlayerStatus {
    pub const SIZE_HINT: usize = 3 * 8;

    pub fn maybe_revisit_ongoing(&mut self) -> Option<IndexRun> {
        if SystemTime::now()
            .duration_since(self.revisited_at)
            .unwrap_or_default()
            > Duration::from_secs(24 * 60 * 60)
        {
            self.revisit_ongoing_created_at
                .map(|since| IndexRun::Revisit { since })
        } else {
            None
        }
    }

    pub fn maybe_index(&self) -> Option<IndexRun> {
        SystemTime::now()
            .duration_since(self.indexed_at)
            .map_or(false, |cooldown| cooldown > Duration::from_secs(60))
            .then(|| IndexRun::Index {
                after: self.latest_created_at,
            })
    }

    pub fn finish_run(&mut self, run: IndexRun) {
        self.indexed_at = SystemTime::now();
        if matches!(run, IndexRun::Revisit { .. }) {
            self.revisited_at = self.indexed_at;
        }
    }

    pub fn read<B: Buf>(buf: &mut B) -> PlayerStatus {
        PlayerStatus {
            latest_created_at: read_uint(buf),
            revisit_ongoing_created_at: Some(read_uint(buf)).filter(|t| *t != 0),
            indexed_at: SystemTime::UNIX_EPOCH + Duration::from_secs(read_uint(buf)),
            revisited_at: SystemTime::UNIX_EPOCH + Duration::from_secs(read_uint(buf)),
        }
    }

    pub fn write<B: BufMut>(&self, buf: &mut B) {
        write_uint(buf, self.latest_created_at);
        write_uint(buf, self.revisit_ongoing_created_at.unwrap_or(0));
        write_uint(
            buf,
            self.indexed_at
                .duration_since(SystemTime::UNIX_EPOCH)
                .expect("duration since unix epoch")
                .as_secs(),
        );
        write_uint(
            buf,
            self.revisited_at
                .duration_since(SystemTime::UNIX_EPOCH)
                .expect("duration since unix epoch")
                .as_secs(),
        );
    }
}

#[derive(Debug)]
pub enum IndexRun {
    Index { after: u64 },
    Revisit { since: u64 },
}

impl IndexRun {
    pub fn since(&self) -> u64 {
        match *self {
            IndexRun::Index { after } => {
                // Plus 1 millisecond, as an optimization to avoid overlap.
                // Might miss games if the index run happens between games
                // created in the same millisecond.
                after.saturating_add(1)
            }
            IndexRun::Revisit { since } => since,
        }
    }
}

impl fmt::Display for IndexRun {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            IndexRun::Index { after } => write!(f, "created_at > {}", after),
            IndexRun::Revisit { since } => write!(f, "created_at >= {}", since),
        }
    }
}

#[cfg(test)]
mod tests {
    use shakmaty::{Color, Square};

    use super::*;

    #[test]
    fn test_header_roundtrip() {
        let headers = [
            Header::Group {
                mode: Mode::Rated,
                speed: Speed::Correspondence,
                num_games: 15,
            },
            Header::End,
        ];

        let mut buf = Vec::new();
        for header in &headers {
            header.write(&mut buf);
        }

        let mut reader = &buf[..];
        for header in headers {
            assert_eq!(Header::read(&mut reader), header);
        }
    }

    #[test]
    fn test_merge_player() {
        // Merge three entries, two of which are for the same move in the same
        // group.
        let uci_ab = Uci::Normal {
            from: Square::E2,
            to: Square::E4,
            promotion: None,
        };

        let a = PlayerEntry::new_single(
            uci_ab.clone(),
            Speed::Bullet,
            Mode::Rated,
            "aaaaaaaa".parse().unwrap(),
            Outcome::Decisive {
                winner: Color::White,
            },
            1600,
        );

        let b = PlayerEntry::new_single(
            uci_ab.clone(),
            Speed::Bullet,
            Mode::Rated,
            "bbbbbbbb".parse().unwrap(),
            Outcome::Decisive {
                winner: Color::Black,
            },
            1800,
        );

        let uci_c = Uci::Normal {
            from: Square::D2,
            to: Square::D4,
            promotion: None,
        };

        let c = PlayerEntry::new_single(
            uci_c,
            Speed::Bullet,
            Mode::Rated,
            "cccccccc".parse().unwrap(),
            Outcome::Draw,
            1700,
        );

        let mut buf = Vec::new();
        a.write(&mut buf);
        assert_eq!(
            buf.len(),
            PlayerEntry::SIZE_HINT,
            "optimized for single entries"
        );

        let mut deserialized = PlayerEntry::default();
        deserialized.extend_from_reader(&mut &buf[..]);

        let mut buf = Vec::new();
        b.write(&mut buf);
        deserialized.extend_from_reader(&mut &buf[..]);

        let mut buf = Vec::new();
        c.write(&mut buf);
        deserialized.extend_from_reader(&mut &buf[..]);

        assert_eq!(deserialized.sub_entries.len(), 2);
        assert_eq!(deserialized.max_game_idx, Some(2));
        let group = &deserialized
            .sub_entries
            .get(&RawUci::from(uci_ab))
            .unwrap()
            .bullet
            .rated;
        assert_eq!(group.stats.white, 1);
        assert_eq!(group.stats.draws, 0);
        assert_eq!(group.stats.black, 1);
        assert_eq!(group.stats.average_rating(), Some(1700));
        assert_eq!(group.games.len(), 2);

        // Roundtrip the combined entry.
        let mut buf = Vec::new();
        deserialized.write(&mut buf);
        let mut deserialized = PlayerEntry::default();
        deserialized.extend_from_reader(&mut &buf[..]);
        assert_eq!(deserialized.sub_entries.len(), 2);
        assert_eq!(deserialized.max_game_idx, Some(2));
    }
}
<commit_msg>respect recentGames limit for /player<commit_after>use std::{
    cmp::{max, min, Reverse},
    fmt,
    time::{Duration, SystemTime},
};

use bytes::{Buf, BufMut};
use rustc_hash::FxHashMap;
use shakmaty::{uci::Uci, Color, Outcome};

use crate::{
    api::{Limits, PlayerQueryFilter},
    model::{
        read_uint, write_uint, ByMode, BySpeed, GameId, LichessGroup, Mode, PreparedMove,
        PreparedResponse, RawUci, Speed, Stats,
    },
    util::sort_by_key_and_truncate,
};

const MAX_PLAYER_GAMES: usize = 8; // must fit into 4 bits

#[derive(Debug, Eq, PartialEq)]
enum Header {
    Group {
        mode: Mode,
        speed: Speed,
        num_games: usize,
    },
    End,
}

impl Header {
    fn read<B: Buf>(buf: &mut B) -> Header {
        let n = buf.get_u8();
        Header::Group {
            speed: match n & 7 {
                0 => return Header::End,
                1 => Speed::UltraBullet,
                2 => Speed::Bullet,
                3 => Speed::Blitz,
                4 => Speed::Rapid,
                5 => Speed::Classical,
                6 => Speed::Correspondence,
                _ => panic!("invalid player header"),
            },
            mode: Mode::from_rated((n >> 3) & 1 == 1),
            num_games: usize::from(n >> 4),
        }
    }

    fn write<B: BufMut>(&self, buf: &mut B) {
        buf.put_u8(match *self {
            Header::End => 0,
            Header::Group {
                mode,
                speed,
                num_games,
            } => {
                (match speed {
                    Speed::UltraBullet => 1,
                    Speed::Bullet => 2,
                    Speed::Blitz => 3,
                    Speed::Rapid => 4,
                    Speed::Classical => 5,
                    Speed::Correspondence => 6,
                }) | (u8::from(mode.is_rated()) << 3)
                    | ((num_games as u8) << 4)
            }
        });
    }
}

#[derive(Default, Debug)]
pub struct PlayerEntry {
    sub_entries: FxHashMap<RawUci, BySpeed<ByMode<LichessGroup>>>,
    max_game_idx: Option<u64>,
}

impl PlayerEntry {
    pub const SIZE_HINT: usize = 13;

    pub fn new_single(
        uci: Uci,
        speed: Speed,
        mode: Mode,
        game_id: GameId,
        outcome: Outcome,
        opponent_rating: u16,
    ) -> PlayerEntry {
        let mut sub_entry: BySpeed<ByMode<LichessGroup>> = Default::default();
        *sub_entry.by_speed_mut(speed).by_mode_mut(mode) = LichessGroup {
            stats: Stats::new_single(outcome, opponent_rating),
            games: vec![(0, game_id)],
        };
        let mut sub_entries = FxHashMap::with_capacity_and_hasher(1, Default::default());
        sub_entries.insert(RawUci::from(uci), sub_entry);

        PlayerEntry {
            sub_entries,
            max_game_idx: Some(0),
        }
    }

    pub fn extend_from_reader<B: Buf>(&mut self, buf: &mut B) {
        let base_game_idx = self.max_game_idx.map_or(0, |idx| idx + 1);

        while buf.has_remaining() {
            let uci = RawUci::read(buf);
            let sub_entry = self.sub_entries.entry(uci).or_default();

            while buf.has_remaining() {
                match Header::read(buf) {
                    Header::End => break,
                    Header::Group {
                        speed,
                        mode,
                        num_games,
                    } => {
                        let group = sub_entry.by_speed_mut(speed).by_mode_mut(mode);
                        group.stats += &Stats::read(buf);
                        group.games.extend((0..num_games).map(|_| {
                            let game_idx = base_game_idx + read_uint(buf);
                            self.max_game_idx = Some(max(self.max_game_idx.unwrap_or(0), game_idx));
                            let game = GameId::read(buf);
                            (game_idx, game)
                        }));
                    }
                }
            }
        }
    }

    pub fn write<B: BufMut>(&self, buf: &mut B) {
        for (i, (uci, sub_entry)) in self.sub_entries.iter().enumerate() {
            if i > 0 {
                Header::End.write(buf);
            }

            uci.write(buf);

            for (speed, by_mode) in sub_entry.as_ref().zip_speed() {
                for (mode, group) in by_mode.as_ref().zip_mode() {
                    if !group.games.is_empty() || !group.stats.is_empty() {
                        Header::Group {
                            speed,
                            mode,
                            num_games: min(group.games.len(), MAX_PLAYER_GAMES),
                        }
                        .write(buf);

                        group.stats.write(buf);

                        for (game_idx, game) in
                            &group.games[group.games.len().saturating_sub(MAX_PLAYER_GAMES)..]
                        {
                            write_uint(buf, *game_idx);
                            game.write(buf);
                        }
                    }
                }
            }
        }
    }

    pub fn prepare(
        self,
        color: Color,
        filter: &PlayerQueryFilter,
        limits: &Limits,
    ) -> PreparedResponse {
        let mut total = Stats::default();
        let mut moves = Vec::with_capacity(self.sub_entries.len());
        let mut recent_games: Vec<(u64, RawUci, GameId)> = Vec::new();

        for (uci, sub_entry) in self.sub_entries {
            let mut latest_game: Option<(u64, GameId)> = None;
            let mut stats = Stats::default();

            for (speed, group) in sub_entry.as_ref().zip_speed() {
                if filter
                    .speeds
                    .as_ref()
                    .map_or(true, |speeds| speeds.contains(&speed))
                {
                    for (mode, group) in group.as_ref().zip_mode() {
                        if filter
                            .modes
                            .as_ref()
                            .map_or(true, |modes| modes.contains(&mode))
                        {
                            stats += &group.stats;

                            for (idx, game) in group.games.iter().copied() {
                                if latest_game.map_or(true, |(latest_idx, _game)| latest_idx < idx)
                                {
                                    latest_game = Some((idx, game));
                                }
                            }

                            recent_games.extend(
                                group
                                    .games
                                    .iter()
                                    .copied()
                                    .map(|(idx, game)| (idx, uci, game)),
                            );
                        }
                    }
                }
            }

            if !stats.is_empty() || latest_game.is_some() {
                total += &stats;

                moves.push(PreparedMove {
                    uci: Uci::from(uci),
                    average_rating: None,
                    average_opponent_rating: stats.average_rating(),
                    performance: stats.performance(color),
                    game: latest_game.filter(|_| stats.is_single()).map(|(_, id)| id),
                    stats,
                });
            }
        }

        sort_by_key_and_truncate(&mut moves, limits.moves.unwrap_or(usize::MAX), |row| {
            Reverse(row.stats.total())
        });
        sort_by_key_and_truncate(
            &mut recent_games,
            min(limits.recent_games, MAX_PLAYER_GAMES),
            |(idx, _, _)| Reverse(*idx),
        );

        PreparedResponse {
            total,
            moves,
            recent_games: recent_games
                .into_iter()
                .map(|(_, uci, game)| (Uci::from(uci), game))
                .collect(),
            top_games: Vec::new(),
        }
    }
}

#[derive(Debug)]
pub struct PlayerStatus {
    pub latest_created_at: u64,
    pub revisit_ongoing_created_at: Option<u64>,
    pub indexed_at: SystemTime,
    pub revisited_at: SystemTime,
}

impl Default for PlayerStatus {
    fn default() -> PlayerStatus {
        PlayerStatus {
            latest_created_at: 0,
            revisit_ongoing_created_at: None,
            indexed_at: SystemTime::UNIX_EPOCH,
            revisited_at: SystemTime::UNIX_EPOCH,
        }
    }
}

impl PlayerStatus {
    pub const SIZE_HINT: usize = 3 * 8;

    pub fn maybe_revisit_ongoing(&mut self) -> Option<IndexRun> {
        if SystemTime::now()
            .duration_since(self.revisited_at)
            .unwrap_or_default()
            > Duration::from_secs(24 * 60 * 60)
        {
            self.revisit_ongoing_created_at
                .map(|since| IndexRun::Revisit { since })
        } else {
            None
        }
    }

    pub fn maybe_index(&self) -> Option<IndexRun> {
        SystemTime::now()
            .duration_since(self.indexed_at)
            .map_or(false, |cooldown| cooldown > Duration::from_secs(60))
            .then(|| IndexRun::Index {
                after: self.latest_created_at,
            })
    }

    pub fn finish_run(&mut self, run: IndexRun) {
        self.indexed_at = SystemTime::now();
        if matches!(run, IndexRun::Revisit { .. }) {
            self.revisited_at = self.indexed_at;
        }
    }

    pub fn read<B: Buf>(buf: &mut B) -> PlayerStatus {
        PlayerStatus {
            latest_created_at: read_uint(buf),
            revisit_ongoing_created_at: Some(read_uint(buf)).filter(|t| *t != 0),
            indexed_at: SystemTime::UNIX_EPOCH + Duration::from_secs(read_uint(buf)),
            revisited_at: SystemTime::UNIX_EPOCH + Duration::from_secs(read_uint(buf)),
        }
    }

    pub fn write<B: BufMut>(&self, buf: &mut B) {
        write_uint(buf, self.latest_created_at);
        write_uint(buf, self.revisit_ongoing_created_at.unwrap_or(0));
        write_uint(
            buf,
            self.indexed_at
                .duration_since(SystemTime::UNIX_EPOCH)
                .expect("duration since unix epoch")
                .as_secs(),
        );
        write_uint(
            buf,
            self.revisited_at
                .duration_since(SystemTime::UNIX_EPOCH)
                .expect("duration since unix epoch")
                .as_secs(),
        );
    }
}

#[derive(Debug)]
pub enum IndexRun {
    Index { after: u64 },
    Revisit { since: u64 },
}

impl IndexRun {
    pub fn since(&self) -> u64 {
        match *self {
            IndexRun::Index { after } => {
                // Plus 1 millisecond, as an optimization to avoid overlap.
                // Might miss games if the index run happens between games
                // created in the same millisecond.
                after.saturating_add(1)
            }
            IndexRun::Revisit { since } => since,
        }
    }
}

impl fmt::Display for IndexRun {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            IndexRun::Index { after } => write!(f, "created_at > {}", after),
            IndexRun::Revisit { since } => write!(f, "created_at >= {}", since),
        }
    }
}

#[cfg(test)]
mod tests {
    use shakmaty::{Color, Square};

    use super::*;

    #[test]
    fn test_header_roundtrip() {
        let headers = [
            Header::Group {
                mode: Mode::Rated,
                speed: Speed::Correspondence,
                num_games: 15,
            },
            Header::End,
        ];

        let mut buf = Vec::new();
        for header in &headers {
            header.write(&mut buf);
        }

        let mut reader = &buf[..];
        for header in headers {
            assert_eq!(Header::read(&mut reader), header);
        }
    }

    #[test]
    fn test_merge_player() {
        // Merge three entries, two of which are for the same move in the same
        // group.
        let uci_ab = Uci::Normal {
            from: Square::E2,
            to: Square::E4,
            promotion: None,
        };

        let a = PlayerEntry::new_single(
            uci_ab.clone(),
            Speed::Bullet,
            Mode::Rated,
            "aaaaaaaa".parse().unwrap(),
            Outcome::Decisive {
                winner: Color::White,
            },
            1600,
        );

        let b = PlayerEntry::new_single(
            uci_ab.clone(),
            Speed::Bullet,
            Mode::Rated,
            "bbbbbbbb".parse().unwrap(),
            Outcome::Decisive {
                winner: Color::Black,
            },
            1800,
        );

        let uci_c = Uci::Normal {
            from: Square::D2,
            to: Square::D4,
            promotion: None,
        };

        let c = PlayerEntry::new_single(
            uci_c,
            Speed::Bullet,
            Mode::Rated,
            "cccccccc".parse().unwrap(),
            Outcome::Draw,
            1700,
        );

        let mut buf = Vec::new();
        a.write(&mut buf);
        assert_eq!(
            buf.len(),
            PlayerEntry::SIZE_HINT,
            "optimized for single entries"
        );

        let mut deserialized = PlayerEntry::default();
        deserialized.extend_from_reader(&mut &buf[..]);

        let mut buf = Vec::new();
        b.write(&mut buf);
        deserialized.extend_from_reader(&mut &buf[..]);

        let mut buf = Vec::new();
        c.write(&mut buf);
        deserialized.extend_from_reader(&mut &buf[..]);

        assert_eq!(deserialized.sub_entries.len(), 2);
        assert_eq!(deserialized.max_game_idx, Some(2));
        let group = &deserialized
            .sub_entries
            .get(&RawUci::from(uci_ab))
            .unwrap()
            .bullet
            .rated;
        assert_eq!(group.stats.white, 1);
        assert_eq!(group.stats.draws, 0);
        assert_eq!(group.stats.black, 1);
        assert_eq!(group.stats.average_rating(), Some(1700));
        assert_eq!(group.games.len(), 2);

        // Roundtrip the combined entry.
        let mut buf = Vec::new();
        deserialized.write(&mut buf);
        let mut deserialized = PlayerEntry::default();
        deserialized.extend_from_reader(&mut &buf[..]);
        assert_eq!(deserialized.sub_entries.len(), 2);
        assert_eq!(deserialized.max_game_idx, Some(2));
    }
}
<|endoftext|>