<commit_before>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use crate::{
    lsp_process_error::{LSPProcessError, LSPProcessResult},
    lsp_runtime_error::LSPRuntimeResult,
    node_resolution_info::{get_node_resolution_info, NodeResolutionInfo},
    utils::{extract_executable_definitions_from_text, extract_executable_document_from_text},
};
use common::{PerfLogEvent, PerfLogger, Span};
use graphql_ir::Program;
use graphql_syntax::{ExecutableDefinition, ExecutableDocument, GraphQLSource};
use interner::StringKey;
use lsp_types::{TextDocumentPositionParams, Url};
use relay_compiler::{
    compiler::Compiler, compiler_state::CompilerState, config::Config, FileCategorizer, FileSource,
    FileSourceSubscription, FileSourceSubscriptionNextChange,
};
use schema::SDLSchema;
use schema_documentation::SchemaDocumentation;
use std::{
    collections::{HashMap, HashSet},
    fmt,
    path::PathBuf,
    sync::{Arc, RwLock},
};
#[derive(Debug)]
pub enum LSPStateError {
    BuildSchemaError,
    /// For the initial build, if we have validation errors in the program -
    /// we won't be able to create a correct state
    InitialBuildError,
    /// But for the `watch` mode - it may break, only there were infra errors (IO, De/Serialization).
    /// IR, GraphQL validation errors won't be reported here.
    WatchBuildError,
}

impl fmt::Display for LSPStateError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                LSPStateError::BuildSchemaError =>
                    "Unable to parse/create an instance of the GraphQL Schema for Relay LSP Server State.",
                LSPStateError::InitialBuildError =>
                    "Validation errors in documents. Please, fix the errors reported in the diagnostics panel.",
                LSPStateError::WatchBuildError =>
                    "Incremental changes broke the internal Relay LSP Server State. This is probably a transient issue; try restarting the Relay VSCode extension.",
            }
        )
    }
}

pub trait LSPExtraDataProvider {
    fn fetch_query_stats(&self, search_token: String) -> Vec<String>;
    fn resolve_field_definition(
        &self,
        project_name: String,
        root_dir: &PathBuf,
        parent_type: String,
        field_name: Option<String>,
    ) -> Option<Result<(String, u64), String>>;
    fn get_schema_documentation(&self, schema_name: String) -> Arc<SchemaDocumentation>;
}

pub(crate) struct LSPState<TPerfLogger: PerfLogger + 'static> {
    config: Arc<Config>,
    compiler: Option<Compiler<TPerfLogger>>,
    root_dir: PathBuf,
    pub extra_data_provider: Box<dyn LSPExtraDataProvider>,
    file_categorizer: FileCategorizer,
    schemas: Arc<RwLock<HashMap<StringKey, Arc<SDLSchema>>>>,
    source_programs: Arc<RwLock<HashMap<StringKey, Program>>>,
    synced_graphql_documents: HashMap<Url, Vec<GraphQLSource>>,
    errors: Arc<RwLock<Vec<LSPStateError>>>,
}

impl<TPerfLogger: PerfLogger + 'static> LSPState<TPerfLogger> {
    /// Private constructor
    fn new(
        config: Arc<Config>,
        extra_data_provider: Box<dyn LSPExtraDataProvider>,
        lsp_state_errors: Arc<RwLock<Vec<LSPStateError>>>,
    ) -> Self {
        let file_categorizer = FileCategorizer::from_config(&config);
        let root_dir = &config.root_dir.clone();
        Self {
            config,
            compiler: None,
            extra_data_provider,
            file_categorizer,
            root_dir: root_dir.clone(),
            schemas: Default::default(),
            source_programs: Default::default(),
            synced_graphql_documents: Default::default(),
            errors: lsp_state_errors,
        }
    }

    /// This method is responsible for creating schema/source_programs for LSP internal state
    /// - so the LSP can provide these to Hover/Completion/GoToDefinition requests.
    /// It also creates a watchman subscription that is responsible for keeping these resources up-to-date.
    pub(crate) async fn create_state(
        config: Arc<Config>,
        lsp_state_errors: Arc<RwLock<Vec<LSPStateError>>>,
        extra_data_provider: Box<dyn LSPExtraDataProvider>,
        perf_logger: Arc<TPerfLogger>,
    ) -> LSPProcessResult<Self> {
        let mut lsp_state = Self::new(config, extra_data_provider, lsp_state_errors);

        let setup_event = perf_logger.create_event("lsp_state_initialize_resources");
        let log_time = setup_event.start("lsp_state_initialize_resources_time");

        let file_source = FileSource::connect(&lsp_state.config, &setup_event)
            .await
            .map_err(LSPProcessError::CompilerError)?;
        let (mut compiler_state, file_source_subscription) = file_source
            .subscribe(&setup_event, perf_logger.as_ref())
            .await
            .map_err(LSPProcessError::CompilerError)?;

        lsp_state.initial_build(&mut compiler_state, &setup_event, Arc::clone(&perf_logger))?;

        // Preload schema documentation - this will warm-up schema documentation cache in the LSP Extra Data providers
        lsp_state.preload_documentation();

        // Finally, start a dedicated watchman subscription, just for LSP to update schemas/programs in lsp_state
        lsp_state.watch_and_update_schemas(
            compiler_state,
            file_source_subscription,
            Arc::clone(&perf_logger),
        );

        // This is an instance of a regular Relay compiler that we will run in
        // watch mode in a separate future (it will report errors)
        setup_event.stop(log_time);
        perf_logger.complete_event(setup_event);
        perf_logger.flush();

        lsp_state.compiler = Some(Compiler::new(Arc::clone(&lsp_state.config), perf_logger));

        Ok(lsp_state)
    }

    fn watch_and_update_schemas(
        &mut self,
        mut compiler_state: CompilerState,
        mut subscription: FileSourceSubscription,
        perf_logger: Arc<TPerfLogger>,
    ) {
        let config = Arc::clone(&self.config);
        let compiler = Compiler::new(Arc::clone(&config), Arc::clone(&perf_logger));
        let source_programs = Arc::clone(&self.source_programs);
        let schemas = self.get_schemas();
        let errors = Arc::clone(&self.errors);

        tokio::spawn(async move {
            loop {
                if let Ok(FileSourceSubscriptionNextChange::Result(file_source_changes)) =
                    subscription.next_change().await
                {
                    let log_event = perf_logger.create_event("lsp_state_watchman_event");
                    let log_time = log_event.start("lsp_state_watchman_event_time");

                    compiler_state
                        .pending_file_source_changes
                        .write()
                        .expect("LSPState::watch_and_update_schemas: expect to acquire write lock on pending_file_source_changes")
                        .push(file_source_changes);

                    let has_new_changes = compiler_state
                        .merge_file_source_changes(&config, &log_event, perf_logger.as_ref(), false)
                        .unwrap_or_else(|err| {
                            Self::log_error(
                                &perf_logger,
                                format!("Unable to merge_file_source_changes: {:?}", err),
                            );
                            false
                        });

                    let mut errors_write_lock = errors.write().unwrap();

                    if has_new_changes {
                        errors_write_lock.clear();
                    }

                    // If changes contains schema files we need to rebuild schemas
                    if has_new_changes && compiler_state.has_schema_changes() {
                        log_event.number("has_schema_change", 1);
                        let (next_schemas, build_schema_errors) =
                            compiler.build_schemas(&compiler_state, &log_event);
                        if !build_schema_errors.is_empty() {
                            Self::log_errors(&perf_logger, &build_schema_errors);
                            errors_write_lock.push(LSPStateError::BuildSchemaError);
                        };

                        schemas.write().expect("LSPState::watch_and_update_schemas: expect to acquire write lock on schemas").clone_from(&next_schemas);
                    } else {
                        log_event.number("has_schema_change", 0);
                    }

                    // Rebuilding programs
                    if has_new_changes {
                        Self::build_in_watch_mode(
                            &config,
                            &compiler,
                            &mut compiler_state,
                            &schemas,
                            &source_programs,
                            Arc::clone(&perf_logger),
                            &log_event,
                        )
                        .unwrap_or_else(|err| {
                            Self::log_error(
                                &perf_logger,
                                format!("Error in build_in_watch_mode: {:?}", err),
                            );
                            errors_write_lock.push(LSPStateError::WatchBuildError);
                        });
                    }

                    log_event.stop(log_time);
                    perf_logger.complete_event(log_event);
                    perf_logger.flush();
                }
            }
        });
    }

    pub(crate) fn get_schemas(&self) -> Arc<RwLock<HashMap<StringKey, Arc<SDLSchema>>>> {
        self.schemas.clone()
    }

    pub(crate) fn get_source_programs_ref(&self) -> &Arc<RwLock<HashMap<StringKey, Program>>> {
        &self.source_programs
    }

    pub(crate) fn resolve_node(
        &self,
        params: TextDocumentPositionParams,
    ) -> LSPRuntimeResult<NodeResolutionInfo> {
        get_node_resolution_info(
            params,
            &self.synced_graphql_documents,
            &self.file_categorizer,
            &self.root_dir,
        )
    }

    pub(crate) fn resolve_executable_definitions(
        &self,
        params: &TextDocumentPositionParams,
    ) -> LSPRuntimeResult<Vec<ExecutableDefinition>> {
        extract_executable_definitions_from_text(params, &self.synced_graphql_documents)
    }

    pub(crate) fn root_dir(&self) -> &PathBuf {
        &self.root_dir
    }

    pub(crate) fn insert_synced_sources(&mut self, url: Url, sources: Vec<GraphQLSource>) {
        self.start_compiler_once();
        self.synced_graphql_documents.insert(url, sources);
    }

    pub(crate) fn remove_synced_sources(&mut self, url: &Url) {
        self.synced_graphql_documents.remove(url);
    }

    pub(crate) fn extract_executable_document_from_text(
        &mut self,
        position: TextDocumentPositionParams,
        index_offset: usize,
    ) -> LSPRuntimeResult<(ExecutableDocument, Span, StringKey)> {
        extract_executable_document_from_text(
            position,
            &self.synced_graphql_documents,
            &self.file_categorizer,
            &self.root_dir,
            index_offset,
        )
    }

    pub(crate) fn get_errors(&self) -> &Arc<RwLock<Vec<LSPStateError>>> {
        &self.errors
    }

    fn start_compiler_once(&mut self) {
        if let Some(compiler) = self.compiler.take() {
            tokio::spawn(async move { compiler.watch().await });
        }
    }

    fn initial_build(
        &mut self,
        compiler_state: &mut CompilerState,
        setup_event: &impl PerfLogEvent,
        perf_logger: Arc<TPerfLogger>,
    ) -> LSPProcessResult<()> {
        let compiler = Compiler::new(Arc::clone(&self.config), Arc::clone(&perf_logger));

        let (schemas, build_schema_errors) = compiler.build_schemas(&compiler_state, setup_event);

        if !build_schema_errors.is_empty() {
            Self::log_errors(&perf_logger, &build_schema_errors);
            self.errors
                .write()
                .unwrap()
                .push(LSPStateError::BuildSchemaError);
        }

        // This will build programs, but won't apply any transformations to them
        // that should be enough for LSP to start showing fragments information
        let (source_programs, build_raw_program_errors) =
            compiler.build_raw_programs(&compiler_state, &schemas, None, setup_event)?;

        if !build_raw_program_errors.is_empty() {
            Self::log_errors(&perf_logger, &build_raw_program_errors);
            self.errors
                .write()
                .unwrap()
                .push(LSPStateError::InitialBuildError);
        }

        compiler_state.complete_compilation();

        self.schemas = Arc::new(RwLock::new(schemas));
        self.source_programs = Arc::new(RwLock::new(source_programs));

        Ok(())
    }

    /// This function should handle internal watchman builds that will
    /// update schemas/source_programs every time those are created/updated
    fn build_in_watch_mode(
        config: &Arc<Config>,
        compiler: &Compiler<TPerfLogger>,
        compiler_state: &mut CompilerState,
        schemas: &Arc<RwLock<HashMap<StringKey, Arc<SDLSchema>>>>,
        source_programs: &Arc<RwLock<HashMap<StringKey, Program>>>,
        perf_logger: Arc<TPerfLogger>,
        log_event: &impl PerfLogEvent,
    ) -> LSPProcessResult<()> {
        // we should trigger build only for changed projects
        // this set will be used in the `build_raw_programs` to ignore unchanged projects
        let affected_projects = config
            .projects
            .keys()
            .filter(|project_name| compiler_state.project_has_pending_changes(**project_name))
            .collect::<HashSet<_>>();

        // This will build programs, but won't apply any transformations to them
        // that should be enough for LSP to start showing fragments information
        let (programs, build_raw_programs_errors) = compiler.build_raw_programs(
            &compiler_state,
            &schemas
                .read()
                .expect("LSPState::build_in_watch_mode: expect to acquire read lock on schemas"),
            Some(affected_projects),
            log_event,
        )?;

        if !build_raw_programs_errors.is_empty() {
            Self::log_errors(&perf_logger, &build_raw_programs_errors)
        }
        let timer = log_event.start("lsp_build_in_watch_writing_programs_time");

        let mut source_programs_write_lock = source_programs.write().expect(
            "LSPState::build_in_watch_mode: expect to acquire write lock on source_programs",
        );
        for (program_name, program) in programs {
            // NOTE: Currently, we rely on the fact that `build_raw_programs`
            // will always return a full program, so we can safely replace it in the current
            // list of source_programs
            source_programs_write_lock.insert(program_name, program);
        }

        compiler_state.complete_compilation();

        log_event.stop(timer);
        Ok(())
    }

    fn preload_documentation(&self) {
        for project_config in self.config.enabled_projects() {
            self.extra_data_provider
                .get_schema_documentation(self.get_schema_name_for_project(&project_config.name));
        }
    }

    pub fn get_schema_name_for_project(&self, project_name: &StringKey) -> String {
        for project_config in self.config.enabled_projects() {
            if project_name == &project_config.name {
                return project_config
                    .schema_name
                    .clone()
                    .unwrap_or_else(|| project_name.lookup().to_string());
            }
        }

        panic!("Expected to find project with name {}", project_name)
    }

    fn log_errors<T: core::fmt::Debug>(logger: &Arc<TPerfLogger>, errors: &[T]) {
        Self::log_error(
            logger,
            errors
                .iter()
                .map(|err| format!("{:?}", err))
                .collect::<Vec<String>>()
                .join("\n"),
        );
    }

    /// A quick helper so we can unwrap things and log, if somethings isn't right
    fn log_error(logger: &Arc<TPerfLogger>, error_message: String) {
        let error_event = logger.create_event("lsp_state_error");
        error_event.string("error", error_message);
        logger.complete_event(error_event);
        logger.flush();
    }
}
<commit_msg>Make `perf_logger` part of LSPState<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use crate::{
    lsp_process_error::{LSPProcessError, LSPProcessResult},
    lsp_runtime_error::LSPRuntimeResult,
    node_resolution_info::{get_node_resolution_info, NodeResolutionInfo},
    utils::{extract_executable_definitions_from_text, extract_executable_document_from_text},
};
use common::{PerfLogEvent, PerfLogger, Span};
use graphql_ir::Program;
use graphql_syntax::{ExecutableDefinition, ExecutableDocument, GraphQLSource};
use interner::StringKey;
use lsp_types::{TextDocumentPositionParams, Url};
use relay_compiler::{
    compiler::Compiler, compiler_state::CompilerState, config::Config, FileCategorizer, FileSource,
    FileSourceSubscription, FileSourceSubscriptionNextChange,
};
use schema::SDLSchema;
use schema_documentation::SchemaDocumentation;
use std::{
    collections::{HashMap, HashSet},
    fmt,
    path::PathBuf,
    sync::{Arc, RwLock},
};
#[derive(Debug)]
pub enum LSPStateError {
    BuildSchemaError,
    /// For the initial build, if we have validation errors in the program -
    /// we won't be able to create a correct state
    InitialBuildError,
    /// But for the `watch` mode - it may break, only there were infra errors (IO, De/Serialization).
    /// IR, GraphQL validation errors won't be reported here.
    WatchBuildError,
}

impl fmt::Display for LSPStateError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                LSPStateError::BuildSchemaError =>
                    "Unable to parse/create an instance of the GraphQL Schema for Relay LSP Server State.",
                LSPStateError::InitialBuildError =>
                    "Validation errors in documents. Please, fix the errors reported in the diagnostics panel.",
                LSPStateError::WatchBuildError =>
                    "Incremental changes broke the internal Relay LSP Server State. This is probably a transient issue; try restarting the Relay VSCode extension.",
            }
        )
    }
}

pub trait LSPExtraDataProvider {
    fn fetch_query_stats(&self, search_token: String) -> Vec<String>;
    fn resolve_field_definition(
        &self,
        project_name: String,
        root_dir: &PathBuf,
        parent_type: String,
        field_name: Option<String>,
    ) -> Option<Result<(String, u64), String>>;
    fn get_schema_documentation(&self, schema_name: String) -> Arc<SchemaDocumentation>;
}

pub(crate) struct LSPState<TPerfLogger: PerfLogger + 'static> {
    config: Arc<Config>,
    compiler: Option<Compiler<TPerfLogger>>,
    root_dir: PathBuf,
    pub extra_data_provider: Box<dyn LSPExtraDataProvider>,
    file_categorizer: FileCategorizer,
    schemas: Arc<RwLock<HashMap<StringKey, Arc<SDLSchema>>>>,
    source_programs: Arc<RwLock<HashMap<StringKey, Program>>>,
    synced_graphql_documents: HashMap<Url, Vec<GraphQLSource>>,
    errors: Arc<RwLock<Vec<LSPStateError>>>,
    perf_logger: Arc<TPerfLogger>,
}

impl<TPerfLogger: PerfLogger + 'static> LSPState<TPerfLogger> {
    /// Private constructor
    fn new(
        config: Arc<Config>,
        extra_data_provider: Box<dyn LSPExtraDataProvider>,
        lsp_state_errors: Arc<RwLock<Vec<LSPStateError>>>,
        perf_logger: Arc<TPerfLogger>,
    ) -> Self {
        let file_categorizer = FileCategorizer::from_config(&config);
        let root_dir = &config.root_dir.clone();
        Self {
            config,
            compiler: None,
            extra_data_provider,
            file_categorizer,
            root_dir: root_dir.clone(),
            schemas: Default::default(),
            source_programs: Default::default(),
            synced_graphql_documents: Default::default(),
            errors: lsp_state_errors,
            perf_logger,
        }
    }

    /// This method is responsible for creating schema/source_programs for LSP internal state
    /// - so the LSP can provide these to Hover/Completion/GoToDefinition requests.
    /// It also creates a watchman subscription that is responsible for keeping these resources up-to-date.
    pub(crate) async fn create_state(
        config: Arc<Config>,
        lsp_state_errors: Arc<RwLock<Vec<LSPStateError>>>,
        extra_data_provider: Box<dyn LSPExtraDataProvider>,
        perf_logger: Arc<TPerfLogger>,
    ) -> LSPProcessResult<Self> {
        let mut lsp_state = Self::new(config, extra_data_provider, lsp_state_errors, perf_logger);

        let setup_event = lsp_state
            .perf_logger
            .create_event("lsp_state_initialize_resources");
        let log_time = setup_event.start("lsp_state_initialize_resources_time");

        let file_source = FileSource::connect(&lsp_state.config, &setup_event)
            .await
            .map_err(LSPProcessError::CompilerError)?;
        let (mut compiler_state, file_source_subscription) = file_source
            .subscribe(&setup_event, lsp_state.perf_logger.as_ref())
            .await
            .map_err(LSPProcessError::CompilerError)?;

        lsp_state.initial_build(&mut compiler_state, &setup_event)?;

        // Preload schema documentation - this will warm-up schema documentation cache in the LSP Extra Data providers
        lsp_state.preload_documentation();

        // Finally, start a dedicated watchman subscription, just for LSP to update schemas/programs in lsp_state
        lsp_state.watch_and_update_schemas(compiler_state, file_source_subscription);

        // This is an instance of a regular Relay compiler that we will run in
        // watch mode in a separate future (it will report errors)
        setup_event.stop(log_time);
        lsp_state.perf_logger.complete_event(setup_event);
        lsp_state.perf_logger.flush();

        lsp_state.compiler = Some(Compiler::new(
            Arc::clone(&lsp_state.config),
            Arc::clone(&lsp_state.perf_logger),
        ));

        Ok(lsp_state)
    }

    fn watch_and_update_schemas(
        &mut self,
        mut compiler_state: CompilerState,
        mut subscription: FileSourceSubscription,
    ) {
        let config = Arc::clone(&self.config);
        let compiler = Compiler::new(Arc::clone(&config), Arc::clone(&self.perf_logger));
        let source_programs = Arc::clone(&self.source_programs);
        let schemas = self.get_schemas();
        let errors = Arc::clone(&self.errors);
        let perf_logger = Arc::clone(&self.perf_logger);

        tokio::spawn(async move {
            loop {
                if let Ok(FileSourceSubscriptionNextChange::Result(file_source_changes)) =
                    subscription.next_change().await
                {
                    let log_event = perf_logger.create_event("lsp_state_watchman_event");
                    let log_time = log_event.start("lsp_state_watchman_event_time");

                    compiler_state
                        .pending_file_source_changes
                        .write()
                        .expect("LSPState::watch_and_update_schemas: expect to acquire write lock on pending_file_source_changes")
                        .push(file_source_changes);

                    let has_new_changes = compiler_state
                        .merge_file_source_changes(&config, &log_event, perf_logger.as_ref(), false)
                        .unwrap_or_else(|err| {
                            Self::log_error(
                                &perf_logger,
                                format!("Unable to merge_file_source_changes: {:?}", err),
                            );
                            false
                        });

                    let mut errors_write_lock = errors.write().unwrap();

                    if has_new_changes {
                        errors_write_lock.clear();
                    }

                    // If changes contains schema files we need to rebuild schemas
                    if has_new_changes && compiler_state.has_schema_changes() {
                        log_event.number("has_schema_change", 1);
                        let (next_schemas, build_schema_errors) =
                            compiler.build_schemas(&compiler_state, &log_event);
                        if !build_schema_errors.is_empty() {
                            Self::log_errors(&perf_logger, &build_schema_errors);
                            errors_write_lock.push(LSPStateError::BuildSchemaError);
                        };

                        schemas.write().expect("LSPState::watch_and_update_schemas: expect to acquire write lock on schemas").clone_from(&next_schemas);
                    } else {
                        log_event.number("has_schema_change", 0);
                    }

                    // Rebuilding programs
                    if has_new_changes {
                        Self::build_in_watch_mode(
                            &config,
                            &compiler,
                            &mut compiler_state,
                            &schemas,
                            &source_programs,
                            Arc::clone(&perf_logger),
                            &log_event,
                        )
                        .unwrap_or_else(|err| {
                            Self::log_error(
                                &perf_logger,
                                format!("Error in build_in_watch_mode: {:?}", err),
                            );
                            errors_write_lock.push(LSPStateError::WatchBuildError);
                        });
                    }

                    log_event.stop(log_time);
                    perf_logger.complete_event(log_event);
                    perf_logger.flush();
                }
            }
        });
    }

    pub(crate) fn get_schemas(&self) -> Arc<RwLock<HashMap<StringKey, Arc<SDLSchema>>>> {
        self.schemas.clone()
    }

    pub(crate) fn get_source_programs_ref(&self) -> &Arc<RwLock<HashMap<StringKey, Program>>> {
        &self.source_programs
    }

    pub(crate) fn resolve_node(
        &self,
        params: TextDocumentPositionParams,
    ) -> LSPRuntimeResult<NodeResolutionInfo> {
        get_node_resolution_info(
            params,
            &self.synced_graphql_documents,
            &self.file_categorizer,
            &self.root_dir,
        )
    }

    pub(crate) fn resolve_executable_definitions(
        &self,
        params: &TextDocumentPositionParams,
    ) -> LSPRuntimeResult<Vec<ExecutableDefinition>> {
        extract_executable_definitions_from_text(params, &self.synced_graphql_documents)
    }

    pub(crate) fn root_dir(&self) -> &PathBuf {
        &self.root_dir
    }

    pub(crate) fn insert_synced_sources(&mut self, url: Url, sources: Vec<GraphQLSource>) {
        self.start_compiler_once();
        self.synced_graphql_documents.insert(url, sources);
    }

    pub(crate) fn remove_synced_sources(&mut self, url: &Url) {
        self.synced_graphql_documents.remove(url);
    }

    pub(crate) fn extract_executable_document_from_text(
        &mut self,
        position: TextDocumentPositionParams,
        index_offset: usize,
    ) -> LSPRuntimeResult<(ExecutableDocument, Span, StringKey)> {
        extract_executable_document_from_text(
            position,
            &self.synced_graphql_documents,
            &self.file_categorizer,
            &self.root_dir,
            index_offset,
        )
    }

    pub(crate) fn get_errors(&self) -> &Arc<RwLock<Vec<LSPStateError>>> {
        &self.errors
    }

    fn start_compiler_once(&mut self) {
        if let Some(compiler) = self.compiler.take() {
            tokio::spawn(async move { compiler.watch().await });
        }
    }

    fn initial_build(
        &mut self,
        compiler_state: &mut CompilerState,
        setup_event: &impl PerfLogEvent,
    ) -> LSPProcessResult<()> {
        let compiler = Compiler::new(Arc::clone(&self.config), Arc::clone(&self.perf_logger));

        let (schemas, build_schema_errors) = compiler.build_schemas(&compiler_state, setup_event);

        if !build_schema_errors.is_empty() {
            Self::log_errors(&self.perf_logger, &build_schema_errors);
            self.errors
                .write()
                .unwrap()
                .push(LSPStateError::BuildSchemaError);
        }

        // This will build programs, but won't apply any transformations to them
        // that should be enough for LSP to start showing fragments information
        let (source_programs, build_raw_program_errors) =
            compiler.build_raw_programs(&compiler_state, &schemas, None, setup_event)?;

        if !build_raw_program_errors.is_empty() {
            Self::log_errors(&self.perf_logger, &build_raw_program_errors);
            self.errors
                .write()
                .unwrap()
                .push(LSPStateError::InitialBuildError);
        }

        compiler_state.complete_compilation();

        self.schemas = Arc::new(RwLock::new(schemas));
        self.source_programs = Arc::new(RwLock::new(source_programs));

        Ok(())
    }

    /// This function should handle internal watchman builds that will
    /// update schemas/source_programs every time those are created/updated
    fn build_in_watch_mode(
        config: &Arc<Config>,
        compiler: &Compiler<TPerfLogger>,
        compiler_state: &mut CompilerState,
        schemas: &Arc<RwLock<HashMap<StringKey, Arc<SDLSchema>>>>,
        source_programs: &Arc<RwLock<HashMap<StringKey, Program>>>,
        perf_logger: Arc<TPerfLogger>,
        log_event: &impl PerfLogEvent,
    ) -> LSPProcessResult<()> {
        // we should trigger build only for changed projects
        // this set will be used in the `build_raw_programs` to ignore unchanged projects
        let affected_projects = config
            .projects
            .keys()
            .filter(|project_name| compiler_state.project_has_pending_changes(**project_name))
            .collect::<HashSet<_>>();

        // This will build programs, but won't apply any transformations to them
        // that should be enough for LSP to start showing fragments information
        let (programs, build_raw_programs_errors) = compiler.build_raw_programs(
            &compiler_state,
            &schemas
                .read()
                .expect("LSPState::build_in_watch_mode: expect to acquire read lock on schemas"),
            Some(affected_projects),
            log_event,
        )?;

        if !build_raw_programs_errors.is_empty() {
            Self::log_errors(&perf_logger, &build_raw_programs_errors)
        }
        let timer = log_event.start("lsp_build_in_watch_writing_programs_time");

        let mut source_programs_write_lock = source_programs.write().expect(
            "LSPState::build_in_watch_mode: expect to acquire write lock on source_programs",
        );
        for (program_name, program) in programs {
            // NOTE: Currently, we rely on the fact that `build_raw_programs`
            // will always return a full program, so we can safely replace it in the current
            // list of source_programs
            source_programs_write_lock.insert(program_name, program);
        }

        compiler_state.complete_compilation();

        log_event.stop(timer);
        Ok(())
    }

    fn preload_documentation(&self) {
        for project_config in self.config.enabled_projects() {
            self.extra_data_provider
                .get_schema_documentation(self.get_schema_name_for_project(&project_config.name));
        }
    }

    pub fn get_schema_name_for_project(&self, project_name: &StringKey) -> String {
        for project_config in self.config.enabled_projects() {
            if project_name == &project_config.name {
                return project_config
                    .schema_name
                    .clone()
                    .unwrap_or_else(|| project_name.lookup().to_string());
            }
        }

        panic!("Expected to find project with name {}", project_name)
    }

    fn log_errors<T: core::fmt::Debug>(logger: &Arc<TPerfLogger>, errors: &[T]) {
        Self::log_error(
            logger,
            errors
                .iter()
                .map(|err| format!("{:?}", err))
                .collect::<Vec<String>>()
                .join("\n"),
        );
    }

    /// A quick helper so we can unwrap things and log, if somethings isn't right
    fn log_error(logger: &Arc<TPerfLogger>, error_message: String) {
        let error_event = logger.create_event("lsp_state_error");
        error_event.string("error", error_message);
        logger.complete_event(error_event);
        logger.flush();
    }
}
<|endoftext|>


<commit_before>use std::path::Path;

use crate::{api_client::{BoxedClient,
                         Client},
            common::{self,
                     command::package::install::{RETRIES,
                                                 RETRY_WAIT},
                     ui::{Status,
                          UIWriter,
                          UI}},
            hcore::{crypto::SigKeyPair,
                    util::wait_for}};

use crate::{error::{Error,
                    Result},
            PRODUCT,
            VERSION};

use retry::retry;

#[allow(clippy::too_many_arguments)]
pub fn start(ui: &mut UI,
             bldr_url: &str,
             origin: &str,
             revision: Option<&str>,
             secret: bool,
             encryption: bool,
             token: Option<&str>,
             cache: &Path)
             -> Result<()> {
    let api_client = Client::new(bldr_url, PRODUCT, VERSION, None)?;

    if secret {
        handle_secret(ui, &api_client, origin, token, cache)
    } else if encryption {
        handle_encryption(ui, &api_client, origin, token, cache)
    } else {
        handle_public(ui, &api_client, origin, revision, token, cache)
    }
}

fn handle_public(ui: &mut UI,
                 api_client: &BoxedClient,
                 origin: &str,
                 revision: Option<&str>,
                 token: Option<&str>,
                 cache: &Path)
                 -> Result<()> {
    match revision {
        Some(revision) => {
            let nwr = format!("{}-{}", origin, revision);
            ui.begin(format!("Downloading public origin key {}", &nwr))?;
            match download_key(ui, api_client, &nwr, origin, revision, token, cache) {
                Ok(()) => {
                    let msg = format!("Download of {} public origin key completed.", nwr);
                    ui.end(msg)?;
                    Ok(())
                }
                Err(e) => Err(e),
            }
        }
        None => {
            ui.begin(format!("Downloading public origin keys for {}", origin))?;
            match api_client.show_origin_keys(origin) {
                Ok(ref keys) if keys.is_empty() => {
                    ui.end(format!("No public keys for {}.", origin))?;
                    Ok(())
                }
                Ok(keys) => {
                    for key in keys {
                        let nwr = format!("{}-{}", key.origin, key.revision);
                        download_key(ui,
                                     api_client,
                                     &nwr,
                                     &key.origin,
                                     &key.revision,
                                     token,
                                     cache)?;
                    }
                    ui.end(format!("Download of {} public origin keys completed.", &origin))?;
                    Ok(())
                }
                Err(e) => Err(Error::from(e)),
            }
        }
    }
}

fn handle_secret(ui: &mut UI,
                 api_client: &BoxedClient,
                 origin: &str,
                 token: Option<&str>,
                 cache: &Path)
                 -> Result<()> {
    if token.is_none() {
        ui.end("No auth token found. You must pass a token to download secret keys.")?;
        return Ok(());
    }

    ui.begin(format!("Downloading secret origin keys for {}", origin))?;
    download_secret_key(ui, &api_client, origin, token.unwrap(), cache)?; // unwrap is safe because we already checked it above
    ui.end(format!("Download of {} public origin keys completed.", &origin))?;
    Ok(())
}

fn handle_encryption(ui: &mut UI,
                     api_client: &BoxedClient,
                     origin: &str,
                     token: Option<&str>,
                     cache: &Path)
                     -> Result<()> {
    if token.is_none() {
        ui.end("No auth token found. You must pass a token to download secret keys.")?;
        return Ok(());
    }

    ui.begin(format!("Downloading public encryption origin key for {}", origin))?;
    download_public_encryption_key(ui, &api_client, origin, token.unwrap(), cache)?; // unwrap is safe because we already checked it above
    ui.end(format!("Download of {} public encryption keys completed.", &origin))?;
    Ok(())
}

pub fn download_public_encryption_key(ui: &mut UI,
                                      api_client: &BoxedClient,
                                      name: &str,
                                      token: &str,
                                      cache: &Path)
                                      -> Result<()> {
    let download_fn = || -> Result<()> {
        ui.status(Status::Downloading, "latest public encryption key")?;
        let key_path =
            api_client.fetch_origin_public_encryption_key(name, token, cache, ui.progress())?;
        ui.status(Status::Cached,
                  key_path.file_name().unwrap().to_str().unwrap() /* lol */)?;
        Ok(())
    };

    if retry(wait_for(RETRY_WAIT, RETRIES), download_fn).is_err() {
        return Err(Error::from(common::error::Error::DownloadFailed(format!(
            "We tried {} times but could not download the latest public encryption key. Giving up.",
            RETRIES,
        ))));
    }

    Ok(())
}

fn download_secret_key(ui: &mut UI,
                       api_client: &BoxedClient,
                       name: &str,
                       token: &str,
                       cache: &Path)
                       -> Result<()> {
    let download_fn = || -> Result<()> {
        ui.status(Status::Downloading, "latest secret key")?;
        let key_path = api_client.fetch_secret_origin_key(name, token, cache, ui.progress())?;
        ui.status(Status::Cached,
                  key_path.file_name().unwrap().to_str().unwrap() /* lol */)?;
        Ok(())
    };

    if retry(wait_for(RETRY_WAIT, RETRIES), download_fn).is_err() {
        return Err(Error::from(common::error::Error::DownloadFailed(format!(
            "We tried {} times but could not download the latest secret origin key. Giving up.",
            RETRIES,
        ))));
    }

    Ok(())
}

fn download_key(ui: &mut UI,
                api_client: &BoxedClient,
                nwr: &str,
                name: &str,
                rev: &str,
                token: Option<&str>,
                cache: &Path)
                -> Result<()> {
    match SigKeyPair::get_public_key_path(&nwr, &cache) {
        Ok(_) => ui.status(Status::Using, &format!("{} in {}", nwr, cache.display()))?,
        Err(_) => {
            let download_fn = || -> Result<()> {
                ui.status(Status::Downloading, &nwr)?;
                api_client.fetch_origin_key(name, rev, token, cache, ui.progress())?;
                ui.status(Status::Cached, &format!("{} to {}", nwr, cache.display()))?;
                Ok(())
            };

            if retry(wait_for(RETRY_WAIT, RETRIES), download_fn).is_err() {
                return Err(Error::from(common::error::Error::DownloadFailed(format!(
                    "We tried {} times but could not download {}/{} origin key. Giving up.",
                    RETRIES, &name, &rev
                ))));
            }
        }
    }
    Ok(())
}
<commit_msg>Fixing typo public->secret in origin secret key download<commit_after>use std::path::Path;

use crate::{api_client::{BoxedClient,
                         Client},
            common::{self,
                     command::package::install::{RETRIES,
                                                 RETRY_WAIT},
                     ui::{Status,
                          UIWriter,
                          UI}},
            hcore::{crypto::SigKeyPair,
                    util::wait_for}};

use crate::{error::{Error,
                    Result},
            PRODUCT,
            VERSION};

use retry::retry;

#[allow(clippy::too_many_arguments)]
pub fn start(ui: &mut UI,
             bldr_url: &str,
             origin: &str,
             revision: Option<&str>,
             secret: bool,
             encryption: bool,
             token: Option<&str>,
             cache: &Path)
             -> Result<()> {
    let api_client = Client::new(bldr_url, PRODUCT, VERSION, None)?;

    if secret {
        handle_secret(ui, &api_client, origin, token, cache)
    } else if encryption {
        handle_encryption(ui, &api_client, origin, token, cache)
    } else {
        handle_public(ui, &api_client, origin, revision, token, cache)
    }
}

fn handle_public(ui: &mut UI,
                 api_client: &BoxedClient,
                 origin: &str,
                 revision: Option<&str>,
                 token: Option<&str>,
                 cache: &Path)
                 -> Result<()> {
    match revision {
        Some(revision) => {
            let nwr = format!("{}-{}", origin, revision);
            ui.begin(format!("Downloading public origin key {}", &nwr))?;
            match download_key(ui, api_client, &nwr, origin, revision, token, cache) {
                Ok(()) => {
                    let msg = format!("Download of {} public origin key completed.", nwr);
                    ui.end(msg)?;
                    Ok(())
                }
                Err(e) => Err(e),
            }
        }
        None => {
            ui.begin(format!("Downloading public origin keys for {}", origin))?;
            match api_client.show_origin_keys(origin) {
                Ok(ref keys) if keys.is_empty() => {
                    ui.end(format!("No public keys for {}.", origin))?;
                    Ok(())
                }
                Ok(keys) => {
                    for key in keys {
                        let nwr = format!("{}-{}", key.origin, key.revision);
                        download_key(ui,
                                     api_client,
                                     &nwr,
                                     &key.origin,
                                     &key.revision,
                                     token,
                                     cache)?;
                    }
                    ui.end(format!("Download of {} public origin keys completed.", &origin))?;
                    Ok(())
                }
                Err(e) => Err(Error::from(e)),
            }
        }
    }
}

fn handle_secret(ui: &mut UI,
                 api_client: &BoxedClient,
                 origin: &str,
                 token: Option<&str>,
                 cache: &Path)
                 -> Result<()> {
    if token.is_none() {
        ui.end("No auth token found. You must pass a token to download secret keys.")?;
        return Ok(());
    }

    ui.begin(format!("Downloading secret origin keys for {}", origin))?;
    download_secret_key(ui, &api_client, origin, token.unwrap(), cache)?; // unwrap is safe because we already checked it above
    ui.end(format!("Download of {} secret origin keys completed.", &origin))?;
    Ok(())
}

fn handle_encryption(ui: &mut UI,
                     api_client: &BoxedClient,
                     origin: &str,
                     token: Option<&str>,
                     cache: &Path)
                     -> Result<()> {
    if token.is_none() {
        ui.end("No auth token found. You must pass a token to download secret keys.")?;
        return Ok(());
    }

    ui.begin(format!("Downloading public encryption origin key for {}", origin))?;
    download_public_encryption_key(ui, &api_client, origin, token.unwrap(), cache)?; // unwrap is safe because we already checked it above
    ui.end(format!("Download of {} public encryption keys completed.", &origin))?;
    Ok(())
}

pub fn download_public_encryption_key(ui: &mut UI,
                                      api_client: &BoxedClient,
                                      name: &str,
                                      token: &str,
                                      cache: &Path)
                                      -> Result<()> {
    let download_fn = || -> Result<()> {
        ui.status(Status::Downloading, "latest public encryption key")?;
        let key_path =
            api_client.fetch_origin_public_encryption_key(name, token, cache, ui.progress())?;
        ui.status(Status::Cached,
                  key_path.file_name().unwrap().to_str().unwrap() /* lol */)?;
        Ok(())
    };

    if retry(wait_for(RETRY_WAIT, RETRIES), download_fn).is_err() {
        return Err(Error::from(common::error::Error::DownloadFailed(format!(
            "We tried {} times but could not download the latest public encryption key. Giving up.",
            RETRIES,
        ))));
    }

    Ok(())
}

fn download_secret_key(ui: &mut UI,
                       api_client: &BoxedClient,
                       name: &str,
                       token: &str,
                       cache: &Path)
                       -> Result<()> {
    let download_fn = || -> Result<()> {
        ui.status(Status::Downloading, "latest secret key")?;
        let key_path = api_client.fetch_secret_origin_key(name, token, cache, ui.progress())?;
        ui.status(Status::Cached,
                  key_path.file_name().unwrap().to_str().unwrap() /* lol */)?;
        Ok(())
    };

    if retry(wait_for(RETRY_WAIT, RETRIES), download_fn).is_err() {
        return Err(Error::from(common::error::Error::DownloadFailed(format!(
            "We tried {} times but could not download the latest secret origin key. Giving up.",
            RETRIES,
        ))));
    }

    Ok(())
}

fn download_key(ui: &mut UI,
                api_client: &BoxedClient,
                nwr: &str,
                name: &str,
                rev: &str,
                token: Option<&str>,
                cache: &Path)
                -> Result<()> {
    match SigKeyPair::get_public_key_path(&nwr, &cache) {
        Ok(_) => ui.status(Status::Using, &format!("{} in {}", nwr, cache.display()))?,
        Err(_) => {
            let download_fn = || -> Result<()> {
                ui.status(Status::Downloading, &nwr)?;
                api_client.fetch_origin_key(name, rev, token, cache, ui.progress())?;
                ui.status(Status::Cached, &format!("{} to {}", nwr, cache.display()))?;
                Ok(())
            };

            if retry(wait_for(RETRY_WAIT, RETRIES), download_fn).is_err() {
                return Err(Error::from(common::error::Error::DownloadFailed(format!(
                    "We tried {} times but could not download {}/{} origin key. Giving up.",
                    RETRIES, &name, &rev
                ))));
            }
        }
    }
    Ok(())
}
<|endoftext|>


<commit_before>use std::{ io, fs };
use std::sync::Arc;
use std::os::unix::io::AsRawFd;
use libc::off_t;
use bytes::buf::{ Buf, BufMut };
use futures::{ Poll, Stream, Async };
use futures::sync::{ BiLock, BiLockAcquired };
use tokio_io::{ AsyncRead, AsyncWrite };
use tokio_core::net::TcpStream;
use nix;
use ::error;

pub struct BiTcpStream(pub BiLockAcquired<TcpStream>);

impl io::Read for BiTcpStream {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.0.read(buf)
    }
}

impl io::Write for BiTcpStream {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.0.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.0.flush()
    }
}

impl AsyncRead for BiTcpStream {
    unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
        AsyncRead::prepare_uninitialized_buffer(&self.0 as &TcpStream, buf)
    }

    fn read_buf<B: BufMut>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
        AsyncRead::read_buf(&mut self.0 as &mut TcpStream, buf)
    }
}

impl AsyncWrite for BiTcpStream {
    fn shutdown(&mut self) -> Poll<(), io::Error> {
        AsyncWrite::shutdown(&mut self.0 as &mut TcpStream)
    }

    fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
        AsyncWrite::write_buf(&mut self.0 as &mut TcpStream, buf)
    }
}

pub struct SendFileFut {
    pub socket: Arc<BiLock<TcpStream>>,
    pub fd: fs::File,
    pub offset: off_t,
    pub end: usize
}

impl Stream for SendFileFut {
    type Item = usize;
    type Error = error::Error;

    fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
        #[cfg(any(target_os = "linux", target_os = "android"))]
        use nix::sys::sendfile::sendfile;

        #[cfg(any(apple, freebsdlike))]
        use self::bsd::sendfile;

        let count = match self.end.checked_sub(self.offset as usize) {
            Some(0) | None => return Ok(Async::Ready(None)),
            Some(count) => count
        };

        let socket = match self.socket.poll_lock() {
            Async::Ready(socket) => socket,
            Async::NotReady => return Ok(Async::NotReady)
        };

        if let Async::NotReady = socket.poll_write() {
            return Ok(Async::NotReady)
        }

        match sendfile(socket.as_raw_fd(), self.fd.as_raw_fd(), Some(&mut self.offset), count) {
            Ok(len) => {
                Ok(Async::Ready(Some(len)))
            },
            Err(ref err) if nix::Errno::EAGAIN == err.errno() => {
                // TODO https://github.com/tokio-rs/tokio-core/issues/196
                // socket.need_write();

                Ok(Async::NotReady)
            },
            Err(err) => Err(err.into())
        }
    }
}

#[cfg(any(apple, freebsdlike))]
mod bsd {
    use std::ptr;
    use std::os::unix::io::RawFd;
    use libc::{ off_t, sendfile as __sendfile };
    use nix;

    pub fn sendfile(out_fd: RawFd, in_fd: RawFd, offset: Option<&mut off_t>, count: usize) -> nix::Result<usize> {
        let off =
            if let Some(ref off) = offset { **off }
            else { 0 };
        let mut len = count as _;

        #[cfg(apple)]
        let ret = unsafe { __sendfile(in_fd, out_fd, off, &mut len, ptr::null_mut(), 0) };

        #[cfg(freebsdlike)]
        let ret = unsafe { __sendfile(in_fd, out_fd, off, count, ptr::null_mut(), &mut len, 0) };

        if let Some(offset) = offset {
            *offset += len;
        }

        match ret {
            0 => Ok(len as usize),
            _ => Err(nix::Error::last())
        }
    }
}
<commit_msg>[Fixed] nix sendfile<commit_after>use std::{ io, fs };
use std::sync::Arc;
use std::os::unix::io::AsRawFd;
use libc::off_t;
use bytes::buf::{ Buf, BufMut };
use futures::{ Poll, Stream, Async };
use futures::sync::{ BiLock, BiLockAcquired };
use tokio_io::{ AsyncRead, AsyncWrite };
use tokio_core::net::TcpStream;
use nix;
use ::error;

pub struct BiTcpStream(pub BiLockAcquired<TcpStream>);

impl io::Read for BiTcpStream {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.0.read(buf)
    }
}

impl io::Write for BiTcpStream {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.0.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.0.flush()
    }
}

impl AsyncRead for BiTcpStream {
    unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
        AsyncRead::prepare_uninitialized_buffer(&self.0 as &TcpStream, buf)
    }

    fn read_buf<B: BufMut>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
        AsyncRead::read_buf(&mut self.0 as &mut TcpStream, buf)
    }
}

impl AsyncWrite for BiTcpStream {
    fn shutdown(&mut self) -> Poll<(), io::Error> {
        AsyncWrite::shutdown(&mut self.0 as &mut TcpStream)
    }

    fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
        AsyncWrite::write_buf(&mut self.0 as &mut TcpStream, buf)
    }
}

pub struct SendFileFut {
    pub socket: Arc<BiLock<TcpStream>>,
    pub fd: fs::File,
    pub offset: off_t,
    pub end: usize
}

impl Stream for SendFileFut {
    type Item = usize;
    type Error = error::Error;

    fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
        #[cfg(any(target_os = "linux", target_os = "android"))]
        use nix::sys::sendfile::sendfile;

        #[cfg(any(apple, freebsdlike))]
        use self::bsd::sendfile;

        let count = match self.end.checked_sub(self.offset as usize) {
            Some(0) | None => return Ok(Async::Ready(None)),
            Some(count) => count
        };

        let socket = match self.socket.poll_lock() {
            Async::Ready(socket) => socket,
            Async::NotReady => return Ok(Async::NotReady)
        };

        if let Async::NotReady = socket.poll_write() {
            return Ok(Async::NotReady)
        }

        match sendfile(socket.as_raw_fd(), self.fd.as_raw_fd(), Some(&mut self.offset), count) {
            Ok(len) => {
                Ok(Async::Ready(Some(len)))
            },
            Err(ref err) if &nix::Error::Sys(nix::Errno::EAGAIN) == err => {
                // TODO https://github.com/tokio-rs/tokio-core/issues/196
                // socket.need_write();

                Ok(Async::NotReady)
            },
            Err(err) => Err(err.into())
        }
    }
}

#[cfg(any(apple, freebsdlike))]
mod bsd {
    use std::ptr;
    use std::os::unix::io::RawFd;
    use libc::{ off_t, sendfile as __sendfile };
    use nix;

    pub fn sendfile(out_fd: RawFd, in_fd: RawFd, offset: Option<&mut off_t>, count: usize) -> nix::Result<usize> {
        let off =
            if let Some(ref off) = offset { **off }
            else { 0 };
        let mut len = count as _;

        #[cfg(apple)]
        let ret = unsafe { __sendfile(in_fd, out_fd, off, &mut len, ptr::null_mut(), 0) };

        #[cfg(freebsdlike)]
        let ret = unsafe { __sendfile(in_fd, out_fd, off, count, ptr::null_mut(), &mut len, 0) };

        if let Some(offset) = offset {
            *offset += len;
        }

        match ret {
            0 => Ok(len as usize),
            _ => Err(nix::Error::last())
        }
    }
}
<|endoftext|>


<commit_before>/*
 * UpdateHub
 * Copyright (C) 2017
 * O.S. Systems Sofware LTDA: contact@ossystems.com.br
 *
 * SPDX-License-Identifier:     GPL-2.0
 */

use serde::{Deserialize, Deserializer, de};
use serde_ini;

use std::io;
use std::time::Duration;

const SYSTEM_SETTINGS_PATH: &'static str = "/etc/updatehub.conf";

#[derive(Default, PartialEq, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Settings {
    pub polling: Polling,
    pub storage: Storage,
    pub update: Update,
    pub network: Network,
    pub firmware: Firmware,
}

impl Settings {
    pub fn new() -> Self {
        Settings::default()
    }

    pub fn load(self) -> Result<Self, SettingsError> {
        use std::path::Path;
        use std::fs::File;
        use std::io::Read;

        let path = Path::new(SYSTEM_SETTINGS_PATH);

        if path.exists() {
            info!(
                "Loading system settings from '{}'...",
                path.to_string_lossy()
            );

            let mut content = String::new();
            File::open(path)?.read_to_string(&mut content)?;

            Ok(self.parse(&content)?)
        } else {
            debug!(
                "System settings file {} does not exists.",
                path.to_string_lossy()
            );
            info!("Using default system settings...");
            Ok(self)
        }
    }

    fn parse(self, content: &str) -> Result<Self, SettingsError> {
        fn has_protocol_prefix(server: &str) -> bool {
            server.starts_with("http://") || server.starts_with("https://")
        }

        let settings = serde_ini::from_str::<Settings>(&content)?;

        if settings.polling.interval.as_secs() < 60 {
            error!("Invalid setting for polling interval. The interval cannot be less than 60 seconds");
            return Err(SettingsError::InvalidInterval);
        }

        if !has_protocol_prefix(&settings.network.server_address) {
            error!("Invalid setting for server address. The server address must use the protocol prefix");
            return Err(SettingsError::InvalidServerAddress);
        }

        Ok(settings)
    }
}

#[derive(Debug)]
pub enum SettingsError {
    Io(io::Error),
    Ini(serde_ini::de::Error),
    InvalidInterval,
    InvalidServerAddress,
}

impl From<io::Error> for SettingsError {
    fn from(err: io::Error) -> SettingsError {
        SettingsError::Io(err)
    }
}

impl From<serde_ini::de::Error> for SettingsError {
    fn from(err: serde_ini::de::Error) -> SettingsError {
        SettingsError::Ini(err)
    }
}

#[derive(Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct Polling {
    #[serde(deserialize_with = "ini_de_duration_from_str")]
    pub interval: Duration,
    #[serde(deserialize_with = "ini_de_bool_from_str")]
    pub enabled: bool,
}

impl Default for Polling {
    fn default() -> Self {
        Polling {
            interval: Duration::new(86_400, 0), // 1 day
            enabled: true,
        }
    }
}

#[derive(Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct Storage {
    #[serde(deserialize_with = "ini_de_bool_from_str")]
    pub read_only: bool,
    pub runtime_settings: String,
}

impl Default for Storage {
    fn default() -> Self {
        Storage {
            read_only: false,
            runtime_settings: "/var/lib/updatehub.conf".to_string(),
        }
    }
}

#[derive(Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct Update {
    pub download_dir: String,
    #[serde(rename = "SupportedInstallModes")]
    #[serde(deserialize_with = "ini_de_vec_from_str")]
    pub install_modes: Vec<String>,
}

impl Default for Update {
    fn default() -> Self {
        Update {
            download_dir: "/tmp".to_string(),
            install_modes: [
                "dry-run",
                "copy",
                "flash",
                "imxkobs",
                "raw",
                "tarball",
                "ubifs",
            ].iter()
                .map(|i| i.to_string())
                .collect(),
        }
    }
}

#[derive(Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct Network {
    pub server_address: String,
}

impl Default for Network {
    fn default() -> Self {
        Network { server_address: "https://api.updatehub.io".to_string() }
    }
}

#[derive(Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct Firmware {
    pub metadata_path: String,
}

impl Default for Firmware {
    fn default() -> Self {
        Firmware { metadata_path: "/usr/share/updatehub".to_string() }
    }
}

fn ini_de_duration_from_str<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
    D: Deserializer<'de>,
{
    use parse_duration::parse;

    let s = String::deserialize(deserializer)?;
    parse(&s).map_err(de::Error::custom)
}

fn ini_de_bool_from_str<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
    D: Deserializer<'de>,
{
    use std::str::FromStr;

    let s = String::deserialize(deserializer)?;
    bool::from_str(&s).map_err(de::Error::custom)
}

fn ini_de_vec_from_str<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
    D: Deserializer<'de>,
{
    let v: Vec<String> = String::deserialize(deserializer)?
        .split(",")
        .map(|s| s.to_string())
        .collect();

    Ok(v)
}

/*
extern crate chrono;
use self::chrono::{DateTime, Utc};

#[derive(Deserialize)]
struct PersistentSettings {
    polling: PersistentPolling,
}

struct PersistentPolling {
    last: DateTime<Utc>,
    first: DateTime<Utc>,
    extra_interval: usize,
    retries: usize,
    now: bool,
}

struct PersistentUpdate {
    upgrading_to: i8,
}
impl Default for PersistentPolling {
    fn default() -> Self {
        PersistentPolling {
            last: Utc::now(),
            first: Utc::now(),
            extra_interval: 0,
            retries: 0,
            now: false,
        }
    }
}
*/

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

    #[test]
    fn ok() {
        let ini = r"
[Polling]
Interval=60s
Enabled=false

[Storage]
ReadOnly=true
RuntimeSettings=/run/updatehub/state

[Update]
DownloadDir=/tmp/download
SupportedInstallModes=mode1,mode2

[Network]
ServerAddress=http://localhost

[Firmware]
MetadataPath=/tmp/metadata
";

        let expected = Settings {
            polling: Polling {
                interval: Duration::new(60, 0),
                enabled: false,
            },
            storage: Storage {
                read_only: true,
                runtime_settings: "/run/updatehub/state".to_string(),
            },
            update: Update {
                download_dir: "/tmp/download".to_string(),
                install_modes: ["mode1", "mode2"].iter().map(|i| i.to_string()).collect(),
            },
            network: Network { server_address: "http://localhost".to_string() },
            firmware: Firmware { metadata_path: "/tmp/metadata".to_string() },
        };

        assert!(
            serde_ini::from_str::<Settings>(&ini)
                .map_err(|e| println!("{}", e))
                .ok() == Some(expected)
        );
    }

    #[test]
    fn invalid_polling_interval() {
        let ini = r"
[Polling]
Interval=59s
Enabled=false

[Storage]
ReadOnly=true
RuntimeSettings=/run/updatehub/state

[Update]
DownloadDir=/tmp/download
SupportedInstallModes=mode1,mode2

[Network]
ServerAddress=http://localhost

[Firmware]
MetadataPath=/tmp/metadata
";
        assert!(Settings::new().parse(&ini).is_err());
    }

    #[test]
    fn invalid_network_server_address() {
        let ini = r"
[Polling]
Interval=60s
Enabled=false

[Storage]
ReadOnly=true
RuntimeSettings=/run/updatehub/state

[Update]
DownloadDir=/tmp/download
SupportedInstallModes=mode1,mode2

[Network]
ServerAddress=localhost

[Firmware]
MetadataPath=/tmp/metadata
";
        assert!(Settings::new().parse(&ini).is_err());
    }
}
<commit_msg>settings: rework validation of polling interval<commit_after>/*
 * UpdateHub
 * Copyright (C) 2017
 * O.S. Systems Sofware LTDA: contact@ossystems.com.br
 *
 * SPDX-License-Identifier:     GPL-2.0
 */

use serde::{Deserialize, Deserializer, de};
use serde_ini;

use std::io;
use std::time::Duration;

const SYSTEM_SETTINGS_PATH: &'static str = "/etc/updatehub.conf";

#[derive(Default, PartialEq, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Settings {
    pub polling: Polling,
    pub storage: Storage,
    pub update: Update,
    pub network: Network,
    pub firmware: Firmware,
}

impl Settings {
    pub fn new() -> Self {
        Settings::default()
    }

    pub fn load(self) -> Result<Self, SettingsError> {
        use std::path::Path;
        use std::fs::File;
        use std::io::Read;

        let path = Path::new(SYSTEM_SETTINGS_PATH);

        if path.exists() {
            info!(
                "Loading system settings from '{}'...",
                path.to_string_lossy()
            );

            let mut content = String::new();
            File::open(path)?.read_to_string(&mut content)?;

            Ok(self.parse(&content)?)
        } else {
            debug!(
                "System settings file {} does not exists.",
                path.to_string_lossy()
            );
            info!("Using default system settings...");
            Ok(self)
        }
    }

    fn parse(self, content: &str) -> Result<Self, SettingsError> {
        fn has_protocol_prefix(server: &str) -> bool {
            server.starts_with("http://") || server.starts_with("https://")
        }

        fn has_valid_polling_interval(interval: &Duration) -> bool {
            if *interval < Duration::new(60, 0) {
                false
            } else {
                true
            }
        }

        let settings = serde_ini::from_str::<Settings>(&content)?;

        if !has_valid_polling_interval(&settings.polling.interval) {
            error!("Invalid setting for polling interval. The interval cannot be less than 60 seconds");
            return Err(SettingsError::InvalidInterval);
        }

        if !has_protocol_prefix(&settings.network.server_address) {
            error!("Invalid setting for server address. The server address must use the protocol prefix");
            return Err(SettingsError::InvalidServerAddress);
        }

        Ok(settings)
    }
}

#[derive(Debug)]
pub enum SettingsError {
    Io(io::Error),
    Ini(serde_ini::de::Error),
    InvalidInterval,
    InvalidServerAddress,
}

impl From<io::Error> for SettingsError {
    fn from(err: io::Error) -> SettingsError {
        SettingsError::Io(err)
    }
}

impl From<serde_ini::de::Error> for SettingsError {
    fn from(err: serde_ini::de::Error) -> SettingsError {
        SettingsError::Ini(err)
    }
}

#[derive(Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct Polling {
    #[serde(deserialize_with = "ini_de_duration_from_str")]
    pub interval: Duration,
    #[serde(deserialize_with = "ini_de_bool_from_str")]
    pub enabled: bool,
}

impl Default for Polling {
    fn default() -> Self {
        Polling {
            interval: Duration::new(86_400, 0), // 1 day
            enabled: true,
        }
    }
}

#[derive(Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct Storage {
    #[serde(deserialize_with = "ini_de_bool_from_str")]
    pub read_only: bool,
    pub runtime_settings: String,
}

impl Default for Storage {
    fn default() -> Self {
        Storage {
            read_only: false,
            runtime_settings: "/var/lib/updatehub.conf".to_string(),
        }
    }
}

#[derive(Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct Update {
    pub download_dir: String,
    #[serde(rename = "SupportedInstallModes")]
    #[serde(deserialize_with = "ini_de_vec_from_str")]
    pub install_modes: Vec<String>,
}

impl Default for Update {
    fn default() -> Self {
        Update {
            download_dir: "/tmp".to_string(),
            install_modes: [
                "dry-run",
                "copy",
                "flash",
                "imxkobs",
                "raw",
                "tarball",
                "ubifs",
            ].iter()
                .map(|i| i.to_string())
                .collect(),
        }
    }
}

#[derive(Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct Network {
    pub server_address: String,
}

impl Default for Network {
    fn default() -> Self {
        Network { server_address: "https://api.updatehub.io".to_string() }
    }
}

#[derive(Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct Firmware {
    pub metadata_path: String,
}

impl Default for Firmware {
    fn default() -> Self {
        Firmware { metadata_path: "/usr/share/updatehub".to_string() }
    }
}

fn ini_de_duration_from_str<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
    D: Deserializer<'de>,
{
    use parse_duration::parse;

    let s = String::deserialize(deserializer)?;
    parse(&s).map_err(de::Error::custom)
}

fn ini_de_bool_from_str<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
    D: Deserializer<'de>,
{
    use std::str::FromStr;

    let s = String::deserialize(deserializer)?;
    bool::from_str(&s).map_err(de::Error::custom)
}

fn ini_de_vec_from_str<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
    D: Deserializer<'de>,
{
    let v: Vec<String> = String::deserialize(deserializer)?
        .split(",")
        .map(|s| s.to_string())
        .collect();

    Ok(v)
}

/*
extern crate chrono;
use self::chrono::{DateTime, Utc};

#[derive(Deserialize)]
struct PersistentSettings {
    polling: PersistentPolling,
}

struct PersistentPolling {
    last: DateTime<Utc>,
    first: DateTime<Utc>,
    extra_interval: usize,
    retries: usize,
    now: bool,
}

struct PersistentUpdate {
    upgrading_to: i8,
}
impl Default for PersistentPolling {
    fn default() -> Self {
        PersistentPolling {
            last: Utc::now(),
            first: Utc::now(),
            extra_interval: 0,
            retries: 0,
            now: false,
        }
    }
}
*/

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

    #[test]
    fn ok() {
        let ini = r"
[Polling]
Interval=60s
Enabled=false

[Storage]
ReadOnly=true
RuntimeSettings=/run/updatehub/state

[Update]
DownloadDir=/tmp/download
SupportedInstallModes=mode1,mode2

[Network]
ServerAddress=http://localhost

[Firmware]
MetadataPath=/tmp/metadata
";

        let expected = Settings {
            polling: Polling {
                interval: Duration::new(60, 0),
                enabled: false,
            },
            storage: Storage {
                read_only: true,
                runtime_settings: "/run/updatehub/state".to_string(),
            },
            update: Update {
                download_dir: "/tmp/download".to_string(),
                install_modes: ["mode1", "mode2"].iter().map(|i| i.to_string()).collect(),
            },
            network: Network { server_address: "http://localhost".to_string() },
            firmware: Firmware { metadata_path: "/tmp/metadata".to_string() },
        };

        assert!(
            serde_ini::from_str::<Settings>(&ini)
                .map_err(|e| println!("{}", e))
                .ok() == Some(expected)
        );
    }

    #[test]
    fn invalid_polling_interval() {
        let ini = r"
[Polling]
Interval=59s
Enabled=false

[Storage]
ReadOnly=true
RuntimeSettings=/run/updatehub/state

[Update]
DownloadDir=/tmp/download
SupportedInstallModes=mode1,mode2

[Network]
ServerAddress=http://localhost

[Firmware]
MetadataPath=/tmp/metadata
";
        assert!(Settings::new().parse(&ini).is_err());
    }

    #[test]
    fn invalid_network_server_address() {
        let ini = r"
[Polling]
Interval=60s
Enabled=false

[Storage]
ReadOnly=true
RuntimeSettings=/run/updatehub/state

[Update]
DownloadDir=/tmp/download
SupportedInstallModes=mode1,mode2

[Network]
ServerAddress=localhost

[Firmware]
MetadataPath=/tmp/metadata
";
        assert!(Settings::new().parse(&ini).is_err());
    }
}
<|endoftext|>


<commit_before>use action;
use settings;
use glfw;
use Settings = piston::game::Settings;
use Game = piston::game::Game;
use piston::gl::Gl;
use graphics;
use graphics::*;
use object;
use Object = object::Object;
use text;
use game_state;

pub struct SnakeApp {
    settings: Settings,
    // Tells where the surface is.
    surface_y: Option<f64>,
    game_state: Option<game_state::GameState>,
    camera_pos: Option<[f64, ..2]>,
    camera_follow_percentage: Option<f64>,
    player_index: Option<uint>,
    blood_bar_index: Option<uint>,
    air_bar_index: Option<uint>,
    // Contains the game objects.
    objects: Vec<Object>,
}

impl Game for SnakeApp {
    fn get_settings<'a>(&'a self) -> &'a Settings { &self.settings }

    fn render(&self, c: &graphics::Context, gl: &mut Gl) {
        // Get camera coordinates.
        let (cam_x, cam_y) = if self.camera_pos.is_some() {
                let camera_pos = self.camera_pos.unwrap();
                (camera_pos[0], camera_pos[1])
            } else { (0.0, 0.0) };

        // Render surface.
        let surface_y = self.surface_y.unwrap();
        c.rect(-1.0, surface_y - cam_y, 2.0, 0.05).color(settings::BLUE).fill(gl);

        // Render objects in layers.
        let cam = &c.trans(-cam_x, -cam_y);
        for i in range(0u, settings::NUMBER_OF_LAYERS) {
            for obj in self.objects.iter() {
                if obj.layer == i { obj.render(cam, c, gl); }
            }
        }

        let text_c = c.flip_v_local();
        let text_c = text_c.zoom(0.0025);
        match self.game_state.unwrap() {
            game_state::Win => {
                let pos = settings::YOU_WIN_POS;
                text::text(settings::YOU_WIN_TEXT,
                    &text_c
                    .trans(pos[0], pos[1])
                    .color(settings::YOU_WIN_TEXT_COLOR)
                , gl);
            },
            game_state::Loose => {
                let pos = settings::YOU_LOOSE_POS;
                text::text(settings::YOU_LOOSE_TEXT,
                    &text_c
                    .trans(pos[0], pos[1])
                    .color(settings::YOU_LOOSE_TEXT_COLOR)
                , gl);
            },
            game_state::Play => {

            },
        }

        // TEST
        // text::text("restart", &c.flip_v_local().zoom(0.001).color(settings::BLACK), gl);
    }

    fn update(&mut self, dt: f64) {
        self.update_objects(dt);
        self.fill_air();
        self.win();
        self.loose();
        self.show_blood();
        self.show_air();
        self.follow_player(dt);
    }

    fn load(&mut self) {
        self.camera_follow_percentage = Some(settings::CAMERA_FOLLOW_PERCENTAGE);
        self.camera_pos = Some(settings::INITIAL_CAMERA_POS);
        self.surface_y = Some(settings::SURFACE_Y);
        self.game_state = Some(settings::INITIAL_GAME_STATE);

        // Add player.
        self.objects.push(Object::player(
            settings::ORIGIN,
            settings::BLUE,
            settings::PLAYER_INITIAL_BLOOD,
            settings::PLAYER_INITIAL_AIR,
            [settings::PLAYER_ACCELERATION_LEFT, settings::PLAYER_ACCELERATION_RIGHT],
            [settings::PLAYER_ACCELERATION_UP, settings::PLAYER_ACCELERATION_DOWN]
        ));
        self.objects.push(Object::bar_background());
        self.player_index = Some(0);

        // Add blood and air bar.
        self.add_bars();

        // Add air bottles.
        self.add_air_bottles();

        // Add sharks.
        self.add_snakes();
    }

    fn key_press(&mut self, key: glfw::Key) {
        // TEST
        // println!("Key pressed {}", key);

        if self.game_state.unwrap() != game_state::Play { return; }

        match (key, self.player_index) {
            (glfw::KeyRight, Some(player_index)) => {
                self.objects.get_mut(player_index).move_right();
            },
            (glfw::KeyUp, Some(player_index)) => {
                self.objects.get_mut(player_index).move_up();
            },
            (glfw::KeyLeft, Some(player_index)) => {
                self.objects.get_mut(player_index).move_left();
            },
            (glfw::KeyDown, Some(player_index)) => {
                self.objects.get_mut(player_index).move_down();
            },
            _ => {},
        }
    }

    fn key_release(&mut self, key: glfw::Key) {
        // TEST
        // println!("Key released {}", key);

        if key == glfw::KeyEnter || key == glfw::KeySpace {
            match self.game_state.unwrap() {
                game_state::Win | game_state::Loose => self.restart(),
                _ => {},
            }
        }
    }
}

impl SnakeApp {
    pub fn new() -> SnakeApp {
        let exit_on_esc = true;
        let background_color = settings::WATER_COLOR;
        SnakeApp {
            camera_pos: None,
            camera_follow_percentage: None,
            settings: Settings::new(exit_on_esc, background_color),
            game_state: None,
            surface_y: None,
            objects: Vec::new(),
            player_index: None,
            blood_bar_index: None,
            air_bar_index: None,
        }
    }

    pub fn add_bars(&mut self) {
        self.objects.push(Object::bar(
            settings::AIR_BAR_POS,
            "air",
            settings::AIR_BAR_TEXT_COLOR,
            settings::AIR_BAR_BACKGROUND_COLOR,
            settings::AIR_BAR_BAR_COLOR,
            settings::AIR_BAR_INITIAL_VALUE
        ));
        self.air_bar_index = Some(self.objects.len() - 1);
        self.objects.push(Object::bar(
            settings::BLOOD_BAR_POS,
            "blood",
            settings::BLOOD_BAR_TEXT_COLOR,
            settings::BLOOD_BAR_BACKGROUND_COLOR,
            settings::BLOOD_BAR_BAR_COLOR,
            settings::BLOOD_BAR_INITIAL_VALUE
        ));
        self.blood_bar_index = Some(self.objects.len() - 1);
    }

    pub fn add_snakes(&mut self) {
        if settings::SNAKE_1_ADD { self.objects.push(Object::snake(settings::SNAKE_1_POS, settings::SNAKE_1_SETTINGS)); }
        if settings::SNAKE_2_ADD { self.objects.push(Object::snake(settings::SNAKE_2_POS, settings::SNAKE_2_SETTINGS)); }
    }

    pub fn add_air_bottles(&mut self) {
        let air_bottles = settings::AIR_BOTTLE_POS;
        let n = air_bottles.len() / 2;
        for i in range(0, n) {
            self.objects.push(Object::air_bottle([air_bottles[i * 2], air_bottles[i * 2 + 1]]));
        }
    }

    fn follow_player(&mut self, dt: f64) {
        if self.camera_pos.is_none() { return; }
        // Make camera follow player.
        let camera_pos = self.camera_pos.unwrap();
        let camera_follow_percentage = self.camera_follow_percentage.unwrap();
        let player_pos = self.player_pos();
        let (dx, dy) = (player_pos[0] - camera_pos[0], player_pos[1] - camera_pos[1]);
        let dx = camera_follow_percentage * dt * dx;
        let dy = camera_follow_percentage * dt * dy;
        self.camera_pos = Some([camera_pos[0] + dx, camera_pos[1] + dy]);
    }

    fn show_blood(&mut self) {
        if self.blood_bar_index == None { return; }
        // Show blood.
        let player_blood = self.player_blood();
        let blood_bar_index = self.blood_bar_index.unwrap();
        let blood_bar = self.objects.get_mut(blood_bar_index);
        match blood_bar.data {
            object::BarData(ref mut bar) => {
                bar.value = player_blood;
            },
            _ => {},
        }
    }

    fn show_air(&mut self) {
        if self.air_bar_index == None { return; }
        // Show air.
        let player_air = self.player_air();
        let air_bar_index = self.air_bar_index.unwrap();
        let air_bar = self.objects.get_mut(air_bar_index);
        match air_bar.data {
            object::BarData(ref mut bar) => {
                bar.value = player_air;
            },
            _ => {},
        }
    }

    fn win(&mut self) {
        let player_pos = self.player_pos();
        // When player reaches surface, win.
        if player_pos[1] >= self.surface_y.unwrap() {
            self.game_state = Some(game_state::Win);
            return;
        }
    }

    fn loose(&mut self) {
        let blood = self.player_blood();
        let air = self.player_air();
        if blood < 0.0 || air < 0.0 {
            self.game_state = Some(game_state::Loose);
            return;
        }
    }

    fn restart(&mut self) {
        *self = SnakeApp::new();
        self.load();
    }

    fn update_objects(&mut self, dt: f64) {
        if self.game_state.unwrap() != game_state::Play { return; }

        // Update states of objects.
        let player_pos = self.player_pos();
        let mut attack_damage: f64 = 0.0;
        for obj in self.objects.mut_iter() {
            match obj.update(dt, player_pos) {
                action::Passive => {},
                action::Attack(attack) => { attack_damage += attack; },
            }
        }
        // Bite player.
        self.bite_player(attack_damage);

        // Decrease the player's air with time.
        let air = self.player_air();
        self.set_player_air(air - dt * settings::PLAYER_LOOSE_AIR_SPEED);
    }

    fn player_pos(&self) -> [f64, ..2] {
        let player_index = self.player_index.unwrap();
        self.objects.get(player_index).pos
    }

    fn player_blood(&self) -> f64 {
        let player_index = self.player_index.unwrap();
        self.objects.get(player_index).blood().unwrap()
    }

    fn set_player_blood(&mut self, val: f64) {
        let player_index = self.player_index.unwrap();
        *self.objects.get_mut(player_index).blood_mut().unwrap() = val;
    }

    fn player_air(&self) -> f64 {
        let player_index = self.player_index.unwrap();
        self.objects.get(player_index).air().unwrap()
    }

    fn set_player_air(&mut self, val: f64) {
        let player_index = self.player_index.unwrap();
        *self.objects.get_mut(player_index).air_mut().unwrap() = val;
    }

    fn bite_player(&mut self, damage: f64) {
        let player_index = self.player_index.unwrap();
        self.objects.get_mut(player_index).player_mut().unwrap().bite(damage);
    }

    fn fill_air(&mut self) {
        let player_pos = self.player_pos();
        let mut air = self.player_air();
        for obj in self.objects.mut_iter() {
            let pos = obj.pos;
            match obj.air_bottle_mut() {
                None => {},
                Some(air_bottle) => {
                    let dx = pos[0] - player_pos[0];
                    let dy = pos[1] - player_pos[1];
                    let d = dx * dx + dy * dy;
                    if d <= settings::AIR_BOTTLE_RADIUS {
                        air += air_bottle.fill_up;
                        air_bottle.fill_up = 0.0;
                    }
                },
            }
        }

        air = if air > 1.0 { 1.0 } else { air };
        self.set_player_air(air);
    }
}

<commit_msg>Fixed bug with air bottle<commit_after>use action;
use settings;
use glfw;
use Settings = piston::game::Settings;
use Game = piston::game::Game;
use piston::gl::Gl;
use graphics;
use graphics::*;
use object;
use Object = object::Object;
use text;
use game_state;

pub struct SnakeApp {
    settings: Settings,
    // Tells where the surface is.
    surface_y: Option<f64>,
    game_state: Option<game_state::GameState>,
    camera_pos: Option<[f64, ..2]>,
    camera_follow_percentage: Option<f64>,
    player_index: Option<uint>,
    blood_bar_index: Option<uint>,
    air_bar_index: Option<uint>,
    // Contains the game objects.
    objects: Vec<Object>,
}

impl Game for SnakeApp {
    fn get_settings<'a>(&'a self) -> &'a Settings { &self.settings }

    fn render(&self, c: &graphics::Context, gl: &mut Gl) {
        // Get camera coordinates.
        let (cam_x, cam_y) = if self.camera_pos.is_some() {
                let camera_pos = self.camera_pos.unwrap();
                (camera_pos[0], camera_pos[1])
            } else { (0.0, 0.0) };

        // Render surface.
        let surface_y = self.surface_y.unwrap();
        c.rect(-1.0, surface_y - cam_y, 2.0, 0.05).color(settings::BLUE).fill(gl);

        // Render objects in layers.
        let cam = &c.trans(-cam_x, -cam_y);
        for i in range(0u, settings::NUMBER_OF_LAYERS) {
            for obj in self.objects.iter() {
                if obj.layer == i { obj.render(cam, c, gl); }
            }
        }

        let text_c = c.flip_v_local();
        let text_c = text_c.zoom(0.0025);
        match self.game_state.unwrap() {
            game_state::Win => {
                let pos = settings::YOU_WIN_POS;
                text::text(settings::YOU_WIN_TEXT,
                    &text_c
                    .trans(pos[0], pos[1])
                    .color(settings::YOU_WIN_TEXT_COLOR)
                , gl);
            },
            game_state::Loose => {
                let pos = settings::YOU_LOOSE_POS;
                text::text(settings::YOU_LOOSE_TEXT,
                    &text_c
                    .trans(pos[0], pos[1])
                    .color(settings::YOU_LOOSE_TEXT_COLOR)
                , gl);
            },
            game_state::Play => {

            },
        }

        // TEST
        // text::text("restart", &c.flip_v_local().zoom(0.001).color(settings::BLACK), gl);
    }

    fn update(&mut self, dt: f64) {
        self.update_objects(dt);
        self.fill_air();
        self.win();
        self.loose();
        self.show_blood();
        self.show_air();
        self.follow_player(dt);
    }

    fn load(&mut self) {
        self.camera_follow_percentage = Some(settings::CAMERA_FOLLOW_PERCENTAGE);
        self.camera_pos = Some(settings::INITIAL_CAMERA_POS);
        self.surface_y = Some(settings::SURFACE_Y);
        self.game_state = Some(settings::INITIAL_GAME_STATE);

        // Add player.
        self.objects.push(Object::player(
            settings::ORIGIN,
            settings::BLUE,
            settings::PLAYER_INITIAL_BLOOD,
            settings::PLAYER_INITIAL_AIR,
            [settings::PLAYER_ACCELERATION_LEFT, settings::PLAYER_ACCELERATION_RIGHT],
            [settings::PLAYER_ACCELERATION_UP, settings::PLAYER_ACCELERATION_DOWN]
        ));
        self.objects.push(Object::bar_background());
        self.player_index = Some(0);

        // Add blood and air bar.
        self.add_bars();

        // Add air bottles.
        self.add_air_bottles();

        // Add sharks.
        self.add_snakes();
    }

    fn key_press(&mut self, key: glfw::Key) {
        // TEST
        // println!("Key pressed {}", key);

        if self.game_state.unwrap() != game_state::Play { return; }

        match (key, self.player_index) {
            (glfw::KeyRight, Some(player_index)) => {
                self.objects.get_mut(player_index).move_right();
            },
            (glfw::KeyUp, Some(player_index)) => {
                self.objects.get_mut(player_index).move_up();
            },
            (glfw::KeyLeft, Some(player_index)) => {
                self.objects.get_mut(player_index).move_left();
            },
            (glfw::KeyDown, Some(player_index)) => {
                self.objects.get_mut(player_index).move_down();
            },
            _ => {},
        }
    }

    fn key_release(&mut self, key: glfw::Key) {
        // TEST
        // println!("Key released {}", key);

        if key == glfw::KeyEnter || key == glfw::KeySpace {
            match self.game_state.unwrap() {
                game_state::Win | game_state::Loose => self.restart(),
                _ => {},
            }
        }
    }
}

impl SnakeApp {
    pub fn new() -> SnakeApp {
        let exit_on_esc = true;
        let background_color = settings::WATER_COLOR;
        SnakeApp {
            camera_pos: None,
            camera_follow_percentage: None,
            settings: Settings::new(exit_on_esc, background_color),
            game_state: None,
            surface_y: None,
            objects: Vec::new(),
            player_index: None,
            blood_bar_index: None,
            air_bar_index: None,
        }
    }

    pub fn add_bars(&mut self) {
        self.objects.push(Object::bar(
            settings::AIR_BAR_POS,
            "air",
            settings::AIR_BAR_TEXT_COLOR,
            settings::AIR_BAR_BACKGROUND_COLOR,
            settings::AIR_BAR_BAR_COLOR,
            settings::AIR_BAR_INITIAL_VALUE
        ));
        self.air_bar_index = Some(self.objects.len() - 1);
        self.objects.push(Object::bar(
            settings::BLOOD_BAR_POS,
            "blood",
            settings::BLOOD_BAR_TEXT_COLOR,
            settings::BLOOD_BAR_BACKGROUND_COLOR,
            settings::BLOOD_BAR_BAR_COLOR,
            settings::BLOOD_BAR_INITIAL_VALUE
        ));
        self.blood_bar_index = Some(self.objects.len() - 1);
    }

    pub fn add_snakes(&mut self) {
        if settings::SNAKE_1_ADD { self.objects.push(Object::snake(settings::SNAKE_1_POS, settings::SNAKE_1_SETTINGS)); }
        if settings::SNAKE_2_ADD { self.objects.push(Object::snake(settings::SNAKE_2_POS, settings::SNAKE_2_SETTINGS)); }
    }

    pub fn add_air_bottles(&mut self) {
        let air_bottles = settings::AIR_BOTTLE_POS;
        let n = air_bottles.len() / 2;
        for i in range(0, n) {
            self.objects.push(Object::air_bottle([air_bottles[i * 2], air_bottles[i * 2 + 1]]));
        }
    }

    fn follow_player(&mut self, dt: f64) {
        if self.camera_pos.is_none() { return; }
        // Make camera follow player.
        let camera_pos = self.camera_pos.unwrap();
        let camera_follow_percentage = self.camera_follow_percentage.unwrap();
        let player_pos = self.player_pos();
        let (dx, dy) = (player_pos[0] - camera_pos[0], player_pos[1] - camera_pos[1]);
        let dx = camera_follow_percentage * dt * dx;
        let dy = camera_follow_percentage * dt * dy;
        self.camera_pos = Some([camera_pos[0] + dx, camera_pos[1] + dy]);
    }

    fn show_blood(&mut self) {
        if self.blood_bar_index == None { return; }
        // Show blood.
        let player_blood = self.player_blood();
        let blood_bar_index = self.blood_bar_index.unwrap();
        let blood_bar = self.objects.get_mut(blood_bar_index);
        match blood_bar.data {
            object::BarData(ref mut bar) => {
                bar.value = player_blood;
            },
            _ => {},
        }
    }

    fn show_air(&mut self) {
        if self.air_bar_index == None { return; }
        // Show air.
        let player_air = self.player_air();
        let air_bar_index = self.air_bar_index.unwrap();
        let air_bar = self.objects.get_mut(air_bar_index);
        match air_bar.data {
            object::BarData(ref mut bar) => {
                bar.value = player_air;
            },
            _ => {},
        }
    }

    fn win(&mut self) {
        let player_pos = self.player_pos();
        // When player reaches surface, win.
        if player_pos[1] >= self.surface_y.unwrap() {
            self.game_state = Some(game_state::Win);
            return;
        }
    }

    fn loose(&mut self) {
        let blood = self.player_blood();
        let air = self.player_air();
        if blood < 0.0 || air < 0.0 {
            self.game_state = Some(game_state::Loose);
            return;
        }
    }

    fn restart(&mut self) {
        *self = SnakeApp::new();
        self.load();
    }

    fn update_objects(&mut self, dt: f64) {
        if self.game_state.unwrap() != game_state::Play { return; }

        // Update states of objects.
        let player_pos = self.player_pos();
        let mut attack_damage: f64 = 0.0;
        for obj in self.objects.mut_iter() {
            match obj.update(dt, player_pos) {
                action::Passive => {},
                action::Attack(attack) => { attack_damage += attack; },
            }
        }
        // Bite player.
        self.bite_player(attack_damage);

        // Decrease the player's air with time.
        let air = self.player_air();
        self.set_player_air(air - dt * settings::PLAYER_LOOSE_AIR_SPEED);
    }

    fn player_pos(&self) -> [f64, ..2] {
        let player_index = self.player_index.unwrap();
        self.objects.get(player_index).pos
    }

    fn player_blood(&self) -> f64 {
        let player_index = self.player_index.unwrap();
        self.objects.get(player_index).blood().unwrap()
    }

    fn set_player_blood(&mut self, val: f64) {
        let player_index = self.player_index.unwrap();
        *self.objects.get_mut(player_index).blood_mut().unwrap() = val;
    }

    fn player_air(&self) -> f64 {
        let player_index = self.player_index.unwrap();
        self.objects.get(player_index).air().unwrap()
    }

    fn set_player_air(&mut self, val: f64) {
        let player_index = self.player_index.unwrap();
        *self.objects.get_mut(player_index).air_mut().unwrap() = val;
    }

    fn bite_player(&mut self, damage: f64) {
        let player_index = self.player_index.unwrap();
        self.objects.get_mut(player_index).player_mut().unwrap().bite(damage);
    }

    fn fill_air(&mut self) {
        let player_pos = self.player_pos();
        let mut air = self.player_air();
        for obj in self.objects.mut_iter() {
            let pos = obj.pos;
            match obj.air_bottle_mut() {
                None => {},
                Some(air_bottle) => {
                    let dx = pos[0] - player_pos[0];
                    let dy = pos[1] - player_pos[1];
                    let d = dx * dx + dy * dy;
                    if d <= settings::AIR_BOTTLE_RADIUS * settings::AIR_BOTTLE_RADIUS {
                        air += air_bottle.fill_up;
                        air_bottle.fill_up = 0.0;
                    }
                },
            }
        }

        air = if air > 1.0 { 1.0 } else { air };
        self.set_player_air(air);
    }
}

<|endoftext|>


<commit_before>#[macro_use]
mod common;

#[allow(unused_imports)]
#[cfg(unix)]
#[macro_use]
extern crate lazy_static;

#[cfg(unix)]
extern crate rust_users;

// For conditional compilation
macro_rules! unix_only {
    ($($fea:expr, $m:ident);+) => {
        $(
            #[cfg(unix)]
            #[cfg(feature = $fea)]
            mod $m;
         )+
    };
}
unix_only! {
    "chmod", test_chmod;
    "chown", test_chown;
    "chgrp", test_chgrp;
    "install", test_install;
    "pathchk", test_pathchk;
    "pinky", test_pinky;
    "stdbuf", test_stdbuf;
    "uname", test_uname;
    "unlink", test_unlink;
    "uptime", test_uptime;
    "users", test_users;
    "who", test_who;
    // Be aware of the trailing semicolon after the last item
    "stat", test_stat
}

macro_rules! generic {
    ($($fea:expr, $m:ident);+) => {
        $(
            #[cfg(feature = $fea)]
            mod $m;
         )+
    };
}
generic! {
    "arch", test_arch;
    "base32", test_base32;
    "base64", test_base64;
    "basename", test_basename;
    "cat", test_cat;
    "cksum", test_cksum;
    "comm", test_comm;
    "cp", test_cp;
    "cut", test_cut;
    "date", test_date;
    "dircolors", test_dircolors;
    "dirname", test_dirname;
    "df", test_df;
    "du", test_du;
    "echo", test_echo;
    "env", test_env;
    "expand", test_expand;
    "expr", test_expr;
    "factor", test_factor;
    "false", test_false;
    "fmt", test_fmt;
    "fold", test_fold;
    "hashsum", test_hashsum;
    "head", test_head;
    "id", test_id;
    "join", test_join;
    "link", test_link;
    "ln", test_ln;
    "ls", test_ls;
    "mkdir", test_mkdir;
    "mktemp", test_mktemp;
    "more", test_more;
    "mv", test_mv;
    "numfmt", test_numfmt;
    "nl", test_nl;
    "od", test_od;
    "paste", test_paste;
    "printenv", test_printenv;
    "printf", test_printf;
    "ptx", test_ptx;
    "pwd", test_pwd;
    "readlink", test_readlink;
    "realpath", test_realpath;
    "rm", test_rm;
    "rmdir", test_rmdir;
    "seq", test_seq;
    "sort", test_sort;
    "split", test_split;
    "sum", test_sum;
    "tac", test_tac;
    "tail", test_tail;
    "test", test_test;
    "touch", test_touch;
    "tr", test_tr;
    "true", test_true;
    "truncate", test_truncate;
    "tsort", test_tsort;
    "unexpand", test_unexpand;
    "uniq", test_uniq;
    "wc", test_wc;
    "whoami", test_whoami;
    // Be aware of the trailing semicolon after the last item
    "hostname", test_hostname
}
<commit_msg>tests ~ use build.rs build list of test_UTIL module files<commit_after>#[macro_use]
mod common;

#[allow(unused_imports)]
#[cfg(unix)]
#[macro_use]
extern crate lazy_static;

#[cfg(unix)]
extern crate rust_users;

include!(concat!(env!("OUT_DIR"), "/test_modules.rs"));
<|endoftext|>